From 74f22db892adfddd72be4305bd76fbc8501c6954 Mon Sep 17 00:00:00 2001 From: Sebastian <39752847+sebastian0619@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:02:59 +0800 Subject: [PATCH 001/138] Add files via upload --- public/favicon.ico | Bin 15406 -> 67646 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/favicon.ico b/public/favicon.ico index 92e7de8ffce12527bfb75119df2c350a65f82959..691fefe23cfce796401c40a0b4cf97c460f68c7b 100644 GIT binary patch literal 67646 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

mcJ|NfR&-_zXTbuE-D}Ew&o*pS04L; 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 literal 15406 zcmeI350F(=9mnsoZ2v4JYIC5C-PT=LmR;Bd(UJ)qv?vsrnvt5ch6HTOsY#opp$1Yc zgrJPFloZOOX*6a+(W!)jOdOr@Pc@7)WHE^(iI|2HF`)JNzCGvlduQ*x@4UzQRvhol zZ-2jYe*eDb&pr3t^X?`|T~eQn7?HqRlD^?daz>IQEiL`!j<+Vsdg^+5oPKPQ>}*Vu z4s3A2qrVBw2OR<-{3=-KU_hl8;uE zUaFH$HfV-fZEtVybmDJ-KL+3IXsx%QzR%Y?n+=p_cs=qe;m=iM07%0E|5ezwSzGjen@*L_i(KZ?gy_5AI3@Be!o_m2&eFh*6~za>E;3R-7cptB*ZPlJ8Ac5Gn`wn!fA z_*wDXIIJP>UdDQ%$jNKUtYcXrW9v1KE>$l0YTeh~Q8IPkbh=BaFIfk@UA3IJ)1ilH z>z&Mdo+Rm)bh2@_;eC+qlv7KbX5#7I+ePeIIi@8l*~-WHHkT+P3izLd|JC^KgFcT7 zPtuC7;qoQUlknw=84;n5al8nhz0g0P#rQ4=iTZQGSYyYIeS2u9vSL8{Aa*_>^CLmB+v4gG0;Q)p+)_?#;rt@{!x>+AcA=$^@B40+<))RYrpa^pY^&`ytJ;A z@rj;);&#=GNR5=wC?5y_z^}@ zcS_cpn&XwSzGFvnRSei>*)I#ib)$Z7I%<+`97Y5q( zP7@X z51;MOzrpcqSH3QMhsrDC+n0r(J*v*)ZqM_#I&j}04DdVf(|GKl9cO06NB7sKnZUEyPJkIxe}Fy8CyLhkUdv-c-4F8A?bIAeTa z7}uAZo15PNh7B=sWCNb1-dFn*eAmU`S3h|_@GpwZCp*Tg_-^iFc}J%_lqT^<2)n>S^Da*m&SCuEo{jr~hZh=93-kL|*^vJBF2vLFRe3%>{maQ&YIA z#n1lXeKuw}S>xq%rf{7}>s&4hKW$y$V^oWu{?Gl*ENl<>=lYx}T<5FT)Dl1Q;q(w= zUKxCu>p$(${^#mi;-~*}&+NmtFlR4L>33h{@kjl(Fc|ZPdDjzS%qxSxxMq9MhU*;p zD*C@>_?geZ{r`7r%p(@NoscgRx$tg(dN{50Oq33&nww~)_na@-_pke zen;0p==VRy=RmnJ^!mY=|Lnh~`FOFMHt4hCcfW}W(wfMOgF9_pi zmBFfaF-&OJi67tfcf#m^j^pO?q# z$aw?n?Wwe5zpbxS#$Uhhex@*S+@IsXe_2d^avr0tiE+f7m7+jIB|V(YAPz}`4t{!uZHGS&?L*!JJ&61> zd@t8{8Gr3ld!W_mqt6e0PI1?cK5{%QBF=KK6!rtw&dI`^`)#tmV*}3!txq}|7T5UL zdrh(28P`>=jnC5dzHnS!&L~E|yo{|RLNv=j{6?yl< zAE!;1K<@zWfcrP44LXBpE{|trzs=WcqI#eR)Wn za<{dS%v{m`@%&0Qw0j5gRQZN>LsbTz&gVY|uE!uf|CWKXMe7&xQs^M`XW7{E`Juv_ kl^47x~eoCKjl8nA&KNi@(R(!1ohNK7n2PJxF%K!iX From 18916e448f1b6f0f6ff6e5c85094ecc6d11907ff Mon Sep 17 00:00:00 2001 From: tangly1024 Date: Sun, 29 Oct 2023 22:45:37 +0800 Subject: [PATCH 002/138] =?UTF-8?q?CONFIG-TABLE=20=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E9=A6=96=E8=A1=8C=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/notion/getNotionData.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/notion/getNotionData.js b/lib/notion/getNotionData.js index f885f22b..6c01c294 100755 --- a/lib/notion/getNotionData.js +++ b/lib/notion/getNotionData.js @@ -272,6 +272,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { // 文章计数 let postCount = 0 + // 查找所有的Post和Page const allPages = collectionData.filter(post => { if (post?.type === 'Post' && post.status === 'Published') { @@ -282,6 +283,16 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { (post?.status === 'Invisible' || post?.status === 'Published') }) + // 从notion中读取配置 + const configPage = collectionData.find(post => { + return post && post?.type && post?.type === 'CONFIG' + }) + + if (configPage) { + const config = await getPostBlocks(configPage.id, 'config-table') + console.log('配置中心', configPage, config) + } + // Sort by date if (BLOG.POSTS_SORT_BY === 'date') { allPages.sort((a, b) => { From ff552b7de4a64aee829346e1ec22e7bb24ccb58e Mon Sep 17 00:00:00 2001 From: tangly1024 Date: Mon, 30 Oct 2023 22:39:06 +0800 Subject: [PATCH 003/138] =?UTF-8?q?=E4=BB=8ENotion=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E9=85=8D=E7=BD=AE=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/notion/getNotionConfig.js | 137 ++++++++++++++++++++++++++++++++ lib/notion/getNotionData.js | 13 +-- lib/notion/getPageProperties.js | 10 +++ next-sitemap.config.js | 3 + 4 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 lib/notion/getNotionConfig.js diff --git a/lib/notion/getNotionConfig.js b/lib/notion/getNotionConfig.js new file mode 100644 index 00000000..7a475ed3 --- /dev/null +++ b/lib/notion/getNotionConfig.js @@ -0,0 +1,137 @@ +/** + * 从Notion中读取站点配置; + * 在Notion模板中创建一个类型为CONFIG的页面,再添加一个数据库表格,即可用于填写配置 + * Notion数据库配置优先级最高,将覆盖vercel环境变量以及blog.config.js中的配置 + * --注意-- + * 数据库请从模板复制 https://www.notion.so/tanghh/287869a92e3d4d598cf366bd6994755e + * + */ +import { getDateValue, getTextContent } from 'notion-utils' +import { getPostBlocks } from './getPostBlocks' +import getAllPageIds from './getAllPageIds' +import BLOG from '@/blog.config' + +/** + * 从Notion中读取Config配置表 + * @param {*} allPages + * @returns + */ +export async function getConfigMapFromConfigPage(allPages) { + // 默认返回配置文件 + const notionConfig = BLOG + + if (!allPages || !Array.isArray(allPages) || allPages.length === 0) { + console.warn('[Notion配置] 忽略的配置', allPages, typeof allPages) + return null + } + const configPage = allPages?.find(post => { + return post && post?.type && post?.type === 'CONFIG' + }) + + if (!configPage) { + console.warn('[Notion配置]未找到配置页面', allPages, typeof allPages) + return null + } + const configPageId = configPage.id + + console.log('[Notion配置]请求配置数据 ', configPage.id) + const pageRecordMap = await getPostBlocks(configPageId, 'config-table') + // console.log('配置中心Page', configPageId, pageRecordMap) + const content = pageRecordMap.block[configPageId].value.content + + if (!content) { + console.warn('[Notion配置]未找到配置表格', pageRecordMap.block[configPageId], pageRecordMap.block[configPageId].value) + return null + } + + // 找到配置文件中的database + // for (const contentId of content) { + // console.log('内容', contentId, configPageRecordMap.block[contentId].value.type === 'collection_view') + // } + const configTableId = content?.find(contentId => { + return pageRecordMap.block[contentId].value.type === 'collection_view' + }) + + // eslint-disable-next-line no-constant-condition, no-self-compare + if (!configTableId) { + console.warn('[Notion配置]未找到配置表格数据', pageRecordMap.block[configPageId], pageRecordMap.block[configPageId].value) + return null + } + + // 页面查找 + const databaseRecordMap = pageRecordMap.block[configTableId] + const block = pageRecordMap.block || {} + const rawMetadata = databaseRecordMap.value + // Check Type Page-Database和Inline-Database + if ( + rawMetadata?.type !== 'collection_view_page' && rawMetadata?.type !== 'collection_view' + ) { + console.error(`pageId "${configTableId}" is not a database`) + return null + } + console.log('表格', databaseRecordMap, block, rawMetadata) + const collectionId = rawMetadata?.collection_id + const collection = pageRecordMap.collection[collectionId].value + const collectionQuery = pageRecordMap.collection_query + const collectionView = pageRecordMap.collection_view + const schema = collection?.schema + const viewIds = rawMetadata?.view_ids + const pageIds = getAllPageIds(collectionQuery, collectionId, collectionView, viewIds) + if (pageIds?.length === 0) { + console.error('[Notion配置]获取到的文章列表为空,请检查notion模板', collectionQuery, collection, collectionView, viewIds, databaseRecordMap) + } + // 遍历用户的表格 + for (let i = 0; i < pageIds.length; i++) { + const id = pageIds[i] + const value = block[id]?.value + if (!value) { + continue + } + const rawProperties = Object.entries(block?.[id]?.value?.properties || []) + const excludeProperties = ['date', 'select', 'multi_select', 'person'] + const properties = {} + for (let i = 0; i < rawProperties.length; i++) { + const [key, val] = rawProperties[i] + properties.id = id + if (schema[key]?.type && !excludeProperties.includes(schema[key].type)) { + properties[schema[key].name] = getTextContent(val) + } else { + switch (schema[key]?.type) { + case 'date': { + const dateProperty = getDateValue(val) + delete dateProperty.type + properties[schema[key].name] = dateProperty + break + } + case 'select': + case 'multi_select': { + const selects = getTextContent(val) + if (selects[0]?.length) { + properties[schema[key].name] = selects.split(',') + } + break + } + default: + break + } + } + } + + if (properties) { + // 将表格中的字段映射成 英文 + const config = { + enable: properties['启用'] === 'Yes', + key: properties['配置名'], + value: properties['配置值'] + } + + // 只导入生效的配置 + if (config.enable) { + console.log('[覆盖代码配置]', config.key) + notionConfig[config.key] = config.value + } + } + } + + return notionConfig +} diff --git a/lib/notion/getNotionData.js b/lib/notion/getNotionData.js index 6c01c294..3d063bcf 100755 --- a/lib/notion/getNotionData.js +++ b/lib/notion/getNotionData.js @@ -8,6 +8,7 @@ import getAllPageIds from './getAllPageIds' import { getAllTags } from './getAllTags' import getPageProperties from './getPageProperties' import { mapImgUrl, compressImage } from './mapImage' +import { getConfigMapFromConfigPage } from './getNotionConfig' /** * 获取博客数据 @@ -283,15 +284,8 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { (post?.status === 'Invisible' || post?.status === 'Published') }) - // 从notion中读取配置 - const configPage = collectionData.find(post => { - return post && post?.type && post?.type === 'CONFIG' - }) - - if (configPage) { - const config = await getPostBlocks(configPage.id, 'config-table') - console.log('配置中心', configPage, config) - } + // 站点配置优先读取配置表格,否则读取blog.config.js 文件 + const CONFIG = await getConfigMapFromConfigPage(collectionData) || BLOG // Sort by date if (BLOG.POSTS_SORT_BY === 'date') { @@ -311,6 +305,7 @@ async function getDataBaseInfoByNotionAPI({ pageId, from }) { const allNavPages = getNavPages({ allPages }) return { + CONFIG, notice, siteInfo, allPages, diff --git a/lib/notion/getPageProperties.js b/lib/notion/getPageProperties.js index ddf47f3e..3ea1ce28 100644 --- a/lib/notion/getPageProperties.js +++ b/lib/notion/getPageProperties.js @@ -6,6 +6,15 @@ import formatDate from '../formatDate' import md5 from 'js-md5' import { mapImgUrl } from './mapImage' +/** + * 获取页面元素成员属性 + * @param {*} id + * @param {*} block + * @param {*} schema + * @param {*} authToken + * @param {*} tagOptions + * @returns + */ export default async function getPageProperties(id, block, schema, authToken, tagOptions) { const rawProperties = Object.entries(block?.[id]?.value?.properties || []) const excludeProperties = ['date', 'select', 'multi_select', 'person'] @@ -108,6 +117,7 @@ export default async function getPageProperties(id, block, schema, authToken, ta properties.slug += '.html' } } + // 密码字段md5 properties.password = properties.password ? md5(properties.slug + properties.password) : '' return properties } diff --git a/next-sitemap.config.js b/next-sitemap.config.js index a094e809..29e1ca67 100644 --- a/next-sitemap.config.js +++ b/next-sitemap.config.js @@ -1,5 +1,8 @@ const BLOG = require('./blog.config') +/** + * 通常没啥用,sitemap交给 /pages/sitemap.xml.js 动态生成 + */ module.exports = { siteUrl: BLOG.LINK, changefreq: 'daily', From 52495e020074f2e052f64772d0f478abcce0270c Mon Sep 17 00:00:00 2001 From: "tangly1024.com" Date: Wed, 1 Nov 2023 18:40:00 +0800 Subject: [PATCH 004/138] =?UTF-8?q?=E9=83=A8=E5=88=86=E9=85=8D=E7=BD=AEsit?= =?UTF-8?q?eConfig=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Artalk.js | 3 +- components/Comment.js | 2 +- components/CommonHead.js | 40 ++++++++-------- lib/config.js | 49 ++++++++++++++++++++ lib/global.js | 5 +- lib/notion/getNotionConfig.js | 4 +- lib/notion/getNotionData.js | 4 +- pages/404.js | 5 +- pages/[prefix]/index.js | 3 +- pages/archive/index.js | 5 +- pages/category/[category]/index.js | 5 +- pages/category/[category]/page/[page].js | 5 +- pages/category/index.js | 5 +- pages/index.js | 24 +++++----- pages/page/[page].js | 5 +- pages/search/[keyword]/index.js | 5 +- pages/search/[keyword]/page/[page].js | 5 +- pages/search/index.js | 5 +- pages/tag/[tag]/index.js | 5 +- pages/tag/[tag]/page/[page].js | 5 +- pages/tag/index.js | 5 +- themes/example/components/BlogPostCard.js | 3 +- themes/example/components/Footer.js | 3 +- themes/example/components/Header.js | 5 +- themes/example/components/Title.js | 7 +-- themes/fukasawa/components/AsideLeft.js | 8 ++-- themes/fukasawa/components/Logo.js | 4 +- themes/fukasawa/components/SiteInfo.js | 3 +- themes/gitbook/components/Footer.js | 5 +- themes/gitbook/components/InfoCard.js | 6 +-- themes/gitbook/components/LogoBar.js | 6 +-- themes/heo/components/ArticleCopyright.js | 3 +- themes/heo/components/Footer.js | 4 +- themes/heo/components/InfoCard.js | 6 +-- themes/heo/components/Logo.js | 6 +-- themes/heo/components/NavBar.js | 4 +- themes/heo/components/SideBar.js | 4 +- themes/heo/index.js | 4 +- themes/hexo/components/ArticleCopyright.js | 11 +++-- themes/hexo/components/Footer.js | 4 +- themes/hexo/components/Hero.js | 3 +- themes/hexo/components/InfoCard.js | 5 +- themes/hexo/components/Logo.js | 4 +- themes/hexo/components/SideBar.js | 4 +- themes/hexo/index.js | 10 ++-- themes/matery/components/ArticleCopyright.js | 3 +- themes/matery/components/Footer.js | 4 +- themes/matery/components/Hero.js | 3 +- themes/matery/components/InfoCard.js | 6 ++- themes/matery/components/Logo.js | 4 +- themes/matery/components/SideBar.js | 8 ++-- themes/matery/index.js | 3 +- themes/medium/components/ArticleInfo.js | 5 +- themes/medium/components/Footer.js | 3 +- themes/medium/components/InfoCard.js | 5 +- themes/medium/components/LogoBar.js | 4 +- themes/medium/index.js | 5 +- themes/nav/components/Footer.js | 6 +-- themes/nav/components/InfoCard.js | 6 +-- themes/nav/components/LogoBar.js | 5 +- themes/next/components/ArticleDetail.js | 3 +- themes/next/components/Footer.js | 4 +- themes/next/components/InfoCard.js | 6 +-- themes/next/components/Logo.js | 7 +-- themes/next/index.js | 5 +- themes/nobelium/components/BlogListScroll.js | 7 +-- themes/nobelium/components/Footer.js | 3 +- themes/nobelium/components/Nav.js | 7 +-- themes/nobelium/components/Title.js | 8 ++-- themes/plog/components/BlogListScroll.js | 3 +- themes/plog/components/Footer.js | 3 +- themes/plog/components/LogoBar.js | 7 +-- themes/plog/components/Nav.js | 7 +-- themes/plog/components/SlideOvers.js | 3 +- themes/plog/components/Title.js | 8 ++-- themes/simple/components/ArticleInfo.js | 4 +- themes/simple/components/BlogItem.js | 3 +- themes/simple/components/Footer.js | 3 +- themes/simple/components/Header.js | 8 ++-- themes/simple/components/Title.js | 8 ++-- 80 files changed, 293 insertions(+), 200 deletions(-) create mode 100644 lib/config.js diff --git a/components/Artalk.js b/components/Artalk.js index 0da7734f..9f047a2f 100644 --- a/components/Artalk.js +++ b/components/Artalk.js @@ -1,4 +1,5 @@ import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' import { loadExternalResource } from '@/lib/utils' // import { loadExternalResource } from '@/lib/utils' import { useEffect } from 'react' @@ -19,7 +20,7 @@ const Artalk = ({ siteInfo }) => { locale: BLOG.LANG, // pageKey: '/post/1', // 固定链接 (留空自动获取) // pageTitle: '关于引入 Artalk 的这档子事', // 页面标题 (留空自动获取) - site: siteInfo?.title // 你的站点名 + site: siteConfig('TITLE') // 你的站点名 }) }, []) return ( diff --git a/components/Comment.js b/components/Comment.js index 6be4bc64..9359ed36 100644 --- a/components/Comment.js +++ b/components/Comment.js @@ -86,7 +86,7 @@ const Comment = ({ siteInfo, frontMatter, className }) => {

{BLOG.COMMENT_ARTALK_SERVER && (
- +
)} {BLOG.COMMENT_TWIKOO_ENV_ID && (
diff --git a/components/CommonHead.js b/components/CommonHead.js index 24bfae69..752a65bf 100644 --- a/components/CommonHead.js +++ b/components/CommonHead.js @@ -1,34 +1,34 @@ -import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' import Head from 'next/head' const CommonHead = ({ meta, children }) => { - let url = BLOG?.PATH?.length ? `${BLOG.LINK}/${BLOG.SUB_PATH}` : BLOG.LINK + let url = siteConfig('PATH')?.length ? `${siteConfig('LINK')}/${siteConfig('SUB_PATH')}` : siteConfig('LINK') let image if (meta) { url = `${url}/${meta.slug}` image = meta.image || '/bg_image.jpg' } - const title = meta?.title || BLOG.TITLE - const description = meta?.description || BLOG.DESCRIPTION + const title = meta?.title || siteConfig('TITLE') + const description = meta?.description || siteConfig('DESCRIPTION') const type = meta?.type || 'website' - const keywords = meta?.tags || BLOG.KEYWORDS - const lang = BLOG.LANG.replace('-', '_') // Facebook OpenGraph 要 zh_CN 這樣的格式才抓得到語言 - const category = meta?.category || BLOG.KEYWORDS || '軟體科技' // section 主要是像是 category 這樣的分類,Facebook 用這個來抓連結的分類 + const keywords = meta?.tags || siteConfig('KEYWORDS') + const lang = siteConfig('LANG').replace('-', '_') // Facebook OpenGraph 要 zh_CN 這樣的格式才抓得到語言 + const category = meta?.category || siteConfig('KEYWORDS') || '軟體科技' // section 主要是像是 category 這樣的分類,Facebook 用這個來抓連結的分類 return ( {title} - + - {BLOG.SEO_GOOGLE_SITE_VERIFICATION && ( + {siteConfig('SEO_GOOGLE_SITE_VERIFICATION') && ( )} - {BLOG.SEO_BAIDU_SITE_VERIFICATION && ()} + {siteConfig('SEO_BAIDU_SITE_VERIFICATION') && ()} @@ -36,33 +36,33 @@ const CommonHead = ({ meta, children }) => { - + - {BLOG.COMMENT_WEBMENTION.ENABLE && ( + {siteConfig('COMMENT_WEBMENTION').ENABLE && ( <> - - + + )} - {BLOG.COMMENT_WEBMENTION.ENABLE && BLOG.COMMENT_WEBMENTION.AUTH !== '' && ( - + {siteConfig('COMMENT_WEBMENTION').ENABLE && siteConfig('COMMENT_WEBMENTION').AUTH !== '' && ( + )} - {JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && } + {JSON.parse(siteConfig('ANALYTICS_BUSUANZI_ENABLE')) && } {meta?.type === 'Post' && ( <> - + - + )} {children} diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 00000000..62b5149b --- /dev/null +++ b/lib/config.js @@ -0,0 +1,49 @@ +'use client' + +import BLOG from '@/blog.config' +import { useGlobal } from './global' + +/** + * 读取配置 + * @param {*} key + * @returns + */ +export const siteConfig = (key) => { + let global = null + try { + // eslint-disable-next-line react-hooks/rules-of-hooks + global = useGlobal() + } catch (error) {} + + // 首先 配置最优先读取NOTION中的表格配置 + let val = null + let siteInfo = null + + if (global) { + val = global.NOTION_CONFIG?.[key] + siteInfo = global.siteInfo + console.log('当前变量', key, val) + } + + if (!val) { + // 这里针对部分key做一些兼容处理 + switch (key) { + case 'HOME_BANNER_IMAGE': + val = siteInfo?.pageCover // 封面图取Notion的封面 + break + case 'AVATAR': + val = siteInfo?.icon // 封面图取Notion的封面 + break + case 'TITLE': + val = siteConfig('TITLE') // 标题取Notion中的标题 + break + } + } + + // 其次 NOTION没有找到配置,则会读取blog.config.js文件 + if (!val) { + val = BLOG[key] + } + console.log('配置', key, val) + return val +} diff --git a/lib/global.js b/lib/global.js index c290a9e0..d1fc4142 100644 --- a/lib/global.js +++ b/lib/global.js @@ -1,10 +1,10 @@ import { generateLocaleDict, initLocale } from './lang' import { createContext, useContext, useEffect, useState } from 'react' import { useRouter } from 'next/router' -import BLOG from '@/blog.config' import { THEMES, initDarkMode } from '@/themes/theme' import NProgress from 'nprogress' import { getQueryVariable, isBrowser } from './utils' +import BLOG from '@/blog.config' const GlobalContext = createContext() @@ -15,7 +15,7 @@ const GlobalContext = createContext() * @constructor */ export function GlobalContextProvider(props) { - const { children, siteInfo, categoryOptions, tagOptions } = props + const { children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props const router = useRouter() const [lang, updateLang] = useState(BLOG.LANG) // 默认语言 const [locale, updateLocale] = useState(generateLocaleDict(BLOG.LANG)) // 默认语言 @@ -68,6 +68,7 @@ export function GlobalContextProvider(props) { return ( { - const { siteInfo } = useGlobal() - const meta = { title: `${props?.siteInfo?.title} | 页面找不到啦`, image: siteInfo?.pageCover } + const meta = { title: `${siteConfig('TITLE')} | 页面找不到啦`, image: siteConfig('HOME_BANNER_IMAGE') } props = { ...props, meta } diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js index 6fa5d47b..b350fb18 100644 --- a/pages/[prefix]/index.js +++ b/pages/[prefix]/index.js @@ -10,6 +10,7 @@ import { getLayoutByTheme } from '@/themes/theme' import md5 from 'js-md5' import { isBrowser } from '@/lib/utils' import { uploadDataToAlgolia } from '@/lib/algolia' +import { siteConfig } from '@/lib/config' /** * 根据notion的slug访问页面 @@ -66,7 +67,7 @@ const Slug = props => { }, [post]) const meta = { - title: post ? `${post?.title} | ${siteInfo?.title}` : `${props?.siteInfo?.title || BLOG.TITLE} | loading`, + title: post ? `${post?.title} | ${siteConfig('TITLE')}` : `${props?.siteConfig('TITLE') || BLOG.TITLE} | loading`, description: post?.summary, type: post?.type, slug: post?.slug, diff --git a/pages/archive/index.js b/pages/archive/index.js index 106ad7d2..3726291d 100644 --- a/pages/archive/index.js +++ b/pages/archive/index.js @@ -6,6 +6,7 @@ import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' import { isBrowser } from '@/lib/utils' import { formatDateFmt } from '@/lib/formatDate' +import { siteConfig } from '@/lib/config' const ArchiveIndex = props => { const { siteInfo } = props @@ -29,8 +30,8 @@ const ArchiveIndex = props => { }, []) const meta = { - title: `${locale.NAV.ARCHIVE} | ${siteInfo?.title}`, - description: siteInfo?.description, + title: `${locale.NAV.ARCHIVE} | ${siteConfig('TITLE')}`, + description: siteConfig('HOME_BANNER_IMAGE'), image: siteInfo?.pageCover, slug: 'archive', type: 'website' diff --git a/pages/category/[category]/index.js b/pages/category/[category]/index.js index 03cd4fbd..a2c30722 100644 --- a/pages/category/[category]/index.js +++ b/pages/category/[category]/index.js @@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global' import BLOG from '@/blog.config' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' /** * 分类页 @@ -19,9 +20,9 @@ export default function Category(props) { const meta = { title: `${props.category} | ${locale.COMMON.CATEGORY} | ${ - siteInfo?.title || '' + siteConfig('TITLE') || '' }`, - description: siteInfo?.description, + description: siteConfig('HOME_BANNER_IMAGE'), slug: 'category/' + props.category, image: siteInfo?.pageCover, type: 'website' diff --git a/pages/category/[category]/page/[page].js b/pages/category/[category]/page/[page].js index 9174e117..8a6cccc8 100644 --- a/pages/category/[category]/page/[page].js +++ b/pages/category/[category]/page/[page].js @@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global' import BLOG from '@/blog.config' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' /** * 分类页 @@ -19,9 +20,9 @@ export default function Category(props) { const meta = { title: `${props.category} | ${locale.COMMON.CATEGORY} | ${ - siteInfo?.title || '' + siteConfig('TITLE') || '' }`, - description: siteInfo?.description, + description: siteConfig('HOME_BANNER_IMAGE'), slug: 'category/' + props.category, image: siteInfo?.pageCover, type: 'website' diff --git a/pages/category/index.js b/pages/category/index.js index b4872000..8f6d98bc 100644 --- a/pages/category/index.js +++ b/pages/category/index.js @@ -4,6 +4,7 @@ import { useGlobal } from '@/lib/global' import BLOG from '@/blog.config' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' /** * 分类首页 @@ -18,8 +19,8 @@ export default function Category(props) { const Layout = getLayoutByTheme(useRouter()) const meta = { - title: `${locale.COMMON.CATEGORY} | ${siteInfo?.title}`, - description: siteInfo?.description, + title: `${locale.COMMON.CATEGORY} | ${siteConfig('TITLE')}`, + description: siteConfig('HOME_BANNER_IMAGE'), image: siteInfo?.pageCover, slug: 'category', type: 'website' diff --git a/pages/index.js b/pages/index.js index 328ece1e..ac72c1b5 100644 --- a/pages/index.js +++ b/pages/index.js @@ -5,6 +5,7 @@ import { generateRss } from '@/lib/rss' import { generateRobotsTxt } from '@/lib/robots.txt' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' /** * 首页布局 @@ -14,7 +15,15 @@ import { getLayoutByTheme } from '@/themes/theme' const Index = props => { // 根据页面路径加载不同Layout文件 const Layout = getLayoutByTheme(useRouter()) - return + + const meta = { + title: `${siteConfig('TITLE')} | ${siteConfig('BIO')}`, + description: siteConfig('DESCRIPTION'), + image: siteConfig('HOME_BANNER_IMAGE'), + slug: '', + type: 'website' + } + return } /** @@ -25,16 +34,8 @@ export async function getStaticProps() { const from = 'index' const props = await getGlobalData({ from }) - const { siteInfo } = props props.posts = props.allPages?.filter(page => page.type === 'Post' && page.status === 'Published') - const meta = { - title: `${siteInfo?.title} | ${siteInfo?.description}`, - description: siteInfo?.description, - image: siteInfo?.pageCover, - slug: '', - type: 'website' - } // 处理分页 if (BLOG.POST_LIST_STYLE === 'scroll') { // 滚动列表默认给前端返回所有数据 @@ -65,10 +66,7 @@ export async function getStaticProps() { delete props.allPages return { - props: { - meta, - ...props - }, + props, revalidate: parseInt(BLOG.NEXT_REVALIDATE_SECOND) } } diff --git a/pages/page/[page].js b/pages/page/[page].js index 02b847a8..131feb45 100644 --- a/pages/page/[page].js +++ b/pages/page/[page].js @@ -3,6 +3,7 @@ import { getPostBlocks } from '@/lib/notion' import { getGlobalData } from '@/lib/notion/getNotionData' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' /** * 文章列表分页 @@ -16,8 +17,8 @@ const Page = props => { const Layout = getLayoutByTheme(useRouter()) const meta = { - title: `${props?.page} | Page | ${siteInfo?.title}`, - description: siteInfo?.description, + title: `${props?.page} | Page | ${siteConfig('TITLE')}`, + description: siteConfig('HOME_BANNER_IMAGE'), image: siteInfo?.pageCover, slug: 'page/' + props.page, type: 'website' diff --git a/pages/search/[keyword]/index.js b/pages/search/[keyword]/index.js index 042bbf85..bc2514a1 100644 --- a/pages/search/[keyword]/index.js +++ b/pages/search/[keyword]/index.js @@ -4,6 +4,7 @@ import { getDataFromCache } from '@/lib/cache/cache_manager' import BLOG from '@/blog.config' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' const Index = props => { const { keyword, siteInfo } = props @@ -13,8 +14,8 @@ const Index = props => { const Layout = getLayoutByTheme(useRouter()) const meta = { - title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`, - description: siteInfo?.title, + title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`, + description: siteConfig('TITLE'), image: siteInfo?.pageCover, slug: 'search/' + (keyword || ''), type: 'website' diff --git a/pages/search/[keyword]/page/[page].js b/pages/search/[keyword]/page/[page].js index 20240f19..c5c4aeb8 100644 --- a/pages/search/[keyword]/page/[page].js +++ b/pages/search/[keyword]/page/[page].js @@ -4,6 +4,7 @@ import { getDataFromCache } from '@/lib/cache/cache_manager' import BLOG from '@/blog.config' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' const Index = props => { const { keyword, siteInfo } = props @@ -13,8 +14,8 @@ const Index = props => { const Layout = getLayoutByTheme(useRouter()) const meta = { - title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`, - description: siteInfo?.title, + title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`, + description: siteConfig('TITLE'), image: siteInfo?.pageCover, slug: 'search/' + (keyword || ''), type: 'website' diff --git a/pages/search/index.js b/pages/search/index.js index 22061cbd..31d09af3 100644 --- a/pages/search/index.js +++ b/pages/search/index.js @@ -3,6 +3,7 @@ import { useGlobal } from '@/lib/global' import { useRouter } from 'next/router' import BLOG from '@/blog.config' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' /** * 搜索路由 @@ -34,8 +35,8 @@ const Search = props => { } const meta = { - title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteInfo?.title}`, - description: siteInfo?.description, + title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`, + description: siteConfig('HOME_BANNER_IMAGE'), image: siteInfo?.pageCover, slug: 'search', type: 'website' diff --git a/pages/tag/[tag]/index.js b/pages/tag/[tag]/index.js index bd9f7529..f12cdc41 100644 --- a/pages/tag/[tag]/index.js +++ b/pages/tag/[tag]/index.js @@ -3,6 +3,7 @@ import { getGlobalData } from '@/lib/notion/getNotionData' import BLOG from '@/blog.config' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' /** * 标签下的文章列表 @@ -17,8 +18,8 @@ const Tag = props => { const Layout = getLayoutByTheme(useRouter()) const meta = { - title: `${tag} | ${locale.COMMON.TAGS} | ${siteInfo?.title}`, - description: siteInfo?.description, + title: `${tag} | ${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`, + description: siteConfig('HOME_BANNER_IMAGE'), image: siteInfo?.pageCover, slug: 'tag/' + tag, type: 'website' diff --git a/pages/tag/[tag]/page/[page].js b/pages/tag/[tag]/page/[page].js index 2eaef518..c708793b 100644 --- a/pages/tag/[tag]/page/[page].js +++ b/pages/tag/[tag]/page/[page].js @@ -3,6 +3,7 @@ import { getGlobalData } from '@/lib/notion/getNotionData' import BLOG from '@/blog.config' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' const Tag = props => { const { locale } = useGlobal() @@ -12,8 +13,8 @@ const Tag = props => { const Layout = getLayoutByTheme(useRouter()) const meta = { - title: `${tag} | ${locale.COMMON.TAGS} | ${siteInfo?.title}`, - description: siteInfo?.description, + title: `${tag} | ${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`, + description: siteConfig('HOME_BANNER_IMAGE'), image: siteInfo?.pageCover, slug: 'tag/' + tag, type: 'website' diff --git a/pages/tag/index.js b/pages/tag/index.js index 54dab9a4..cb6db4da 100644 --- a/pages/tag/index.js +++ b/pages/tag/index.js @@ -3,6 +3,7 @@ import { useGlobal } from '@/lib/global' import BLOG from '@/blog.config' import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' +import { siteConfig } from '@/lib/config' /** * 标签首页 @@ -17,8 +18,8 @@ const TagIndex = props => { const Layout = getLayoutByTheme(useRouter()) const meta = { - title: `${locale.COMMON.TAGS} | ${siteInfo?.title}`, - description: siteInfo?.description, + title: `${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`, + description: siteConfig('HOME_BANNER_IMAGE'), image: siteInfo?.pageCover, slug: 'tag', type: 'website' diff --git a/themes/example/components/BlogPostCard.js b/themes/example/components/BlogPostCard.js index faf1753d..fb91a307 100644 --- a/themes/example/components/BlogPostCard.js +++ b/themes/example/components/BlogPostCard.js @@ -3,6 +3,7 @@ import CONFIG from '../config' import Link from 'next/link' import TwikooCommentCount from '@/components/TwikooCommentCount' import LazyImage from '@/components/LazyImage' +import { siteConfig } from '@/lib/config' const BlogPostCard = ({ post }) => { const showPageCover = CONFIG.POST_LIST_COVER && post?.pageCoverThumbnail @@ -18,7 +19,7 @@ const BlogPostCard = ({ post }) => {
- by {BLOG.AUTHOR} on {post.date?.start_date || post.createdTime} + by {siteConfig('AUTHOR')} on {post.date?.start_date || post.createdTime} | {post.category} diff --git a/themes/example/components/Footer.js b/themes/example/components/Footer.js index 09af18fe..26c1ae74 100644 --- a/themes/example/components/Footer.js +++ b/themes/example/components/Footer.js @@ -1,5 +1,6 @@ import BLOG from '@/blog.config' import DarkModeButton from '@/components/DarkModeButton' +import { siteConfig } from '@/lib/config' export const Footer = (props) => { const d = new Date() @@ -15,7 +16,7 @@ export const Footer = (props) => {
-
©{`${copyrightDate}`} {BLOG.AUTHOR}. All rights reserved.
+
©{`${copyrightDate}`} {siteConfig('AUTHOR')}. All rights reserved.
{/* 右侧链接 */} {/* Privacy Policy */} diff --git a/themes/example/components/Header.js b/themes/example/components/Header.js index 0cd85268..2ec1e27b 100644 --- a/themes/example/components/Header.js +++ b/themes/example/components/Header.js @@ -1,12 +1,11 @@ import Link from 'next/link' +import { siteConfig } from '@/lib/config' /** * 网站顶部 * @returns */ export const Header = (props) => { - const { siteInfo } = props - return (
@@ -14,7 +13,7 @@ export const Header = (props) => { href='/' className="py-6 w-full text-center md:text-left md:w-auto text-gray-dark no-underline flex justify-center items-center"> - {siteInfo?.title} + {siteConfig('TITLE')}
{/* 右侧文字 */} diff --git a/themes/example/components/Title.js b/themes/example/components/Title.js index e57e2347..08fd67f1 100644 --- a/themes/example/components/Title.js +++ b/themes/example/components/Title.js @@ -1,4 +1,5 @@ import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' /** * 标题栏 @@ -6,9 +7,9 @@ import BLOG from '@/blog.config' * @returns */ export const Title = (props) => { - const { siteInfo, post } = props - const title = post?.title || siteInfo?.description - const description = post?.description || BLOG.AUTHOR + const { post } = props + const title = post?.title || siteConfig('HOME_BANNER_IMAGE') + const description = post?.description || siteConfig('AUTHOR') return

{title}

diff --git a/themes/fukasawa/components/AsideLeft.js b/themes/fukasawa/components/AsideLeft.js index bf6edb45..131e5f31 100644 --- a/themes/fukasawa/components/AsideLeft.js +++ b/themes/fukasawa/components/AsideLeft.js @@ -12,9 +12,7 @@ import SocialButton from './SocialButton' import { useFukasawaGlobal } from '..' import CONFIG from '@/themes/fukasawa/config' import { AdSlot } from '@/components/GoogleAdsense' - -// import { debounce } from 'lodash' -// import { useEffect } from 'react' +import { siteConfig } from '@/lib/config' /** * 侧边栏 @@ -22,7 +20,7 @@ import { AdSlot } from '@/components/GoogleAdsense' * @returns */ function AsideLeft(props) { - const { tagOptions, currentTag, categoryOptions, currentCategory, post, slot, siteInfo, notice } = props + const { tagOptions, currentTag, categoryOptions, currentCategory, post, slot, notice } = props const router = useRouter() const { isCollapsed, setIsCollapse } = useFukasawaGlobal() // 折叠侧边栏 @@ -65,7 +63,7 @@ function AsideLeft(props) {
- {siteInfo?.description} + {siteConfig('HOME_BANNER_IMAGE')}
diff --git a/themes/fukasawa/components/Logo.js b/themes/fukasawa/components/Logo.js index 3b6720fb..1b28e1ec 100644 --- a/themes/fukasawa/components/Logo.js +++ b/themes/fukasawa/components/Logo.js @@ -1,13 +1,13 @@ import Link from 'next/link' +import { siteConfig } from '@/lib/config' const Logo = props => { - const { siteInfo } = props return (
- {siteInfo?.title} + {siteConfig('TITLE')}
); diff --git a/themes/fukasawa/components/SiteInfo.js b/themes/fukasawa/components/SiteInfo.js index 3a18bcd1..d4b25b54 100644 --- a/themes/fukasawa/components/SiteInfo.js +++ b/themes/fukasawa/components/SiteInfo.js @@ -1,4 +1,5 @@ import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' function SiteInfo ({ title }) { const d = new Date() @@ -14,7 +15,7 @@ function SiteInfo ({ title }) {
- © {`${copyrightDate}`} {BLOG.AUTHOR}.
+ © {`${copyrightDate}`} {siteConfig('AUTHOR')}.
{BLOG.BEI_AN && <> {BLOG.BEI_AN}
} diff --git a/themes/gitbook/components/Footer.js b/themes/gitbook/components/Footer.js index fb1d339c..09cb9daf 100644 --- a/themes/gitbook/components/Footer.js +++ b/themes/gitbook/components/Footer.js @@ -1,5 +1,6 @@ import React from 'react' import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' const Footer = ({ siteInfo }) => { const d = new Date() @@ -18,7 +19,7 @@ const Footer = ({ siteInfo }) => {
- + © {`${copyrightDate}`}
@@ -30,7 +31,7 @@ const Footer = ({ siteInfo }) => {
-

{siteInfo?.title}

+

{siteConfig('TITLE')}

) diff --git a/themes/gitbook/components/InfoCard.js b/themes/gitbook/components/InfoCard.js index 13fe31c8..6687aa04 100644 --- a/themes/gitbook/components/InfoCard.js +++ b/themes/gitbook/components/InfoCard.js @@ -1,17 +1,17 @@ import BLOG from '@/blog.config' import LazyImage from '@/components/LazyImage' import Router from 'next/router' -import React from 'react' import SocialButton from './SocialButton' +import { siteConfig } from '@/lib/config' const InfoCard = (props) => { const { siteInfo } = props return
{ Router.push('/about') }}> - +
-
{BLOG.AUTHOR}
+
{siteConfig('AUTHOR')}
{BLOG.BIO}
diff --git a/themes/gitbook/components/LogoBar.js b/themes/gitbook/components/LogoBar.js index 70c96b46..e0f7b4cb 100644 --- a/themes/gitbook/components/LogoBar.js +++ b/themes/gitbook/components/LogoBar.js @@ -1,7 +1,7 @@ -import BLOG from '@/blog.config' import LazyImage from '@/components/LazyImage' import { useGitBookGlobal } from '@/themes/gitbook' import Link from 'next/link' +import { siteConfig } from '@/lib/config' /** * Logo区域 @@ -21,8 +21,8 @@ export default function LogoBar(props) {
- - {siteInfo?.title} + + {siteConfig('TITLE')}
) diff --git a/themes/heo/components/ArticleCopyright.js b/themes/heo/components/ArticleCopyright.js index 78500ddf..740fdc1c 100644 --- a/themes/heo/components/ArticleCopyright.js +++ b/themes/heo/components/ArticleCopyright.js @@ -4,6 +4,7 @@ import Link from 'next/link' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import CONFIG from '../config' +import { siteConfig } from '@/lib/config' export default function ArticleCopyright () { const router = useRouter() @@ -24,7 +25,7 @@ export default function ArticleCopyright () {
  • {locale.COMMON.AUTHOR}: - {BLOG.AUTHOR} + {siteConfig('AUTHOR')}
  • diff --git a/themes/heo/components/Footer.js b/themes/heo/components/Footer.js index 772ade19..4899a544 100644 --- a/themes/heo/components/Footer.js +++ b/themes/heo/components/Footer.js @@ -1,7 +1,7 @@ import React from 'react' import BLOG from '@/blog.config' import SocialButton from './SocialButton' -// import DarkModeButton from '@/components/DarkModeButton' +import { siteConfig } from '@/lib/config' const Footer = ({ title }) => { const d = new Date() @@ -34,7 +34,7 @@ const Footer = ({ title }) => { {/* 页脚 */} -
    +
  • ) } diff --git a/themes/nobelium/components/BlogListScroll.js b/themes/nobelium/components/BlogListScroll.js index d3d91abb..90d1d0e1 100644 --- a/themes/nobelium/components/BlogListScroll.js +++ b/themes/nobelium/components/BlogListScroll.js @@ -1,9 +1,10 @@ import BLOG from '@/blog.config' import { useGlobal } from '@/lib/global' import Link from 'next/link' -import React from 'react' import throttle from 'lodash.throttle' import { deepClone } from '@/lib/utils' +import { siteConfig } from '@/lib/config' +import { useEffect } from 'react' export const BlogListScroll = props => { const { posts } = props @@ -36,7 +37,7 @@ export const BlogListScroll = props => { } }, 500)) - React.useEffect(() => { + useEffect(() => { window.addEventListener('scroll', scrollTrigger) return () => { @@ -57,7 +58,7 @@ export const BlogListScroll = props => {
    - by {BLOG.AUTHOR} on {p.date?.start_date || p.createdTime} + by {siteConfig('AUTHOR')} on {p.date?.start_date || p.createdTime} | {p.category} | diff --git a/themes/nobelium/components/Footer.js b/themes/nobelium/components/Footer.js index 6b4b7857..3b4044a8 100644 --- a/themes/nobelium/components/Footer.js +++ b/themes/nobelium/components/Footer.js @@ -1,6 +1,7 @@ import BLOG from '@/blog.config' import DarkModeButton from '@/components/DarkModeButton' import Vercel from '@/components/Vercel' +import { siteConfig } from '@/lib/config' export const Footer = (props) => { const d = new Date() @@ -25,7 +26,7 @@ export const Footer = (props) => {

    - © {BLOG.AUTHOR} {copyrightDate} + © {siteConfig('AUTHOR')} {copyrightDate}

    diff --git a/themes/nobelium/components/Nav.js b/themes/nobelium/components/Nav.js index 7e6c12ac..73df49a3 100644 --- a/themes/nobelium/components/Nav.js +++ b/themes/nobelium/components/Nav.js @@ -10,6 +10,7 @@ import { MenuItemCollapse } from './MenuItemCollapse' import LazyImage from '@/components/LazyImage' import RandomPostButton from './RandomPostButton' import SearchButton from './SearchButton' +import { siteConfig } from '@/lib/config' const Nav = props => { const { navBarTitle, fullWidth, siteInfo } = props @@ -48,7 +49,7 @@ const Nav = props => {
    {/* */} {CONFIG.NAV_NOTION_ICON - ? + ? : }
    @@ -62,8 +63,8 @@ const Nav = props => { ) : (

    - {siteInfo?.title} - {/* ,{' '}{siteInfo?.description} */} + {siteConfig('TITLE')} + {/* ,{' '}{siteConfig('HOME_BANNER_IMAGE')} */}

    )}
    diff --git a/themes/nobelium/components/Title.js b/themes/nobelium/components/Title.js index e57e2347..a1512af2 100644 --- a/themes/nobelium/components/Title.js +++ b/themes/nobelium/components/Title.js @@ -1,4 +1,4 @@ -import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' /** * 标题栏 @@ -6,9 +6,9 @@ import BLOG from '@/blog.config' * @returns */ export const Title = (props) => { - const { siteInfo, post } = props - const title = post?.title || siteInfo?.description - const description = post?.description || BLOG.AUTHOR + const { post } = props + const title = post?.title || siteConfig('HOME_BANNER_IMAGE') + const description = post?.description || siteConfig('AUTHOR') return

    {title}

    diff --git a/themes/plog/components/BlogListScroll.js b/themes/plog/components/BlogListScroll.js index 157e80e4..809451f6 100644 --- a/themes/plog/components/BlogListScroll.js +++ b/themes/plog/components/BlogListScroll.js @@ -3,6 +3,7 @@ import { useGlobal } from '@/lib/global' import Link from 'next/link' import React from 'react' import throttle from 'lodash.throttle' +import { siteConfig } from '@/lib/config' export const BlogListScroll = props => { const { posts } = props @@ -56,7 +57,7 @@ export const BlogListScroll = props => {
    - by {BLOG.AUTHOR} on {p.date?.start_date || p.createdTime} + by {siteConfig('AUTHOR')} on {p.date?.start_date || p.createdTime} | {p.category} | diff --git a/themes/plog/components/Footer.js b/themes/plog/components/Footer.js index 2a6bdcb9..d53de2f3 100644 --- a/themes/plog/components/Footer.js +++ b/themes/plog/components/Footer.js @@ -1,5 +1,6 @@ import BLOG from '@/blog.config' import Vercel from '@/components/Vercel' +import { siteConfig } from '@/lib/config' export const Footer = (props) => { const d = new Date() @@ -15,7 +16,7 @@ export const Footer = (props) => { return
    -
    © {BLOG.AUTHOR} {copyrightDate}
    +
    © {siteConfig('AUTHOR')} {copyrightDate}
    diff --git a/themes/plog/components/LogoBar.js b/themes/plog/components/LogoBar.js index 07539f2f..070b45ae 100644 --- a/themes/plog/components/LogoBar.js +++ b/themes/plog/components/LogoBar.js @@ -3,6 +3,7 @@ import LazyImage from '@/components/LazyImage' import Link from 'next/link' import CONFIG from '../config' import { SvgIcon } from './SvgIcon' +import { siteConfig } from '@/lib/config' /** * logo文字栏 @@ -16,7 +17,7 @@ export default function LogoBar(props) {
    {CONFIG.NAV_NOTION_ICON - ? + ? : }
    @@ -30,8 +31,8 @@ export default function LogoBar(props) { ) : (

    - {siteInfo?.title} - {' '}{siteInfo?.description} + {siteConfig('TITLE')} + {' '}{siteConfig('HOME_BANNER_IMAGE')}

    )}
    diff --git a/themes/plog/components/Nav.js b/themes/plog/components/Nav.js index 401f0d49..f57accac 100644 --- a/themes/plog/components/Nav.js +++ b/themes/plog/components/Nav.js @@ -8,11 +8,12 @@ import { MenuItemDrop } from './MenuItemDrop' import Collapse from '@/components/Collapse' import { MenuItemCollapse } from './MenuItemCollapse' import LazyImage from '@/components/LazyImage' +import { siteConfig } from '@/lib/config' const Header = props => { const { fullWidth, siteInfo } = props - const title = siteInfo?.title + const title = siteConfig('TITLE') return
    ) diff --git a/themes/simple/components/Title.js b/themes/simple/components/Title.js index e57e2347..a1512af2 100644 --- a/themes/simple/components/Title.js +++ b/themes/simple/components/Title.js @@ -1,4 +1,4 @@ -import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' /** * 标题栏 @@ -6,9 +6,9 @@ import BLOG from '@/blog.config' * @returns */ export const Title = (props) => { - const { siteInfo, post } = props - const title = post?.title || siteInfo?.description - const description = post?.description || BLOG.AUTHOR + const { post } = props + const title = post?.title || siteConfig('HOME_BANNER_IMAGE') + const description = post?.description || siteConfig('AUTHOR') return

    {title}

    From 47b82d12bd2dfd9230686ac33af6294123765447 Mon Sep 17 00:00:00 2001 From: "tangly1024.com" Date: Wed, 1 Nov 2023 18:41:13 +0800 Subject: [PATCH 005/138] =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/config.js | 3 +++ themes/example/components/Title.js | 1 - themes/medium/components/ArticleInfo.js | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/config.js b/lib/config.js index 62b5149b..b6c99afb 100644 --- a/lib/config.js +++ b/lib/config.js @@ -5,6 +5,9 @@ import { useGlobal } from './global' /** * 读取配置 + * 1. 优先读取NotionConfig表 + * 2. 其次读取环境变量 + * 3. 再读取blog.config.js文件 * @param {*} key * @returns */ diff --git a/themes/example/components/Title.js b/themes/example/components/Title.js index 08fd67f1..a1512af2 100644 --- a/themes/example/components/Title.js +++ b/themes/example/components/Title.js @@ -1,4 +1,3 @@ -import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' /** diff --git a/themes/medium/components/ArticleInfo.js b/themes/medium/components/ArticleInfo.js index 9b0ea2b9..12c189db 100644 --- a/themes/medium/components/ArticleInfo.js +++ b/themes/medium/components/ArticleInfo.js @@ -1,4 +1,3 @@ -import BLOG from '@/blog.config' import LazyImage from '@/components/LazyImage' import Link from 'next/link' import { siteConfig } from '@/lib/config' From 3e58c6b637ab98ba15c0a014dbba783b1b6d76b1 Mon Sep 17 00:00:00 2001 From: tangly1024 Date: Wed, 1 Nov 2023 23:13:25 +0800 Subject: [PATCH 006/138] =?UTF-8?q?=E5=8A=A8=E6=80=81=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/ExternalPlugins.js | 14 ++------ lib/config.js | 6 ++-- lib/global.js | 53 +++++++++++++++++------------- lib/lang.js | 2 +- lib/notion/getNotionConfig.js | 2 +- pages/_app.js | 5 ++- themes/hexo/components/Footer.js | 4 +-- themes/hexo/components/InfoCard.js | 2 +- 8 files changed, 43 insertions(+), 45 deletions(-) diff --git a/components/ExternalPlugins.js b/components/ExternalPlugins.js index 5b771fbb..c65836c4 100644 --- a/components/ExternalPlugins.js +++ b/components/ExternalPlugins.js @@ -2,17 +2,6 @@ import BLOG from 'blog.config' import dynamic from 'next/dynamic' import WebWhiz from './Webwhiz' -// import TwikooCommentCounter from '@/components/TwikooCommentCounter' -// import { DebugPanel } from '@/components/DebugPanel' -// import { ThemeSwitch } from '@/components/ThemeSwitch' -// import { Fireworks } from '@/components/Fireworks' -// import { Nest } from '@/components/Nest' -// import { FlutteringRibbon } from '@/components/FlutteringRibbon' -// import { Ribbon } from '@/components/Ribbon' -// import { Sakura } from '@/components/Sakura' -// import { StarrySky } from '@/components/StarrySky' -// import { Analytics } from '@vercel/analytics/react' - const TwikooCommentCounter = dynamic(() => import('@/components/TwikooCommentCounter'), { ssr: false }) const DebugPanel = dynamic(() => import('@/components/DebugPanel'), { ssr: false }) const ThemeSwitch = dynamic(() => import('@/components/ThemeSwitch'), { ssr: false }) @@ -33,8 +22,9 @@ const VConsole = dynamic(() => import('@/components/VConsole'), { ssr: false }) const CustomContextMenu = dynamic(() => import('@/components/CustomContextMenu'), { ssr: false }) const DisableCopy = dynamic(() => import('@/components/DisableCopy'), { ssr: false }) const AdBlockDetect = dynamic(() => import('@/components/AdBlockDetect'), { ssr: false }) + /** - * 各种第三方组件 + * 各种插件脚本 * @param {*} props * @returns */ diff --git a/lib/config.js b/lib/config.js index b6c99afb..ff931386 100644 --- a/lib/config.js +++ b/lib/config.js @@ -25,7 +25,7 @@ export const siteConfig = (key) => { if (global) { val = global.NOTION_CONFIG?.[key] siteInfo = global.siteInfo - console.log('当前变量', key, val) + // console.log('当前变量', key, val) } if (!val) { @@ -38,7 +38,7 @@ export const siteConfig = (key) => { val = siteInfo?.icon // 封面图取Notion的封面 break case 'TITLE': - val = siteConfig('TITLE') // 标题取Notion中的标题 + val = siteInfo?.title // 标题取Notion中的标题 break } } @@ -47,6 +47,6 @@ export const siteConfig = (key) => { if (!val) { val = BLOG[key] } - console.log('配置', key, val) + console.log('实际配置', key, val) return val } diff --git a/lib/global.js b/lib/global.js index d1fc4142..548b6d8c 100644 --- a/lib/global.js +++ b/lib/global.js @@ -2,33 +2,53 @@ import { generateLocaleDict, initLocale } from './lang' import { createContext, useContext, useEffect, useState } from 'react' import { useRouter } from 'next/router' import { THEMES, initDarkMode } from '@/themes/theme' -import NProgress from 'nprogress' -import { getQueryVariable, isBrowser } from './utils' + import BLOG from '@/blog.config' +import NProgress from 'nprogress' +import { isBrowser } from './utils' const GlobalContext = createContext() /** - * 全局变量Provider,包括语言本地化、样式主题、搜索词 + * 定义全局变量,包括语言、主题、深色模式、加载状态 * @param children * @returns {JSX.Element} * @constructor */ export function GlobalContextProvider(props) { const { children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props + console.log('config', NOTION_CONFIG) const router = useRouter() - const [lang, updateLang] = useState(BLOG.LANG) // 默认语言 - const [locale, updateLocale] = useState(generateLocaleDict(BLOG.LANG)) // 默认语言 - const [theme, setTheme] = useState(BLOG.THEME) // 默认博客主题 - const [isDarkMode, updateDarkMode] = useState(BLOG.APPEARANCE === 'dark') // 默认深色模式 + const [lang, updateLang] = useState(NOTION_CONFIG.LANG || BLOG.LANG) // 默认语言 + const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG.LANG || BLOG.LANG)) // 默认语言 + const [theme, setTheme] = useState(NOTION_CONFIG.THEME || BLOG.THEME) // 默认博客主题 + const [isDarkMode, updateDarkMode] = useState(NOTION_CONFIG.APPEARANCE || BLOG.APPEARANCE === 'dark') // 默认深色模式 const [onLoading, setOnLoading] = useState(false) // 抓取文章数据 + // 切换主题 + function switchTheme() { + const currentIndex = THEMES.indexOf(theme) + const newIndex = currentIndex < THEMES.length - 1 ? currentIndex + 1 : 0 + const newTheme = THEMES[newIndex] + const query = router.query + query.theme = newTheme + router.push({ pathname: router.pathname, query }) + return newTheme + } + useEffect(() => { initLocale(lang, locale, updateLang, updateLocale) initDarkMode(updateDarkMode) - initTheme() + checkThemeDOM() }, []) + // 加载默认主题 + // useEffect(() => { + // const queryTheme = getQueryVariable('theme') || theme + // setTheme(queryTheme) + // }, [router]) + + // 加载进度条 useEffect(() => { const handleStart = (url) => { NProgress.start() @@ -43,8 +63,6 @@ export function GlobalContextProvider(props) { NProgress.done() setOnLoading(false) } - const queryTheme = getQueryVariable('theme') || BLOG.THEME - setTheme(queryTheme) router.events.on('routeChangeStart', handleStart) router.events.on('routeChangeError', handleStop) router.events.on('routeChangeComplete', handleStop) @@ -55,22 +73,13 @@ export function GlobalContextProvider(props) { } }, [router]) - // 切换主题 - function switchTheme() { - const currentIndex = THEMES.indexOf(theme) - const newIndex = currentIndex < THEMES.length - 1 ? currentIndex + 1 : 0 - const newTheme = THEMES[newIndex] - const query = router.query - query.theme = newTheme - router.push({ pathname: router.pathname, query }) - return newTheme - } - return ( { +const checkThemeDOM = () => { if (isBrowser) { setTimeout(() => { const elements = document.querySelectorAll('[id^="theme-"]') diff --git a/lib/lang.js b/lib/lang.js index abc51354..d69da08f 100644 --- a/lib/lang.js +++ b/lib/lang.js @@ -65,7 +65,7 @@ export function generateLocaleDict(langString) { */ export function initLocale(lang, locale, changeLang, changeLocale) { if (isBrowser) { - const queryLang = getQueryVariable('lang') || loadLangFromCookies() || window.navigator.language + const queryLang = getQueryVariable('lang') || loadLangFromCookies() let currentLang = lang if (queryLang !== lang) { currentLang = queryLang diff --git a/lib/notion/getNotionConfig.js b/lib/notion/getNotionConfig.js index d5d51f51..d03eeaca 100644 --- a/lib/notion/getNotionConfig.js +++ b/lib/notion/getNotionConfig.js @@ -127,7 +127,7 @@ export async function getConfigMapFromConfigPage(allPages) { // 只导入生效的配置 if (config.enable) { - // console.log('[覆盖代码配置]', config.key) + console.log('[Notion配置]', config.key, config.value) notionConfig[config.key] = config.value } } diff --git a/pages/_app.js b/pages/_app.js index a16513b9..a19933cb 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -11,12 +11,11 @@ import { GlobalContextProvider } from '@/lib/global' import AOS from 'aos' import 'aos/dist/aos.css' // You can also use for styles -import dynamic from 'next/dynamic' import { isBrowser, loadExternalResource } from '@/lib/utils' import BLOG from '@/blog.config' - // 各种扩展插件 动画等 -const ExternalPlugins = dynamic(() => import('@/components/ExternalPlugins')) +import ExternalPlugins from '@/components/ExternalPlugins' +// const ExternalPlugins = dynamic(() => import('@/components/ExternalPlugins')) const MyApp = ({ Component, pageProps }) => { // 自定义样式css和js引入 diff --git a/themes/hexo/components/Footer.js b/themes/hexo/components/Footer.js index 3ccda463..ced637e9 100644 --- a/themes/hexo/components/Footer.js +++ b/themes/hexo/components/Footer.js @@ -26,8 +26,8 @@ const Footer = ({ title }) => { -

    {title} {BLOG.BIO && <>|} {BLOG.BIO}

    -

    Powered by NotionNext {BLOG.VERSION}.


    +

    {title} {siteConfig('BIO') && <>|} {siteConfig('BIO')}

    +

    Powered by NotionNext {siteConfig('VERSION')}.


    ) diff --git a/themes/hexo/components/InfoCard.js b/themes/hexo/components/InfoCard.js index 4f62a120..0676f8f7 100644 --- a/themes/hexo/components/InfoCard.js +++ b/themes/hexo/components/InfoCard.js @@ -26,7 +26,7 @@ export function InfoCard(props) {
    {siteConfig('AUTHOR')}
    -
    {BLOG.BIO}
    +
    {siteConfig('BIO')}
    From a6cc5a11f37c8e2232cf80050dbe300874ad15cd Mon Sep 17 00:00:00 2001 From: "tangly1024.com" Date: Thu, 2 Nov 2023 09:38:19 +0800 Subject: [PATCH 007/138] Notion_Config --- themes/hexo/components/Hero.js | 4 ++-- themes/matery/components/Hero.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/themes/hexo/components/Hero.js b/themes/hexo/components/Hero.js index b6a32b8f..f279f18e 100644 --- a/themes/hexo/components/Hero.js +++ b/themes/hexo/components/Hero.js @@ -4,7 +4,6 @@ import Typed from 'typed.js' import CONFIG from '../config' import NavButtonGroup from './NavButtonGroup' import { useGlobal } from '@/lib/global' -import BLOG from '@/blog.config' import LazyImage from '@/components/LazyImage' import { siteConfig } from '@/lib/config' @@ -21,13 +20,14 @@ const Hero = props => { const scrollToWrapper = () => { window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) } + const GREETING_WORDS = siteConfig('GREETING_WORDS').split(',') useEffect(() => { updateHeaderHeight() if (!typed && window && document.getElementById('typed')) { changeType( new Typed('#typed', { - strings: BLOG.GREETING_WORDS.split(','), + strings: GREETING_WORDS, typeSpeed: 200, backSpeed: 100, backDelay: 400, diff --git a/themes/matery/components/Hero.js b/themes/matery/components/Hero.js index bb8d65ff..5ca4d14d 100644 --- a/themes/matery/components/Hero.js +++ b/themes/matery/components/Hero.js @@ -3,7 +3,6 @@ import { useEffect, useState } from 'react' import Typed from 'typed.js' import CONFIG from '../config' import { useGlobal } from '@/lib/global' -import BLOG from '@/blog.config' import LazyImage from '@/components/LazyImage' import { siteConfig } from '@/lib/config' @@ -17,13 +16,14 @@ const Hero = props => { const [typed, changeType] = useState() const { siteInfo } = props const { locale } = useGlobal() + const GREETING_WORDS = siteConfig('GREETING_WORDS').split(',') useEffect(() => { updateHeaderHeight() if (!typed && window && document.getElementById('typed')) { changeType( new Typed('#typed', { - strings: BLOG.GREETING_WORDS.split(','), + strings: GREETING_WORDS, typeSpeed: 200, backSpeed: 100, backDelay: 400, From a47a7bce427b3d4498228d566ee3f89e0b5b230f Mon Sep 17 00:00:00 2001 From: "tangly1024.com" Date: Thu, 2 Nov 2023 09:41:29 +0800 Subject: [PATCH 008/138] 4.1.0 --- .env.local | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.local b/.env.local index 48b76dc9..0f9ee7c1 100644 --- a/.env.local +++ b/.env.local @@ -1,2 +1,2 @@ # 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables -NEXT_PUBLIC_VERSION=4.0.18 \ No newline at end of file +NEXT_PUBLIC_VERSION=4.1.0 \ No newline at end of file diff --git a/package.json b/package.json index 6164cbb4..ca67780c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "notion-next", - "version": "4.0.18", + "version": "4.1.0", "homepage": "https://github.com/tangly1024/NotionNext.git", "license": "MIT", "repository": { From 5ccf8823a64e2942a0b4a8d8f17545f1d5908c88 Mon Sep 17 00:00:00 2001 From: "tangly1024.com" Date: Thu, 2 Nov 2023 14:10:46 +0800 Subject: [PATCH 009/138] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/DebugPanel.js | 8 ++-- components/ExternalPlugins.js | 42 +++++++++---------- components/ThemeSwitch.js | 8 ++-- lib/config.js | 20 ++++++++- lib/global.js | 10 ++--- lib/lang.js | 3 +- pages/404.js | 2 +- pages/[prefix]/index.js | 4 +- pages/archive/index.js | 2 +- pages/category/[category]/index.js | 2 +- pages/category/[category]/page/[page].js | 2 +- pages/category/index.js | 2 +- pages/index.js | 4 +- pages/page/[page].js | 2 +- pages/search/[keyword]/index.js | 2 +- pages/search/[keyword]/page/[page].js | 2 +- pages/search/index.js | 2 +- pages/tag/[tag]/index.js | 2 +- pages/tag/[tag]/page/[page].js | 2 +- pages/tag/index.js | 2 +- themes/example/components/BlogPostCard.js | 4 +- themes/example/components/Title.js | 2 +- themes/fukasawa/components/AsideLeft.js | 2 +- .../fukasawa/components/MenuItemCollapse.js | 4 +- themes/heo/components/MenuItemCollapse.js | 4 +- themes/matery/components/MenuItemCollapse.js | 4 +- themes/matery/components/MenuItemDrop.js | 4 +- themes/next/components/Logo.js | 2 +- themes/next/components/MenuItemCollapse.js | 4 +- .../nobelium/components/MenuItemCollapse.js | 4 +- themes/nobelium/components/MenuItemDrop.js | 6 +-- themes/simple/components/MenuItemCollapse.js | 4 +- themes/simple/components/MenuItemDrop.js | 4 +- themes/theme.js | 10 ++--- 34 files changed, 101 insertions(+), 80 deletions(-) diff --git a/components/DebugPanel.js b/components/DebugPanel.js index 026b5ee0..29408994 100644 --- a/components/DebugPanel.js +++ b/components/DebugPanel.js @@ -1,9 +1,10 @@ -import BLOG from '@/blog.config' import { useEffect, useState } from 'react' import Select from './Select' import { useGlobal } from '@/lib/global' import { THEMES } from '@/themes/theme' import { useRouter } from 'next/router' +import { siteConfigMap } from '@/lib/config' +import { getQueryParam } from '@/lib/utils' /** * @@ -13,13 +14,14 @@ const DebugPanel = () => { const [show, setShow] = useState(false) const { theme, switchTheme, locale } = useGlobal() const router = useRouter() + const currentTheme = getQueryParam(router.asPath, 'theme') || theme const [siteConfig, updateSiteConfig] = useState({}) // 主题下拉框 const themeOptions = THEMES?.map(t => ({ value: t, text: t })) useEffect(() => { - updateSiteConfig(Object.assign({}, BLOG)) + updateSiteConfig(Object.assign({}, siteConfigMap())) // updateThemeConfig(Object.assign({}, ThemeMap[BLOG.THEME].THEME_CONFIG)) }, []) @@ -71,7 +73,7 @@ const DebugPanel = () => {
    +
    - +
    {/* 切换主题加载时的全屏遮罩 */} diff --git a/lib/config.js b/lib/config.js index ff931386..56ae36ee 100644 --- a/lib/config.js +++ b/lib/config.js @@ -2,6 +2,7 @@ import BLOG from '@/blog.config' import { useGlobal } from './global' +import { deepClone } from './utils' /** * 读取配置 @@ -47,6 +48,23 @@ export const siteConfig = (key) => { if (!val) { val = BLOG[key] } - console.log('实际配置', key, val) + // console.log('实际配置', key, val) + return val +} + +/** + * 读取所有配置 + * 1. 优先读取NotionConfig表 + * 2. 其次读取环境变量 + * 3. 再读取blog.config.js文件 + * @param {*} key + * @returns + */ +export const siteConfigMap = () => { + const val = deepClone(BLOG) + for (const key in val) { + val[key] = siteConfig(key) + console.log('site', key, val[key], siteConfig(key)) + } return val } diff --git a/lib/global.js b/lib/global.js index 548b6d8c..204fc432 100644 --- a/lib/global.js +++ b/lib/global.js @@ -17,12 +17,11 @@ const GlobalContext = createContext() */ export function GlobalContextProvider(props) { const { children, siteInfo, categoryOptions, tagOptions, NOTION_CONFIG } = props - console.log('config', NOTION_CONFIG) const router = useRouter() - const [lang, updateLang] = useState(NOTION_CONFIG.LANG || BLOG.LANG) // 默认语言 - const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG.LANG || BLOG.LANG)) // 默认语言 - const [theme, setTheme] = useState(NOTION_CONFIG.THEME || BLOG.THEME) // 默认博客主题 - const [isDarkMode, updateDarkMode] = useState(NOTION_CONFIG.APPEARANCE || BLOG.APPEARANCE === 'dark') // 默认深色模式 + const [lang, updateLang] = useState(NOTION_CONFIG?.LANG || BLOG.LANG) // 默认语言 + const [locale, updateLocale] = useState(generateLocaleDict(NOTION_CONFIG?.LANG || BLOG.LANG)) // 默认语言 + const [theme, setTheme] = useState(NOTION_CONFIG?.THEME || BLOG.THEME) // 默认博客主题 + const [isDarkMode, updateDarkMode] = useState(NOTION_CONFIG?.APPEARANCE || BLOG.APPEARANCE === 'dark') // 默认深色模式 const [onLoading, setOnLoading] = useState(false) // 抓取文章数据 // 切换主题 @@ -98,7 +97,6 @@ export function GlobalContextProvider(props) { /** * 切换主题时的特殊处理 - * @param {*} setTheme */ const checkThemeDOM = () => { if (isBrowser) { diff --git a/lib/lang.js b/lib/lang.js index d69da08f..15106572 100644 --- a/lib/lang.js +++ b/lib/lang.js @@ -34,7 +34,7 @@ export function generateLocaleDict(langString) { let userLocale // 将语言字符串拆分为语言和地区代码,例如将 "zh-CN" 拆分为 "zh" 和 "CN" - const [language, region] = langString.split(/[-_]/) + const [language, region] = langString?.split(/[-_]/) // 优先匹配语言和地区都匹配的情况 const specificLocale = `${language}-${region}` @@ -70,6 +70,7 @@ export function initLocale(lang, locale, changeLang, changeLocale) { if (queryLang !== lang) { currentLang = queryLang } + console.log('初始化语言', currentLang) changeLang(currentLang) saveLangToCookies(currentLang) diff --git a/pages/404.js b/pages/404.js index 1c9e5c30..ae8eea37 100644 --- a/pages/404.js +++ b/pages/404.js @@ -14,7 +14,7 @@ const NoFound = props => { props = { ...props, meta } // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) return } diff --git a/pages/[prefix]/index.js b/pages/[prefix]/index.js index b350fb18..dfc90fcc 100644 --- a/pages/[prefix]/index.js +++ b/pages/[prefix]/index.js @@ -67,7 +67,7 @@ const Slug = props => { }, [post]) const meta = { - title: post ? `${post?.title} | ${siteConfig('TITLE')}` : `${props?.siteConfig('TITLE') || BLOG.TITLE} | loading`, + title: post ? `${post?.title} | ${siteConfig('TITLE')}` : `${siteConfig('TITLE')} | loading`, description: post?.summary, type: post?.type, slug: post?.slug, @@ -77,7 +77,7 @@ const Slug = props => { } props = { ...props, lock, meta, setLock, validPassword } // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) return } diff --git a/pages/archive/index.js b/pages/archive/index.js index 3726291d..662cb7b6 100644 --- a/pages/archive/index.js +++ b/pages/archive/index.js @@ -13,7 +13,7 @@ const ArchiveIndex = props => { const { locale } = useGlobal() // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) useEffect(() => { if (isBrowser) { diff --git a/pages/category/[category]/index.js b/pages/category/[category]/index.js index a2c30722..9c90f22d 100644 --- a/pages/category/[category]/index.js +++ b/pages/category/[category]/index.js @@ -16,7 +16,7 @@ export default function Category(props) { const { locale } = useGlobal() // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${props.category} | ${locale.COMMON.CATEGORY} | ${ diff --git a/pages/category/[category]/page/[page].js b/pages/category/[category]/page/[page].js index 8a6cccc8..dee16235 100644 --- a/pages/category/[category]/page/[page].js +++ b/pages/category/[category]/page/[page].js @@ -16,7 +16,7 @@ export default function Category(props) { const { siteInfo } = props const { locale } = useGlobal() // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${props.category} | ${locale.COMMON.CATEGORY} | ${ diff --git a/pages/category/index.js b/pages/category/index.js index 8f6d98bc..78e7676e 100644 --- a/pages/category/index.js +++ b/pages/category/index.js @@ -16,7 +16,7 @@ export default function Category(props) { const { siteInfo } = props // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${locale.COMMON.CATEGORY} | ${siteConfig('TITLE')}`, diff --git a/pages/index.js b/pages/index.js index ac72c1b5..6a02cd98 100644 --- a/pages/index.js +++ b/pages/index.js @@ -3,9 +3,9 @@ import { getPostBlocks } from '@/lib/notion' import { getGlobalData } from '@/lib/notion/getNotionData' import { generateRss } from '@/lib/rss' import { generateRobotsTxt } from '@/lib/robots.txt' -import { useRouter } from 'next/router' import { getLayoutByTheme } from '@/themes/theme' import { siteConfig } from '@/lib/config' +import { useRouter } from 'next/router' /** * 首页布局 @@ -14,7 +14,7 @@ import { siteConfig } from '@/lib/config' */ const Index = props => { // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${siteConfig('TITLE')} | ${siteConfig('BIO')}`, diff --git a/pages/page/[page].js b/pages/page/[page].js index 131feb45..6e83c5a6 100644 --- a/pages/page/[page].js +++ b/pages/page/[page].js @@ -14,7 +14,7 @@ const Page = props => { const { siteInfo } = props // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${props?.page} | Page | ${siteConfig('TITLE')}`, diff --git a/pages/search/[keyword]/index.js b/pages/search/[keyword]/index.js index bc2514a1..cd6166ed 100644 --- a/pages/search/[keyword]/index.js +++ b/pages/search/[keyword]/index.js @@ -11,7 +11,7 @@ const Index = props => { const { locale } = useGlobal() // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`, diff --git a/pages/search/[keyword]/page/[page].js b/pages/search/[keyword]/page/[page].js index c5c4aeb8..9de43cc1 100644 --- a/pages/search/[keyword]/page/[page].js +++ b/pages/search/[keyword]/page/[page].js @@ -11,7 +11,7 @@ const Index = props => { const { locale } = useGlobal() // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${keyword || ''}${keyword ? ' | ' : ''}${locale.NAV.SEARCH} | ${siteConfig('TITLE')}`, diff --git a/pages/search/index.js b/pages/search/index.js index 31d09af3..1504fe86 100644 --- a/pages/search/index.js +++ b/pages/search/index.js @@ -15,7 +15,7 @@ const Search = props => { const { locale } = useGlobal() // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const router = useRouter() const keyword = getSearchKey(router) diff --git a/pages/tag/[tag]/index.js b/pages/tag/[tag]/index.js index f12cdc41..8805c686 100644 --- a/pages/tag/[tag]/index.js +++ b/pages/tag/[tag]/index.js @@ -15,7 +15,7 @@ const Tag = props => { const { tag, siteInfo } = props // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${tag} | ${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`, diff --git a/pages/tag/[tag]/page/[page].js b/pages/tag/[tag]/page/[page].js index c708793b..7cbb2e6a 100644 --- a/pages/tag/[tag]/page/[page].js +++ b/pages/tag/[tag]/page/[page].js @@ -10,7 +10,7 @@ const Tag = props => { const { tag, siteInfo } = props // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${tag} | ${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`, diff --git a/pages/tag/index.js b/pages/tag/index.js index cb6db4da..f82613eb 100644 --- a/pages/tag/index.js +++ b/pages/tag/index.js @@ -15,7 +15,7 @@ const TagIndex = props => { const { siteInfo } = props // 根据页面路径加载不同Layout文件 - const Layout = getLayoutByTheme(useRouter()) + const Layout = getLayoutByTheme({ theme: siteConfig('THEME'), router: useRouter() }) const meta = { title: `${locale.COMMON.TAGS} | ${siteConfig('TITLE')}`, diff --git a/themes/example/components/BlogPostCard.js b/themes/example/components/BlogPostCard.js index fb91a307..5e9bdd33 100644 --- a/themes/example/components/BlogPostCard.js +++ b/themes/example/components/BlogPostCard.js @@ -41,9 +41,9 @@ const BlogPostCard = ({ post }) => {
    {/* 图片封面 */} {showPageCover && ( -
    +
    - +
    )} diff --git a/themes/example/components/Title.js b/themes/example/components/Title.js index a1512af2..5c251b0d 100644 --- a/themes/example/components/Title.js +++ b/themes/example/components/Title.js @@ -7,7 +7,7 @@ import { siteConfig } from '@/lib/config' */ export const Title = (props) => { const { post } = props - const title = post?.title || siteConfig('HOME_BANNER_IMAGE') + const title = post?.title || siteConfig('TITLE') const description = post?.description || siteConfig('AUTHOR') return
    diff --git a/themes/fukasawa/components/AsideLeft.js b/themes/fukasawa/components/AsideLeft.js index 131e5f31..f3ca1e98 100644 --- a/themes/fukasawa/components/AsideLeft.js +++ b/themes/fukasawa/components/AsideLeft.js @@ -63,7 +63,7 @@ function AsideLeft(props) {
    - {siteConfig('HOME_BANNER_IMAGE')} + {siteConfig('DESCRIPTION')}
    diff --git a/themes/fukasawa/components/MenuItemCollapse.js b/themes/fukasawa/components/MenuItemCollapse.js index a70e43bb..f854c16f 100644 --- a/themes/fukasawa/components/MenuItemCollapse.js +++ b/themes/fukasawa/components/MenuItemCollapse.js @@ -48,8 +48,8 @@ export const MenuItemCollapse = (props) => { {/* 折叠子菜单 */} {hasSubMenu && - {link.subMenus.map(sLink => { - return
    diff --git a/themes/heo/components/MenuItemCollapse.js b/themes/heo/components/MenuItemCollapse.js index 89b5f012..ab1472c1 100644 --- a/themes/heo/components/MenuItemCollapse.js +++ b/themes/heo/components/MenuItemCollapse.js @@ -42,8 +42,8 @@ export const MenuItemCollapse = ({ link }) => { {/* 折叠子菜单 */} {hasSubMenu && - {link.subMenus.map(sLink => { - return
    + {link.subMenus.map((sLink, index) => { + return
    {link?.icon && } {sLink.title} diff --git a/themes/matery/components/MenuItemCollapse.js b/themes/matery/components/MenuItemCollapse.js index 1bf88c81..dbd9aca1 100644 --- a/themes/matery/components/MenuItemCollapse.js +++ b/themes/matery/components/MenuItemCollapse.js @@ -49,8 +49,8 @@ export const MenuItemCollapse = ({ link }) => { {/* 折叠子菜单 */} {hasSubMenu && - {link.subMenus.map(sLink => { - return
    + {link.subMenus.map((sLink, index) => { + return
    {sLink.title} diff --git a/themes/matery/components/MenuItemDrop.js b/themes/matery/components/MenuItemDrop.js index 1fb3263a..5e666fa4 100644 --- a/themes/matery/components/MenuItemDrop.js +++ b/themes/matery/components/MenuItemDrop.js @@ -28,8 +28,8 @@ export const MenuItemDrop = ({ link }) => { {/* 子菜单 */} {hasSubMenu &&
      - {link.subMenus.map(sLink => { - return
    • + {link.subMenus.map((sLink, index) => { + return
    • {link?.icon &&   }{sLink.title} diff --git a/themes/next/components/Logo.js b/themes/next/components/Logo.js index 9619a454..7306052f 100644 --- a/themes/next/components/Logo.js +++ b/themes/next/components/Logo.js @@ -17,7 +17,7 @@ const Logo = props => { data-aos-delay="300" data-aos-once="true" data-aos-anchor-placement="top-bottom" - className='text-sm text-gray-300 font-light text-center'> {siteConfig('HOME_BANNER_IMAGE')}
    + className='text-sm text-gray-300 font-light text-center'> {siteConfig('DESCRIPTION')}
    ) diff --git a/themes/next/components/MenuItemCollapse.js b/themes/next/components/MenuItemCollapse.js index 20a26521..77fd4282 100644 --- a/themes/next/components/MenuItemCollapse.js +++ b/themes/next/components/MenuItemCollapse.js @@ -39,8 +39,8 @@ export const MenuItemCollapse = (props) => { {/* 折叠子菜单 */} {hasSubMenu && - {link.subMenus.map(sLink => { - return
    diff --git a/themes/nobelium/components/MenuItemCollapse.js b/themes/nobelium/components/MenuItemCollapse.js index d2fd9d87..8f53f622 100644 --- a/themes/nobelium/components/MenuItemCollapse.js +++ b/themes/nobelium/components/MenuItemCollapse.js @@ -43,8 +43,8 @@ export const MenuItemCollapse = (props) => { {/* 折叠子菜单 */} {hasSubMenu && - {link.subMenus.map(sLink => { - return
    + {link.subMenus.map((sLink, index) => { + return
    {sLink.title} diff --git a/themes/nobelium/components/MenuItemDrop.js b/themes/nobelium/components/MenuItemDrop.js index 9ea57461..0386910c 100644 --- a/themes/nobelium/components/MenuItemDrop.js +++ b/themes/nobelium/components/MenuItemDrop.js @@ -30,12 +30,12 @@ export const MenuItemDrop = ({ link }) => { {/* 子菜单 */} {hasSubMenu &&
      - {link.subMenus.map(sLink => { - return
    • + {link.subMenus.map((sLink, index) => { + return
      {link?.icon &&   }{sLink.title} -
    • +
    })} } diff --git a/themes/simple/components/MenuItemCollapse.js b/themes/simple/components/MenuItemCollapse.js index 1d3face5..d335d806 100644 --- a/themes/simple/components/MenuItemCollapse.js +++ b/themes/simple/components/MenuItemCollapse.js @@ -43,8 +43,8 @@ export const MenuItemCollapse = (props) => { {/* 折叠子菜单 */} {hasSubMenu && - {link.subMenus.map(sLink => { - return
    + {link.subMenus.map((sLink, index) => { + return
    {sLink?.icon && }{sLink.title} diff --git a/themes/simple/components/MenuItemDrop.js b/themes/simple/components/MenuItemDrop.js index 9f79e609..81f57d31 100644 --- a/themes/simple/components/MenuItemDrop.js +++ b/themes/simple/components/MenuItemDrop.js @@ -28,8 +28,8 @@ export const MenuItemDrop = ({ link }) => { {/* 子菜单 */} {hasSubMenu &&
      - {link.subMenus.map(sLink => { - return
    • + {link.subMenus.map((sLink, index) => { + return
    • {sLink?.icon &&   }{sLink.title} diff --git a/themes/theme.js b/themes/theme.js index f03f3fe6..d375d661 100644 --- a/themes/theme.js +++ b/themes/theme.js @@ -12,13 +12,13 @@ export const { THEMES = [] } = getConfig().publicRuntimeConfig * @param {*} router * @returns */ -export const getLayoutByTheme = (router) => { - const themeQuery = getQueryParam(router.asPath, 'theme') || BLOG.THEME - const layout = getLayoutNameByPath(router.pathname) +export const getLayoutByTheme = ({ router, theme }) => { + const themeQuery = getQueryParam(router.asPath, 'theme') || theme + const layoutName = getLayoutNameByPath(router.pathname) if (themeQuery !== BLOG.THEME) { - return dynamic(() => import(`@/themes/${themeQuery}`).then(m => m[layout]), { ssr: true }) + return dynamic(() => import(`@/themes/${themeQuery}`).then(m => m[layoutName]), { ssr: true }) } else { - return ThemeComponents[layout] + return ThemeComponents[layoutName] } } From 0f1f4157826fa76403511334e3e19fb657c5a42c Mon Sep 17 00:00:00 2001 From: "tangly1024.com" Date: Thu, 2 Nov 2023 17:01:00 +0800 Subject: [PATCH 010/138] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/CusdisComponent.js | 4 +-- components/DarkModeButton.js | 17 ++------- components/ThemeSwitch.js | 53 +++++++++++++++++++++------- lib/cache/cache_manager.js | 31 ++++++++++------- lib/cache/local_file_cache.js | 10 +++++- lib/config.js | 2 +- lib/global.js | 56 +++++++++++++++++++----------- lib/lang.js | 18 +++++----- lib/lang/en-US.js | 2 +- lib/lang/fr-FR.js | 2 +- lib/lang/ja-JP.js | 2 +- lib/lang/tr-TR.js | 2 +- lib/lang/zh-CN.js | 2 +- lib/lang/zh-HK.js | 1 + lib/lang/zh-TW.js | 2 +- pages/api/cache.js | 15 ++++++++ themes/next/components/MenuList.js | 4 +-- 17 files changed, 141 insertions(+), 82 deletions(-) create mode 100644 pages/api/cache.js diff --git a/components/CusdisComponent.js b/components/CusdisComponent.js index ad780aea..a276eac6 100644 --- a/components/CusdisComponent.js +++ b/components/CusdisComponent.js @@ -5,7 +5,7 @@ import { useEffect } from 'react' import { loadExternalResource } from '@/lib/utils' const CusdisComponent = ({ frontMatter }) => { - const { locale } = useGlobal() + const { lang } = useGlobal() const router = useRouter() const { isDarkMode } = useGlobal() @@ -18,7 +18,7 @@ const CusdisComponent = ({ frontMatter }) => { }, [isDarkMode]) return
      { const { cRef, className } = props - const { isDarkMode, updateDarkMode } = useGlobal() + const { isDarkMode, toggleDarkMode } = useGlobal() /** * 对外暴露方法 @@ -16,22 +15,12 @@ const DarkModeButton = (props) => { useImperativeHandle(cRef, () => { return { handleChangeDarkMode: () => { - handleChangeDarkMode() + toggleDarkMode() } } }) - // 用户手动设置主题 - const handleChangeDarkMode = () => { - const newStatus = !isDarkMode - saveDarkModeToCookies(newStatus) - updateDarkMode(newStatus) - const htmlElement = document.getElementsByTagName('html')[0] - htmlElement.classList?.remove(newStatus ? 'light' : 'dark') - htmlElement.classList?.add(newStatus ? 'dark' : 'light') - } - - return
      + return
      {isDarkMode ? : }
      } diff --git a/components/ThemeSwitch.js b/components/ThemeSwitch.js index a4b13f31..0c74a63e 100644 --- a/components/ThemeSwitch.js +++ b/components/ThemeSwitch.js @@ -5,19 +5,21 @@ import { THEMES } from '@/themes/theme' import { useRouter } from 'next/router' import DarkModeButton from './DarkModeButton' import { getQueryParam } from '@/lib/utils' +import LANGS from '@/lib/lang' /** * * @returns 主题切换 */ const ThemeSwitch = () => { - const { theme } = useGlobal() + const { theme, lang, changeLang, locale, isDarkMode, toggleDarkMode } = useGlobal() const router = useRouter() const currentTheme = getQueryParam(router.asPath, 'theme') || theme + // const currentLang = getQueryParam(router.asPath, 'lang') || lang const [isLoading, setIsLoading] = useState(false) // 修改当前路径url中的 theme 参数 // 例如 http://localhost?theme=hexo 跳转到 http://localhost?theme=newTheme - const onSelectChange = (e) => { + const onThemeSelectChange = (e) => { setIsLoading(true) const newTheme = e.target.value const query = router.query @@ -27,27 +29,52 @@ const ThemeSwitch = () => { }) } + const onLangSelectChange = (e) => { + const newLang = e.target.value + changeLang(newLang) + } + return (<> -
      -
      - -
      - + {Object.keys(LANGS)?.map(t => { + return + })} + +
      +
      + + {/* 主题切换按钮 */} +
      + +
      +
      -
      - {/* 切换主题加载时的全屏遮罩 */} -
      - -
      + + {/* 切换主题加载时的全屏遮罩 */} +
      + +
      ) } diff --git a/lib/cache/cache_manager.js b/lib/cache/cache_manager.js index 8796d3f4..4928bca9 100644 --- a/lib/cache/cache_manager.js +++ b/lib/cache/cache_manager.js @@ -3,15 +3,6 @@ import FileCache from './local_file_cache' import MongoCache from './mongo_db_cache' import BLOG from '@/blog.config' -let api -if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) { - api = MongoCache -} else if (process.env.ENABLE_FILE_CACHE) { - api = FileCache -} else { - api = MemoryCache -} - /** * 为减少频繁接口请求,notion数据将被缓存 * @param {*} key @@ -19,11 +10,11 @@ if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) { */ export async function getDataFromCache(key, force) { if (BLOG.ENABLE_CACHE || force) { - const dataFromCache = await api.getCache(key) + const dataFromCache = await getApi().getCache(key) if (JSON.stringify(dataFromCache) === '[]') { return null } - return api.getCache(key) + return getApi().getCache(key) } else { return null } @@ -33,12 +24,26 @@ export async function setDataToCache(key, data) { if (!data) { return } - await api.setCache(key, data) + await getApi().setCache(key, data) } export async function delCacheData(key) { if (!BLOG.ENABLE_CACHE) { return } - await api.delCache(key) + await getApi().delCache(key) +} + +/** + * 缓存实现类 + * @returns + */ +function getApi() { + if (process.env.MONGO_DB_URL && process.env.MONGO_DB_NAME) { + return MongoCache + } else if (process.env.ENABLE_FILE_CACHE) { + return FileCache + } else { + return MemoryCache + } } diff --git a/lib/cache/local_file_cache.js b/lib/cache/local_file_cache.js index cda7f35a..c411d6e5 100644 --- a/lib/cache/local_file_cache.js +++ b/lib/cache/local_file_cache.js @@ -41,7 +41,7 @@ export async function setCache (key, data) { fs.writeFileSync(jsonFile, JSON.stringify(json)) } -export async function delCache (key, data) { +export async function delCache (key) { const exist = await fs.existsSync(jsonFile) const json = exist ? JSON.parse(await fs.readFileSync(jsonFile)) : {} delete json.key @@ -49,4 +49,12 @@ export async function delCache (key, data) { fs.writeFileSync(jsonFile, JSON.stringify(json)) } +/** + * 清理缓存 + */ +export async function cleanCache() { + const json = {} + fs.writeFileSync(jsonFile, JSON.stringify(json)) +} + export default { getCache, setCache, delCache } diff --git a/lib/config.js b/lib/config.js index 56ae36ee..36a1112d 100644 --- a/lib/config.js +++ b/lib/config.js @@ -64,7 +64,7 @@ export const siteConfigMap = () => { const val = deepClone(BLOG) for (const key in val) { val[key] = siteConfig(key) - console.log('site', key, val[key], siteConfig(key)) + // console.log('site', key, val[key], siteConfig(key)) } return val } diff --git a/lib/global.js b/lib/global.js index 204fc432..eaabd278 100644 --- a/lib/global.js +++ b/lib/global.js @@ -1,11 +1,10 @@ import { generateLocaleDict, initLocale } from './lang' import { createContext, useContext, useEffect, useState } from 'react' import { useRouter } from 'next/router' -import { THEMES, initDarkMode } from '@/themes/theme' +import { THEMES, initDarkMode, saveDarkModeToCookies } from '@/themes/theme' import BLOG from '@/blog.config' import NProgress from 'nprogress' -import { isBrowser } from './utils' const GlobalContext = createContext() @@ -35,17 +34,34 @@ export function GlobalContextProvider(props) { return newTheme } + // 切换深色模式 + const toggleDarkMode = () => { + const newStatus = !isDarkMode + saveDarkModeToCookies(newStatus) + updateDarkMode(newStatus) + const htmlElement = document.getElementsByTagName('html')[0] + htmlElement.classList?.remove(newStatus ? 'light' : 'dark') + htmlElement.classList?.add(newStatus ? 'dark' : 'light') + } + + /** + * 更新语言 + */ + function changeLang(lang) { + if (lang) { + updateLang(lang) + updateLocale(generateLocaleDict(lang)) + } + } + useEffect(() => { - initLocale(lang, locale, updateLang, updateLocale) initDarkMode(updateDarkMode) - checkThemeDOM() + initLocale(lang, locale, updateLang, updateLocale) }, []) - // 加载默认主题 - // useEffect(() => { - // const queryTheme = getQueryVariable('theme') || theme - // setTheme(queryTheme) - // }, [router]) + useEffect(() => { + checkThemeDOM() + }) // 加载进度条 useEffect(() => { @@ -62,6 +78,7 @@ export function GlobalContextProvider(props) { NProgress.done() setOnLoading(false) } + router.events.on('routeChangeStart', handleStart) router.events.on('routeChangeError', handleStop) router.events.on('routeChangeComplete', handleStop) @@ -75,10 +92,11 @@ export function GlobalContextProvider(props) { return ( { - if (isBrowser) { - setTimeout(() => { - const elements = document.querySelectorAll('[id^="theme-"]') - if (elements?.length > 1) { - elements[elements.length - 1].scrollIntoView() - // 删除前面的元素,只保留最后一个元素 - for (let i = 0; i < elements.length - 1; i++) { - elements[i].parentNode.removeChild(elements[i]) - } - } - }, 500) + const elements = document.querySelectorAll('[id^="theme-"]') + if (elements?.length > 1) { + elements[elements.length - 1].scrollIntoView() + // 删除前面的元素,只保留最后一个元素 + for (let i = 0; i < elements.length - 1; i++) { + elements[i].parentNode.removeChild(elements[i]) + } } } diff --git a/lib/lang.js b/lib/lang.js index 15106572..3e2307f7 100644 --- a/lib/lang.js +++ b/lib/lang.js @@ -12,7 +12,7 @@ import { getQueryVariable, isBrowser, mergeDeep } from './utils' * 在这里配置所有支持的语言 * 国家-地区 */ -const lang = { +const LANGS = { 'en-US': enUS, 'zh-CN': zhCN, 'zh-HK': zhHK, @@ -22,7 +22,7 @@ const lang = { 'ja-JP': jaJP } -export default lang +export default LANGS /** * 获取当前语言字典 @@ -30,7 +30,7 @@ export default lang * @returns 不同语言对应字典 */ export function generateLocaleDict(langString) { - const supportedLocales = Object.keys(lang) + const supportedLocales = Object.keys(LANGS) let userLocale // 将语言字符串拆分为语言和地区代码,例如将 "zh-CN" 拆分为 "zh" 和 "CN" @@ -39,24 +39,24 @@ export function generateLocaleDict(langString) { // 优先匹配语言和地区都匹配的情况 const specificLocale = `${language}-${region}` if (supportedLocales.includes(specificLocale)) { - userLocale = lang[specificLocale] + userLocale = LANGS[specificLocale] } // 然后尝试匹配只有语言匹配的情况 if (!userLocale) { const languageOnlyLocales = supportedLocales.filter(locale => locale.startsWith(language)) if (languageOnlyLocales.length > 0) { - userLocale = lang[languageOnlyLocales[0]] + userLocale = LANGS[languageOnlyLocales[0]] } } // 如果还没匹配到,则返回最接近的语言包 if (!userLocale) { const fallbackLocale = supportedLocales.find(locale => locale.startsWith('en')) - userLocale = lang[fallbackLocale] + userLocale = LANGS[fallbackLocale] } - return mergeDeep({}, lang['en-US'], userLocale) + return mergeDeep({}, LANGS['en-US'], userLocale) } /** @@ -67,10 +67,10 @@ export function initLocale(lang, locale, changeLang, changeLocale) { if (isBrowser) { const queryLang = getQueryVariable('lang') || loadLangFromCookies() let currentLang = lang - if (queryLang !== lang) { + if (queryLang && queryLang !== 'undefined' && queryLang !== lang) { currentLang = queryLang } - console.log('初始化语言', currentLang) + changeLang(currentLang) saveLangToCookies(currentLang) diff --git a/lib/lang/en-US.js b/lib/lang/en-US.js index 51e38115..594ddfc0 100644 --- a/lib/lang/en-US.js +++ b/lib/lang/en-US.js @@ -1,5 +1,5 @@ export default { - LOCALE: 'en-US', + LOCALE: 'English', MENU: { WALK_AROUND: 'Walk Around', CATEGORY: 'Category', diff --git a/lib/lang/fr-FR.js b/lib/lang/fr-FR.js index 30089a70..2bc40057 100644 --- a/lib/lang/fr-FR.js +++ b/lib/lang/fr-FR.js @@ -1,5 +1,5 @@ export default { - LOCALE: 'fr-FR', + LOCALE: 'français', NAV: { INDEX: 'Accueil', RSS: 'RSS', diff --git a/lib/lang/ja-JP.js b/lib/lang/ja-JP.js index ab0e81f8..c27eef0b 100644 --- a/lib/lang/ja-JP.js +++ b/lib/lang/ja-JP.js @@ -1,5 +1,5 @@ export default { - LOCALE: 'ja-JP', + LOCALE: '日本語', NAV: { INDEX: 'ホーム', RSS: '購読', diff --git a/lib/lang/tr-TR.js b/lib/lang/tr-TR.js index 227b3fba..5be7a520 100644 --- a/lib/lang/tr-TR.js +++ b/lib/lang/tr-TR.js @@ -1,5 +1,5 @@ export default { - LOCALE: 'tr-TR', + LOCALE: 'Türkçe', NAV: { INDEX: 'Blog', RSS: 'RSS', diff --git a/lib/lang/zh-CN.js b/lib/lang/zh-CN.js index 91a4600a..e29af3d5 100644 --- a/lib/lang/zh-CN.js +++ b/lib/lang/zh-CN.js @@ -1,5 +1,5 @@ export default { - LOCALE: 'zh-CN', + LOCALE: '中文(简体)', MENU: { WALK_AROUND: '随便逛逛', CATEGORY: '博客分类', diff --git a/lib/lang/zh-HK.js b/lib/lang/zh-HK.js index 6a05df55..f434fe6e 100644 --- a/lib/lang/zh-HK.js +++ b/lib/lang/zh-HK.js @@ -1,4 +1,5 @@ export default { + LOCALE: '中文(繁体香港)', NAV: { INDEX: '網誌', RSS: '訂閱', diff --git a/lib/lang/zh-TW.js b/lib/lang/zh-TW.js index 673fe109..de746c08 100644 --- a/lib/lang/zh-TW.js +++ b/lib/lang/zh-TW.js @@ -1,5 +1,5 @@ export default { - LOCALE: 'zh-TW', + LOCALE: '中文(繁体台湾)', NAV: { INDEX: '部落格', RSS: '訂閱', diff --git a/pages/api/cache.js b/pages/api/cache.js new file mode 100644 index 00000000..97738651 --- /dev/null +++ b/pages/api/cache.js @@ -0,0 +1,15 @@ +import { cleanCache } from '@/lib/cache/local_file_cache' + +/** + * 清理缓存 + * @param {*} req + * @param {*} res + */ +export default async function handler(req, res) { + try { + await cleanCache() + res.status(200).json({ status: 'success', message: 'Clean cache successful!' }) + } catch (error) { + res.status(400).json({ status: 'error', message: 'Clean cache failed!', error }) + } +} diff --git a/themes/next/components/MenuList.js b/themes/next/components/MenuList.js index ec03a7bd..c9131929 100644 --- a/themes/next/components/MenuList.js +++ b/themes/next/components/MenuList.js @@ -40,12 +40,12 @@ export const MenuList = (props) => { data-aos-once="true" data-aos-anchor-placement="top-bottom" className='hidden md:block leading-8 text-gray-500 dark:text-gray-400 font-sans'> - {links.map(link => link && link.show && )} + {links.map((link, index) => link && link.show && )} {/* 移动端菜单 */} ) From ce68786c00e0f2a3070da33411600648ab59efbd Mon Sep 17 00:00:00 2001 From: "tangly1024.com" Date: Thu, 2 Nov 2023 17:06:41 +0800 Subject: [PATCH 011/138] log --- components/ThemeSwitch.js | 2 +- lib/notion/getNotionConfig.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ThemeSwitch.js b/components/ThemeSwitch.js index 0c74a63e..d3913d30 100644 --- a/components/ThemeSwitch.js +++ b/components/ThemeSwitch.js @@ -45,7 +45,7 @@ const ThemeSwitch = () => { {/* 翻译按钮 */}
      - +
      { + if (e.key === 'Enter') { + submitPassword() + } + }} + ref={passwordInputRef} // 绑定ref到passwordInputRef变量 + className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg font-light leading-10 bg-gray-100 dark:bg-gray-500'> + +
      +  {locale.COMMON.SUBMIT} +
      +
      +
      +
      +
      +
      +} diff --git a/themes/commerce/components/ArticleRecommend.js b/themes/commerce/components/ArticleRecommend.js new file mode 100644 index 00000000..dc5ef58e --- /dev/null +++ b/themes/commerce/components/ArticleRecommend.js @@ -0,0 +1,60 @@ +import Link from 'next/link' +import CONFIG from '../config' +import BLOG from '@/blog.config' +import { useGlobal } from '@/lib/global' +import LazyImage from '@/components/LazyImage' + +/** + * 关联推荐文章 + * @param {prev,next} param0 + * @returns + */ +export default function ArticleRecommend({ recommendPosts, siteInfo }) { + const { locale } = useGlobal() + + if ( + !CONFIG.ARTICLE_RECOMMEND || + !recommendPosts || + recommendPosts.length === 0 + ) { + return <> + } + + return ( +
      +
      +
      + + {locale.COMMON.RELATE_POSTS} +
      +
      +
      + {recommendPosts.map(post => { + const headerImage = post?.pageCoverThumbnail + ? post.pageCoverThumbnail + : siteInfo?.pageCover + + return ( + ( + +
      +
      +
      + {post.title} +
      +
      + +
      + + ) + ) + })} +
      +
      + ) +} diff --git a/themes/commerce/components/BlogPostArchive.js b/themes/commerce/components/BlogPostArchive.js new file mode 100644 index 00000000..85fe02d1 --- /dev/null +++ b/themes/commerce/components/BlogPostArchive.js @@ -0,0 +1,49 @@ +import React from 'react' +import Link from 'next/link' +import BLOG from '@/blog.config' +/** + * 博客归档列表 + * @param posts 所有文章 + * @param archiveTitle 归档标题 + * @returns {JSX.Element} + * @constructor + */ +const BlogPostArchive = ({ posts = [], archiveTitle }) => { + if (!posts || posts.length === 0) { + return <> + } else { + return ( +
      +
      + {archiveTitle} +
      +
        + {posts?.map(post => ( +
      • +
        + {post.date?.start_date}{' '} +   + + + {post.title} + + +
        +
      • + ))} +
      +
      + ) + } +} + +export default BlogPostArchive diff --git a/themes/commerce/components/BlogPostCard.js b/themes/commerce/components/BlogPostCard.js new file mode 100644 index 00000000..f1febbdd --- /dev/null +++ b/themes/commerce/components/BlogPostCard.js @@ -0,0 +1,49 @@ +import Link from 'next/link' +import React from 'react' +import CONFIG from '../config' +import { BlogPostCardInfo } from './BlogPostCardInfo' +import BLOG from '@/blog.config' +import LazyImage from '@/components/LazyImage' +// import Image from 'next/image' + +const BlogPostCard = ({ index, post, showSummary, siteInfo }) => { + const showPreview = CONFIG.POST_LIST_PREVIEW && post.blockMap + if (post && !post.pageCoverThumbnail && CONFIG.POST_LIST_COVER_DEFAULT) { + post.pageCoverThumbnail = siteInfo?.pageCover + } + const showPageCover = CONFIG.POST_LIST_COVER && post?.pageCoverThumbnail && !showPreview + // const delay = (index % 2) * 200 + + return ( + +
      +
      + + {/* 文字内容 */} + + + {/* 图片封面 */} + {showPageCover && ( +
      + + + +
      + )} + +
      + +
      + + ) +} + +export default BlogPostCard diff --git a/themes/commerce/components/BlogPostCardInfo.js b/themes/commerce/components/BlogPostCardInfo.js new file mode 100644 index 00000000..eb2b5676 --- /dev/null +++ b/themes/commerce/components/BlogPostCardInfo.js @@ -0,0 +1,94 @@ +import NotionPage from '@/components/NotionPage' +import Link from 'next/link' +import TagItemMini from './TagItemMini' +import TwikooCommentCount from '@/components/TwikooCommentCount' +import BLOG from '@/blog.config' +import { formatDateFmt } from '@/lib/formatDate' + +/** + * 博客列表的文字内容 + * @param {*} param0 + * @returns + */ +export const BlogPostCardInfo = ({ post, showPreview, showPageCover, showSummary }) => { + return
      +
      + {/* 标题 */} + + + {post.title} + + + + {/* 分类 */} + { post?.category &&
      + + + + {post.category} + + + + +
      } + + {/* 摘要 */} + {(!showPreview || showSummary) && !post.results && ( +

      + {post.summary} +

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

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

      + )} + {/* 预览 */} + {showPreview && ( +
      + +
      + )} + +
      + +
      + {/* 日期标签 */} +
      + {/* 日期 */} + + + + {post?.publishDay || post.lastEditedDay} + + + +
      +
      + {' '} + {post.tagItems?.map(tag => ( + + ))} +
      +
      +
      +
      +
      +} diff --git a/themes/commerce/components/BlogPostListEmpty.js b/themes/commerce/components/BlogPostListEmpty.js new file mode 100644 index 00000000..5f75c3e7 --- /dev/null +++ b/themes/commerce/components/BlogPostListEmpty.js @@ -0,0 +1,14 @@ +import { useGlobal } from '@/lib/global' + +/** + * 空白博客 列表 + * @returns {JSX.Element} + * @constructor + */ +const BlogPostListEmpty = ({ currentSearch }) => { + const { locale } = useGlobal() + return
      +
      {locale.COMMON.NO_MORE} {(currentSearch &&
      {currentSearch}
      )}
      +
      +} +export default BlogPostListEmpty diff --git a/themes/commerce/components/BlogPostListPage.js b/themes/commerce/components/BlogPostListPage.js new file mode 100644 index 00000000..92008f83 --- /dev/null +++ b/themes/commerce/components/BlogPostListPage.js @@ -0,0 +1,34 @@ +import BlogPostCard from './BlogPostCard' +import PaginationNumber from './PaginationNumber' +import BLOG from '@/blog.config' +import BlogPostListEmpty from './BlogPostListEmpty' + +/** + * 文章列表分页表格 + * @param page 当前页 + * @param posts 所有文章 + * @param tags 所有标签 + * @returns {JSX.Element} + * @constructor + */ +const BlogPostListPage = ({ page = 1, posts = [], postCount, siteInfo }) => { + const totalPage = Math.ceil(postCount / BLOG.POSTS_PER_PAGE) + const showPagination = postCount >= BLOG.POSTS_PER_PAGE + if (!posts || posts.length === 0 || page > totalPage) { + return + } else { + return ( +
      + {/* 文章列表 */} +
      + {posts?.map(post => ( + + ))} +
      + {showPagination && } +
      + ) + } +} + +export default BlogPostListPage diff --git a/themes/commerce/components/BlogPostListScroll.js b/themes/commerce/components/BlogPostListScroll.js new file mode 100644 index 00000000..7646b056 --- /dev/null +++ b/themes/commerce/components/BlogPostListScroll.js @@ -0,0 +1,75 @@ +import BLOG from '@/blog.config' +import BlogPostCard from './BlogPostCard' +import BlogPostListEmpty from './BlogPostListEmpty' +import { useGlobal } from '@/lib/global' +import React from 'react' +import CONFIG from '../config' +import { getListByPage } from '@/lib/utils' + +/** + * 博客列表滚动分页 + * @param posts 所有文章 + * @param tags 所有标签 + * @returns {JSX.Element} + * @constructor + */ +const BlogPostListScroll = ({ posts = [], currentSearch, showSummary = CONFIG.POST_LIST_SUMMARY, siteInfo }) => { + const postsPerPage = BLOG.POSTS_PER_PAGE + const [page, updatePage] = React.useState(1) + const postsToShow = getListByPage(posts, page, postsPerPage) + + let hasMore = false + if (posts) { + const totalCount = posts.length + hasMore = page * postsPerPage < totalCount + } + + const handleGetMore = () => { + if (!hasMore) return + updatePage(page + 1) + } + + // 监听滚动自动分页加载 + 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(() => { + window.addEventListener('scroll', scrollTrigger) + return () => { + window.removeEventListener('scroll', scrollTrigger) + } + }) + + const targetRef = React.useRef(null) + const { locale } = useGlobal() + + if (!postsToShow || postsToShow.length === 0) { + return + } else { + return
      + + {/* 文章列表 */} +
      + {postsToShow.map(post => ( + + ))} +
      + +
      +
      { handleGetMore() }} + className='w-full my-4 py-4 text-center cursor-pointer rounded-xl dark:text-gray-200' + > {hasMore ? locale.COMMON.MORE : `${locale.COMMON.NO_MORE}`}
      +
      +
      + } +} + +export default BlogPostListScroll diff --git a/themes/commerce/components/Card.js b/themes/commerce/components/Card.js new file mode 100644 index 00000000..c2db0e49 --- /dev/null +++ b/themes/commerce/components/Card.js @@ -0,0 +1,9 @@ +const Card = ({ children, headerSlot, className }) => { + return
      + <>{headerSlot} +
      + {children} +
      +
      +} +export default Card diff --git a/themes/commerce/components/Catalog.js b/themes/commerce/components/Catalog.js new file mode 100644 index 00000000..5cf1e4ad --- /dev/null +++ b/themes/commerce/components/Catalog.js @@ -0,0 +1,95 @@ +import { useCallback, useEffect, useRef, useState } from 'react' +import throttle from 'lodash.throttle' +import { uuidToId } from 'notion-utils' +import Progress from './Progress' +import { useGlobal } from '@/lib/global' + +/** + * 目录导航组件 + * @param toc + * @returns {JSX.Element} + * @constructor + */ +const Catalog = ({ toc }) => { + const { locale } = useGlobal() + // 监听滚动事件 + useEffect(() => { + window.addEventListener('scroll', actionSectionScrollSpy) + actionSectionScrollSpy() + return () => { + window.removeEventListener('scroll', actionSectionScrollSpy) + } + }, []) + + // 目录自动滚动 + const tRef = useRef(null) + const tocIds = [] + + // 同步选中目录事件 + const [activeSection, setActiveSection] = useState(null) + + const throttleMs = 200 + const actionSectionScrollSpy = useCallback(throttle(() => { + const sections = document.getElementsByClassName('notion-h') + let prevBBox = null + let currentSectionId = activeSection + for (let i = 0; i < sections.length; ++i) { + const section = sections[i] + if (!section || !(section instanceof Element)) continue + if (!currentSectionId) { + currentSectionId = section.getAttribute('data-id') + } + const bbox = section.getBoundingClientRect() + const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0 + const offset = Math.max(150, prevHeight / 4) + // GetBoundingClientRect returns values relative to viewport + if (bbox.top - offset < 0) { + currentSectionId = section.getAttribute('data-id') + prevBBox = bbox + continue + } + // No need to continue loop, if last element has been detected + break + } + setActiveSection(currentSectionId) + const index = tocIds.indexOf(currentSectionId) || 0 + tRef?.current?.scrollTo({ top: 28 * index, behavior: 'smooth' }) + }, throttleMs)) + + // 无目录就直接返回空 + if (!toc || toc.length < 1) { + return <> + } + + return
      +
      {locale.COMMON.TABLE_OF_CONTENTS}
      +
      + +
      +
      + + +
      +
      +} + +export default Catalog diff --git a/themes/commerce/components/CategoryGroup.js b/themes/commerce/components/CategoryGroup.js new file mode 100644 index 00000000..3e8ff7d0 --- /dev/null +++ b/themes/commerce/components/CategoryGroup.js @@ -0,0 +1,31 @@ +import Link from 'next/link' +import React from 'react' + +const CategoryGroup = ({ currentCategory, categories }) => { + if (!categories) { + return <> + } + return <> +
      + {categories.map(category => { + const selected = currentCategory === category.name + return ( + + +
      {category.name}({category.count})
      + + + ); + })} +
      + ; +} + +export default CategoryGroup diff --git a/themes/commerce/components/FloatDarkModeButton.js b/themes/commerce/components/FloatDarkModeButton.js new file mode 100644 index 00000000..f693d1f0 --- /dev/null +++ b/themes/commerce/components/FloatDarkModeButton.js @@ -0,0 +1,31 @@ +import { useGlobal } from '@/lib/global' +import { saveDarkModeToCookies } from '@/themes/theme' +import CONFIG from '../config' + +export default function FloatDarkModeButton () { + const { isDarkMode, updateDarkMode } = useGlobal() + + if (!CONFIG.WIDGET_DARK_MODE) { + return <> + } + + // 用户手动设置主题 + const handleChangeDarkMode = () => { + const newStatus = !isDarkMode + saveDarkModeToCookies(newStatus) + updateDarkMode(newStatus) + const htmlElement = document.getElementsByTagName('html')[0] + htmlElement.classList?.remove(newStatus ? 'light' : 'dark') + htmlElement.classList?.add(newStatus ? 'dark' : 'light') + } + + return ( +
      + +
      + ) +} diff --git a/themes/commerce/components/Footer.js b/themes/commerce/components/Footer.js new file mode 100644 index 00000000..ced637e9 --- /dev/null +++ b/themes/commerce/components/Footer.js @@ -0,0 +1,36 @@ +import React from 'react' +import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' + +const Footer = ({ title }) => { + const d = new Date() + const currentYear = d.getFullYear() + const copyrightDate = (function() { + if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) { + return BLOG.SINCE + '-' + currentYear + } + return currentYear + })() + + return ( + + ) +} + +export default Footer diff --git a/themes/commerce/components/Hero.js b/themes/commerce/components/Hero.js new file mode 100644 index 00000000..ef50465f --- /dev/null +++ b/themes/commerce/components/Hero.js @@ -0,0 +1,68 @@ +// import Image from 'next/image' +import { useEffect, useState } from 'react' +import Typed from 'typed.js' +import CONFIG from '../config' +import NavButtonGroup from './NavButtonGroup' +import { useGlobal } from '@/lib/global' +import LazyImage from '@/components/LazyImage' +import { siteConfig } from '@/lib/config' + +let wrapperTop = 0 + +/** + * 顶部全屏大图 + * @returns + */ +const Hero = props => { + const [typed, changeType] = useState() + const { siteInfo } = props + const { locale } = useGlobal() + const scrollToWrapper = () => { + window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) + } + const GREETING_WORDS = siteConfig('GREETING_WORDS').split(',') + useEffect(() => { + updateHeaderHeight() + + if (!typed && window && document.getElementById('typed')) { + changeType( + new Typed('#typed', { + strings: GREETING_WORDS, + typeSpeed: 200, + backSpeed: 100, + backDelay: 400, + showCursor: true, + smartBackspace: true + }) + ) + } + + window.addEventListener('resize', updateHeaderHeight) + return () => { + window.removeEventListener('resize', updateHeaderHeight) + } + }) + + function updateHeaderHeight() { + requestAnimationFrame(() => { + const wrapperElement = document.getElementById('wrapper') + wrapperTop = wrapperElement?.offsetTop + }) + } + + return ( + + ) +} + +export default Hero diff --git a/themes/commerce/components/HexoRecentComments.js b/themes/commerce/components/HexoRecentComments.js new file mode 100644 index 00000000..2ebf00c8 --- /dev/null +++ b/themes/commerce/components/HexoRecentComments.js @@ -0,0 +1,47 @@ +import React from 'react' +import BLOG from '@/blog.config' +import Card from '@/themes/hexo/components/Card' +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { RecentComments } from '@waline/client' + +/** + * @see https://waline.js.org/guide/get-started.html + * @param {*} props + * @returns + */ +const HexoRecentComments = (props) => { + const [comments, updateComments] = React.useState([]) + const { locale } = useGlobal() + const [onLoading, changeLoading] = React.useState(true) + React.useEffect(() => { + RecentComments({ + serverURL: BLOG.COMMENT_WALINE_SERVER_URL, + count: 5 + }).then(({ comments }) => { + changeLoading(false) + updateComments(comments) + }) + }, []) + + return ( + +
      + + {locale.COMMON.RECENT_COMMENTS} +
      + + {onLoading &&
      Loading...
      } + {!onLoading && comments && comments.length === 0 &&
      No Comments
      } + {!onLoading && comments && comments.length > 0 && comments.map((comment) =>
      +
      +
      + --{comment.nick} +
      +
      )} + + + ) +} + +export default HexoRecentComments diff --git a/themes/commerce/components/InfoCard.js b/themes/commerce/components/InfoCard.js new file mode 100644 index 00000000..6b33034e --- /dev/null +++ b/themes/commerce/components/InfoCard.js @@ -0,0 +1,33 @@ +import { useRouter } from 'next/router' +import Card from './Card' +import SocialButton from './SocialButton' +import MenuGroupCard from './MenuGroupCard' +import LazyImage from '@/components/LazyImage' +import { siteConfig } from '@/lib/config' + +/** + * 社交信息卡 + * @param {*} props + * @returns + */ +export function InfoCard(props) { + const { className, siteInfo } = props + const router = useRouter() + return ( + +
      { + router.push('/') + }} + > + {/* eslint-disable-next-line @next/next/no-img-element */} + +
      +
      {siteConfig('AUTHOR')}
      +
      {siteConfig('BIO')}
      + + +
      + ) +} diff --git a/themes/commerce/components/JumpToCommentButton.js b/themes/commerce/components/JumpToCommentButton.js new file mode 100644 index 00000000..fb007712 --- /dev/null +++ b/themes/commerce/components/JumpToCommentButton.js @@ -0,0 +1,29 @@ +import React from 'react' +import CONFIG from '../config' + +/** + * 跳转到评论区 + * @returns {JSX.Element} + * @constructor + */ +const JumpToCommentButton = () => { + if (!CONFIG.WIDGET_TO_COMMENT) { + return <> + } + + function navToComment() { + if (document.getElementById('comment')) { + window.scrollTo({ top: document.getElementById('comment').offsetTop, behavior: 'smooth' }) + } + // 兼容性不好 + // const commentElement = document.getElementById('comment') + // if (commentElement) { + // commentElement?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' }) + } + + return (
      + +
      ) +} + +export default JumpToCommentButton diff --git a/themes/commerce/components/JumpToTopButton.js b/themes/commerce/components/JumpToTopButton.js new file mode 100644 index 00000000..77313f46 --- /dev/null +++ b/themes/commerce/components/JumpToTopButton.js @@ -0,0 +1,25 @@ +import { useGlobal } from '@/lib/global' +import React from 'react' +import CONFIG from '../config' + +/** + * 跳转到网页顶部 + * 当屏幕下滑500像素后会出现该控件 + * @param targetRef 关联高度的目标html标签 + * @param showPercent 是否显示百分比 + * @returns {JSX.Element} + * @constructor + */ +const JumpToTopButton = ({ showPercent = true, percent }) => { + const { locale } = useGlobal() + + if (!CONFIG.WIDGET_TO_TOP) { + return <> + } + return (
      window.scrollTo({ top: 0, behavior: 'smooth' })} > +
      + {showPercent && (
      {percent}
      )} +
      ) +} + +export default JumpToTopButton diff --git a/themes/commerce/components/LatestPostsGroup.js b/themes/commerce/components/LatestPostsGroup.js new file mode 100644 index 00000000..a6d9ace2 --- /dev/null +++ b/themes/commerce/components/LatestPostsGroup.js @@ -0,0 +1,64 @@ +import BLOG from '@/blog.config' +import LazyImage from '@/components/LazyImage' +import { useGlobal } from '@/lib/global' +// import Image from 'next/image' +import Link from 'next/link' +import { useRouter } from 'next/router' + +/** + * 最新文章列表 + * @param posts 所有文章数据 + * @param sliceCount 截取展示的数量 默认6 + * @constructor + */ +const LatestPostsGroup = ({ latestPosts, siteInfo }) => { + // 获取当前路径 + const currentPath = useRouter().asPath + const { locale } = useGlobal() + + if (!latestPosts) { + return <> + } + + return <> +
      +
      + + {locale.COMMON.LATEST_POSTS} +
      +
      + {latestPosts.map(post => { + const selected = currentPath === `${BLOG.SUB_PATH}/${post.slug}` + + const headerImage = post?.pageCoverThumbnail ? post.pageCoverThumbnail : siteInfo?.pageCover + + return ( + ( + +
      + +
      +
      +
      +
      {post.title}
      +
      {post.lastEditedDay}
      +
      +
      + + ) + ) + })} + +} +export default LatestPostsGroup diff --git a/themes/commerce/components/LoadingCover.js b/themes/commerce/components/LoadingCover.js new file mode 100644 index 00000000..c6418fad --- /dev/null +++ b/themes/commerce/components/LoadingCover.js @@ -0,0 +1,8 @@ +export default function LoadingCover () { + return (
      +
      + +
      +
      + ) +} diff --git a/themes/commerce/components/LogoBar.js b/themes/commerce/components/LogoBar.js new file mode 100644 index 00000000..925f1063 --- /dev/null +++ b/themes/commerce/components/LogoBar.js @@ -0,0 +1,20 @@ +import Link from 'next/link' +import { siteConfig } from '@/lib/config' +import LazyImage from '@/components/LazyImage'; + +/** + * Logo图标 + * @param {*} props + * @returns + */ +export default function LogoBar (props) { + const { siteInfo } = props + return ( +
      + + + +
      {siteConfig('TITLE')}
      +
      + ); +} diff --git a/themes/commerce/components/MenuBarMobile.js b/themes/commerce/components/MenuBarMobile.js new file mode 100644 index 00000000..d21fbfe5 --- /dev/null +++ b/themes/commerce/components/MenuBarMobile.js @@ -0,0 +1,39 @@ +import React from 'react' +import { useGlobal } from '@/lib/global' +import CONFIG from '../config' +import BLOG from '@/blog.config' +import { MenuItemCollapse } from './MenuItemCollapse' + +export const MenuBarMobile = (props) => { + const { customMenu, customNav } = props + const { locale } = useGlobal() + + let links = [ + // { name: locale.NAV.INDEX, to: '/' || '/', show: true }, + { name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG.MENU_CATEGORY }, + { name: locale.COMMON.TAGS, to: '/tag', show: CONFIG.MENU_TAG }, + { name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG.MENU_ARCHIVE } + // { name: locale.NAV.SEARCH, to: '/search', show: CONFIG.MENU_SEARCH } + ] + + if (customNav) { + links = links.concat(customNav) + } + + // 如果 开启自定义菜单,则不再使用 Page生成菜单。 + if (BLOG.CUSTOM_MENU) { + links = customMenu + } + + if (!links || links.length === 0) { + return null + } + + return ( + + ) +} diff --git a/themes/commerce/components/MenuGroupCard.js b/themes/commerce/components/MenuGroupCard.js new file mode 100644 index 00000000..3b5ac595 --- /dev/null +++ b/themes/commerce/components/MenuGroupCard.js @@ -0,0 +1,51 @@ +import React from 'react' +import Link from 'next/link' +import { useGlobal } from '@/lib/global' +import CONFIG from '../config' + +const MenuGroupCard = (props) => { + const { postCount, categoryOptions, tagOptions } = props + const { locale } = useGlobal() + const archiveSlot =
      {postCount}
      + const categorySlot =
      {categoryOptions?.length}
      + const tagSlot =
      {tagOptions?.length}
      + + const links = [ + { name: locale.COMMON.ARTICLE, to: '/archive', slot: archiveSlot, show: CONFIG.MENU_ARCHIVE }, + { name: locale.COMMON.CATEGORY, to: '/category', slot: categorySlot, show: CONFIG.MENU_CATEGORY }, + { name: locale.COMMON.TAGS, to: '/tag', slot: tagSlot, show: CONFIG.MENU_TAG } + ] + + for (let i = 0; i < links.length; i++) { + if (links[i].id !== i) { + links[i].id = i + } + } + + return ( + + ) +} +export default MenuGroupCard diff --git a/themes/commerce/components/MenuItemCollapse.js b/themes/commerce/components/MenuItemCollapse.js new file mode 100644 index 00000000..ccd9d6bc --- /dev/null +++ b/themes/commerce/components/MenuItemCollapse.js @@ -0,0 +1,54 @@ +import Collapse from '@/components/Collapse' +import Link from 'next/link' +import { useState } from 'react' + +/** + * 折叠菜单 + * @param {*} param0 + * @returns + */ +export const MenuItemCollapse = ({ link }) => { + const [show, changeShow] = useState(false) + const hasSubMenu = link?.subMenus?.length > 0 + + const [isOpen, changeIsOpen] = useState(false) + + const toggleShow = () => { + changeShow(!show) + } + + const toggleOpenSubMenu = () => { + changeIsOpen(!isOpen) + } + + if (!link || !link.show) { + return null + } + + return <> +
      + {!hasSubMenu && + {link?.icon && }{link?.name} + } + {hasSubMenu &&
      + {link?.icon && }{link?.name} + +
      } +
      + + {/* 折叠子菜单 */} + {hasSubMenu && + {link.subMenus.map((sLink, index) => { + return
      + + {link?.icon && } {sLink.title} + +
      + })} +
      } + +} diff --git a/themes/commerce/components/MenuItemDrop.js b/themes/commerce/components/MenuItemDrop.js new file mode 100644 index 00000000..05a6b4e6 --- /dev/null +++ b/themes/commerce/components/MenuItemDrop.js @@ -0,0 +1,43 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' +import { useState } from 'react' + +export const MenuItemDrop = ({ link }) => { + const [show, changeShow] = useState(false) + const hasSubMenu = link?.subMenus?.length > 0 + const selected = useRouter().asPath === link.to + + if (!link || !link.show) { + return null + } + + return
      changeShow(true)} onMouseOut={() => changeShow(false)} className='h-full'> + + {!hasSubMenu && + + {link?.icon && } {link?.name} + {/* {hasSubMenu && } */} + } + + {hasSubMenu && <> +
      + {link?.icon && } {link?.name} + {/* */} +
      + } + + {/* 子菜单 */} + {hasSubMenu &&
        + {link.subMenus.map((sLink, index) => { + return
      • + + {link?.icon &&   }{sLink.title} + +
      • + })} +
      } + +
      +} diff --git a/themes/commerce/components/MenuListSide.js b/themes/commerce/components/MenuListSide.js new file mode 100644 index 00000000..789d07d2 --- /dev/null +++ b/themes/commerce/components/MenuListSide.js @@ -0,0 +1,43 @@ +import React from 'react' +import { useGlobal } from '@/lib/global' +import BLOG from '@/blog.config' +import { MenuItemCollapse } from './MenuItemCollapse' +import CONFIG from '../config' + +export const MenuListSide = (props) => { + const { customNav, customMenu } = props + const { locale } = useGlobal() + + let links = [ + { icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG.MENU_ARCHIVE }, + { icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG.MENU_SEARCH }, + { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG.MENU_CATEGORY }, + { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG.MENU_TAG } + ] + + if (customNav) { + links = customNav.concat(links) + } + + for (let i = 0; i < links.length; i++) { + if (links[i].id !== i) { + links[i].id = i + } + } + + // 如果 开启自定义菜单,则覆盖Page生成的菜单 + if (BLOG.CUSTOM_MENU) { + links = customMenu + } + + if (!links || links.length === 0) { + return null + } + + return ( + + ) +} diff --git a/themes/commerce/components/MenuListTop.js b/themes/commerce/components/MenuListTop.js new file mode 100644 index 00000000..4a07b562 --- /dev/null +++ b/themes/commerce/components/MenuListTop.js @@ -0,0 +1,43 @@ +import React from 'react' +import { useGlobal } from '@/lib/global' +import CONFIG from '../config' +import { MenuItemDrop } from './MenuItemDrop' +import { siteConfig } from '@/lib/config' + +export const MenuListTop = (props) => { + const { customNav, customMenu } = props + const { locale } = useGlobal() + + let links = [ + { id: 1, icon: 'fa-solid fa-house', name: locale.NAV.INDEX, to: '/', show: CONFIG.MENU_INDEX }, + { id: 2, icon: 'fas fa-search', name: locale.NAV.SEARCH, to: '/search', show: CONFIG.MENU_SEARCH }, + { id: 3, icon: 'fas fa-archive', name: locale.NAV.ARCHIVE, to: '/archive', show: CONFIG.MENU_ARCHIVE } + // { icon: 'fas fa-folder', name: locale.COMMON.CATEGORY, to: '/category', show: CONFIG.MENU_CATEGORY }, + // { icon: 'fas fa-tag', name: locale.COMMON.TAGS, to: '/tag', show: CONFIG.MENU_TAG } + ] + + if (customNav) { + links = links.concat(customNav) + } + + for (let i = 0; i < links.length; i++) { + if (links[i].id !== i) { + links[i].id = i + } + } + + // 如果 开启自定义菜单,则覆盖Page生成的菜单 + if (siteConfig('CUSTOM_MENU')) { + links = customMenu + } + + if (!links || links.length === 0) { + return null + } + + return (<> + + ) +} diff --git a/themes/commerce/components/NavButtonGroup.js b/themes/commerce/components/NavButtonGroup.js new file mode 100644 index 00000000..38709213 --- /dev/null +++ b/themes/commerce/components/NavButtonGroup.js @@ -0,0 +1,33 @@ + +import React from 'react' +import Link from 'next/link' + +/** + * 首页导航大按钮组件 + * @param {*} props + * @returns + */ +const NavButtonGroup = (props) => { + const { categoryOptions } = props + if (!categoryOptions || categoryOptions.length === 0) { + return <> + } + + return ( + + ) +} +export default NavButtonGroup diff --git a/themes/commerce/components/PaginationNumber.js b/themes/commerce/components/PaginationNumber.js new file mode 100644 index 00000000..d041fe6c --- /dev/null +++ b/themes/commerce/components/PaginationNumber.js @@ -0,0 +1,107 @@ +import Link from 'next/link' +import { useRouter } from 'next/router' + +/** + * 数字翻页插件 + * @param page 当前页码 + * @param showNext 是否有下一页 + * @returns {JSX.Element} + * @constructor + */ +const PaginationNumber = ({ page, totalPage }) => { + const router = useRouter() + const currentPage = +page + const showNext = page < totalPage + const pagePrefix = router.asPath.split('?')[0].replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '') + const pages = generatePages(pagePrefix, page, currentPage, totalPage) + + return ( +
      + {/* 上一页 */} + + + + + + + {pages} + + {/* 下一页 */} + + + + + +
      + ) +} + +function getPageElement(page, currentPage, pagePrefix) { + return ( + ( + + {page} + + ) + ) +} + +function generatePages(pagePrefix, page, currentPage, totalPage) { + const pages = [] + const groupCount = 7 // 最多显示页签数 + if (totalPage <= groupCount) { + for (let i = 1; i <= totalPage; i++) { + pages.push(getPageElement(i, page, pagePrefix)) + } + } else { + pages.push(getPageElement(1, page, pagePrefix)) + const dynamicGroupCount = groupCount - 2 + let startPage = currentPage - 2 + if (startPage <= 1) { + startPage = 2 + } + if (startPage + dynamicGroupCount > totalPage) { + startPage = totalPage - dynamicGroupCount + } + if (startPage > 2) { + pages.push(
      ...
      ) + } + + for (let i = 0; i < dynamicGroupCount; i++) { + if (startPage + i < totalPage) { + pages.push(getPageElement(startPage + i, page, pagePrefix)) + } + } + + if (startPage + dynamicGroupCount < totalPage) { + pages.push(
      ...
      ) + } + + pages.push(getPageElement(totalPage, page, pagePrefix)) + } + return pages +} +export default PaginationNumber diff --git a/themes/commerce/components/PostHeader.js b/themes/commerce/components/PostHeader.js new file mode 100644 index 00000000..acafaf0f --- /dev/null +++ b/themes/commerce/components/PostHeader.js @@ -0,0 +1,79 @@ +import Link from 'next/link' +import TagItemMini from './TagItemMini' +import { useGlobal } from '@/lib/global' +import BLOG from '@/blog.config' +import NotionIcon from '@/components/NotionIcon' +import LazyImage from '@/components/LazyImage' +import { formatDateFmt } from '@/lib/formatDate' + +export default function PostHeader({ post, siteInfo }) { + const { locale } = useGlobal() + + if (!post) { + return <> + } + const headerImage = post?.pageCover ? post.pageCover : siteInfo?.pageCover + + return ( + + ) +} diff --git a/themes/commerce/components/Progress.js b/themes/commerce/components/Progress.js new file mode 100644 index 00000000..8cb9fe0e --- /dev/null +++ b/themes/commerce/components/Progress.js @@ -0,0 +1,44 @@ +import React, { useEffect, useState } from 'react' +import { isBrowser } from '@/lib/utils' + +/** + * 顶部页面阅读进度条 + * @returns {JSX.Element} + * @constructor + */ +const Progress = ({ targetRef, showPercent = true }) => { + const currentRef = targetRef?.current || targetRef + const [percent, changePercent] = useState(0) + const scrollListener = () => { + const target = currentRef || (isBrowser && document.getElementById('article-wrapper')) + 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) + }, []) + + return ( +
      +
      + {showPercent && ( +
      {percent}%
      + )} +
      +
      + ) +} + +export default Progress diff --git a/themes/commerce/components/RightFloatArea.js b/themes/commerce/components/RightFloatArea.js new file mode 100644 index 00000000..06fef20e --- /dev/null +++ b/themes/commerce/components/RightFloatArea.js @@ -0,0 +1,42 @@ +import throttle from 'lodash.throttle' +import { useCallback, useEffect, useState } from 'react' +import FloatDarkModeButton from './FloatDarkModeButton' +import JumpToTopButton from './JumpToTopButton' + +/** + * 悬浮在右下角的按钮,当页面向下滚动100px时会出现 + * @param {*} param0 + * @returns + */ +export default function RightFloatArea({ floatSlot }) { + const [showFloatButton, switchShow] = useState(false) + const scrollListener = useCallback(throttle(() => { + const targetRef = document.getElementById('wrapper') + const clientHeight = targetRef?.clientHeight + const scrollY = window.pageYOffset + const fullHeight = clientHeight - window.outerHeight + let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0)) + if (per > 100) per = 100 + const shouldShow = scrollY > 100 && per > 0 + + // 右下角显示悬浮按钮 + if (shouldShow !== showFloatButton) { + switchShow(shouldShow) + } + }, 200)) + + useEffect(() => { + document.addEventListener('scroll', scrollListener) + return () => document.removeEventListener('scroll', scrollListener) + }, []) + + return ( +
      +
      + + {floatSlot} + +
      +
      + ) +} diff --git a/themes/commerce/components/SearchDrawer.js b/themes/commerce/components/SearchDrawer.js new file mode 100644 index 00000000..c7ec88a7 --- /dev/null +++ b/themes/commerce/components/SearchDrawer.js @@ -0,0 +1,36 @@ +import { Router } from 'next/router' +import { useImperativeHandle, useRef } from 'react' +import SearchInput from './SearchInput' +const SearchDrawer = ({ cRef, slot }) => { + const searchDrawer = useRef() + const searchInputRef = useRef() + useImperativeHandle(cRef, () => { + return { + show: () => { + searchDrawer?.current?.classList?.remove('hidden') + searchInputRef?.current?.focus() + } + } + }) + const hidden = () => { + searchDrawer?.current?.classList?.add('hidden') + } + Router.events.on('routeChangeComplete', (...args) => { + hidden() + }) + return ( +
      +
      +
      + + {slot} +
      +
      + + {/* 背景蒙版 */} +
      +
      + ) +} + +export default SearchDrawer diff --git a/themes/commerce/components/SearchInput.js b/themes/commerce/components/SearchInput.js new file mode 100644 index 00000000..462c58b3 --- /dev/null +++ b/themes/commerce/components/SearchInput.js @@ -0,0 +1,106 @@ +import { useRouter } from 'next/router' +import { useImperativeHandle, useRef, useState } from 'react' +import { useGlobal } from '@/lib/global' +let lock = false + +const SearchInput = props => { + const { currentSearch, cRef, className } = props + const [onLoading, setLoadingState] = useState(false) + const router = useRouter() + const searchInputRef = useRef() + const { locale } = useGlobal() + useImperativeHandle(cRef, () => { + return { + focus: () => { + searchInputRef?.current?.focus() + } + } + }) + + const handleSearch = () => { + const key = searchInputRef.current.value + if (key && key !== '') { + setLoadingState(true) + router.push({ pathname: '/search/' + key }).then(r => { + setLoadingState(false) + }) + // location.href = '/search/' + key + } else { + router.push({ pathname: '/' }).then(r => {}) + } + } + const handleKeyUp = e => { + if (e.keyCode === 13) { + // 回车 + handleSearch(searchInputRef.current.value) + } else if (e.keyCode === 27) { + // ESC + cleanSearch() + } + } + const cleanSearch = () => { + searchInputRef.current.value = '' + } + + const [showClean, setShowClean] = useState(false) + const updateSearchKey = val => { + if (lock) { + return + } + searchInputRef.current.value = val + + if (val) { + setShowClean(true) + } else { + setShowClean(false) + } + } + function lockSearchInput () { + lock = true + } + + function unLockSearchInput () { + lock = false + } + + return ( +
      + updateSearchKey(e.target.value)} + defaultValue={currentSearch || ''} + /> + +
      + +
      + + {showClean && ( +
      + +
      + )} +
      + ) +} + +export default SearchInput diff --git a/themes/commerce/components/SearchNav.js b/themes/commerce/components/SearchNav.js new file mode 100644 index 00000000..fc393c1b --- /dev/null +++ b/themes/commerce/components/SearchNav.js @@ -0,0 +1,70 @@ +import { useGlobal } from '@/lib/global' +import Link from 'next/link' +import { useEffect, useRef } from 'react' +import Card from './Card' +import SearchInput from './SearchInput' +import TagItemMini from './TagItemMini' + +/** + * 搜索页面的导航 + * @param {*} props + * @returns + */ +export default function SearchNav(props) { + const { tagOptions, categoryOptions } = props + const cRef = useRef(null) + const { locale } = useGlobal() + useEffect(() => { + // 自动聚焦到搜索框 + cRef?.current?.focus() + }, []) + + return <> +
      + + {/* 分类 */} + +
      + + {locale.COMMON.CATEGORY}: +
      +
      + {categoryOptions?.map(category => { + return ( + +
      + + {category.name}({category.count}) +
      + + ) + })} +
      +
      + {/* 标签 */} + +
      + + {locale.COMMON.TAGS}: +
      +
      + {tagOptions?.map(tag => { + return ( +
      + +
      + ) + })} +
      +
      +
      + +} diff --git a/themes/commerce/components/SideBar.js b/themes/commerce/components/SideBar.js new file mode 100644 index 00000000..913ce0a9 --- /dev/null +++ b/themes/commerce/components/SideBar.js @@ -0,0 +1,33 @@ +import { siteConfig } from '@/lib/config' +import LazyImage from '@/components/LazyImage' +import { useRouter } from 'next/router' +import MenuGroupCard from './MenuGroupCard' +import { MenuListSide } from './MenuListSide' + +/** + * 侧边抽屉 + * @param tags + * @param currentTag + * @returns {JSX.Element} + * @constructor + */ +const SideBar = (props) => { + const { siteInfo } = props + const router = useRouter() + return ( + + ) +} + +export default SideBar diff --git a/themes/commerce/components/SideBarDrawer.js b/themes/commerce/components/SideBarDrawer.js new file mode 100644 index 00000000..f457d4a3 --- /dev/null +++ b/themes/commerce/components/SideBarDrawer.js @@ -0,0 +1,52 @@ +import { useRouter } from 'next/router' +import { useEffect } from 'react' + +/** + * 侧边栏抽屉面板,可以从侧面拉出 + * @returns {JSX.Element} + * @constructor + */ +const SideBarDrawer = ({ children, isOpen, onOpen, onClose, className }) => { + const router = useRouter() + useEffect(() => { + const sideBarDrawerRouteListener = () => { + switchSideDrawerVisible(false) + } + router.events.on('routeChangeComplete', sideBarDrawerRouteListener) + return () => { + router.events.off('routeChangeComplete', sideBarDrawerRouteListener) + } + }, [router.events]) + + // 点击按钮更改侧边抽屉状态 + const switchSideDrawerVisible = (showStatus) => { + if (showStatus) { + onOpen && onOpen() + } else { + onClose && onClose() + } + const sideBarDrawer = window.document.getElementById('sidebar-drawer') + const sideBarDrawerBackground = window.document.getElementById('sidebar-drawer-background') + + if (showStatus) { + sideBarDrawer?.classList.replace('-mr-72', 'mr-0') + sideBarDrawerBackground?.classList.replace('hidden', 'block') + } else { + sideBarDrawer?.classList.replace('mr-0', '-mr-72') + sideBarDrawerBackground?.classList.replace('block', 'hidden') + } + } + + return + + } export default Footer diff --git a/themes/commerce/components/Hero.js b/themes/commerce/components/Hero.js index ef50465f..34ddbd57 100644 --- a/themes/commerce/components/Hero.js +++ b/themes/commerce/components/Hero.js @@ -1,65 +1,24 @@ // import Image from 'next/image' -import { useEffect, useState } from 'react' -import Typed from 'typed.js' import CONFIG from '../config' -import NavButtonGroup from './NavButtonGroup' -import { useGlobal } from '@/lib/global' import LazyImage from '@/components/LazyImage' -import { siteConfig } from '@/lib/config' - -let wrapperTop = 0 /** * 顶部全屏大图 * @returns */ const Hero = props => { - const [typed, changeType] = useState() const { siteInfo } = props - const { locale } = useGlobal() - const scrollToWrapper = () => { - window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) - } - const GREETING_WORDS = siteConfig('GREETING_WORDS').split(',') - useEffect(() => { - updateHeaderHeight() - - if (!typed && window && document.getElementById('typed')) { - changeType( - new Typed('#typed', { - strings: GREETING_WORDS, - typeSpeed: 200, - backSpeed: 100, - backDelay: 400, - showCursor: true, - smartBackspace: true - }) - ) - } - - window.addEventListener('resize', updateHeaderHeight) - return () => { - window.removeEventListener('resize', updateHeaderHeight) - } - }) - - function updateHeaderHeight() { - requestAnimationFrame(() => { - const wrapperElement = document.getElementById('wrapper') - wrapperTop = wrapperElement?.offsetTop - }) - } return ( ) diff --git a/themes/commerce/components/LogoBar.js b/themes/commerce/components/LogoBar.js index 925f1063..de5a5bfe 100644 --- a/themes/commerce/components/LogoBar.js +++ b/themes/commerce/components/LogoBar.js @@ -1,5 +1,5 @@ import Link from 'next/link' -import { siteConfig } from '@/lib/config' +// import { siteConfig } from '@/lib/config' import LazyImage from '@/components/LazyImage'; /** @@ -14,7 +14,7 @@ export default function LogoBar (props) { -
      {siteConfig('TITLE')}
      + {/*
      {siteConfig('TITLE')}
      */}
      ); } diff --git a/themes/commerce/components/MenuItemDrop.js b/themes/commerce/components/MenuItemDrop.js index 05a6b4e6..cec4b854 100644 --- a/themes/commerce/components/MenuItemDrop.js +++ b/themes/commerce/components/MenuItemDrop.js @@ -16,14 +16,14 @@ export const MenuItemDrop = ({ link }) => { {!hasSubMenu && - {link?.icon && } {link?.name} + className={`${selected && 'border-b-2 border-[#D2232A]'} h-full flex space-x-1 whitespace-nowrap items-center font-sans menu-link pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1`}> + {link?.icon && }
      {link?.name}
      {/* {hasSubMenu && } */} } {hasSubMenu && <> -
      - {link?.icon && } {link?.name} +
      + {link?.icon && }
      {link?.name}
      {/* */}
      } diff --git a/themes/commerce/components/MenuListSide.js b/themes/commerce/components/MenuListSide.js index 789d07d2..a60ba77e 100644 --- a/themes/commerce/components/MenuListSide.js +++ b/themes/commerce/components/MenuListSide.js @@ -18,7 +18,7 @@ export const MenuListSide = (props) => { if (customNav) { links = customNav.concat(links) } - + for (let i = 0; i < links.length; i++) { if (links[i].id !== i) { links[i].id = i diff --git a/themes/commerce/components/TopNavBar.js b/themes/commerce/components/TopNavBar.js index 00a232fa..c2bf6065 100644 --- a/themes/commerce/components/TopNavBar.js +++ b/themes/commerce/components/TopNavBar.js @@ -17,7 +17,6 @@ export default function TopNavBar(props) { const { customNav, customMenu } = props const [isOpen, changeShow] = useState(false) const collapseRef = useRef(null) - let windowTop = 0 const { locale } = useGlobal() @@ -43,19 +42,17 @@ export default function TopNavBar(props) { } }, []) - const throttleMs = 200 + const throttleMs = 150 const scrollTrigger = throttle(() => { const scrollS = window.scrollY - const nav = document.querySelector('#navbar') + const nav = document.querySelector('#top-navbar') - const narrowNav = scrollS >= windowTop || scrollS > 200 + const narrowNav = scrollS > 50 if (narrowNav) { - nav && nav.classList.replace('h-24', 'h-16') - windowTop = scrollS + nav && nav.classList.replace('h-24', 'h-14') } else { - nav && nav.classList.replace('h-16', 'h-24') - windowTop = scrollS + nav && nav.classList.replace('h-14', 'h-24') } }, throttleMs) @@ -68,34 +65,32 @@ export default function TopNavBar(props) { return null } - return ( -
      + return
      - {/* 移动端折叠菜单 */} - -
      - collapseRef.current?.updateCollapseHeight(param)} /> -
      -
      + {/* 移动端折叠菜单 */} + +
      + collapseRef.current?.updateCollapseHeight(param)} /> +
      +
      - {/* 导航栏菜单 */} -