diff --git a/.env.local b/.env.local index 490200b3..5c68a5c9 100644 --- a/.env.local +++ b/.env.local @@ -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 diff --git a/README.md b/README.md index a985ae42..87a96423 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,12 @@ Pylogmon
派了个萌

🔧 🐛 - SkysCrystal
Simon Shi -

🔧 🐛 - + SkysCrystal
Simon Shi

🔧 🐛 + S.Y. Lee
S.Y. Lee

🔧 🐛 + + fighting-buf
fighting-buf

🔧 🐛 + diff --git a/components/Collapse.js b/components/Collapse.js index 798e2b86..a787bfa2 100644 --- a/components/Collapse.js +++ b/components/Collapse.js @@ -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 ( -
+
{props.children}
) diff --git a/components/CusdisComponent.js b/components/CusdisComponent.js index f1fce6ef..83b962b5 100644 --- a/components/CusdisComponent.js +++ b/components/CusdisComponent.js @@ -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 { htmlElement.classList?.add(newStatus ? 'dark' : 'light') } - return
+ return
diff --git a/components/DebugPanel.js b/components/DebugPanel.js index 1c5ebea6..01e3c579 100644 --- a/components/DebugPanel.js +++ b/components/DebugPanel.js @@ -69,7 +69,7 @@ export function DebugPanel() { {/* 调试侧拉抽屉 */}
diff --git a/components/ExternalScript.js b/components/ExternalScript.js index a168caab..66fe457c 100644 --- a/components/ExternalScript.js +++ b/components/ExternalScript.js @@ -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 } diff --git a/components/NotionPage.js b/components/NotionPage.js index 8328598e..abb8586e 100644 --- a/components/NotionPage.js +++ b/components/NotionPage.js @@ -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 +} + const NotionPage = ({ post, className }) => { const zoom = isBrowser() && mediumZoom({ container: '.notion-viewport', @@ -79,7 +84,7 @@ const NotionPage = ({ post, className }) => { return <>{post?.summary || ''} } - return
+ return
{ Collection, Equation, Modal, - Pdf + Pdf, + Tweet }} /> diff --git a/components/StarrySky.js b/components/StarrySky.js index 3a3a77d6..089a95b9 100644 --- a/components/StarrySky.js +++ b/components/StarrySky.js @@ -7,7 +7,7 @@ export const StarrySky = () => { }, []) return (
- +
) } diff --git a/components/Tabs.js b/components/Tabs.js index 36305069..b354e533 100644 --- a/components/Tabs.js +++ b/components/Tabs.js @@ -51,8 +51,7 @@ const Tabs = ({ className, children }) => { {children.map((item, index) => { return
{currentTab === index && item} diff --git a/lib/notion/getNotionData.js b/lib/notion/getNotionData.js index 93a22d29..44d97f2f 100644 --- a/lib/notion/getNotionData.js +++ b/lib/notion/getNotionData.js @@ -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 diff --git a/lib/notion/getPageProperties.js b/lib/notion/getPageProperties.js index eb3428d0..0ba6a3bd 100644 --- a/lib/notion/getPageProperties.js +++ b/lib/notion/getPageProperties.js @@ -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)}` +} diff --git a/lib/notion/mapImage.js b/lib/notion/mapImage.js index 6d070dab..3d91cd4d 100644 --- a/lib/notion/mapImage.js +++ b/lib/notion/mapImage.js @@ -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 } diff --git a/lib/robots.txt.js b/lib/robots.txt.js new file mode 100644 index 00000000..4405164f --- /dev/null +++ b/lib/robots.txt.js @@ -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等其他平台这行代码会成功执行 + } +} diff --git a/lib/rss.js b/lib/rss.js index 68214659..02121d49 100644 --- a/lib/rss.js +++ b/lib/rss.js @@ -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() const regexExp = - /
.*?<\/svg>
.*?<\/div><\/div>
.*?<\/div><\/div><\/div><\/div>/g + /
.*?<\/svg>
.*?<\/div><\/div>
.*?<\/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被高频词访问将大量消耗服务端资源,故作为静态文件 + } } diff --git a/lib/sitemap.xml.js b/lib/sitemap.xml.js new file mode 100644 index 00000000..7522951d --- /dev/null +++ b/lib/sitemap.xml.js @@ -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 += ` + ${u.loc} + ${u.lastmod} + ${u.changefreq} + + ` + }) + + return ` + + ${urlsXml} + + ` +} diff --git a/lib/theme.js b/lib/theme.js index 61314289..a8e24505 100644 --- a/lib/theme.js +++ b/lib/theme.js @@ -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 diff --git a/lib/utils.js b/lib/utils.js index 0b997696..82bfad5c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -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 +} diff --git a/next.config.js b/next.config.js index 33b66908..65fde6e7 100644 --- a/next.config.js +++ b/next.config.js @@ -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 diff --git a/package.json b/package.json index d3d82615..2412a5c9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pages/[...slug].js b/pages/[...slug].js index b7c41a6d..e14b98a2 100644 --- a/pages/[...slug].js +++ b/pages/[...slug].js @@ -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 } @@ -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 } } diff --git a/pages/_app.js b/pages/_app.js index 980f1638..e6749281 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -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 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 }) => { - if (isBrowser()) { + useEffect(() => { AOS.init() - } + if (isMobile()) { + smoothscroll.polyfill() + } + }, []) return ( - {externalPlugins} + {externalPlugins} ) } diff --git a/pages/index.js b/pages/index.js index a26d57bd..542500fb 100644 --- a/pages/index.js +++ b/pages/index.js @@ -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: { diff --git a/pages/search/[keyword]/index.js b/pages/search/[keyword]/index.js index f5616d5c..b1ea5bc4 100644 --- a/pages/search/[keyword]/index.js +++ b/pages/search/[keyword]/index.js @@ -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 diff --git a/pages/sitemap.xml.js b/pages/sitemap.xml.js index 17980067..8f753f5d 100644 --- a/pages/sitemap.xml.js +++ b/pages/sitemap.xml.js @@ -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) } diff --git a/public/avatar.svg b/public/avatar.svg new file mode 100644 index 00000000..31a1446c --- /dev/null +++ b/public/avatar.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/bg_image.jpg b/public/bg_image.jpg old mode 100644 new mode 100755 index d602ff36..191b9554 Binary files a/public/bg_image.jpg and b/public/bg_image.jpg differ diff --git a/public/css/img-shadow.css b/public/css/img-shadow.css new file mode 100644 index 00000000..ea7bfdff --- /dev/null +++ b/public/css/img-shadow.css @@ -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; +} diff --git a/public/css/theme-fukasawa.css b/public/css/theme-fukasawa.css new file mode 100644 index 00000000..5a62c5d9 --- /dev/null +++ b/public/css/theme-fukasawa.css @@ -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; + } + } \ No newline at end of file diff --git a/public/css/theme-simple.css b/public/css/theme-simple.css new file mode 100644 index 00000000..43c514c1 --- /dev/null +++ b/public/css/theme-simple.css @@ -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; +} + diff --git a/styles/globals.css b/styles/globals.css index de157173..569fbfc6 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -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 *{ diff --git a/styles/notion.css b/styles/notion.css index 2476a6ac..ee915f41 100644 --- a/styles/notion.css +++ b/styles/notion.css @@ -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; } \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 6da86610..76f8e710 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -21,9 +21,10 @@ module.exports = { } }, maxWidth: { - side: '14rem' + side: '14rem', + '9/10': '90%' } - }, + } }, variants: { extend: {} diff --git a/themes/example/LayoutBase.js b/themes/example/LayoutBase.js index 83193866..90ec3ff9 100644 --- a/themes/example/LayoutBase.js +++ b/themes/example/LayoutBase.js @@ -16,7 +16,7 @@ import BLOG from '@/blog.config' const LayoutBase = props => { const { children, meta } = props return ( -
+
{/* 顶栏LOGO */}
diff --git a/themes/example/components/ArticleLock.js b/themes/example/components/ArticleLock.js index 6e90f7b2..f21ce74a 100644 --- a/themes/example/components/ArticleLock.js +++ b/themes/example/components/ArticleLock.js @@ -26,7 +26,7 @@ export const ArticleLock = props => {
{locale.COMMON.ARTICLE_LOCK_TIPS}
- +
 {locale.COMMON.SUBMIT}
diff --git a/themes/example/components/BlogListScroll.js b/themes/example/components/BlogListScroll.js index d70e4bc5..b3d7f3f0 100644 --- a/themes/example/components/BlogListScroll.js +++ b/themes/example/components/BlogListScroll.js @@ -78,5 +78,5 @@ export const BlogListScroll = props => {
- ); + ) } diff --git a/themes/example/components/Header.js b/themes/example/components/Header.js index 7651018b..0cd85268 100644 --- a/themes/example/components/Header.js +++ b/themes/example/components/Header.js @@ -21,5 +21,5 @@ export const Header = (props) => {
- ); + ) } diff --git a/themes/example/components/Nav.js b/themes/example/components/Nav.js index 701e902c..9e05646b 100644 --- a/themes/example/components/Nav.js +++ b/themes/example/components/Nav.js @@ -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) { diff --git a/themes/example/components/SearchInput.js b/themes/example/components/SearchInput.js index 6a95ba01..fdb129b9 100644 --- a/themes/example/components/SearchInput.js +++ b/themes/example/components/SearchInput.js @@ -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} diff --git a/themes/example/config_empty.js b/themes/example/config_example.js similarity index 75% rename from themes/example/config_empty.js rename to themes/example/config_example.js index 1c349a89..c77b1d2d 100644 --- a/themes/example/config_empty.js +++ b/themes/example/config_example.js @@ -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 diff --git a/themes/example/index.js b/themes/example/index.js index 95b0a6b2..dd762bd4 100644 --- a/themes/example/index.js +++ b/themes/example/index.js @@ -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, diff --git a/themes/fukasawa/LayoutBase.js b/themes/fukasawa/LayoutBase.js index d06e92d1..6cb6de67 100644 --- a/themes/fukasawa/LayoutBase.js +++ b/themes/fukasawa/LayoutBase.js @@ -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 = - return (<> + + if (isBrowser()) { + loadExternalResource('/css/theme-fukasawa.css', 'css') + } + + return (
@@ -38,7 +44,7 @@ const LayoutBase = (props) => {
- ) +
) } export default LayoutBase diff --git a/themes/fukasawa/components/ArticleLock.js b/themes/fukasawa/components/ArticleLock.js index 1dedba16..9a8e6081 100644 --- a/themes/fukasawa/components/ArticleLock.js +++ b/themes/fukasawa/components/ArticleLock.js @@ -30,7 +30,7 @@ export const ArticleLock = props => {
{ +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 ( - -
-
- +
+
+
+
+ - {post.title} + {post.title} - + - {(!showPreview || showSummary) && ( -

- {post.summary} -

- )} -
- - {showPageCover && ( - -
- {/* eslint-disable-next-line @next/next/no-img-element */} - {post.title} - {/* {post.title} */} + {(!showPreview || showSummary) && ( +

+ {post.summary} +

+ )}
- - )} -
- + + {showPageCover && ( + +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {post.title} + {/* {post.title} */} +
+ + )} +
+
+
+ ) } diff --git a/themes/fukasawa/components/BlogListPage.js b/themes/fukasawa/components/BlogListPage.js index 2f0af0df..c919ef94 100644 --- a/themes/fukasawa/components/BlogListPage.js +++ b/themes/fukasawa/components/BlogListPage.js @@ -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 @@ -41,10 +21,10 @@ const BlogListPage = ({ page = 1, posts = [], postCount, siteInfo }) => { return (
{/* 文章列表 */} -
+
{posts?.map(post => ( -
- +
+
))}
diff --git a/themes/fukasawa/components/BlogListScroll.js b/themes/fukasawa/components/BlogListScroll.js index 003f789d..fad59cec 100644 --- a/themes/fukasawa/components/BlogListScroll.js +++ b/themes/fukasawa/components/BlogListScroll.js @@ -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 } else { return ( -
- {/* 文章列表 */} -
- {postsToShow?.map(post => ( -
- -
- ))} -
+
+ {/* 文章列表 */} + {postsToShow?.map(post => ( +
+ +
+ ))} -
- {' '} - {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} -
-
+
+ {' '} + {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE} 😰`}{' '} +
+
) } } diff --git a/themes/fukasawa/components/Card.js b/themes/fukasawa/components/Card.js index 2cd01cbe..e88dee68 100644 --- a/themes/fukasawa/components/Card.js +++ b/themes/fukasawa/components/Card.js @@ -1,11 +1,5 @@ const Card = ({ children, headerSlot, className }) => { - return
+ return
<>{headerSlot}
{children} diff --git a/themes/fukasawa/components/Catalog.js b/themes/fukasawa/components/Catalog.js index 500fa909..446a3e7b 100644 --- a/themes/fukasawa/components/Catalog.js +++ b/themes/fukasawa/components/Catalog.js @@ -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 diff --git a/themes/fukasawa/components/SearchInput.js b/themes/fukasawa/components/SearchInput.js index 2d54b6bc..6290415c 100644 --- a/themes/fukasawa/components/SearchInput.js +++ b/themes/fukasawa/components/SearchInput.js @@ -53,7 +53,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef }) => { { - 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 ( -
+
{headerSlot} -
+
{
{/* 右下角悬浮 */} -
-
+
+
{floatSlot} diff --git a/themes/hexo/LayoutIndex.js b/themes/hexo/LayoutIndex.js index 1361c1e6..48cc29e4 100644 --- a/themes/hexo/LayoutIndex.js +++ b/themes/hexo/LayoutIndex.js @@ -7,8 +7,8 @@ import LayoutBase from './LayoutBase' import React from 'react' export const LayoutIndex = (props) => { - return }> - + const headerSlot = CONFIG_HEXO.HOME_BANNER_ENABLE &&
+ return {BLOG.POST_LIST_STYLE === 'page' ? : } } diff --git a/themes/hexo/LayoutSlug.js b/themes/hexo/LayoutSlug.js index 12799877..73e4ca75 100644 --- a/themes/hexo/LayoutSlug.js +++ b/themes/hexo/LayoutSlug.js @@ -46,7 +46,7 @@ export const LayoutSlug = props => { showTag={false} floatSlot={floatSlot} > -
+
{lock && } {!lock &&
diff --git a/themes/hexo/components/Announcement.js b/themes/hexo/components/Announcement.js index 96a7bd34..677cb467 100644 --- a/themes/hexo/components/Announcement.js +++ b/themes/hexo/components/Announcement.js @@ -6,14 +6,8 @@ const NotionPage = dynamic(() => import('@/components/NotionPage')) const Announcement = ({ post, className }) => { const { locale } = useGlobal() if (post?.blockMap) { - return
-
+ return
+
{locale.COMMON.ANNOUNCEMENT}
{post && (
diff --git a/themes/hexo/components/ArticleLock.js b/themes/hexo/components/ArticleLock.js index 24f4b55f..0a56f39e 100644 --- a/themes/hexo/components/ArticleLock.js +++ b/themes/hexo/components/ArticleLock.js @@ -25,7 +25,7 @@ export const ArticleLock = props => {
{locale.COMMON.ARTICLE_LOCK_TIPS}
- +
 {locale.COMMON.SUBMIT}
diff --git a/themes/hexo/components/BlogPostCard.js b/themes/hexo/components/BlogPostCard.js index 508ad814..52cfa885 100644 --- a/themes/hexo/components/BlogPostCard.js +++ b/themes/hexo/components/BlogPostCard.js @@ -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 (
+ 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`}> -
- - - {post.title} - - - -
- - - - {post.date?.start_date || post.lastEditedTime} - - -
- - {(!showPreview || showSummary) && !post.results && ( -

- {post.summary} -

- )} - - {/* 搜索结果 */} - {post.results && ( -

- {post.results.map(r => ( - {r} - ))} -

- )} - - {showPreview && ( -
- -
- )} - -
- - - - {post.category} - - -
-
- {' '} - {post.tagItems.map(tag => ( - - ))} -
-
-
-
+ {/* 文字内容 */} + + {/* 图片封面 */} {showPageCover && !showPreview && post?.page_cover && ( -
+
{/* eslint-disable-next-line @next/next/no-img-element */} - {post.title} - {/* {post.title} */} + loading='lazy' + className="w-full relative cursor-pointer object-cover duration-200 hover:scale-125 " + /> */} +
+ + {/*
+ {post.title} +
*/}
)} diff --git a/themes/hexo/components/BlogPostCardInfo.js b/themes/hexo/components/BlogPostCardInfo.js new file mode 100644 index 00000000..e55394c1 --- /dev/null +++ b/themes/hexo/components/BlogPostCardInfo.js @@ -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
+ +
+ {/* 标题 */} + + + {post.title} + + + + {/* 日期 */} +
+ + + + {post.date?.start_date || post.lastEditedTime} + + +
+ + {/* 摘要 */} + {(!showPreview || showSummary) && !post.results && ( +

+ {post.summary} +

+ )} + + {/* 搜索结果 */} + {post.results && ( +

+ {post.results.map(r => ( + {r} + ))} +

+ )} + {/* 预览 */} + {showPreview && ( +
+ +
+ )} + +
+ +
+ {/* 分类标签 */} +
+ + + + {post.category} + + +
+
+ {' '} + {post.tagItems.map(tag => ( + + ))} +
+
+
+
+
+} diff --git a/themes/hexo/components/BlogPostListPage.js b/themes/hexo/components/BlogPostListPage.js index ba4089c6..9a2c68f7 100644 --- a/themes/hexo/components/BlogPostListPage.js +++ b/themes/hexo/components/BlogPostListPage.js @@ -22,7 +22,7 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => { {/* 文章列表 */}
{posts.map(post => ( - + ))}
{showPagination && } diff --git a/themes/hexo/components/BlogPostListScroll.js b/themes/hexo/components/BlogPostListScroll.js index a65eb4ee..2123700d 100644 --- a/themes/hexo/components/BlogPostListScroll.js +++ b/themes/hexo/components/BlogPostListScroll.js @@ -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 {/* 文章列表 */}
{postsToShow.map(post => ( - + ))}
diff --git a/themes/hexo/components/Card.js b/themes/hexo/components/Card.js index 26c3093a..285554ca 100644 --- a/themes/hexo/components/Card.js +++ b/themes/hexo/components/Card.js @@ -1,7 +1,7 @@ const Card = ({ children, headerSlot, className }) => { return
<>{headerSlot} -
+
{children}
diff --git a/themes/hexo/components/Catalog.js b/themes/hexo/components/Catalog.js index 3252f2ec..1e7d5b40 100644 --- a/themes/hexo/components/Catalog.js +++ b/themes/hexo/components/Catalog.js @@ -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 diff --git a/themes/hexo/components/Header.js b/themes/hexo/components/Header.js index fa2c5e39..87e83e10 100644 --- a/themes/hexo/components/Header.js +++ b/themes/hexo/components/Header.js @@ -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 ( - ) } -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 diff --git a/themes/hexo/components/HeaderArticle.js b/themes/hexo/components/HeaderArticle.js index fef4ec74..df1593a6 100644 --- a/themes/hexo/components/HeaderArticle.js +++ b/themes/hexo/components/HeaderArticle.js @@ -19,7 +19,7 @@ export default function HeaderArticle({ post, siteInfo }) { return (
- ); + ) } diff --git a/themes/hexo/components/JumpToTopButton.js b/themes/hexo/components/JumpToTopButton.js index 427ef500..3602ad99 100644 --- a/themes/hexo/components/JumpToTopButton.js +++ b/themes/hexo/components/JumpToTopButton.js @@ -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 (
window.scrollTo({ top: 0, behavior: 'smooth' })} >
{showPercent && (
{percent}
)} diff --git a/themes/hexo/components/LatestPostsGroup.js b/themes/hexo/components/LatestPostsGroup.js index 9e799aa9..e5d02a71 100644 --- a/themes/hexo/components/LatestPostsGroup.js +++ b/themes/hexo/components/LatestPostsGroup.js @@ -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 <> -
-
- - {locale.COMMON.LATEST_POSTS} -
-
- {latestPosts.map(post => { - const selected = currentPath === `${BLOG.SUB_PATH}/${post.slug}` - const headerImage = post?.page_cover - ? `url("${post.page_cover}")` - : `url("${siteInfo?.pageCover}")` - - return ( - ( - -
-
+
-
{post.title}
-
{post.lastEditedTime}
+ + {locale.COMMON.LATEST_POSTS}
-
+
+ {latestPosts.map(post => { + const selected = currentPath === `${BLOG.SUB_PATH}/${post.slug}` - ) - ) - })} - + const headerImage = post?.page_cover ? post.page_cover : siteInfo?.pageCover + + return ( + ( + +
+ {/* {post.title} */} + {/* eslint-disable-next-line @next/next/no-img-element */} + +
+
+
+
{post.title}
+
{post.lastEditedTime}
+
+
+ + ) + ) + })} + } export default LatestPostsGroup diff --git a/themes/hexo/components/MenuButtonGroupTop.js b/themes/hexo/components/MenuButtonGroupTop.js index f6ae7a29..866898e8 100644 --- a/themes/hexo/components/MenuButtonGroupTop.js +++ b/themes/hexo/components/MenuButtonGroupTop.js @@ -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) => {
- ); + ) } else { return null } })} - ); + ) } export default MenuButtonGroupTop diff --git a/themes/hexo/components/Progress.js b/themes/hexo/components/Progress.js index eeb40fd2..c54f422b 100644 --- a/themes/hexo/components/Progress.js +++ b/themes/hexo/components/Progress.js @@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => { useEffect(() => { document.addEventListener('scroll', scrollListener) return () => document.removeEventListener('scroll', scrollListener) - }, [percent]) + }, []) return (
diff --git a/themes/hexo/components/SearchInput.js b/themes/hexo/components/SearchInput.js index fefca73a..462c58b3 100644 --- a/themes/hexo/components/SearchInput.js +++ b/themes/hexo/components/SearchInput.js @@ -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} diff --git a/themes/hexo/components/TopNav.js b/themes/hexo/components/TopNav.js index 3aa310ee..43aa5340 100644 --- a/themes/hexo/components/TopNav.js +++ b/themes/hexo/components/TopNav.js @@ -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 && ( -
-
-
{locale.COMMON.CATEGORY}
- + {categories && ( +
+
+
{locale.COMMON.CATEGORY}
+ - {locale.COMMON.MORE} + {locale.COMMON.MORE} - -
- -
- ) } + +
+ +
+ )} - { tags && ( -
-
-
{locale.COMMON.TAGS}
- + {tags && ( +
+
+
{locale.COMMON.TAGS}
+ - {locale.COMMON.MORE} + {locale.COMMON.MORE} - -
-
- -
-
- ) } + +
+
+ +
+
+ )} return (
- + - {/* 导航栏 */} - ) +
) } export default TopNav diff --git a/themes/hexo/config_hexo.js b/themes/hexo/config_hexo.js index feb6dbd8..73febab2 100644 --- a/themes/hexo/config_hexo.js +++ b/themes/hexo/config_hexo.js @@ -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, // 显示文章版权声明 diff --git a/themes/index.js b/themes/index.js index 386a45dc..7f87efb1 100644 --- a/themes/index.js +++ b/themes/index.js @@ -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 +} diff --git a/themes/matery/LayoutBase.js b/themes/matery/LayoutBase.js index c529c788..e5acdb63 100644 --- a/themes/matery/LayoutBase.js +++ b/themes/matery/LayoutBase.js @@ -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 ( -
+
@@ -67,12 +61,8 @@ const LayoutBase = props => {
{/* 右下角悬浮 */} -
-
+ diff --git a/themes/matery/LayoutSlug.js b/themes/matery/LayoutSlug.js index 02fc3060..12fefce4 100644 --- a/themes/matery/LayoutSlug.js +++ b/themes/matery/LayoutSlug.js @@ -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 { > } - return ( - } - {...props} - showCategory={false} - showTag={false} - > + return (} + {...props} + showCategory={false} + showTag={false} + > -
-
-
- {lock && } +
+
+
+ {lock && } - {!lock &&
- {post?.type === 'Post' && <> -
- -
-
- } - -
-
- {/* Notion文章主体 */} -
- {post && } -
- -
- {/* 文章内嵌广告 */} - -
- - {post.type === 'Post' && } - -
- -
- - {/* 评论互动 */} -
- -
+ {!lock &&
+ {post?.type && post?.type === 'Post' && <> +
+
+
+ } -
} -
- {post.type === 'Post' && } +
+
+ {/* Notion文章主体 */} +
+ {post && } +
- {post?.toc?.length > 0 &&
-
-
-
- -
+
+ {/* 文章内嵌广告 */} + +
+ + {/* 文章版权说明 */} + {post.type === 'Post' && } + +
+ +
+ + {/* 评论互动 */} +
+
+
} -
-
- -
+ {/* 文章推荐 */} + {post.type === 'Post' && } + + {/* 文章目录 */} + {post?.toc?.length > 0 &&
+
+
+
+ +
+
+
+
}
- +
+ +
+ +
+ + ) } diff --git a/themes/matery/components/ArticleLock.js b/themes/matery/components/ArticleLock.js index 24f4b55f..0a56f39e 100644 --- a/themes/matery/components/ArticleLock.js +++ b/themes/matery/components/ArticleLock.js @@ -25,7 +25,7 @@ export const ArticleLock = props => {
{locale.COMMON.ARTICLE_LOCK_TIPS}
- +
 {locale.COMMON.SUBMIT}
diff --git a/themes/matery/components/BlogPostCard.js b/themes/matery/components/BlogPostCard.js index ff252dd8..b3941fdc 100644 --- a/themes/matery/components/BlogPostCard.js +++ b/themes/matery/components/BlogPostCard.js @@ -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 (
+ className="w-full mb-4 overflow-auto shadow-md border dark:border-black rounded-xl bg-white dark:bg-hexo-black-gray"> {/* 固定高度 ,空白用图片拉升填充 */} -
+
{/* 头部图片 填充卡片 */} {showPageCover && ( -
+
{/* eslint-disable-next-line @next/next/no-img-element */} {post.title} - {post.title} + {post.title}
)} + {/* 文字描述 */}
{/* 描述 */}
diff --git a/themes/matery/components/BlogPostListPage.js b/themes/matery/components/BlogPostListPage.js index 2a458aea..4a041aa7 100644 --- a/themes/matery/components/BlogPostListPage.js +++ b/themes/matery/components/BlogPostListPage.js @@ -21,9 +21,9 @@ const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => {
{/* 文章列表 */} -
+
{posts.map(post => ( - +
))}
{showPagination && } diff --git a/themes/matery/components/BlogPostListScroll.js b/themes/matery/components/BlogPostListScroll.js index 105d32db..fb4da657 100644 --- a/themes/matery/components/BlogPostListScroll.js +++ b/themes/matery/components/BlogPostListScroll.js @@ -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
{/* 文章列表 */} -
+
{postsToShow.map(post => ( - +
+ +
))}
diff --git a/themes/matery/components/Catalog.js b/themes/matery/components/Catalog.js index a86326e3..56bff03e 100644 --- a/themes/matery/components/Catalog.js +++ b/themes/matery/components/Catalog.js @@ -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 diff --git a/themes/matery/components/Header.js b/themes/matery/components/Header.js index 4f954bdd..454e5249 100644 --- a/themes/matery/components/Header.js +++ b/themes/matery/components/Header.js @@ -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 ( - ) } diff --git a/themes/matery/components/HeaderArticle.js b/themes/matery/components/HeaderArticle.js index e48160cb..62317d06 100644 --- a/themes/matery/components/HeaderArticle.js +++ b/themes/matery/components/HeaderArticle.js @@ -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 ( ) diff --git a/themes/matery/components/Progress.js b/themes/matery/components/Progress.js index eeb40fd2..985ffac4 100644 --- a/themes/matery/components/Progress.js +++ b/themes/matery/components/Progress.js @@ -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 (
diff --git a/themes/matery/components/SearchInput.js b/themes/matery/components/SearchInput.js index fefca73a..462c58b3 100644 --- a/themes/matery/components/SearchInput.js +++ b/themes/matery/components/SearchInput.js @@ -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} diff --git a/themes/matery/components/TopNav.js b/themes/matery/components/TopNav.js index 44d524cb..1aa58db6 100644 --- a/themes/matery/components/TopNav.js +++ b/themes/matery/components/TopNav.js @@ -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') diff --git a/themes/medium/LayoutBase.js b/themes/medium/LayoutBase.js index 83ee9807..e4429ef8 100644 --- a/themes/medium/LayoutBase.js +++ b/themes/medium/LayoutBase.js @@ -28,7 +28,7 @@ const LayoutBase = props => { return ( -
+
@@ -46,7 +46,6 @@ const LayoutBase = props => {
diff --git a/themes/medium/components/ArticleLock.js b/themes/medium/components/ArticleLock.js index af7b4ec1..8ffe4044 100644 --- a/themes/medium/components/ArticleLock.js +++ b/themes/medium/components/ArticleLock.js @@ -26,7 +26,7 @@ export const ArticleLock = props => {
{locale.COMMON.ARTICLE_LOCK_TIPS}
- +
 {locale.COMMON.SUBMIT}
diff --git a/themes/medium/components/BlogPostCard.js b/themes/medium/components/BlogPostCard.js index 2f9155ec..6a031b51 100644 --- a/themes/medium/components/BlogPostCard.js +++ b/themes/medium/components/BlogPostCard.js @@ -14,8 +14,7 @@ const BlogPostCard = ({ post, showSummary }) => {
{ } }, []) - const throttleMs = 100 + const throttleMs = 200 const actionSectionScrollSpy = React.useCallback(throttle(() => { const sections = document.getElementsByClassName('notion-h') let prevBBox = null diff --git a/themes/medium/components/GroupMenu.js b/themes/medium/components/GroupMenu.js index ee1ddb43..a64cf4e7 100644 --- a/themes/medium/components/GroupMenu.js +++ b/themes/medium/components/GroupMenu.js @@ -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} - ); + ) } else { return null } })} - ); + ) } export default GroupMenu diff --git a/themes/medium/components/Progress.js b/themes/medium/components/Progress.js index 77be3c3d..48ba8e14 100644 --- a/themes/medium/components/Progress.js +++ b/themes/medium/components/Progress.js @@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => { useEffect(() => { document.addEventListener('scroll', scrollListener) return () => document.removeEventListener('scroll', scrollListener) - }, [percent]) + }, []) return (
diff --git a/themes/medium/components/SearchInput.js b/themes/medium/components/SearchInput.js index 5b037540..f6c84d9c 100644 --- a/themes/medium/components/SearchInput.js +++ b/themes/medium/components/SearchInput.js @@ -61,7 +61,7 @@ const SearchInput = ({ currentTag, currentSearch, cRef, className }) => { - ); + ) } else { return null } @@ -82,5 +82,5 @@ export default function TopNavBar(props) {
- ); + ) } diff --git a/themes/next/LayoutBase.js b/themes/next/LayoutBase.js index 647dbcc8..37e7c21f 100644 --- a/themes/next/LayoutBase.js +++ b/themes/next/LayoutBase.js @@ -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 (
@@ -90,7 +87,7 @@ const LayoutBase = (props) => {
- +
) } diff --git a/themes/next/LayoutSlug.js b/themes/next/LayoutSlug.js index fa1915a1..0087bf93 100644 --- a/themes/next/LayoutSlug.js +++ b/themes/next/LayoutSlug.js @@ -19,32 +19,23 @@ export const LayoutSlug = (props) => { }} />
: null - if (!post) { - return - } - /> - } + const rightAreaSlog = CONFIG_NEXT.RIGHT_LATEST_POSTS && return ( - } + rightAreaSlot={rightAreaSlog} > - {!lock && } + {post && !lock && } - {lock && } + {post && lock && } {/* 悬浮目录按钮 */} -
+ {post &&
-
+
}
) diff --git a/themes/next/components/ArticleDetail.js b/themes/next/components/ArticleDetail.js index 65419194..44362e82 100644 --- a/themes/next/components/ArticleDetail.js +++ b/themes/next/components/ArticleDetail.js @@ -28,9 +28,8 @@ export default function ArticleDetail(props) { return (
diff --git a/themes/next/components/ArticleLock.js b/themes/next/components/ArticleLock.js index 1f1c6b6b..6d2ec730 100644 --- a/themes/next/components/ArticleLock.js +++ b/themes/next/components/ArticleLock.js @@ -30,7 +30,7 @@ export const ArticleLock = props => {
{ return
<>{headerSlot} diff --git a/themes/next/components/JumpToBottomButton.js b/themes/next/components/JumpToBottomButton.js index a629c78d..c54b7b59 100644 --- a/themes/next/components/JumpToBottomButton.js +++ b/themes/next/components/JumpToBottomButton.js @@ -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 (
diff --git a/themes/next/components/JumpToTopButton.js b/themes/next/components/JumpToTopButton.js index ce7685a1..9ff51635 100644 --- a/themes/next/components/JumpToTopButton.js +++ b/themes/next/components/JumpToTopButton.js @@ -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 (
window.scrollTo({ top: 0, behavior: 'smooth' })} >
diff --git a/themes/next/components/PaginationNumber.js b/themes/next/components/PaginationNumber.js index c805b8a8..577eb458 100644 --- a/themes/next/components/PaginationNumber.js +++ b/themes/next/components/PaginationNumber.js @@ -18,8 +18,7 @@ const PaginationNumber = ({ page, totalPage }) => { return (
@@ -62,7 +61,7 @@ const PaginationNumber = ({ page, totalPage }) => {
- ); + ) } function getPageElement(pagePrefix, page, currentPage) { @@ -81,7 +80,7 @@ function getPageElement(pagePrefix, page, currentPage) { {page} ) - ); + ) } function generatePages(pagePrefix, page, currentPage, totalPage) { const pages = [] diff --git a/themes/next/components/PaginationSimple.js b/themes/next/components/PaginationSimple.js index 68b0b5cb..3a1b0101 100644 --- a/themes/next/components/PaginationSimple.js +++ b/themes/next/components/PaginationSimple.js @@ -17,8 +17,7 @@ const PaginationSimple = ({ page, showNext }) => { return (
@@ -58,7 +57,7 @@ const PaginationSimple = ({ page, showNext }) => {
- ); + ) } export default PaginationSimple diff --git a/themes/next/components/Progress.js b/themes/next/components/Progress.js index 91dfecc5..ab59fafd 100644 --- a/themes/next/components/Progress.js +++ b/themes/next/components/Progress.js @@ -25,7 +25,7 @@ const Progress = ({ targetRef, showPercent = true }) => { useEffect(() => { document.addEventListener('scroll', scrollListener) return () => document.removeEventListener('scroll', scrollListener) - }, [percent]) + }, []) return (
diff --git a/themes/next/components/SearchInput.js b/themes/next/components/SearchInput.js index 9ba7d04b..0f1e667a 100644 --- a/themes/next/components/SearchInput.js +++ b/themes/next/components/SearchInput.js @@ -66,7 +66,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-800 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-800 dark:text-white'} onKeyUp={handleKeyUp} onCompositionStart={lockSearchInput} onCompositionUpdate={lockSearchInput} diff --git a/themes/next/components/ShareBar.js b/themes/next/components/ShareBar.js index 2f174b13..c595da13 100644 --- a/themes/next/components/ShareBar.js +++ b/themes/next/components/ShareBar.js @@ -8,17 +8,17 @@ import { useGlobal } from '@/lib/global' import CONFIG_NEXT from '../config_next' const ShareBar = ({ post }) => { + const router = useRouter() + const [qrCodeShow, setQrCodeShow] = React.useState(false) + const { locale } = useGlobal() if (!CONFIG_NEXT.ARTICLE_SHARE) { return <> } - const router = useRouter() const shareUrl = BLOG.LINK + router.asPath // 二维码悬浮 - const [qrCodeShow, setQrCodeShow] = React.useState(false) const btnRef = React.createRef() const popoverRef = React.createRef() - const { locale } = useGlobal() const openPopover = () => { createPopper(btnRef.current, popoverRef.current, { @@ -57,7 +57,7 @@ const ShareBar = ({ post }) => {
-
+
diff --git a/themes/next/components/SideAreaLeft.js b/themes/next/components/SideAreaLeft.js index a2f8b67f..0cba620e 100644 --- a/themes/next/components/SideAreaLeft.js +++ b/themes/next/components/SideAreaLeft.js @@ -27,8 +27,7 @@ const SideAreaLeft = props => {
diff --git a/themes/next/components/SideAreaRight.js b/themes/next/components/SideAreaRight.js index 26372663..915316b2 100644 --- a/themes/next/components/SideAreaRight.js +++ b/themes/next/components/SideAreaRight.js @@ -22,7 +22,7 @@ const NextRecentComments = dynamic(() => import('./NextRecentComments')) * @constructor */ const SideAreaRight = (props) => { - const { tags, currentTag, slot, categories, currentCategory, notice } = props + const { tagOptions, currentTag, slot, categoryOptions, currentCategory, notice } = props const { locale } = useGlobal() const router = useRouter() return (