diff --git a/blog.config.js b/blog.config.js index 72752320..df1cd74c 100644 --- a/blog.config.js +++ b/blog.config.js @@ -25,10 +25,10 @@ const BLOG = { // 社交链接,不需要可留空白,例如 CONTACT_WEIBO:'' CONTACT_EMAIL: 'tlyong1992@hotmail.com', - CONTACT_WEIBO: 'https://weibo.com/tangly1024', - CONTACT_TWITTER: 'https://twitter.com/troy1024_1', + CONTACT_WEIBO: '', + CONTACT_TWITTER: '', CONTACT_GITHUB: 'https://github.com/tangly1024', - CONTACT_TELEGRAM: 'https://t.me/tangly_1024', + CONTACT_TELEGRAM: '', // 评论互动 可同时开启 CUSDIS UTTERRANCES GITALK COMMENT_CUSDIS_APP_ID: process.env.NEXT_PUBLIC_COMMENT_CUSDIS_APP_ID || '', // data-app-id 36位 see https://cusdis.com/ diff --git a/components/Comment.js b/components/Comment.js index fcb7879b..a1cc1c04 100644 --- a/components/Comment.js +++ b/components/Comment.js @@ -1,7 +1,6 @@ import BLOG from '@/blog.config' import dynamic from 'next/dynamic' import { useRouter } from 'next/router' -import { useGlobal } from '@/lib/global' import 'gitalk/dist/gitalk.css' import Tabs from '@/components/Tabs' @@ -19,30 +18,25 @@ const UtterancesComponent = dynamic( ) const CusdisComponent = dynamic( () => { - return import('react-cusdis').then(m => m.ReactCusdis) + return import('@/components/Cusdis') }, { ssr: false } ) const Comment = ({ frontMatter }) => { const router = useRouter() - const { theme } = useGlobal() return ( -
Slug - {post?.title}
diff --git a/themes/Hexo/LayoutBase.js b/themes/Hexo/LayoutBase.js index 26f55831..5f7e5b16 100644 --- a/themes/Hexo/LayoutBase.js +++ b/themes/Hexo/LayoutBase.js @@ -44,10 +44,10 @@ const LayoutBase = (props) => { {headerSlot} -
+
-
- {children}
+
+ {children}
diff --git a/themes/Hexo/LayoutSlug.js b/themes/Hexo/LayoutSlug.js
index c4d5ce7e..5800b844 100644
--- a/themes/Hexo/LayoutSlug.js
+++ b/themes/Hexo/LayoutSlug.js
@@ -1,6 +1,7 @@
import BLOG from '@/blog.config'
import formatDate from '@/lib/formatDate'
import { useGlobal } from '@/lib/global'
+import { getPageTableOfContents } from 'notion-utils'
import { faFolderOpen } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Link from 'next/link'
@@ -13,8 +14,10 @@ import 'prismjs/components/prism-python'
import 'prismjs/components/prism-typescript'
import CONFIG_NEXT from '../NEXT/config_next'
import ArticleDetail from './components/ArticleDetail'
-import Card from './components/Card'
import LayoutBase from './LayoutBase'
+import TocDrawerButton from './components/TocDrawerButton'
+import { useRef } from 'react'
+import TocDrawer from './components/TocDrawer'
export const LayoutSlug = props => {
const { post } = props
@@ -31,10 +34,16 @@ export const LayoutSlug = props => {
locale.LOCALE
)
- const headerSlot = (
-
+ if (post?.blockMap?.block) {
+ post.content = Object.keys(post.blockMap.block)
+ post.toc = getPageTableOfContents(post, post.blockMap)
+ }
+ const headerSlot = (
+
{/* 文章Title */}
@@ -52,7 +61,8 @@ export const LayoutSlug = props => {
|
- {post.type[0] !== 'Page' && (<>
+ {post.type[0] !== 'Page' && (
+ <>
{
{date}
- >)}
+ >
+ )}
|
- 次访问
+
+ 次访问
@@ -73,12 +85,39 @@ export const LayoutSlug = props => {
)
+ const drawerRight = useRef(null)
+ const targetRef = typeof window !== 'undefined' ? document.getElementById('container') : null
+
+ const floatSlot =
+ post?.toc?.length > 1
+ ? (
+
+ {
+ drawerRight?.current?.handleSwitchVisible()
+ }}
+ />
+
+ )
+ : null
return (
-
-
+
+
-
+
+
+
+
+
+
)
}
diff --git a/themes/Hexo/components/ArticleDetail.js b/themes/Hexo/components/ArticleDetail.js
index a30585b9..e006309d 100644
--- a/themes/Hexo/components/ArticleDetail.js
+++ b/themes/Hexo/components/ArticleDetail.js
@@ -33,13 +33,13 @@ export default function ArticleDetail ({ post, recommendPosts, prev, next }) {
}
})
- return (
+ return (
{/* Notion文章主体 */}
-
+
{post.blockMap && (
+
- 发表评论
+
)
diff --git a/themes/Hexo/components/BlogPostCard.js b/themes/Hexo/components/BlogPostCard.js
index 2c98753a..93a2735b 100644
--- a/themes/Hexo/components/BlogPostCard.js
+++ b/themes/Hexo/components/BlogPostCard.js
@@ -29,7 +29,7 @@ const BlogPostCard = ({ post, showSummary }) => {
- {(!showPreview || showSummary) &&
+ return
{currentSearch} )}
}
diff --git a/themes/Hexo/components/BlogPostListPage.js b/themes/Hexo/components/BlogPostListPage.js
index f10811af..b08a0da6 100644
--- a/themes/Hexo/components/BlogPostListPage.js
+++ b/themes/Hexo/components/BlogPostListPage.js
@@ -20,7 +20,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount }) => {
return (
{/* 文章列表 */}
-
+
{posts.map(post => (
))}
diff --git a/themes/Hexo/components/Catalog.js b/themes/Hexo/components/Catalog.js
new file mode 100644
index 00000000..830e6b04
--- /dev/null
+++ b/themes/Hexo/components/Catalog.js
@@ -0,0 +1,88 @@
+import React from 'react'
+import throttle from 'lodash.throttle'
+import { uuidToId } from 'notion-utils'
+import Progress from './Progress'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faStream } from '@fortawesome/free-solid-svg-icons'
+// import { cs } from 'react-notion-x'
+
+/**
+ * 目录导航组件
+ * @param toc
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const Catalog = ({ toc }) => {
+ // 无目录就直接返回空
+ if (!toc || toc.length < 1) {
+ return <>>
+ }
+ // 监听滚动事件
+ React.useEffect(() => {
+ window.addEventListener('scroll', actionSectionScrollSpy)
+ actionSectionScrollSpy()
+ return () => {
+ window.removeEventListener('scroll', actionSectionScrollSpy)
+ }
+ }, [])
+
+ // 同步选中目录事件
+ const [activeSection, setActiveSection] = React.useState(null)
+ const throttleMs = 100
+ const actionSectionScrollSpy = React.useCallback(throttle(() => {
+ const sections = document.getElementsByClassName('notion-h')
+ let prevBBox = null
+ let currentSectionId = activeSection
+ for (let i = 0; i < sections.length; ++i) {
+ const section = sections[i]
+ if (!section || !(section instanceof Element)) continue
+ if (!currentSectionId) {
+ currentSectionId = section.getAttribute('data-id')
+ }
+ const bbox = section.getBoundingClientRect()
+ const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0
+ const offset = Math.max(150, prevHeight / 4)
+ // GetBoundingClientRect returns values relative to viewport
+ if (bbox.top - offset < 0) {
+ currentSectionId = section.getAttribute('data-id')
+ prevBBox = bbox
+ continue
+ }
+ // No need to continue loop, if last element has been detected
+ break
+ }
+ setActiveSection(currentSectionId)
+ }, throttleMs))
+
+ return
+ 目录
+
+
+
+
+
+}
+
+export default Catalog
diff --git a/themes/Hexo/components/Collapse.js b/themes/Hexo/components/Collapse.js
index 7607b732..8af6f100 100644
--- a/themes/Hexo/components/Collapse.js
+++ b/themes/Hexo/components/Collapse.js
@@ -1,6 +1,7 @@
import React, { useEffect, useRef } from 'react'
const Collapse = props => {
+ const { id, className } = props
const collapseRef = useRef(null)
const collapseSection = element => {
const sectionHeight = element.scrollHeight
@@ -28,7 +29,7 @@ const Collapse = props => {
}
}, [props.isOpen])
return (
-
+
{props.children}
)
diff --git a/themes/Hexo/components/LatestPostsGroup.js b/themes/Hexo/components/LatestPostsGroup.js
index 78782e69..8e9d7bc8 100644
--- a/themes/Hexo/components/LatestPostsGroup.js
+++ b/themes/Hexo/components/LatestPostsGroup.js
@@ -20,14 +20,14 @@ const LatestPostsGroup = ({ posts }) => {
const { locale } = useGlobal()
return <>
-
- {locale.COMMON.LATEST_POSTS}
+
+ {locale.COMMON.LATEST_POSTS}
{posts.map(post => {
const selected = currentPath === `${BLOG.PATH}/article/${post.slug}`
return (
-
+
diff --git a/themes/Hexo/components/Logo.js b/themes/Hexo/components/Logo.js
index a2852287..b0b1e70f 100644
--- a/themes/Hexo/components/Logo.js
+++ b/themes/Hexo/components/Logo.js
@@ -4,7 +4,7 @@ import React from 'react'
const Logo = () => {
return
-
+
{BLOG.TITLE}
diff --git a/themes/Hexo/components/MenuButtonGroup.js b/themes/Hexo/components/MenuButtonGroup.js
index 53528653..e62a3215 100644
--- a/themes/Hexo/components/MenuButtonGroup.js
+++ b/themes/Hexo/components/MenuButtonGroup.js
@@ -23,7 +23,7 @@ const MenuButtonGroup = ({ postCount }) => {
if (link.show) {
const selected = (router.pathname === link.to) || (router.asPath === link.to)
return
-
diff --git a/themes/Hexo/components/PaginationNumber.js b/themes/Hexo/components/PaginationNumber.js
index f37d8704..8b99f564 100644
--- a/themes/Hexo/components/PaginationNumber.js
+++ b/themes/Hexo/components/PaginationNumber.js
@@ -18,7 +18,7 @@ const PaginationNumber = ({ page, totalPage }) => {
const pages = generatePages(page, currentPage, totalPage)
return (
-
+
{/* 上一页 */}
{
} } passHref >
@@ -39,7 +39,7 @@ const PaginationNumber = ({ page, totalPage }) => {
@@ -51,11 +51,12 @@ const PaginationNumber = ({ page, totalPage }) => {
function getPageElement (page, currentPage) {
return
+ ' border-white dark:border-blue-700 dark:hover:border-blue-400 cursor-pointer pb-0.5 w-6 text-center font-light hover:font-bold'}>
{page}
}
+
function generatePages (page, currentPage, totalPage) {
const pages = []
const groupCount = 7 // 最多显示页签数
diff --git a/themes/Hexo/components/Progress.js b/themes/Hexo/components/Progress.js
new file mode 100644
index 00000000..8dcde403
--- /dev/null
+++ b/themes/Hexo/components/Progress.js
@@ -0,0 +1,43 @@
+import React, { useEffect, useState } from 'react'
+
+/**
+ * 顶部页面阅读进度条
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const Progress = ({ targetRef, showPercent = true }) => {
+ const currentRef = targetRef?.current || targetRef
+ const [percent, changePercent] = useState(0)
+ const scrollListener = () => {
+ const target = currentRef || document.getElementById('container')
+ if (target) {
+ const clientHeight = target.clientHeight
+ const scrollY = window.pageYOffset
+ const fullHeight = clientHeight - window.outerHeight
+ let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
+ if (per > 100) per = 100
+ if (per < 0) per = 0
+ changePercent(per)
+ }
+ }
+
+ useEffect(() => {
+ document.addEventListener('scroll', scrollListener)
+ return () => document.removeEventListener('scroll', scrollListener)
+ }, [percent])
+
+ return (
+
+
+ {showPercent && (
+ {percent}%
+ )}
+
+
+ )
+}
+
+export default Progress
diff --git a/themes/Hexo/components/SearchInput.js b/themes/Hexo/components/SearchInput.js
index 57e16633..e728c320 100644
--- a/themes/Hexo/components/SearchInput.js
+++ b/themes/Hexo/components/SearchInput.js
@@ -46,19 +46,19 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
updateSearchKey(e.target.value)}
defaultValue={searchKey}
/>
- { handleSearch(searchKey) }}>
{(searchKey && searchKey.length &&
-
+
)}
diff --git a/themes/Hexo/components/SideRight.js b/themes/Hexo/components/SideRight.js
index adac7e09..167d4ba4 100644
--- a/themes/Hexo/components/SideRight.js
+++ b/themes/Hexo/components/SideRight.js
@@ -10,55 +10,96 @@ import CategoryGroup from './CategoryGroup'
import LatestPostsGroup from './LatestPostsGroup'
import TagGroups from './TagGroups'
import SocialButton from './SocialButton'
+import Catalog from './Catalog'
export default function SideRight (props) {
- const { postCount, currentCategory, categories, latestPosts, tags, currentTag } = props
- return
-
- { Router.push('/') }}>
-
-
- {BLOG.TITLE}
-
-
-
-
-
-
-
-
- 统计
-
-
+ const {
+ post,
+ postCount,
+ currentCategory,
+ categories,
+ latestPosts,
+ tags,
+ currentTag,
+ showCategory,
+ showTag
+ } = props
+ return (
+
+
+ {
+ Router.push('/')
+ }}
+ >
+
+
+ {BLOG.TITLE}
+
+
+
+
+
+
+
+
+ 统计
+
+
- 文章数: {postCount}
+
+ 文章数:
+ {postCount}
+
-
- 访问量:
+
+
+ 访问量:
+
+
-
- 访客数:
+
+
+ 访客数:
+
+
-
-
-
- 分类
-
-
-
-
-
-
-
-
-
-
+
+
+ {showCategory && (
+
+
+ 分类
+
+
+
+ )}
+ {showTag && (
+
+
+
+ )}
+ {latestPosts &&
+
+ }
+
+ {post && post.toc && (
+
+
+
+ )}
+
+ )
}
diff --git a/themes/Hexo/components/TocDrawer.js b/themes/Hexo/components/TocDrawer.js
new file mode 100644
index 00000000..70f8a41a
--- /dev/null
+++ b/themes/Hexo/components/TocDrawer.js
@@ -0,0 +1,42 @@
+import Catalog from './Catalog'
+import React, { useImperativeHandle, useState } from 'react'
+
+/**
+ * 目录抽屉栏
+ * @param toc
+ * @param post
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const TocDrawer = ({ post, cRef }) => {
+ // 暴露给父组件 通过cRef.current.handleMenuClick 调用
+ useImperativeHandle(cRef, () => {
+ return {
+ handleSwitchVisible: () => switchVisible()
+ }
+ })
+ const [showDrawer, switchShowDrawer] = useState(false)
+ const switchVisible = () => {
+ switchShowDrawer(!showDrawer)
+ }
+ return <>
+
+ {/* 侧边菜单 */}
+
+ {post && <>
+
+
+
+ >
+ }
+
+
+ {/* 背景蒙版 */}
+
+ >
+}
+export default TocDrawer
diff --git a/themes/Hexo/components/TocDrawerButton.js b/themes/Hexo/components/TocDrawerButton.js
new file mode 100644
index 00000000..60553462
--- /dev/null
+++ b/themes/Hexo/components/TocDrawerButton.js
@@ -0,0 +1,24 @@
+import { useGlobal } from '@/lib/global'
+import { faListOl } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React from 'react'
+import CONFIG_HEXO from '../config_hexo'
+
+/**
+ * 点击召唤目录抽屉
+ * 当屏幕下滑500像素后会出现该控件
+ * @param props 父组件传入props
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const TocDrawerButton = (props) => {
+ if (!CONFIG_HEXO.WIDGET_TOC) {
+ return <>>
+ }
+ const { locale } = useGlobal()
+ return (
+
+ )
+}
+
+export default TocDrawerButton
diff --git a/themes/Hexo/components/TopNav.js b/themes/Hexo/components/TopNav.js
index fc94ff5b..b3ddc1e8 100644
--- a/themes/Hexo/components/TopNav.js
+++ b/themes/Hexo/components/TopNav.js
@@ -1,9 +1,9 @@
import { useGlobal } from '@/lib/global'
-import { faAngleDoubleRight, faBars, faSearch, faTag, faThList, faTimes } from '@fortawesome/free-solid-svg-icons'
+import { faAngleDoubleRight, faBars, faTag, faThList, faTimes } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import throttle from 'lodash.throttle'
import Link from 'next/link'
-import { useCallback, useEffect, useRef, useState } from 'react'
+import { useEffect, useRef, useState } from 'react'
import CategoryGroup from './CategoryGroup'
import Collapse from './Collapse'
import Logo from './Logo'
@@ -11,6 +11,7 @@ import MenuButtonGroup from './MenuButtonGroup'
import SearchDrawer from './SearchDrawer'
import TagGroups from './TagGroups'
import CONFIG_HEXO from '../config_hexo'
+import SearchInput from './SearchInput'
let windowTop = 0
@@ -23,18 +24,18 @@ const TopNav = ({ tags, currentTag, categories, currentCategory, postCount }) =>
const { locale } = useGlobal()
const searchDrawer = useRef()
- const scrollTrigger = useCallback(throttle(() => {
+ const scrollTrigger = throttle(() => {
const scrollS = window.scrollY
if (scrollS >= windowTop && scrollS > 10) {
const nav = document.querySelector('#sticky-nav')
- nav && nav.classList.replace('top-0', '-top-40')
+ nav && nav.classList.replace('top-0', '-top-16')
windowTop = scrollS
} else {
const nav = document.querySelector('#sticky-nav')
- nav && nav.classList.replace('-top-40', 'top-0')
+ nav && nav.classList.replace('-top-16', 'top-0')
windowTop = scrollS
}
- }, 200), [])
+ }, 200)
// 监听滚动
useEffect(() => {
@@ -85,35 +86,29 @@ const TopNav = ({ tags, currentTag, categories, currentCategory, postCount }) =>
) }
>
- return (
+ return (
{/* 导航栏 */}
-
-
- {/* 左侧LOGO 标题 */}
-
-
- { isOpen ? : }
-
-
-
+
+
{/* 右侧功能 */}
-
- { searchDrawer?.current?.show() }}>
- {locale.NAV.SEARCH}
+
+
+ { isOpen ? : }
-
-
+
+
-
+
+
diff --git a/themes/Hexo/config_hexo.js b/themes/Hexo/config_hexo.js
index 5a85be89..ef742382 100644
--- a/themes/Hexo/config_hexo.js
+++ b/themes/Hexo/config_hexo.js
@@ -12,7 +12,10 @@ const CONFIG_HEXO = {
POST_LIST_COVER: true, // 文章封面
POST_LIST_SUMMARY: true, // 文章摘要
+ POST_LIST_PREVIEW: false,
NAV_TYPE: 'autoCollapse', // ['fixed','autoCollapse','normal'] 分别是固定屏幕顶部、屏幕顶部自动折叠,不固定
- WIDGET_TO_TOP: true
+
+ WIDGET_TO_TOP: true,
+ WIDGET_TOC: true // 移动端悬浮目录
}
export default CONFIG_HEXO
diff --git a/themes/index.js b/themes/index.js
index 4f34ef8d..b28dd755 100644
--- a/themes/index.js
+++ b/themes/index.js
@@ -5,4 +5,4 @@
// export * from './Empty' // 空主题
// export * from './NEXT'
// export * from './Fukasawa'
-export * from './Hexo' //
+export * from './Hexo'
+ {/* 评论互动 */} -
+ {(!showPreview || showSummary) &&
{post.summary}
} diff --git a/themes/Hexo/components/BlogPostListEmpty.js b/themes/Hexo/components/BlogPostListEmpty.js index b1d87253..86977fd0 100644 --- a/themes/Hexo/components/BlogPostListEmpty.js +++ b/themes/Hexo/components/BlogPostListEmpty.js @@ -5,7 +5,7 @@ * @constructor */ const BlogPostListEmpty = ({ currentSearch }) => { - return没有找到文章 {(currentSearch &&