mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 07:26:52 +00:00
完善插件,顶部导航栏
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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 (<div className='bg-white'>
|
||||
<CommonHead meta={meta} />
|
||||
<TopNav {...props}/>
|
||||
|
||||
{headerSlot}
|
||||
|
||||
@@ -26,6 +53,14 @@ const LayoutBase = (props) => {
|
||||
|
||||
</main>
|
||||
|
||||
{/* 右下角悬浮 */}
|
||||
<div className='right-8 bottom-12 lg:right-2 fixed justify-end z-20 font-sans'>
|
||||
<div className={(show ? 'animate__animated ' : 'hidden') + ' animate__fadeInUp rounded-md glassmorphism justify-center duration-500 animate__faster flex space-x-2 items-center cursor-pointer '}>
|
||||
<JumpToTopButton percent={percent}/>
|
||||
{floatSlot}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer title={meta.title}/>
|
||||
|
||||
</div>)
|
||||
|
||||
38
themes/Hexo/components/Collapse.js
Normal file
38
themes/Hexo/components/Collapse.js
Normal file
@@ -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 (
|
||||
<div ref={collapseRef} style={{ height: '0px' }} className='overflow-hidden duration-200'>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Collapse.defaultProps = { isOpen: false }
|
||||
|
||||
export default Collapse
|
||||
28
themes/Hexo/components/JumpToTopButton.js
Normal file
28
themes/Hexo/components/JumpToTopButton.js
Normal file
@@ -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 (<div className='flex space-x-1 items-center transform hover:scale-105 duration-200 py-2 px-3' onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} >
|
||||
<div className='dark:text-gray-200' title={locale.POST.TOP} >
|
||||
<FontAwesomeIcon icon={faArrowUp} />
|
||||
</div>
|
||||
{showPercent && (<div className='text-xs dark:text-gray-200 block lg:hidden'>{percent}%</div>)}
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default JumpToTopButton
|
||||
12
themes/Hexo/components/Logo.js
Normal file
12
themes/Hexo/components/Logo.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Link from 'next/link'
|
||||
import BLOG from '@/blog.config'
|
||||
import React from 'react'
|
||||
|
||||
const Logo = () => {
|
||||
return <Link href='/' passHref>
|
||||
<div className='flex flex-col justify-center items-center cursor-pointer bg-black space-y-3 font-bold'>
|
||||
<div className='font-serif text-xl text-white'> {BLOG.TITLE}</div>
|
||||
</div>
|
||||
</Link>
|
||||
}
|
||||
export default Logo
|
||||
36
themes/Hexo/components/SearchDrawer.js
Normal file
36
themes/Hexo/components/SearchDrawer.js
Normal file
@@ -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 (
|
||||
<div id='search-drawer-wrapper' ref={searchDrawer} className='hidden'>
|
||||
<div className='flex-col fixed px-5 w-full left-0 top-14 z-50 justify-center'>
|
||||
<div className='md:max-w-3xl w-full mx-auto animate__animated animate__faster animate__fadeIn'>
|
||||
<SearchInput cRef={searchInputRef} />
|
||||
{slot}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 背景蒙版 */}
|
||||
<div id='search-drawer-background' onClick={hidden} className='animate__animated animate__faster animate__fadeIn fixed bg-day dark:bg-night top-0 left-0 z-40 w-full h-full' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchDrawer
|
||||
123
themes/Hexo/components/TopNav.js
Normal file
123
themes/Hexo/components/TopNav.js
Normal file
@@ -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 && (
|
||||
<section className='mt-8'>
|
||||
<div className='text-sm flex flex-nowrap justify-between font-light px-2'>
|
||||
<div className='text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faThList} className='mr-2' />{locale.COMMON.CATEGORY}</div>
|
||||
<Link href={'/category'} passHref>
|
||||
<a className='mb-3 text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
|
||||
{locale.COMMON.MORE} <FontAwesomeIcon icon={faAngleDoubleRight} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<CategoryGroup currentCategory={currentCategory} categories={categories} />
|
||||
</section>
|
||||
) }
|
||||
|
||||
{ tags && (
|
||||
<section className='mt-4'>
|
||||
<div className='text-sm py-2 px-2 flex flex-nowrap justify-between font-light dark:text-gray-200'>
|
||||
<div className='text-gray-600 dark:text-gray-200'><FontAwesomeIcon icon={faTag} className='mr-2'/>{locale.COMMON.TAGS}</div>
|
||||
<Link href={'/tag'} passHref>
|
||||
<a className='text-gray-400 hover:text-black dark:hover:text-white hover:underline cursor-pointer'>
|
||||
{locale.COMMON.MORE} <FontAwesomeIcon icon={faAngleDoubleRight} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className='p-2'>
|
||||
<TagGroups tags={tags} currentTag={currentTag} />
|
||||
</div>
|
||||
</section>
|
||||
) }
|
||||
</>
|
||||
|
||||
return (<div id='top-nav' className='z-40 block lg:hidden'>
|
||||
<SearchDrawer cRef={searchDrawer} slot={searchDrawerSlot}/>
|
||||
|
||||
{/* 导航栏 */}
|
||||
<div id='sticky-nav' className={`${CONFIG_HEXO.NAV_TYPE !== 'normal' ? 'fixed' : ''} lg:relative w-full top-0 z-20 transform duration-500`}>
|
||||
<div className='w-full flex justify-between items-center p-4 bg-black text-white'>
|
||||
{/* 左侧LOGO 标题 */}
|
||||
<div className='flex flex-none flex-grow-0'>
|
||||
<div onClick={toggleMenuOpen} className='w-8 cursor-pointer'>
|
||||
{ isOpen ? <FontAwesomeIcon icon={faTimes} size={'lg'}/> : <FontAwesomeIcon icon={faBars} size={'lg'}/> }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex'>
|
||||
<Logo/>
|
||||
</div>
|
||||
|
||||
{/* 右侧功能 */}
|
||||
<div className='mr-1 flex justify-end items-center text-sm space-x-4 font-serif dark:text-gray-200'>
|
||||
<div className="cursor-pointer block lg:hidden" onClick={() => { searchDrawer?.current?.show() }}>
|
||||
<FontAwesomeIcon icon={faSearch} className="mr-2" />{locale.NAV.SEARCH}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Collapse isOpen={isOpen}>
|
||||
<div className='bg-white py-1 px-5'>
|
||||
<MenuButtonGroup postCount={postCount}/>
|
||||
</div>
|
||||
</Collapse>
|
||||
</div>
|
||||
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default TopNav
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user