mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-14 07:26:52 +00:00
Merge branch 'main' of https://github.com/tangly1024/NotionNext
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
import { useState, useImperativeHandle, useRef, useEffect, Fragment } from 'react'
|
||||
import algoliasearch from 'algoliasearch'
|
||||
import replaceSearchResult from '@/components/Mark'
|
||||
import Link from 'next/link'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import throttle from 'lodash/throttle'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import algoliasearch from 'algoliasearch'
|
||||
import throttle from 'lodash/throttle'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import {
|
||||
Fragment,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
const ShortCutActions = [
|
||||
{
|
||||
@@ -20,7 +27,6 @@ const ShortCutActions = [
|
||||
key: 'Esc',
|
||||
action: '关闭'
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
/**
|
||||
@@ -36,31 +42,49 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
const [totalPage, setTotalPage] = useState(0)
|
||||
const [totalHit, setTotalHit] = useState(0)
|
||||
const [useTime, setUseTime] = useState(0)
|
||||
const inputRef = useRef(null)
|
||||
const [activeIndex, setActiveIndex] = useState(0)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
useHotkeys('ctrl+k', (e) => {
|
||||
const inputRef = useRef(null)
|
||||
const router = useRouter()
|
||||
|
||||
/**
|
||||
* 快捷键设置
|
||||
*/
|
||||
useHotkeys('ctrl+k', e => {
|
||||
e.preventDefault()
|
||||
setIsModalOpen(true)
|
||||
})
|
||||
// 方向键调整选中
|
||||
useHotkeys('down', (e) => {
|
||||
e.preventDefault()
|
||||
if (activeIndex < searchResults.length - 1) {
|
||||
setActiveIndex(activeIndex + 1)
|
||||
}
|
||||
}, { enableOnFormTags: true })
|
||||
useHotkeys('up', (e) => {
|
||||
e.preventDefault()
|
||||
if (activeIndex > 0) {
|
||||
setActiveIndex(activeIndex - 1)
|
||||
}
|
||||
}, { enableOnFormTags: true })
|
||||
useHotkeys(
|
||||
'down',
|
||||
e => {
|
||||
e.preventDefault()
|
||||
if (activeIndex < searchResults.length - 1) {
|
||||
setActiveIndex(activeIndex + 1)
|
||||
}
|
||||
},
|
||||
{ enableOnFormTags: true }
|
||||
)
|
||||
useHotkeys(
|
||||
'up',
|
||||
e => {
|
||||
e.preventDefault()
|
||||
if (activeIndex > 0) {
|
||||
setActiveIndex(activeIndex - 1)
|
||||
}
|
||||
},
|
||||
{ enableOnFormTags: true }
|
||||
)
|
||||
// esc关闭
|
||||
useHotkeys('esc', (e) => {
|
||||
e.preventDefault()
|
||||
setIsModalOpen(false)
|
||||
}, { enableOnFormTags: true })
|
||||
useHotkeys(
|
||||
'esc',
|
||||
e => {
|
||||
e.preventDefault()
|
||||
setIsModalOpen(false)
|
||||
},
|
||||
{ enableOnFormTags: true }
|
||||
)
|
||||
|
||||
// 跳转Search结果
|
||||
const onJumpSearchResult = () => {
|
||||
if (searchResults.length > 0) {
|
||||
@@ -68,11 +92,15 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
}
|
||||
}
|
||||
// enter跳转
|
||||
useHotkeys('enter', (e) => {
|
||||
if (searchResults.length > 0) {
|
||||
onJumpSearchResult(index)
|
||||
}
|
||||
}, { enableOnFormTags: true })
|
||||
useHotkeys(
|
||||
'enter',
|
||||
e => {
|
||||
if (searchResults.length > 0) {
|
||||
onJumpSearchResult(index)
|
||||
}
|
||||
},
|
||||
{ enableOnFormTags: true }
|
||||
)
|
||||
|
||||
const resetSearch = () => {
|
||||
setActiveIndex(0)
|
||||
@@ -84,6 +112,16 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
if (inputRef.current) inputRef.current.value = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面路径变化后,自动关闭此modal
|
||||
*/
|
||||
useEffect(() => {
|
||||
setIsModalOpen(false)
|
||||
}, [router])
|
||||
|
||||
/**
|
||||
* 自动聚焦搜索框
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (isModalOpen) {
|
||||
setTimeout(() => {
|
||||
@@ -93,9 +131,10 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
resetSearch()
|
||||
}
|
||||
}, [isModalOpen])
|
||||
|
||||
/**
|
||||
* 对外暴露方法
|
||||
*/
|
||||
* 对外暴露方法
|
||||
**/
|
||||
useImperativeHandle(cRef, () => {
|
||||
return {
|
||||
openSearch: () => {
|
||||
@@ -104,7 +143,10 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
}
|
||||
})
|
||||
|
||||
const client = algoliasearch(siteConfig('ALGOLIA_APP_ID'), siteConfig('ALGOLIA_SEARCH_ONLY_APP_KEY'))
|
||||
const client = algoliasearch(
|
||||
siteConfig('ALGOLIA_APP_ID'),
|
||||
siteConfig('ALGOLIA_SEARCH_ONLY_APP_KEY')
|
||||
)
|
||||
const index = client.initIndex(siteConfig('ALGOLIA_INDEX'))
|
||||
|
||||
/**
|
||||
@@ -131,7 +173,9 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
setTotalHit(nbHits)
|
||||
setSearchResults(hits)
|
||||
setIsLoading(false)
|
||||
const doms = document.getElementById('search-wrapper').getElementsByClassName('replace')
|
||||
const doms = document
|
||||
.getElementById('search-wrapper')
|
||||
.getElementsByClassName('replace')
|
||||
|
||||
setTimeout(() => {
|
||||
replaceSearchResult({
|
||||
@@ -149,33 +193,35 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
}
|
||||
|
||||
// 定义节流函数,确保在用户停止输入一段时间后才会调用处理搜索的方法
|
||||
const throttledHandleInputChange = useRef(throttle((query, page = 0) => {
|
||||
handleSearch(query, page);
|
||||
}, 1000));
|
||||
const throttledHandleInputChange = useRef(
|
||||
throttle((query, page = 0) => {
|
||||
handleSearch(query, page)
|
||||
}, 1000)
|
||||
)
|
||||
|
||||
// 用于存储搜索延迟的计时器
|
||||
const searchTimer = useRef(null);
|
||||
const searchTimer = useRef(null)
|
||||
|
||||
// 修改input的onChange事件处理函数
|
||||
const handleInputChange = (e) => {
|
||||
const query = e.target.value;
|
||||
const handleInputChange = e => {
|
||||
const query = e.target.value
|
||||
|
||||
// 如果已经有计时器在等待搜索,先清除之前的计时器
|
||||
if (searchTimer.current) {
|
||||
clearTimeout(searchTimer.current);
|
||||
clearTimeout(searchTimer.current)
|
||||
}
|
||||
|
||||
// 设置新的计时器,在用户停止输入一段时间后触发搜索
|
||||
searchTimer.current = setTimeout(() => {
|
||||
throttledHandleInputChange.current(query);
|
||||
}, 800);
|
||||
};
|
||||
throttledHandleInputChange.current(query)
|
||||
}, 800)
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换页码
|
||||
* @param {*} page
|
||||
*/
|
||||
const switchPage = (page) => {
|
||||
const switchPage = page => {
|
||||
throttledHandleInputChange.current(keyword, page)
|
||||
}
|
||||
|
||||
@@ -191,58 +237,58 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
}
|
||||
return (
|
||||
<div
|
||||
id="search-wrapper"
|
||||
className={`${isModalOpen ? 'opacity-100' : 'invisible opacity-0 pointer-events-none'
|
||||
} z-30 fixed h-screen w-screen left-0 top-0 sm:mt-12 flex items-start justify-center mt-0`}
|
||||
>
|
||||
id='search-wrapper'
|
||||
className={`${
|
||||
isModalOpen ? 'opacity-100' : 'invisible opacity-0 pointer-events-none'
|
||||
} z-30 fixed h-screen w-screen left-0 top-0 sm:mt-12 flex items-start justify-center mt-0`}>
|
||||
{/* 模态框 */}
|
||||
<div
|
||||
className={`${isModalOpen ? 'opacity-100' : 'invisible opacity-0 translate-y-10'
|
||||
} flex flex-col justify-between w-full min-h-[10rem] h-full md:h-fit max-w-xl dark:bg-hexo-black-gray dark:border-gray-800 bg-white dark:bg- p-5 rounded-lg z-50 shadow border hover:border-blue-600 duration-300 transition-all `}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-2xl text-blue-600 dark:text-yellow-600 font-bold">搜索</div>
|
||||
className={`${
|
||||
isModalOpen ? 'opacity-100' : 'invisible opacity-0 translate-y-10'
|
||||
} flex flex-col justify-between w-full min-h-[10rem] h-full md:h-fit max-w-xl dark:bg-hexo-black-gray dark:border-gray-800 bg-white dark:bg- p-5 rounded-lg z-50 shadow border hover:border-blue-600 duration-300 transition-all `}>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='text-2xl text-blue-600 dark:text-yellow-600 font-bold'>
|
||||
搜索
|
||||
</div>
|
||||
<div>
|
||||
<i
|
||||
className="text-gray-600 fa-solid fa-xmark p-1 cursor-pointer hover:text-blue-600"
|
||||
onClick={closeModal}
|
||||
></i>
|
||||
className='text-gray-600 fa-solid fa-xmark p-1 cursor-pointer hover:text-blue-600'
|
||||
onClick={closeModal}></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="在这里输入搜索关键词..."
|
||||
type='text'
|
||||
placeholder='在这里输入搜索关键词...'
|
||||
onChange={e => handleInputChange(e)}
|
||||
className="text-black dark:text-gray-200 bg-gray-50 dark:bg-gray-600 outline-blue-500 w-full px-4 my-2 py-1 mb-4 border rounded-md"
|
||||
className='text-black dark:text-gray-200 bg-gray-50 dark:bg-gray-600 outline-blue-500 w-full px-4 my-2 py-1 mb-4 border rounded-md'
|
||||
ref={inputRef}
|
||||
/>
|
||||
|
||||
{/* 标签组 */}
|
||||
<div className="mb-4">
|
||||
<div className='mb-4'>
|
||||
<TagGroups />
|
||||
</div>
|
||||
{
|
||||
searchResults.length === 0 && keyword && !isLoading && (
|
||||
<div>
|
||||
<p className=" text-slate-600 text-center my-4 text-base"> 无法找到相关结果
|
||||
<span
|
||||
className='font-semibold'
|
||||
>"{keyword}"</span></p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{searchResults.length === 0 && keyword && !isLoading && (
|
||||
<div>
|
||||
<p className=' text-slate-600 text-center my-4 text-base'>
|
||||
{' '}
|
||||
无法找到相关结果
|
||||
<span className='font-semibold'>"{keyword}"</span>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<ul className='flex-1 overflow-auto'>
|
||||
{searchResults.map((result, index) => (
|
||||
<li key={result.objectID}
|
||||
<li
|
||||
key={result.objectID}
|
||||
onMouseEnter={() => setActiveIndex(index)}
|
||||
onClick={() => onJumpSearchResult(index)}
|
||||
className={`cursor-pointer replace my-2 p-2 duration-100
|
||||
rounded-lg
|
||||
${activeIndex === index ? 'bg-blue-600 dark:bg-yellow-600' : ''}`}>
|
||||
<a
|
||||
className={`${activeIndex === index ? ' text-white' : ' text-black dark:text-gray-300 '}`}
|
||||
>
|
||||
className={`${activeIndex === index ? ' text-white' : ' text-black dark:text-gray-300 '}`}>
|
||||
{result.title}
|
||||
</a>
|
||||
</li>
|
||||
@@ -250,15 +296,22 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
</ul>
|
||||
<Pagination totalPage={totalPage} page={page} switchPage={switchPage} />
|
||||
<div className='flex items-center justify-between mt-2 sm:text-sm text-xs dark:text-gray-300'>
|
||||
{totalHit === 0 && (<div className='flex items-center'>
|
||||
{
|
||||
ShortCutActions.map((action, index) => {
|
||||
return <Fragment key={index}><div className='border-gray-300 dark:text-gray-300 text-gray-600 px-2 rounded border inline-block'>{action.key}</div>
|
||||
<span className='ml-2 mr-4 text-gray-600 dark:text-gray-300'>{action.action}</span></Fragment>
|
||||
})
|
||||
}
|
||||
</div>)
|
||||
}
|
||||
{totalHit === 0 && (
|
||||
<div className='flex items-center'>
|
||||
{ShortCutActions.map((action, index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<div className='border-gray-300 dark:text-gray-300 text-gray-600 px-2 rounded border inline-block'>
|
||||
{action.key}
|
||||
</div>
|
||||
<span className='ml-2 mr-4 text-gray-600 dark:text-gray-300'>
|
||||
{action.action}
|
||||
</span>
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
{totalHit > 0 && (
|
||||
<p>
|
||||
@@ -266,19 +319,18 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-gray-600 dark:text-gray-300 text-right">
|
||||
<span >
|
||||
<i className="fa-brands fa-algolia"></i> Algolia 提供搜索服务
|
||||
<div className='text-gray-600 dark:text-gray-300 text-right'>
|
||||
<span>
|
||||
<i className='fa-brands fa-algolia'></i> Algolia 提供搜索服务
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* 遮罩 */}
|
||||
<div
|
||||
onClick={closeModal}
|
||||
className="z-30 fixed top-0 left-0 w-full h-full flex items-center justify-center glassmorphism"
|
||||
className='z-30 fixed top-0 left-0 w-full h-full flex items-center justify-center glassmorphism'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -292,21 +344,31 @@ function TagGroups() {
|
||||
// 获取tagOptions数组前十个
|
||||
const firstTenTags = tagOptions?.slice(0, 10)
|
||||
|
||||
return <div id='tags-group' className='dark:border-gray-700 space-y-2'>
|
||||
{
|
||||
firstTenTags?.map((tag, index) => {
|
||||
return <Link passHref
|
||||
key={index}
|
||||
href={`/tag/${encodeURIComponent(tag.name)}`}
|
||||
className={'cursor-pointer inline-block whitespace-nowrap'}>
|
||||
<div className={'flex items-center text-black dark:text-gray-300 hover:bg-blue-600 dark:hover:bg-yellow-600 hover:scale-110 hover:text-white rounded-lg px-2 py-0.5 duration-150 transition-all'}>
|
||||
<div className='text-lg'>{tag.name} </div>{tag.count ? <sup className='relative ml-1'>{tag.count}</sup> : <></>}
|
||||
</div>
|
||||
|
||||
</Link>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
return (
|
||||
<div id='tags-group' className='dark:border-gray-700 space-y-2'>
|
||||
{firstTenTags?.map((tag, index) => {
|
||||
return (
|
||||
<Link
|
||||
passHref
|
||||
key={index}
|
||||
href={`/tag/${encodeURIComponent(tag.name)}`}
|
||||
className={'cursor-pointer inline-block whitespace-nowrap'}>
|
||||
<div
|
||||
className={
|
||||
'flex items-center text-black dark:text-gray-300 hover:bg-blue-600 dark:hover:bg-yellow-600 hover:scale-110 hover:text-white rounded-lg px-2 py-0.5 duration-150 transition-all'
|
||||
}>
|
||||
<div className='text-lg'>{tag.name} </div>
|
||||
{tag.count ? (
|
||||
<sup className='relative ml-1'>{tag.count}</sup>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,16 +383,16 @@ function Pagination(props) {
|
||||
return (
|
||||
<div className='flex space-x-1 w-full justify-center py-1'>
|
||||
{Array.from({ length: totalPage }, (_, i) => {
|
||||
const classNames = page === i
|
||||
? 'font-bold text-white bg-blue-600 dark:bg-yellow-600 rounded'
|
||||
: 'hover:text-blue-600 hover:font-bold dark:text-gray-300'
|
||||
const classNames =
|
||||
page === i
|
||||
? 'font-bold text-white bg-blue-600 dark:bg-yellow-600 rounded'
|
||||
: 'hover:text-blue-600 hover:font-bold dark:text-gray-300'
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => switchPage(i)}
|
||||
className={`text-center cursor-pointer w-6 h-6 ${classNames}`}
|
||||
key={i}
|
||||
>
|
||||
key={i}>
|
||||
{i + 1}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState, useRef, useLayoutEffect } from 'react'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { saveDarkModeToLocalStorage, THEMES } from '@/themes/theme'
|
||||
import useWindowSize from '@/hooks/useWindowSize'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { THEMES, saveDarkModeToLocalStorage } from '@/themes/theme'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
|
||||
/**
|
||||
* 自定义右键菜单
|
||||
@@ -20,14 +20,14 @@ export default function CustomContextMenu(props) {
|
||||
const [width, setWidth] = useState(0)
|
||||
const [height, setHeight] = useState(0)
|
||||
|
||||
const { latestPosts } = props
|
||||
const { allNavPages } = props
|
||||
const router = useRouter()
|
||||
/**
|
||||
* 随机跳转文章
|
||||
*/
|
||||
function handleJumpToRandomPost() {
|
||||
const randomIndex = Math.floor(Math.random() * latestPosts.length)
|
||||
const randomPost = latestPosts[randomIndex]
|
||||
const randomIndex = Math.floor(Math.random() * allNavPages.length)
|
||||
const randomPost = allNavPages[randomIndex]
|
||||
router.push(`${siteConfig('SUB_PATH', '')}/${randomPost?.slug}`)
|
||||
}
|
||||
|
||||
@@ -37,16 +37,26 @@ export default function CustomContextMenu(props) {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const handleContextMenu = (event) => {
|
||||
setShow(false)
|
||||
}, [router])
|
||||
|
||||
useEffect(() => {
|
||||
const handleContextMenu = event => {
|
||||
event.preventDefault()
|
||||
// 计算点击位置加菜单宽高是否超出屏幕,如果超出则贴边弹出
|
||||
const x = (event.clientX < windowSize.width - width) ? event.clientX : windowSize.width - width
|
||||
const y = (event.clientY < windowSize.height - height) ? event.clientY : windowSize.height - height
|
||||
const x =
|
||||
event.clientX < windowSize.width - width
|
||||
? event.clientX
|
||||
: windowSize.width - width
|
||||
const y =
|
||||
event.clientY < windowSize.height - height
|
||||
? event.clientY
|
||||
: windowSize.height - height
|
||||
setPosition({ y: `${y}px`, x: `${x}px` })
|
||||
setShow(true)
|
||||
}
|
||||
|
||||
const handleClick = (event) => {
|
||||
const handleClick = event => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||
setShow(false)
|
||||
}
|
||||
@@ -80,19 +90,20 @@ export default function CustomContextMenu(props) {
|
||||
|
||||
function handleCopyLink() {
|
||||
const url = window.location.href
|
||||
navigator.clipboard.writeText(url)
|
||||
navigator.clipboard
|
||||
.writeText(url)
|
||||
.then(() => {
|
||||
console.log('页面地址已复制')
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(error => {
|
||||
console.error('复制页面地址失败:', error)
|
||||
})
|
||||
setShow(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换主题
|
||||
*/
|
||||
* 切换主题
|
||||
*/
|
||||
function handleChangeTheme() {
|
||||
const randomTheme = THEMES[Math.floor(Math.random() * THEMES.length)] // 从THEMES数组中 随机取一个主题
|
||||
const query = router.query
|
||||
@@ -104,14 +115,14 @@ export default function CustomContextMenu(props) {
|
||||
* 复制内容
|
||||
*/
|
||||
function handleCopy() {
|
||||
const selectedText = document.getSelection().toString();
|
||||
const selectedText = document.getSelection().toString()
|
||||
if (selectedText) {
|
||||
const tempInput = document.createElement('input');
|
||||
tempInput.value = selectedText;
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(tempInput);
|
||||
const tempInput = document.createElement('input')
|
||||
tempInput.value = selectedText
|
||||
document.body.appendChild(tempInput)
|
||||
tempInput.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(tempInput)
|
||||
// alert("Text copied: " + selectedText);
|
||||
} else {
|
||||
// alert("Please select some text first.");
|
||||
@@ -130,76 +141,119 @@ export default function CustomContextMenu(props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={menuRef}
|
||||
style={{ top: position.y, left: position.x }}
|
||||
className={`${show ? '' : 'invisible opacity-0'} select-none transition-opacity duration-200 fixed z-50`}
|
||||
>
|
||||
<div
|
||||
ref={menuRef}
|
||||
style={{ top: position.y, left: position.x }}
|
||||
className={`${show ? '' : 'invisible opacity-0'} select-none transition-opacity duration-200 fixed z-50`}>
|
||||
{/* 菜单内容 */}
|
||||
<div className='rounded-xl w-52 dark:hover:border-yellow-600 bg-white dark:bg-[#040404] dark:text-gray-200 dark:border-gray-600 p-3 border drop-shadow-lg flex-col duration-300 transition-colors'>
|
||||
{/* 顶部导航按钮 */}
|
||||
<div className='flex justify-between'>
|
||||
<i
|
||||
onClick={handleBack}
|
||||
className='hover:bg-blue-600 hover:text-white px-2 py-2 text-center w-8 rounded cursor-pointer fa-solid fa-arrow-left'></i>
|
||||
<i
|
||||
onClick={handleForward}
|
||||
className='hover:bg-blue-600 hover:text-white px-2 py-2 text-center w-8 rounded cursor-pointer fa-solid fa-arrow-right'></i>
|
||||
<i
|
||||
onClick={handleRefresh}
|
||||
className='hover:bg-blue-600 hover:text-white px-2 py-2 text-center w-8 rounded cursor-pointer fa-solid fa-rotate-right'></i>
|
||||
<i
|
||||
onClick={handleScrollTop}
|
||||
className='hover:bg-blue-600 hover:text-white px-2 py-2 text-center w-8 rounded cursor-pointer fa-solid fa-arrow-up'></i>
|
||||
</div>
|
||||
|
||||
{/* 菜单内容 */}
|
||||
<div className='rounded-xl w-52 dark:hover:border-yellow-600 bg-white dark:bg-[#040404] dark:text-gray-200 dark:border-gray-600 p-3 border drop-shadow-lg flex-col duration-300 transition-colors'>
|
||||
{/* 顶部导航按钮 */}
|
||||
<div className='flex justify-between'>
|
||||
<i onClick={handleBack} className="hover:bg-blue-600 hover:text-white px-2 py-2 text-center w-8 rounded cursor-pointer fa-solid fa-arrow-left"></i>
|
||||
<i onClick={handleForward} className="hover:bg-blue-600 hover:text-white px-2 py-2 text-center w-8 rounded cursor-pointer fa-solid fa-arrow-right"></i>
|
||||
<i onClick={handleRefresh} className="hover:bg-blue-600 hover:text-white px-2 py-2 text-center w-8 rounded cursor-pointer fa-solid fa-rotate-right"></i>
|
||||
<i onClick={handleScrollTop} className="hover:bg-blue-600 hover:text-white px-2 py-2 text-center w-8 rounded cursor-pointer fa-solid fa-arrow-up"></i>
|
||||
</div>
|
||||
|
||||
<hr className='my-2 border-dashed' />
|
||||
|
||||
{/* 跳转导航按钮 */}
|
||||
<div className='w-full px-2'>
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST') && <div onClick={handleJumpToRandomPost} title={locale.MENU.WALK_AROUND} className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className="fa-solid fa-podcast mr-2" />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.WALK_AROUND}</div>
|
||||
</div>}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY') && <Link href='/category' title={locale.MENU.CATEGORY} className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className="fa-solid fa-square-minus mr-2" />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.CATEGORY}</div>
|
||||
</Link>}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG') && <Link href='/tag' title={locale.MENU.TAGS} className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className="fa-solid fa-tag mr-2" />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.TAGS}</div>
|
||||
</Link>}
|
||||
|
||||
</div>
|
||||
|
||||
<hr className='my-2 border-dashed' />
|
||||
|
||||
{/* 功能按钮 */}
|
||||
<div className='w-full px-2'>
|
||||
|
||||
{siteConfig('CAN_COPY') && (
|
||||
<div onClick={handleCopy} title={locale.MENU.COPY} className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className="fa-solid fa-copy mr-2" />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.COPY}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK') && <div onClick={handleCopyLink} title={locale.MENU.SHARE_URL} className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className="fa-solid fa-arrow-up-right-from-square mr-2" />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.SHARE_URL}</div>
|
||||
</div>}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE') && <div onClick={handleChangeDarkMode} title={isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE} className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
{isDarkMode ? <i className="fa-regular fa-sun mr-2" /> : <i className="fa-regular fa-moon mr-2" />}
|
||||
<div className='whitespace-nowrap'> {isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE}</div>
|
||||
</div>}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH') && (
|
||||
<div onClick={handleChangeTheme} title={locale.MENU.THEME_SWITCH} className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className="fa-solid fa-palette mr-2" />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.THEME_SWITCH}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<hr className='my-2 border-dashed' />
|
||||
|
||||
{/* 跳转导航按钮 */}
|
||||
<div className='w-full px-2'>
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST') && (
|
||||
<div
|
||||
onClick={handleJumpToRandomPost}
|
||||
title={locale.MENU.WALK_AROUND}
|
||||
className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className='fa-solid fa-podcast mr-2' />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.WALK_AROUND}</div>
|
||||
</div>
|
||||
</div >
|
||||
)}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY') && (
|
||||
<Link
|
||||
href='/category'
|
||||
title={locale.MENU.CATEGORY}
|
||||
className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className='fa-solid fa-square-minus mr-2' />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.CATEGORY}</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG') && (
|
||||
<Link
|
||||
href='/tag'
|
||||
title={locale.MENU.TAGS}
|
||||
className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className='fa-solid fa-tag mr-2' />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.TAGS}</div>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<hr className='my-2 border-dashed' />
|
||||
|
||||
{/* 功能按钮 */}
|
||||
<div className='w-full px-2'>
|
||||
{siteConfig('CAN_COPY') && (
|
||||
<div
|
||||
onClick={handleCopy}
|
||||
title={locale.MENU.COPY}
|
||||
className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className='fa-solid fa-copy mr-2' />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.COPY}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK') && (
|
||||
<div
|
||||
onClick={handleCopyLink}
|
||||
title={locale.MENU.SHARE_URL}
|
||||
className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className='fa-solid fa-arrow-up-right-from-square mr-2' />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.SHARE_URL}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE') && (
|
||||
<div
|
||||
onClick={handleChangeDarkMode}
|
||||
title={
|
||||
isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE
|
||||
}
|
||||
className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
{isDarkMode ? (
|
||||
<i className='fa-regular fa-sun mr-2' />
|
||||
) : (
|
||||
<i className='fa-regular fa-moon mr-2' />
|
||||
)}
|
||||
<div className='whitespace-nowrap'>
|
||||
{' '}
|
||||
{isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH') && (
|
||||
<div
|
||||
onClick={handleChangeTheme}
|
||||
title={locale.MENU.THEME_SWITCH}
|
||||
className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className='fa-solid fa-palette mr-2' />
|
||||
<div className='whitespace-nowrap'>
|
||||
{locale.MENU.THEME_SWITCH}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import algoliasearch from 'algoliasearch'
|
||||
* 生成全文索引
|
||||
* @param {*} allPages
|
||||
*/
|
||||
const generateAlgoliaSearch = async({ allPages, force = false }) => {
|
||||
const generateAlgoliaSearch = async ({ allPages, force = false }) => {
|
||||
allPages?.forEach(p => {
|
||||
// 判断这篇文章是否需要重新创建索引
|
||||
if (p && !p.password) {
|
||||
@@ -19,7 +19,7 @@ const generateAlgoliaSearch = async({ allPages, force = false }) => {
|
||||
* 上传数据
|
||||
* 根据上次修改文章日期和上次更新索引数据判断是否需要更新algolia索引
|
||||
*/
|
||||
const uploadDataToAlgolia = async(post) => {
|
||||
const uploadDataToAlgolia = async post => {
|
||||
// Connect and authenticate with your Algolia app
|
||||
const client = algoliasearch(BLOG.ALGOLIA_APP_ID, BLOG.ALGOLIA_ADMIN_APP_KEY)
|
||||
|
||||
@@ -61,14 +61,18 @@ const uploadDataToAlgolia = async(post) => {
|
||||
summary: post.summary,
|
||||
lastEditedDate: post.lastEditedDate, // 更新文章时间
|
||||
lastIndexDate: new Date(), // 更新索引时间
|
||||
content: truncate(getPageContentText(post, post.blockMap), 9000) // 索引9000个字节,因为api限制总请求内容上限1万个字节
|
||||
content: truncate(getPageContentText(post, post.blockMap), 8192) // 索引8192个字符,API限制总请求内容上限1万个字节
|
||||
}
|
||||
// console.log('更新Algolia索引', record)
|
||||
index.saveObject(record).wait().then(r => {
|
||||
console.log('Algolia索引更新', r)
|
||||
}).catch(err => {
|
||||
console.log('Algolia异常', err)
|
||||
})
|
||||
index
|
||||
.saveObject(record)
|
||||
.wait()
|
||||
.then(r => {
|
||||
console.log('Algolia索引更新', r)
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('Algolia异常', err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -523,8 +523,10 @@ summary > .notion-h {
|
||||
.notion-page {
|
||||
/* width: var(--notion-max-width); */
|
||||
width: 100% !important;
|
||||
padding-left: calc(min(12px, 8vw));
|
||||
padding-right: calc(min(12px, 8vw));
|
||||
padding-left: 0px !important;
|
||||
padding-right: 0px !important;
|
||||
/* padding-left: calc(min(12px, 8vw)); */
|
||||
/* padding-right: calc(min(12px, 8vw)); */
|
||||
}
|
||||
|
||||
.notion-full-width {
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import CONFIG from '../config'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
|
||||
/**
|
||||
* 上一篇,下一篇文章
|
||||
* @param {prev,next} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function ArticleAdjacent({ prev, next }) {
|
||||
const [isScrollEnd, setIsScrollEnd] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
setIsScrollEnd(false)
|
||||
}, [router])
|
||||
|
||||
useEffect(() => {
|
||||
// 文章是否已经到了底部
|
||||
const targetElement = document.getElementById('article-end')
|
||||
|
||||
const handleIntersect = (entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsScrollEnd(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const options = {
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
threshold: 0.1
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(handleIntersect, options)
|
||||
observer.observe(targetElement)
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!prev || !next || !siteConfig('HEO_ARTICLE_ADJACENT', null, CONFIG)) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<div id='article-end'>
|
||||
{/* 移动端 */}
|
||||
<section className='lg:hidden pt-8 text-gray-800 items-center text-xs md:text-sm flex flex-col m-1 '>
|
||||
<Link
|
||||
href={`/${prev.slug}`}
|
||||
passHref
|
||||
className='cursor-pointer justify-between space-y-1 px-5 py-6 rounded-t-xl dark:bg-[#1e1e1e] border dark:border-gray-600 border-b-0 items-center dark:text-white flex flex-col w-full h-18 duration-200'
|
||||
>
|
||||
<div className='flex justify-start items-center w-full'>上一篇</div>
|
||||
<div className='flex justify-center items-center text-lg font-bold'>{prev.title}</div>
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${next.slug}`}
|
||||
passHref
|
||||
className='cursor-pointer justify-between space-y-1 px-5 py-6 rounded-b-xl dark:bg-[#1e1e1e] border dark:border-gray-600 items-center dark:text-white flex flex-col w-full h-18 duration-200'
|
||||
>
|
||||
<div className='flex justify-start items-center w-full'>下一篇</div>
|
||||
<div className='flex justify-center items-center text-lg font-bold'>{next.title}</div>
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
{/* 桌面端 */}
|
||||
|
||||
<div id='pc-next-post' className={`hidden md:block fixed z-40 right-24 bottom-4 duration-200 transition-all ${isScrollEnd ? 'mb-0 opacity-100' : '-mb-24 opacity-0'}`}>
|
||||
<Link
|
||||
href={`/${next.slug}`}
|
||||
className='cursor-pointer drop-shadow-xl duration transition-all h-24 dark:bg-[#1e1e1e] border dark:border-gray-600 p-3 bg-white dark:text-gray-300 dark:hover:text-yellow-600 hover:text-white hover:font-bold hover:bg-blue-600 rounded-lg flex flex-col justify-between'
|
||||
>
|
||||
<div className='text-xs'>下一篇</div>
|
||||
<hr />
|
||||
<div>{next?.title}</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
187
themes/heo/components/Header.js
Normal file
187
themes/heo/components/Header.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import throttle from 'lodash.throttle'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import DarkModeButton from './DarkModeButton'
|
||||
import Logo from './Logo'
|
||||
import { MenuListTop } from './MenuListTop'
|
||||
import RandomPostButton from './RandomPostButton'
|
||||
import ReadingProgress from './ReadingProgress'
|
||||
import SearchButton from './SearchButton'
|
||||
import SlideOver from './SlideOver'
|
||||
|
||||
/**
|
||||
* 页头:顶部导航
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const Header = props => {
|
||||
const [fixedNav, setFixedNav] = useState(false)
|
||||
const [textWhite, setTextWhite] = useState(false)
|
||||
const [navBgWhite, setBgWhite] = useState(false)
|
||||
const [activeIndex, setActiveIndex] = useState(0)
|
||||
|
||||
const slideOverRef = useRef()
|
||||
|
||||
const toggleMenuOpen = () => {
|
||||
slideOverRef?.current?.toggleSlideOvers()
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据滚动条,切换导航栏样式
|
||||
*/
|
||||
const scrollTrigger = useCallback(
|
||||
throttle(() => {
|
||||
const scrollS = window.scrollY
|
||||
// 导航栏设置 白色背景
|
||||
if (scrollS <= 0) {
|
||||
setFixedNav(false)
|
||||
setBgWhite(false)
|
||||
|
||||
// 文章详情页特殊处理
|
||||
if (document.querySelector('#post-bg')) {
|
||||
setFixedNav(true)
|
||||
setTextWhite(true)
|
||||
setBgWhite(false)
|
||||
}
|
||||
} else {
|
||||
// 向下滚动后的导航样式
|
||||
setFixedNav(true)
|
||||
setTextWhite(false)
|
||||
setBgWhite(true)
|
||||
}
|
||||
}, 200)
|
||||
)
|
||||
|
||||
// 监听滚动
|
||||
useEffect(() => {
|
||||
scrollTrigger()
|
||||
window.addEventListener('scroll', scrollTrigger)
|
||||
return () => {
|
||||
window.removeEventListener('scroll', scrollTrigger)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 监听导航栏显示文字
|
||||
useEffect(() => {
|
||||
let prevScrollY = 0
|
||||
let ticking = false
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
const currentScrollY = window.scrollY
|
||||
|
||||
if (currentScrollY > prevScrollY) {
|
||||
setActiveIndex(1) // 向下滚动时设置activeIndex为1
|
||||
} else {
|
||||
setActiveIndex(0) // 向上滚动时设置activeIndex为0
|
||||
}
|
||||
|
||||
prevScrollY = currentScrollY
|
||||
ticking = false
|
||||
})
|
||||
|
||||
ticking = true
|
||||
}
|
||||
}
|
||||
|
||||
if (isBrowser) {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (isBrowser) {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx>{`
|
||||
@keyframes fade-in-down {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
transform: translateY(-30%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in-up {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
transform: translateY(30%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in-down {
|
||||
animation: fade-in-down 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.fade-in-up {
|
||||
animation: fade-in-up 0.3s ease-in-out;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* 顶部导航菜单栏 */}
|
||||
<nav
|
||||
id='nav'
|
||||
className={`z-20 h-16 top-0 w-full
|
||||
${fixedNav ? 'fixed' : 'relative bg-transparent'}
|
||||
${textWhite ? 'text-white ' : 'text-black dark:text-white'}
|
||||
${navBgWhite ? 'bg-white dark:bg-[#18171d] shadow' : 'bg-transparent'}`}>
|
||||
<div className='flex h-full mx-auto justify-between items-center max-w-[86rem] px-8'>
|
||||
{/* 左侧logo */}
|
||||
<div className='flex'>
|
||||
<Logo {...props} />
|
||||
</div>
|
||||
|
||||
{/* 中间菜单 */}
|
||||
<div
|
||||
id='nav-bar-swipe'
|
||||
className={`hidden lg:flex flex-grow flex-col items-center justify-center h-full relative w-full ${activeIndex === 0 ? 'fade-in-down' : 'fade-in-up'}`}>
|
||||
{activeIndex === 0 && <MenuListTop {...props} />}
|
||||
{activeIndex === 1 && (
|
||||
<h1 className='font-bold text-center text-light-400 dark:text-gray-400'>
|
||||
{siteConfig('AUTHOR') || siteConfig('TITLE')}{' '}
|
||||
{siteConfig('BIO') && <>|</>} {siteConfig('BIO')}
|
||||
</h1>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 右侧固定 */}
|
||||
<div className='flex flex-shrink-0 justify-center items-center'>
|
||||
<RandomPostButton {...props} />
|
||||
<SearchButton {...props} />
|
||||
{!JSON.parse(siteConfig('THEME_SWITCH')) && (
|
||||
<div className='hidden md:block'>
|
||||
<DarkModeButton {...props} />
|
||||
</div>
|
||||
)}
|
||||
<ReadingProgress />
|
||||
|
||||
{/* 移动端菜单按钮 */}
|
||||
<div
|
||||
onClick={toggleMenuOpen}
|
||||
className='flex lg:hidden w-8 justify-center items-center h-8 cursor-pointer'>
|
||||
<i className='fas fa-bars' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右边侧拉抽屉 */}
|
||||
<SlideOver cRef={slideOverRef} {...props} />
|
||||
</div>
|
||||
</nav>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
@@ -62,13 +62,13 @@ function BannerGroup(props) {
|
||||
*/
|
||||
function Banner(props) {
|
||||
const router = useRouter()
|
||||
const { latestPosts } = props
|
||||
const { allNavPages } = props
|
||||
/**
|
||||
* 随机跳转文章
|
||||
*/
|
||||
function handleClickBanner() {
|
||||
const randomIndex = Math.floor(Math.random() * latestPosts.length)
|
||||
const randomPost = latestPosts[randomIndex]
|
||||
const randomIndex = Math.floor(Math.random() * allNavPages.length)
|
||||
const randomPost = allNavPages[randomIndex]
|
||||
router.push(`${siteConfig('SUB_PATH', '')}/${randomPost?.slug}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import Logo from './Logo'
|
||||
import throttle from 'lodash.throttle'
|
||||
import RandomPostButton from './RandomPostButton'
|
||||
import SearchButton from './SearchButton'
|
||||
import DarkModeButton from './DarkModeButton'
|
||||
import SlideOver from './SlideOver'
|
||||
import ReadingProgress from './ReadingProgress'
|
||||
import { MenuListTop } from './MenuListTop'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
|
||||
/**
|
||||
* 顶部导航
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const NavBar = props => {
|
||||
const [fixedNav, setFixedNav] = useState(false)
|
||||
const [textWhite, setTextWhite] = useState(false)
|
||||
const [navBgWhite, setBgWhite] = useState(false)
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState(0)
|
||||
|
||||
const slideOverRef = useRef()
|
||||
|
||||
const toggleMenuOpen = () => {
|
||||
slideOverRef?.current?.toggleSlideOvers()
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据滚动条,切换导航栏样式
|
||||
*/
|
||||
const scrollTrigger = useCallback(throttle(() => {
|
||||
const scrollS = window.scrollY
|
||||
// 导航栏设置 白色背景
|
||||
if (scrollS <= 0) {
|
||||
setFixedNav(false)
|
||||
setBgWhite(false)
|
||||
|
||||
// 文章详情页特殊处理
|
||||
if (document.querySelector('#post-bg')) {
|
||||
setFixedNav(true)
|
||||
setTextWhite(true)
|
||||
setBgWhite(false)
|
||||
}
|
||||
} else {
|
||||
// 向下滚动后的导航样式
|
||||
setFixedNav(true)
|
||||
setTextWhite(false)
|
||||
setBgWhite(true)
|
||||
}
|
||||
}, 200))
|
||||
|
||||
// 监听滚动
|
||||
useEffect(() => {
|
||||
scrollTrigger()
|
||||
window.addEventListener('scroll', scrollTrigger)
|
||||
return () => {
|
||||
window.removeEventListener('scroll', scrollTrigger)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 监听导航栏显示文字
|
||||
useEffect(() => {
|
||||
let prevScrollY = 0
|
||||
let ticking = false
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(() => {
|
||||
const currentScrollY = window.scrollY
|
||||
|
||||
if (currentScrollY > prevScrollY) {
|
||||
setActiveIndex(1) // 向下滚动时设置activeIndex为1
|
||||
} else {
|
||||
setActiveIndex(0) // 向上滚动时设置activeIndex为0
|
||||
}
|
||||
|
||||
prevScrollY = currentScrollY
|
||||
ticking = false
|
||||
})
|
||||
|
||||
ticking = true
|
||||
}
|
||||
}
|
||||
|
||||
if (isBrowser) {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (isBrowser) {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (<>
|
||||
<style jsx>{`
|
||||
@keyframes fade-in-down {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
transform: translateY(-30%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in-up {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
transform: translateY(30%);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in-down {
|
||||
animation: fade-in-down 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.fade-in-up {
|
||||
animation: fade-in-up 0.3s ease-in-out;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{/* 顶部导航菜单栏 */}
|
||||
<nav id='nav' className={`z-20 h-16 top-0 w-full
|
||||
${fixedNav ? 'fixed' : 'relative bg-transparent'}
|
||||
${textWhite ? 'text-white ' : 'text-black dark:text-white'}
|
||||
${navBgWhite ? 'bg-white dark:bg-[#18171d]' : 'bg-transparent'}`}>
|
||||
|
||||
<div className='flex h-full mx-auto justify-between items-center max-w-[86rem] px-8'>
|
||||
{/* 左侧logo */}
|
||||
<div className='flex'>
|
||||
<Logo {...props} />
|
||||
</div>
|
||||
|
||||
{/* 中间菜单 */}
|
||||
<div id='nav-bar-swipe' className={`hidden lg:flex flex-grow flex-col items-center justify-center h-full relative w-full ${activeIndex === 0 ? 'fade-in-down' : 'fade-in-up'}`}>
|
||||
{activeIndex === 0 && <MenuListTop {...props} />}
|
||||
{activeIndex === 1 && <h1 className='font-bold text-center text-light-400 dark:text-gray-400'>{siteConfig('AUTHOR') || siteConfig('TITLE')} {siteConfig('BIO') && <>|</>} {siteConfig('BIO')}</h1>}
|
||||
</div>
|
||||
|
||||
{/* 右侧固定 */}
|
||||
<div className='flex flex-shrink-0 justify-center items-center'>
|
||||
<RandomPostButton {...props} />
|
||||
<SearchButton {...props}/>
|
||||
{!JSON.parse(siteConfig('THEME_SWITCH')) && <div className='hidden md:block'><DarkModeButton {...props} /></div>}
|
||||
<ReadingProgress />
|
||||
|
||||
{/* 移动端菜单按钮 */}
|
||||
<div onClick={toggleMenuOpen} className='flex lg:hidden w-8 justify-center items-center h-8 cursor-pointer'>
|
||||
<i className='fas fa-bars' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右边侧拉抽屉 */}
|
||||
<SlideOver cRef={slideOverRef} {...props} />
|
||||
</div>
|
||||
</nav>
|
||||
</>)
|
||||
}
|
||||
|
||||
export default NavBar
|
||||
89
themes/heo/components/PostAdjacent.js
Normal file
89
themes/heo/components/PostAdjacent.js
Normal file
@@ -0,0 +1,89 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import CONFIG from '../config'
|
||||
|
||||
/**
|
||||
* 上一篇,下一篇文章
|
||||
* @param {prev,next} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function PostAdjacent({ prev, next }) {
|
||||
const [isScrollEnd, setIsScrollEnd] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
setIsScrollEnd(false)
|
||||
}, [router])
|
||||
|
||||
useEffect(() => {
|
||||
// 文章是否已经到了底部
|
||||
const targetElement = document.getElementById('article-end')
|
||||
|
||||
const handleIntersect = entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsScrollEnd(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const options = {
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
threshold: 0.1
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(handleIntersect, options)
|
||||
observer.observe(targetElement)
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!prev || !next || !siteConfig('HEO_ARTICLE_ADJACENT', null, CONFIG)) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<div id='article-end'>
|
||||
{/* 移动端 */}
|
||||
<section className='lg:hidden pt-8 text-gray-800 items-center text-xs md:text-sm flex flex-col m-1 '>
|
||||
<Link
|
||||
href={`/${prev.slug}`}
|
||||
passHref
|
||||
className='cursor-pointer justify-between space-y-1 px-5 py-6 rounded-t-xl dark:bg-[#1e1e1e] border dark:border-gray-600 border-b-0 items-center dark:text-white flex flex-col w-full h-18 duration-200'>
|
||||
<div className='flex justify-start items-center w-full'>上一篇</div>
|
||||
<div className='flex justify-center items-center text-lg font-bold'>
|
||||
{prev.title}
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${next.slug}`}
|
||||
passHref
|
||||
className='cursor-pointer justify-between space-y-1 px-5 py-6 rounded-b-xl dark:bg-[#1e1e1e] border dark:border-gray-600 items-center dark:text-white flex flex-col w-full h-18 duration-200'>
|
||||
<div className='flex justify-start items-center w-full'>下一篇</div>
|
||||
<div className='flex justify-center items-center text-lg font-bold'>
|
||||
{next.title}
|
||||
</div>
|
||||
</Link>
|
||||
</section>
|
||||
|
||||
{/* 桌面端 */}
|
||||
|
||||
<div
|
||||
id='pc-next-post'
|
||||
className={`hidden md:block fixed z-40 right-24 bottom-4 duration-200 transition-all ${isScrollEnd ? 'mb-0 opacity-100' : '-mb-24 opacity-0'}`}>
|
||||
<Link
|
||||
href={`/${next.slug}`}
|
||||
className='cursor-pointer drop-shadow-xl duration transition-all h-24 dark:bg-[#1e1e1e] border dark:border-gray-600 p-3 bg-white dark:text-gray-300 dark:hover:text-yellow-600 hover:font-bold hover:text-blue-600 rounded-lg flex flex-col justify-between'>
|
||||
<div className='text-xs'>下一篇</div>
|
||||
<hr />
|
||||
<div>{next?.title}</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import CONFIG from '../config'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
|
||||
export default function ArticleCopyright () {
|
||||
/**
|
||||
* 版权声明
|
||||
* @returns
|
||||
*/
|
||||
export default function PostCopyright() {
|
||||
const router = useRouter()
|
||||
const [path, setPath] = useState(siteConfig('LINK') + router.asPath)
|
||||
useEffect(() => {
|
||||
@@ -19,17 +23,19 @@ export default function ArticleCopyright () {
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="dark:text-gray-300 mt-6 mx-1 ">
|
||||
<ul className="overflow-x-auto whitespace-nowrap text-sm dark:bg-gray-900 bg-gray-100 p-5 leading-8 border-l-2 border-indigo-500">
|
||||
<section className='dark:text-gray-300 mt-6 mx-1 '>
|
||||
<ul className='overflow-x-auto whitespace-nowrap text-sm dark:bg-gray-900 bg-gray-100 p-5 leading-8 border-l-2 border-indigo-500'>
|
||||
<li>
|
||||
<strong className='mr-2'>{locale.COMMON.AUTHOR}:</strong>
|
||||
<Link href={'/about'} className="hover:underline">
|
||||
<Link href={'/about'} className='hover:underline'>
|
||||
{siteConfig('AUTHOR')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<strong className='mr-2'>{locale.COMMON.URL}:</strong>
|
||||
<a className="whitespace-normal break-words hover:underline" href={path}>
|
||||
<strong className='mr-2'>{locale.COMMON.URL}:</strong>
|
||||
<a
|
||||
className='whitespace-normal break-words hover:underline'
|
||||
href={path}>
|
||||
{path}
|
||||
</a>
|
||||
</li>
|
||||
@@ -1,12 +1,17 @@
|
||||
import Link from 'next/link'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import NotionIcon from '@/components/NotionIcon'
|
||||
import WavesArea from './WavesArea'
|
||||
import { HashTag } from '@/components/HeroIcons'
|
||||
import WordCount from '@/components/WordCount'
|
||||
import LazyImage from '@/components/LazyImage'
|
||||
import NotionIcon from '@/components/NotionIcon'
|
||||
import WordCount from '@/components/WordCount'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { formatDateFmt } from '@/lib/utils/formatDate'
|
||||
import Link from 'next/link'
|
||||
import WavesArea from './WavesArea'
|
||||
|
||||
/**
|
||||
* 文章页头
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function PostHeader({ post, siteInfo }) {
|
||||
if (!post) {
|
||||
return <></>
|
||||
@@ -15,91 +20,121 @@ export default function PostHeader({ post, siteInfo }) {
|
||||
const headerImage = post?.pageCover ? post.pageCover : siteInfo?.pageCover
|
||||
|
||||
return (
|
||||
<div id='post-bg' className="w-full h-[30rem] relative md:flex-shrink-0 overflow-hidden bg-cover bg-center bg-no-repeat z-10 mb-5">
|
||||
<style jsx>{`
|
||||
.coverdiv:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-shadow: 110px -130px 300px 60px #0060e0 inset;
|
||||
}
|
||||
`}</style>
|
||||
<div
|
||||
id='post-bg'
|
||||
className='w-full h-[30rem] relative md:flex-shrink-0 overflow-hidden bg-cover bg-center bg-no-repeat z-10 mb-5'>
|
||||
<style jsx>{`
|
||||
.coverdiv:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-shadow: 110px -130px 300px 60px #0060e0 inset;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<div style={{ backdropFilter: 'blur(15px)' }} className={'bg-[#0060e0] absolute top-0 w-full h-full py-10 flex justify-center items-center'}>
|
||||
|
||||
{/* 文章背景图 */}
|
||||
<div id='post-cover-wrapper' style={{ filter: 'blur(15px)' }} className='coverdiv lg:translate-x-96 opacity-50 lg:rotate-12'>
|
||||
<LazyImage id='post-cover' className='w-full h-full object-cover opacity-80 max-h-[50rem] min-w-[50vw] min-h-[20rem]' src={headerImage} />
|
||||
</div>
|
||||
|
||||
{/* 文章文字描述 */}
|
||||
<div id='post-info' className='absolute top-48 z-10 flex flex-col space-y-4 lg:-mt-12 w-full max-w-[86rem] px-5'>
|
||||
{/* 分类+标签 */}
|
||||
<div className='flex justify-center md:justify-start items-center'>
|
||||
{post.category && <>
|
||||
<Link href={`/category/${post.category}`} className='mr-4' passHref legacyBehavior>
|
||||
<div className="cursor-pointer font-sm font-bold px-3 py-1 rounded-lg bg-blue-500 hover:bg-white text-white hover:text-blue-500 duration-200 ">
|
||||
{post.category}
|
||||
</div>
|
||||
</Link>
|
||||
</>}
|
||||
|
||||
{post.tagItems && (
|
||||
<div className="hidden md:flex justify-center flex-nowrap overflow-x-auto">
|
||||
{post.tagItems.map((tag, index) => (
|
||||
<Link
|
||||
key={index}
|
||||
href={`/tag/${encodeURIComponent(tag.name)}`}
|
||||
passHref
|
||||
className={'cursor-pointer inline-block text-gray-50 hover:text-white duration-200 py-0.5 px-1 whitespace-nowrap '}>
|
||||
<div className='font-light flex items-center'><HashTag className='text-gray-200 stroke-2 mr-0.5 w-3 h-3' /> {tag.name + (tag.count ? `(${tag.count})` : '')} </div>
|
||||
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 文章Title */}
|
||||
<div className="max-w-5xl font-bold text-3xl lg:text-5xl md:leading-snug shadow-text-md flex justify-center md:justify-start text-white">
|
||||
{siteConfig('POST_TITLE_ICON') && <NotionIcon icon={post.pageIcon} />}{post.title}
|
||||
</div>
|
||||
|
||||
{/* 标题底部补充信息 */}
|
||||
<section className="flex-wrap shadow-text-md flex text-sm justify-center md:justify-start mt-4 text-white dark:text-gray-400 font-light leading-8">
|
||||
|
||||
<div className='flex justify-center dark:text-gray-200 text-opacity-70'>
|
||||
<div className='mr-2'><WordCount /></div>
|
||||
{post?.type !== 'Page' && (
|
||||
<>
|
||||
<Link
|
||||
href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`}
|
||||
passHref
|
||||
className="pl-1 mr-2 cursor-pointer hover:underline">
|
||||
<i className="fa-regular fa-calendar"></i> {post?.publishDay}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="pl-1 mr-2">
|
||||
<i className="fa-regular fa-calendar-check"></i> {post.lastEditedDay}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{JSON.parse(siteConfig('ANALYTICS_BUSUANZI_ENABLE')) && <div className="busuanzi_container_page_pv font-light mr-2">
|
||||
<i className="fa-solid fa-fire-flame-curved"></i> <span className="mr-2 busuanzi_value_page_pv" />
|
||||
</div>}
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<WavesArea />
|
||||
|
||||
</div>
|
||||
<div
|
||||
style={{ backdropFilter: 'blur(15px)' }}
|
||||
className={
|
||||
'bg-[#0060e0] absolute top-0 w-full h-full py-10 flex justify-center items-center'
|
||||
}>
|
||||
{/* 文章背景图 */}
|
||||
<div
|
||||
id='post-cover-wrapper'
|
||||
style={{ filter: 'blur(15px)' }}
|
||||
className='coverdiv lg:translate-x-96 opacity-50 lg:rotate-12'>
|
||||
<LazyImage
|
||||
id='post-cover'
|
||||
className='w-full h-full object-cover opacity-80 max-h-[50rem] min-w-[50vw] min-h-[20rem]'
|
||||
src={headerImage}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 文章文字描述 */}
|
||||
<div
|
||||
id='post-info'
|
||||
className='absolute top-48 z-10 flex flex-col space-y-4 lg:-mt-12 w-full max-w-[86rem] px-5'>
|
||||
{/* 分类+标签 */}
|
||||
<div className='flex justify-center md:justify-start items-center'>
|
||||
{post.category && (
|
||||
<>
|
||||
<Link
|
||||
href={`/category/${post.category}`}
|
||||
className='mr-4'
|
||||
passHref
|
||||
legacyBehavior>
|
||||
<div className='cursor-pointer font-sm font-bold px-3 py-1 rounded-lg bg-blue-500 hover:bg-white text-white hover:text-blue-500 duration-200 '>
|
||||
{post.category}
|
||||
</div>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
{post.tagItems && (
|
||||
<div className='hidden md:flex justify-center flex-nowrap overflow-x-auto'>
|
||||
{post.tagItems.map((tag, index) => (
|
||||
<Link
|
||||
key={index}
|
||||
href={`/tag/${encodeURIComponent(tag.name)}`}
|
||||
passHref
|
||||
className={
|
||||
'cursor-pointer inline-block text-gray-50 hover:text-white duration-200 py-0.5 px-1 whitespace-nowrap '
|
||||
}>
|
||||
<div className='font-light flex items-center'>
|
||||
<HashTag className='text-gray-200 stroke-2 mr-0.5 w-3 h-3' />{' '}
|
||||
{tag.name + (tag.count ? `(${tag.count})` : '')}{' '}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 文章Title */}
|
||||
<div className='max-w-5xl font-bold text-3xl lg:text-5xl md:leading-snug shadow-text-md flex justify-center md:justify-start text-white'>
|
||||
{siteConfig('POST_TITLE_ICON') && (
|
||||
<NotionIcon icon={post.pageIcon} />
|
||||
)}
|
||||
{post.title}
|
||||
</div>
|
||||
|
||||
{/* 标题底部补充信息 */}
|
||||
<section className='flex-wrap shadow-text-md flex text-sm justify-center md:justify-start mt-4 text-white dark:text-gray-400 font-light leading-8'>
|
||||
<div className='flex justify-center dark:text-gray-200 text-opacity-70'>
|
||||
<div className='mr-2'>
|
||||
<WordCount />
|
||||
</div>
|
||||
{post?.type !== 'Page' && (
|
||||
<>
|
||||
<Link
|
||||
href={`/archive#${formatDateFmt(post?.publishDate, 'yyyy-MM')}`}
|
||||
passHref
|
||||
className='pl-1 mr-2 cursor-pointer hover:underline'>
|
||||
<i className='fa-regular fa-calendar'></i>{' '}
|
||||
{post?.publishDay}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className='pl-1 mr-2'>
|
||||
<i className='fa-regular fa-calendar-check'></i>{' '}
|
||||
{post.lastEditedDay}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{JSON.parse(siteConfig('ANALYTICS_BUSUANZI_ENABLE')) && (
|
||||
<div className='busuanzi_container_page_pv font-light mr-2'>
|
||||
<i className='fa-solid fa-fire-flame-curved'></i>{' '}
|
||||
<span className='mr-2 busuanzi_value_page_pv' />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<WavesArea />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useEffect, useRef } from 'react'
|
||||
* @param validPassword(bool) 回调函数,校验正确回调入参为true
|
||||
* @returns
|
||||
*/
|
||||
export const ArticleLock = props => {
|
||||
export const PostLock = props => {
|
||||
const { validPassword } = props
|
||||
const { locale } = useGlobal()
|
||||
const submitPassword = () => {
|
||||
@@ -27,25 +27,35 @@ export const ArticleLock = props => {
|
||||
passwordInputRef.current.focus()
|
||||
}, [])
|
||||
|
||||
return <div id='container' className='w-full flex justify-center items-center h-96 '>
|
||||
<div className='text-center space-y-3'>
|
||||
<div className='font-bold dark:text-gray-300 text-black'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||
<div className='flex mx-4'>
|
||||
<input id="password" type='password'
|
||||
onKeyDown={(e) => {
|
||||
return (
|
||||
<div
|
||||
id='container'
|
||||
className='w-full flex justify-center items-center h-96 '>
|
||||
<div className='text-center space-y-3'>
|
||||
<div className='font-bold dark:text-gray-300 text-black'>
|
||||
{locale.COMMON.ARTICLE_LOCK_TIPS}
|
||||
</div>
|
||||
<div className='flex mx-4'>
|
||||
<input
|
||||
id='password'
|
||||
type='password'
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'Enter') {
|
||||
submitPassword()
|
||||
}
|
||||
}}
|
||||
ref={passwordInputRef} // 绑定ref到passwordInputRef变量
|
||||
className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg font-light leading-10 bg-gray-100 dark:bg-gray-500'>
|
||||
</input>
|
||||
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 bg-indigo-500 hover:bg-indigo-400 text-white rounded-r duration-300" >
|
||||
<i className={'duration-200 cursor-pointer fas fa-key'} > {locale.COMMON.SUBMIT}</i>
|
||||
className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg font-light leading-10 bg-gray-100 dark:bg-gray-500'></input>
|
||||
<div
|
||||
onClick={submitPassword}
|
||||
className='px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 bg-indigo-500 hover:bg-indigo-400 text-white rounded-r duration-300'>
|
||||
<i className={'duration-200 cursor-pointer fas fa-key'}>
|
||||
{locale.COMMON.SUBMIT}
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id='tips'>
|
||||
<div id='tips'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import CONFIG from '../config'
|
||||
* @param {prev,next} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function ArticleRecommend({ recommendPosts, siteInfo }) {
|
||||
export default function PostRecommend({ recommendPosts, siteInfo }) {
|
||||
const { locale } = useGlobal()
|
||||
|
||||
if (
|
||||
@@ -55,6 +55,10 @@ export default function ArticleRecommend({ recommendPosts, siteInfo }) {
|
||||
src={headerImage}
|
||||
className='absolute top-0 w-full h-full object-cover object-center group-hover:scale-110 group-hover:brightness-50 transform duration-200'
|
||||
/>
|
||||
{/* 卡片的阴影遮罩,为了凸显图片上的文字 */}
|
||||
<div className='h-3/4 w-full absolute left-0 bottom-0'>
|
||||
<div className='h-full w-full absolute opacity-80 group-hover:opacity-100 transition-all duration-1000 bg-gradient-to-b from-transparent to-black'></div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
@@ -6,41 +6,41 @@
|
||||
* 2. 更多说明参考此[文档](https://docs.tangly1024.com/article/notionnext-heo)
|
||||
*/
|
||||
|
||||
import CONFIG from './config'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Footer from './components/Footer'
|
||||
import SideRight from './components/SideRight'
|
||||
import NavBar from './components/NavBar'
|
||||
import Comment from '@/components/Comment'
|
||||
import { AdSlot } from '@/components/GoogleAdsense'
|
||||
import { HashTag } from '@/components/HeroIcons'
|
||||
import LazyImage from '@/components/LazyImage'
|
||||
import replaceSearchResult from '@/components/Mark'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
import WWAds from '@/components/WWAds'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { loadWowJS } from '@/lib/plugins/wow'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import BlogPostArchive from './components/BlogPostArchive'
|
||||
import BlogPostListPage from './components/BlogPostListPage'
|
||||
import BlogPostListScroll from './components/BlogPostListScroll'
|
||||
import Hero from './components/Hero'
|
||||
import { useRouter } from 'next/router'
|
||||
import SearchNav from './components/SearchNav'
|
||||
import BlogPostArchive from './components/BlogPostArchive'
|
||||
import { ArticleLock } from './components/ArticleLock'
|
||||
import PostHeader from './components/PostHeader'
|
||||
import Comment from '@/components/Comment'
|
||||
import NotionPage from '@/components/NotionPage'
|
||||
import ArticleAdjacent from './components/ArticleAdjacent'
|
||||
import ArticleCopyright from './components/ArticleCopyright'
|
||||
import ArticleRecommend from './components/ArticleRecommend'
|
||||
import ShareBar from '@/components/ShareBar'
|
||||
import Link from 'next/link'
|
||||
import CategoryBar from './components/CategoryBar'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import { Style } from './style'
|
||||
import { NoticeBar } from './components/NoticeBar'
|
||||
import { HashTag } from '@/components/HeroIcons'
|
||||
import LatestPostsGroup from './components/LatestPostsGroup'
|
||||
import FloatTocButton from './components/FloatTocButton'
|
||||
import replaceSearchResult from '@/components/Mark'
|
||||
import LazyImage from '@/components/LazyImage'
|
||||
import WWAds from '@/components/WWAds'
|
||||
import { AdSlot } from '@/components/GoogleAdsense'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { isBrowser } from '@/lib/utils'
|
||||
import { loadWowJS } from '@/lib/plugins/wow'
|
||||
import Footer from './components/Footer'
|
||||
import Header from './components/Header'
|
||||
import Hero from './components/Hero'
|
||||
import LatestPostsGroup from './components/LatestPostsGroup'
|
||||
import { NoticeBar } from './components/NoticeBar'
|
||||
import PostAdjacent from './components/PostAdjacent'
|
||||
import PostCopyright from './components/PostCopyright'
|
||||
import PostHeader from './components/PostHeader'
|
||||
import { PostLock } from './components/PostLock'
|
||||
import PostRecommend from './components/PostRecommend'
|
||||
import SearchNav from './components/SearchNav'
|
||||
import SideRight from './components/SideRight'
|
||||
import CONFIG from './config'
|
||||
import { Style } from './style'
|
||||
|
||||
/**
|
||||
* 基础布局 采用上中下布局,移动端使用顶部侧边导航栏
|
||||
@@ -49,38 +49,39 @@ import { loadWowJS } from '@/lib/plugins/wow'
|
||||
* @constructor
|
||||
*/
|
||||
const LayoutBase = props => {
|
||||
const {
|
||||
children,
|
||||
slotTop,
|
||||
className
|
||||
} = props
|
||||
const { children, slotTop, className } = props
|
||||
|
||||
// 全屏模式下的最大宽度
|
||||
const { fullWidth } = useGlobal()
|
||||
const router = useRouter()
|
||||
|
||||
const headerSlot = (
|
||||
<header>
|
||||
<header className='shadow'>
|
||||
{/* 顶部导航 */}
|
||||
<NavBar {...props} />
|
||||
<Header {...props} />
|
||||
|
||||
{/* 通知横幅 */}
|
||||
{router.route === '/'
|
||||
? <>
|
||||
<NoticeBar />
|
||||
<Hero {...props} />
|
||||
{router.route === '/' ? (
|
||||
<>
|
||||
<NoticeBar />
|
||||
<Hero {...props} />
|
||||
</>
|
||||
: null}
|
||||
) : null}
|
||||
{fullWidth ? null : <PostHeader {...props} />}
|
||||
</header>
|
||||
)
|
||||
|
||||
// 右侧栏 用户信息+标签列表
|
||||
const slotRight = (router.route === '/404' || fullWidth) ? null : <SideRight {...props} />
|
||||
const slotRight =
|
||||
router.route === '/404' || fullWidth ? null : <SideRight {...props} />
|
||||
|
||||
const maxWidth = fullWidth ? 'max-w-[96rem] mx-auto' : 'max-w-[86rem]' // 普通最大宽度是86rem和顶部菜单栏对齐,留空则与窗口对齐
|
||||
|
||||
const HEO_HERO_BODY_REVERSE = siteConfig('HEO_HERO_BODY_REVERSE', false, CONFIG)
|
||||
const HEO_HERO_BODY_REVERSE = siteConfig(
|
||||
'HEO_HERO_BODY_REVERSE',
|
||||
false,
|
||||
CONFIG
|
||||
)
|
||||
|
||||
// 加载wow动画
|
||||
useEffect(() => {
|
||||
@@ -89,10 +90,8 @@ const LayoutBase = props => {
|
||||
|
||||
return (
|
||||
<div
|
||||
id="theme-heo"
|
||||
className={`${siteConfig('FONT_STYLE')} bg-[#f7f9fe] dark:bg-[#18171d] h-full min-h-screen flex flex-col scroll-smooth`}
|
||||
>
|
||||
|
||||
id='theme-heo'
|
||||
className={`${siteConfig('FONT_STYLE')} bg-[#f7f9fe] dark:bg-[#18171d] h-full min-h-screen flex flex-col scroll-smooth`}>
|
||||
<Style />
|
||||
|
||||
{/* 顶部嵌入 导航栏,首页放hero,文章页放文章详情 */}
|
||||
@@ -100,15 +99,11 @@ const LayoutBase = props => {
|
||||
|
||||
{/* 主区块 */}
|
||||
<main
|
||||
id="wrapper-outer"
|
||||
className={`flex-grow w-full ${maxWidth} mx-auto relative md:px-5`}
|
||||
>
|
||||
id='wrapper-outer'
|
||||
className={`flex-grow w-full ${maxWidth} mx-auto relative md:px-5`}>
|
||||
<div
|
||||
id="container-inner"
|
||||
className={
|
||||
`${HEO_HERO_BODY_REVERSE ? 'flex-row-reverse' : ''} w-full mx-auto lg:flex justify-center relative z-10`
|
||||
}
|
||||
>
|
||||
id='container-inner'
|
||||
className={`${HEO_HERO_BODY_REVERSE ? 'flex-row-reverse' : ''} w-full mx-auto lg:flex justify-center relative z-10`}>
|
||||
<div className={`w-full h-auto ${className || ''}`}>
|
||||
{/* 主区上部嵌入 */}
|
||||
{slotTop}
|
||||
@@ -117,12 +112,11 @@ const LayoutBase = props => {
|
||||
|
||||
<div className='lg:px-2'></div>
|
||||
|
||||
<div className="hidden xl:block">
|
||||
<div className='hidden xl:block'>
|
||||
{/* 主区快右侧 */}
|
||||
{slotRight}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
{/* 页脚 */}
|
||||
@@ -139,17 +133,15 @@ const LayoutBase = props => {
|
||||
*/
|
||||
const LayoutIndex = props => {
|
||||
return (
|
||||
<div id="post-outer-wrapper" className="px-5 md:px-0">
|
||||
{/* 文章分类条 */}
|
||||
<CategoryBar {...props} />
|
||||
{siteConfig('POST_LIST_STYLE') === 'page'
|
||||
? (
|
||||
<BlogPostListPage {...props} />
|
||||
)
|
||||
: (
|
||||
<BlogPostListScroll {...props} />
|
||||
)}
|
||||
</div>
|
||||
<div id='post-outer-wrapper' className='px-5 md:px-0'>
|
||||
{/* 文章分类条 */}
|
||||
<CategoryBar {...props} />
|
||||
{siteConfig('POST_LIST_STYLE') === 'page' ? (
|
||||
<BlogPostListPage {...props} />
|
||||
) : (
|
||||
<BlogPostListScroll {...props} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -160,17 +152,15 @@ const LayoutIndex = props => {
|
||||
*/
|
||||
const LayoutPostList = props => {
|
||||
return (
|
||||
<div id="post-outer-wrapper" className="px-5 md:px-0">
|
||||
{/* 文章分类条 */}
|
||||
<CategoryBar {...props} />
|
||||
{siteConfig('POST_LIST_STYLE') === 'page'
|
||||
? (
|
||||
<BlogPostListPage {...props} />
|
||||
)
|
||||
: (
|
||||
<BlogPostListScroll {...props} />
|
||||
)}
|
||||
</div>
|
||||
<div id='post-outer-wrapper' className='px-5 md:px-0'>
|
||||
{/* 文章分类条 */}
|
||||
<CategoryBar {...props} />
|
||||
{siteConfig('POST_LIST_STYLE') === 'page' ? (
|
||||
<BlogPostListPage {...props} />
|
||||
) : (
|
||||
<BlogPostListScroll {...props} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -200,26 +190,19 @@ const LayoutSearch = props => {
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
currentSearch={currentSearch}
|
||||
>
|
||||
<div id="post-outer-wrapper" className="px-5 md:px-0">
|
||||
{!currentSearch
|
||||
? (
|
||||
<SearchNav {...props} />
|
||||
)
|
||||
: (
|
||||
<div id="posts-wrapper">
|
||||
{siteConfig('POST_LIST_STYLE') === 'page'
|
||||
? (
|
||||
<BlogPostListPage {...props} />
|
||||
)
|
||||
: (
|
||||
<BlogPostListScroll {...props} />
|
||||
)}
|
||||
</div>
|
||||
<div {...props} currentSearch={currentSearch}>
|
||||
<div id='post-outer-wrapper' className='px-5 md:px-0'>
|
||||
{!currentSearch ? (
|
||||
<SearchNav {...props} />
|
||||
) : (
|
||||
<div id='posts-wrapper'>
|
||||
{siteConfig('POST_LIST_STYLE') === 'page' ? (
|
||||
<BlogPostListPage {...props} />
|
||||
) : (
|
||||
<BlogPostListScroll {...props} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -236,20 +219,20 @@ const LayoutArchive = props => {
|
||||
// 归档页顶部显示条,如果是默认归档则不显示。分类详情页显示分类列表,标签详情页显示当前标签
|
||||
|
||||
return (
|
||||
<div className="p-5 rounded-xl border dark:border-gray-600 max-w-6xl w-full bg-white dark:bg-[#1e1e1e]">
|
||||
{/* 文章分类条 */}
|
||||
<CategoryBar {...props} border={false} />
|
||||
<div className='p-5 rounded-xl border dark:border-gray-600 max-w-6xl w-full bg-white dark:bg-[#1e1e1e]'>
|
||||
{/* 文章分类条 */}
|
||||
<CategoryBar {...props} border={false} />
|
||||
|
||||
<div className="px-3">
|
||||
{Object.keys(archivePosts).map(archiveTitle => (
|
||||
<BlogPostArchive
|
||||
key={archiveTitle}
|
||||
posts={archivePosts[archiveTitle]}
|
||||
archiveTitle={archiveTitle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className='px-3'>
|
||||
{Object.keys(archivePosts).map(archiveTitle => (
|
||||
<BlogPostArchive
|
||||
key={archiveTitle}
|
||||
posts={archivePosts[archiveTitle]}
|
||||
archiveTitle={archiveTitle}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -269,80 +252,88 @@ const LayoutSlug = props => {
|
||||
setHasCode(hasCode)
|
||||
}, [])
|
||||
|
||||
const commentEnable = siteConfig('COMMENT_TWIKOO_ENV_ID') || siteConfig('COMMENT_WALINE_SERVER_URL') || siteConfig('COMMENT_VALINE_APP_ID') ||
|
||||
siteConfig('COMMENT_GISCUS_REPO') || siteConfig('COMMENT_CUSDIS_APP_ID') || siteConfig('COMMENT_UTTERRANCES_REPO') ||
|
||||
siteConfig('COMMENT_GITALK_CLIENT_ID') || siteConfig('COMMENT_WEBMENTION_ENABLE')
|
||||
const commentEnable =
|
||||
siteConfig('COMMENT_TWIKOO_ENV_ID') ||
|
||||
siteConfig('COMMENT_WALINE_SERVER_URL') ||
|
||||
siteConfig('COMMENT_VALINE_APP_ID') ||
|
||||
siteConfig('COMMENT_GISCUS_REPO') ||
|
||||
siteConfig('COMMENT_CUSDIS_APP_ID') ||
|
||||
siteConfig('COMMENT_UTTERRANCES_REPO') ||
|
||||
siteConfig('COMMENT_GITALK_CLIENT_ID') ||
|
||||
siteConfig('COMMENT_WEBMENTION_ENABLE')
|
||||
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
// 404
|
||||
if (!post) {
|
||||
setTimeout(() => {
|
||||
if (isBrowser) {
|
||||
const article = document.getElementById('notion-article')
|
||||
if (!article) {
|
||||
router.push('/404').then(() => {
|
||||
console.warn('找不到页面', router.asPath)
|
||||
})
|
||||
setTimeout(
|
||||
() => {
|
||||
if (isBrowser) {
|
||||
const article = document.getElementById('notion-article')
|
||||
if (!article) {
|
||||
router.push('/404').then(() => {
|
||||
console.warn('找不到页面', router.asPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}, siteConfig('POST_WAITING_TIME_FOR_404') * 1000)
|
||||
},
|
||||
siteConfig('POST_WAITING_TIME_FOR_404') * 1000
|
||||
)
|
||||
}
|
||||
}, [post])
|
||||
return (
|
||||
<>
|
||||
<div className={`w-full ${fullWidth ? '' : 'xl:max-w-5xl'} ${hasCode ? 'xl:w-[73.15vw]' : ''} lg:hover:shadow lg:border rounded-2xl lg:px-2 lg:py-4 bg-white dark:bg-[#18171d] dark:border-gray-600 article`}>
|
||||
{lock && <ArticleLock validPassword={validPassword} />}
|
||||
<div
|
||||
className={`w-full ${fullWidth ? '' : 'xl:max-w-5xl'} ${hasCode ? 'xl:w-[73.15vw]' : ''} lg:hover:shadow lg:border rounded-2xl lg:px-2 lg:py-4 bg-white dark:bg-[#18171d] dark:border-gray-600 article`}>
|
||||
{lock && <PostLock validPassword={validPassword} />}
|
||||
|
||||
{!lock && (
|
||||
<div
|
||||
id="article-wrapper"
|
||||
className="overflow-x-auto flex-grow mx-auto md:w-full md:px-5 "
|
||||
>
|
||||
id='article-wrapper'
|
||||
className='overflow-x-auto flex-grow mx-auto md:w-full md:px-5 '>
|
||||
<article
|
||||
itemScope
|
||||
itemType="https://schema.org/Movie"
|
||||
data-wow-delay=".2s"
|
||||
className="wow fadeInUp subpixel-antialiased overflow-y-hidden"
|
||||
>
|
||||
itemType='https://schema.org/Movie'
|
||||
data-wow-delay='.2s'
|
||||
className='wow fadeInUp subpixel-antialiased overflow-y-hidden'>
|
||||
{/* Notion文章主体 */}
|
||||
<section className="px-5 justify-center mx-auto">
|
||||
<WWAds orientation="horizontal" className="w-full" />
|
||||
<section className='px-5 justify-center mx-auto'>
|
||||
<WWAds orientation='horizontal' className='w-full' />
|
||||
{post && <NotionPage post={post} />}
|
||||
<WWAds orientation="horizontal" className="w-full" />
|
||||
<WWAds orientation='horizontal' className='w-full' />
|
||||
</section>
|
||||
|
||||
{/* 分享 */}
|
||||
<ShareBar post={post} />
|
||||
{post?.type === 'Post' && (
|
||||
<div className="px-5">
|
||||
<div className='px-5'>
|
||||
{/* 版权 */}
|
||||
<ArticleCopyright {...props} />
|
||||
<PostCopyright {...props} />
|
||||
{/* 文章推荐 */}
|
||||
<ArticleRecommend {...props} />
|
||||
<PostRecommend {...props} />
|
||||
{/* 上一篇\下一篇文章 */}
|
||||
<ArticleAdjacent {...props} />
|
||||
<PostAdjacent {...props} />
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
|
||||
{fullWidth
|
||||
? null
|
||||
: <div className={`${commentEnable && post ? '' : 'hidden'}`}>
|
||||
<hr className="my-4 border-dashed" />
|
||||
{fullWidth ? null : (
|
||||
<div className={`${commentEnable && post ? '' : 'hidden'}`}>
|
||||
<hr className='my-4 border-dashed' />
|
||||
{/* 评论区上方广告 */}
|
||||
<div className="py-2">
|
||||
<AdSlot />
|
||||
<div className='py-2'>
|
||||
<AdSlot />
|
||||
</div>
|
||||
{/* 评论互动 */}
|
||||
<div className="duration-200 overflow-x-auto px-5">
|
||||
<div className="text-2xl dark:text-white">
|
||||
<i className="fas fa-comment mr-1" />
|
||||
<div className='duration-200 overflow-x-auto px-5'>
|
||||
<div className='text-2xl dark:text-white'>
|
||||
<i className='fas fa-comment mr-1' />
|
||||
{locale.COMMON.COMMENTS}
|
||||
</div>
|
||||
<Comment frontMatter={post} className="" />
|
||||
<Comment frontMatter={post} className='' />
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -361,39 +352,38 @@ const Layout404 = props => {
|
||||
const { onLoading, fullWidth } = useGlobal()
|
||||
return (
|
||||
<>
|
||||
{/* 主区块 */}
|
||||
{/* 主区块 */}
|
||||
<main
|
||||
id="wrapper-outer"
|
||||
className={`flex-grow ${fullWidth ? '' : 'max-w-4xl'} w-screen mx-auto px-5`}
|
||||
>
|
||||
<div id="error-wrapper" className={'w-full mx-auto justify-center'}>
|
||||
id='wrapper-outer'
|
||||
className={`flex-grow ${fullWidth ? '' : 'max-w-4xl'} w-screen mx-auto px-5`}>
|
||||
<div id='error-wrapper' className={'w-full mx-auto justify-center'}>
|
||||
<Transition
|
||||
show={!onLoading}
|
||||
appear={true}
|
||||
enter="transition ease-in-out duration-700 transform order-first"
|
||||
enterFrom="opacity-0 translate-y-16"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-in-out duration-300 transform"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 -translate-y-16"
|
||||
unmount={false}
|
||||
>
|
||||
enter='transition ease-in-out duration-700 transform order-first'
|
||||
enterFrom='opacity-0 translate-y-16'
|
||||
enterTo='opacity-100'
|
||||
leave='transition ease-in-out duration-300 transform'
|
||||
leaveFrom='opacity-100 translate-y-0'
|
||||
leaveTo='opacity-0 -translate-y-16'
|
||||
unmount={false}>
|
||||
{/* 404卡牌 */}
|
||||
<div className="error-content flex flex-col md:flex-row w-full mt-12 h-[30rem] md:h-96 justify-center items-center bg-white dark:bg-[#1B1C20] border dark:border-gray-800 rounded-3xl">
|
||||
<div className='error-content flex flex-col md:flex-row w-full mt-12 h-[30rem] md:h-96 justify-center items-center bg-white dark:bg-[#1B1C20] border dark:border-gray-800 rounded-3xl'>
|
||||
{/* 左侧动图 */}
|
||||
<LazyImage
|
||||
className="error-img h-60 md:h-full p-4"
|
||||
src={'https://bu.dusays.com/2023/03/03/6401a7906aa4a.gif'}
|
||||
></LazyImage>
|
||||
className='error-img h-60 md:h-full p-4'
|
||||
src={
|
||||
'https://bu.dusays.com/2023/03/03/6401a7906aa4a.gif'
|
||||
}></LazyImage>
|
||||
|
||||
{/* 右侧文字 */}
|
||||
<div className="error-info flex-1 flex flex-col justify-center items-center space-y-4">
|
||||
<h1 className="error-title font-extrabold md:text-9xl text-7xl dark:text-white">
|
||||
<div className='error-info flex-1 flex flex-col justify-center items-center space-y-4'>
|
||||
<h1 className='error-title font-extrabold md:text-9xl text-7xl dark:text-white'>
|
||||
404
|
||||
</h1>
|
||||
<div className='dark:text-white'>请尝试站内搜索寻找文章</div>
|
||||
<Link href="/">
|
||||
<button className="bg-blue-500 py-2 px-4 text-white shadow rounded-lg hover:bg-blue-600 hover:shadow-md duration-200 transition-all">
|
||||
<Link href='/'>
|
||||
<button className='bg-blue-500 py-2 px-4 text-white shadow rounded-lg hover:bg-blue-600 hover:shadow-md duration-200 transition-all'>
|
||||
回到主页
|
||||
</button>
|
||||
</Link>
|
||||
@@ -401,7 +391,7 @@ const Layout404 = props => {
|
||||
</div>
|
||||
|
||||
{/* 404页面底部显示最新文章 */}
|
||||
<div className="mt-12">
|
||||
<div className='mt-12'>
|
||||
<LatestPostsGroup {...props} />
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -421,38 +411,35 @@ const LayoutCategoryIndex = props => {
|
||||
const { locale } = useGlobal()
|
||||
|
||||
return (
|
||||
<div id="category-outer-wrapper" className="mt-8 px-5 md:px-0">
|
||||
<div className="text-4xl font-extrabold dark:text-gray-200 mb-5">
|
||||
{locale.COMMON.CATEGORY}
|
||||
</div>
|
||||
<div
|
||||
id="category-list"
|
||||
className="duration-200 flex flex-wrap m-10 justify-center"
|
||||
>
|
||||
{categoryOptions?.map(category => {
|
||||
return (
|
||||
<Link
|
||||
key={category.name}
|
||||
href={`/category/${category.name}`}
|
||||
passHref
|
||||
legacyBehavior
|
||||
>
|
||||
<div
|
||||
className={
|
||||
'group mr-5 mb-5 flex flex-nowrap items-center border bg-white text-2xl rounded-xl dark:hover:text-white px-4 cursor-pointer py-3 hover:text-white hover:bg-indigo-600 transition-all hover:scale-110 duration-150'
|
||||
}
|
||||
>
|
||||
<HashTag className={'w-5 h-5 stroke-gray-500 stroke-2'} />
|
||||
{category.name}
|
||||
<div className="bg-[#f1f3f8] ml-1 px-2 rounded-lg group-hover:text-indigo-600 ">
|
||||
{category.count}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div id='category-outer-wrapper' className='mt-8 px-5 md:px-0'>
|
||||
<div className='text-4xl font-extrabold dark:text-gray-200 mb-5'>
|
||||
{locale.COMMON.CATEGORY}
|
||||
</div>
|
||||
<div
|
||||
id='category-list'
|
||||
className='duration-200 flex flex-wrap m-10 justify-center'>
|
||||
{categoryOptions?.map(category => {
|
||||
return (
|
||||
<Link
|
||||
key={category.name}
|
||||
href={`/category/${category.name}`}
|
||||
passHref
|
||||
legacyBehavior>
|
||||
<div
|
||||
className={
|
||||
'group mr-5 mb-5 flex flex-nowrap items-center border bg-white text-2xl rounded-xl dark:hover:text-white px-4 cursor-pointer py-3 hover:text-white hover:bg-indigo-600 transition-all hover:scale-110 duration-150'
|
||||
}>
|
||||
<HashTag className={'w-5 h-5 stroke-gray-500 stroke-2'} />
|
||||
{category.name}
|
||||
<div className='bg-[#f1f3f8] ml-1 px-2 rounded-lg group-hover:text-indigo-600 '>
|
||||
{category.count}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -466,50 +453,47 @@ const LayoutTagIndex = props => {
|
||||
const { locale } = useGlobal()
|
||||
|
||||
return (
|
||||
<div id="tag-outer-wrapper" className="px-5 mt-8 md:px-0">
|
||||
<div className="text-4xl font-extrabold dark:text-gray-200 mb-5">
|
||||
{locale.COMMON.TAGS}
|
||||
</div>
|
||||
<div
|
||||
id="tag-list"
|
||||
className="duration-200 flex flex-wrap space-x-5 space-y-5 m-10 justify-center"
|
||||
>
|
||||
{tagOptions.map(tag => {
|
||||
return (
|
||||
<Link
|
||||
key={tag.name}
|
||||
href={`/tag/${tag.name}`}
|
||||
passHref
|
||||
legacyBehavior
|
||||
>
|
||||
<div
|
||||
className={
|
||||
'group flex flex-nowrap items-center border bg-white text-2xl rounded-xl dark:hover:text-white px-4 cursor-pointer py-3 hover:text-white hover:bg-indigo-600 transition-all hover:scale-110 duration-150'
|
||||
}
|
||||
>
|
||||
<HashTag className={'w-5 h-5 stroke-gray-500 stroke-2'} />
|
||||
{tag.name}
|
||||
<div className="bg-[#f1f3f8] ml-1 px-2 rounded-lg group-hover:text-indigo-600 ">
|
||||
{tag.count}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div id='tag-outer-wrapper' className='px-5 mt-8 md:px-0'>
|
||||
<div className='text-4xl font-extrabold dark:text-gray-200 mb-5'>
|
||||
{locale.COMMON.TAGS}
|
||||
</div>
|
||||
<div
|
||||
id='tag-list'
|
||||
className='duration-200 flex flex-wrap space-x-5 space-y-5 m-10 justify-center'>
|
||||
{tagOptions.map(tag => {
|
||||
return (
|
||||
<Link
|
||||
key={tag.name}
|
||||
href={`/tag/${tag.name}`}
|
||||
passHref
|
||||
legacyBehavior>
|
||||
<div
|
||||
className={
|
||||
'group flex flex-nowrap items-center border bg-white text-2xl rounded-xl dark:hover:text-white px-4 cursor-pointer py-3 hover:text-white hover:bg-indigo-600 transition-all hover:scale-110 duration-150'
|
||||
}>
|
||||
<HashTag className={'w-5 h-5 stroke-gray-500 stroke-2'} />
|
||||
{tag.name}
|
||||
<div className='bg-[#f1f3f8] ml-1 px-2 rounded-lg group-hover:text-indigo-600 '>
|
||||
{tag.count}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
CONFIG as THEME_CONFIG,
|
||||
LayoutBase,
|
||||
LayoutIndex,
|
||||
LayoutSearch,
|
||||
LayoutArchive,
|
||||
LayoutSlug,
|
||||
Layout404,
|
||||
LayoutPostList,
|
||||
LayoutArchive,
|
||||
LayoutBase,
|
||||
LayoutCategoryIndex,
|
||||
LayoutTagIndex
|
||||
LayoutIndex,
|
||||
LayoutPostList,
|
||||
LayoutSearch,
|
||||
LayoutSlug,
|
||||
LayoutTagIndex,
|
||||
CONFIG as THEME_CONFIG
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user