mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-13 23:16:47 +00:00
group-nav-page
This commit is contained in:
@@ -13,9 +13,8 @@ const DarkModeButton = (props) => {
|
||||
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
|
||||
}
|
||||
|
||||
return <div className={'text-gray-200 z-10 duration-200 text-xl py-2 ' + props.className}>
|
||||
<i id='darkModeButton' className={`hover:scale-125 cursor-pointer transform duration-200 fas ${isDarkMode ? 'fa-sun' : 'fa-moon'}`}
|
||||
onClick={handleChangeDarkMode} />
|
||||
return <div onClick={handleChangeDarkMode} className={'px-1 dark:text-gray-200 text-gray-800 z-10 duration-200 text-xl hover:scale-125 cursor-pointer transform ' + props.className}>
|
||||
<i id='darkModeButton' className={`${isDarkMode ? 'far fa-sun' : 'far fa-moon'}`}/>
|
||||
</div>
|
||||
}
|
||||
export default DarkModeButton
|
||||
|
||||
@@ -26,6 +26,7 @@ export async function getGlobalNotionData({
|
||||
}) {
|
||||
// 获取Notion数据
|
||||
const notionPageData = deepClone(await getNotionPageData({ pageId, from }))
|
||||
notionPageData.allNavPages = getNavPages({ allPages: notionPageData.allPages })
|
||||
delete notionPageData.block
|
||||
delete notionPageData.schema
|
||||
delete notionPageData.rawMetadata
|
||||
@@ -168,6 +169,37 @@ function getSiteInfo({ collection, block }) {
|
||||
return { title, description, pageCover, icon }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导航pages
|
||||
* 转为gitbook这类文档主题设计,精减的标题和内容
|
||||
* 导航页面的条件,必须是Posts
|
||||
* @param {*} param0
|
||||
*/
|
||||
export function getNavPages({ allPages }) {
|
||||
const allNavPages = allPages.filter(post => {
|
||||
return post && post?.slug && (!post?.slug?.startsWith('http')) && post?.type === 'Post' && post?.status === 'Published'
|
||||
})
|
||||
const result = allNavPages.map(item => ({ id: item.id, title: item.title, category: item.category || null, tags: item.tags || null, summary: item.summary || null, slug: item.slug }))
|
||||
|
||||
const groupedArray = result.reduce((groups, item) => {
|
||||
const categoryName = item.category ? item.category.join('/') : '' // 将category转换为字符串
|
||||
const lastGroup = groups[groups.length - 1] // 获取最后一个分组
|
||||
|
||||
if (!lastGroup || lastGroup.category !== categoryName) { // 如果当前元素的category与上一个元素不同,则创建新分组
|
||||
groups.push({ category: categoryName, items: [] })
|
||||
}
|
||||
|
||||
groups[groups.length - 1].items.push(item) // 将元素加入对应的分组
|
||||
|
||||
return groups
|
||||
}, [])
|
||||
|
||||
return groupedArray
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公告
|
||||
*/
|
||||
async function getNotice(post) {
|
||||
if (!post) {
|
||||
return null
|
||||
@@ -183,6 +215,7 @@ const EmptyData = (pageId) => {
|
||||
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', lastEditedTime: '2023-04-24', tagItems: [] } }],
|
||||
allNavPages: [],
|
||||
collection: [],
|
||||
collectionQuery: {},
|
||||
collectionId: null,
|
||||
@@ -275,11 +308,13 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) {
|
||||
// 新的菜单
|
||||
const customMenu = await getCustomMenu({ collectionData })
|
||||
const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 5 })
|
||||
const allNavPages = getNavPages({ allPages })
|
||||
|
||||
return {
|
||||
notice,
|
||||
siteInfo,
|
||||
allPages,
|
||||
allNavPages,
|
||||
collection,
|
||||
collectionQuery,
|
||||
collectionId,
|
||||
|
||||
@@ -4,11 +4,10 @@ import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { idToUuid } from 'notion-utils'
|
||||
import { useRouter } from 'next/router'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import { getNotion } from '@/lib/notion/getNotion'
|
||||
import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
|
||||
import md5 from 'js-md5'
|
||||
import { getLayoutByTheme } from '@/themes/theme'
|
||||
import md5 from 'js-md5'
|
||||
|
||||
/**
|
||||
* 根据notion的slug访问页面
|
||||
@@ -19,16 +18,13 @@ const Slug = props => {
|
||||
const { post, siteInfo } = props
|
||||
const router = useRouter()
|
||||
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme(useRouter())
|
||||
|
||||
// 文章锁🔐
|
||||
const [lock, setLock] = useState(post?.password && post?.password !== '')
|
||||
|
||||
/**
|
||||
* 验证文章密码
|
||||
* @param {*} result
|
||||
*/
|
||||
* 验证文章密码
|
||||
* @param {*} result
|
||||
*/
|
||||
const validPassword = passInput => {
|
||||
const encrypt = md5(post.slug + passInput)
|
||||
if (passInput && encrypt === post.password) {
|
||||
@@ -41,28 +37,28 @@ const Slug = props => {
|
||||
// 文章加载
|
||||
useEffect(() => {
|
||||
// 404
|
||||
if (!post) {
|
||||
setTimeout(() => {
|
||||
if (isBrowser()) {
|
||||
const article = document.getElementById('notion-article')
|
||||
if (!article) {
|
||||
router.push('/404').then(() => {
|
||||
console.warn('找不到页面', router.asPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
}, 8 * 1000) // 404时长 8秒
|
||||
}
|
||||
// if (!post) {
|
||||
// setTimeout(() => {
|
||||
// if (isBrowser()) {
|
||||
// const article = document.getElementById('notion-article')
|
||||
// if (!article) {
|
||||
// router.push('/404').then(() => {
|
||||
// console.warn('找不到页面', router.asPath)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }, 8 * 1000) // 404时长 8秒
|
||||
// }
|
||||
|
||||
// 文章加密
|
||||
if (post?.password && post?.password !== '') {
|
||||
setLock(true)
|
||||
} else {
|
||||
setLock(false)
|
||||
if (!lock && post?.blockMap?.block) {
|
||||
post.content = Object.keys(post.blockMap.block).filter(key => post.blockMap.block[key]?.value?.parent_id === post.id)
|
||||
post.toc = getPageTableOfContents(post, post.blockMap)
|
||||
}
|
||||
setLock(false)
|
||||
}
|
||||
router.events.on('routeChangeComplete', () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
@@ -79,7 +75,8 @@ const Slug = props => {
|
||||
tags: post?.tags
|
||||
}
|
||||
props = { ...props, lock, meta, setLock, validPassword }
|
||||
|
||||
// 根据页面路径加载不同Layout文件
|
||||
const Layout = getLayoutByTheme(useRouter())
|
||||
return <Layout {...props} />
|
||||
}
|
||||
|
||||
|
||||
@@ -1142,7 +1142,6 @@ code[class*='language-'] {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@apply text-blue-600 dark:text-blue-200
|
||||
}
|
||||
|
||||
.notion-table-of-contents-item:hover {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import CommonHead from '@/components/CommonHead'
|
||||
import { useState, createContext, useContext } from 'react'
|
||||
import { useState, createContext, useContext, useEffect } from 'react'
|
||||
import Footer from './components/Footer'
|
||||
import InfoCard from './components/InfoCard'
|
||||
import RevolverMaps from './components/RevolverMaps'
|
||||
import CONFIG_MEDIUM from './config_medium'
|
||||
import Tabs from '@/components/Tabs'
|
||||
import TopNavBar from './components/TopNavBar'
|
||||
import SearchInput from './components/SearchInput'
|
||||
import BottomMenuBar from './components/BottomMenuBar'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { useRouter } from 'next/router'
|
||||
import Live2D from '@/components/Live2D'
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import ArticleInfo from './components/ArticleInfo'
|
||||
import Catalog from './components/Catalog'
|
||||
import { useRouter } from 'next/router'
|
||||
const ThemeGlobalMedium = createContext()
|
||||
|
||||
/**
|
||||
@@ -22,12 +22,16 @@ const ThemeGlobalMedium = createContext()
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const LayoutBase = props => {
|
||||
const { children, meta, posts, post, showInfoCard = true, slotLeft, slotRight, slotTop, siteInfo } = props
|
||||
const { locale } = useGlobal()
|
||||
const router = useRouter()
|
||||
const LayoutBase = (props) => {
|
||||
const { children, meta, post, allNavPages, slotLeft, slotRight, slotTop, siteInfo } = props
|
||||
const [tocVisible, changeTocVisible] = useState(false)
|
||||
const [filterPosts, setFilterPosts] = useState(allNavPages)
|
||||
const { onLoading } = useGlobal()
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
setFilterPosts(allNavPages)
|
||||
}, [post])
|
||||
|
||||
const LoadingCover = <div id='cover-loading' className={`${onLoading ? 'z-50 opacity-50' : '-z-10 opacity-0'} pointer-events-none transition-all duration-300`}>
|
||||
<div className='w-full h-screen flex justify-center items-center'>
|
||||
@@ -36,7 +40,7 @@ const LayoutBase = props => {
|
||||
</div>
|
||||
|
||||
return (
|
||||
<ThemeGlobalMedium.Provider value={{ tocVisible, changeTocVisible }}>
|
||||
<ThemeGlobalMedium.Provider value={{ tocVisible, changeTocVisible, filterPosts, setFilterPosts, allNavPages }}>
|
||||
<CommonHead meta={meta} />
|
||||
|
||||
<div id='theme-medium' className='bg-white dark:bg-hexo-black-gray w-full h-full min-h-screen justify-center dark:text-gray-300'>
|
||||
@@ -46,21 +50,21 @@ const LayoutBase = props => {
|
||||
<main id='wrapper' className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + 'relative flex justify-between w-full h-full mx-auto'}>
|
||||
|
||||
{/* 左侧推拉抽屉 */}
|
||||
<div className={`hidden xl:block border-l dark:border-transparent w-96 relative z-10 ${CONFIG_MEDIUM.RIGHT_PANEL_DARK ? 'bg-hexo-black-gray dark' : ''}`}>
|
||||
<div style={{ width: '32rem' }} className={`font-sans hidden xl:block border-r dark:border-transparent relative z-10 ${CONFIG_MEDIUM.RIGHT_PANEL_DARK ? 'bg-hexo-black-gray dark' : ''}`}>
|
||||
<div className='py-14 px-6 sticky top-0 overflow-y-scroll h-screen'>
|
||||
{slotLeft}
|
||||
|
||||
{router.pathname !== '/search' && <SearchInput className='mt-6 mb-12' />}
|
||||
<SearchInput className='my-3' />
|
||||
|
||||
{/* 所有文章列表 */}
|
||||
<BlogPostListScroll posts={posts} />
|
||||
<BlogPostListScroll posts={filterPosts} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='center-wrapper' className='flex flex-col justify-between w-full relative z-10 pt-12 min-h-screen'>
|
||||
|
||||
<div id='container-inner' className='w-full px-7 max-w-5xl justify-center mx-auto'>
|
||||
<div id='container-inner' className='w-full px-7 max-w-3xl justify-center mx-auto'>
|
||||
{slotTop}
|
||||
|
||||
{onLoading ? LoadingCover : children}
|
||||
@@ -81,15 +85,20 @@ const LayoutBase = props => {
|
||||
</div>
|
||||
|
||||
{/* 右侧侧推拉抽屉 */}
|
||||
<div className={`hidden xl:block dark:border-transparent w-96 relative z-10 ${CONFIG_MEDIUM.RIGHT_PANEL_DARK ? 'bg-hexo-black-gray dark' : ''}`}>
|
||||
<div style={{ width: '32rem' }} className={`hidden xl:block dark:border-transparent relative z-10 ${CONFIG_MEDIUM.RIGHT_PANEL_DARK ? 'bg-hexo-black-gray dark' : ''}`}>
|
||||
<div className='py-14 px-6 sticky top-0'>
|
||||
{slotRight}
|
||||
<ArticleInfo post={props?.post ? props.post : props.notice }/>
|
||||
<div className='pt-12'>
|
||||
<InfoCard {...props} />
|
||||
{CONFIG_MEDIUM.WIDGET_REVOLVER_MAPS === 'true' && <RevolverMaps />}
|
||||
<ArticleInfo post={props?.post ? props.post : props.notice} />
|
||||
|
||||
<div className='pt-6'>
|
||||
<Catalog {...props} />
|
||||
{slotRight}
|
||||
{router.route === '/' && <>
|
||||
<InfoCard {...props} />
|
||||
{CONFIG_MEDIUM.WIDGET_REVOLVER_MAPS === 'true' && <RevolverMaps />}
|
||||
<Live2D />
|
||||
</>}
|
||||
</div>
|
||||
<Live2D />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
import Announcement from './components/Announcement'
|
||||
import ArticleInfo from './components/ArticleInfo'
|
||||
|
||||
export const LayoutIndex = (props) => {
|
||||
return <LayoutBase {...props}>
|
||||
|
||||
@@ -30,7 +30,7 @@ export const LayoutSearch = (props) => {
|
||||
}
|
||||
},
|
||||
100)
|
||||
})
|
||||
}, [currentSearch])
|
||||
return <LayoutBase {...props}>
|
||||
|
||||
<div className='py-12'>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import LayoutBase from './LayoutBase'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import React from 'react'
|
||||
import Catalog from './components/Catalog'
|
||||
import { ArticleLock } from './components/ArticleLock'
|
||||
import formatDate from '@/lib/formatDate'
|
||||
import BLOG from '@/blog.config'
|
||||
import Link from 'next/link'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import CONFIG_MEDIUM from './config_medium'
|
||||
import Comment from '@/components/Comment'
|
||||
@@ -16,22 +11,14 @@ import TagItemMini from './components/TagItemMini'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
|
||||
export const LayoutSlug = (props) => {
|
||||
const { post, prev, next, siteInfo, lock, validPassword } = props
|
||||
const { locale } = useGlobal()
|
||||
|
||||
const date = formatDate(
|
||||
post?.date?.start_date || post?.createdTime,
|
||||
locale.LOCALE
|
||||
)
|
||||
|
||||
const slotRight = post?.toc && post?.toc?.length > 3 && <Catalog toc={post.toc} />
|
||||
console.log(slotRight, post, 'jhhh')
|
||||
const { post, prev, next, lock, validPassword } = props
|
||||
|
||||
if (!post) {
|
||||
return <LayoutBase {...props}/>
|
||||
}
|
||||
|
||||
return (
|
||||
<LayoutBase slogRight={slotRight} {...props} >
|
||||
<LayoutBase {...props} >
|
||||
{/* 文章锁 */}
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
|
||||
@@ -40,30 +27,8 @@ export const LayoutSlug = (props) => {
|
||||
{/* title */}
|
||||
<h1 className="text-3xl pt-12 dark:text-gray-300">{post?.title}</h1>
|
||||
|
||||
{/* meta */}
|
||||
<section className="py-2 items-center text-sm px-1">
|
||||
<div className='flex flex-wrap text-gray-500 py-1 dark:text-gray-600'>
|
||||
<span className='whitespace-nowrap'> <i className='far fa-calendar mr-2' />{date}</span>
|
||||
<span className='mx-1'>|</span>
|
||||
<span className='whitespace-nowrap mr-2'><i className='far fa-calendar-check mr-2' />{post.lastEditedTime}</span>
|
||||
<div className="hidden busuanzi_container_page_pv font-light mr-2 whitespace-nowrap">
|
||||
<i className="mr-1 fas fa-eye" /><span className="busuanzi_value_page_pv" />
|
||||
</div>
|
||||
</div>
|
||||
<Link href="/about" passHref legacyBehavior>
|
||||
<div className='flex pt-2'>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={siteInfo?.icon} className='rounded-full cursor-pointer' width={22} alt={BLOG.AUTHOR} />
|
||||
|
||||
<div className="mr-3 ml-2 my-auto text-green-500 cursor-pointer">
|
||||
{BLOG.AUTHOR}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
{/* Notion文章主体 */}
|
||||
<section id="notion-article" className="px-1 max-w-4xl">
|
||||
<section id="notion-article" className="px-1">
|
||||
{post && (<NotionPage post={post} />)}
|
||||
</section>
|
||||
|
||||
@@ -73,13 +38,13 @@ export const LayoutSlug = (props) => {
|
||||
<ShareBar post={post} />
|
||||
{/* 文章分类和标签信息 */}
|
||||
<div className='flex justify-between'>
|
||||
{CONFIG_MEDIUM.POST_DETAIL_CATEGORY && post.category && <CategoryItem category={post.category} />}
|
||||
{CONFIG_MEDIUM.POST_DETAIL_CATEGORY && post?.category && <CategoryItem category={post.category} />}
|
||||
<div>
|
||||
{CONFIG_MEDIUM.POST_DETAIL_TAG && post?.tagItems?.map(tag => <TagItemMini key={tag.name} tag={tag} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{post.type === 'Post' && <ArticleAround prev={prev} next={next} />}
|
||||
{post?.type === 'Post' && <ArticleAround prev={prev} next={next} />}
|
||||
<Comment frontMatter={post} />
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
|
||||
const BlogPostCard = ({ post, showSummary }) => {
|
||||
const BlogPostCard = ({ post }) => {
|
||||
const router = useRouter()
|
||||
const currentSelected = router.asPath.split('?')[0] === '/' + post.slug
|
||||
return (
|
||||
<div
|
||||
key={post.id}
|
||||
className="mb-6"
|
||||
>
|
||||
|
||||
<div key={post.id} className="py-0.5">
|
||||
<div className="flex flex-col w-full">
|
||||
<Link
|
||||
href={`${BLOG.SUB_PATH}/${post.slug}`}
|
||||
passHref
|
||||
className={
|
||||
'cursor-pointer font-bold hover:underline leading-tight text-gray-700 dark:text-gray-300 hover:text-green-500 dark:hover:text-green-400'
|
||||
`${currentSelected ? 'bg-gray-500 text-white' : 'text-gray-700 dark:text-gray-300 '} hover:font-bold py-0.5 px-1 text-sm cursor-pointer`
|
||||
}>
|
||||
<div>
|
||||
{post.category} - {post.title}
|
||||
</div>
|
||||
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import BlogPostCard from './BlogPostCard'
|
||||
import BlogPostListEmpty from './BlogPostListEmpty'
|
||||
import throttle from 'lodash.throttle'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useRef } from 'react'
|
||||
|
||||
/**
|
||||
* 博客列表滚动分页
|
||||
@@ -13,85 +10,19 @@ import { useRouter } from 'next/router'
|
||||
* @constructor
|
||||
*/
|
||||
const BlogPostListScroll = ({ posts = [], currentSearch }) => {
|
||||
const postsPerPage = BLOG.POSTS_PER_PAGE
|
||||
const [page, updatePage] = useState(1)
|
||||
let filteredPosts = Object.assign(posts)
|
||||
const router = useRouter()
|
||||
const searchKey = getSearchKey(router)
|
||||
if (searchKey) {
|
||||
filteredPosts = posts.filter(post => {
|
||||
const tagContent = post.tags ? post.tags.join(' ') : ''
|
||||
const searchContent = post.title + post.summary + tagContent
|
||||
return searchContent.toLowerCase().includes(searchKey.toLowerCase())
|
||||
})
|
||||
}
|
||||
const postsToShow = getPostByPage(page, filteredPosts, postsPerPage)
|
||||
|
||||
let hasMore = false
|
||||
if (filteredPosts) {
|
||||
const totalCount = filteredPosts.length
|
||||
hasMore = page * postsPerPage < totalCount
|
||||
}
|
||||
|
||||
const handleGetMore = () => {
|
||||
if (!hasMore) return
|
||||
updatePage(page + 1)
|
||||
}
|
||||
|
||||
// 监听滚动自动分页加载
|
||||
const scrollTrigger = useCallback(throttle(() => {
|
||||
const scrollS = window.scrollY + window.outerHeight
|
||||
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
|
||||
if (scrollS > clientHeight + 100) {
|
||||
handleGetMore()
|
||||
}
|
||||
}, 500))
|
||||
|
||||
// 监听滚动
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', scrollTrigger)
|
||||
return () => {
|
||||
window.removeEventListener('scroll', scrollTrigger)
|
||||
}
|
||||
})
|
||||
|
||||
const targetRef = useRef(null)
|
||||
const filteredPosts = Object.assign(posts)
|
||||
|
||||
if (!postsToShow || postsToShow.length === 0) {
|
||||
if (!filteredPosts || filteredPosts.length === 0) {
|
||||
return <BlogPostListEmpty currentSearch={currentSearch} />
|
||||
} else {
|
||||
return <div id='container' ref={targetRef} className='w-full'>
|
||||
|
||||
{/* 文章列表 */}
|
||||
<div className='space-y-1 lg:space-y-4'>
|
||||
{postsToShow?.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} showSummary={true} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* 文章列表 */}
|
||||
{filteredPosts?.map(post => (
|
||||
<BlogPostCard key={post.id} post={post} showSummary={true} />
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取从第1页到指定页码的文章
|
||||
* @param page 第几页
|
||||
* @param totalPosts 所有文章
|
||||
* @param postsPerPage 每页文章数量
|
||||
* @returns {*}
|
||||
*/
|
||||
const getPostByPage = function (page, totalPosts, postsPerPage) {
|
||||
return totalPosts.slice(
|
||||
0,
|
||||
postsPerPage * page
|
||||
)
|
||||
}
|
||||
|
||||
function getSearchKey(router) {
|
||||
if (router.query && router.query.s) {
|
||||
return router.query.s
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export default BlogPostListScroll
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import throttle from 'lodash.throttle'
|
||||
import { uuidToId } from 'notion-utils'
|
||||
import Progress from './Progress'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
|
||||
/**
|
||||
* 目录导航组件
|
||||
@@ -9,11 +9,9 @@ import Progress from './Progress'
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const Catalog = ({ toc }) => {
|
||||
const Catalog = ({ post }) => {
|
||||
const tocIds = []
|
||||
|
||||
// 目录自动滚动
|
||||
const tRef = useRef(null)
|
||||
const toc = post?.toc
|
||||
// 同步选中目录事件
|
||||
const [activeSection, setActiveSection] = useState(null)
|
||||
|
||||
@@ -24,7 +22,7 @@ const Catalog = ({ toc }) => {
|
||||
return () => {
|
||||
window.removeEventListener('scroll', actionSectionScrollSpy)
|
||||
}
|
||||
}, [])
|
||||
}, [post])
|
||||
|
||||
const throttleMs = 200
|
||||
const actionSectionScrollSpy = useCallback(throttle(() => {
|
||||
@@ -51,19 +49,18 @@ const Catalog = ({ toc }) => {
|
||||
}
|
||||
setActiveSection(currentSectionId)
|
||||
const index = tocIds.indexOf(currentSectionId) || 0
|
||||
tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' })
|
||||
if (isBrowser()) {
|
||||
document?.getElementById('toc-wrapper')?.scrollTo({ top: 28 * index, behavior: 'smooth' })
|
||||
}
|
||||
}, throttleMs))
|
||||
|
||||
// 无目录就直接返回空
|
||||
if (!toc || toc.length < 1) {
|
||||
return <></>
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className='px-3'>
|
||||
<div className='w-full mt-2 mb-4'>
|
||||
<Progress />
|
||||
</div>
|
||||
<div className='overflow-y-auto max-h-96 overscroll-none scroll-hidden' ref={tRef}>
|
||||
return <>
|
||||
<div id='toc-wrapper' className='overflow-y-auto max-h-96 overscroll-none scroll-hidden'>
|
||||
<nav className='h-full text-black'>
|
||||
{toc.map((tocItem) => {
|
||||
const id = uuidToId(tocItem.id)
|
||||
@@ -76,7 +73,7 @@ const Catalog = ({ toc }) => {
|
||||
notion-table-of-contents-item-indent-level-${tocItem.indentLevel} `}
|
||||
>
|
||||
<span style={{ display: 'inline-block', marginLeft: tocItem.indentLevel * 16 }}
|
||||
className={`${activeSection === id && ' font-bold text-green-500 underline'}`}
|
||||
className={`${activeSection === id && ' font-bold text-gray-500 underline'}`}
|
||||
>
|
||||
{tocItem.text}
|
||||
</span>
|
||||
@@ -85,7 +82,7 @@ const Catalog = ({ toc }) => {
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
export default Catalog
|
||||
|
||||
@@ -5,7 +5,7 @@ import DarkModeButton from '@/components/DarkModeButton'
|
||||
const Footer = ({ title }) => {
|
||||
const d = new Date()
|
||||
const currentYear = d.getFullYear()
|
||||
const copyrightDate = (function() {
|
||||
const copyrightDate = (function () {
|
||||
if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) {
|
||||
return BLOG.SINCE + '-' + currentYear
|
||||
}
|
||||
@@ -13,22 +13,26 @@ const Footer = ({ title }) => {
|
||||
})()
|
||||
|
||||
return (
|
||||
<footer
|
||||
className='z-10 dark:bg-hexo-black-gray flex-shrink-0 justify-center text-center mx-auto w-full leading-6 text-sm px-6 pb-6 relative'
|
||||
>
|
||||
<DarkModeButton/>
|
||||
<i className='fas fa-copyright' /> {`${copyrightDate}`} <span><i className='mx-1 animate-pulse fas fa-heart'/> <a href={BLOG.LINK} className='underline font-bold text-gray-500 dark:text-gray-300 '>{BLOG.AUTHOR}</a>.<br/>
|
||||
<footer
|
||||
className='max-w-3xl z-10 dark:bg-hexo-black-gray flex-shrink-0 justify-center text-center mx-auto w-full leading-6 text-sm px-6 pb-6 relative'
|
||||
>
|
||||
<hr />
|
||||
|
||||
{BLOG.BEI_AN && <><i className='fas fa-shield-alt'/> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br/></>}
|
||||
<div className='flex justify-between'>
|
||||
<div className='flex'>© {`${copyrightDate}`} <div><i className='mx-1 animate-pulse fas fa-heart' /> <a href={BLOG.LINK} className='underline font-bold text-gray-500 dark:text-gray-300 '>{BLOG.AUTHOR}</a>.<br /></div></div>
|
||||
<div className='text-xs font-serif'><a href='https://github.com/tangly1024/NotionNext' className='underline text-gray-500 dark:text-gray-300'>NotionNext {BLOG.VERSION}</a></div>
|
||||
</div>
|
||||
|
||||
<span className='hidden busuanzi_container_site_pv'>
|
||||
<i className='fas fa-eye'/><span className='px-1 busuanzi_value_site_pv'> </span> </span>
|
||||
<span className='pl-2 hidden busuanzi_container_site_uv'>
|
||||
<i className='fas fa-users'/> <span className='px-1 busuanzi_value_site_uv'> </span> </span>
|
||||
<br/>
|
||||
<h1>{title}</h1>
|
||||
<span className='text-xs font-serif'>Powered by <a href='https://github.com/tangly1024/NotionNext' className='underline text-gray-500 dark:text-gray-300'>NotionNext {BLOG.VERSION}</a>.</span></span>
|
||||
</footer>
|
||||
{BLOG.BEI_AN && <><i className='fas fa-shield-alt' /> <a href='https://beian.miit.gov.cn/' className='mr-2'>{BLOG.BEI_AN}</a><br /></>}
|
||||
|
||||
<span className='hidden busuanzi_container_site_pv'>
|
||||
<i className='fas fa-eye' /><span className='px-1 busuanzi_value_site_pv'> </span> </span>
|
||||
<span className='pl-2 hidden busuanzi_container_site_uv'>
|
||||
<i className='fas fa-users' /> <span className='px-1 busuanzi_value_site_uv'> </span> </span>
|
||||
<br />
|
||||
<h1>{title}</h1>
|
||||
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export const MenuItemDrop = ({ link }) => {
|
||||
return <li className='cursor-pointer list-none items-center flex mx-2' onMouseOver={() => changeShow(true)} onMouseOut={() => changeShow(false)} >
|
||||
|
||||
{hasSubMenu &&
|
||||
<div className={'px-3 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
<div className={'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')}>
|
||||
<div>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
@@ -27,7 +27,7 @@ export const MenuItemDrop = ({ link }) => {
|
||||
}
|
||||
|
||||
{!hasSubMenu &&
|
||||
<div className={'px-3 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
<div className={'px-2 h-full whitespace-nowrap duration-300 text-sm justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
|
||||
(selected ? 'bg-green-600 text-white hover:text-white' : 'hover:text-green-600')}>
|
||||
<Link href={link?.to}>
|
||||
{link?.icon && <i className={link?.icon} />} {link?.name}
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function RevolverMaps () {
|
||||
initRevolverMaps()
|
||||
changeLoad(true)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
return <div id="revolvermaps" className='p-4'/>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { useImperativeHandle, useRef, useState } from 'react'
|
||||
import { useMediumGlobal } from '../LayoutBase'
|
||||
let lock = false
|
||||
|
||||
const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
|
||||
const [onLoading, setLoadingState] = useState(false)
|
||||
const router = useRouter()
|
||||
const SearchInput = ({ currentSearch, cRef, className }) => {
|
||||
const searchInputRef = useRef()
|
||||
const { setFilterPosts, allNavPages } = useMediumGlobal()
|
||||
|
||||
useImperativeHandle(cRef, () => {
|
||||
return {
|
||||
focus: () => {
|
||||
@@ -15,15 +15,43 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
|
||||
})
|
||||
|
||||
const handleSearch = () => {
|
||||
const key = searchInputRef.current.value
|
||||
|
||||
if (key && key !== '') {
|
||||
setLoadingState(true)
|
||||
location.href = '/search/' + key
|
||||
let keyword = searchInputRef.current.value
|
||||
const filterPosts = []
|
||||
if (keyword) {
|
||||
keyword = keyword.trim()
|
||||
} else {
|
||||
router.push({ pathname: '/' }).then(r => {
|
||||
})
|
||||
setFilterPosts(allNavPages)
|
||||
}
|
||||
for (const post of allNavPages) {
|
||||
const tagContent = post.tags && Array.isArray(post.tags) ? post.tags.join(' ') : ''
|
||||
const categoryContent = post.category && Array.isArray(post.category) ? post.category.join(' ') : ''
|
||||
const articleInfo = post.title + post.summary + tagContent + categoryContent
|
||||
let hit = articleInfo.toLowerCase().indexOf(keyword) > -1
|
||||
const indexContent = [post.summary]
|
||||
// console.log('全文搜索缓存', cacheKey, page != null)
|
||||
post.results = []
|
||||
let hitCount = 0
|
||||
for (const i in indexContent) {
|
||||
const c = indexContent[i]
|
||||
if (!c) {
|
||||
continue
|
||||
}
|
||||
const index = c.toLowerCase().indexOf(keyword.toLowerCase())
|
||||
if (index > -1) {
|
||||
hit = true
|
||||
hitCount += 1
|
||||
post.results.push(c)
|
||||
} else {
|
||||
if ((post.results.length - 1) / hitCount < 3 || i === 0) {
|
||||
post.results.push(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hit) {
|
||||
filterPosts.push(post)
|
||||
}
|
||||
}
|
||||
setFilterPosts(filterPosts)
|
||||
}
|
||||
const handleKeyUp = (e) => {
|
||||
if (e.keyCode === 13) { // 回车
|
||||
@@ -72,7 +100,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
|
||||
|
||||
<div className='-ml-8 cursor-pointer float-right items-center justify-center py-2'
|
||||
onClick={handleSearch}>
|
||||
<i className={`hover:text-black transform duration-200 text-gray-500 dark:hover:text-gray-300 cursor-pointer fas ${onLoading ? 'fa-spinner animate-spin' : 'fa-search'} `} />
|
||||
<i className={'hover:text-black transform duration-200 text-gray-500 dark:hover:text-gray-300 cursor-pointer fas fa-search'} />
|
||||
</div>
|
||||
|
||||
{(showClean &&
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useGlobal } from '@/lib/global'
|
||||
import CONFIG_MEDIUM from '../config_medium'
|
||||
import BLOG from '@/blog.config'
|
||||
import { MenuItemDrop } from './MenuItemDrop'
|
||||
import DarkModeButton from '@/components/DarkModeButton'
|
||||
|
||||
/**
|
||||
* 顶部导航栏 + 菜单
|
||||
@@ -67,6 +68,7 @@ export default function TopNavBar(props) {
|
||||
{/* 桌面端顶部菜单 */}
|
||||
<div className='hidden md:flex'>
|
||||
{links && links?.map(link => <MenuItemDrop key={link?.id} link={link}/>)}
|
||||
<DarkModeButton className='text-sm flex items-center h-full' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user