This commit is contained in:
Vixcity
2023-03-22 17:04:35 +08:00
143 changed files with 2286 additions and 786 deletions

View File

@@ -1,2 +1,2 @@
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
NEXT_PUBLIC_VERSION=3.11.0
NEXT_PUBLIC_VERSION=3.12.4

View File

@@ -81,10 +81,12 @@
<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
</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/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>
<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>
</table>

View File

@@ -8,6 +8,10 @@ import React from 'react'
const Collapse = props => {
const collapseRef = React.useRef(null)
const type = props.type || 'vertical'
/**
* 折叠
* @param {*} element
*/
const collapseSection = element => {
const sectionHeight = element.scrollHeight
const sectionWidth = element.scrollWidth
@@ -54,17 +58,20 @@ const Collapse = props => {
clearTimeout(clearTime)
}
const updateHeight = () => {
collapseRef.current.style.height = 'auto'
}
React.useEffect(() => {
const element = collapseRef.current
if (props.isOpen) {
expandSection(element)
expandSection(collapseRef.current)
} else {
collapseSection(element)
collapseSection(collapseRef.current)
}
}, [props.isOpen])
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}
</div>
)

View File

@@ -2,10 +2,31 @@ import { useGlobal } from '@/lib/global'
import { ReactCusdis } from 'react-cusdis'
import BLOG from '@/blog.config'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
const CusdisComponent = ({ frontMatter }) => {
const { locale } = useGlobal()
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
lang={locale.LOCALE.toLowerCase()}

View File

@@ -13,7 +13,7 @@ const DarkModeButton = (props) => {
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'}`}
onClick={handleChangeDarkMode} />
</div>

View File

@@ -69,7 +69,7 @@ export function DebugPanel() {
{/* 调试侧拉抽屉 */}
<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'>

View File

@@ -1,16 +1,23 @@
import BLOG from '@/blog.config'
import { isBrowser, loadExternalResource } from '@/lib/utils'
import { loadExternalResource } from '@/lib/utils'
import { useEffect } from 'react'
/**
* 自定义引入外部JS 和 CSS
* @returns
*/
const ExternalScript = () => {
if (isBrowser()) {
useEffect(() => {
// 静态导入本地自定义样式
loadExternalResource(BLOG.FONT_AWESOME, 'css')
loadExternalResource('/css/custom.css', 'css')
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) {
for (const url of BLOG.CUSTOM_EXTERNAL_JS) {
loadExternalResource(url, 'js')
@@ -25,7 +32,7 @@ const ExternalScript = () => {
BLOG.FONT_URL?.forEach(e => {
loadExternalResource(e, 'css')
})
}
}, [])
return null
}

View File

@@ -4,6 +4,7 @@ import mediumZoom from '@fisch0920/medium-zoom'
import React from 'react'
import { isBrowser } from '@/lib/utils'
import { Code } from 'react-notion-x/build/third-party/code'
import TweetEmbed from 'react-tweet-embed'
import 'katex/dist/katex.min.css'
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 }
)
const Tweet = ({ id }) => {
return <TweetEmbed tweetId={id} />
}
const NotionPage = ({ post, className }) => {
const zoom = isBrowser() && mediumZoom({
container: '.notion-viewport',
@@ -79,7 +84,7 @@ const NotionPage = ({ post, className }) => {
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
recordMap={post.blockMap}
mapPageUrl={mapPageUrl}
@@ -89,7 +94,8 @@ const NotionPage = ({ post, className }) => {
Collection,
Equation,
Modal,
Pdf
Pdf,
Tweet
}} />
<PrismMac />

View File

@@ -7,7 +7,7 @@ export const StarrySky = () => {
}, [])
return (
<div className="relative">
<canvas id="starry-sky-vixcity" className="fixed"></canvas>
<canvas id="starry-sky-vixcity" className="fixed pointer-events-none"></canvas>
</div>
)
}

View File

@@ -51,8 +51,7 @@ const Tabs = ({ className, children }) => {
{children.map((item, index) => {
return <section key={index}
data-aos="fade-up"
data-aos-duration="600"
data-aos-easing="ease-in-out"
data-aos-duration="300"
data-aos-once="true"
data-aos-anchor-placement="top-bottom">
{currentTab === index && item}

View File

@@ -85,18 +85,39 @@ function getCustomNav({ allPages }) {
const customNav = []
if (allPages && allPages.length > 0) {
allPages.forEach(p => {
if (p?.status === 'Published' && p?.type === 'Page') {
if (p?.slug?.indexOf('http') === 0) {
customNav.push({ icon: p.icon || null, name: p.title, to: p.slug, show: true })
} else {
customNav.push({ icon: p.icon || null, name: p.title, to: '/' + p.slug, show: true })
}
if (p?.slug?.indexOf('http') === 0) {
customNav.push({ icon: p.icon || null, name: p.title, to: p.slug, show: true })
} else {
customNav.push({ icon: p.icon || null, name: p.title, to: '/' + p.slug, show: true })
}
})
}
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
@@ -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 tagOptions = getAllTags({ allPages, tagOptions: getTagOptions(schema) })
const siteInfo = getBlogInfo({ collection, block })
const customNav = getCustomNav({ allPages: collectionData.filter(post => post.type === 'Page' && post.status === 'Published') })
// 新的菜单
const customMenu = getCustomMenu({ collectionData })
const latestPosts = getLatestPosts({ allPages, from, latestPostCount: 5 })
return {
@@ -236,6 +259,7 @@ async function getPageRecordMapByNotionAPI({ pageId, from }) {
categoryOptions,
rawMetadata,
customNav,
customMenu,
postCount,
pageIds,
latestPosts

View File

@@ -76,10 +76,14 @@ export default async function getPageProperties(id, block, schema, authToken, ta
// 映射值用户个性化type和status字段的下拉框选项在此映射回代码的英文标识
mapProperties(properties)
if (properties.type === 'Post') {
properties.slug = BLOG.POST_URL_PREFIX ? (BLOG.POST_URL_PREFIX + '/' + (properties.slug ?? properties.id)) : (properties.slug ?? properties.id)
} else {
properties.slug = (properties.slug ?? properties.id)
if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_post) {
properties.slug = (BLOG.POST_URL_PREFIX) ? generateCustomizeUrl(properties) : (properties.slug ?? properties.id)
} else if (properties.type === BLOG.NOTION_PROPERTY_NAME.type_page) {
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'
}
}
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)}`
}

View File

@@ -1,5 +1,7 @@
import BLOG from '@/blog.config'
/**
* Notion图片映射处理有emjji的图标
* Notion图片映射处理有emoji的图标
* @param {*} img
* @param {*} value
* @returns
@@ -18,7 +20,7 @@ const mapImgUrl = (img, block, type = 'block') => {
}
// 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
}

25
lib/robots.txt.js Normal file
View 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等其他平台这行代码会成功执行
}
}

View File

@@ -5,6 +5,11 @@ import ReactDOMServer from 'react-dom/server'
import { getPostBlocks } from './notion'
import NotionPage from '@/components/NotionPage'
/**
* 生成RSS内容
* @param {*} post
* @returns
*/
const createFeedContent = async post => {
// 加密的文章内容只返回摘要
if (post.password && post.password !== '') {
@@ -15,7 +20,7 @@ const createFeedContent = async post => {
post.blockMap = blockMap
const content = ReactDOMServer.renderToString(<NotionPage post={post} />)
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, '')
}
}
@@ -38,7 +43,6 @@ export async function generateRss(posts) {
for (const post of posts) {
feed.addItem({
title: post.title,
guid: `${post.id}`,
link: `${BLOG.LINK}/${post.slug}`,
description: post.summary,
content: await createFeedContent(post),
@@ -46,8 +50,14 @@ export async function generateRss(posts) {
})
}
fs.mkdirSync('./public/rss', { recursive: true })
fs.writeFileSync('./public/rss/feed.xml', feed.rss2())
fs.writeFileSync('./public/rss/atom.xml', feed.atom1())
fs.writeFileSync('./public/rss/feed.json', feed.json1())
try {
fs.mkdirSync('./public/rss', { recursive: true })
fs.writeFileSync('./public/rss/feed.xml', feed.rss2())
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
View 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>
`
}

View File

@@ -28,7 +28,8 @@ export const initDarkMode = (isDarkMode, updateDarkMode) => {
*/
export const initTheme = (theme, changeTheme) => {
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
if (queryTheme !== theme && ALL_THEME.indexOf(queryTheme) > -1) {
currentTheme = queryTheme

View File

@@ -124,3 +124,32 @@ export const getListByPage = function (list, 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
}

View File

@@ -11,7 +11,9 @@ module.exports = withBundleAnalyzer({
'gravatar.com',
'www.notion.so',
'avatars.githubusercontent.com',
'images.unsplash.com'
'images.unsplash.com',
'source.unsplash.com',
'p1.qhimg.com'
]
},
// 默认将feed重定向至 /public/rss/feed.xml

View File

@@ -1,6 +1,6 @@
{
"name": "notion-next",
"version": "3.11.0",
"version": "3.12.4",
"homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT",
"repository": {
@@ -50,8 +50,9 @@
"react-dom": "^18.2.0",
"react-facebook": "^8.1.4",
"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-tweet-embed": "~2.0.0",
"smoothscroll-polyfill": "^0.4.4",
"twikoo": "1.6.9",
"typed.js": "^2.0.12",

View File

@@ -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 }
return <ThemeComponents.LayoutSlug {...props} showArticleInfo={true} meta={meta} />
}
@@ -100,7 +100,7 @@ export async function getStaticPaths() {
const from = 'slug-paths'
const { allPages } = await getGlobalNotionData({ from })
return {
paths: allPages.map(row => ({ params: { slug: [row.slug] } })),
paths: allPages?.map(row => ({ params: { slug: [row.slug] } })),
fallback: true
}
}

View File

@@ -1,5 +1,5 @@
import BLOG from 'blog.config'
import React from 'react'
import React, { useEffect } from 'react'
import dynamic from 'next/dynamic'
import 'animate.css'
@@ -20,10 +20,11 @@ import { Sakura } from '@/components/Sakura'
import { StarrySky } from '@/components/StarrySky'
import MusicPlayer from '@/components/MusicPlayer'
import ExternalScript from '@/components/ExternalScript'
import { isBrowser } from '@/lib/utils'
import smoothscroll from 'smoothscroll-polyfill'
import AOS from 'aos'
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 Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false })
@@ -55,14 +56,17 @@ const MyApp = ({ Component, pageProps }) => {
<ExternalScript/>
</>
if (isBrowser()) {
useEffect(() => {
AOS.init()
}
if (isMobile()) {
smoothscroll.polyfill()
}
}, [])
return (
<GlobalContextProvider>
{externalPlugins}
<Component {...pageProps} />
{externalPlugins}
</GlobalContextProvider>
)
}

View File

@@ -4,6 +4,7 @@ import { getGlobalNotionData } from '@/lib/notion/getNotionData'
import * as ThemeMap from '@/themes'
import { useGlobal } from '@/lib/global'
import { generateRss } from '@/lib/rss'
import { generateRobotsTxt } from '@/lib/robots.txt'
const Index = props => {
const { theme } = useGlobal()
const ThemeComponents = ThemeMap[theme]
@@ -17,7 +18,6 @@ export async function getStaticProps() {
const { siteInfo } = props
props.posts = props.allPages.filter(page => page.type === 'Post' && page.status === 'Published')
delete props.allPages
const meta = {
title: `${siteInfo?.title} | ${siteInfo?.description}`,
description: siteInfo?.description,
@@ -43,8 +43,14 @@ export async function getStaticProps() {
}
}
// 异步生成Feed订阅
generateRss(props?.latestPosts || [])
// 生成robotTxt
generateRobotsTxt()
// 生成Feed订阅
if (JSON.parse(BLOG.ENABLE_RSS)) {
generateRss(props?.latestPosts || [])
}
delete props.allPages
return {
props: {

View File

@@ -122,7 +122,8 @@ async function filterByMemCache(allPosts, keyword) {
const articleInfo = post.title + post.summary + tagContent + categoryContent
let hit = articleInfo.toLowerCase().indexOf(keyword) > -1
let indexContent = [post.summary]
if (page && page.block) {
// 防止搜到加密文章的内容
if (page && page.block && !post.password) {
const contentIds = Object.keys(page.block)
contentIds.forEach(id => {
const properties = page?.block[id]?.value?.properties

View File

@@ -38,7 +38,7 @@ export const getServerSideProps = async (ctx) => {
priority: '0.7'
}
]
const postFields = allPages?.map(post => {
const postFields = allPages?.filter(p => p.status === BLOG.NOTION_PROPERTY_NAME.status_publish)?.map(post => {
return {
loc: `${BLOG.LINK}/${post.slug}`,
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)
// 缓存
// ctx.res.setHeader(
// 'Cache-Control',
// 'public, s-maxage=10, stale-while-revalidate=59'
// )
ctx.res.setHeader(
'Cache-Control',
'public, max-age=3600, stale-while-revalidate=59'
)
return getServerSideSitemap(ctx, fields)
}

11
public/avatar.svg Normal file
View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View 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;
}

View 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;
}
}

View 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;
}

View File

@@ -167,6 +167,13 @@ nav {
-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 {
display: inline-block;
vertical-align: baseline;
@@ -204,7 +211,7 @@ nav {
}
.nobelium .notion-code{
@apply max-w-2xl;
/* @apply max-w-2xl; */
}
.next #announcement-content *{

View File

@@ -180,7 +180,7 @@
}
.notion-simple-table {
@apply whitespace-nowrap overflow-x-scroll block
@apply whitespace-nowrap overflow-x-auto block
}
.notion-app {
@@ -207,7 +207,6 @@
.medium-zoom-image {
border-radius: 0;
@apply bg-gray-100 dark:bg-black
}
.medium-zoom-image--opened {
@@ -939,7 +938,7 @@ code[class*='language-'] {
align-self: flex-start;
width: 24px;
height: 24px;
font-size: 1.3em;
font-size: 1em;
line-height: 1em;
}
@@ -1118,10 +1117,11 @@ code[class*='language-'] {
.notion-table-of-contents {
width: 100%;
margin: 4px 0;
@apply bg-gray-50 dark:bg-black p-2
}
.notion-table-of-contents-item {
color: inherit;
/* color: inherit; */
text-decoration: none;
user-select: none;
transition: background 20ms ease-in 0s;
@@ -1137,6 +1137,8 @@ code[class*='language-'] {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@apply text-blue-600 dark:text-blue-200
}
.notion-table-of-contents-item:hover {
@@ -2008,4 +2010,11 @@ code.language-mermaid {
.notion-equation-inline .katex-display {
margin: 0 0 !important;
}
.two-line-clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}

View File

@@ -21,9 +21,10 @@ module.exports = {
}
},
maxWidth: {
side: '14rem'
side: '14rem',
'9/10': '90%'
}
},
}
},
variants: {
extend: {}

View File

@@ -16,7 +16,7 @@ import BLOG from '@/blog.config'
const LayoutBase = props => {
const { children, meta } = props
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} />
{/* 顶栏LOGO */}
<Header {...props} />

View File

@@ -26,7 +26,7 @@ export const ArticleLock = props => {
<div className='text-center space-y-3'>
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
<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" >
<i className={'duration-200 cursor-pointer fas fa-key dark:text-black'} >&nbsp;{locale.COMMON.SUBMIT}</i>
</div>

View File

@@ -78,5 +78,5 @@ export const BlogListScroll = props => {
</div>
</div>
);
)
}

View File

@@ -21,5 +21,5 @@ export const Header = (props) => {
</div>
</div>
</header>
);
)
}

View File

@@ -1,6 +1,6 @@
import { useGlobal } from '@/lib/global'
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 { locale } = useGlobal()
let links = [
{ icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG_EMPTY.MENU_SEARCH },
{ icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG_EMPTY.MENU_ARCHIVE },
{ icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG_EMPTY.MENU_CATEGORY },
{ icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG_EMPTY.MENU_TAG }
{ 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_EXAMPLE.MENU_ARCHIVE },
{ 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_EXAMPLE.MENU_TAG }
]
if (customNav) {

View File

@@ -62,7 +62,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
ref={searchInputRef}
type='text'
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}
onCompositionStart={lockSearchInput}
onCompositionUpdate={lockSearchInput}

View File

@@ -1,8 +1,8 @@
const CONFIG_EMPTY = {
const CONFIG_EXAMPLE = {
// 菜单配置
MENU_CATEGORY: true, // 显示分类
MENU_TAG: true, // 显示标签
MENU_ARCHIVE: true, // 显示归档
MENU_SEARCH: true // 显示搜索
}
export default CONFIG_EMPTY
export default CONFIG_EXAMPLE

View File

@@ -1,4 +1,4 @@
import CONFIG_EMPTY from './config_empty'
import CONFIG_EXAMPLE from './config_example'
import { LayoutIndex } from './LayoutIndex'
import { LayoutSearch } from './LayoutSearch'
import { LayoutArchive } from './LayoutArchive'
@@ -11,7 +11,7 @@ import { LayoutTag } from './LayoutTag'
import { LayoutTagIndex } from './LayoutTagIndex'
export {
CONFIG_EMPTY as THEME_CONFIG,
CONFIG_EXAMPLE as THEME_CONFIG,
LayoutIndex,
LayoutSearch,
LayoutArchive,

View File

@@ -3,6 +3,7 @@ import TopNav from './components/TopNav'
import AsideLeft from './components/AsideLeft'
import Live2D from '@/components/Live2D'
import BLOG from '@/blog.config'
import { isBrowser, loadExternalResource } from '@/lib/utils'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
@@ -25,7 +26,12 @@ const LayoutBase = (props) => {
meta
} = props
const leftAreaSlot = <Live2D/>
return (<>
if (isBrowser()) {
loadExternalResource('/css/theme-fukasawa.css', 'css')
}
return (<div id='theme-fukasawa' >
<CommonHead meta={meta} />
<TopNav {...props}/>
<div className={(BLOG.LAYOUT_SIDEBAR_REVERSE ? 'flex-row-reverse' : '') + ' flex'}>
@@ -38,7 +44,7 @@ const LayoutBase = (props) => {
</main>
</div>
</>)
</div>)
}
export default LayoutBase

View File

@@ -30,7 +30,7 @@ export const ArticleLock = props => {
<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"
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}

View File

@@ -2,9 +2,8 @@ import BLOG from '@/blog.config'
import Link from 'next/link'
import React from 'react'
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
// matery 主题默认强制显示图片
if (post && !post.page_cover) {
@@ -13,44 +12,45 @@ const BlogCard = ({ post, showSummary, siteInfo }) => {
const showPageCover = CONFIG_FUKA.POST_LIST_COVER && post?.page_cover
return (
<Card className="w-full lg:max-w-sm p-2 h-full overflow-auto">
<div
key={post.id}
className="flex flex-col-reverse justify-between duration-300"
>
<div className="p-2 flex flex-col w-full">
<Link
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
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`}>
<div data-aos="fade-up" data-aos-duration="500" data-aos-once="true"
className='w-full lg:max-w-sm p-2 h-full overflow-auto'>
<section className="shadow mb-4 p-2 bg-white dark:bg-hexo-black-gray hover:shadow-lg duration-200">
<div className="flex flex-col-reverse justify-between duration-300">
<div className="p-2 flex flex-col w-full">
<Link
href={`${BLOG.SUB_PATH}/${post.slug}`}
passHref
className={`break-words 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`}>
{post.title}
{post.title}
</Link>
</Link>
{(!showPreview || showSummary) && (
<p className="mt-4 mb-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7 overflow-hidden">
{post.summary}
</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' /> */}
{(!showPreview || showSummary) && (
<p className="mt-4 mb-4 text-gray-700 dark:text-gray-300 text-sm font-light leading-7 overflow-hidden">
{post.summary}
</p>
)}
</div>
</Link>
)}
</div>
</Card>
{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>
</Link>
)}
</div>
</section>
</div>
)
}

View File

@@ -1,5 +1,4 @@
import BLOG from '@/blog.config'
import { useEffect, useState } from 'react'
import BlogCard from './BlogCard'
import BlogPostListEmpty from './BlogListEmpty'
import PaginationSimple from './PaginationSimple'
@@ -15,25 +14,6 @@ import PaginationSimple from './PaginationSimple'
const BlogListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE)
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) {
return <BlogPostListEmpty />
@@ -41,10 +21,10 @@ const BlogListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
return (
<div>
{/* 文章列表 */}
<div id="container" style={{ columnCount: colCount }}>
<div id="container" className='grid-container'>
{posts?.map(post => (
<div key={post.id} className='justify-center flex' style={{ breakInside: 'avoid' }}>
<BlogCard key={post.id} post={post} siteInfo={siteInfo} />
<div key={post.id} className='grid-item justify-center flex' style={{ breakInside: 'avoid' }}>
<BlogCard index={posts.indexOf(post)} key={post.id} post={post} siteInfo={siteInfo} />
</div>
))}
</div>

View File

@@ -3,7 +3,7 @@ import React from 'react'
import BlogCard from './BlogCard'
import BlogPostListEmpty from './BlogListEmpty'
import { useGlobal } from '@/lib/global'
import throttle from 'lodash.throttle'
/**
* 文章列表分页表格
* @param page 当前页
@@ -14,18 +14,7 @@ import throttle from 'lodash.throttle'
*/
const BlogListScroll = props => {
const { posts = [], siteInfo } = props
const [colCount, changeCol] = React.useState(1)
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 [page, updatePage] = React.useState(1)
@@ -45,45 +34,41 @@ const BlogListScroll = props => {
}
// 监听滚动自动分页加载
const scrollTrigger = React.useCallback(throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500))
const scrollTrigger = () => {
requestAnimationFrame(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
})
}
React.useEffect(() => {
updateCol()
window.addEventListener('scroll', scrollTrigger)
window.addEventListener('resize', updateCol)
return () => {
window.removeEventListener('resize', updateCol)
window.removeEventListener('scroll', scrollTrigger)
}
})
}, [])
if (!posts || posts.length === 0) {
return <BlogPostListEmpty />
} else {
return (
<div id="container" ref={targetRef} >
{/* 文章列表 */}
<div style={{ columnCount: colCount }}>
{postsToShow?.map(post => (
<div key={post.id} className='justify-center flex' style={{ breakInside: 'avoid' }}>
<BlogCard key={post.id} post={post} siteInfo={siteInfo} />
</div>
))}
</div>
<div id="container" ref={targetRef} className='grid-container' >
{/* 文章列表 */}
{postsToShow?.map(post => (
<div key={post.id} className='grid-item justify-center flex' style={{ breakInside: 'avoid' }}>
<BlogCard index={posts.indexOf(post)} key={post.id} post={post} siteInfo={siteInfo} />
</div>
))}
<div className="w-full my-4 py-4 text-center cursor-pointer "
onClick={handleGetMore}>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
</div>
<div className="w-full my-4 py-4 text-center cursor-pointer "
onClick={handleGetMore}>
{' '}
{hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '}
</div>
</div>
)
}
}

View File

@@ -1,11 +1,5 @@
const Card = ({ children, headerSlot, className }) => {
return <div 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"
>
return <div data-aos="fade-in" data-aos-duration="1000" className={className}>
<>{headerSlot}</>
<section className="shadow mb-4 p-2 bg-white dark:bg-hexo-black-gray hover:shadow-lg duration-200">
{children}

View File

@@ -27,7 +27,7 @@ const Catalog = ({ toc }) => {
// 同步选中目录事件
const [activeSection, setActiveSection] = React.useState(null)
const throttleMs = 100
const throttleMs = 200
const actionSectionScrollSpy = React.useCallback(throttle(() => {
const sections = document.getElementsByClassName('notion-h')
let prevBBox = null

View File

@@ -53,7 +53,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => {
<input
ref={searchInputRef}
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}
onCompositionStart={lockSearchInput}
onCompositionUpdate={lockSearchInput}

View File

@@ -1,11 +1,10 @@
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 JumpToTopButton from './components/JumpToTopButton'
import SideRight from './components/SideRight'
import TopNav from './components/TopNav'
import smoothscroll from 'smoothscroll-polyfill'
import FloatDarkModeButton from './components/FloatDarkModeButton'
import Live2D from '@/components/Live2D'
import LoadingCover from './components/LoadingCover'
@@ -15,7 +14,13 @@ import dynamic from 'next/dynamic'
const FacebookPage = dynamic(
() => {
return import('@/components/FacebookPage')
let facebook = <></>
try {
facebook = import('@/components/FacebookPage')
} catch (err) {
console.error(err)
}
return facebook
},
{ ssr: false }
)
@@ -28,7 +33,7 @@ const FacebookPage = dynamic(
*/
const LayoutBase = props => {
const { children, headerSlot, floatSlot, meta, siteInfo } = props
const [show, switchShow] = useState(false)
const [showFloatButton, switchShow] = useState(false)
// const [percent, changePercent] = useState(0) // 页面阅读百分比
const rightAreaSlot = (
<>
@@ -37,8 +42,8 @@ const LayoutBase = props => {
</>
)
const { onLoading } = useGlobal()
const scrollListener = () => {
const throttleMs = 200
const scrollListener = useCallback(throttle(() => {
const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight
const scrollY = window.pageYOffset
@@ -47,26 +52,24 @@ const LayoutBase = props => {
if (per > 100) per = 100
const shouldShow = scrollY > 100 && per > 0
if (shouldShow !== show) {
if (shouldShow !== showFloatButton) {
switchShow(shouldShow)
}
// changePercent(per)
}
}, throttleMs))
useEffect(() => {
smoothscroll.polyfill()
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
}, [])
return (
<div className="bg-hexo-background-gray dark:bg-black">
<div id='theme-hexo'>
<CommonHead meta={meta} siteInfo={siteInfo}/>
<TopNav {...props} />
{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
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'}
@@ -79,13 +82,8 @@ const LayoutBase = props => {
</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={
(show ? 'animate__animated ' : 'hidden') +
' animate__fadeInUp justify-center duration-300 animate__faster flex flex-col items-center cursor-pointer '
}
>
<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 className={'justify-center flex flex-col items-center cursor-pointer'}>
<FloatDarkModeButton />
{floatSlot}
<JumpToTopButton />

View File

@@ -7,8 +7,8 @@ import LayoutBase from './LayoutBase'
import React from 'react'
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} />}
</LayoutBase>
}

View File

@@ -46,7 +46,7 @@ export const LayoutSlug = props => {
showTag={false}
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 && <div id="container" className="overflow-x-auto flex-grow mx-auto md:w-full md:px-5 ">

View File

@@ -6,14 +6,8 @@ const NotionPage = dynamic(() => import('@/components/NotionPage'))
const Announcement = ({ post, className }) => {
const { locale } = useGlobal()
if (post?.blockMap) {
return <div
data-aos="fade-up"
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">
return <div className={className}>
<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">
<div><i className='mr-2 fas fa-bullhorn' />{locale.COMMON.ANNOUNCEMENT}</div>
{post && (<div id="announcement-content">
<NotionPage post={post} className='text-center ' />

View File

@@ -25,7 +25,7 @@ export const ArticleLock = props => {
<div className='text-center space-y-3'>
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
<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" >
<i className={'duration-200 cursor-pointer fas fa-key'} >&nbsp;{locale.COMMON.SUBMIT}</i>
</div>

View File

@@ -1,107 +1,57 @@
import BLOG from '@/blog.config'
import Link from 'next/link'
import React from 'react'
import TagItemMini from './TagItemMini'
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
if (post && !post.page_cover && CONFIG_HEXO.POST_LIST_COVER_DEFAULT) {
post.page_cover = siteInfo?.pageCover
}
const showPageCover = CONFIG_HEXO.POST_LIST_COVER && post?.page_cover
const delay = (index % 2) * 200
return (
<div
key={post.id}
data-aos="fade-up"
data-aos-duration="600"
data-aos-easing="ease-in-out"
data-aos-once="false"
data-aos-duration="200"
data-aos-delay={delay}
data-aos-once="true"
data-aos-anchor-placement="top-bottom"
className={`flex md:flex-row flex-col-reverse ${index % 2 === 0 ? 'md:flex-row-reverse' : ''}
w-full md:h-72 h-96 justify-between overflow-hidden drop-shadow-md
border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray `}>
key={post.id}
className={`flex md:flex-row flex-col-reverse ${CONFIG_HEXO.POST_LIST_IMG_CROSSOVER ? 'even:md:flex-row-reverse' : ''}
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
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>
{/* 文字内容 */}
<BlogPostCardInfo index={index} post={post} showPageCover={showPageCover} showPreview={showPreview} showSummary={showSummary}/>
{/* 图片封面 */}
{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>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
{/* <img
src={post?.page_cover}
alt={post.title}
className="h-full w-full hover:scale-125 transform object-cover duration-500"
/>
{/* <Image className='hover:scale-125 transform duration-500' src={post?.page_cover} alt={post.title} layout='fill' objectFit='cover' loading='lazy' /> */}
loading='lazy'
className="w-full relative cursor-pointer object-cover duration-200 hover:scale-125 "
/> */}
<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>
</div>
)}

View 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>
}

View File

@@ -22,7 +22,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
{/* 文章列表 */}
<div className="space-y-6 px-2">
{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>
{showPagination && <PaginationNumber page={page} totalPage={totalPage} />}

View File

@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
import BlogPostCard from './BlogPostCard'
import BlogPostListEmpty from './BlogPostListEmpty'
import { useGlobal } from '@/lib/global'
import throttle from 'lodash.throttle'
import React from 'react'
import CONFIG_HEXO from '../config_hexo'
import { getListByPage } from '@/lib/utils'
@@ -31,13 +30,15 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_HE
}
// 监听滚动自动分页加载
const scrollTrigger = React.useCallback(throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500))
const scrollTrigger = () => {
requestAnimationFrame(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
})
}
// 监听滚动
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'>
{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>

View File

@@ -1,7 +1,7 @@
const Card = ({ children, headerSlot, className }) => {
return <div className={className}>
<>{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}
</section>
</div>

View File

@@ -28,7 +28,7 @@ const Catalog = ({ toc }) => {
// 同步选中目录事件
const [activeSection, setActiveSection] = React.useState(null)
const throttleMs = 100
const throttleMs = 200
const actionSectionScrollSpy = React.useCallback(throttle(() => {
const sections = document.getElementsByClassName('notion-h')
let prevBBox = null

View File

@@ -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 CONFIG_HEXO from '../config_hexo'
import NavButtonGroup from './NavButtonGroup'
import throttle from 'lodash.throttle'
let wrapperTop = 0
let windowTop = 0
let autoScroll = false
const enableAutoScroll = false // 是否开启自动吸附滚动
/**
*
@@ -16,6 +19,7 @@ const Header = props => {
const { siteInfo } = props
useEffect(() => {
updateHeaderHeight()
if (!typed && window && document.getElementById('typed')) {
changeType(
new Typed('#typed', {
@@ -28,6 +32,7 @@ const Header = props => {
})
)
}
if (enableAutoScroll) {
scrollTrigger()
window.addEventListener('scroll', scrollTrigger)
@@ -42,75 +47,75 @@ const Header = props => {
}
})
function updateHeaderHeight () {
setTimeout(() => {
function updateHeaderHeight() {
requestAnimationFrame(() => {
const wrapperElement = document.getElementById('wrapper')
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 (
<header
id="header"
className="duration-500 md:bg-fixed w-full bg-cover bg-center h-screen bg-black text-white relative z-10"
style={{
backgroundImage:
`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}")`
}}
>
<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>
<header
id="header"
className="w-full h-screen bg-black text-white relative"
>
<div className={`w-full h-full ${CONFIG_HEXO.HOME_NAV_BACKGROUND_IMG_FIXED ? 'fixed' : ''}`}>
{/* <Image src={siteInfo.pageCover} fill
style={{ objectFit: 'cover' }}
className='opacity-70'
placeholder='blur'
blurDataURL='/bg_image.jpg' /> */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo.pageCover} className='h-full w-full object-cover opacity-70 ' />
</div>
{/* 首页导航插件 */}
{ CONFIG_HEXO.HOME_NAV_BUTTONS && <NavButtonGroup {...props}/>}
<div className="absolute bottom-0 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>
<div
onClick={() => {
window.scrollTo({ top: wrapperTop, behavior: 'smooth' })
}}
className="cursor-pointer w-full text-center py-4 text-3xl absolute bottom-10 text-white"
>
<i className='animate-bounce fas fa-angle-down'/>
</div>
</header>
{/* 首页导航插件 */}
{CONFIG_HEXO.HOME_NAV_BUTTONS && <NavButtonGroup {...props} />}
</div>
<div
onClick={() => { window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) }}
className="cursor-pointer w-full text-center py-4 text-3xl absolute bottom-10 text-white"
>
<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

View File

@@ -19,7 +19,7 @@ export default function HeaderArticle({ post, siteInfo }) {
return (
<div
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 }}
>
<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>
</header>
</div>
);
)
}

View File

@@ -11,10 +11,11 @@ import CONFIG_HEXO from '../config_hexo'
* @constructor
*/
const JumpToTopButton = ({ showPercent = true, percent }) => {
const { locale } = useGlobal()
if (!CONFIG_HEXO.WIDGET_TO_TOP) {
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' })} >
<div title={locale.POST.TOP} ><i className='fas fa-arrow-up text-xs' /></div>
{showPercent && (<div className='text-xs hidden lg:block'>{percent}</div>)}

View File

@@ -1,5 +1,6 @@
import BLOG from '@/blog.config'
import { useGlobal } from '@/lib/global'
// import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
@@ -19,46 +20,53 @@ const LatestPostsGroup = ({ latestPosts, siteInfo }) => {
}
return <>
<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 className=" mb-2 px-1 flex flex-nowrap justify-between">
<div>
<div className='text-line-2'>{post.title}</div>
<div className="text-gray-500">{post.lastEditedTime}</div>
<i className="mr-2 fas fas fa-history" />
{locale.COMMON.LATEST_POSTS}
</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

View File

@@ -8,6 +8,7 @@ const MenuButtonGroupTop = (props) => {
const { locale } = useGlobal()
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-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 },
@@ -36,12 +37,12 @@ const MenuButtonGroupTop = (props) => {
</div>
</Link>
);
)
} else {
return null
}
})}
</nav>
);
)
}
export default MenuButtonGroupTop

View File

@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [percent])
}, [])
return (
<div className="h-4 w-full shadow-2xl bg-gray-400 ">

View File

@@ -69,7 +69,7 @@ const SearchInput = props => {
ref={searchInputRef}
type="text"
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}
onCompositionStart={lockSearchInput}

View File

@@ -1,7 +1,6 @@
import { useGlobal } from '@/lib/global'
import throttle from 'lodash.throttle'
import Link from 'next/link'
import { useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import CategoryGroup from './CategoryGroup'
import Collapse from './Collapse'
import Logo from './Logo'
@@ -10,6 +9,7 @@ import TagGroups from './TagGroups'
import MenuButtonGroupTop from './MenuButtonGroupTop'
import MenuList from './MenuList'
import { useRouter } from 'next/router'
import throttle from 'lodash.throttle'
let windowTop = 0
@@ -19,17 +19,33 @@ let windowTop = 0
* @returns
*/
const TopNav = props => {
const searchDrawer = useRef()
const { tags, currentTag, categories, currentCategory } = props
const { locale } = useGlobal()
const searchDrawer = useRef()
const { isDarkMode } = useGlobal()
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 nav = document.querySelector('#sticky-nav')
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 // 透明导航条的条件
@@ -47,6 +63,7 @@ const TopNav = props => {
nav && nav.classList.replace('transparent', 'dark:bg-hexo-black-gray')
}
const showNav = scrollS <= windowTop || scrollS < 5 || (header && scrollS <= header.clientHeight * 2)// 非首页无大图时影藏顶部 滚动条置顶时隐藏
if (!showNav) {
nav && nav.classList.replace('top-0', '-top-20')
windowTop = scrollS
@@ -55,7 +72,8 @@ const TopNav = props => {
windowTop = scrollS
}
navDarkMode()
}, 200)
}, throttleMs)
)
const navDarkMode = () => {
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 = <>
{ categories && (
<section className='mt-8'>
<div className='text-sm flex flex-nowrap justify-between font-light px-2'>
<div className='text-gray-600 dark:text-gray-200'><i className='mr-2 fas fa-th-list' />{locale.COMMON.CATEGORY}</div>
<Link
href={'/category'}
passHref
className='mb-3 text-gray-400 hover:text-black dark:text-gray-400 dark:hover:text-white hover:underline cursor-pointer'>
{categories && (
<section className='mt-8'>
<div className='text-sm flex flex-nowrap justify-between font-light px-2'>
<div className='text-gray-600 dark:text-gray-200'><i className='mr-2 fas fa-th-list' />{locale.COMMON.CATEGORY}</div>
<Link
href={'/category'}
passHref
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>
</div>
<CategoryGroup currentCategory={currentCategory} categories={categories} />
</section>
) }
</Link>
</div>
<CategoryGroup currentCategory={currentCategory} categories={categories} />
</section>
)}
{ tags && (
<section className='mt-4'>
<div className='text-sm py-2 px-2 flex flex-nowrap justify-between font-light dark:text-gray-200'>
<div className='text-gray-600 dark:text-gray-200'><i className='mr-2 fas fa-tag'/>{locale.COMMON.TAGS}</div>
<Link
href={'/tag'}
passHref
className='text-gray-400 hover:text-black dark:hover:text-white hover:underline cursor-pointer'>
{tags && (
<section className='mt-4'>
<div className='text-sm py-2 px-2 flex flex-nowrap justify-between font-light dark:text-gray-200'>
<div className='text-gray-600 dark:text-gray-200'><i className='mr-2 fas fa-tag' />{locale.COMMON.TAGS}</div>
<Link
href={'/tag'}
passHref
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>
</div>
<div className='p-2'>
<TagGroups tags={tags} currentTag={currentTag} />
</div>
</section>
) }
</Link>
</div>
<div className='p-2'>
<TagGroups tags={tags} currentTag={currentTag} />
</div>
</section>
)}
</>
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 className='w-full flex justify-between items-center px-4 py-2'>
<div className='flex'>
<Logo {...props}/>
</div>
{/* 导航栏 */}
<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='flex'>
<Logo {...props} />
</div>
{/* 右侧功能 */}
<div className='mr-1 justify-end items-center '>
<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'>
{ isOpen ? <i className='fas fa-times'/> : <i className='fas fa-bars'/> }
</div>
</div>
</div>
{/* 右侧功能 */}
<div className='mr-1 justify-end items-center '>
<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'>
{isOpen ? <i className='fas fa-times' /> : <i className='fas fa-bars' />}
</div>
</div>
</div>
<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 '>
<MenuList {...props}/>
<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 '>
<MenuList {...props} />
</div>
</Collapse>
</div>
</Collapse>
</div>
</div>)
</div>)
}
export default TopNav

View File

@@ -3,8 +3,10 @@ const CONFIG_HEXO = {
HOME_BANNER_GREETINGS: ['Hi我是一个程序员', 'Hi我是一个打工人', 'Hi我是一个干饭人', '欢迎来到我的博客🎉'], // 首页大图标语文字
HOME_NAV_BUTTONS: true, // 首页是否显示分类大图标按钮
HOME_NAV_BACKGROUND_IMG_FIXED: true, // 首页背景图滚动时是否固定true 则滚动时图片不懂; false则随鼠标滚动
// 菜单配置
MENU_INDEX: true, // 显示首页
MENU_CATEGORY: true, // 显示分类
MENU_TAG: true, // 显示标签
MENU_ARCHIVE: true, // 显示归档
@@ -14,6 +16,7 @@ const CONFIG_HEXO = {
POST_LIST_COVER_DEFAULT: true, // 封面为空时用站点背景做默认封面
POST_LIST_SUMMARY: true, // 文章摘要
POST_LIST_PREVIEW: true, // 读取文章预览
POST_LIST_IMG_CROSSOVER: true, // 博客列表图片左右交错
ARTICLE_ADJACENT: true, // 显示上一篇下一篇文章推荐
ARTICLE_COPYRIGHT: true, // 显示文章版权声明

View File

@@ -8,6 +8,25 @@ import * as medium from './medium'
import * as nobelium from './nobelium'
import * as matery from './matery'
import * as example from './example'
import * as simple from './simple'
export const ALL_THEME = ['hexo', 'matery', 'next', 'medium', 'fukasawa', 'nobelium', 'example']
export { hexo, next, medium, fukasawa, nobelium, matery, example }
export const ALL_THEME = [
'hexo',
'matery',
'next',
'medium',
'fukasawa',
'nobelium',
'example',
'simple'
]
export {
hexo,
next,
medium,
fukasawa,
nobelium,
matery,
example,
simple
}

View File

@@ -1,15 +1,15 @@
import CommonHead from '@/components/CommonHead'
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import Footer from './components/Footer'
import JumpToTopButton from './components/JumpToTopButton'
import TopNav from './components/TopNav'
import smoothscroll from 'smoothscroll-polyfill'
import Live2D from '@/components/Live2D'
import LoadingCover from './components/LoadingCover'
import { useGlobal } from '@/lib/global'
import BLOG from '@/blog.config'
import FloatDarkModeButton from './components/FloatDarkModeButton'
import throttle from 'lodash.throttle'
/**
* 基础布局 采用左右两侧布局,移动端使用顶部导航栏
@@ -22,28 +22,22 @@ const LayoutBase = props => {
const [show, switchShow] = useState(false)
const { onLoading } = useGlobal()
const scrollListener = () => {
const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight
const throttleMs = 200
const scrollListener = useCallback(throttle(() => {
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
if (per > 100) per = 100
const shouldShow = scrollY > 300 && per > 0
const shouldShow = scrollY > 300
if (shouldShow !== show) {
switchShow(shouldShow)
}
// changePercent(per)
}
}, throttleMs))
useEffect(() => {
smoothscroll.polyfill()
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
}, [])
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} />
@@ -67,12 +61,8 @@ const LayoutBase = props => {
</div>
{/* 右下角悬浮 */}
<div className="bottom-12 right-2 fixed justify-end z-20">
<div className={
(show ? 'animate__animated ' : 'hidden') +
' animate__fadeInUp justify-center duration-300 animate__faster flex flex-col items-center cursor-pointer '
}
>
<div className={ (show ? ' opacity-100 fixed ' : ' hidden opacity-0 ') + ' transition-all duration-200 bottom-12 right-2 justify-end z-20' }>
<div className= ' justify-center flex flex-col items-center cursor-pointer '>
<JumpToTopButton />
</div>
</div>

View File

@@ -1,4 +1,4 @@
import React from 'react'
import React, { useCallback, useEffect } from 'react'
import { ArticleLock } from './components/ArticleLock'
import HeaderArticle from './components/HeaderArticle'
import LayoutBase from './LayoutBase'
@@ -9,23 +9,25 @@ import ArticleCopyright from './components/ArticleCopyright'
import { ArticleInfo } from './components/ArticleInfo'
import Catalog from './components/Catalog'
import JumpToCommentButton from './components/JumpToCommentButton'
import throttle from 'lodash.throttle'
export const LayoutSlug = props => {
const { post, lock, validPassword } = props
const [show, switchShow] = React.useState(false)
const throttleMs = 200
const scrollListener = () => {
const scrollListener = useCallback(throttle(() => {
const scrollY = window.pageYOffset
const shouldShow = scrollY > 220 && post?.toc?.length > 0
if (shouldShow !== show) {
switchShow(shouldShow)
}
}
React.useEffect(() => {
}, throttleMs))
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
}, [])
if (!post) {
return <LayoutBase
@@ -36,98 +38,92 @@ export const LayoutSlug = props => {
></LayoutBase>
}
return (
<LayoutBase
headerSlot={<HeaderArticle {...props} />}
{...props}
showCategory={false}
showTag={false}
>
return (<LayoutBase
headerSlot={<HeaderArticle {...props} />}
{...props}
showCategory={false}
showTag={false}
>
<div id='inner-wrapper'>
<div className={'drop-shadow-xl 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">
{lock && <ArticleLock validPassword={validPassword} />}
<div id='inner-wrapper'>
<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">
{lock && <ArticleLock validPassword={validPassword} />}
{!lock && <div id="container" className="overflow-x-auto md:w-full px-3 ">
{post?.type === 'Post' && <>
<div
data-aos="fade-down"
data-aos-duration="500"
data-aos-easing="ease-in-out"
data-aos-once="false"
data-aos-anchor-placement="top-center"
className='px-10'>
<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>
{!lock && <div id="container" className="overflow-x-auto md:w-full px-3 ">
{post?.type && post?.type === 'Post' && <>
<div
data-aos="fade-down"
data-aos-duration="100"
data-aos-once="false"
data-aos-anchor-placement="top-center"
className='px-10'>
<ArticleInfo post={post} />
</div>
<hr />
</>}
</div>}
</div>
{post.type === 'Post' && <ArticleAdjacent {...props} />}
<div className='lg:px-10 subpixel-antialiased'>
<article itemScope >
{/* 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' >
<div 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-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>
<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="overflow-x-auto dark:bg-hexo-black-gray px-3">
<Comment frontMatter={post} />
</div>
</div>
</div>}
</div>
<div className='fixed bottom-28 right-4'>
<JumpToCommentButton />
</div>
{/* 文章推荐 */}
{post.type === 'Post' && <ArticleAdjacent {...props} />}
{/* 文章目录 */}
{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>
</LayoutBase>
<div className='fixed bottom-28 right-4'>
<JumpToCommentButton />
</div>
</div>
</LayoutBase>
)
}

View File

@@ -25,7 +25,7 @@ export const ArticleLock = props => {
<div className='text-center space-y-3'>
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
<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" >
<i className={'duration-200 cursor-pointer fas fa-key'} >&nbsp;{locale.COMMON.SUBMIT}</i>
</div>

View File

@@ -3,41 +3,45 @@ import Link from 'next/link'
import React from 'react'
import TagItemMini from './TagItemMini'
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
// matery 主题默认强制显示图片
if (post && !post.page_cover) {
post.page_cover = siteInfo?.pageCover
}
const showPageCover = CONFIG_MATERY.POST_LIST_COVER && !showPreview && post?.page_cover
const delay = (index % 3) * 300
return (
<div
data-aos="zoom-in"
data-aos-duration="300"
data-aos-easing="ease-in-out"
data-aos-once="false"
data-aos-duration="500"
data-aos-delay={delay}
data-aos-once="true"
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 && (
<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 */}
<img
src={post?.page_cover}
alt={post.title}
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>
</Link>
)}
{/* 文字描述 */}
<div >
{/* 描述 */}
<div className="px-4 flex flex-col w-full text-gray-700 dark:text-gray-300">

View File

@@ -21,9 +21,9 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
<div id="container" className='w-full'>
<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 => (
<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>
{showPagination && <PaginationSimple page={page} totalPage={totalPage} />}

View File

@@ -2,10 +2,10 @@ import BLOG from '@/blog.config'
import BlogPostCard from './BlogPostCard'
import BlogPostListEmpty from './BlogPostListEmpty'
import { useGlobal } from '@/lib/global'
import throttle from 'lodash.throttle'
import React from 'react'
import React, { useCallback } from 'react'
import CONFIG_MATERY from '../config_matery'
import { getListByPage } from '@/lib/utils'
import throttle from 'lodash.throttle'
/**
* 博客列表滚动分页
@@ -30,15 +30,16 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_MA
updatePage(page + 1)
}
// 监听滚动自动分页加载
const scrollTrigger = React.useCallback(throttle(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
}, 500))
const throttleMs = 200
const scrollTrigger = useCallback(throttle(() => {
requestAnimationFrame(() => {
const scrollS = window.scrollY + window.outerHeight
const clientHeight = targetRef ? (targetRef.current ? (targetRef.current.clientHeight) : 0) : 0
if (scrollS > clientHeight + 100) {
handleGetMore()
}
})
}, throttleMs))
// 监听滚动
React.useEffect(() => {
window.addEventListener('scroll', scrollTrigger)
@@ -56,9 +57,11 @@ const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG_MA
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 => (
<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>

View File

@@ -27,7 +27,7 @@ const Catalog = ({ toc }) => {
// 同步选中目录事件
const [activeSection, setActiveSection] = React.useState(null)
const throttleMs = 100
const throttleMs = 200
const actionSectionScrollSpy = React.useCallback(throttle(() => {
const sections = document.getElementsByClassName('notion-h')
let prevBBox = null

View File

@@ -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 CONFIG_MATERY from '../config_matery'
import throttle from 'lodash.throttle'
let wrapperTop = 0
let windowTop = 0
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)
return () => {
window.removeEventListener('scroll', scrollTrigger)
window.removeEventListener('resize', updateHeaderHeight)
if (enableAutoScroll) {
window.removeEventListener('scroll', scrollTrigger)
} window.removeEventListener('resize', updateHeaderHeight)
}
})
}, [])
const autoScrollEnd = () => {
if (autoScroll) {
@@ -44,10 +52,10 @@ const Header = props => {
}
/**
* 吸附滚动,移动端关闭
* @returns
*/
const scrollTrigger = () => {
* 吸附滚动,移动端关闭
* @returns
*/
const scrollTrigger = useCallback(throttle(() => {
if (screen.width <= 768) {
return
}
@@ -67,36 +75,42 @@ const Header = props => {
setTimeout(autoScrollEnd, 500)
}
windowTop = scrollS
}
}, throttleMs))
function updateHeaderHeight () {
setTimeout(() => {
function updateHeaderHeight() {
requestAnimationFrame(() => {
const wrapperElement = document.getElementById('wrapper')
wrapperTop = wrapperElement?.offsetTop
}, 500)
})
}
return (
<header
id="header"
className="duration-500 md:bg-fixed w-full bg-cover bg-center h-screen bg-black text-white relative z-10"
style={{
backgroundImage:
`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}")`
}}
>
<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
id="header"
className="md:bg-fixed w-full h-screen bg-black text-white relative"
>
<div className='w-full h-full absolute'>
{/* <Image src={siteInfo.pageCover} fill
style={{ objectFit: 'cover' }}
className='opacity-70'
placeholder='blur'
blurDataURL='/bg_image.jpg' /> */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={siteInfo.pageCover} className='h-full w-full object-cover opacity-70 ' />
</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>
)
}

View File

@@ -1,20 +1,26 @@
import Image from 'next/image'
export default function HeaderArticle({ post, siteInfo }) {
const headerImage = post?.page_cover ? post?.page_cover : siteInfo?.pageCover
const title = post?.title
return (
<div
data-aos="fade-down"
data-aos-duration="500"
data-aos-easing="ease-in-out"
data-aos-duration="300"
data-aos-once="false"
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">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
{/* <img
src={headerImage}
alt={title}
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>
</div>
)

View File

@@ -10,22 +10,24 @@ const Progress = ({ targetRef, showPercent = true }) => {
const currentRef = targetRef?.current || targetRef
const [percent, changePercent] = useState(0)
const scrollListener = () => {
const target = currentRef || (isBrowser() && document.getElementById('container'))
if (target) {
const clientHeight = target.clientHeight
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
if (per > 100) per = 100
if (per < 0) per = 0
changePercent(per)
}
requestAnimationFrame(() => {
const target = currentRef || (isBrowser() && document.getElementById('container'))
if (target) {
const clientHeight = target.clientHeight
const scrollY = window.pageYOffset
const fullHeight = clientHeight - window.outerHeight
let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
if (per > 100) per = 100
if (per < 0) per = 0
changePercent(per)
}
})
}
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [percent])
}, [])
return (
<div className="h-4 w-full shadow-2xl bg-gray-400 ">

View File

@@ -69,7 +69,7 @@ const SearchInput = props => {
ref={searchInputRef}
type="text"
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}
onCompositionStart={lockSearchInput}

View File

@@ -1,7 +1,6 @@
import { useGlobal } from '@/lib/global'
import throttle from 'lodash.throttle'
import Link from 'next/link'
import { useEffect, useRef, useState } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import CategoryGroup from './CategoryGroup'
import Logo from './Logo'
import SearchDrawer from './SearchDrawer'
@@ -9,6 +8,7 @@ import TagGroups from './TagGroups'
import MenuButtonGroupTop from './MenuButtonGroupTop'
import SideBarDrawer from '@/components/SideBarDrawer'
import SideBar from './SideBar'
import throttle from 'lodash.throttle'
let windowTop = 0
@@ -22,36 +22,38 @@ const TopNav = props => {
const { locale } = useGlobal()
const searchDrawer = useRef()
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(() => {
const scrollS = window.scrollY
const nav = document.querySelector('#sticky-nav')
const header = document.querySelector('#header')
const showNav = scrollS <= windowTop || scrollS < 5 // 非首页无大图时影藏顶部 滚动条置顶时隐藏
// 是否将导航栏透明
const navTransparent = header && scrollS < 300 // 透明导航条的条件
if (navTransparent) {
nav && nav.classList.replace('bg-indigo-700', 'bg-none')
nav && nav.classList.replace('text-black', 'text-white')
nav && nav.classList.replace('drop-shadow-xl', 'shadow-none')
nav && nav.classList.replace('dark:bg-hexo-black-gray', 'transparent')
} else {
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) {
nav && nav.classList.replace('bg-indigo-700', 'bg-none')
nav && nav.classList.replace('text-black', 'text-white')
nav && nav.classList.replace('drop-shadow-xl', 'shadow-none')
nav && nav.classList.replace('dark:bg-hexo-black-gray', 'transparent')
} else {
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 (!showNav) {
nav && nav.classList.replace('top-0', '-top-20')
windowTop = scrollS
} else {
nav && nav.classList.replace('-top-20', 'top-0')
windowTop = scrollS
}
navDarkMode()
}, 200)
if (!showNav) {
nav && nav.classList.replace('top-0', '-top-20')
windowTop = scrollS
} else {
nav && nav.classList.replace('-top-20', 'top-0')
windowTop = scrollS
}
navDarkMode()
})
}, throttleMs))
const navDarkMode = () => {
const nav = document.getElementById('sticky-nav')

View File

@@ -28,7 +28,7 @@ const LayoutBase = props => {
return (
<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} />
<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
data-aos="fade-up"
data-aos-duration="300"
data-aos-easing="ease-in-out"
data-aos-once="false"
data-aos-anchor-placement="top-center"
className='fixed xl:right-80 right-2 mr-10 bottom-24 hidden lg:block z-20'>

View File

@@ -26,7 +26,7 @@ export const ArticleLock = props => {
<div className='text-center space-y-3'>
<div className='font-bold'>{locale.COMMON.ARTICLE_LOCK_TIPS}</div>
<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" >
<i className={'duration-200 cursor-pointer fas fa-key'} >&nbsp;{locale.COMMON.SUBMIT}</i>
</div>

View File

@@ -14,8 +14,7 @@ const BlogPostCard = ({ post, showSummary }) => {
<div
key={post.id}
data-aos="fade-up"
data-aos-duration="600"
data-aos-easing="ease-in-out"
data-aos-duration="300"
data-aos-once="false"
data-aos-anchor-placement="top-bottom"
className="mb-6 max-w-7xl border-b dark:border-gray-800 "

View File

@@ -26,7 +26,7 @@ const Catalog = ({ toc }) => {
}
}, [])
const throttleMs = 100
const throttleMs = 200
const actionSectionScrollSpy = React.useCallback(throttle(() => {
const sections = document.getElementsByClassName('notion-h')
let prevBBox = null

View File

@@ -4,7 +4,7 @@ import { useRouter } from 'next/router'
import { useGlobal } from '@/lib/global'
import CONFIG_MEDIUM from '../config_medium'
function GroupMenu ({ customNav }) {
function GroupMenu ({ customMenu, customNav }) {
const { locale } = useGlobal()
const router = useRouter()
@@ -39,13 +39,13 @@ function GroupMenu ({ customNav }) {
{link.slot}
</Link>
);
)
} else {
return null
}
})}
</nav>
);
)
}
export default GroupMenu

View File

@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [percent])
}, [])
return (
<div className="h-4 w-full shadow-2xl bg-hexo-light-gray dark:bg-black">

View File

@@ -61,7 +61,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => {
<input
ref={searchInputRef}
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}
onCompositionStart={lockSearchInput}
onCompositionUpdate={lockSearchInput}

View File

@@ -74,7 +74,7 @@ export default function TopNavBar(props) {
{link.slot}
</Link>
);
)
} else {
return null
}
@@ -82,5 +82,5 @@ export default function TopNavBar(props) {
</div>
</div>
</div>
);
)
}

View File

@@ -10,7 +10,6 @@ import TopNav from './components/TopNav'
import { useGlobal } from '@/lib/global'
import PropTypes from 'prop-types'
import React from 'react'
import smoothscroll from 'smoothscroll-polyfill'
import CONFIG_NEXT from './config_next'
import Live2D from '@/components/Live2D'
import BLOG from '@/blog.config'
@@ -45,8 +44,6 @@ const LayoutBase = (props) => {
}
React.useEffect(() => {
smoothscroll.polyfill()
// facebook messenger 插件需要调整右下角悬浮按钮的高度
const fb = document.getElementsByClassName('fb-customerchat')
if (fb.length === 0) {
@@ -59,7 +56,7 @@ const LayoutBase = (props) => {
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
return (<>
return (<div id='theme-next'>
<CommonHead meta={meta} />
@@ -90,7 +87,7 @@ const LayoutBase = (props) => {
</div>
<Footer title={siteInfo?.title}/>
</>
</div>
)
}

View File

@@ -19,32 +19,23 @@ export const LayoutSlug = (props) => {
}} /></div>
: null
if (!post) {
return <LayoutBase
{...props}
rightAreaSlot={
CONFIG_NEXT.RIGHT_LATEST_POSTS && <Card><LatestPostsGroup posts={latestPosts} /></Card>
}
/>
}
const rightAreaSlog = CONFIG_NEXT.RIGHT_LATEST_POSTS && <Card><LatestPostsGroup latestPosts={latestPosts} /></Card>
return (
<LayoutBase
{...props}
floatSlot={floatSlot}
rightAreaSlot={
CONFIG_NEXT.RIGHT_LATEST_POSTS && <Card><LatestPostsGroup posts={latestPosts} /></Card>
}
rightAreaSlot={rightAreaSlog}
>
{!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} />
</div>
</div>}
</LayoutBase>
)

View File

@@ -28,9 +28,8 @@ export default function ArticleDetail(props) {
return (
<div id="container"
data-aos="fade-down"
data-aos-duration="600"
data-aos-easing="ease-in-out"
data-aos-once="false"
data-aos-duration="300"
data-aos-once="true"
data-aos-anchor-placement="top-bottom"
className="shadow md:hover:shadow-2xl overflow-x-auto flex-grow mx-auto w-screen md:w-full ">

View File

@@ -30,7 +30,7 @@ export const ArticleLock = props => {
<div className="flex mx-4">
<input
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>
<div
onClick={submitPassword}

View File

@@ -1,9 +1,8 @@
const Card = ({ children, headerSlot, className }) => {
return <div
data-aos="fade-down"
data-aos-duration="600"
data-aos-easing="ease-in-out"
data-aos-once="false"
data-aos-duration="300"
data-aos-once="true"
data-aos-anchor-placement="top-bottom"
className={className}>
<>{headerSlot}</>

View File

@@ -1,5 +1,4 @@
import React, { useEffect, useState } from 'react'
import smoothscroll from 'smoothscroll-polyfill'
import CONFIG_NEXT from '../config_next'
/**
@@ -11,12 +10,14 @@ import CONFIG_NEXT from '../config_next'
* @constructor
*/
const JumpToBottomButton = ({ showPercent = false }) => {
if (!CONFIG_NEXT.WIDGET_TO_BOTTOM) {
return <></>
}
const [show, switchShow] = useState(false)
const [percent, changePercent] = useState(0)
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
const scrollListener = () => {
const targetRef = document.getElementById('wrapper')
const clientHeight = targetRef?.clientHeight
@@ -36,12 +37,9 @@ const JumpToBottomButton = ({ showPercent = false }) => {
window.scrollTo({ top: targetRef.clientHeight, behavior: 'smooth' })
}
useEffect(() => {
smoothscroll.polyfill()
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [show])
if (!CONFIG_NEXT.WIDGET_TO_BOTTOM) {
return <></>
}
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' >

View File

@@ -11,10 +11,10 @@ import CONFIG_NEXT from '../config_next'
* @constructor
*/
const JumpToTopButton = ({ showPercent = true, percent }) => {
const { locale } = useGlobal()
if (!CONFIG_NEXT.WIDGET_TO_TOP) {
return <></>
}
const { locale } = useGlobal()
return (<div className='flex space-x-1 items-center transform hover:scale-105 duration-200 py-2 px-3' onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} >
<div className='dark:text-gray-200' title={locale.POST.TOP} >
<i className='fa-arrow-up fas' />

View File

@@ -18,8 +18,7 @@ const PaginationNumber = ({ page, totalPage }) => {
return (
<div
data-aos="fade-down"
data-aos-duration="600"
data-aos-easing="ease-in-out"
data-aos-duration="300"
data-aos-once="false"
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">
@@ -62,7 +61,7 @@ const PaginationNumber = ({ page, totalPage }) => {
</div>
</Link>
</div>
);
)
}
function getPageElement(pagePrefix, page, currentPage) {
@@ -81,7 +80,7 @@ function getPageElement(pagePrefix, page, currentPage) {
{page}
</Link>)
);
)
}
function generatePages(pagePrefix, page, currentPage, totalPage) {
const pages = []

View File

@@ -17,8 +17,7 @@ const PaginationSimple = ({ page, showNext }) => {
return (
<div
data-aos="fade-down"
data-aos-duration="600"
data-aos-easing="ease-in-out"
data-aos-duration="300"
data-aos-once="false"
data-aos-anchor-placement="top-bottom"
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>
</Link>
</div>
);
)
}
export default PaginationSimple

View File

@@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => {
useEffect(() => {
document.addEventListener('scroll', scrollListener)
return () => document.removeEventListener('scroll', scrollListener)
}, [percent])
}, [])
return (
<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