diff --git a/lib/db/getSiteData.js b/lib/db/getSiteData.js index 85dd0c7a..997a97c6 100755 --- a/lib/db/getSiteData.js +++ b/lib/db/getSiteData.js @@ -25,10 +25,7 @@ export { getPost } from '../notion/getNotionPost' * @returns * */ -export async function getGlobalData({ - pageId = BLOG.NOTION_PAGE_ID, - from -}) { +export async function getGlobalData({ pageId = BLOG.NOTION_PAGE_ID, from }) { // 从notion获取 const data = await getNotionPageData({ pageId, from }) const db = deepClone(data) @@ -89,7 +86,9 @@ function cleanBlock(post) { * @returns */ function getLatestPosts({ allPages, from, latestPostCount }) { - const allPosts = allPages?.filter(page => page.type === 'Post' && page.status === 'Published') + const allPosts = allPages?.filter( + page => page.type === 'Post' && page.status === 'Published' + ) const latestPosts = Object.create(allPosts).sort((a, b) => { const dateA = new Date(a?.lastEditedDate || a?.publishDate) @@ -140,7 +139,13 @@ function getCustomNav({ allPages }) { p.to = '/' + p.slug } } - customNav.push({ icon: p.icon || null, name: p.title, to: p.slug, target: '_blank', show: true }) + customNav.push({ + icon: p.icon || null, + name: p.title, + to: p.slug, + target: '_blank', + show: true + }) }) } return customNav @@ -152,7 +157,12 @@ function getCustomNav({ allPages }) { * @returns */ function getCustomMenu({ collectionData }) { - const menuPages = collectionData.filter(post => post.status === 'Published' && (post?.type === BLOG.NOTION_PROPERTY_NAME.type_menu || post?.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu)) + const menuPages = collectionData.filter( + post => + post.status === 'Published' && + (post?.type === BLOG.NOTION_PROPERTY_NAME.type_menu || + post?.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu) + ) const menus = [] if (menuPages && menuPages.length > 0) { menuPages.forEach(e => { @@ -190,7 +200,9 @@ function getCustomMenu({ collectionData }) { */ function getTagOptions(schema) { if (!schema) return {} - const tagSchema = Object.values(schema).find(e => e.name === BLOG.NOTION_PROPERTY_NAME.tags) + const tagSchema = Object.values(schema).find( + e => e.name === BLOG.NOTION_PROPERTY_NAME.tags + ) return tagSchema?.options || [] } @@ -201,7 +213,9 @@ function getTagOptions(schema) { */ function getCategoryOptions(schema) { if (!schema) return {} - const categorySchema = Object.values(schema).find(e => e.name === BLOG.NOTION_PROPERTY_NAME.category) + const categorySchema = Object.values(schema).find( + e => e.name === BLOG.NOTION_PROPERTY_NAME.category + ) return categorySchema?.options || [] } @@ -213,9 +227,15 @@ function getCategoryOptions(schema) { */ function getSiteInfo({ collection, block }) { const title = collection?.name?.[0][0] || BLOG.TITLE - const description = collection?.description ? Object.assign(collection).description[0][0] : BLOG.DESCRIPTION - const pageCover = collection?.cover ? mapImgUrl(collection?.cover, block[idToUuid(BLOG.NOTION_PAGE_ID)]?.value) : BLOG.HOME_BANNER_IMAGE - let icon = collection?.icon ? mapImgUrl(collection?.icon, collection, 'collection') : BLOG.AVATAR + const description = collection?.description + ? Object.assign(collection).description[0][0] + : BLOG.DESCRIPTION + const pageCover = collection?.cover + ? mapImgUrl(collection?.cover, block[idToUuid(BLOG.NOTION_PAGE_ID)]?.value) + : BLOG.HOME_BANNER_IMAGE + let icon = collection?.icon + ? mapImgUrl(collection?.icon, collection, 'collection') + : BLOG.AVATAR // 用户头像压缩一下 icon = compressImage(icon) @@ -236,7 +256,13 @@ function getSiteInfo({ collection, block }) { */ export function getNavPages({ allPages }) { const allNavPages = allPages?.filter(post => { - return post && post?.slug && (!post?.slug?.startsWith('http')) && post?.type === 'Post' && post?.status === 'Published' + return ( + post && + post?.slug && + !post?.slug?.startsWith('http') && + post?.type === 'Post' && + post?.status === 'Published' + ) }) return allNavPages.map(item => ({ @@ -248,7 +274,8 @@ export function getNavPages({ allPages }) { summary: item.summary || null, slug: item.slug, pageIcon: item.pageIcon || '', - lastEditedDate: item.lastEditedDate + lastEditedDate: item.lastEditedDate, + publishDate: item.publishDate })) } @@ -265,19 +292,26 @@ async function getNotice(post) { } // 没有数据时返回 -const EmptyData = (pageId) => { +const EmptyData = pageId => { const empty = { notice: null, siteInfo: getSiteInfo({}), - allPages: [{ - id: 1, - title: `无法获取Notion数据,请检查Notion_ID: \n 当前 ${pageId}`, - summary: '访问文档获取帮助→ https://tangly1024.com/article/vercel-deploy-notion-next', - status: 'Published', - type: 'Post', - slug: '13a171332816461db29d50e9f575b00d', - date: { start_date: '2023-04-24', lastEditedDay: '2023-04-24', tagItems: [] } - }], + allPages: [ + { + id: 1, + title: `无法获取Notion数据,请检查Notion_ID: \n 当前 ${pageId}`, + summary: + '访问文档获取帮助→ https://tangly1024.com/article/vercel-deploy-notion-next', + status: 'Published', + type: 'Post', + slug: '13a171332816461db29d50e9f575b00d', + date: { + start_date: '2023-04-24', + lastEditedDay: '2023-04-24', + tagItems: [] + } + } + ], allNavPages: [], collection: [], collectionQuery: {}, @@ -313,7 +347,8 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { const rawMetadata = block[pageId]?.value // Check Type Page-Database和Inline-Database if ( - rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view' + rawMetadata?.type !== 'collection_view_page' && + rawMetadata?.type !== 'collection_view' ) { console.error(`pageId "${pageId}" is not a database`) return EmptyData(pageId) @@ -328,9 +363,21 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { const viewIds = rawMetadata?.view_ids const collectionData = [] - const pageIds = getAllPageIds(collectionQuery, collectionId, collectionView, viewIds) + const pageIds = getAllPageIds( + collectionQuery, + collectionId, + collectionView, + viewIds + ) if (pageIds?.length === 0) { - console.error('获取到的文章列表为空,请检查notion模板', collectionQuery, collection, collectionView, viewIds, pageRecordMap) + console.error( + '获取到的文章列表为空,请检查notion模板', + collectionQuery, + collection, + collectionView, + viewIds, + pageRecordMap + ) } else { // console.log('有效Page数量', pageIds?.length) } @@ -343,7 +390,14 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { // 如果找不到文章对应的block,说明发生了溢出,使用pageID再去请求 const pageBlock = await getSingleBlock(id, from) if (pageBlock.block[id].value) { - const properties = (await getPageProperties(id, pageBlock.block[id].value, schema, null, getTagOptions(schema))) || null + const properties = + (await getPageProperties( + id, + pageBlock.block[id].value, + schema, + null, + getTagOptions(schema) + )) || null if (properties) { collectionData.push(properties) } @@ -351,7 +405,14 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { continue } - const properties = (await getPageProperties(id, value, schema, null, getTagOptions(schema))) || null + const properties = + (await getPageProperties( + id, + value, + schema, + null, + getTagOptions(schema) + )) || null if (properties) { collectionData.push(properties) } @@ -365,13 +426,16 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { if (post?.type === 'Post' && post.status === 'Published') { postCount++ } - return post && post?.slug && - (!post?.slug?.startsWith('http')) && + return ( + post && + post?.slug && + !post?.slug?.startsWith('http') && (post?.status === 'Invisible' || post?.status === 'Published') + ) }) // 站点配置优先读取配置表格,否则读取blog.config.js 文件 - const NOTION_CONFIG = await getConfigMapFromConfigPage(collectionData) || {} + const NOTION_CONFIG = (await getConfigMapFromConfigPage(collectionData)) || {} // Sort by date if (BLOG.POSTS_SORT_BY === 'date') { @@ -380,13 +444,27 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { }) } - const notice = await getNotice(collectionData.filter(post => { - return post && post?.type && post?.type === 'Notice' && post.status === 'Published' - })?.[0]) - const categoryOptions = getAllCategories({ allPages, categoryOptions: getCategoryOptions(schema) }) + const notice = await getNotice( + collectionData.filter(post => { + return ( + post && + post?.type && + post?.type === 'Notice' && + post.status === 'Published' + ) + })?.[0] + ) + const categoryOptions = getAllCategories({ + allPages, + categoryOptions: getCategoryOptions(schema) + }) const tagOptions = getAllTags({ allPages, tagOptions: getTagOptions(schema) }) // 旧的菜单 - const customNav = getCustomNav({ allPages: collectionData.filter(post => post?.type === 'Page' && post.status === 'Published') }) + const customNav = getCustomNav({ + allPages: collectionData.filter( + post => post?.type === 'Page' && post.status === 'Published' + ) + }) // 新的菜单 const customMenu = await getCustomMenu({ collectionData }) const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 6 }) diff --git a/themes/movie/components/ArchiveDateList.js b/themes/movie/components/ArchiveDateList.js new file mode 100644 index 00000000..cd389028 --- /dev/null +++ b/themes/movie/components/ArchiveDateList.js @@ -0,0 +1,38 @@ +import { useGlobal } from '@/lib/global' +import { formatDateFmt } from '@/lib/utils/formatDate' +import Link from 'next/link' + +export default function ArchiveDateList(props) { + const postsSortByDate = Object.create(props.allNavPages) + const { locale } = useGlobal() + + postsSortByDate.sort((a, b) => { + return b?.publishDate - a?.publishDate + }) + + let dates = [] + postsSortByDate.forEach(post => { + const date = formatDateFmt(post.publishDate, 'yyyy-MM') + if (!dates[date]) { + dates.push(date) + } + }) + dates = dates.slice(0, 5) + return ( +
+
{locale.NAV.ARCHIVE}
+ {dates?.map((date, index) => { + return ( +
+ + {date} + +
+ ) + })} +
+ ) +} diff --git a/themes/movie/components/CategoryGroup.js b/themes/movie/components/CategoryGroup.js new file mode 100644 index 00000000..63c3917c --- /dev/null +++ b/themes/movie/components/CategoryGroup.js @@ -0,0 +1,43 @@ +import { siteConfig } from '@/lib/config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' + +const CategoryGroup = props => { + const { currentCategory, categoryOptions } = props + const { locale } = useGlobal() + if (!categoryOptions || categoryOptions.length === 0) return <> + const categoryCount = siteConfig('PREVIEW_CATEGORY_COUNT') + const categories = categoryOptions.slice(0, categoryCount) + return ( + <> +
+

{locale.COMMON.CATEGORY}

+
+ {categories.map(category => { + const selected = currentCategory === category.name + return ( + + + {category.name}({category.count}) + + ) + })} +
+
+ + ) +} + +export default CategoryGroup diff --git a/themes/movie/components/Header.js b/themes/movie/components/Header.js index 5a8864d8..ae5e3a2a 100644 --- a/themes/movie/components/Header.js +++ b/themes/movie/components/Header.js @@ -8,7 +8,7 @@ import { MenuListTop } from './MenuListTop' */ export const Header = props => { return ( -
+
{ {siteConfig('TITLE')}
- {/* 右侧文字 */} + {/* 右侧菜单 */}
diff --git a/themes/movie/components/LatestPostsGroup.js b/themes/movie/components/LatestPostsGroup.js new file mode 100644 index 00000000..b7ada599 --- /dev/null +++ b/themes/movie/components/LatestPostsGroup.js @@ -0,0 +1,62 @@ +import { siteConfig } from '@/lib/config' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { checkContainHttp, sliceUrlFromHttp } from '@/lib/utils' + +/** + * 最新文章列表 + * @param posts 所有文章数据 + * @param sliceCount 截取展示的数量 默认6 + * @constructor + */ +const LatestPostsGroup = ({ latestPosts }) => { + // 获取当前路径 + const currentPath = useRouter().asPath + const { locale } = useGlobal() + + if (!latestPosts) { + return <> + } + + return ( +
+
+
+ + {locale.COMMON.LATEST_POSTS} +
+
+ + {latestPosts.map(post => { + const selected = + currentPath === `${siteConfig('SUB_PATH', '')}/${post.slug}` + const url = checkContainHttp(post.slug) + ? sliceUrlFromHttp(post.slug) + : `${siteConfig('SUB_PATH', '')}/${post.slug}` + return ( + +
+
  • {post.title}
  • +
    + + ) + })} +
    + ) +} +export default LatestPostsGroup diff --git a/themes/movie/components/MenuItemDrop.js b/themes/movie/components/MenuItemDrop.js index 070c9d15..501140a4 100644 --- a/themes/movie/components/MenuItemDrop.js +++ b/themes/movie/components/MenuItemDrop.js @@ -40,19 +40,19 @@ export const MenuItemDrop = ({ link }) => { {hasSubMenu && (
      {link.subMenus.map((sLink, index) => { return (
    • - + {link?.icon &&   } {sLink.title} diff --git a/themes/movie/components/MenuListTop.js b/themes/movie/components/MenuListTop.js index f5d2c9cd..d79b2ab5 100644 --- a/themes/movie/components/MenuListTop.js +++ b/themes/movie/components/MenuListTop.js @@ -3,14 +3,32 @@ import CONFIG from '../config' import { siteConfig } from '@/lib/config' import { MenuItemDrop } from './MenuItemDrop' -export const MenuListTop = (props) => { +export const MenuListTop = props => { const { customNav, customMenu } = props const { locale } = useGlobal() let links = [ - { id: 1, icon: 'fa-solid fa-house', name: locale.NAV.INDEX, to: '/', show: siteConfig('HEXO_MENU_INDEX', null, CONFIG) }, - { id: 2, icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: siteConfig('HEXO_MENU_SEARCH', null, CONFIG) }, - { id: 3, icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: siteConfig('HEXO_MENU_ARCHIVE', null, CONFIG) } + { + id: 1, + icon: 'fa-solid fa-house', + name: locale.NAV.INDEX, + to: '/', + show: siteConfig('HEXO_MENU_INDEX', null, CONFIG) + }, + { + id: 2, + icon: 'fas fa-search', + name: locale.NAV.SEARCH, + to: '/search', + show: siteConfig('HEXO_MENU_SEARCH', null, CONFIG) + }, + { + id: 3, + icon: 'fas fa-archive', + name: locale.NAV.ARCHIVE, + to: '/archive', + show: siteConfig('HEXO_MENU_ARCHIVE', null, CONFIG) + } // { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: siteConfig('MENU_CATEGORY', null, CONFIG) }, // { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: siteConfig('MENU_TAG', null, CONFIG) } ] @@ -34,9 +52,14 @@ export const MenuListTop = (props) => { return null } - return (<> - - ) + return ( + <> + + + ) } diff --git a/themes/movie/components/TagGroups.js b/themes/movie/components/TagGroups.js new file mode 100644 index 00000000..597cddc1 --- /dev/null +++ b/themes/movie/components/TagGroups.js @@ -0,0 +1,51 @@ +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useRouter } from 'next/router' + +/** + * 标签组 + * @param tags + * @param currentTag + * @returns {JSX.Element} + * @constructor + */ +const TagGroups = ({ tagOptions, className }) => { + const router = useRouter() + const { locale } = useGlobal() + const { tag: currentTag } = router.query + if (!tagOptions) return <> + + return ( +
      +
      {locale.COMMON.TAGS}
      +
      + {tagOptions.map((tag, index) => { + const selected = currentTag === tag.name + return ( + +
      +
      {tag.name}
      + {tag.count ? ( + {tag.count} + ) : ( + <> + )} +
      + + ) + })} +
      +
      + ) +} + +export default TagGroups diff --git a/themes/movie/components/TagItemMini.js b/themes/movie/components/TagItemMini.js index e2dcc69f..e5d29782 100644 --- a/themes/movie/components/TagItemMini.js +++ b/themes/movie/components/TagItemMini.js @@ -6,11 +6,9 @@ const TagItemMini = ({ tag, selected = false }) => { key={tag} href={selected ? '/' : `/tag/${encodeURIComponent(tag.name)}`} passHref - className={ - 'inline-block text-md font-extrabold rounded-xl text-shadow py-0.5 mr-2 text-[#2EBF8B]' - } + className={'inline-block rounded-xl py-0.5 mr-2 text-[#2EBF8B]'} > -
      +
      {selected && }{' '} {tag.name + (tag.count ? `(${tag.count})` : '')}{' '}
      diff --git a/themes/movie/index.js b/themes/movie/index.js index 15034d73..2aaa95da 100644 --- a/themes/movie/index.js +++ b/themes/movie/index.js @@ -1,7 +1,7 @@ 'use client' import CONFIG from './config' -import { useEffect } from 'react' +import { createContext, useContext, useEffect, useRef } from 'react' import { Header } from './components/Header' import { Nav } from './components/Nav' import { Footer } from './components/Footer' @@ -26,6 +26,16 @@ import { useRouter } from 'next/router' import { Transition } from '@headlessui/react' import { Style } from './style' import { siteConfig } from '@/lib/config' +import AlgoliaSearchModal from '@/components/AlgoliaSearchModal' +import LatestPostsGroup from './components/LatestPostsGroup' +import CategoryGroup from './components/CategoryGroup' +import { formatDateFmt } from '@/lib/utils/formatDate' +import ArchiveDateList from './components/ArchiveDateList' +import TagGroups from './components/TagGroups' + +// 主题全局状态 +const ThemeGlobalMovie = createContext() +export const useMovieGlobal = () => useContext(ThemeGlobalMovie) /** * 基础布局框架 @@ -35,32 +45,11 @@ import { siteConfig } from '@/lib/config' * @constructor */ const LayoutBase = props => { - const { children } = props + const { children, slotTop } = props const { onLoading, fullWidth } = useGlobal() const router = useRouter() const { category, tag } = props - // 顶部如果是按照分类或标签查看文章列表,列表顶部嵌入一个横幅 - // 如果是搜索,则列表顶部嵌入 搜索框 - let slotTop = null - if (category) { - slotTop = ( -
      - - {category} -
      - ) - } else if (tag) { - slotTop =
      #{tag}
      - } else if (props.slotTop) { - slotTop = props.slotTop - } else if (router.route === '/search') { - // 嵌入一个搜索框在顶部 - slotTop = ( -
      - -
      - ) - } + const searchModal = useRef(null) // 增加一个状态以触发 Transition 组件的动画 // const [showTransition, setShowTransition] = useState(true) @@ -71,58 +60,63 @@ const LayoutBase = props => { // }, [onLoading]) return ( -
      -