'use client'
import AlgoliaSearchModal from '@/components/AlgoliaSearchModal'
import Comment from '@/components/Comment'
import replaceSearchResult from '@/components/Mark'
import NotionPage from '@/components/NotionPage'
import ShareBar from '@/components/ShareBar'
import { siteConfig } from '@/lib/config'
import { useGlobal } from '@/lib/global'
import { loadWowJS } from '@/lib/plugins/wow'
import { isBrowser } from '@/lib/utils'
import { Transition } from '@headlessui/react'
import { useRouter } from 'next/router'
import { createContext, useContext, useEffect, useRef, useState } from 'react'
import Announcement from './components/Announcement'
import ArchiveDateList from './components/ArchiveDateList'
import ArticleFooter from './components/ArticleFooter'
import { ArticleHeader } from './components/ArticleInfo'
import { ArticleLock } from './components/ArticleLock'
import BlogListGroupByDate from './components/BlogListGroupByDate'
import BlogRecommend from './components/BlogRecommend'
import CategoryGroup from './components/CategoryGroup'
import CategoryItem from './components/CategoryItem'
import { Footer } from './components/Footer'
import { Header } from './components/Header'
import { HomeBackgroundImage } from './components/HomeBackgroundImage'
import JumpToTopButton from './components/JumpToTopButton'
import LatestPostsGroup from './components/LatestPostsGroup'
import SlotBar from './components/SlotBar'
import Swiper from './components/Swiper'
import TagGroups from './components/TagGroups'
import TagItem from './components/TagItem'
import CONFIG from './config'
import { Style } from './style'
// 主题全局状态
const ThemeGlobalPhoto = createContext()
export const usePhotoGlobal = () => useContext(ThemeGlobalPhoto)
/**
* 基础布局框架
* 1.其它页面都嵌入在LayoutBase中
* 2.采用左右两侧布局,移动端使用顶部导航栏
* @returns {JSX.Element}
* @constructor
*/
const LayoutBase = props => {
const { children, slotTop } = props
const { onLoading, fullWidth } = useGlobal()
const collapseRef = useRef(null)
const router = useRouter()
const searchModal = useRef(null)
const [expandMenu, updateExpandMenu] = useState(false)
useEffect(() => {
loadWowJS()
}, [])
// 首页背景图
const headerSlot =
router.route === '/' &&
siteConfig('MOVIE_HOME_BACKGROUND', null, CONFIG) ? (
) : null
return (
{/* 页头 */}
{headerSlot}
{/* 主体 */}
{/* 内容 */}
{/* 嵌入模块 */}
{slotTop}
{children}
{/* 页脚 */}
{/* 搜索框 */}
{/* 回顶按钮 */}
)
}
/**
* 首页
* @param {*} props
* @returns 此主题首页就是列表
*/
const LayoutIndex = props => {
return
}
/**
* 文章列表
* @param {*} props
* @returns
*/
const LayoutPostList = props => {
return (
)
}
/**
* 文章详情页
* @param {*} props
* @returns
*/
const LayoutSlug = props => {
const { post, lock, validPassword } = props
const router = useRouter()
const waiting404 = siteConfig('POST_WAITING_TIME_FOR_404') * 1000
useEffect(() => {
// 用js 实现将页面中的多个视频聚合为一个分集的视频
function combineVideo() {
// 找到 id 为 notion-article 的元素
const notionArticle = document.querySelector('#article-wrapper #notion-article')
if (!notionArticle) return // 如果找不到对应的元素,则退出函数
// 找到所有的 .notion-asset-wrapper 元素
const assetWrappers = document.querySelectorAll('.notion-asset-wrapper')
if (!assetWrappers || assetWrappers.length === 0) return // 如果找不到对应的元素,则退出函数
// 不要重复创建
const exists = document.querySelectorAll('.video-wrapper')
if (exists && exists.length > 0) return
// 创建视频区块容器元素
const videoWrapper = document.createElement('div')
videoWrapper.className =
'video-wrapper py-1 px-3 bg-gray-100 dark:bg-white dark:text-black mx-auto'
// 创建走马灯封装容器元素
const carouselWrapper = document.createElement('div')
carouselWrapper.classList.add('notion-carousel-wrapper')
// 创建分集按钮figcaption文本的数组
const figCaptionValues = []
// 遍历所有 .notion-asset-wrapper 元素
assetWrappers.forEach((wrapper, index) => {
// 检查 .notion-asset-wrapper 元素是否有子元素 figcaption
const figCaption = wrapper.querySelector('figcaption')
// 检查 .notion-asset-wrapper 元素是否有 notion-asset-wrapper-video 或 notion-asset-wrapper-embed 类
if (
!wrapper.classList.contains('notion-asset-wrapper-video') &&
!wrapper.classList.contains('notion-asset-wrapper-embed')
)
return
if (!figCaption) return // 如果没有子元素 figcaption,则不处理该元素
// 获取 figcaption 的文本内容并添加到数组中
const figCaptionValue = figCaption
? figCaption?.textContent?.trim()
: `P-${index}`
figCaptionValues.push(figCaptionValue)
// 创建一个新的 div 元素用于包裹当前的 .notion-asset-wrapper 元素
const carouselItem = document.createElement('div')
carouselItem.classList.add('notion-carousel')
carouselItem.appendChild(wrapper)
// 如有外链、保存在data-src中
const iframe = wrapper.querySelector('iframe')
if (iframe) {
iframe?.setAttribute('data-src', iframe?.getAttribute('src'))
}
// 如果是第一个元素,设置为 active
if (index === 0) {
carouselItem.classList.add('active')
} else {
iframe?.setAttribute('src', '')
}
// 将元素添加到容器中
carouselWrapper.appendChild(carouselItem)
// 从 DOM 中移除原始的 .notion-asset-wrapper 元素
// wrapper.parentNode.removeChild(wrapper)
})
// 创建一个用于保存 figcaption 值的容器元素
const figCaptionWrapper = document.createElement('div')
figCaptionWrapper.className =
'notion-carousel-route py-2 max-h-36 overflow-y-auto'
// 遍历 figCaptionValues 数组,并将每个值添加到容器元素中
figCaptionValues.forEach(value => {
const div = document.createElement('div')
div.textContent = value
div.addEventListener('click', function () {
// 遍历所有的 carouselItem 元素
document.querySelectorAll('.notion-carousel').forEach(item => {
// 外链保存在data-src中
const iframe = item.querySelector('iframe')
// 判断当前元素是否包含该 figCaption 的文本内容,如果是则设置为 active,否则取消 active
if (item.querySelector('figcaption').textContent.trim() === value) {
item.classList.add('active')
if (iframe) {
iframe.setAttribute('src', iframe.getAttribute('data-src'))
}
} else {
item.classList.remove('active')
// 不活跃窗口暂停播放,仅支持notion上传视频、不支持外链
item.querySelectorAll('video')?.forEach(video => {
video.pause()
})
// 外链通过设置src来实现视频暂停播放
if (iframe) {
iframe.setAttribute('src', '')
}
}
})
})
figCaptionWrapper.appendChild(div)
})
if (carouselWrapper.children.length > 0) {
// 将包含 figcaption 值的容器元素添加到 notion-article 的第一个子元素插入
videoWrapper.appendChild(carouselWrapper)
// 显示分集按钮 大于1集才显示 ;或者用户 要求强制显示
if (
figCaptionWrapper.children.length > 1 ||
siteConfig('MOVIE_VIDEO_COMBINE_SHOW_PAGE_FORCE', false, CONFIG)
) {
videoWrapper.appendChild(figCaptionWrapper)
}
// 放入页面
if (
notionArticle.firstChild &&
notionArticle.contains(notionArticle.firstChild)
) {
notionArticle.insertBefore(videoWrapper, notionArticle.firstChild)
} else {
notionArticle.appendChild(videoWrapper)
}
}
}
setTimeout(() => {
combineVideo()
}, 1500)
// 404
if (!post) {
setTimeout(
() => {
if (isBrowser) {
const article = document.querySelector('#article-wrapper #notion-article')
if (!article) {
router.push('/404').then(() => {
console.warn('找不到页面', router.asPath)
})
}
}
},
waiting404
)
}
return () => {
// 获取所有 class="video-wrapper" 的元素
const videoWrappers = document.querySelectorAll('.video-wrapper')
// 遍历所有匹配的元素并移除它们
videoWrappers.forEach(wrapper => {
wrapper.parentNode.removeChild(wrapper) // 从 DOM 中移除元素
})
}
}, [post])
return (
<>
{!lock ? post && (
{/* 标题 */}
{/* 页面元素 */}
{/* 文章页脚 */}
{/* 推荐 */}
{/* 分享栏目 */}
{/* 评论区 */}
) : (
)}
>
)
}
/**
* 404页
* @param {*} props
* @returns
*/
const Layout404 = props => {
const { locale } = useGlobal()
const { searchModal } = usePhotoGlobal()
const router = useRouter()
// 展示搜索框
const toggleShowSearchInput = () => {
if (siteConfig('ALGOLIA_APP_ID')) {
searchModal.current.openSearch()
}
}
const onKeyUp = e => {
if (e.keyCode === 13) {
const search = document.getElementById('search').value
if (search) {
router.push({ pathname: '/search/' + search })
}
}
}
return (
<>
{/* 底部导航 */}
>
)
}
/**
* 搜索页
* @param {*} props
* @returns
*/
const LayoutSearch = props => {
const { keyword } = props
const router = useRouter()
useEffect(() => {
if (isBrowser) {
// 高亮搜索到的结果
const container = document.getElementById('posts-wrapper')
if (keyword && container) {
replaceSearchResult({
doms: container,
search: keyword,
target: {
element: 'span',
className: 'text-red-500 border-b border-dashed'
}
})
}
}
}, [router])
return
}
/**
* 归档列表
* @param {*} props
* @returns 按照日期将文章分组排序
*/
const LayoutArchive = props => {
const { archivePosts } = props
return (
<>
{Object.keys(archivePosts).map(archiveTitle => (
))}
>
)
}
/**
* 分类列表
* @param {*} props
* @returns
*/
const LayoutCategoryIndex = props => {
const { categoryOptions } = props
return (
<>
{categoryOptions?.map(category => (
))}
>
)
}
/**
* 标签列表
* @param {*} props
* @returns
*/
const LayoutTagIndex = props => {
const { tagOptions } = props
return (
<>
{tagOptions.map(tag => (
))}
>
)
}
export {
Layout404,
LayoutArchive,
LayoutBase,
LayoutCategoryIndex,
LayoutIndex,
LayoutPostList,
LayoutSearch,
LayoutSlug,
LayoutTagIndex,
CONFIG as THEME_CONFIG
}