mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-06-02 23:16:51 +00:00
提交
This commit is contained in:
@@ -1,2 +1,2 @@
|
|||||||
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
|
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
|
||||||
NEXT_PUBLIC_VERSION=3.11.0
|
NEXT_PUBLIC_VERSION=3.12.4
|
||||||
|
|||||||
@@ -81,9 +81,11 @@
|
|||||||
|
|
||||||
<td align="center"><a href="https://github.com/Pylogmon"><img src="https://avatars.githubusercontent.com/u/59004461" width="64px;" alt="Pylogmon"/><br/><sub><b>派了个萌 </b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=Pylogmon" title="Pylogmon" >🔧 🐛</a></td>
|
<td align="center"><a href="https://github.com/Pylogmon"><img src="https://avatars.githubusercontent.com/u/59004461" width="64px;" alt="Pylogmon"/><br/><sub><b>派了个萌 </b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=Pylogmon" title="Pylogmon" >🔧 🐛</a></td>
|
||||||
|
|
||||||
<td align="center"><a href="https://github.com/SkysCrystal"><img src="https://avatars.githubusercontent.com/u/49473463" width="64px;" alt="SkysCrystal"/><br/><sub><b>Simon Shi
|
<td align="center"><a href="https://github.com/SkysCrystal"><img src="https://avatars.githubusercontent.com/u/49473463" width="64px;" alt="SkysCrystal"/><br/><sub><b>Simon Shi</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=SkysCrystal" title="SkysCrystal" >🔧 🐛</a></td>
|
||||||
</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=SkysCrystal" title="SkysCrystal" >🔧 🐛</a></td>
|
|
||||||
|
|
||||||
|
<td align="center"><a href="https://github.com/siygle"><img src="https://avatars.githubusercontent.com/u/173408" width="64px;" alt="S.Y. Lee"/><br/><sub><b>S.Y. Lee</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=siygle" title="siygle" >🔧 🐛</a></td>
|
||||||
|
|
||||||
|
<td align="center"><a href="https://github.com/fighting-bug"><img src="https://avatars.githubusercontent.com/u/56441589" width="64px;" alt="fighting-buf"/><br/><sub><b>fighting-buf</b></sub></a><br/><a href="https://github.com/tangly1024/NotionNext/commits?author=fighting-bug" title="fighting-buf" >🔧 🐛</a></td>
|
||||||
|
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import React from 'react'
|
|||||||
const Collapse = props => {
|
const Collapse = props => {
|
||||||
const collapseRef = React.useRef(null)
|
const collapseRef = React.useRef(null)
|
||||||
const type = props.type || 'vertical'
|
const type = props.type || 'vertical'
|
||||||
|
/**
|
||||||
|
* 折叠
|
||||||
|
* @param {*} element
|
||||||
|
*/
|
||||||
const collapseSection = element => {
|
const collapseSection = element => {
|
||||||
const sectionHeight = element.scrollHeight
|
const sectionHeight = element.scrollHeight
|
||||||
const sectionWidth = element.scrollWidth
|
const sectionWidth = element.scrollWidth
|
||||||
@@ -54,17 +58,20 @@ const Collapse = props => {
|
|||||||
clearTimeout(clearTime)
|
clearTimeout(clearTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateHeight = () => {
|
||||||
|
collapseRef.current.style.height = 'auto'
|
||||||
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const element = collapseRef.current
|
|
||||||
if (props.isOpen) {
|
if (props.isOpen) {
|
||||||
expandSection(element)
|
expandSection(collapseRef.current)
|
||||||
} else {
|
} else {
|
||||||
collapseSection(element)
|
collapseSection(collapseRef.current)
|
||||||
}
|
}
|
||||||
}, [props.isOpen])
|
}, [props.isOpen])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={collapseRef} style={type === 'vertical' ? { height: '0px' } : { width: '0px' }} className={'overflow-hidden duration-200 ' + props.className }>
|
<div ref={collapseRef} onClick={updateHeight} style={type === 'vertical' ? { height: '0px' } : { width: '0px' }} className={'overflow-hidden duration-200 ' + props.className }>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,10 +2,31 @@ import { useGlobal } from '@/lib/global'
|
|||||||
import { ReactCusdis } from 'react-cusdis'
|
import { ReactCusdis } from 'react-cusdis'
|
||||||
import BLOG from '@/blog.config'
|
import BLOG from '@/blog.config'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
const CusdisComponent = ({ frontMatter }) => {
|
const CusdisComponent = ({ frontMatter }) => {
|
||||||
const { locale } = useGlobal()
|
const { locale } = useGlobal()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { isDarkMode } = useGlobal()
|
||||||
|
|
||||||
|
// 处理cusdis主题
|
||||||
|
useEffect(() => {
|
||||||
|
const cusdisThread = document?.getElementById('cusdis_thread')
|
||||||
|
const cusdisIframe = cusdisThread?.getElementsByTagName('iframe')
|
||||||
|
if (cusdisIframe) {
|
||||||
|
const cusdisWrapper = cusdisIframe[0]?.contentDocument?.getElementById('root')
|
||||||
|
if (isDarkMode) {
|
||||||
|
cusdisWrapper?.classList?.remove('light')
|
||||||
|
cusdisWrapper?.classList?.add('dark')
|
||||||
|
} else {
|
||||||
|
cusdisWrapper?.classList?.remove('dark')
|
||||||
|
cusdisWrapper?.classList?.add('light')
|
||||||
|
}
|
||||||
|
if (!cusdisWrapper?.firstElementChild?.classList?.contains('dark:text-gray-100')) {
|
||||||
|
cusdisWrapper?.firstElementChild?.classList?.add('dark:text-gray-100')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return <ReactCusdis
|
return <ReactCusdis
|
||||||
lang={locale.LOCALE.toLowerCase()}
|
lang={locale.LOCALE.toLowerCase()}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const DarkModeButton = (props) => {
|
|||||||
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
|
htmlElement.classList?.add(newStatus ? 'dark' : 'light')
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={'z-10 duration-200 text-xl py-2 ' + props.className}>
|
return <div className={'text-white z-10 duration-200 text-xl py-2 ' + props.className}>
|
||||||
<i id='darkModeButton' className={`hover:scale-125 cursor-pointer transform duration-200 fas ${isDarkMode ? 'fa-sun' : 'fa-moon'}`}
|
<i id='darkModeButton' className={`hover:scale-125 cursor-pointer transform duration-200 fas ${isDarkMode ? 'fa-sun' : 'fa-moon'}`}
|
||||||
onClick={handleChangeDarkMode} />
|
onClick={handleChangeDarkMode} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function DebugPanel() {
|
|||||||
|
|
||||||
{/* 调试侧拉抽屉 */}
|
{/* 调试侧拉抽屉 */}
|
||||||
<div
|
<div
|
||||||
className={` ${show ? 'shadow-card w-96 right-0 ' : '-right-96 w-0'} overflow-y-scroll h-full p-5 bg-white fixed bottom-0 z-50 duration-200`}
|
className={` ${show ? 'shadow-card w-96 right-0 ' : '-right-96 invisible w-0'} overflow-y-scroll h-full p-5 bg-white fixed bottom-0 z-50 duration-200`}
|
||||||
>
|
>
|
||||||
<div className="flex justify-between space-x-1 my-5">
|
<div className="flex justify-between space-x-1 my-5">
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import BLOG from '@/blog.config'
|
import BLOG from '@/blog.config'
|
||||||
import { isBrowser, loadExternalResource } from '@/lib/utils'
|
import { loadExternalResource } from '@/lib/utils'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义引入外部JS 和 CSS
|
* 自定义引入外部JS 和 CSS
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const ExternalScript = () => {
|
const ExternalScript = () => {
|
||||||
if (isBrowser()) {
|
useEffect(() => {
|
||||||
// 静态导入本地自定义样式
|
// 静态导入本地自定义样式
|
||||||
loadExternalResource(BLOG.FONT_AWESOME, 'css')
|
loadExternalResource(BLOG.FONT_AWESOME, 'css')
|
||||||
loadExternalResource('/css/custom.css', 'css')
|
loadExternalResource('/css/custom.css', 'css')
|
||||||
loadExternalResource('/js/custom.js', 'js')
|
loadExternalResource('/js/custom.js', 'js')
|
||||||
|
|
||||||
|
// 自动添加图片阴影
|
||||||
|
if (BLOG.IMG_SHADOW) {
|
||||||
|
loadExternalResource('/css/img-shadow.css', 'css')
|
||||||
|
}
|
||||||
|
|
||||||
if (BLOG.CUSTOM_EXTERNAL_JS && BLOG.CUSTOM_EXTERNAL_JS.length > 0) {
|
if (BLOG.CUSTOM_EXTERNAL_JS && BLOG.CUSTOM_EXTERNAL_JS.length > 0) {
|
||||||
for (const url of BLOG.CUSTOM_EXTERNAL_JS) {
|
for (const url of BLOG.CUSTOM_EXTERNAL_JS) {
|
||||||
loadExternalResource(url, 'js')
|
loadExternalResource(url, 'js')
|
||||||
@@ -25,7 +32,7 @@ const ExternalScript = () => {
|
|||||||
BLOG.FONT_URL?.forEach(e => {
|
BLOG.FONT_URL?.forEach(e => {
|
||||||
loadExternalResource(e, 'css')
|
loadExternalResource(e, 'css')
|
||||||
})
|
})
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import mediumZoom from '@fisch0920/medium-zoom'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { isBrowser } from '@/lib/utils'
|
import { isBrowser } from '@/lib/utils'
|
||||||
import { Code } from 'react-notion-x/build/third-party/code'
|
import { Code } from 'react-notion-x/build/third-party/code'
|
||||||
|
import TweetEmbed from 'react-tweet-embed'
|
||||||
|
|
||||||
import 'katex/dist/katex.min.css'
|
import 'katex/dist/katex.min.css'
|
||||||
import { mapImgUrl } from '@/lib/notion/mapImage'
|
import { mapImgUrl } from '@/lib/notion/mapImage'
|
||||||
@@ -36,6 +37,10 @@ const Modal = dynamic(
|
|||||||
() => import('react-notion-x/build/third-party/modal').then((m) => m.Modal), { ssr: false }
|
() => import('react-notion-x/build/third-party/modal').then((m) => m.Modal), { ssr: false }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const Tweet = ({ id }) => {
|
||||||
|
return <TweetEmbed tweetId={id} />
|
||||||
|
}
|
||||||
|
|
||||||
const NotionPage = ({ post, className }) => {
|
const NotionPage = ({ post, className }) => {
|
||||||
const zoom = isBrowser() && mediumZoom({
|
const zoom = isBrowser() && mediumZoom({
|
||||||
container: '.notion-viewport',
|
container: '.notion-viewport',
|
||||||
@@ -79,7 +84,7 @@ const NotionPage = ({ post, className }) => {
|
|||||||
return <>{post?.summary || ''}</>
|
return <>{post?.summary || ''}</>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div id='container' className={`max-w-5xl font-medium mx-auto ${className}`}>
|
return <div id='container' className={`font-medium mx-auto ${className}`}>
|
||||||
<NotionRenderer
|
<NotionRenderer
|
||||||
recordMap={post.blockMap}
|
recordMap={post.blockMap}
|
||||||
mapPageUrl={mapPageUrl}
|
mapPageUrl={mapPageUrl}
|
||||||
@@ -89,7 +94,8 @@ const NotionPage = ({ post, className }) => {
|
|||||||
Collection,
|
Collection,
|
||||||
Equation,
|
Equation,
|
||||||
Modal,
|
Modal,
|
||||||
Pdf
|
Pdf,
|
||||||
|
Tweet
|
||||||
}} />
|
}} />
|
||||||
|
|
||||||
<PrismMac />
|
<PrismMac />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const StarrySky = () => {
|
|||||||
}, [])
|
}, [])
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<canvas id="starry-sky-vixcity" className="fixed"></canvas>
|
<canvas id="starry-sky-vixcity" className="fixed pointer-events-none"></canvas>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ const Tabs = ({ className, children }) => {
|
|||||||
{children.map((item, index) => {
|
{children.map((item, index) => {
|
||||||
return <section key={index}
|
return <section key={index}
|
||||||
data-aos="fade-up"
|
data-aos="fade-up"
|
||||||
data-aos-duration="600"
|
data-aos-duration="300"
|
||||||
data-aos-easing="ease-in-out"
|
|
||||||
data-aos-once="true"
|
data-aos-once="true"
|
||||||
data-aos-anchor-placement="top-bottom">
|
data-aos-anchor-placement="top-bottom">
|
||||||
{currentTab === index && item}
|
{currentTab === index && item}
|
||||||
|
|||||||
@@ -85,18 +85,39 @@ function getCustomNav({ allPages }) {
|
|||||||
const customNav = []
|
const customNav = []
|
||||||
if (allPages && allPages.length > 0) {
|
if (allPages && allPages.length > 0) {
|
||||||
allPages.forEach(p => {
|
allPages.forEach(p => {
|
||||||
if (p?.status === 'Published' && p?.type === 'Page') {
|
if (p?.slug?.indexOf('http') === 0) {
|
||||||
if (p?.slug?.indexOf('http') === 0) {
|
customNav.push({ icon: p.icon || null, name: p.title, to: p.slug, show: true })
|
||||||
customNav.push({ icon: p.icon || null, name: p.title, to: p.slug, show: true })
|
} else {
|
||||||
} else {
|
customNav.push({ icon: p.icon || null, name: p.title, to: '/' + p.slug, show: true })
|
||||||
customNav.push({ icon: p.icon || null, name: p.title, to: '/' + p.slug, show: true })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return customNav
|
return customNav
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCustomMenu({ collectionData }) {
|
||||||
|
const menuPages = collectionData.filter(post => (post.type === BLOG.NOTION_PROPERTY_NAME.type_menu || post.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu) && post.status === 'Published')
|
||||||
|
const menus = []
|
||||||
|
if (menuPages && menuPages.length > 0) {
|
||||||
|
menuPages.forEach(e => {
|
||||||
|
e.show = true
|
||||||
|
if (e.type === BLOG.NOTION_PROPERTY_NAME.type_menu) {
|
||||||
|
menus.push(e)
|
||||||
|
} else if (e.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu) {
|
||||||
|
const parentMenu = menus[menus.length - 1]
|
||||||
|
if (parentMenu) {
|
||||||
|
if (parentMenu.subMenus) {
|
||||||
|
parentMenu.subMenus.push(e)
|
||||||
|
} else {
|
||||||
|
parentMenu.subMenus = [e]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return menus
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取标签选项
|
* 获取标签选项
|
||||||
* @param schema
|
* @param schema
|
||||||
@@ -214,11 +235,13 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const notice = await getNotice(collectionData.filter(post => { return post?.type === 'Notice' && post.status === 'Published' })?.[0])
|
const notice = await getNotice(collectionData.filter(post => { return post && post?.type && post?.type === 'Notice' && post.status === 'Published' })?.[0])
|
||||||
const categoryOptions = getAllCategories({ allPages, categoryOptions: getCategoryOptions(schema) })
|
const categoryOptions = getAllCategories({ allPages, categoryOptions: getCategoryOptions(schema) })
|
||||||
const tagOptions = getAllTags({ allPages, tagOptions: getTagOptions(schema) })
|
const tagOptions = getAllTags({ allPages, tagOptions: getTagOptions(schema) })
|
||||||
const siteInfo = getBlogInfo({ collection, block })
|
const siteInfo = getBlogInfo({ collection, block })
|
||||||
const customNav = getCustomNav({ allPages: collectionData.filter(post => post.type === 'Page' && post.status === 'Published') })
|
const customNav = getCustomNav({ allPages: collectionData.filter(post => post.type === 'Page' && post.status === 'Published') })
|
||||||
|
// 新的菜单
|
||||||
|
const customMenu = getCustomMenu({ collectionData })
|
||||||
const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 5 })
|
const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 5 })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -236,6 +259,7 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
|
|||||||
categoryOptions,
|
categoryOptions,
|
||||||
rawMetadata,
|
rawMetadata,
|
||||||
customNav,
|
customNav,
|
||||||
|
customMenu,
|
||||||
postCount,
|
postCount,
|
||||||
pageIds,
|
pageIds,
|
||||||
latestPosts
|
latestPosts
|
||||||
|
|||||||
@@ -76,10 +76,14 @@ export default async function getPageProperties(id, block, schema, authToken, ta
|
|||||||
// 映射值:用户个性化type和status字段的下拉框选项,在此映射回代码的英文标识
|
// 映射值:用户个性化type和status字段的下拉框选项,在此映射回代码的英文标识
|
||||||
mapProperties(properties)
|
mapProperties(properties)
|
||||||
|
|
||||||
if (properties.type === 'Post') {
|
if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_post) {
|
||||||
properties.slug = BLOG.POST_URL_PREFIX ? (BLOG.POST_URL_PREFIX + '/' + (properties.slug ?? properties.id)) : (properties.slug ?? properties.id)
|
properties.slug = (BLOG.POST_URL_PREFIX) ? generateCustomizeUrl(properties) : (properties.slug ?? properties.id)
|
||||||
} else {
|
} else if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_page) {
|
||||||
properties.slug = (properties.slug ?? properties.id)
|
properties.slug = properties.slug ?? properties.id
|
||||||
|
} else if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_menu || properties.type === BLOG.NOTION_PROPERTY_NAME.type_sub_menu) {
|
||||||
|
// 菜单路径为空、作为可展开菜单使用
|
||||||
|
properties.to = properties.slug ?? null
|
||||||
|
properties.name = properties.title ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开启伪静态路径
|
// 开启伪静态路径
|
||||||
@@ -143,3 +147,30 @@ function mapProperties(properties) {
|
|||||||
properties.status = 'Invisible'
|
properties.status = 'Invisible'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateCustomizeUrl(postProperties) {
|
||||||
|
let fullSlug = ''
|
||||||
|
const allSlugPatterns = BLOG.POST_URL_PREFIX.split('/')
|
||||||
|
allSlugPatterns.forEach((pattern, idx) => {
|
||||||
|
if (pattern === '%year%' && postProperties?.date?.start_date) {
|
||||||
|
const formatPostCreatedDate = new Date(postProperties?.date?.start_date)
|
||||||
|
fullSlug += formatPostCreatedDate.getUTCFullYear()
|
||||||
|
} else if (pattern === '%month%' && postProperties?.date?.start_date) {
|
||||||
|
const formatPostCreatedDate = new Date(postProperties?.date?.start_date)
|
||||||
|
fullSlug += String(formatPostCreatedDate.getUTCMonth() + 1).padStart(2, 0)
|
||||||
|
} else if (pattern === '%day%' && postProperties?.date?.start_date) {
|
||||||
|
const formatPostCreatedDate = new Date(postProperties?.date?.start_date)
|
||||||
|
fullSlug += String(formatPostCreatedDate.getUTCDate()).padStart(2, 0)
|
||||||
|
} else if (pattern === '%slug%') {
|
||||||
|
fullSlug += (postProperties.slug ?? postProperties.id)
|
||||||
|
} else if (!pattern.includes('%')) {
|
||||||
|
fullSlug += pattern
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (idx !== allSlugPatterns.length - 1) {
|
||||||
|
fullSlug += '/'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return `${fullSlug}/${(postProperties.slug ?? postProperties.id)}`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import BLOG from '@/blog.config'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notion图片映射处理有emjji的图标
|
* Notion图片映射处理有emoji的图标
|
||||||
* @param {*} img
|
* @param {*} img
|
||||||
* @param {*} value
|
* @param {*} value
|
||||||
* @returns
|
* @returns
|
||||||
@@ -18,7 +20,7 @@ const mapImgUrl = (img, block, type = 'block') => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// notion永久图床地址
|
// notion永久图床地址
|
||||||
if (!ret && img.indexOf('secure.notion-static.com') > 0) {
|
if (!ret && img.indexOf('secure.notion-static.com') > 0 && (BLOG.IMG_URL_TYPE === 'Notion' || type !== 'block')) {
|
||||||
ret = 'https://www.notion.so/image/' + encodeURIComponent(img) + '?table=' + type + '&id=' + block.id
|
ret = 'https://www.notion.so/image/' + encodeURIComponent(img) + '?table=' + type + '&id=' + block.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
lib/robots.txt.js
Normal file
25
lib/robots.txt.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
import fs from 'fs'
|
||||||
|
import BLOG from '@/blog.config'
|
||||||
|
|
||||||
|
export async function generateRobotsTxt() {
|
||||||
|
const content = `
|
||||||
|
# *
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Host
|
||||||
|
Host: ${BLOG.LINK}
|
||||||
|
|
||||||
|
# Sitemaps
|
||||||
|
Sitemap: ${BLOG.LINK}/sitemap.xml
|
||||||
|
|
||||||
|
`
|
||||||
|
try {
|
||||||
|
fs.mkdirSync('./public', { recursive: true })
|
||||||
|
fs.writeFileSync('./public/robots.txt', content)
|
||||||
|
} catch (error) {
|
||||||
|
// 在vercel运行环境是只读的,这里会报错;
|
||||||
|
// 但在vercel编译阶段、或VPS等其他平台这行代码会成功执行
|
||||||
|
}
|
||||||
|
}
|
||||||
22
lib/rss.js
22
lib/rss.js
@@ -5,6 +5,11 @@ import ReactDOMServer from 'react-dom/server'
|
|||||||
import { getPostBlocks } from './notion'
|
import { getPostBlocks } from './notion'
|
||||||
import NotionPage from '@/components/NotionPage'
|
import NotionPage from '@/components/NotionPage'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成RSS内容
|
||||||
|
* @param {*} post
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
const createFeedContent = async post => {
|
const createFeedContent = async post => {
|
||||||
// 加密的文章内容只返回摘要
|
// 加密的文章内容只返回摘要
|
||||||
if (post.password && post.password !== '') {
|
if (post.password && post.password !== '') {
|
||||||
@@ -15,7 +20,7 @@ const createFeedContent = async post => {
|
|||||||
post.blockMap = blockMap
|
post.blockMap = blockMap
|
||||||
const content = ReactDOMServer.renderToString(<NotionPage post={post} />)
|
const content = ReactDOMServer.renderToString(<NotionPage post={post} />)
|
||||||
const regexExp =
|
const regexExp =
|
||||||
/<div class="notion-collection-row"><div class="notion-collection-row-body"><div class="notion-collection-row-property"><div class="notion-collection-column-title"><svg.*?class="notion-collection-column-title-icon">.*?<\/svg><div class="notion-collection-column-title-body">.*?<\/div><\/div><div class="notion-collection-row-value">.*?<\/div><\/div><\/div><\/div>/g
|
/<div class="notion-collection-row"><div class="notion-collection-row-body"><div class="notion-collection-row-property"><div class="notion-collection-column-title"><svg.*?class="notion-collection-column-title-icon">.*?<\/svg><div class="notion-collection-column-title-body">.*?<\/div><\/div><div class="notion-collection-row-value">.*?<\/div><\/div><\/div><\/div>/g
|
||||||
return content.replace(regexExp, '')
|
return content.replace(regexExp, '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +43,6 @@ export async function generateRss(posts) {
|
|||||||
for (const post of posts) {
|
for (const post of posts) {
|
||||||
feed.addItem({
|
feed.addItem({
|
||||||
title: post.title,
|
title: post.title,
|
||||||
guid: `${post.id}`,
|
|
||||||
link: `${BLOG.LINK}/${post.slug}`,
|
link: `${BLOG.LINK}/${post.slug}`,
|
||||||
description: post.summary,
|
description: post.summary,
|
||||||
content: await createFeedContent(post),
|
content: await createFeedContent(post),
|
||||||
@@ -46,8 +50,14 @@ export async function generateRss(posts) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.mkdirSync('./public/rss', { recursive: true })
|
try {
|
||||||
fs.writeFileSync('./public/rss/feed.xml', feed.rss2())
|
fs.mkdirSync('./public/rss', { recursive: true })
|
||||||
fs.writeFileSync('./public/rss/atom.xml', feed.atom1())
|
fs.writeFileSync('./public/rss/feed.xml', feed.rss2())
|
||||||
fs.writeFileSync('./public/rss/feed.json', feed.json1())
|
fs.writeFileSync('./public/rss/atom.xml', feed.atom1())
|
||||||
|
fs.writeFileSync('./public/rss/feed.json', feed.json1())
|
||||||
|
} catch (error) {
|
||||||
|
// 在vercel运行环境是只读的,这里会报错;
|
||||||
|
// 但在vercel编译阶段、或VPS等其他平台这行代码会成功执行
|
||||||
|
// RSS被高频词访问将大量消耗服务端资源,故作为静态文件
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
59
lib/sitemap.xml.js
Normal file
59
lib/sitemap.xml.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
import fs from 'fs'
|
||||||
|
import BLOG from '@/blog.config'
|
||||||
|
|
||||||
|
export async function generateSitemapXml({ allPages }) {
|
||||||
|
const urls = [{
|
||||||
|
loc: `${BLOG.LINK}`,
|
||||||
|
lastmod: new Date().toISOString().split('T')[0],
|
||||||
|
changefreq: 'daily'
|
||||||
|
}, {
|
||||||
|
loc: `${BLOG.LINK}/archive`,
|
||||||
|
lastmod: new Date().toISOString().split('T')[0],
|
||||||
|
changefreq: 'daily'
|
||||||
|
}, {
|
||||||
|
loc: `${BLOG.LINK}/category`,
|
||||||
|
lastmod: new Date().toISOString().split('T')[0],
|
||||||
|
changefreq: 'daily'
|
||||||
|
}, {
|
||||||
|
loc: `${BLOG.LINK}/tag`,
|
||||||
|
lastmod: new Date().toISOString().split('T')[0],
|
||||||
|
changefreq: 'daily'
|
||||||
|
}]
|
||||||
|
|
||||||
|
allPages?.forEach(post => {
|
||||||
|
urls.push({
|
||||||
|
loc: `${BLOG.LINK}/${post.slug}`,
|
||||||
|
lastmod: new Date(post?.date?.start_date || post?.createdTime).toISOString().split('T')[0],
|
||||||
|
changefreq: 'daily'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const xml = createSitemapXml(urls)
|
||||||
|
try {
|
||||||
|
fs.writeFileSync('sitemap.xml', xml)
|
||||||
|
fs.writeFileSync('./public/sitemap.xml', xml)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('无法写入文件', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function createSitemapXml(urls) {
|
||||||
|
let urlsXml = ''
|
||||||
|
urls.forEach(u => {
|
||||||
|
urlsXml += `<url>
|
||||||
|
<loc>${u.loc}</loc>
|
||||||
|
<lastmod>${u.lastmod}</lastmod>
|
||||||
|
<changefreq>${u.changefreq}</changefreq>
|
||||||
|
</url>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
return `
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
|
||||||
|
xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0"
|
||||||
|
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
|
||||||
|
${urlsXml}
|
||||||
|
</urlset>
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -28,7 +28,8 @@ export const initDarkMode = (isDarkMode, updateDarkMode) => {
|
|||||||
*/
|
*/
|
||||||
export const initTheme = (theme, changeTheme) => {
|
export const initTheme = (theme, changeTheme) => {
|
||||||
if (isBrowser()) {
|
if (isBrowser()) {
|
||||||
const queryTheme = getQueryVariable('theme') || loadThemeFromCookies() || BLOG.THEME
|
// const queryTheme = getQueryVariable('theme') || loadThemeFromCookies() || BLOG.THEME
|
||||||
|
const queryTheme = getQueryVariable('theme') || BLOG.THEME
|
||||||
let currentTheme = theme
|
let currentTheme = theme
|
||||||
if (queryTheme !== theme && ALL_THEME.indexOf(queryTheme) > -1) {
|
if (queryTheme !== theme && ALL_THEME.indexOf(queryTheme) > -1) {
|
||||||
currentTheme = queryTheme
|
currentTheme = queryTheme
|
||||||
|
|||||||
29
lib/utils.js
29
lib/utils.js
@@ -124,3 +124,32 @@ export const getListByPage = function (list, pageIndex, pageSize) {
|
|||||||
pageIndex * pageSize
|
pageIndex * pageSize
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否移动设备
|
||||||
|
*/
|
||||||
|
export const isMobile = () => {
|
||||||
|
let isMobile = false
|
||||||
|
if (!isBrowser()) {
|
||||||
|
return isMobile
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这个判断会引发 TypeError: navigator.userAgentData.mobile is undefined 问题,导致博客无法正常工作
|
||||||
|
// if (!isMobile && navigator.userAgentData.mobile) {
|
||||||
|
// isMobile = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!isMobile && (/Mobi|Android|iPhone/i.test(navigator.userAgent))) {
|
||||||
|
isMobile = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/Android|iPhone|iPad|iPod/i.test(navigator.platform)) {
|
||||||
|
isMobile = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window.orientation !== 'undefined') {
|
||||||
|
isMobile = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return isMobile
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ module.exports = withBundleAnalyzer({
|
|||||||
'gravatar.com',
|
'gravatar.com',
|
||||||
'www.notion.so',
|
'www.notion.so',
|
||||||
'avatars.githubusercontent.com',
|
'avatars.githubusercontent.com',
|
||||||
'images.unsplash.com'
|
'images.unsplash.com',
|
||||||
|
'source.unsplash.com',
|
||||||
|
'p1.qhimg.com'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// 默认将feed重定向至 /public/rss/feed.xml
|
// 默认将feed重定向至 /public/rss/feed.xml
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "notion-next",
|
"name": "notion-next",
|
||||||
"version": "3.11.0",
|
"version": "3.12.4",
|
||||||
"homepage": "https://github.com/tangly1024/NotionNext.git",
|
"homepage": "https://github.com/tangly1024/NotionNext.git",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -50,8 +50,9 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-facebook": "^8.1.4",
|
"react-facebook": "^8.1.4",
|
||||||
"react-messenger-customer-chat": "^0.8.0",
|
"react-messenger-customer-chat": "^0.8.0",
|
||||||
"react-notion-x": "6.15.8",
|
"react-notion-x": "6.16.0",
|
||||||
"react-share": "^4.4.0",
|
"react-share": "^4.4.0",
|
||||||
|
"react-tweet-embed": "~2.0.0",
|
||||||
"smoothscroll-polyfill": "^0.4.4",
|
"smoothscroll-polyfill": "^0.4.4",
|
||||||
"twikoo": "1.6.9",
|
"twikoo": "1.6.9",
|
||||||
"typed.js": "^2.0.12",
|
"typed.js": "^2.0.12",
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const Slug = props => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 20 * 1000)
|
}, 8 * 1000) // 404时长
|
||||||
const meta = { title: `${props?.siteInfo?.title || BLOG.TITLE} | loading`, image: siteInfo?.pageCover || BLOG.HOME_BANNER_IMAGE }
|
const meta = { title: `${props?.siteInfo?.title || BLOG.TITLE} | loading`, image: siteInfo?.pageCover || BLOG.HOME_BANNER_IMAGE }
|
||||||
return <ThemeComponents.LayoutSlug {...props} showArticleInfo={true} meta={meta} />
|
return <ThemeComponents.LayoutSlug {...props} showArticleInfo={true} meta={meta} />
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ export async function getStaticPaths() {
|
|||||||
const from = 'slug-paths'
|
const from = 'slug-paths'
|
||||||
const { allPages } = await getGlobalNotionData({ from })
|
const { allPages } = await getGlobalNotionData({ from })
|
||||||
return {
|
return {
|
||||||
paths: allPages.map(row => ({ params: { slug: [row.slug] } })),
|
paths: allPages?.map(row => ({ params: { slug: [row.slug] } })),
|
||||||
fallback: true
|
fallback: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import BLOG from 'blog.config'
|
import BLOG from 'blog.config'
|
||||||
import React from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
|
|
||||||
import 'animate.css'
|
import 'animate.css'
|
||||||
@@ -20,10 +20,11 @@ import { Sakura } from '@/components/Sakura'
|
|||||||
import { StarrySky } from '@/components/StarrySky'
|
import { StarrySky } from '@/components/StarrySky'
|
||||||
import MusicPlayer from '@/components/MusicPlayer'
|
import MusicPlayer from '@/components/MusicPlayer'
|
||||||
import ExternalScript from '@/components/ExternalScript'
|
import ExternalScript from '@/components/ExternalScript'
|
||||||
import { isBrowser } from '@/lib/utils'
|
import smoothscroll from 'smoothscroll-polyfill'
|
||||||
|
|
||||||
import AOS from 'aos'
|
import AOS from 'aos'
|
||||||
import 'aos/dist/aos.css' // You can also use <link> for styles
|
import 'aos/dist/aos.css' // You can also use <link> for styles
|
||||||
|
import { isMobile } from '@/lib/utils'
|
||||||
|
|
||||||
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
|
const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false })
|
||||||
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
|
const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
|
||||||
@@ -55,14 +56,17 @@ const MyApp = ({ Component, pageProps }) => {
|
|||||||
<ExternalScript/>
|
<ExternalScript/>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
if (isBrowser()) {
|
useEffect(() => {
|
||||||
AOS.init()
|
AOS.init()
|
||||||
}
|
if (isMobile()) {
|
||||||
|
smoothscroll.polyfill()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GlobalContextProvider>
|
<GlobalContextProvider>
|
||||||
{externalPlugins}
|
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
{externalPlugins}
|
||||||
</GlobalContextProvider>
|
</GlobalContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { getGlobalNotionData } from '@/lib/notion/getNotionData'
|
|||||||
import * as ThemeMap from '@/themes'
|
import * as ThemeMap from '@/themes'
|
||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import { generateRss } from '@/lib/rss'
|
import { generateRss } from '@/lib/rss'
|
||||||
|
import { generateRobotsTxt } from '@/lib/robots.txt'
|
||||||
const Index = props => {
|
const Index = props => {
|
||||||
const { theme } = useGlobal()
|
const { theme } = useGlobal()
|
||||||
const ThemeComponents = ThemeMap[theme]
|
const ThemeComponents = ThemeMap[theme]
|
||||||
@@ -17,7 +18,6 @@ export async function getStaticProps() {
|
|||||||
const { siteInfo } = props
|
const { siteInfo } = props
|
||||||
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')
|
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')
|
||||||
|
|
||||||
delete props.allPages
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: `${siteInfo?.title} | ${siteInfo?.description}`,
|
title: `${siteInfo?.title} | ${siteInfo?.description}`,
|
||||||
description: siteInfo?.description,
|
description: siteInfo?.description,
|
||||||
@@ -43,8 +43,14 @@ export async function getStaticProps() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 异步生成Feed订阅
|
// 生成robotTxt
|
||||||
generateRss(props?.latestPosts || [])
|
generateRobotsTxt()
|
||||||
|
// 生成Feed订阅
|
||||||
|
if (JSON.parse(BLOG.ENABLE_RSS)) {
|
||||||
|
generateRss(props?.latestPosts || [])
|
||||||
|
}
|
||||||
|
|
||||||
|
delete props.allPages
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ async function filterByMemCache(allPosts, keyword) {
|
|||||||
const articleInfo = post.title + post.summary + tagContent + categoryContent
|
const articleInfo = post.title + post.summary + tagContent + categoryContent
|
||||||
let hit = articleInfo.toLowerCase().indexOf(keyword) > -1
|
let hit = articleInfo.toLowerCase().indexOf(keyword) > -1
|
||||||
let indexContent = [post.summary]
|
let indexContent = [post.summary]
|
||||||
if (page && page.block) {
|
// 防止搜到加密文章的内容
|
||||||
|
if (page && page.block && !post.password) {
|
||||||
const contentIds = Object.keys(page.block)
|
const contentIds = Object.keys(page.block)
|
||||||
contentIds.forEach(id => {
|
contentIds.forEach(id => {
|
||||||
const properties = page?.block[id]?.value?.properties
|
const properties = page?.block[id]?.value?.properties
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const getServerSideProps = async (ctx) => {
|
|||||||
priority: '0.7'
|
priority: '0.7'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
const postFields = allPages?.map(post => {
|
const postFields = allPages?.filter(p => p.status === BLOG.NOTION_PROPERTY_NAME.status_publish)?.map(post => {
|
||||||
return {
|
return {
|
||||||
loc: `${BLOG.LINK}/${post.slug}`,
|
loc: `${BLOG.LINK}/${post.slug}`,
|
||||||
lastmod: new Date(post?.date?.start_date || post?.createdTime).toISOString().split('T')[0],
|
lastmod: new Date(post?.date?.start_date || post?.createdTime).toISOString().split('T')[0],
|
||||||
@@ -49,10 +49,10 @@ export const getServerSideProps = async (ctx) => {
|
|||||||
const fields = defaultFields.concat(postFields)
|
const fields = defaultFields.concat(postFields)
|
||||||
|
|
||||||
// 缓存
|
// 缓存
|
||||||
// ctx.res.setHeader(
|
ctx.res.setHeader(
|
||||||
// 'Cache-Control',
|
'Cache-Control',
|
||||||
// 'public, s-maxage=10, stale-while-revalidate=59'
|
'public, max-age=3600, stale-while-revalidate=59'
|
||||||
// )
|
)
|
||||||
|
|
||||||
return getServerSideSitemap(ctx, fields)
|
return getServerSideSitemap(ctx, fields)
|
||||||
}
|
}
|
||||||
|
|||||||
11
public/avatar.svg
Normal file
11
public/avatar.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0)">
|
||||||
|
<rect width="200" height="200" fill="#111111"/>
|
||||||
|
<path d="M200.563 83.7392H169V144.594H168.092L126.765 83.7392H99.9706V200H131.534V138.918H132.215L174.223 200H200.563V83.7392Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0">
|
||||||
|
<rect width="200" height="200" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 426 B |
BIN
public/bg_image.jpg
Normal file → Executable file
BIN
public/bg_image.jpg
Normal file → Executable file
Binary file not shown.
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 16 KiB |
5
public/css/img-shadow.css
Normal file
5
public/css/img-shadow.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/* 图片阴影 */
|
||||||
|
#notion-article img{
|
||||||
|
box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
29
public/css/theme-fukasawa.css
Normal file
29
public/css/theme-fukasawa.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#theme-fukasawa .grid-item {
|
||||||
|
height: auto;
|
||||||
|
break-inside: avoid-column;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 大屏幕(宽度≥1024px)下显示3列 */
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
#theme-fukasawa .grid-container {
|
||||||
|
column-count: 3;
|
||||||
|
column-gap: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 小屏幕(宽度≥640px)下显示2列 */
|
||||||
|
@media (min-width: 640px) and (max-width: 1023px) {
|
||||||
|
#theme-fukasawa .grid-container {
|
||||||
|
column-count: 2;
|
||||||
|
column-gap: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端(宽度<640px)下显示1列 */
|
||||||
|
@media (max-width: 639px) {
|
||||||
|
#theme-fukasawa .grid-container {
|
||||||
|
column-count: 1;
|
||||||
|
column-gap: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
public/css/theme-simple.css
Normal file
33
public/css/theme-simple.css
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#theme-simple #announcement-content {
|
||||||
|
/* background-color: #f6f6f6; */
|
||||||
|
}
|
||||||
|
|
||||||
|
#theme-simple #blog-item-title {
|
||||||
|
color: #276077;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #theme-simple #blog-item-title {
|
||||||
|
color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notion {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 菜单下划线动画 */
|
||||||
|
.menu-link {
|
||||||
|
text-decoration: none;
|
||||||
|
background-image: linear-gradient(#dd3333, #dd3333);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: bottom center;
|
||||||
|
background-size: 0 2px;
|
||||||
|
transition: background-size 100ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-link:hover {
|
||||||
|
background-size: 100% 2px;
|
||||||
|
color: #dd3333;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -167,6 +167,13 @@ nav {
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.waline-recent-content .wl-emoji {
|
||||||
|
height: 1.1rem !important;
|
||||||
|
display: inline-block !important;
|
||||||
|
line-height: 1.25rem !important;
|
||||||
|
vertical-align: text-bottom !important;
|
||||||
|
}
|
||||||
|
|
||||||
.vcontent .wl-emoji {
|
.vcontent .wl-emoji {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
@@ -204,7 +211,7 @@ nav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nobelium .notion-code{
|
.nobelium .notion-code{
|
||||||
@apply max-w-2xl;
|
/* @apply max-w-2xl; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.next #announcement-content *{
|
.next #announcement-content *{
|
||||||
|
|||||||
@@ -180,7 +180,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notion-simple-table {
|
.notion-simple-table {
|
||||||
@apply whitespace-nowrap overflow-x-scroll block
|
@apply whitespace-nowrap overflow-x-auto block
|
||||||
}
|
}
|
||||||
|
|
||||||
.notion-app {
|
.notion-app {
|
||||||
@@ -207,7 +207,6 @@
|
|||||||
|
|
||||||
.medium-zoom-image {
|
.medium-zoom-image {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@apply bg-gray-100 dark:bg-black
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.medium-zoom-image--opened {
|
.medium-zoom-image--opened {
|
||||||
@@ -939,7 +938,7 @@ code[class*='language-'] {
|
|||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
font-size: 1.3em;
|
font-size: 1em;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1118,10 +1117,11 @@ code[class*='language-'] {
|
|||||||
.notion-table-of-contents {
|
.notion-table-of-contents {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
|
@apply bg-gray-50 dark:bg-black p-2
|
||||||
}
|
}
|
||||||
|
|
||||||
.notion-table-of-contents-item {
|
.notion-table-of-contents-item {
|
||||||
color: inherit;
|
/* color: inherit; */
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: background 20ms ease-in 0s;
|
transition: background 20ms ease-in 0s;
|
||||||
@@ -1137,6 +1137,8 @@ code[class*='language-'] {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
@apply text-blue-600 dark:text-blue-200
|
||||||
}
|
}
|
||||||
|
|
||||||
.notion-table-of-contents-item:hover {
|
.notion-table-of-contents-item:hover {
|
||||||
@@ -2009,3 +2011,10 @@ code.language-mermaid {
|
|||||||
.notion-equation-inline .katex-display {
|
.notion-equation-inline .katex-display {
|
||||||
margin: 0 0 !important;
|
margin: 0 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.two-line-clamp {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -21,9 +21,10 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
maxWidth: {
|
maxWidth: {
|
||||||
side: '14rem'
|
side: '14rem',
|
||||||
|
'9/10': '90%'
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
extend: {}
|
extend: {}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import BLOG from '@/blog.config'
|
|||||||
const LayoutBase = props => {
|
const LayoutBase = props => {
|
||||||
const { children, meta } = props
|
const { children, meta } = props
|
||||||
return (
|
return (
|
||||||
<div className='dark:text-gray-300 bg-white dark:bg-black'>
|
<div id='theme-example' className='dark:text-gray-300 bg-white dark:bg-black'>
|
||||||
<CommonHead meta={meta} />
|
<CommonHead meta={meta} />
|
||||||
{/* 顶栏LOGO */}
|
{/* 顶栏LOGO */}
|
||||||
<Header {...props} />
|
<Header {...props} />
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const ArticleLock = props => {
|
|||||||
<div className='text-center space-y-3'>
|
<div className='text-center space-y-3'>
|
||||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||||
<div className='flex mx-4'>
|
<div className='flex mx-4'>
|
||||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'></input>
|
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition font-light leading-10 text-black dark:bg-gray-500 bg-gray-50'></input>
|
||||||
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 rounded-r duration-300 bg-gray-300" >
|
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 rounded-r duration-300 bg-gray-300" >
|
||||||
<i className={'duration-200 cursor-pointer fas fa-key dark:text-black'} > {locale.COMMON.SUBMIT}</i>
|
<i className={'duration-200 cursor-pointer fas fa-key dark:text-black'} > {locale.COMMON.SUBMIT}</i>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,5 +78,5 @@ export const BlogListScroll = props => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ export const Header = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import CONFIG_EMPTY from '../config_empty'
|
import CONFIG_EXAMPLE from '../config_example'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单导航
|
* 菜单导航
|
||||||
@@ -11,10 +11,10 @@ export const Nav = (props) => {
|
|||||||
const { customNav } = props
|
const { customNav } = props
|
||||||
const { locale } = useGlobal()
|
const { locale } = useGlobal()
|
||||||
let links = [
|
let links = [
|
||||||
{ icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_EMPTY.MENU_SEARCH },
|
{ icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_EXAMPLE.MENU_SEARCH },
|
||||||
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_EMPTY.MENU_ARCHIVE },
|
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_EXAMPLE.MENU_ARCHIVE },
|
||||||
{ icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_EMPTY.MENU_CATEGORY },
|
{ icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_EXAMPLE.MENU_CATEGORY },
|
||||||
{ icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_EMPTY.MENU_TAG }
|
{ icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_EXAMPLE.MENU_TAG }
|
||||||
]
|
]
|
||||||
|
|
||||||
if (customNav) {
|
if (customNav) {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
|||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
type='text'
|
type='text'
|
||||||
placeholder={currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`}
|
placeholder={currentTag ? `${locale.SEARCH.TAGS} #${currentTag}` : `${locale.SEARCH.ARTICLES}`}
|
||||||
className={'w-full text-sm pl-4 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
|
className={'outline-none w-full text-sm pl-4 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
onCompositionStart={lockSearchInput}
|
onCompositionStart={lockSearchInput}
|
||||||
onCompositionUpdate={lockSearchInput}
|
onCompositionUpdate={lockSearchInput}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const CONFIG_EMPTY = {
|
const CONFIG_EXAMPLE = {
|
||||||
// 菜单配置
|
// 菜单配置
|
||||||
MENU_CATEGORY: true, // 显示分类
|
MENU_CATEGORY: true, // 显示分类
|
||||||
MENU_TAG: true, // 显示标签
|
MENU_TAG: true, // 显示标签
|
||||||
MENU_ARCHIVE: true, // 显示归档
|
MENU_ARCHIVE: true, // 显示归档
|
||||||
MENU_SEARCH: true // 显示搜索
|
MENU_SEARCH: true // 显示搜索
|
||||||
}
|
}
|
||||||
export default CONFIG_EMPTY
|
export default CONFIG_EXAMPLE
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import CONFIG_EMPTY from './config_empty'
|
import CONFIG_EXAMPLE from './config_example'
|
||||||
import { LayoutIndex } from './LayoutIndex'
|
import { LayoutIndex } from './LayoutIndex'
|
||||||
import { LayoutSearch } from './LayoutSearch'
|
import { LayoutSearch } from './LayoutSearch'
|
||||||
import { LayoutArchive } from './LayoutArchive'
|
import { LayoutArchive } from './LayoutArchive'
|
||||||
@@ -11,7 +11,7 @@ import { LayoutTag } from './LayoutTag'
|
|||||||
import { LayoutTagIndex } from './LayoutTagIndex'
|
import { LayoutTagIndex } from './LayoutTagIndex'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CONFIG_EMPTY as THEME_CONFIG,
|
CONFIG_EXAMPLE as THEME_CONFIG,
|
||||||
LayoutIndex,
|
LayoutIndex,
|
||||||
LayoutSearch,
|
LayoutSearch,
|
||||||
LayoutArchive,
|
LayoutArchive,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import TopNav from './components/TopNav'
|
|||||||
import AsideLeft from './components/AsideLeft'
|
import AsideLeft from './components/AsideLeft'
|
||||||
import Live2D from '@/components/Live2D'
|
import Live2D from '@/components/Live2D'
|
||||||
import BLOG from '@/blog.config'
|
import BLOG from '@/blog.config'
|
||||||
|
import { isBrowser, loadExternalResource } from '@/lib/utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
|
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
|
||||||
@@ -25,7 +26,12 @@ const LayoutBase = (props) => {
|
|||||||
meta
|
meta
|
||||||
} = props
|
} = props
|
||||||
const leftAreaSlot = <Live2D/>
|
const leftAreaSlot = <Live2D/>
|
||||||
return (<>
|
|
||||||
|
if (isBrowser()) {
|
||||||
|
loadExternalResource('/css/theme-fukasawa.css', 'css')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<div id='theme-fukasawa' >
|
||||||
<CommonHead meta={meta} />
|
<CommonHead meta={meta} />
|
||||||
<TopNav {...props}/>
|
<TopNav {...props}/>
|
||||||
<div className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + ' flex'}>
|
<div className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + ' flex'}>
|
||||||
@@ -38,7 +44,7 @@ const LayoutBase = (props) => {
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LayoutBase
|
export default LayoutBase
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const ArticleLock = props => {
|
|||||||
<div className="flex mx-4">
|
<div className="flex mx-4">
|
||||||
<input
|
<input
|
||||||
id="password" type='password'
|
id="password" type='password'
|
||||||
className="w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500"
|
className="outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500"
|
||||||
></input>
|
></input>
|
||||||
<div
|
<div
|
||||||
onClick={submitPassword}
|
onClick={submitPassword}
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import BLOG from '@/blog.config'
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CONFIG_FUKA from '../config_fuka'
|
import CONFIG_FUKA from '../config_fuka'
|
||||||
import Card from './Card'
|
|
||||||
|
|
||||||
const BlogCard = ({ post, showSummary, siteInfo }) => {
|
const BlogCard = ({ index, post, showSummary, siteInfo }) => {
|
||||||
const showPreview = CONFIG_FUKA.POST_LIST_PREVIEW && post.blockMap
|
const showPreview = CONFIG_FUKA.POST_LIST_PREVIEW && post.blockMap
|
||||||
// matery 主题默认强制显示图片
|
// matery 主题默认强制显示图片
|
||||||
if (post && !post.page_cover) {
|
if (post && !post.page_cover) {
|
||||||
@@ -13,44 +12,45 @@ const BlogCard = ({ post, showSummary, siteInfo }) => {
|
|||||||
const showPageCover = CONFIG_FUKA.POST_LIST_COVER && post?.page_cover
|
const showPageCover = CONFIG_FUKA.POST_LIST_COVER && post?.page_cover
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full lg:max-w-sm p-2 h-full overflow-auto">
|
<div data-aos="fade-up" data-aos-duration="500" data-aos-once="true"
|
||||||
<div
|
className='w-full lg:max-w-sm p-2 h-full overflow-auto'>
|
||||||
key={post.id}
|
<section className="shadow mb-4 p-2 bg-white dark:bg-hexo-black-gray hover:shadow-lg duration-200">
|
||||||
className="flex flex-col-reverse justify-between duration-300"
|
<div className="flex flex-col-reverse justify-between duration-300">
|
||||||
>
|
<div className="p-2 flex flex-col w-full">
|
||||||
<div className="p-2 flex flex-col w-full">
|
<Link
|
||||||
<Link
|
href={`${BLOG.SUB_PATH}/${post.slug}`}
|
||||||
href={`${BLOG.SUB_PATH}/${post.slug}`}
|
passHref
|
||||||
passHref
|
className={`break-words cursor-pointer font-bold hover:underline text-xl ${showPreview ? 'justify-center' : 'justify-start'}
|
||||||
className={`cursor-pointer font-bold hover:underline text-xl ${showPreview ? 'justify-center' : 'justify-start'
|
leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}>
|
||||||
} leading-tight text-gray-700 dark:text-gray-100 hover:text-blue-500 dark:hover:text-blue-400`}>
|
|
||||||
|
|
||||||
{post.title}
|
{post.title}
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{(!showPreview || showSummary) && (
|
{(!showPreview || showSummary) && (
|
||||||
<p className="mt-4 mb-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7 overflow-hidden">
|
<p className="mt-4 mb-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7 overflow-hidden">
|
||||||
{post.summary}
|
{post.summary}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{showPageCover && (
|
|
||||||
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
|
|
||||||
<div className="h-40 w-full relative duration-200 cursor-pointer transform overflow-hidden">
|
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
||||||
<img
|
|
||||||
src={post?.page_cover}
|
|
||||||
alt={post.title}
|
|
||||||
className="w-full hover:scale-125 transform duration-500"
|
|
||||||
></img>
|
|
||||||
{/* <Image className='hover:scale-105 transform duration-500' src={post?.page_cover} alt={post.title} layout='fill' objectFit='cover' loading='lazy' /> */}
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
|
||||||
)}
|
{showPageCover && (
|
||||||
</div>
|
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
|
||||||
</Card>
|
<div className="h-40 w-full relative duration-200 cursor-pointer transform overflow-hidden">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={post?.page_cover}
|
||||||
|
alt={post.title}
|
||||||
|
className="w-full hover:scale-125 transform duration-500"
|
||||||
|
></img>
|
||||||
|
{/* <Image className='hover:scale-105 transform duration-500' src={post?.page_cover} alt={post.title} layout='fill' objectFit='cover' loading='lazy' /> */}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import BLOG from '@/blog.config'
|
import BLOG from '@/blog.config'
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import BlogCard from './BlogCard'
|
import BlogCard from './BlogCard'
|
||||||
import BlogPostListEmpty from './BlogListEmpty'
|
import BlogPostListEmpty from './BlogListEmpty'
|
||||||
import PaginationSimple from './PaginationSimple'
|
import PaginationSimple from './PaginationSimple'
|
||||||
@@ -15,25 +14,6 @@ import PaginationSimple from './PaginationSimple'
|
|||||||
const BlogListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
|
const BlogListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
|
||||||
const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
|
const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
|
||||||
const showNext = page < totalPage
|
const showNext = page < totalPage
|
||||||
const [colCount, changeCol] = useState(1)
|
|
||||||
|
|
||||||
function updateCol() {
|
|
||||||
if (window.outerWidth > 1200) {
|
|
||||||
changeCol(3)
|
|
||||||
} else if (window.outerWidth > 900) {
|
|
||||||
changeCol(2)
|
|
||||||
} else {
|
|
||||||
changeCol(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateCol()
|
|
||||||
window.addEventListener('resize', updateCol)
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', updateCol)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!posts || posts.length === 0) {
|
if (!posts || posts.length === 0) {
|
||||||
return <BlogPostListEmpty />
|
return <BlogPostListEmpty />
|
||||||
@@ -41,10 +21,10 @@ const BlogListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* 文章列表 */}
|
{/* 文章列表 */}
|
||||||
<div id="container" style={{ columnCount: colCount }}>
|
<div id="container" className='grid-container'>
|
||||||
{posts?.map(post => (
|
{posts?.map(post => (
|
||||||
<div key={post.id} className='justify-center flex' style={{ breakInside: 'avoid' }}>
|
<div key={post.id} className='grid-item justify-center flex' style={{ breakInside: 'avoid' }}>
|
||||||
<BlogCard key={post.id} post={post} siteInfo={siteInfo} />
|
<BlogCard index={posts.indexOf(post)} key={post.id} post={post} siteInfo={siteInfo} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react'
|
|||||||
import BlogCard from './BlogCard'
|
import BlogCard from './BlogCard'
|
||||||
import BlogPostListEmpty from './BlogListEmpty'
|
import BlogPostListEmpty from './BlogListEmpty'
|
||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import throttle from 'lodash.throttle'
|
|
||||||
/**
|
/**
|
||||||
* 文章列表分页表格
|
* 文章列表分页表格
|
||||||
* @param page 当前页
|
* @param page 当前页
|
||||||
@@ -14,18 +14,7 @@ import throttle from 'lodash.throttle'
|
|||||||
*/
|
*/
|
||||||
const BlogListScroll = props => {
|
const BlogListScroll = props => {
|
||||||
const { posts = [], siteInfo } = props
|
const { posts = [], siteInfo } = props
|
||||||
const [colCount, changeCol] = React.useState(1)
|
|
||||||
const { locale } = useGlobal()
|
const { locale } = useGlobal()
|
||||||
|
|
||||||
function updateCol() {
|
|
||||||
if (window.outerWidth > 1200) {
|
|
||||||
changeCol(3)
|
|
||||||
} else if (window.outerWidth > 900) {
|
|
||||||
changeCol(2)
|
|
||||||
} else {
|
|
||||||
changeCol(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const targetRef = React.useRef(null)
|
const targetRef = React.useRef(null)
|
||||||
|
|
||||||
const [page, updatePage] = React.useState(1)
|
const [page, updatePage] = React.useState(1)
|
||||||
@@ -45,45 +34,41 @@ const BlogListScroll = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 监听滚动自动分页加载
|
// 监听滚动自动分页加载
|
||||||
const scrollTrigger = React.useCallback(throttle(() => {
|
const scrollTrigger = () => {
|
||||||
const scrollS = window.scrollY + window.outerHeight
|
requestAnimationFrame(() => {
|
||||||
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
|
const scrollS = window.scrollY + window.outerHeight
|
||||||
if (scrollS > clientHeight + 100) {
|
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
|
||||||
handleGetMore()
|
if (scrollS > clientHeight + 100) {
|
||||||
}
|
handleGetMore()
|
||||||
}, 500))
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
updateCol()
|
|
||||||
window.addEventListener('scroll', scrollTrigger)
|
window.addEventListener('scroll', scrollTrigger)
|
||||||
|
|
||||||
window.addEventListener('resize', updateCol)
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', updateCol)
|
|
||||||
window.removeEventListener('scroll', scrollTrigger)
|
window.removeEventListener('scroll', scrollTrigger)
|
||||||
}
|
}
|
||||||
})
|
}, [])
|
||||||
|
|
||||||
if (!posts || posts.length === 0) {
|
if (!posts || posts.length === 0) {
|
||||||
return <BlogPostListEmpty />
|
return <BlogPostListEmpty />
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div id="container" ref={targetRef} >
|
<div id="container" ref={targetRef} className='grid-container' >
|
||||||
{/* 文章列表 */}
|
{/* 文章列表 */}
|
||||||
<div style={{ columnCount: colCount }}>
|
{postsToShow?.map(post => (
|
||||||
{postsToShow?.map(post => (
|
<div key={post.id} className='grid-item justify-center flex' style={{ breakInside: 'avoid' }}>
|
||||||
<div key={post.id} className='justify-center flex' style={{ breakInside: 'avoid' }}>
|
<BlogCard index={posts.indexOf(post)} key={post.id} post={post} siteInfo={siteInfo} />
|
||||||
<BlogCard key={post.id} post={post} siteInfo={siteInfo} />
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="w-full my-4 py-4 text-center cursor-pointer "
|
<div className="w-full my-4 py-4 text-center cursor-pointer "
|
||||||
onClick={handleGetMore}>
|
onClick={handleGetMore}>
|
||||||
{' '}
|
{' '}
|
||||||
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
|
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
const Card = ({ children, headerSlot, className }) => {
|
const Card = ({ children, headerSlot, className }) => {
|
||||||
return <div className={className}
|
return <div data-aos="fade-in" data-aos-duration="1000" className={className}>
|
||||||
data-aos="fade-up"
|
|
||||||
data-aos-duration="600"
|
|
||||||
data-aos-easing="ease-in-out"
|
|
||||||
data-aos-once="false"
|
|
||||||
data-aos-anchor-placement="top-bottom"
|
|
||||||
>
|
|
||||||
<>{headerSlot}</>
|
<>{headerSlot}</>
|
||||||
<section className="shadow mb-4 p-2 bg-white dark:bg-hexo-black-gray hover:shadow-lg duration-200">
|
<section className="shadow mb-4 p-2 bg-white dark:bg-hexo-black-gray hover:shadow-lg duration-200">
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const Catalog = ({ toc }) => {
|
|||||||
|
|
||||||
// 同步选中目录事件
|
// 同步选中目录事件
|
||||||
const [activeSection, setActiveSection] = React.useState(null)
|
const [activeSection, setActiveSection] = React.useState(null)
|
||||||
const throttleMs = 100
|
const throttleMs = 200
|
||||||
const actionSectionScrollSpy = React.useCallback(throttle(() => {
|
const actionSectionScrollSpy = React.useCallback(throttle(() => {
|
||||||
const sections = document.getElementsByClassName('notion-h')
|
const sections = document.getElementsByClassName('notion-h')
|
||||||
let prevBBox = null
|
let prevBBox = null
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
|
|||||||
<input
|
<input
|
||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
type='text'
|
type='text'
|
||||||
className={'w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-800 dark:text-white'}
|
className={'outline-none w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-800 dark:text-white'}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
onCompositionStart={lockSearchInput}
|
onCompositionStart={lockSearchInput}
|
||||||
onCompositionUpdate={lockSearchInput}
|
onCompositionUpdate={lockSearchInput}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import CommonHead from '@/components/CommonHead'
|
import CommonHead from '@/components/CommonHead'
|
||||||
import { useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
import throttle from 'lodash.throttle'
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import JumpToTopButton from './components/JumpToTopButton'
|
import JumpToTopButton from './components/JumpToTopButton'
|
||||||
import SideRight from './components/SideRight'
|
import SideRight from './components/SideRight'
|
||||||
import TopNav from './components/TopNav'
|
import TopNav from './components/TopNav'
|
||||||
import smoothscroll from 'smoothscroll-polyfill'
|
|
||||||
import FloatDarkModeButton from './components/FloatDarkModeButton'
|
import FloatDarkModeButton from './components/FloatDarkModeButton'
|
||||||
import Live2D from '@/components/Live2D'
|
import Live2D from '@/components/Live2D'
|
||||||
import LoadingCover from './components/LoadingCover'
|
import LoadingCover from './components/LoadingCover'
|
||||||
@@ -15,7 +14,13 @@ import dynamic from 'next/dynamic'
|
|||||||
|
|
||||||
const FacebookPage = dynamic(
|
const FacebookPage = dynamic(
|
||||||
() => {
|
() => {
|
||||||
return import('@/components/FacebookPage')
|
let facebook = <></>
|
||||||
|
try {
|
||||||
|
facebook = import('@/components/FacebookPage')
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
return facebook
|
||||||
},
|
},
|
||||||
{ ssr: false }
|
{ ssr: false }
|
||||||
)
|
)
|
||||||
@@ -28,7 +33,7 @@ const FacebookPage = dynamic(
|
|||||||
*/
|
*/
|
||||||
const LayoutBase = props => {
|
const LayoutBase = props => {
|
||||||
const { children, headerSlot, floatSlot, meta, siteInfo } = props
|
const { children, headerSlot, floatSlot, meta, siteInfo } = props
|
||||||
const [show, switchShow] = useState(false)
|
const [showFloatButton, switchShow] = useState(false)
|
||||||
// const [percent, changePercent] = useState(0) // 页面阅读百分比
|
// const [percent, changePercent] = useState(0) // 页面阅读百分比
|
||||||
const rightAreaSlot = (
|
const rightAreaSlot = (
|
||||||
<>
|
<>
|
||||||
@@ -37,8 +42,8 @@ const LayoutBase = props => {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
const { onLoading } = useGlobal()
|
const { onLoading } = useGlobal()
|
||||||
|
const throttleMs = 200
|
||||||
const scrollListener = () => {
|
const scrollListener = useCallback(throttle(() => {
|
||||||
const targetRef = document.getElementById('wrapper')
|
const targetRef = document.getElementById('wrapper')
|
||||||
const clientHeight = targetRef?.clientHeight
|
const clientHeight = targetRef?.clientHeight
|
||||||
const scrollY = window.pageYOffset
|
const scrollY = window.pageYOffset
|
||||||
@@ -47,26 +52,24 @@ const LayoutBase = props => {
|
|||||||
if (per > 100) per = 100
|
if (per > 100) per = 100
|
||||||
const shouldShow = scrollY > 100 && per > 0
|
const shouldShow = scrollY > 100 && per > 0
|
||||||
|
|
||||||
if (shouldShow !== show) {
|
if (shouldShow !== showFloatButton) {
|
||||||
switchShow(shouldShow)
|
switchShow(shouldShow)
|
||||||
}
|
}
|
||||||
// changePercent(per)
|
}, throttleMs))
|
||||||
}
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
smoothscroll.polyfill()
|
|
||||||
document.addEventListener('scroll', scrollListener)
|
document.addEventListener('scroll', scrollListener)
|
||||||
return () => document.removeEventListener('scroll', scrollListener)
|
return () => document.removeEventListener('scroll', scrollListener)
|
||||||
}, [show])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-hexo-background-gray dark:bg-black">
|
<div id='theme-hexo'>
|
||||||
<CommonHead meta={meta} siteInfo={siteInfo}/>
|
<CommonHead meta={meta} siteInfo={siteInfo}/>
|
||||||
|
|
||||||
<TopNav {...props} />
|
<TopNav {...props} />
|
||||||
|
|
||||||
{headerSlot}
|
{headerSlot}
|
||||||
|
|
||||||
<main id="wrapper" className="w-full py-8 md:px-8 lg:px-24 min-h-screen relative">
|
<main id="wrapper" className="bg-hexo-background-gray dark:bg-black w-full py-8 md:px-8 lg:px-24 min-h-screen relative">
|
||||||
<div
|
<div
|
||||||
id="container-inner"
|
id="container-inner"
|
||||||
className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + ' pt-14 w-full mx-auto lg:flex lg:space-x-4 justify-center relative z-10'}
|
className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + ' pt-14 w-full mx-auto lg:flex lg:space-x-4 justify-center relative z-10'}
|
||||||
@@ -79,13 +82,8 @@ const LayoutBase = props => {
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* 右下角悬浮 */}
|
{/* 右下角悬浮 */}
|
||||||
<div className="bottom-12 right-1 fixed justify-end z-20 text-white bg-indigo-500 dark:bg-hexo-black-gray rounded-sm">
|
<div className={(showFloatButton ? 'opacity-100 ' : 'invisible opacity-0') + ' duration-300 transition-all bottom-12 right-1 fixed justify-end z-20 text-white bg-indigo-500 dark:bg-hexo-black-gray rounded-sm'}>
|
||||||
<div
|
<div className={'justify-center flex flex-col items-center cursor-pointer'}>
|
||||||
className={
|
|
||||||
(show ? 'animate__animated ' : 'hidden') +
|
|
||||||
' animate__fadeInUp justify-center duration-300 animate__faster flex flex-col items-center cursor-pointer '
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FloatDarkModeButton />
|
<FloatDarkModeButton />
|
||||||
{floatSlot}
|
{floatSlot}
|
||||||
<JumpToTopButton />
|
<JumpToTopButton />
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import LayoutBase from './LayoutBase'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export const LayoutIndex = (props) => {
|
export const LayoutIndex = (props) => {
|
||||||
return <LayoutBase {...props} headerSlot={CONFIG_HEXO.HOME_BANNER_ENABLE && <Header {...props} />}>
|
const headerSlot = CONFIG_HEXO.HOME_BANNER_ENABLE && <Header {...props} />
|
||||||
|
return <LayoutBase {...props} headerSlot={headerSlot}>
|
||||||
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
{BLOG.POST_LIST_STYLE === 'page' ? <BlogPostListPage {...props} /> : <BlogPostListScroll {...props} />}
|
||||||
</LayoutBase>
|
</LayoutBase>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const LayoutSlug = props => {
|
|||||||
showTag={false}
|
showTag={false}
|
||||||
floatSlot={floatSlot}
|
floatSlot={floatSlot}
|
||||||
>
|
>
|
||||||
<div className="w-full lg:shadow-sm lg:hover:shadow lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
|
<div className="w-full lg:hover:shadow lg:border lg:rounded-xl lg:px-2 lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
|
||||||
{lock && <ArticleLock validPassword={validPassword} />}
|
{lock && <ArticleLock validPassword={validPassword} />}
|
||||||
|
|
||||||
{!lock && <div id="container" className="overflow-x-auto flex-grow mx-auto md:w-full md:px-5 ">
|
{!lock && <div id="container" className="overflow-x-auto flex-grow mx-auto md:w-full md:px-5 ">
|
||||||
|
|||||||
@@ -6,14 +6,8 @@ const NotionPage = dynamic(() => import('@/components/NotionPage'))
|
|||||||
const Announcement = ({ post, className }) => {
|
const Announcement = ({ post, className }) => {
|
||||||
const { locale } = useGlobal()
|
const { locale } = useGlobal()
|
||||||
if (post?.blockMap) {
|
if (post?.blockMap) {
|
||||||
return <div
|
return <div className={className}>
|
||||||
data-aos="fade-up"
|
<section id='announcement-wrapper' className="dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray">
|
||||||
data-aos-duration="600"
|
|
||||||
data-aos-easing="ease-in-out"
|
|
||||||
data-aos-once="false"
|
|
||||||
data-aos-anchor-placement="top-bottom"
|
|
||||||
className={className}>
|
|
||||||
<section id='announcement-wrapper' className="hover:shadow-md dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray">
|
|
||||||
<div><i className='mr-2 fas fa-bullhorn' />{locale.COMMON.ANNOUNCEMENT}</div>
|
<div><i className='mr-2 fas fa-bullhorn' />{locale.COMMON.ANNOUNCEMENT}</div>
|
||||||
{post && (<div id="announcement-content">
|
{post && (<div id="announcement-content">
|
||||||
<NotionPage post={post} className='text-center ' />
|
<NotionPage post={post} className='text-center ' />
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const ArticleLock = props => {
|
|||||||
<div className='text-center space-y-3'>
|
<div className='text-center space-y-3'>
|
||||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||||
<div className='flex mx-4'>
|
<div className='flex mx-4'>
|
||||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'></input>
|
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black 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" >
|
<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>
|
<i className={'duration-200 cursor-pointer fas fa-key'} > {locale.COMMON.SUBMIT}</i>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,107 +1,57 @@
|
|||||||
import BLOG from '@/blog.config'
|
import BLOG from '@/blog.config'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import TagItemMini from './TagItemMini'
|
|
||||||
import CONFIG_HEXO from '../config_hexo'
|
import CONFIG_HEXO from '../config_hexo'
|
||||||
import NotionPage from '@/components/NotionPage'
|
import { BlogPostCardInfo } from './BlogPostCardInfo'
|
||||||
|
// import Image from 'next/image'
|
||||||
|
|
||||||
const BlogPostCard = ({ post, showSummary, index, siteInfo }) => {
|
const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
|
||||||
const showPreview = CONFIG_HEXO.POST_LIST_PREVIEW && post.blockMap
|
const showPreview = CONFIG_HEXO.POST_LIST_PREVIEW && post.blockMap
|
||||||
if (post && !post.page_cover && CONFIG_HEXO.POST_LIST_COVER_DEFAULT) {
|
if (post && !post.page_cover && CONFIG_HEXO.POST_LIST_COVER_DEFAULT) {
|
||||||
post.page_cover = siteInfo?.pageCover
|
post.page_cover = siteInfo?.pageCover
|
||||||
}
|
}
|
||||||
const showPageCover = CONFIG_HEXO.POST_LIST_COVER && post?.page_cover
|
const showPageCover = CONFIG_HEXO.POST_LIST_COVER && post?.page_cover
|
||||||
|
const delay = (index % 2) * 200
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={post.id}
|
|
||||||
data-aos="fade-up"
|
data-aos="fade-up"
|
||||||
data-aos-duration="600"
|
data-aos-duration="200"
|
||||||
data-aos-easing="ease-in-out"
|
data-aos-delay={delay}
|
||||||
data-aos-once="false"
|
data-aos-once="true"
|
||||||
data-aos-anchor-placement="top-bottom"
|
data-aos-anchor-placement="top-bottom"
|
||||||
className={`flex md:flex-row flex-col-reverse ${index % 2 === 0 ? 'md:flex-row-reverse' : ''}
|
key={post.id}
|
||||||
w-full md:h-72 h-96 justify-between overflow-hidden drop-shadow-md
|
className={`flex md:flex-row flex-col-reverse ${CONFIG_HEXO.POST_LIST_IMG_CROSSOVER ? 'even:md:flex-row-reverse' : ''}
|
||||||
border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray `}>
|
w-full justify-between overflow-hidden
|
||||||
|
border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray`}>
|
||||||
|
|
||||||
<div className={`lg:p-8 p-4 flex flex-col ${showPageCover ? 'md:w-7/12 w-full' : 'w-full'}`}>
|
{/* 文字内容 */}
|
||||||
<Link
|
<BlogPostCardInfo index={index} post={post} showPageCover={showPageCover} showPreview={showPreview} showSummary={showSummary}/>
|
||||||
href={`${BLOG.SUB_PATH}/${post.slug}`}
|
|
||||||
passHref
|
|
||||||
className={`replace cursor-pointer hover:underline text-2xl ${showPreview ? 'text-center' : ''
|
|
||||||
} leading-tight text-gray-600 dark:text-gray-100 hover:text-indigo-700 dark:hover:text-indigo-400`}>
|
|
||||||
|
|
||||||
{post.title}
|
|
||||||
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`flex mt-2 items-center ${showPreview ? 'justify-center' : 'justify-start'
|
|
||||||
} flex-wrap dark:text-gray-500 text-gray-400 hover:text-indigo-700 dark:hover:text-indigo-400`}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
|
||||||
passHref
|
|
||||||
className="font-light hover:underline cursor-pointer text-sm leading-4 mr-3">
|
|
||||||
|
|
||||||
<i className="far fa-calendar-alt mr-1" />
|
|
||||||
{post.date?.start_date || post.lastEditedTime}
|
|
||||||
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{(!showPreview || showSummary) && !post.results && (
|
|
||||||
<p style={{ overflow: 'hidden', textOverflow: 'ellipsis', display: '-webkit-box', WebkitLineClamp: '4', WebkitBoxOrient: 'vertical' }}
|
|
||||||
className="replace h-full max-h-32 my-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
|
||||||
{post.summary}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 搜索结果 */}
|
|
||||||
{post.results && (
|
|
||||||
<p className="mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
|
||||||
{post.results.map(r => (
|
|
||||||
<span key={r}>{r}</span>
|
|
||||||
))}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showPreview && (
|
|
||||||
<div className="overflow-ellipsis truncate">
|
|
||||||
<NotionPage post={post} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="text-gray-400 justify-between flex">
|
|
||||||
<Link
|
|
||||||
href={`/category/${post.category}`}
|
|
||||||
passHref
|
|
||||||
className="cursor-pointer font-light text-sm hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform">
|
|
||||||
|
|
||||||
<i className="mr-1 far fa-folder" />
|
|
||||||
{post.category}
|
|
||||||
|
|
||||||
</Link>
|
|
||||||
<div className="md:flex-nowrap flex-wrap md:justify-start inline-block">
|
|
||||||
<div>
|
|
||||||
{' '}
|
|
||||||
{post.tagItems.map(tag => (
|
|
||||||
<TagItemMini key={tag.name} tag={tag} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{/* 图片封面 */}
|
||||||
{showPageCover && !showPreview && post?.page_cover && (
|
{showPageCover && !showPreview && post?.page_cover && (
|
||||||
<div className="flex relative duration-200 cursor-pointer transform overflow-hidden md:w-5/12 ">
|
<div className="h-auto md:w-5/12">
|
||||||
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
|
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
{/* <img
|
||||||
src={post?.page_cover}
|
src={post?.page_cover}
|
||||||
alt={post.title}
|
alt={post.title}
|
||||||
className="h-full w-full hover:scale-125 transform object-cover duration-500"
|
loading='lazy'
|
||||||
/>
|
className="w-full relative cursor-pointer object-cover duration-200 hover:scale-125 "
|
||||||
{/* <Image className='hover:scale-125 transform duration-500' src={post?.page_cover} alt={post.title} layout='fill' objectFit='cover' loading='lazy' /> */}
|
/> */}
|
||||||
|
<div className='bg-center bg-cover md:h-full h-52' style={{ backgroundImage: `url('${post?.page_cover}')` }}/>
|
||||||
|
|
||||||
|
{/* <div className='relative w-full h-full'>
|
||||||
|
<Image
|
||||||
|
className='hover:scale-125 transition cursor-pointer duration-500'
|
||||||
|
src={post?.page_cover}
|
||||||
|
alt={post.title}
|
||||||
|
quality={30}
|
||||||
|
placeholder='blur'
|
||||||
|
blurDataURL='/bg_image.jpg'
|
||||||
|
style={{ objectFit: 'cover' }}
|
||||||
|
fill/>
|
||||||
|
</div> */}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
89
themes/hexo/components/BlogPostCardInfo.js
Normal file
89
themes/hexo/components/BlogPostCardInfo.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import BLOG from '@/blog.config'
|
||||||
|
import NotionPage from '@/components/NotionPage'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import TagItemMini from './TagItemMini'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 博客列表的文字内容
|
||||||
|
* @param {*} param0
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const BlogPostCardInfo = ({ post, showPreview, showPageCover, showSummary }) => {
|
||||||
|
return <div className={`h-56 flex flex-col justify-between lg:p-6 p-4 md:max-h-60 ${showPageCover ? 'md:w-7/12 w-full ' : 'w-full'}`}>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{/* 标题 */}
|
||||||
|
<Link
|
||||||
|
href={`${BLOG.SUB_PATH}/${post.slug}`}
|
||||||
|
passHref
|
||||||
|
className={`replace cursor-pointer hover:underline text-2xl ${showPreview ? 'text-center' : ''
|
||||||
|
} leading-tight text-gray-600 dark:text-gray-100 hover:text-indigo-700 dark:hover:text-indigo-400`}>
|
||||||
|
|
||||||
|
{post.title}
|
||||||
|
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* 日期 */}
|
||||||
|
<div
|
||||||
|
className={`flex mt-2 items-center ${showPreview ? 'justify-center' : 'justify-start'
|
||||||
|
} flex-wrap dark:text-gray-500 text-gray-400 hover:text-indigo-700 dark:hover:text-indigo-400`}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={`/archive#${post?.date?.start_date?.substr(0, 7)}`}
|
||||||
|
passHref
|
||||||
|
className="font-light hover:underline cursor-pointer text-sm leading-4 mr-3">
|
||||||
|
|
||||||
|
<i className="far fa-calendar-alt mr-1" />
|
||||||
|
{post.date?.start_date || post.lastEditedTime}
|
||||||
|
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 摘要 */}
|
||||||
|
{(!showPreview || showSummary) && !post.results && (
|
||||||
|
<p className="two-line-clamp replace my-3 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||||
|
{post.summary}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 搜索结果 */}
|
||||||
|
{post.results && (
|
||||||
|
<p className="two-line-clamp mt-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7">
|
||||||
|
{post.results.map(r => (
|
||||||
|
<span key={r}>{r}</span>
|
||||||
|
))}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{/* 预览 */}
|
||||||
|
{showPreview && (
|
||||||
|
<div className="overflow-ellipsis truncate">
|
||||||
|
<NotionPage post={post} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{/* 分类标签 */}
|
||||||
|
<div className="text-gray-400 justify-between flex">
|
||||||
|
<Link
|
||||||
|
href={`/category/${post.category}`}
|
||||||
|
passHref
|
||||||
|
className="cursor-pointer font-light text-sm hover:underline hover:text-indigo-700 dark:hover:text-indigo-400 transform">
|
||||||
|
|
||||||
|
<i className="mr-1 far fa-folder" />
|
||||||
|
{post.category}
|
||||||
|
|
||||||
|
</Link>
|
||||||
|
<div className="md:flex-nowrap flex-wrap md:justify-start inline-block">
|
||||||
|
<div>
|
||||||
|
{' '}
|
||||||
|
{post.tagItems.map(tag => (
|
||||||
|
<TagItemMini key={tag.name} tag={tag} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
|
|||||||
{/* 文章列表 */}
|
{/* 文章列表 */}
|
||||||
<div className="space-y-6 px-2">
|
<div className="space-y-6 px-2">
|
||||||
{posts.map(post => (
|
{posts.map(post => (
|
||||||
<BlogPostCard key={post.id} post={post} index={posts.indexOf(post)} siteInfo={siteInfo}/>
|
<BlogPostCard index={posts.indexOf(post)} key={post.id} post={post} siteInfo={siteInfo}/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{showPagination && <PaginationNumber page={page} totalPage={totalPage} />}
|
{showPagination && <PaginationNumber page={page} totalPage={totalPage} />}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
|
|||||||
import BlogPostCard from './BlogPostCard'
|
import BlogPostCard from './BlogPostCard'
|
||||||
import BlogPostListEmpty from './BlogPostListEmpty'
|
import BlogPostListEmpty from './BlogPostListEmpty'
|
||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import throttle from 'lodash.throttle'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CONFIG_HEXO from '../config_hexo'
|
import CONFIG_HEXO from '../config_hexo'
|
||||||
import { getListByPage } from '@/lib/utils'
|
import { getListByPage } from '@/lib/utils'
|
||||||
@@ -31,13 +30,15 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_HE
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 监听滚动自动分页加载
|
// 监听滚动自动分页加载
|
||||||
const scrollTrigger = React.useCallback(throttle(() => {
|
const scrollTrigger = () => {
|
||||||
const scrollS = window.scrollY + window.outerHeight
|
requestAnimationFrame(() => {
|
||||||
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
|
const scrollS = window.scrollY + window.outerHeight
|
||||||
if (scrollS > clientHeight + 100) {
|
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
|
||||||
handleGetMore()
|
if (scrollS > clientHeight + 100) {
|
||||||
}
|
handleGetMore()
|
||||||
}, 500))
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 监听滚动
|
// 监听滚动
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -58,7 +59,7 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_HE
|
|||||||
{/* 文章列表 */}
|
{/* 文章列表 */}
|
||||||
<div className='flex flex-wrap space-y-1 lg:space-y-4 px-2'>
|
<div className='flex flex-wrap space-y-1 lg:space-y-4 px-2'>
|
||||||
{postsToShow.map(post => (
|
{postsToShow.map(post => (
|
||||||
<BlogPostCard key={post.id} post={post} index={posts.indexOf(post)} showSummary={showSummary} siteInfo={siteInfo}/>
|
<BlogPostCard key={post.id} post={post} showSummary={showSummary} siteInfo={siteInfo}/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const Card = ({ children, headerSlot, className }) => {
|
const Card = ({ children, headerSlot, className }) => {
|
||||||
return <div className={className}>
|
return <div className={className}>
|
||||||
<>{headerSlot}</>
|
<>{headerSlot}</>
|
||||||
<section className=" drop-shadow-md hover:shadow-md dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray lg:duration-100">
|
<section className="shadow-md hover:shadow-md dark:text-gray-300 border dark:border-black rounded-xl px-2 py-4 bg-white dark:bg-hexo-black-gray lg:duration-100">
|
||||||
{children}
|
{children}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const Catalog = ({ toc }) => {
|
|||||||
// 同步选中目录事件
|
// 同步选中目录事件
|
||||||
const [activeSection, setActiveSection] = React.useState(null)
|
const [activeSection, setActiveSection] = React.useState(null)
|
||||||
|
|
||||||
const throttleMs = 100
|
const throttleMs = 200
|
||||||
const actionSectionScrollSpy = React.useCallback(throttle(() => {
|
const actionSectionScrollSpy = React.useCallback(throttle(() => {
|
||||||
const sections = document.getElementsByClassName('notion-h')
|
const sections = document.getElementsByClassName('notion-h')
|
||||||
let prevBBox = null
|
let prevBBox = null
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { useEffect, useState } from 'react'
|
// import Image from 'next/image'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import Typed from 'typed.js'
|
import Typed from 'typed.js'
|
||||||
import CONFIG_HEXO from '../config_hexo'
|
import CONFIG_HEXO from '../config_hexo'
|
||||||
import NavButtonGroup from './NavButtonGroup'
|
import NavButtonGroup from './NavButtonGroup'
|
||||||
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
let wrapperTop = 0
|
let wrapperTop = 0
|
||||||
let windowTop = 0
|
let windowTop = 0
|
||||||
let autoScroll = false
|
let autoScroll = false
|
||||||
|
const enableAutoScroll = false // 是否开启自动吸附滚动
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -16,6 +19,7 @@ const Header = props => {
|
|||||||
const { siteInfo } = props
|
const { siteInfo } = props
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateHeaderHeight()
|
updateHeaderHeight()
|
||||||
|
|
||||||
if (!typed && window && document.getElementById('typed')) {
|
if (!typed && window && document.getElementById('typed')) {
|
||||||
changeType(
|
changeType(
|
||||||
new Typed('#typed', {
|
new Typed('#typed', {
|
||||||
@@ -28,6 +32,7 @@ const Header = props => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableAutoScroll) {
|
if (enableAutoScroll) {
|
||||||
scrollTrigger()
|
scrollTrigger()
|
||||||
window.addEventListener('scroll', scrollTrigger)
|
window.addEventListener('scroll', scrollTrigger)
|
||||||
@@ -42,75 +47,75 @@ const Header = props => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateHeaderHeight () {
|
function updateHeaderHeight() {
|
||||||
setTimeout(() => {
|
requestAnimationFrame(() => {
|
||||||
const wrapperElement = document.getElementById('wrapper')
|
const wrapperElement = document.getElementById('wrapper')
|
||||||
wrapperTop = wrapperElement?.offsetTop
|
wrapperTop = wrapperElement?.offsetTop
|
||||||
}, 500)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const autoScrollEnd = () => {
|
||||||
|
if (autoScroll) {
|
||||||
|
windowTop = window.scrollY
|
||||||
|
autoScroll = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const throttleMs = 200
|
||||||
|
const scrollTrigger = useCallback(throttle(() => {
|
||||||
|
if (screen.width <= 768) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollS = window.scrollY
|
||||||
|
// 自动滚动
|
||||||
|
if ((scrollS > windowTop) & (scrollS < window.innerHeight) && !autoScroll
|
||||||
|
) {
|
||||||
|
autoScroll = true
|
||||||
|
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
|
||||||
|
autoScrollEnd()
|
||||||
|
}
|
||||||
|
if ((scrollS < windowTop) && (scrollS < window.innerHeight) && !autoScroll) {
|
||||||
|
autoScroll = true
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
|
autoScrollEnd()
|
||||||
|
}
|
||||||
|
windowTop = scrollS
|
||||||
|
}, throttleMs))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
id="header"
|
id="header"
|
||||||
className="duration-500 md:bg-fixed w-full bg-cover bg-center h-screen bg-black text-white relative z-10"
|
className="w-full h-screen bg-black text-white relative"
|
||||||
style={{
|
>
|
||||||
backgroundImage:
|
<div className={`w-full h-full ${CONFIG_HEXO.HOME_NAV_BACKGROUND_IMG_FIXED ? 'fixed' : ''}`}>
|
||||||
`linear-gradient(rgba(0, 0, 0, 0.9), rgba(0,0,0,0.5), rgba(0,0,0,0.3), rgba(0,0,0,0.5), rgba(0, 0, 0, 0.9) ),url("${siteInfo?.pageCover}")`
|
{/* <Image src={siteInfo.pageCover} fill
|
||||||
}}
|
style={{ objectFit: 'cover' }}
|
||||||
>
|
className='opacity-70'
|
||||||
<div className="absolute flex flex-col h-full items-center justify-center w-full ">
|
placeholder='blur'
|
||||||
<div className='text-4xl md:text-5xl text-white shadow-text'>{siteInfo?.title}</div>
|
blurDataURL='/bg_image.jpg' /> */}
|
||||||
<div className='mt-2 h-12 items-center text-center shadow-text text-white text-lg'>
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<span id='typed'/>
|
<img src={siteInfo.pageCover} className='h-full w-full object-cover opacity-70 ' />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 首页导航插件 */}
|
<div className="absolute bottom-0 flex flex-col h-full items-center justify-center w-full ">
|
||||||
{ CONFIG_HEXO.HOME_NAV_BUTTONS && <NavButtonGroup {...props}/>}
|
<div className='text-4xl md:text-5xl text-white shadow-text'>{siteInfo?.title}</div>
|
||||||
|
<div className='mt-2 h-12 items-center text-center shadow-text text-white text-lg'>
|
||||||
|
<span id='typed' />
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
{/* 首页导航插件 */}
|
||||||
<div
|
{CONFIG_HEXO.HOME_NAV_BUTTONS && <NavButtonGroup {...props} />}
|
||||||
onClick={() => {
|
|
||||||
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
|
</div>
|
||||||
}}
|
|
||||||
className="cursor-pointer w-full text-center py-4 text-3xl absolute bottom-10 text-white"
|
<div
|
||||||
>
|
onClick={() => { window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) }}
|
||||||
<i className='animate-bounce fas fa-angle-down'/>
|
className="cursor-pointer w-full text-center py-4 text-3xl absolute bottom-10 text-white"
|
||||||
</div>
|
>
|
||||||
</header>
|
<i className='animate-bounce fas fa-angle-down' />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const enableAutoScroll = false // 是否开启自动吸附滚动
|
|
||||||
|
|
||||||
const autoScrollEnd = () => {
|
|
||||||
if (autoScroll) {
|
|
||||||
windowTop = window.scrollY
|
|
||||||
autoScroll = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动吸附滚动,移动端体验不好暂时关闭
|
|
||||||
*/
|
|
||||||
const scrollTrigger = () => {
|
|
||||||
if (screen.width <= 768) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollS = window.scrollY
|
|
||||||
// 自动滚动
|
|
||||||
if ((scrollS > windowTop) & (scrollS < window.innerHeight) && !autoScroll
|
|
||||||
) {
|
|
||||||
autoScroll = true
|
|
||||||
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
|
|
||||||
setTimeout(autoScrollEnd, 500)
|
|
||||||
}
|
|
||||||
if ((scrollS < windowTop) && (scrollS < window.innerHeight) && !autoScroll) {
|
|
||||||
autoScroll = true
|
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
||||||
setTimeout(autoScrollEnd, 500)
|
|
||||||
}
|
|
||||||
windowTop = scrollS
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Header
|
export default Header
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default function HeaderArticle({ post, siteInfo }) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="header"
|
id="header"
|
||||||
className="w-full h-96 relative md:flex-shrink-0 overflow-hidden bg-cover bg-center bg-no-repeat animate__animated animate__fadeIn relative"
|
className="w-full h-96 relative md:flex-shrink-0 overflow-hidden bg-cover bg-center bg-no-repeat animate__animated animate__fadeIn"
|
||||||
style={{ backgroundImage: headerImage }}
|
style={{ backgroundImage: headerImage }}
|
||||||
>
|
>
|
||||||
<header className="animate__slideInDown animate__animated bg-black bg-opacity-70 absolute top-0 w-full h-96 py-10 flex justify-center items-center ">
|
<header className="animate__slideInDown animate__animated bg-black bg-opacity-70 absolute top-0 w-full h-96 py-10 flex justify-center items-center ">
|
||||||
@@ -65,5 +65,5 @@ export default function HeaderArticle({ post, siteInfo }) {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ import CONFIG_HEXO from '../config_hexo'
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
const JumpToTopButton = ({ showPercent = true, percent }) => {
|
const JumpToTopButton = ({ showPercent = true, percent }) => {
|
||||||
|
const { locale } = useGlobal()
|
||||||
|
|
||||||
if (!CONFIG_HEXO.WIDGET_TO_TOP) {
|
if (!CONFIG_HEXO.WIDGET_TO_TOP) {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
const { locale } = useGlobal()
|
|
||||||
return (<div className='space-x-1 items-center justify-center transform hover:scale-105 duration-200 w-7 h-auto pb-1 text-center' onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} >
|
return (<div className='space-x-1 items-center justify-center transform hover:scale-105 duration-200 w-7 h-auto pb-1 text-center' onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} >
|
||||||
<div title={locale.POST.TOP} ><i className='fas fa-arrow-up text-xs' /></div>
|
<div title={locale.POST.TOP} ><i className='fas fa-arrow-up text-xs' /></div>
|
||||||
{showPercent && (<div className='text-xs hidden lg:block'>{percent}</div>)}
|
{showPercent && (<div className='text-xs hidden lg:block'>{percent}</div>)}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import BLOG from '@/blog.config'
|
import BLOG from '@/blog.config'
|
||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
|
// import Image from 'next/image'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
@@ -19,46 +20,53 @@ const LatestPostsGroup = ({ latestPosts, siteInfo }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<div className=" mb-2 px-1 flex flex-nowrap justify-between">
|
<div className=" mb-2 px-1 flex flex-nowrap justify-between">
|
||||||
<div>
|
|
||||||
<i className="mr-2 fas fas fa-history" />
|
|
||||||
{locale.COMMON.LATEST_POSTS}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{latestPosts.map(post => {
|
|
||||||
const selected = currentPath === `${BLOG.SUB_PATH}/${post.slug}`
|
|
||||||
const headerImage = post?.page_cover
|
|
||||||
? `url("${post.page_cover}")`
|
|
||||||
: `url("${siteInfo?.pageCover}")`
|
|
||||||
|
|
||||||
return (
|
|
||||||
(<Link
|
|
||||||
key={post.id}
|
|
||||||
title={post.title}
|
|
||||||
href={`${BLOG.SUB_PATH}/${post.slug}`}
|
|
||||||
passHref
|
|
||||||
className={'my-2 flex'}>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="w-20 h-16 bg-cover bg-center bg-no-repeat"
|
|
||||||
style={{ backgroundImage: headerImage }}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
(selected ? ' text-indigo-400 ' : 'dark:text-gray-400 ') +
|
|
||||||
' text-sm overflow-x-hidden hover:text-indigo-600 px-2 duration-200 w-full rounded ' +
|
|
||||||
' hover:text-indigo-400 cursor-pointer items-center flex'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<div className='text-line-2'>{post.title}</div>
|
<i className="mr-2 fas fas fa-history" />
|
||||||
<div className="text-gray-500">{post.lastEditedTime}</div>
|
{locale.COMMON.LATEST_POSTS}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{latestPosts.map(post => {
|
||||||
|
const selected = currentPath === `${BLOG.SUB_PATH}/${post.slug}`
|
||||||
|
|
||||||
</Link>)
|
const headerImage = post?.page_cover ? post.page_cover : siteInfo?.pageCover
|
||||||
)
|
|
||||||
})}
|
return (
|
||||||
</>
|
(<Link
|
||||||
|
key={post.id}
|
||||||
|
title={post.title}
|
||||||
|
href={`${BLOG.SUB_PATH}/${post.slug}`}
|
||||||
|
passHref
|
||||||
|
className={'my-2 flex'}>
|
||||||
|
|
||||||
|
<div className="w-20 h-16 overflow-hidden relative">
|
||||||
|
{/* <Image
|
||||||
|
src={headerImage}
|
||||||
|
fill
|
||||||
|
style={{ objectFit: 'cover' }}
|
||||||
|
placeholder='blur'
|
||||||
|
blurDataURL='/bg_image.jpg'
|
||||||
|
quality={10}
|
||||||
|
alt={post.title} /> */}
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img src={headerImage} className='object-cover w-full h-full'/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
(selected ? ' text-indigo-400 ' : 'dark:text-gray-400 ') +
|
||||||
|
' text-sm overflow-x-hidden hover:text-indigo-600 px-2 duration-200 w-full rounded ' +
|
||||||
|
' hover:text-indigo-400 cursor-pointer items-center flex'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className='text-line-2'>{post.title}</div>
|
||||||
|
<div className="text-gray-500">{post.lastEditedTime}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Link>)
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
export default LatestPostsGroup
|
export default LatestPostsGroup
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const MenuButtonGroupTop = (props) => {
|
|||||||
const { locale } = useGlobal()
|
const { locale } = useGlobal()
|
||||||
|
|
||||||
let links = [
|
let links = [
|
||||||
|
{ icon: 'fa-solid fa-house', name: locale.NAV.INDEX, to: '/', show: CONFIG_HEXO.MENU_INDEX },
|
||||||
{ icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_HEXO.MENU_SEARCH },
|
{ icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_HEXO.MENU_SEARCH },
|
||||||
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_HEXO.MENU_ARCHIVE }
|
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_HEXO.MENU_ARCHIVE }
|
||||||
// { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_HEXO.MENU_CATEGORY },
|
// { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_HEXO.MENU_CATEGORY },
|
||||||
@@ -36,12 +37,12 @@ const MenuButtonGroupTop = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
export default MenuButtonGroupTop
|
export default MenuButtonGroupTop
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('scroll', scrollListener)
|
document.addEventListener('scroll', scrollListener)
|
||||||
return () => document.removeEventListener('scroll', scrollListener)
|
return () => document.removeEventListener('scroll', scrollListener)
|
||||||
}, [percent])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-4 w-full shadow-2xl bg-gray-400 ">
|
<div className="h-4 w-full shadow-2xl bg-gray-400 ">
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const SearchInput = props => {
|
|||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
type="text"
|
type="text"
|
||||||
className={
|
className={
|
||||||
'w-full text-sm pl-5 rounded-lg transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'
|
'outline-none w-full text-sm pl-5 rounded-lg transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'
|
||||||
}
|
}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
onCompositionStart={lockSearchInput}
|
onCompositionStart={lockSearchInput}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import throttle from 'lodash.throttle'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import CategoryGroup from './CategoryGroup'
|
import CategoryGroup from './CategoryGroup'
|
||||||
import Collapse from './Collapse'
|
import Collapse from './Collapse'
|
||||||
import Logo from './Logo'
|
import Logo from './Logo'
|
||||||
@@ -10,6 +9,7 @@ import TagGroups from './TagGroups'
|
|||||||
import MenuButtonGroupTop from './MenuButtonGroupTop'
|
import MenuButtonGroupTop from './MenuButtonGroupTop'
|
||||||
import MenuList from './MenuList'
|
import MenuList from './MenuList'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
let windowTop = 0
|
let windowTop = 0
|
||||||
|
|
||||||
@@ -19,17 +19,33 @@ let windowTop = 0
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const TopNav = props => {
|
const TopNav = props => {
|
||||||
|
const searchDrawer = useRef()
|
||||||
const { tags, currentTag, categories, currentCategory } = props
|
const { tags, currentTag, categories, currentCategory } = props
|
||||||
const { locale } = useGlobal()
|
const { locale } = useGlobal()
|
||||||
const searchDrawer = useRef()
|
|
||||||
const { isDarkMode } = useGlobal()
|
const { isDarkMode } = useGlobal()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const scrollTrigger = throttle(() => {
|
const [isOpen, changeShow] = useState(false)
|
||||||
|
|
||||||
|
const toggleMenuOpen = () => {
|
||||||
|
changeShow(!isOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听滚动
|
||||||
|
useEffect(() => {
|
||||||
|
scrollTrigger()
|
||||||
|
window.addEventListener('scroll', scrollTrigger)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', scrollTrigger)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const throttleMs = 200
|
||||||
|
|
||||||
|
const scrollTrigger = useCallback(throttle(() => {
|
||||||
const scrollS = window.scrollY
|
const scrollS = window.scrollY
|
||||||
const nav = document.querySelector('#sticky-nav')
|
const nav = document.querySelector('#sticky-nav')
|
||||||
const header = document.querySelector('#header')
|
const header = document.querySelector('#header')
|
||||||
const showNav = scrollS <= windowTop || scrollS < 5 || (header && scrollS <= header.clientHeight)// 非首页无大图时影藏顶部 滚动条置顶时隐藏
|
|
||||||
// 是否将导航栏透明
|
// 是否将导航栏透明
|
||||||
const navTransparent = (scrollS < document.documentElement.clientHeight - 12 && router.route === '/') || scrollS < 300 // 透明导航条的条件
|
const navTransparent = (scrollS < document.documentElement.clientHeight - 12 && router.route === '/') || scrollS < 300 // 透明导航条的条件
|
||||||
|
|
||||||
@@ -47,6 +63,7 @@ const TopNav = props => {
|
|||||||
nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
|
nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showNav = scrollS <= windowTop || scrollS < 5 || (header && scrollS <= header.clientHeight * 2)// 非首页无大图时影藏顶部 滚动条置顶时隐藏
|
||||||
if (!showNav) {
|
if (!showNav) {
|
||||||
nav && nav.classList.replace('top-0', '-top-20')
|
nav && nav.classList.replace('top-0', '-top-20')
|
||||||
windowTop = scrollS
|
windowTop = scrollS
|
||||||
@@ -55,7 +72,8 @@ const TopNav = props => {
|
|||||||
windowTop = scrollS
|
windowTop = scrollS
|
||||||
}
|
}
|
||||||
navDarkMode()
|
navDarkMode()
|
||||||
}, 200)
|
}, throttleMs)
|
||||||
|
)
|
||||||
|
|
||||||
const navDarkMode = () => {
|
const navDarkMode = () => {
|
||||||
const nav = document.getElementById('sticky-nav')
|
const nav = document.getElementById('sticky-nav')
|
||||||
@@ -69,86 +87,70 @@ const TopNav = props => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听滚动
|
|
||||||
useEffect(() => {
|
|
||||||
scrollTrigger()
|
|
||||||
|
|
||||||
window.addEventListener('scroll', scrollTrigger)
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('scroll', scrollTrigger)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const [isOpen, changeShow] = useState(false)
|
|
||||||
|
|
||||||
const toggleMenuOpen = () => {
|
|
||||||
changeShow(!isOpen)
|
|
||||||
}
|
|
||||||
|
|
||||||
const searchDrawerSlot = <>
|
const searchDrawerSlot = <>
|
||||||
{ categories && (
|
{categories && (
|
||||||
<section className='mt-8'>
|
<section className='mt-8'>
|
||||||
<div className='text-sm flex flex-nowrap justify-between font-light px-2'>
|
<div className='text-sm flex flex-nowrap justify-between font-light px-2'>
|
||||||
<div className='text-gray-600 dark:text-gray-200'><i className='mr-2 fas fa-th-list' />{locale.COMMON.CATEGORY}</div>
|
<div className='text-gray-600 dark:text-gray-200'><i className='mr-2 fas fa-th-list' />{locale.COMMON.CATEGORY}</div>
|
||||||
<Link
|
<Link
|
||||||
href={'/category'}
|
href={'/category'}
|
||||||
passHref
|
passHref
|
||||||
className='mb-3 text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
|
className='mb-3 text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
|
||||||
|
|
||||||
{locale.COMMON.MORE} <i className='fas fa-angle-double-right' />
|
{locale.COMMON.MORE} <i className='fas fa-angle-double-right' />
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<CategoryGroup currentCategory={currentCategory} categories={categories} />
|
<CategoryGroup currentCategory={currentCategory} categories={categories} />
|
||||||
</section>
|
</section>
|
||||||
) }
|
)}
|
||||||
|
|
||||||
{ tags && (
|
{tags && (
|
||||||
<section className='mt-4'>
|
<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-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'><i className='mr-2 fas fa-tag'/>{locale.COMMON.TAGS}</div>
|
<div className='text-gray-600 dark:text-gray-200'><i className='mr-2 fas fa-tag' />{locale.COMMON.TAGS}</div>
|
||||||
<Link
|
<Link
|
||||||
href={'/tag'}
|
href={'/tag'}
|
||||||
passHref
|
passHref
|
||||||
className='text-gray-400 hover:text-black dark:hover:text-white hover:underline cursor-pointer'>
|
className='text-gray-400 hover:text-black dark:hover:text-white hover:underline cursor-pointer'>
|
||||||
|
|
||||||
{locale.COMMON.MORE} <i className='fas fa-angle-double-right' />
|
{locale.COMMON.MORE} <i className='fas fa-angle-double-right' />
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className='p-2'>
|
<div className='p-2'>
|
||||||
<TagGroups tags={tags} currentTag={currentTag} />
|
<TagGroups tags={tags} currentTag={currentTag} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
) }
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
||||||
return (<div id='top-nav' className='z-40'>
|
return (<div id='top-nav' className='z-40'>
|
||||||
<SearchDrawer cRef={searchDrawer} slot={searchDrawerSlot}/>
|
<SearchDrawer cRef={searchDrawer} slot={searchDrawerSlot} />
|
||||||
|
|
||||||
{/* 导航栏 */}
|
{/* 导航栏 */}
|
||||||
<div id='sticky-nav' className={'top-0 shadow-none fixed bg-none dark:bg-hexo-black-gray dark:text-gray-200 text-black w-full z-20 transform transition-all duration-200 border-transparent dark:border-transparent'}>
|
<div id='sticky-nav' className={'top-0 duration-200 transition-all shadow-none fixed bg-none dark:bg-hexo-black-gray dark:text-gray-200 text-black w-full z-20 transform border-transparent dark:border-transparent'}>
|
||||||
<div className='w-full flex justify-between items-center px-4 py-2'>
|
<div className='w-full flex justify-between items-center px-4 py-2'>
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<Logo {...props}/>
|
<Logo {...props} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 右侧功能 */}
|
{/* 右侧功能 */}
|
||||||
<div className='mr-1 justify-end items-center '>
|
<div className='mr-1 justify-end items-center '>
|
||||||
<div className='hidden lg:flex'> <MenuButtonGroupTop {...props}/></div>
|
<div className='hidden lg:flex'> <MenuButtonGroupTop {...props} /></div>
|
||||||
<div onClick={toggleMenuOpen} className='w-8 justify-center items-center h-8 cursor-pointer flex lg:hidden'>
|
<div onClick={toggleMenuOpen} className='w-8 justify-center items-center h-8 cursor-pointer flex lg:hidden'>
|
||||||
{ isOpen ? <i className='fas fa-times'/> : <i className='fas fa-bars'/> }
|
{isOpen ? <i className='fas fa-times' /> : <i className='fas fa-bars' />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Collapse type='vertical' isOpen={isOpen} className='shadow-xl'>
|
<Collapse type='vertical' isOpen={isOpen} className='shadow-xl'>
|
||||||
<div className='bg-white dark:bg-hexo-black-gray pt-1 py-2 px-5 lg:hidden '>
|
<div className='bg-white dark:bg-hexo-black-gray pt-1 py-2 px-5 lg:hidden '>
|
||||||
<MenuList {...props}/>
|
<MenuList {...props} />
|
||||||
|
</div>
|
||||||
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
</Collapse>
|
</div>)
|
||||||
</div>
|
|
||||||
</div>)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TopNav
|
export default TopNav
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ const CONFIG_HEXO = {
|
|||||||
HOME_BANNER_GREETINGS: ['Hi,我是一个程序员', 'Hi,我是一个打工人', 'Hi,我是一个干饭人', '欢迎来到我的博客🎉'], // 首页大图标语文字
|
HOME_BANNER_GREETINGS: ['Hi,我是一个程序员', 'Hi,我是一个打工人', 'Hi,我是一个干饭人', '欢迎来到我的博客🎉'], // 首页大图标语文字
|
||||||
|
|
||||||
HOME_NAV_BUTTONS: true, // 首页是否显示分类大图标按钮
|
HOME_NAV_BUTTONS: true, // 首页是否显示分类大图标按钮
|
||||||
|
HOME_NAV_BACKGROUND_IMG_FIXED: true, // 首页背景图滚动时是否固定,true 则滚动时图片不懂; false则随鼠标滚动
|
||||||
|
|
||||||
// 菜单配置
|
// 菜单配置
|
||||||
|
MENU_INDEX: true, // 显示首页
|
||||||
MENU_CATEGORY: true, // 显示分类
|
MENU_CATEGORY: true, // 显示分类
|
||||||
MENU_TAG: true, // 显示标签
|
MENU_TAG: true, // 显示标签
|
||||||
MENU_ARCHIVE: true, // 显示归档
|
MENU_ARCHIVE: true, // 显示归档
|
||||||
@@ -14,6 +16,7 @@ const CONFIG_HEXO = {
|
|||||||
POST_LIST_COVER_DEFAULT: true, // 封面为空时用站点背景做默认封面
|
POST_LIST_COVER_DEFAULT: true, // 封面为空时用站点背景做默认封面
|
||||||
POST_LIST_SUMMARY: true, // 文章摘要
|
POST_LIST_SUMMARY: true, // 文章摘要
|
||||||
POST_LIST_PREVIEW: true, // 读取文章预览
|
POST_LIST_PREVIEW: true, // 读取文章预览
|
||||||
|
POST_LIST_IMG_CROSSOVER: true, // 博客列表图片左右交错
|
||||||
|
|
||||||
ARTICLE_ADJACENT: true, // 显示上一篇下一篇文章推荐
|
ARTICLE_ADJACENT: true, // 显示上一篇下一篇文章推荐
|
||||||
ARTICLE_COPYRIGHT: true, // 显示文章版权声明
|
ARTICLE_COPYRIGHT: true, // 显示文章版权声明
|
||||||
|
|||||||
@@ -8,6 +8,25 @@ import * as medium from './medium'
|
|||||||
import * as nobelium from './nobelium'
|
import * as nobelium from './nobelium'
|
||||||
import * as matery from './matery'
|
import * as matery from './matery'
|
||||||
import * as example from './example'
|
import * as example from './example'
|
||||||
|
import * as simple from './simple'
|
||||||
|
|
||||||
export const ALL_THEME = ['hexo', 'matery', 'next', 'medium', 'fukasawa', 'nobelium', 'example']
|
export const ALL_THEME = [
|
||||||
export { hexo, next, medium, fukasawa, nobelium, matery, example }
|
'hexo',
|
||||||
|
'matery',
|
||||||
|
'next',
|
||||||
|
'medium',
|
||||||
|
'fukasawa',
|
||||||
|
'nobelium',
|
||||||
|
'example',
|
||||||
|
'simple'
|
||||||
|
]
|
||||||
|
export {
|
||||||
|
hexo,
|
||||||
|
next,
|
||||||
|
medium,
|
||||||
|
fukasawa,
|
||||||
|
nobelium,
|
||||||
|
matery,
|
||||||
|
example,
|
||||||
|
simple
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import CommonHead from '@/components/CommonHead'
|
import CommonHead from '@/components/CommonHead'
|
||||||
import { useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import JumpToTopButton from './components/JumpToTopButton'
|
import JumpToTopButton from './components/JumpToTopButton'
|
||||||
import TopNav from './components/TopNav'
|
import TopNav from './components/TopNav'
|
||||||
import smoothscroll from 'smoothscroll-polyfill'
|
|
||||||
import Live2D from '@/components/Live2D'
|
import Live2D from '@/components/Live2D'
|
||||||
import LoadingCover from './components/LoadingCover'
|
import LoadingCover from './components/LoadingCover'
|
||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import BLOG from '@/blog.config'
|
import BLOG from '@/blog.config'
|
||||||
import FloatDarkModeButton from './components/FloatDarkModeButton'
|
import FloatDarkModeButton from './components/FloatDarkModeButton'
|
||||||
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
|
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
|
||||||
@@ -22,28 +22,22 @@ const LayoutBase = props => {
|
|||||||
const [show, switchShow] = useState(false)
|
const [show, switchShow] = useState(false)
|
||||||
const { onLoading } = useGlobal()
|
const { onLoading } = useGlobal()
|
||||||
|
|
||||||
const scrollListener = () => {
|
const throttleMs = 200
|
||||||
const targetRef = document.getElementById('wrapper')
|
const scrollListener = useCallback(throttle(() => {
|
||||||
const clientHeight = targetRef?.clientHeight
|
|
||||||
const scrollY = window.pageYOffset
|
const scrollY = window.pageYOffset
|
||||||
const fullHeight = clientHeight - window.outerHeight
|
const shouldShow = scrollY > 300
|
||||||
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
|
|
||||||
if (per > 100) per = 100
|
|
||||||
const shouldShow = scrollY > 300 && per > 0
|
|
||||||
|
|
||||||
if (shouldShow !== show) {
|
if (shouldShow !== show) {
|
||||||
switchShow(shouldShow)
|
switchShow(shouldShow)
|
||||||
}
|
}
|
||||||
// changePercent(per)
|
}, throttleMs))
|
||||||
}
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
smoothscroll.polyfill()
|
|
||||||
document.addEventListener('scroll', scrollListener)
|
document.addEventListener('scroll', scrollListener)
|
||||||
return () => document.removeEventListener('scroll', scrollListener)
|
return () => document.removeEventListener('scroll', scrollListener)
|
||||||
}, [show])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="outer-wrapper" className="min-h-screen flex flex-col justify-between bg-hexo-background-gray dark:bg-black w-full">
|
<div id='theme-matery' className="min-h-screen flex flex-col justify-between bg-hexo-background-gray dark:bg-black w-full">
|
||||||
|
|
||||||
<CommonHead meta={meta} siteInfo={siteInfo} />
|
<CommonHead meta={meta} siteInfo={siteInfo} />
|
||||||
|
|
||||||
@@ -67,12 +61,8 @@ const LayoutBase = props => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 右下角悬浮 */}
|
{/* 右下角悬浮 */}
|
||||||
<div className="bottom-12 right-2 fixed justify-end z-20">
|
<div className={ (show ? ' opacity-100 fixed ' : ' hidden opacity-0 ') + ' transition-all duration-200 bottom-12 right-2 justify-end z-20' }>
|
||||||
<div className={
|
<div className= ' justify-center flex flex-col items-center cursor-pointer '>
|
||||||
(show ? 'animate__animated ' : 'hidden') +
|
|
||||||
' animate__fadeInUp justify-center duration-300 animate__faster flex flex-col items-center cursor-pointer '
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<JumpToTopButton />
|
<JumpToTopButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, { useCallback, useEffect } from 'react'
|
||||||
import { ArticleLock } from './components/ArticleLock'
|
import { ArticleLock } from './components/ArticleLock'
|
||||||
import HeaderArticle from './components/HeaderArticle'
|
import HeaderArticle from './components/HeaderArticle'
|
||||||
import LayoutBase from './LayoutBase'
|
import LayoutBase from './LayoutBase'
|
||||||
@@ -9,23 +9,25 @@ import ArticleCopyright from './components/ArticleCopyright'
|
|||||||
import { ArticleInfo } from './components/ArticleInfo'
|
import { ArticleInfo } from './components/ArticleInfo'
|
||||||
import Catalog from './components/Catalog'
|
import Catalog from './components/Catalog'
|
||||||
import JumpToCommentButton from './components/JumpToCommentButton'
|
import JumpToCommentButton from './components/JumpToCommentButton'
|
||||||
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
export const LayoutSlug = props => {
|
export const LayoutSlug = props => {
|
||||||
const { post, lock, validPassword } = props
|
const { post, lock, validPassword } = props
|
||||||
|
|
||||||
const [show, switchShow] = React.useState(false)
|
const [show, switchShow] = React.useState(false)
|
||||||
|
const throttleMs = 200
|
||||||
|
|
||||||
const scrollListener = () => {
|
const scrollListener = useCallback(throttle(() => {
|
||||||
const scrollY = window.pageYOffset
|
const scrollY = window.pageYOffset
|
||||||
const shouldShow = scrollY > 220 && post?.toc?.length > 0
|
const shouldShow = scrollY > 220 && post?.toc?.length > 0
|
||||||
if (shouldShow !== show) {
|
if (shouldShow !== show) {
|
||||||
switchShow(shouldShow)
|
switchShow(shouldShow)
|
||||||
}
|
}
|
||||||
}
|
}, throttleMs))
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('scroll', scrollListener)
|
document.addEventListener('scroll', scrollListener)
|
||||||
return () => document.removeEventListener('scroll', scrollListener)
|
return () => document.removeEventListener('scroll', scrollListener)
|
||||||
}, [show])
|
}, [])
|
||||||
|
|
||||||
if (!post) {
|
if (!post) {
|
||||||
return <LayoutBase
|
return <LayoutBase
|
||||||
@@ -36,98 +38,92 @@ export const LayoutSlug = props => {
|
|||||||
></LayoutBase>
|
></LayoutBase>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (<LayoutBase
|
||||||
<LayoutBase
|
headerSlot={<HeaderArticle {...props} />}
|
||||||
headerSlot={<HeaderArticle {...props} />}
|
{...props}
|
||||||
{...props}
|
showCategory={false}
|
||||||
showCategory={false}
|
showTag={false}
|
||||||
showTag={false}
|
>
|
||||||
>
|
|
||||||
|
|
||||||
<div id='inner-wrapper'>
|
<div id='inner-wrapper'>
|
||||||
<div className={'drop-shadow-xl w-full lg:max-w-3xl 2xl:max-w-4xl'}>
|
<div className={'w-full lg:max-w-3xl 2xl:max-w-4xl'}>
|
||||||
<div className="-mt-32 rounded-md mx-3 lg:border lg:rounded-xl lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
|
<div className="-mt-32 rounded-md mx-3 lg:border lg:rounded-xl lg:py-4 bg-white dark:bg-hexo-black-gray dark:border-black">
|
||||||
{lock && <ArticleLock validPassword={validPassword} />}
|
{lock && <ArticleLock validPassword={validPassword} />}
|
||||||
|
|
||||||
{!lock && <div id="container" className="overflow-x-auto md:w-full px-3 ">
|
{!lock && <div id="container" className="overflow-x-auto md:w-full px-3 ">
|
||||||
{post?.type === 'Post' && <>
|
{post?.type && post?.type === 'Post' && <>
|
||||||
<div
|
<div
|
||||||
data-aos="fade-down"
|
data-aos="fade-down"
|
||||||
data-aos-duration="500"
|
data-aos-duration="100"
|
||||||
data-aos-easing="ease-in-out"
|
data-aos-once="false"
|
||||||
data-aos-once="false"
|
data-aos-anchor-placement="top-center"
|
||||||
data-aos-anchor-placement="top-center"
|
className='px-10'>
|
||||||
className='px-10'>
|
<ArticleInfo post={post} />
|
||||||
<ArticleInfo post={post} />
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
</>}
|
|
||||||
|
|
||||||
<div className='lg:px-10 '>
|
|
||||||
<article itemScope itemType="https://schema.org/Movie" className="subpixel-antialiased overflow-y-hidden" >
|
|
||||||
{/* Notion文章主体 */}
|
|
||||||
<section id='notion-article'
|
|
||||||
data-aos-delay="200"
|
|
||||||
data-aos="fade-down"
|
|
||||||
data-aos-duration="500"
|
|
||||||
data-aos-easing="ease-in-out"
|
|
||||||
data-aos-once="false"
|
|
||||||
data-aos-anchor-placement="top-bottom"
|
|
||||||
className='justify-center mx-auto max-w-2xl lg:max-w-full'>
|
|
||||||
{post && <NotionPage post={post} />}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
|
|
||||||
{/* 文章内嵌广告 */}
|
|
||||||
<ins className="adsbygoogle"
|
|
||||||
style={{ display: 'block', textAlign: 'center' }}
|
|
||||||
data-adtest="on"
|
|
||||||
data-ad-layout="in-article"
|
|
||||||
data-ad-format="fluid"
|
|
||||||
data-ad-client="ca-pub-2708419466378217"
|
|
||||||
data-ad-slot="3806269138" />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{post.type === 'Post' && <ArticleCopyright {...props} />}
|
|
||||||
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<hr className='border-dashed' />
|
|
||||||
|
|
||||||
{/* 评论互动 */}
|
|
||||||
<div className="duration-200 overflow-x-auto dark:bg-hexo-black-gray px-3">
|
|
||||||
<Comment frontMatter={post} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<hr />
|
||||||
|
</>}
|
||||||
|
|
||||||
</div>}
|
<div className='lg:px-10 subpixel-antialiased'>
|
||||||
</div>
|
<article itemScope >
|
||||||
{post.type === 'Post' && <ArticleAdjacent {...props} />}
|
{/* Notion文章主体 */}
|
||||||
|
<section id='notion-article' className='justify-center mx-auto max-w-2xl lg:max-w-full'>
|
||||||
|
{post && <NotionPage post={post} />}
|
||||||
|
</section>
|
||||||
|
|
||||||
{post?.toc?.length > 0 && <div id='toc-wrapper' style={{ zIndex: '-1' }} className='absolute top-0 w-full h-full xl:block hidden' >
|
<section className="px-1 py-2 my-1 text-sm font-light overflow-auto text-gray-600 dark:text-gray-400">
|
||||||
<div data-aos-delay="200"
|
{/* 文章内嵌广告 */}
|
||||||
data-aos="fade-down"
|
<ins className="adsbygoogle"
|
||||||
data-aos-duration="500"
|
style={{ display: 'block', textAlign: 'center' }}
|
||||||
data-aos-easing="ease-in-out"
|
data-adtest="on"
|
||||||
data-aos-once="false"
|
data-ad-layout="in-article"
|
||||||
data-aos-anchor-placement="top-center"
|
data-ad-format="fluid"
|
||||||
className='relative h-full'>
|
data-ad-client="ca-pub-2708419466378217"
|
||||||
<div className='float-right xl:-mr-72 xl:w-72 w-56 -mr-56 h-full mt-40'>
|
data-ad-slot="3806269138" />
|
||||||
<div className='sticky top-24'>
|
</section>
|
||||||
<Catalog toc={post.toc} />
|
|
||||||
</div>
|
{/* 文章版权说明 */}
|
||||||
|
{post.type === 'Post' && <ArticleCopyright {...props} />}
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<hr className='border-dashed' />
|
||||||
|
|
||||||
|
{/* 评论互动 */}
|
||||||
|
<div className="overflow-x-auto dark:bg-hexo-black-gray px-3">
|
||||||
|
<Comment frontMatter={post} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='fixed bottom-28 right-4'>
|
{/* 文章推荐 */}
|
||||||
<JumpToCommentButton />
|
{post.type === 'Post' && <ArticleAdjacent {...props} />}
|
||||||
</div>
|
|
||||||
|
{/* 文章目录 */}
|
||||||
|
{post?.toc?.length > 0 && <div id='toc-wrapper' style={{ zIndex: '-1' }} className='absolute top-0 w-full h-full xl:block hidden lg:max-w-3xl 2xl:max-w-4xl' >
|
||||||
|
<div data-aos-delay="200"
|
||||||
|
data-aos="fade-down"
|
||||||
|
data-aos-duration="200"
|
||||||
|
data-aos-once="true"
|
||||||
|
data-aos-anchor-placement="top-center"
|
||||||
|
className='relative h-full'>
|
||||||
|
<div className='float-right xl:-mr-72 xl:w-72 w-56 -mr-56 h-full mt-40'>
|
||||||
|
<div className='sticky top-24'>
|
||||||
|
<Catalog toc={post.toc} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</LayoutBase>
|
<div className='fixed bottom-28 right-4'>
|
||||||
|
<JumpToCommentButton />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</LayoutBase>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const ArticleLock = props => {
|
|||||||
<div className='text-center space-y-3'>
|
<div className='text-center space-y-3'>
|
||||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||||
<div className='flex mx-4'>
|
<div className='flex mx-4'>
|
||||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'></input>
|
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black 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" >
|
<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>
|
<i className={'duration-200 cursor-pointer fas fa-key'} > {locale.COMMON.SUBMIT}</i>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,41 +3,45 @@ import Link from 'next/link'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import TagItemMini from './TagItemMini'
|
import TagItemMini from './TagItemMini'
|
||||||
import CONFIG_MATERY from '../config_matery'
|
import CONFIG_MATERY from '../config_matery'
|
||||||
|
// import Image from 'next/image'
|
||||||
|
|
||||||
const BlogPostCard = ({ post, showSummary, siteInfo }) => {
|
const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
|
||||||
const showPreview = CONFIG_MATERY.POST_LIST_PREVIEW && post.blockMap
|
const showPreview = CONFIG_MATERY.POST_LIST_PREVIEW && post.blockMap
|
||||||
// matery 主题默认强制显示图片
|
// matery 主题默认强制显示图片
|
||||||
if (post && !post.page_cover) {
|
if (post && !post.page_cover) {
|
||||||
post.page_cover = siteInfo?.pageCover
|
post.page_cover = siteInfo?.pageCover
|
||||||
}
|
}
|
||||||
const showPageCover = CONFIG_MATERY.POST_LIST_COVER && !showPreview && post?.page_cover
|
const showPageCover = CONFIG_MATERY.POST_LIST_COVER && !showPreview && post?.page_cover
|
||||||
|
const delay = (index % 3) * 300
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-aos="zoom-in"
|
data-aos="zoom-in"
|
||||||
data-aos-duration="300"
|
data-aos-duration="500"
|
||||||
data-aos-easing="ease-in-out"
|
data-aos-delay={delay}
|
||||||
data-aos-once="false"
|
data-aos-once="true"
|
||||||
data-aos-anchor-placement="top-bottom"
|
data-aos-anchor-placement="top-bottom"
|
||||||
className="w-full mb-4 h-full overflow-auto drop-shadow-md border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray">
|
className="w-full mb-4 overflow-auto shadow-md border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray">
|
||||||
|
|
||||||
{/* 固定高度 ,空白用图片拉升填充 */}
|
{/* 固定高度 ,空白用图片拉升填充 */}
|
||||||
<div key={post.id} className="flex flex-col h-96 justify-between">
|
<div className="flex flex-col h-80 justify-between">
|
||||||
|
|
||||||
{/* 头部图片 填充卡片 */}
|
{/* 头部图片 填充卡片 */}
|
||||||
{showPageCover && (
|
{showPageCover && (
|
||||||
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
|
<Link href={`${BLOG.SUB_PATH}/${post.slug}`} passHref legacyBehavior>
|
||||||
<div className="flex flex-grow w-full relative duration-200 bg-black rounded-t-md cursor-pointer transform overflow-hidden">
|
<div
|
||||||
|
className="flex flex-grow w-full relative duration-200 bg-black rounded-t-md cursor-pointer transform overflow-hidden">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
<img
|
||||||
src={post?.page_cover}
|
src={post?.page_cover}
|
||||||
alt={post.title}
|
alt={post.title}
|
||||||
className="opacity-50 h-full w-full hover:scale-125 rounded-t-md transform object-cover duration-500"
|
className="opacity-50 h-full w-full hover:scale-125 rounded-t-md transform object-cover duration-500"
|
||||||
/>
|
/>
|
||||||
<span className='absolute bottom-0 left-0 text-white p-6 text-2xl replace' > {post.title}</span>
|
<span className='absolute bottom-0 left-0 text-white p-6 text-2xl replace break-words w-full' > {post.title}</span>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 文字描述 */}
|
||||||
<div >
|
<div >
|
||||||
{/* 描述 */}
|
{/* 描述 */}
|
||||||
<div className="px-4 flex flex-col w-full text-gray-700 dark:text-gray-300">
|
<div className="px-4 flex flex-col w-full text-gray-700 dark:text-gray-300">
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
|
|||||||
<div id="container" className='w-full'>
|
<div id="container" className='w-full'>
|
||||||
<div className='pt-6'></div>
|
<div className='pt-6'></div>
|
||||||
{/* 文章列表 */}
|
{/* 文章列表 */}
|
||||||
<div className="px-4 pt-4 xl:columns-3 md:columns-2 pb-24" >
|
<div className="px-4 pt-4 flex flex-wrap pb-24" >
|
||||||
{posts.map(post => (
|
{posts.map(post => (
|
||||||
<BlogPostCard key={post.id} post={post} siteInfo={siteInfo} />
|
<div key={post.id} className='xl:w-1/3 md:w-1/2 w-full p-4'> <BlogPostCard index={posts.indexOf(post)} post={post} siteInfo={siteInfo} /></div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{showPagination && <PaginationSimple page={page} totalPage={totalPage} />}
|
{showPagination && <PaginationSimple page={page} totalPage={totalPage} />}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import BLOG from '@/blog.config'
|
|||||||
import BlogPostCard from './BlogPostCard'
|
import BlogPostCard from './BlogPostCard'
|
||||||
import BlogPostListEmpty from './BlogPostListEmpty'
|
import BlogPostListEmpty from './BlogPostListEmpty'
|
||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import throttle from 'lodash.throttle'
|
import React, { useCallback } from 'react'
|
||||||
import React from 'react'
|
|
||||||
import CONFIG_MATERY from '../config_matery'
|
import CONFIG_MATERY from '../config_matery'
|
||||||
import { getListByPage } from '@/lib/utils'
|
import { getListByPage } from '@/lib/utils'
|
||||||
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 博客列表滚动分页
|
* 博客列表滚动分页
|
||||||
@@ -30,15 +30,16 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_MA
|
|||||||
updatePage(page + 1)
|
updatePage(page + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听滚动自动分页加载
|
const throttleMs = 200
|
||||||
const scrollTrigger = React.useCallback(throttle(() => {
|
const scrollTrigger = useCallback(throttle(() => {
|
||||||
const scrollS = window.scrollY + window.outerHeight
|
requestAnimationFrame(() => {
|
||||||
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
|
const scrollS = window.scrollY + window.outerHeight
|
||||||
if (scrollS > clientHeight + 100) {
|
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
|
||||||
handleGetMore()
|
if (scrollS > clientHeight + 100) {
|
||||||
}
|
handleGetMore()
|
||||||
}, 500))
|
}
|
||||||
|
})
|
||||||
|
}, throttleMs))
|
||||||
// 监听滚动
|
// 监听滚动
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
window.addEventListener('scroll', scrollTrigger)
|
window.addEventListener('scroll', scrollTrigger)
|
||||||
@@ -56,9 +57,11 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_MA
|
|||||||
return <div id='container' ref={targetRef} className='w-full'>
|
return <div id='container' ref={targetRef} className='w-full'>
|
||||||
|
|
||||||
{/* 文章列表 */}
|
{/* 文章列表 */}
|
||||||
<div className='flex flex-wrap space-y-1 lg:space-y-4 px-2'>
|
<div className="px-4 pt-4 flex flex-wrap pb-24" >
|
||||||
{postsToShow.map(post => (
|
{postsToShow.map(post => (
|
||||||
<BlogPostCard key={post.id} post={post} showSummary={showSummary} siteInfo={siteInfo}/>
|
<div key={post.id} className='xl:w-1/3 md:w-1/2 w-full p-4'>
|
||||||
|
<BlogPostCard index={posts.indexOf(post)} post={post} siteInfo={siteInfo} />
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const Catalog = ({ toc }) => {
|
|||||||
|
|
||||||
// 同步选中目录事件
|
// 同步选中目录事件
|
||||||
const [activeSection, setActiveSection] = React.useState(null)
|
const [activeSection, setActiveSection] = React.useState(null)
|
||||||
const throttleMs = 100
|
const throttleMs = 200
|
||||||
const actionSectionScrollSpy = React.useCallback(throttle(() => {
|
const actionSectionScrollSpy = React.useCallback(throttle(() => {
|
||||||
const sections = document.getElementsByClassName('notion-h')
|
const sections = document.getElementsByClassName('notion-h')
|
||||||
let prevBBox = null
|
let prevBBox = null
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { useEffect, useState } from 'react'
|
// import Image from 'next/image'
|
||||||
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import Typed from 'typed.js'
|
import Typed from 'typed.js'
|
||||||
import CONFIG_MATERY from '../config_matery'
|
import CONFIG_MATERY from '../config_matery'
|
||||||
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
let wrapperTop = 0
|
let wrapperTop = 0
|
||||||
let windowTop = 0
|
let windowTop = 0
|
||||||
let autoScroll = false
|
let autoScroll = false
|
||||||
|
const enableAutoScroll = false // 是否开启自动吸附滚动
|
||||||
|
const throttleMs = 200
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -28,13 +32,17 @@ const Header = props => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
window.addEventListener('scroll', scrollTrigger)
|
if (enableAutoScroll) {
|
||||||
|
scrollTrigger()
|
||||||
|
window.addEventListener('scroll', scrollTrigger)
|
||||||
|
}
|
||||||
window.addEventListener('resize', updateHeaderHeight)
|
window.addEventListener('resize', updateHeaderHeight)
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('scroll', scrollTrigger)
|
if (enableAutoScroll) {
|
||||||
window.removeEventListener('resize', updateHeaderHeight)
|
window.removeEventListener('scroll', scrollTrigger)
|
||||||
|
} window.removeEventListener('resize', updateHeaderHeight)
|
||||||
}
|
}
|
||||||
})
|
}, [])
|
||||||
|
|
||||||
const autoScrollEnd = () => {
|
const autoScrollEnd = () => {
|
||||||
if (autoScroll) {
|
if (autoScroll) {
|
||||||
@@ -44,10 +52,10 @@ const Header = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 吸附滚动,移动端关闭
|
* 吸附滚动,移动端关闭
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const scrollTrigger = () => {
|
const scrollTrigger = useCallback(throttle(() => {
|
||||||
if (screen.width <= 768) {
|
if (screen.width <= 768) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -67,36 +75,42 @@ const Header = props => {
|
|||||||
setTimeout(autoScrollEnd, 500)
|
setTimeout(autoScrollEnd, 500)
|
||||||
}
|
}
|
||||||
windowTop = scrollS
|
windowTop = scrollS
|
||||||
}
|
}, throttleMs))
|
||||||
|
|
||||||
function updateHeaderHeight () {
|
function updateHeaderHeight() {
|
||||||
setTimeout(() => {
|
requestAnimationFrame(() => {
|
||||||
const wrapperElement = document.getElementById('wrapper')
|
const wrapperElement = document.getElementById('wrapper')
|
||||||
wrapperTop = wrapperElement?.offsetTop
|
wrapperTop = wrapperElement?.offsetTop
|
||||||
}, 500)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
id="header"
|
id="header"
|
||||||
className="duration-500 md:bg-fixed w-full bg-cover bg-center h-screen bg-black text-white relative z-10"
|
className="md:bg-fixed w-full h-screen bg-black text-white relative"
|
||||||
style={{
|
>
|
||||||
backgroundImage:
|
<div className='w-full h-full absolute'>
|
||||||
`linear-gradient(rgba(0, 0, 0, 0.9), rgba(0,0,0,0.5), rgba(0,0,0,0.3), rgba(0,0,0,0.5), rgba(0, 0, 0, 0.9) ),url("${siteInfo?.pageCover}")`
|
{/* <Image src={siteInfo.pageCover} fill
|
||||||
}}
|
style={{ objectFit: 'cover' }}
|
||||||
>
|
className='opacity-70'
|
||||||
<div className="absolute flex flex-col h-full items-center justify-center w-full ">
|
placeholder='blur'
|
||||||
<div className='text-4xl md:text-5xl text-white shadow-text'>{siteInfo?.title}</div>
|
blurDataURL='/bg_image.jpg' /> */}
|
||||||
<div className='mt-2 h-12 items-center text-center shadow-text text-white text-lg'>
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<span id='typed'/>
|
<img src={siteInfo.pageCover} className='h-full w-full object-cover opacity-70 ' />
|
||||||
</div>
|
</div>
|
||||||
<div onClick={() => { window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) }}
|
|
||||||
className="mt-12 border cursor-pointer w-40 text-center pt-4 pb-3 text-md text-white hover:bg-orange-600 duration-300 rounded-3xl">
|
|
||||||
<i className='animate-bounce fas fa-angle-double-down'/> <span>开始阅读</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</header>
|
<div className="absolute flex flex-col h-full items-center justify-center w-full ">
|
||||||
|
<div className='text-4xl md:text-5xl text-white shadow-text'>{siteInfo?.title}</div>
|
||||||
|
<div className='mt-2 h-12 items-center text-center shadow-text text-white text-lg'>
|
||||||
|
<span id='typed' />
|
||||||
|
</div>
|
||||||
|
<div onClick={() => { window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) }}
|
||||||
|
className="mt-12 border cursor-pointer w-40 text-center pt-4 pb-3 text-md text-white hover:bg-orange-600 duration-300 rounded-3xl">
|
||||||
|
<i className='animate-bounce fas fa-angle-double-down' /> <span>开始阅读</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
export default function HeaderArticle({ post, siteInfo }) {
|
export default function HeaderArticle({ post, siteInfo }) {
|
||||||
const headerImage = post?.page_cover ? post?.page_cover : siteInfo?.pageCover
|
const headerImage = post?.page_cover ? post?.page_cover : siteInfo?.pageCover
|
||||||
const title = post?.title
|
const title = post?.title
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-aos="fade-down"
|
data-aos="fade-down"
|
||||||
data-aos-duration="500"
|
data-aos-duration="300"
|
||||||
data-aos-easing="ease-in-out"
|
|
||||||
data-aos-once="false"
|
data-aos-once="false"
|
||||||
data-aos-anchor-placement="top-center"
|
data-aos-anchor-placement="top-center"
|
||||||
id='header' className="flex h-96 justify-center align-middle items-center w-full relative duration-200 bg-black">
|
id='header' className="flex h-96 justify-center align-middle items-center w-full relative duration-200 bg-black">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img
|
{/* <img
|
||||||
src={headerImage}
|
src={headerImage}
|
||||||
alt={title}
|
alt={title}
|
||||||
className="opacity-50 dark:opacity-40 h-full w-full object-cover"
|
className="opacity-50 dark:opacity-40 h-full w-full object-cover"
|
||||||
/>
|
/> */}
|
||||||
|
<Image alt={title} src={headerImage} fill
|
||||||
|
style={{ objectFit: 'cover' }}
|
||||||
|
className='opacity-50'
|
||||||
|
placeholder='blur'
|
||||||
|
blurDataURL='/bg_image.jpg' />
|
||||||
<span className='absolute text-white p-6 text-3xl'>{title}</span>
|
<span className='absolute text-white p-6 text-3xl'>{title}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,22 +10,24 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
|||||||
const currentRef = targetRef?.current || targetRef
|
const currentRef = targetRef?.current || targetRef
|
||||||
const [percent, changePercent] = useState(0)
|
const [percent, changePercent] = useState(0)
|
||||||
const scrollListener = () => {
|
const scrollListener = () => {
|
||||||
const target = currentRef || (isBrowser() && document.getElementById('container'))
|
requestAnimationFrame(() => {
|
||||||
if (target) {
|
const target = currentRef || (isBrowser() && document.getElementById('container'))
|
||||||
const clientHeight = target.clientHeight
|
if (target) {
|
||||||
const scrollY = window.pageYOffset
|
const clientHeight = target.clientHeight
|
||||||
const fullHeight = clientHeight - window.outerHeight
|
const scrollY = window.pageYOffset
|
||||||
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
|
const fullHeight = clientHeight - window.outerHeight
|
||||||
if (per > 100) per = 100
|
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
|
||||||
if (per < 0) per = 0
|
if (per > 100) per = 100
|
||||||
changePercent(per)
|
if (per < 0) per = 0
|
||||||
}
|
changePercent(per)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('scroll', scrollListener)
|
document.addEventListener('scroll', scrollListener)
|
||||||
return () => document.removeEventListener('scroll', scrollListener)
|
return () => document.removeEventListener('scroll', scrollListener)
|
||||||
}, [percent])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-4 w-full shadow-2xl bg-gray-400 ">
|
<div className="h-4 w-full shadow-2xl bg-gray-400 ">
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const SearchInput = props => {
|
|||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
type="text"
|
type="text"
|
||||||
className={
|
className={
|
||||||
'w-full text-sm pl-5 rounded-lg transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'
|
'outline-none w-full text-sm pl-5 rounded-lg transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'
|
||||||
}
|
}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
onCompositionStart={lockSearchInput}
|
onCompositionStart={lockSearchInput}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import throttle from 'lodash.throttle'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import CategoryGroup from './CategoryGroup'
|
import CategoryGroup from './CategoryGroup'
|
||||||
import Logo from './Logo'
|
import Logo from './Logo'
|
||||||
import SearchDrawer from './SearchDrawer'
|
import SearchDrawer from './SearchDrawer'
|
||||||
@@ -9,6 +8,7 @@ import TagGroups from './TagGroups'
|
|||||||
import MenuButtonGroupTop from './MenuButtonGroupTop'
|
import MenuButtonGroupTop from './MenuButtonGroupTop'
|
||||||
import SideBarDrawer from '@/components/SideBarDrawer'
|
import SideBarDrawer from '@/components/SideBarDrawer'
|
||||||
import SideBar from './SideBar'
|
import SideBar from './SideBar'
|
||||||
|
import throttle from 'lodash.throttle'
|
||||||
|
|
||||||
let windowTop = 0
|
let windowTop = 0
|
||||||
|
|
||||||
@@ -22,36 +22,38 @@ const TopNav = props => {
|
|||||||
const { locale } = useGlobal()
|
const { locale } = useGlobal()
|
||||||
const searchDrawer = useRef()
|
const searchDrawer = useRef()
|
||||||
const { isDarkMode } = useGlobal()
|
const { isDarkMode } = useGlobal()
|
||||||
|
const throttleMs = 200
|
||||||
|
const scrollTrigger = useCallback(throttle(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const scrollS = window.scrollY
|
||||||
|
const nav = document.querySelector('#sticky-nav')
|
||||||
|
const header = document.querySelector('#header')
|
||||||
|
const showNav = scrollS <= windowTop || scrollS < 5 || (header && scrollS <= header.clientHeight * 2)// 非首页无大图时影藏顶部 滚动条置顶时隐藏// 非首页无大图时影藏顶部 滚动条置顶时隐藏
|
||||||
|
// 是否将导航栏透明
|
||||||
|
const navTransparent = header && scrollS < 300 // 透明导航条的条件
|
||||||
|
|
||||||
const scrollTrigger = throttle(() => {
|
if (navTransparent) {
|
||||||
const scrollS = window.scrollY
|
nav && nav.classList.replace('bg-indigo-700', 'bg-none')
|
||||||
const nav = document.querySelector('#sticky-nav')
|
nav && nav.classList.replace('text-black', 'text-white')
|
||||||
const header = document.querySelector('#header')
|
nav && nav.classList.replace('drop-shadow-xl', 'shadow-none')
|
||||||
const showNav = scrollS <= windowTop || scrollS < 5 // 非首页无大图时影藏顶部 滚动条置顶时隐藏
|
nav && nav.classList.replace('dark:bg-hexo-black-gray', 'transparent')
|
||||||
// 是否将导航栏透明
|
} else {
|
||||||
const navTransparent = header && scrollS < 300 // 透明导航条的条件
|
nav && nav.classList.replace('bg-none', 'bg-indigo-700')
|
||||||
|
nav && nav.classList.replace('text-white', 'text-black')
|
||||||
|
nav && nav.classList.replace('shadow-none', 'drop-shadow-xl')
|
||||||
|
nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
|
||||||
|
}
|
||||||
|
|
||||||
if (navTransparent) {
|
if (!showNav) {
|
||||||
nav && nav.classList.replace('bg-indigo-700', 'bg-none')
|
nav && nav.classList.replace('top-0', '-top-20')
|
||||||
nav && nav.classList.replace('text-black', 'text-white')
|
windowTop = scrollS
|
||||||
nav && nav.classList.replace('drop-shadow-xl', 'shadow-none')
|
} else {
|
||||||
nav && nav.classList.replace('dark:bg-hexo-black-gray', 'transparent')
|
nav && nav.classList.replace('-top-20', 'top-0')
|
||||||
} else {
|
windowTop = scrollS
|
||||||
nav && nav.classList.replace('bg-none', 'bg-indigo-700')
|
}
|
||||||
nav && nav.classList.replace('text-white', 'text-black')
|
navDarkMode()
|
||||||
nav && nav.classList.replace('shadow-none', 'drop-shadow-xl')
|
})
|
||||||
nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
|
}, throttleMs))
|
||||||
}
|
|
||||||
|
|
||||||
if (!showNav) {
|
|
||||||
nav && nav.classList.replace('top-0', '-top-20')
|
|
||||||
windowTop = scrollS
|
|
||||||
} else {
|
|
||||||
nav && nav.classList.replace('-top-20', 'top-0')
|
|
||||||
windowTop = scrollS
|
|
||||||
}
|
|
||||||
navDarkMode()
|
|
||||||
}, 200)
|
|
||||||
|
|
||||||
const navDarkMode = () => {
|
const navDarkMode = () => {
|
||||||
const nav = document.getElementById('sticky-nav')
|
const nav = document.getElementById('sticky-nav')
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const LayoutBase = props => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeGlobalMedium.Provider value={{ tocVisible, changeTocVisible }}>
|
<ThemeGlobalMedium.Provider value={{ tocVisible, changeTocVisible }}>
|
||||||
<div className='bg-white dark:bg-hexo-black-gray w-full h-full min-h-screen justify-center dark:text-gray-300'>
|
<div id='theme-medium' className='bg-white dark:bg-hexo-black-gray w-full h-full min-h-screen justify-center dark:text-gray-300'>
|
||||||
<CommonHead meta={meta} />
|
<CommonHead meta={meta} />
|
||||||
|
|
||||||
<main id='wrapper' className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + 'relative flex justify-between w-full h-full mx-auto'}>
|
<main id='wrapper' className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + 'relative flex justify-between w-full h-full mx-auto'}>
|
||||||
@@ -46,7 +46,6 @@ const LayoutBase = props => {
|
|||||||
<div
|
<div
|
||||||
data-aos="fade-up"
|
data-aos="fade-up"
|
||||||
data-aos-duration="300"
|
data-aos-duration="300"
|
||||||
data-aos-easing="ease-in-out"
|
|
||||||
data-aos-once="false"
|
data-aos-once="false"
|
||||||
data-aos-anchor-placement="top-center"
|
data-aos-anchor-placement="top-center"
|
||||||
className='fixed xl:right-80 right-2 mr-10 bottom-24 hidden lg:block z-20'>
|
className='fixed xl:right-80 right-2 mr-10 bottom-24 hidden lg:block z-20'>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const ArticleLock = props => {
|
|||||||
<div className='text-center space-y-3'>
|
<div className='text-center space-y-3'>
|
||||||
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
|
||||||
<div className='flex mx-4'>
|
<div className='flex mx-4'>
|
||||||
<input id="password" type='password' className='w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'></input>
|
<input id="password" type='password' className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black 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-green-500 hover:bg-green-400 text-white rounded-r duration-300" >
|
<div onClick={submitPassword} className="px-3 whitespace-nowrap cursor-pointer items-center justify-center py-2 bg-green-500 hover:bg-green-400 text-white rounded-r duration-300" >
|
||||||
<i className={'duration-200 cursor-pointer fas fa-key'} > {locale.COMMON.SUBMIT}</i>
|
<i className={'duration-200 cursor-pointer fas fa-key'} > {locale.COMMON.SUBMIT}</i>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ const BlogPostCard = ({ post, showSummary }) => {
|
|||||||
<div
|
<div
|
||||||
key={post.id}
|
key={post.id}
|
||||||
data-aos="fade-up"
|
data-aos="fade-up"
|
||||||
data-aos-duration="600"
|
data-aos-duration="300"
|
||||||
data-aos-easing="ease-in-out"
|
|
||||||
data-aos-once="false"
|
data-aos-once="false"
|
||||||
data-aos-anchor-placement="top-bottom"
|
data-aos-anchor-placement="top-bottom"
|
||||||
className="mb-6 max-w-7xl border-b dark:border-gray-800 "
|
className="mb-6 max-w-7xl border-b dark:border-gray-800 "
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const Catalog = ({ toc }) => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const throttleMs = 100
|
const throttleMs = 200
|
||||||
const actionSectionScrollSpy = React.useCallback(throttle(() => {
|
const actionSectionScrollSpy = React.useCallback(throttle(() => {
|
||||||
const sections = document.getElementsByClassName('notion-h')
|
const sections = document.getElementsByClassName('notion-h')
|
||||||
let prevBBox = null
|
let prevBBox = null
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useRouter } from 'next/router'
|
|||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import CONFIG_MEDIUM from '../config_medium'
|
import CONFIG_MEDIUM from '../config_medium'
|
||||||
|
|
||||||
function GroupMenu ({ customNav }) {
|
function GroupMenu ({ customMenu, customNav }) {
|
||||||
const { locale } = useGlobal()
|
const { locale } = useGlobal()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -39,13 +39,13 @@ function GroupMenu ({ customNav }) {
|
|||||||
{link.slot}
|
{link.slot}
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GroupMenu
|
export default GroupMenu
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('scroll', scrollListener)
|
document.addEventListener('scroll', scrollListener)
|
||||||
return () => document.removeEventListener('scroll', scrollListener)
|
return () => document.removeEventListener('scroll', scrollListener)
|
||||||
}, [percent])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-4 w-full shadow-2xl bg-hexo-light-gray dark:bg-black">
|
<div className="h-4 w-full shadow-2xl bg-hexo-light-gray dark:bg-black">
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
|
|||||||
<input
|
<input
|
||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
type='text'
|
type='text'
|
||||||
className={'w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
|
className={'outline-none w-full text-sm pl-2 transition focus:shadow-lg font-light leading-10 text-black bg-gray-100 dark:bg-gray-900 dark:text-white'}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
onCompositionStart={lockSearchInput}
|
onCompositionStart={lockSearchInput}
|
||||||
onCompositionUpdate={lockSearchInput}
|
onCompositionUpdate={lockSearchInput}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export default function TopNavBar(props) {
|
|||||||
{link.slot}
|
{link.slot}
|
||||||
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -82,5 +82,5 @@ export default function TopNavBar(props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import TopNav from './components/TopNav'
|
|||||||
import { useGlobal } from '@/lib/global'
|
import { useGlobal } from '@/lib/global'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import smoothscroll from 'smoothscroll-polyfill'
|
|
||||||
import CONFIG_NEXT from './config_next'
|
import CONFIG_NEXT from './config_next'
|
||||||
import Live2D from '@/components/Live2D'
|
import Live2D from '@/components/Live2D'
|
||||||
import BLOG from '@/blog.config'
|
import BLOG from '@/blog.config'
|
||||||
@@ -45,8 +44,6 @@ const LayoutBase = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
smoothscroll.polyfill()
|
|
||||||
|
|
||||||
// facebook messenger 插件需要调整右下角悬浮按钮的高度
|
// facebook messenger 插件需要调整右下角悬浮按钮的高度
|
||||||
const fb = document.getElementsByClassName('fb-customerchat')
|
const fb = document.getElementsByClassName('fb-customerchat')
|
||||||
if (fb.length === 0) {
|
if (fb.length === 0) {
|
||||||
@@ -59,7 +56,7 @@ const LayoutBase = (props) => {
|
|||||||
return () => document.removeEventListener('scroll', scrollListener)
|
return () => document.removeEventListener('scroll', scrollListener)
|
||||||
}, [show])
|
}, [show])
|
||||||
|
|
||||||
return (<>
|
return (<div id='theme-next'>
|
||||||
|
|
||||||
<CommonHead meta={meta} />
|
<CommonHead meta={meta} />
|
||||||
|
|
||||||
@@ -90,7 +87,7 @@ const LayoutBase = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Footer title={siteInfo?.title}/>
|
<Footer title={siteInfo?.title}/>
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,32 +19,23 @@ export const LayoutSlug = (props) => {
|
|||||||
}} /></div>
|
}} /></div>
|
||||||
: null
|
: null
|
||||||
|
|
||||||
if (!post) {
|
const rightAreaSlog = CONFIG_NEXT.RIGHT_LATEST_POSTS && <Card><LatestPostsGroup latestPosts={latestPosts} /></Card>
|
||||||
return <LayoutBase
|
|
||||||
{...props}
|
|
||||||
rightAreaSlot={
|
|
||||||
CONFIG_NEXT.RIGHT_LATEST_POSTS && <Card><LatestPostsGroup posts={latestPosts} /></Card>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutBase
|
<LayoutBase
|
||||||
{...props}
|
{...props}
|
||||||
floatSlot={floatSlot}
|
floatSlot={floatSlot}
|
||||||
rightAreaSlot={
|
rightAreaSlot={rightAreaSlog}
|
||||||
CONFIG_NEXT.RIGHT_LATEST_POSTS && <Card><LatestPostsGroup posts={latestPosts} /></Card>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
|
|
||||||
{!lock && <ArticleDetail {...props} />}
|
{post && !lock && <ArticleDetail {...props} />}
|
||||||
|
|
||||||
{lock && <ArticleLock validPassword={validPassword} />}
|
{post && lock && <ArticleLock validPassword={validPassword} />}
|
||||||
|
|
||||||
{/* 悬浮目录按钮 */}
|
{/* 悬浮目录按钮 */}
|
||||||
<div className='block lg:hidden'>
|
{post && <div className='block lg:hidden'>
|
||||||
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
|
<TocDrawer post={post} cRef={drawerRight} targetRef={targetRef} />
|
||||||
</div>
|
</div>}
|
||||||
|
|
||||||
</LayoutBase>
|
</LayoutBase>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,9 +28,8 @@ export default function ArticleDetail(props) {
|
|||||||
return (
|
return (
|
||||||
<div id="container"
|
<div id="container"
|
||||||
data-aos="fade-down"
|
data-aos="fade-down"
|
||||||
data-aos-duration="600"
|
data-aos-duration="300"
|
||||||
data-aos-easing="ease-in-out"
|
data-aos-once="true"
|
||||||
data-aos-once="false"
|
|
||||||
data-aos-anchor-placement="top-bottom"
|
data-aos-anchor-placement="top-bottom"
|
||||||
|
|
||||||
className="shadow md:hover:shadow-2xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
|
className="shadow md:hover:shadow-2xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const ArticleLock = props => {
|
|||||||
<div className="flex mx-4">
|
<div className="flex mx-4">
|
||||||
<input
|
<input
|
||||||
id="password" type='password'
|
id="password" type='password'
|
||||||
className="w-full text-sm pl-5 transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500"
|
className="outline-none w-full text-sm pl-5 transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500"
|
||||||
></input>
|
></input>
|
||||||
<div
|
<div
|
||||||
onClick={submitPassword}
|
onClick={submitPassword}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
const Card = ({ children, headerSlot, className }) => {
|
const Card = ({ children, headerSlot, className }) => {
|
||||||
return <div
|
return <div
|
||||||
data-aos="fade-down"
|
data-aos="fade-down"
|
||||||
data-aos-duration="600"
|
data-aos-duration="300"
|
||||||
data-aos-easing="ease-in-out"
|
data-aos-once="true"
|
||||||
data-aos-once="false"
|
|
||||||
data-aos-anchor-placement="top-bottom"
|
data-aos-anchor-placement="top-bottom"
|
||||||
className={className}>
|
className={className}>
|
||||||
<>{headerSlot}</>
|
<>{headerSlot}</>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import smoothscroll from 'smoothscroll-polyfill'
|
|
||||||
import CONFIG_NEXT from '../config_next'
|
import CONFIG_NEXT from '../config_next'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,12 +10,14 @@ import CONFIG_NEXT from '../config_next'
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
const JumpToBottomButton = ({ showPercent = false }) => {
|
const JumpToBottomButton = ({ showPercent = false }) => {
|
||||||
if (!CONFIG_NEXT.WIDGET_TO_BOTTOM) {
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
const [show, switchShow] = useState(false)
|
const [show, switchShow] = useState(false)
|
||||||
const [percent, changePercent] = useState(0)
|
const [percent, changePercent] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('scroll', scrollListener)
|
||||||
|
return () => document.removeEventListener('scroll', scrollListener)
|
||||||
|
}, [show])
|
||||||
|
|
||||||
const scrollListener = () => {
|
const scrollListener = () => {
|
||||||
const targetRef = document.getElementById('wrapper')
|
const targetRef = document.getElementById('wrapper')
|
||||||
const clientHeight = targetRef?.clientHeight
|
const clientHeight = targetRef?.clientHeight
|
||||||
@@ -36,12 +37,9 @@ const JumpToBottomButton = ({ showPercent = false }) => {
|
|||||||
window.scrollTo({ top: targetRef.clientHeight, behavior: 'smooth' })
|
window.scrollTo({ top: targetRef.clientHeight, behavior: 'smooth' })
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
if (!CONFIG_NEXT.WIDGET_TO_BOTTOM) {
|
||||||
smoothscroll.polyfill()
|
return <></>
|
||||||
|
}
|
||||||
document.addEventListener('scroll', scrollListener)
|
|
||||||
return () => document.removeEventListener('scroll', scrollListener)
|
|
||||||
}, [show])
|
|
||||||
|
|
||||||
return (<div className='flex space-x-1 transform hover:scale-105 duration-200 py-2 px-3' onClick={scrollToBottom} >
|
return (<div className='flex space-x-1 transform hover:scale-105 duration-200 py-2 px-3' onClick={scrollToBottom} >
|
||||||
<div className='dark:text-gray-200' >
|
<div className='dark:text-gray-200' >
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import CONFIG_NEXT from '../config_next'
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
const JumpToTopButton = ({ showPercent = true, percent }) => {
|
const JumpToTopButton = ({ showPercent = true, percent }) => {
|
||||||
|
const { locale } = useGlobal()
|
||||||
if (!CONFIG_NEXT.WIDGET_TO_TOP) {
|
if (!CONFIG_NEXT.WIDGET_TO_TOP) {
|
||||||
return <></>
|
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' })} >
|
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} >
|
<div className='dark:text-gray-200' title={locale.POST.TOP} >
|
||||||
<i className='fa-arrow-up fas' />
|
<i className='fa-arrow-up fas' />
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ const PaginationNumber = ({ page, totalPage }) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-aos="fade-down"
|
data-aos="fade-down"
|
||||||
data-aos-duration="600"
|
data-aos-duration="300"
|
||||||
data-aos-easing="ease-in-out"
|
|
||||||
data-aos-once="false"
|
data-aos-once="false"
|
||||||
data-aos-anchor-placement="top-bottom"
|
data-aos-anchor-placement="top-bottom"
|
||||||
className="my-5 flex justify-center items-end font-medium text-black hover:shadow-xl duration-500 bg-white dark:bg-hexo-black-gray dark:text-gray-300 py-3 shadow space-x-2">
|
className="my-5 flex justify-center items-end font-medium text-black hover:shadow-xl duration-500 bg-white dark:bg-hexo-black-gray dark:text-gray-300 py-3 shadow space-x-2">
|
||||||
@@ -62,7 +61,7 @@ const PaginationNumber = ({ page, totalPage }) => {
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPageElement(pagePrefix, page, currentPage) {
|
function getPageElement(pagePrefix, page, currentPage) {
|
||||||
@@ -81,7 +80,7 @@ function getPageElement(pagePrefix, page, currentPage) {
|
|||||||
{page}
|
{page}
|
||||||
|
|
||||||
</Link>)
|
</Link>)
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
function generatePages(pagePrefix, page, currentPage, totalPage) {
|
function generatePages(pagePrefix, page, currentPage, totalPage) {
|
||||||
const pages = []
|
const pages = []
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ const PaginationSimple = ({ page, showNext }) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-aos="fade-down"
|
data-aos="fade-down"
|
||||||
data-aos-duration="600"
|
data-aos-duration="300"
|
||||||
data-aos-easing="ease-in-out"
|
|
||||||
data-aos-once="false"
|
data-aos-once="false"
|
||||||
data-aos-anchor-placement="top-bottom"
|
data-aos-anchor-placement="top-bottom"
|
||||||
className="my-10 flex justify-between font-medium text-black dark:text-gray-100 space-x-2">
|
className="my-10 flex justify-between font-medium text-black dark:text-gray-100 space-x-2">
|
||||||
@@ -58,7 +57,7 @@ const PaginationSimple = ({ page, showNext }) => {
|
|||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PaginationSimple
|
export default PaginationSimple
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('scroll', scrollListener)
|
document.addEventListener('scroll', scrollListener)
|
||||||
return () => document.removeEventListener('scroll', scrollListener)
|
return () => document.removeEventListener('scroll', scrollListener)
|
||||||
}, [percent])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-4 w-full shadow-2xl bg-gray-400 font-sans">
|
<div className="h-4 w-full shadow-2xl bg-gray-400 font-sans">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user