完善插件,顶部导航栏

This commit is contained in:
tangly1024
2022-01-24 17:47:01 +08:00
parent bfadca7ccb
commit b1e7fd6f50
8 changed files with 285 additions and 2 deletions

View File

@@ -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) => {

View File

@@ -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>)

View 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

View 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

View 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

View 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

View 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

View File

@@ -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