diff --git a/.env.local b/.env.local index cfc0b2cd..2dacdbef 100644 --- a/.env.local +++ b/.env.local @@ -1,15 +1,15 @@ # 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables -NEXT_PUBLIC_VERSION=4.2.0 +NEXT_PUBLIC_VERSION=4.3.1 # 可在此添加环境变量,去掉最左边的(# )注释即可 # Notion页面ID,必须 -# NOTION_PAGE_ID= +# NOTION_PAGE_ID=097e5f674880459d8e1b4407758dc4fb # 非必须 # NEXT_PUBLIC_PSEUDO_STATIC= # NEXT_PUBLIC_REVALIDATE_SECOND= -# NEXT_PUBLIC_THEME= +# NEXT_PUBLIC_THEME=matery # NEXT_PUBLIC_THEME_SWITCH= # NEXT_PUBLIC_LANG= # NEXT_PUBLIC_APPEARANCE= diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 4ea399c0..c063ca6c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -27,6 +27,7 @@ assignees: tangly1024 - 【必填】NotionNext版本 [例如. 4.0.18] - 【必填】主题 [例如. hexo] +- 【必填】部署方案 [例如. vercel] - 【可选】操作系统: [例如. iOS, Android, macOS, windows] - 【可选】浏览器 [例如. chrome, safari, firefox] diff --git a/.github/workflows/docker-ghcr.yaml b/.github/workflows/docker-ghcr.yaml index 86036f2f..db58d8d8 100644 --- a/.github/workflows/docker-ghcr.yaml +++ b/.github/workflows/docker-ghcr.yaml @@ -30,6 +30,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + # Login against a Docker registry except on PR # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} @@ -54,6 +57,7 @@ jobs: uses: docker/build-push-action@v2 with: context: . + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 1befa8c4..42744c4b 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,6 @@ yarn-error.log* # dev /data.json -/yarn.lock /pnpm-lock.yaml .idea .vscode @@ -45,4 +44,9 @@ yarn-error.log* # sitemap /public/robots.txt /public/sitemap.xml -/public/rss/* \ No newline at end of file +/public/rss/* + + +# yarn +package-lock.json +# yarn.lock diff --git a/blog.config.js b/blog.config.js index 50e16921..746829cb 100644 --- a/blog.config.js +++ b/blog.config.js @@ -37,14 +37,17 @@ const BLOG = { BLOG_FAVICON: process.env.NEXT_PUBLIC_FAVICON || '/favicon.ico', // blog favicon 配置, 默认使用 /public/favicon.ico,支持在线图片,如 https://img.imesong.com/favicon.png + IMAGE_COMPRESS_WIDTH: process.env.NEXT_PUBLIC_IMAGE_COMPRESS_WIDTH || 800, // 图片压缩宽度默认值,作用于博客封面和文章内容 越小加载图片越快 + IMAGE_ZOOM_IN_WIDTH: process.env.NEXT_PUBLIC_IMAGE_ZOOM_IN_WIDTH || 1200, // 文章图片点击放大后的画质宽度,不代表在网页中的实际展示宽度 RANDOM_IMAGE_URL: process.env.NEXT_PUBLIC_RANDOM_IMAGE_URL || '', // 随机图片API,如果未配置下面的关键字,主页封面,头像,文章封面图都会被替换为随机图片 RANDOM_IMAGE_REPLACE_TEXT: process.env.NEXT_PUBLIC_RANDOM_IMAGE_NOT_REPLACE_TEXT || 'images.unsplash.com', // 触发替换图片的 url 关键字(多个支持用英文逗号分开),只有图片地址中包含此关键字才会替换为上方随机图片url // eg: images.unsplash.com(notion图床的所有图片都会替换),如果你在 notion 里已经添加了一个随机图片 url,恰巧那个服务跑路或者挂掉,想一键切换所有配图可以将该 url 配置在这里 // 默认下会将你上传到 notion的主页封面图和头像也给替换,建议将主页封面图和头像放在其他图床,在 notion 里配置 link 即可。 // START ************网站字体***************** - - FONT_STYLE: process.env.NEXT_PUBLIC_FONT_STYLE || 'font-sans', // ['font-serif','font-sans'] 两种可选,分别是衬线和无衬线: 参考 https://www.jianshu.com/p/55e410bd2115 + // ['font-serif','font-sans'] 两种可选,分别是衬线和无衬线: 参考 https://www.jianshu.com/p/55e410bd2115 + // 后面空格隔开的font-light的字体粗细,留空是默认粗细;参考 https://www.tailwindcss.cn/docs/font-weight + FONT_STYLE: process.env.NEXT_PUBLIC_FONT_STYLE || 'font-sans font-light', // 字体CSS 例如 https://npm.elemecdn.com/lxgw-wenkai-webfont@1.6.0/style.css FONT_URL: [ // 'https://npm.elemecdn.com/lxgw-wenkai-webfont@1.6.0/style.css', @@ -88,9 +91,33 @@ const BLOG = { FONT_AWESOME: process.env.NEXT_PUBLIC_FONT_AWESOME_PATH || 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css', // font-awesome 字体图标地址; 可选 /css/all.min.css , https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/font-awesome/6.0.0/css/all.min.css // END ************网站字体***************** + + // 路径和组件映射,不同路径分别展示主题的什么组件 + LAYOUT_MAPPINGS: { + '-1': 'LayoutBase', + '/': 'LayoutIndex', + '/archive': 'LayoutArchive', + '/page/[page]': 'LayoutPostList', + '/category/[category]': 'LayoutPostList', + '/category/[category]/page/[page]': 'LayoutPostList', + '/tag/[tag]': 'LayoutPostList', + '/tag/[tag]/page/[page]': 'LayoutPostList', + '/search': 'LayoutSearch', + '/search/[keyword]': 'LayoutSearch', + '/search/[keyword]/page/[page]': 'LayoutSearch', + '/404': 'Layout404', + '/tag': 'LayoutTagIndex', + '/category': 'LayoutCategoryIndex', + '/[prefix]': 'LayoutSlug', + '/[prefix]/[slug]': 'LayoutSlug', + '/[prefix]/[slug]/[...suffix]': 'LayoutSlug', + '/signin': 'LayoutSignIn', + '/signup': 'LayoutSignUp' + }, + CAN_COPY: process.env.NEXT_PUBLIC_CAN_COPY || true, // 是否允许复制页面内容 默认允许,如果设置为false、则全栈禁止复制内容。 CUSTOM_RIGHT_CLICK_CONTEXT_MENU: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU || true, // 自定义右键菜单,覆盖系统菜单 - CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH || true, + CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH || true, // 右键菜单是否允许切换主题 // 自定义外部脚本,外部样式 CUSTOM_EXTERNAL_JS: [''], // e.g. ['http://xx.com/script.js','http://xx.com/script.js'] @@ -151,6 +178,8 @@ const BLOG = { POSTS_PER_PAGE: 12, // post counts per page POSTS_SORT_BY: process.env.NEXT_PUBLIC_POST_SORT_BY || 'notion', // 排序方式 'date'按时间,'notion'由notion控制 + POST_WAITING_TIME_FOR_404: process.env.NEXT_PUBLIC_POST_WAITING_TIME_FOR_404 || '8', // 文章加载超时时间,单位秒;超时后跳转到404页面 + ALGOLIA_APP_ID: process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || null, // 在这里查看 https://dashboard.algolia.com/account/api-keys/ ALGOLIA_ADMIN_APP_KEY: process.env.ALGOLIA_ADMIN_APP_KEY || null, // 管理后台的KEY,不要暴露在代码中,在这里查看 https://dashboard.algolia.com/account/api-keys/ ALGOLIA_SEARCH_ONLY_APP_KEY: process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_ONLY_APP_KEY || null, // 客户端搜索用的KEY @@ -244,7 +273,7 @@ const BLOG = { // ********挂件组件相关******** // ----> 评论互动 可同时开启多个支持 WALINE VALINE GISCUS CUSDIS UTTERRANCES GITALK - COMMENT_HIDE_SINGLE_TAB: process.env.NEXT_PUBLIC_COMMENT_HIDE_SINGLE_TAB || false, //Whether hide the tab when there's no tabs. 只有一个评论组件时是否隐藏切换组件的标签页 + COMMENT_HIDE_SINGLE_TAB: process.env.NEXT_PUBLIC_COMMENT_HIDE_SINGLE_TAB || false, // Whether hide the tab when there's no tabs. 只有一个评论组件时是否隐藏切换组件的标签页 // artalk 评论插件 COMMENT_ARTALK_SERVER: process.env.NEXT_PUBLIC_COMMENT_ARTALK_SERVER || '', // ArtalkServert后端地址 https://artalk.js.org/guide/deploy.html @@ -352,6 +381,9 @@ const BLOG = { SEO_BAIDU_SITE_VERIFICATION: process.env.NEXT_PUBLIC_SEO_BAIDU_SITE_VERIFICATION || '', // Remove the value or replace it with your own google site verification code + // 微软 Clarity 站点分析 + CLARITY_ID: process.env.NEXT_PUBLIC_CLARITY_ID || null, // 只需要复制Clarity脚本中的ID部分,ID是一个十位的英文数字组合 + // <---- 站点统计 // START---->营收相关 @@ -417,7 +449,7 @@ const BLOG = { // 开发相关 NOTION_ACCESS_TOKEN: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public DEBUG: process.env.NEXT_PUBLIC_DEBUG || false, // 是否显示调试按钮 - ENABLE_CACHE: process.env.ENABLE_CACHE || process.env.npm_lifecycle_event === 'build', // 缓存在开发调试和打包过程中选择性开启,正式部署开启此功能意义不大。 + ENABLE_CACHE: process.env.ENABLE_CACHE || process.env.npm_lifecycle_event === 'build' || process.env.npm_lifecycle_event === 'export', // 在打包过程中默认开启缓存,开发或运行时开启此功能意义不大。 isProd: process.env.VERCEL_ENV === 'production', // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) isProd: process.env.VERCEL_ENV === 'production' // distinguish between development and production environment (ref: https://vercel.com/docs/environment-variables#system-environment-variables) BUNDLE_ANALYZER: process.env.ANALYZE === 'true' || false, // 是否展示编译依赖内容与大小 VERSION: process.env.NEXT_PUBLIC_VERSION // 版本号 diff --git a/components/AlgoliaSearchModal.js b/components/AlgoliaSearchModal.js index 7c6bfdcb..f7bcdeb9 100644 --- a/components/AlgoliaSearchModal.js +++ b/components/AlgoliaSearchModal.js @@ -1,10 +1,11 @@ -import { useState, useImperativeHandle, useRef } from 'react' +import { useState, useImperativeHandle, useRef, useEffect } from 'react' import algoliasearch from 'algoliasearch' import replaceSearchResult from '@/components/Mark' import Link from 'next/link' import { useGlobal } from '@/lib/global' import throttle from 'lodash/throttle' import { siteConfig } from '@/lib/config' +import { useHotkeys } from 'react-hotkeys-hook'; /** * 结合 Algolia 实现的弹出式搜索框 @@ -19,7 +20,62 @@ export default function AlgoliaSearchModal({ cRef }) { const [totalPage, setTotalPage] = useState(0) const [totalHit, setTotalHit] = useState(0) const [useTime, setUseTime] = useState(0) + const inputRef = useRef(null) + const [activeIndex, setActiveIndex] = useState(0) + useHotkeys('ctrl+k', (e) => { + e.preventDefault() + setIsModalOpen(true) + }) + // 方向键调整选中 + useHotkeys('down', (e) => { + e.preventDefault() + if (activeIndex < searchResults.length - 1) { + setActiveIndex(activeIndex + 1) + } + }, { enableOnFormTags: true }) + useHotkeys('up', (e) => { + e.preventDefault() + if (activeIndex > 0) { + setActiveIndex(activeIndex - 1) + } + }, { enableOnFormTags: true }) + // esc关闭 + useHotkeys('esc', (e) => { + e.preventDefault() + setIsModalOpen(false) + }, { enableOnFormTags: true }) + // 跳转Search结果 + const onJumpSearchResult = () => { + if (searchResults.length > 0) { + window.location.href = `${siteConfig('SUB_PATH', '')}/${searchResults[activeIndex].slug}` + } + } + // enter跳转 + useHotkeys('enter', (e) => { + if (searchResults.length > 0) { + onJumpSearchResult(index) + } + }, { enableOnFormTags: true }) + const resetSearch = () => { + setActiveIndex(0) + setKeyword('') + setSearchResults([]) + setUseTime(0) + setTotalPage(0) + setTotalHit(0) + if (inputRef.current) inputRef.current.value = '' + } + + useEffect(() => { + if (isModalOpen) { + setTimeout(() => { + inputRef.current?.focus() + }, 100) + } else { + resetSearch() + } + }, [isModalOpen]) /** * 对外暴露方法 */ @@ -45,6 +101,7 @@ export default function AlgoliaSearchModal({ cRef }) { setUseTime(0) setTotalPage(0) setTotalHit(0) + setActiveIndex(0) if (!query || query === '') { return } @@ -56,7 +113,6 @@ export default function AlgoliaSearchModal({ cRef }) { setTotalPage(nbPages) setTotalHit(nbHits) setSearchResults(hits) - const doms = document.getElementById('search-wrapper').getElementsByClassName('replace') setTimeout(() => { @@ -65,29 +121,44 @@ export default function AlgoliaSearchModal({ cRef }) { search: query, target: { element: 'span', - className: 'text-blue-600 border-b border-dashed' + className: 'font-bold border-b border-dashed' } }) - }, 150) + }, 200) // 延时高亮 } catch (error) { console.error('Algolia search error:', error) } } - const throttledHandleSearch = useRef(throttle(handleSearch, 300)) // 设置节流延迟时间 + // 定义节流函数,确保在用户停止输入一段时间后才会调用处理搜索的方法 + const throttledHandleInputChange = useRef(throttle((query, page = 0) => { + handleSearch(query, page); + }, 1000)); + + // 用于存储搜索延迟的计时器 + const searchTimer = useRef(null); // 修改input的onChange事件处理函数 const handleInputChange = (e) => { - const query = e.target.value - throttledHandleSearch.current(query, 0) - } + const query = e.target.value; + + // 如果已经有计时器在等待搜索,先清除之前的计时器 + if (searchTimer.current) { + clearTimeout(searchTimer.current); + } + + // 设置新的计时器,在用户停止输入一段时间后触发搜索 + searchTimer.current = setTimeout(() => { + throttledHandleInputChange.current(query); + }, 800); + }; /** * 切换页码 * @param {*} page */ const switchPage = (page) => { - throttledHandleSearch.current(keyword, page) + throttledHandleInputChange.current(keyword, page) } /** @@ -100,22 +171,19 @@ export default function AlgoliaSearchModal({ cRef }) { if (!siteConfig('ALGOLIA_APP_ID')) { return <> } - return (
{/* 模态框 */}
-
搜索
+
搜索
handleInputChange(e)} className="text-black dark:text-gray-200 bg-gray-50 dark:bg-gray-600 outline-blue-500 w-full px-4 my-2 py-1 mb-4 border rounded-md" + ref={inputRef} /> {/* 标签组 */}
- + { + searchResults.length === 0 && keyword && ( +
+

无法找到相关结果 + "{keyword}"

+
+ ) + }
    - {searchResults.map(result => ( -
  • + {searchResults.map((result, index) => ( +
  • setActiveIndex(index)} + onClick={() => onJumpSearchResult(index)} + className={`cursor-pointer replace my-2 p-2 duration-100 ${activeIndex === index ? 'bg-blue-600 dark:bg-yellow-600' : ''}`}> {result.title}
  • ))}
- -
- {totalHit > 0 && ( -
- 共搜索到 {totalHit} 条结果,用时 {useTime} 毫秒 -
- )} -
-
- - Algolia 提供搜索服务 - {' '} +
+
+ {totalHit > 0 && ( +

+ 共搜索到 {totalHit} 条结果,用时 {useTime} 毫秒 +

+ )} +
+
+ + Algolia 提供搜索服务 + +
+
{/* 遮罩 */} @@ -176,26 +258,26 @@ export default function AlgoliaSearchModal({ cRef }) { /** * 标签组 */ -function TagGroups(props) { +function TagGroups() { const { tagOptions } = useGlobal() // 获取tagOptions数组前十个 const firstTenTags = tagOptions?.slice(0, 10) return
- { - firstTenTags?.map((tag, index) => { - return -
-
{tag.name}
{tag.count ? {tag.count} : <>} -
+ { + firstTenTags?.map((tag, index) => { + return +
+
{tag.name}
{tag.count ? {tag.count} : <>} +
- - }) - } -
+ + }) + } +
} /** @@ -207,24 +289,23 @@ function Pagination(props) { if (totalPage <= 0) { return <> } - const pagesElement = [] + return ( +
+ {Array.from({ length: totalPage }, (_, i) => { + const classNames = page === i + ? 'font-bold text-white bg-blue-600 dark:bg-yellow-600 rounded' + : 'hover:text-blue-600 hover:font-bold' - for (let i = 0; i < totalPage; i++) { - const selected = page === i - pagesElement.push(getPageElement(i, selected, switchPage)) - } - return
- {pagesElement.map(p => p)} -
-} - -/** - * 获取分页按钮 - * @param {*} i - * @param {*} selected - */ -function getPageElement(i, selected, switchPage) { - return
switchPage(i)} className={`${selected ? 'font-bold text-white bg-blue-600 rounded' : 'hover:text-blue-600 hover:font-bold'} text-center cursor-pointer w-6 h-6 `}> - {i + 1} -
+ return ( +
switchPage(i)} + className={`text-center cursor-pointer w-6 h-6 ${classNames}`} + key={i} + > + {i + 1} +
+ ) + })} +
+ ) } diff --git a/components/Busuanzi.js b/components/Busuanzi.js index fec41e5f..cbeb54da 100644 --- a/components/Busuanzi.js +++ b/components/Busuanzi.js @@ -2,14 +2,14 @@ import busuanzi from '@/lib/busuanzi' import { useRouter } from 'next/router' import { useGlobal } from '@/lib/global' // import { useRouter } from 'next/router' -import React from 'react' +import { useEffect } from 'react' let path = '' export default function Busuanzi () { const { theme } = useGlobal() - const Router = useRouter() - Router.events.on('routeChangeComplete', (url, option) => { + const router = useRouter() + router.events.on('routeChangeComplete', (url, option) => { if (url !== path) { path = url busuanzi.fetch() @@ -17,7 +17,7 @@ export default function Busuanzi () { }) // 更换主题时更新 - React.useEffect(() => { + useEffect(() => { if (theme) { busuanzi.fetch() } diff --git a/components/Collapse.js b/components/Collapse.js index 4e26f7fd..69ef59eb 100644 --- a/components/Collapse.js +++ b/components/Collapse.js @@ -1,4 +1,4 @@ -import React, { useEffect, useImperativeHandle } from 'react' +import { useEffect, useImperativeHandle, useRef } from 'react' /** * 折叠面板组件,支持水平折叠、垂直折叠 @@ -7,7 +7,7 @@ import React, { useEffect, useImperativeHandle } from 'react' */ const Collapse = props => { const { collapseRef } = props - const ref = React.useRef(null) + const ref = useRef(null) const type = props.type || 'vertical' useImperativeHandle(collapseRef, () => { @@ -17,8 +17,10 @@ const Collapse = props => { * @param {*} param0 */ updateCollapseHeight: ({ height, increase }) => { - ref.current.style.height = ref.current.scrollHeight - ref.current.style.height = 'auto' + if (props.isOpen) { + ref.current.style.height = ref.current.scrollHeight + ref.current.style.height = 'auto' + } } } }) diff --git a/components/CommonHead.js b/components/CommonHead.js deleted file mode 100644 index 1aeb36dd..00000000 --- a/components/CommonHead.js +++ /dev/null @@ -1,73 +0,0 @@ -import { siteConfig } from '@/lib/config' -import Head from 'next/head' - -const CommonHead = ({ meta, children }) => { - 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 || siteConfig('TITLE') - const description = meta?.description || siteConfig('DESCRIPTION') - const type = meta?.type || 'website' - 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} - - - - - {siteConfig('SEO_GOOGLE_SITE_VERIFICATION') && ( - - )} - {siteConfig('SEO_BAIDU_SITE_VERIFICATION') && ()} - - - - - - - - - - - - - - {siteConfig('COMMENT_WEBMENTION_ENABLE') && ( - <> - - - - )} - - {siteConfig('COMMENT_WEBMENTION_ENABLE') && siteConfig('COMMENT_WEBMENTION_AUTH') !== '' && ( - - )} - - {JSON.parse(siteConfig('ANALYTICS_BUSUANZI_ENABLE')) && } - {meta?.type === 'Post' && ( - <> - - - - - - )} - {children} - - ) -} - -export default CommonHead diff --git a/components/CustomContextMenu.js b/components/CustomContextMenu.js index bf396d39..4398c331 100644 --- a/components/CustomContextMenu.js +++ b/components/CustomContextMenu.js @@ -2,7 +2,7 @@ import Link from 'next/link' import { useRouter } from 'next/router' import { useEffect, useState, useRef, useLayoutEffect } from 'react' import { useGlobal } from '@/lib/global' -import { saveDarkModeToCookies, THEMES } from '@/themes/theme' +import { saveDarkModeToLocalStorage, THEMES } from '@/themes/theme' import useWindowSize from '@/hooks/useWindowSize' import { siteConfig } from '@/lib/config' @@ -93,16 +93,36 @@ export default function CustomContextMenu(props) { /** * 切换主题 */ - function handeChangeTheme() { + function handleChangeTheme() { const randomTheme = THEMES[Math.floor(Math.random() * THEMES.length)] // 从THEMES数组中 随机取一个主题 const query = router.query query.theme = randomTheme router.push({ pathname: router.pathname, query }) } + /** + * 复制内容 + */ + function handleCopy() { + const selectedText = document.getSelection().toString(); + if (selectedText) { + const tempInput = document.createElement('input'); + tempInput.value = selectedText; + document.body.appendChild(tempInput); + tempInput.select(); + document.execCommand('copy'); + document.body.removeChild(tempInput); + // alert("Text copied: " + selectedText); + } else { + // alert("Please select some text first."); + } + + setShow(false) + } + function handleChangeDarkMode() { const newStatus = !isDarkMode - saveDarkModeToCookies(newStatus) + saveDarkModeToLocalStorage(newStatus) updateDarkMode(newStatus) const htmlElement = document.getElementsByTagName('html')[0] htmlElement.classList?.remove(newStatus ? 'light' : 'dark') @@ -153,21 +173,29 @@ export default function CustomContextMenu(props) { {/* 功能按钮 */}
-
+ {siteConfig('CAN_COPY') && ( +
+ +
{locale.MENU.COPY}
+
+ )} + +
-
{locale.MENU.COPY_URL}
+
{locale.MENU.SHARE_URL}
{isDarkMode ? : }
{isDarkMode ? locale.MENU.LIGHT_MODE : locale.MENU.DARK_MODE}
- {siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH') && ( -
+ {siteConfig('CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH') && ( +
{locale.MENU.THEME_SWITCH}
- )} + )} +
diff --git a/components/ExternalPlugins.js b/components/ExternalPlugins.js index 77433f97..78da3094 100644 --- a/components/ExternalPlugins.js +++ b/components/ExternalPlugins.js @@ -5,7 +5,7 @@ import WebWhiz from './Webwhiz' import TianLiGPT from './TianliGPT' import { GlobalStyle } from './GlobalStyle' -import { CUSTOM_EXTERNAL_CSS, CUSTOM_EXTERNAL_JS, IMG_SHADOW } from '@/blog.config' +import { CUSTOM_EXTERNAL_CSS, CUSTOM_EXTERNAL_JS } from '@/blog.config' import { isBrowser, loadExternalResource } from '@/lib/utils' const TwikooCommentCounter = dynamic(() => import('@/components/TwikooCommentCounter'), { ssr: false }) @@ -78,6 +78,8 @@ const ExternalPlugin = (props) => { const DIFY_CHATBOT_ENABLED = siteConfig('DIFY_CHATBOT_ENABLED') const TIANLI_KEY = siteConfig('TianliGPT_KEY') const GLOBAL_JS = siteConfig('GLOBAL_JS') + const CLARITY_ID = siteConfig('CLARITY_ID') + const IMG_SHADOW = siteConfig('IMG_SHADOW') // 自定义样式css和js引入 if (isBrowser) { @@ -167,6 +169,18 @@ const ExternalPlugin = (props) => { }} /> )} + {CLARITY_ID && (<> +