diff --git a/lib/notion/getAllPosts.js b/lib/notion/getAllPosts.js index d13ff5f2..6f38bf79 100644 --- a/lib/notion/getAllPosts.js +++ b/lib/notion/getAllPosts.js @@ -25,7 +25,13 @@ export async function getAllPosts ({ notionPageData, from, includePage = false } const collectionQuery = notionPageData.collectionQuery const data = [] + if (!collectionQuery || collectionQuery.toString === '{}') { + console.warn('列表查询条件为空', notionPageData) + } const pageIds = getAllPageIds(collectionQuery) + if (!pageIds || pageIds.length === 0) { + console.warn('页面ID列表为空') + } for (let i = 0; i < pageIds.length; i++) { const id = pageIds[i] const properties = (await getPageProperties(id, pageBlock, schema)) || null @@ -59,6 +65,9 @@ export async function getAllPosts ({ notionPageData, from, includePage = false } } }) + if (!posts || posts.length === 0) { + console.warn('文章列表为空') + } // Sort by date if (BLOG.POSTS_SORT_BY === 'date') { posts.sort((a, b) => { diff --git a/themes/Hexo/LayoutBase.js b/themes/Hexo/LayoutBase.js index 8975f09f..26f55831 100644 --- a/themes/Hexo/LayoutBase.js +++ b/themes/Hexo/LayoutBase.js @@ -1,7 +1,11 @@ import CommonHead from '@/components/CommonHead' +import { useEffect, useState } from 'react' import Footer from './components/Footer' +import JumpToTopButton from './components/JumpToTopButton' import SideRight from './components/SideRight' +import TopNav from './components/TopNav' +import smoothscroll from 'smoothscroll-polyfill' /** * 基础布局 采用左右两侧布局,移动端使用顶部导航栏 @@ -10,10 +14,33 @@ import SideRight from './components/SideRight' * @constructor */ const LayoutBase = (props) => { - const { children, headerSlot, meta } = props + const { children, headerSlot, floatSlot, meta } = props + const [show, switchShow] = useState(false) + const [percent, changePercent] = useState(0) // 页面阅读百分比 + + const scrollListener = () => { + const targetRef = document.getElementById('wrapper') + const clientHeight = targetRef?.clientHeight + const scrollY = window.pageYOffset + const fullHeight = clientHeight - window.outerHeight + let per = parseFloat(((scrollY / fullHeight * 100)).toFixed(0)) + if (per > 100) per = 100 + const shouldShow = scrollY > 100 && per > 0 + + if (shouldShow !== show) { + switchShow(shouldShow) + } + changePercent(per) + } + useEffect(() => { + smoothscroll.polyfill() + document.addEventListener('scroll', scrollListener) + return () => document.removeEventListener('scroll', scrollListener) + }, [show]) return (
+ {headerSlot} @@ -26,6 +53,14 @@ const LayoutBase = (props) => { + {/* 右下角悬浮 */} +
+
+ + {floatSlot} +
+
+
) diff --git a/themes/Hexo/components/Collapse.js b/themes/Hexo/components/Collapse.js new file mode 100644 index 00000000..7607b732 --- /dev/null +++ b/themes/Hexo/components/Collapse.js @@ -0,0 +1,38 @@ +import React, { useEffect, useRef } from 'react' + +const Collapse = props => { + const collapseRef = useRef(null) + const collapseSection = element => { + const sectionHeight = element.scrollHeight + requestAnimationFrame(function () { + element.style.height = sectionHeight + 'px' + requestAnimationFrame(function () { + element.style.height = 0 + 'px' + }) + }) + } + const expandSection = element => { + const sectionHeight = element.scrollHeight + element.style.height = sectionHeight + 'px' + const clearTime = setTimeout(() => { + element.style.height = 'auto' + }, 400) + clearTimeout(clearTime) + } + useEffect(() => { + const element = collapseRef.current + if (props.isOpen) { + expandSection(element) + } else { + collapseSection(element) + } + }, [props.isOpen]) + return ( +
+ {props.children} +
+ ) +} +Collapse.defaultProps = { isOpen: false } + +export default Collapse diff --git a/themes/Hexo/components/JumpToTopButton.js b/themes/Hexo/components/JumpToTopButton.js new file mode 100644 index 00000000..f680f549 --- /dev/null +++ b/themes/Hexo/components/JumpToTopButton.js @@ -0,0 +1,28 @@ +import { useGlobal } from '@/lib/global' +import { faArrowUp } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React from 'react' +import CONFIG_HEXO from '../config_hexo' + +/** + * 跳转到网页顶部 + * 当屏幕下滑500像素后会出现该控件 + * @param targetRef 关联高度的目标html标签 + * @param showPercent 是否显示百分比 + * @returns {JSX.Element} + * @constructor + */ +const JumpToTopButton = ({ showPercent = true, percent }) => { + if (!CONFIG_HEXO.WIDGET_TO_TOP) { + return <> + } + const { locale } = useGlobal() + return (
window.scrollTo({ top: 0, behavior: 'smooth' })} > +
+ +
+ {showPercent && (
{percent}%
)} +
) +} + +export default JumpToTopButton diff --git a/themes/Hexo/components/Logo.js b/themes/Hexo/components/Logo.js new file mode 100644 index 00000000..a2852287 --- /dev/null +++ b/themes/Hexo/components/Logo.js @@ -0,0 +1,12 @@ +import Link from 'next/link' +import BLOG from '@/blog.config' +import React from 'react' + +const Logo = () => { + return +
+
{BLOG.TITLE}
+
+ +} +export default Logo diff --git a/themes/Hexo/components/SearchDrawer.js b/themes/Hexo/components/SearchDrawer.js new file mode 100644 index 00000000..3d107e0f --- /dev/null +++ b/themes/Hexo/components/SearchDrawer.js @@ -0,0 +1,36 @@ +import { Router } from 'next/router' +import { useImperativeHandle, useRef } from 'react' +import SearchInput from './SearchInput' +const SearchDrawer = ({ cRef, slot }) => { + const searchDrawer = useRef() + const searchInputRef = useRef() + useImperativeHandle(cRef, () => { + return { + show: () => { + searchDrawer?.current?.classList?.remove('hidden') + searchInputRef?.current?.focus() + } + } + }) + const hidden = () => { + searchDrawer?.current?.classList?.add('hidden') + } + Router.events.on('routeChangeComplete', (...args) => { + hidden() + }) + return ( +
+
+
+ + {slot} +
+
+ + {/* 背景蒙版 */} +
+
+ ) +} + +export default SearchDrawer diff --git a/themes/Hexo/components/TopNav.js b/themes/Hexo/components/TopNav.js new file mode 100644 index 00000000..fc94ff5b --- /dev/null +++ b/themes/Hexo/components/TopNav.js @@ -0,0 +1,123 @@ +import { useGlobal } from '@/lib/global' +import { faAngleDoubleRight, faBars, faSearch, 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 CategoryGroup from './CategoryGroup' +import Collapse from './Collapse' +import Logo from './Logo' +import MenuButtonGroup from './MenuButtonGroup' +import SearchDrawer from './SearchDrawer' +import TagGroups from './TagGroups' +import CONFIG_HEXO from '../config_hexo' + +let windowTop = 0 + +/** + * 顶部导航 + * @param {*} param0 + * @returns + */ +const TopNav = ({ tags, currentTag, categories, currentCategory, postCount }) => { + const { locale } = useGlobal() + const searchDrawer = useRef() + + const scrollTrigger = useCallback(throttle(() => { + const scrollS = window.scrollY + if (scrollS >= windowTop && scrollS > 10) { + const nav = document.querySelector('#sticky-nav') + nav && nav.classList.replace('top-0', '-top-40') + windowTop = scrollS + } else { + const nav = document.querySelector('#sticky-nav') + nav && nav.classList.replace('-top-40', 'top-0') + windowTop = scrollS + } + }, 200), []) + + // 监听滚动 + useEffect(() => { + if (CONFIG_HEXO.NAV_TYPE === 'autoCollapse') { + scrollTrigger() + window.addEventListener('scroll', scrollTrigger) + } + return () => { + CONFIG_HEXO.NAV_TYPE === 'autoCollapse' && window.removeEventListener('scroll', scrollTrigger) + } + }, []) + + const [isOpen, changeShow] = useState(false) + + const toggleMenuOpen = () => { + changeShow(!isOpen) + } + + const searchDrawerSlot = <> + { categories && ( +
+
+
{locale.COMMON.CATEGORY}
+ + + {locale.COMMON.MORE} + + +
+ +
+ ) } + + { tags && ( +
+
+
{locale.COMMON.TAGS}
+ + + {locale.COMMON.MORE} + + +
+
+ +
+
+ ) } + + + return (
+ + + {/* 导航栏 */} + + +
) +} + +export default TopNav diff --git a/themes/Hexo/config_hexo.js b/themes/Hexo/config_hexo.js index b0b6edf7..5a85be89 100644 --- a/themes/Hexo/config_hexo.js +++ b/themes/Hexo/config_hexo.js @@ -11,6 +11,8 @@ const CONFIG_HEXO = { MENU_SEARCH: true, // 显示搜索 POST_LIST_COVER: true, // 文章封面 - POST_LIST_SUMMARY: true // 文章摘要 + POST_LIST_SUMMARY: true, // 文章摘要 + NAV_TYPE: 'autoCollapse', // ['fixed','autoCollapse','normal'] 分别是固定屏幕顶部、屏幕顶部自动折叠,不固定 + WIDGET_TO_TOP: true } export default CONFIG_HEXO