diff --git a/.env.local b/.env.local index 905e8d94..cd9d8216 100644 --- a/.env.local +++ b/.env.local @@ -1,5 +1,5 @@ # 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables -NEXT_PUBLIC_VERSION=4.5.1 +NEXT_PUBLIC_VERSION=4.5.2 # 可在此添加环境变量,去掉最左边的(# )注释即可 diff --git a/components/ExternalPlugins.js b/components/ExternalPlugins.js index cecfae0e..da0b0fb3 100644 --- a/components/ExternalPlugins.js +++ b/components/ExternalPlugins.js @@ -6,71 +6,12 @@ import TianLiGPT from './TianliGPT' import WebWhiz from './Webwhiz' import { CUSTOM_EXTERNAL_CSS, CUSTOM_EXTERNAL_JS } from '@/blog.config' +import { mapPageUrl } from '@/lib/notion/mapPageUrl' import { isBrowser, loadExternalResource } from '@/lib/utils' +import { useRouter } from 'next/router' import { useEffect } from 'react' import { initGoogleAdsense } from './GoogleAdsense' -const TwikooCommentCounter = dynamic( - () => import('@/components/TwikooCommentCounter'), - { ssr: false } -) -const DebugPanel = dynamic(() => import('@/components/DebugPanel'), { - ssr: false -}) -const ThemeSwitch = dynamic(() => import('@/components/ThemeSwitch'), { - ssr: false -}) -const Fireworks = dynamic(() => import('@/components/Fireworks'), { - ssr: false -}) -const MouseFollow = dynamic(() => import('@/components/MouseFollow'), { - ssr: false -}) -const Nest = dynamic(() => import('@/components/Nest'), { ssr: false }) -const FlutteringRibbon = dynamic( - () => import('@/components/FlutteringRibbon'), - { ssr: false } -) -const Ribbon = dynamic(() => import('@/components/Ribbon'), { ssr: false }) -const Sakura = dynamic(() => import('@/components/Sakura'), { ssr: false }) -const StarrySky = dynamic(() => import('@/components/StarrySky'), { - ssr: false -}) -const DifyChatbot = dynamic(() => import('@/components/DifyChatbot'), { - ssr: false -}) -const Analytics = dynamic( - () => - import('@vercel/analytics/react').then(async m => { - return m.Analytics - }), - { ssr: false } -) -const MusicPlayer = dynamic(() => import('@/components/Player'), { ssr: false }) -const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false }) -const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false }) -const Busuanzi = dynamic(() => import('@/components/Busuanzi'), { ssr: false }) -const Messenger = dynamic(() => import('@/components/FacebookMessenger'), { - ssr: false -}) -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 -}) -const LoadingProgress = dynamic(() => import('@/components/LoadingProgress'), { - ssr: false -}) -const AosAnimation = dynamic(() => import('@/components/AOSAnimation'), { - ssr: false -}) - /** * 各种插件脚本 * @param {*} props @@ -155,6 +96,7 @@ const ExternalPlugin = props => { } } + const router = useRouter() useEffect(() => { // 异步渲染谷歌广告 if (ADSENSE_GOOGLE_ID) { @@ -163,6 +105,11 @@ const ExternalPlugin = props => { }, 1000) } + // 映射url + mapPageUrl(props?.allNavPages) + }, [router]) + + useEffect(() => { // 执行注入脚本 // eslint-disable-next-line no-eval eval(GLOBAL_JS) @@ -389,4 +336,65 @@ const ExternalPlugin = props => { ) } +const TwikooCommentCounter = dynamic( + () => import('@/components/TwikooCommentCounter'), + { ssr: false } +) +const DebugPanel = dynamic(() => import('@/components/DebugPanel'), { + ssr: false +}) +const ThemeSwitch = dynamic(() => import('@/components/ThemeSwitch'), { + ssr: false +}) +const Fireworks = dynamic(() => import('@/components/Fireworks'), { + ssr: false +}) +const MouseFollow = dynamic(() => import('@/components/MouseFollow'), { + ssr: false +}) +const Nest = dynamic(() => import('@/components/Nest'), { ssr: false }) +const FlutteringRibbon = dynamic( + () => import('@/components/FlutteringRibbon'), + { ssr: false } +) +const Ribbon = dynamic(() => import('@/components/Ribbon'), { ssr: false }) +const Sakura = dynamic(() => import('@/components/Sakura'), { ssr: false }) +const StarrySky = dynamic(() => import('@/components/StarrySky'), { + ssr: false +}) +const DifyChatbot = dynamic(() => import('@/components/DifyChatbot'), { + ssr: false +}) +const Analytics = dynamic( + () => + import('@vercel/analytics/react').then(async m => { + return m.Analytics + }), + { ssr: false } +) +const MusicPlayer = dynamic(() => import('@/components/Player'), { ssr: false }) +const Ackee = dynamic(() => import('@/components/Ackee'), { ssr: false }) +const Gtag = dynamic(() => import('@/components/Gtag'), { ssr: false }) +const Busuanzi = dynamic(() => import('@/components/Busuanzi'), { ssr: false }) +const Messenger = dynamic(() => import('@/components/FacebookMessenger'), { + ssr: false +}) +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 +}) +const LoadingProgress = dynamic(() => import('@/components/LoadingProgress'), { + ssr: false +}) +const AosAnimation = dynamic(() => import('@/components/AOSAnimation'), { + ssr: false +}) + export default ExternalPlugin diff --git a/components/NotionPage.js b/components/NotionPage.js index 1d1d9d0d..f8e8e396 100644 --- a/components/NotionPage.js +++ b/components/NotionPage.js @@ -47,9 +47,6 @@ const NotionPage = ({ post, className }) => { processDisableDatabaseUrl() } - // 若url是本站域名,则之间在当前窗口打开、不开新窗口 - processPageUrl() - /** * 放大查看图片时替换成高清图像 */ @@ -146,30 +143,6 @@ const processGalleryImg = zoom => { }, 800) } -/** - * 处理页面内连接跳转 - * 如果链接就是当网站,则不打开新窗口访问 - */ -const processPageUrl = () => { - if (isBrowser) { - const currentURL = window.location.origin + window.location.pathname - const allAnchorTags = document.getElementsByTagName('a') // 或者使用 document.querySelectorAll('a') 获取 NodeList - for (const anchorTag of allAnchorTags) { - if (anchorTag?.target === '_blank') { - const hrefWithoutQueryHash = anchorTag.href.split('?')[0].split('#')[0] - const hrefWithRelativeHash = - currentURL.split('#')[0] + anchorTag.href.split('#')[1] - - if ( - currentURL === hrefWithoutQueryHash || - currentURL === hrefWithRelativeHash - ) { - anchorTag.target = '_self' - } - } - } - } -} /** * 根据url参数自动滚动到指定区域 */ diff --git a/lib/notion/getPageProperties.js b/lib/notion/getPageProperties.js index 83982e4e..39ebb97d 100644 --- a/lib/notion/getPageProperties.js +++ b/lib/notion/getPageProperties.js @@ -5,7 +5,11 @@ import formatDate from '../utils/formatDate' // import { createHash } from 'crypto' import md5 from 'js-md5' import { siteConfig } from '../config' -import { checkContainHttp, sliceUrlFromHttp } from '../utils' +import { + checkContainHttp, + convertUrlStartWithOneSlash, + sliceUrlFromHttp +} from '../utils' import { mapImgUrl } from './mapImage' /** @@ -191,10 +195,10 @@ export function adjustPageProperties(properties, NOTION_CONFIG) { } } - // 最终检查超链接 + // 检查处理外链 properties.href = checkContainHttp(properties?.href) ? sliceUrlFromHttp(properties?.href) - : `/${properties.href}` + : convertUrlStartWithOneSlash(properties?.href) // 设置链接在页内或新页面打开 if (properties.href?.indexOf('http') === 0) { @@ -224,6 +228,12 @@ function generateCustomizeSlug(postProperties, NOTION_CONFIG) { NOTION_CONFIG ).split('/') + const POST_URL_PREFIX_MAPPING_CATEGORY = siteConfig( + 'POST_URL_PREFIX_MAPPING_CATEGORY', + {}, + NOTION_CONFIG + ) + allSlugPatterns.forEach((pattern, idx) => { if (pattern === '%year%' && postProperties?.publishDay) { const formatPostCreatedDate = new Date(postProperties?.publishDay) @@ -240,7 +250,13 @@ function generateCustomizeSlug(postProperties, NOTION_CONFIG) { } else if (pattern === '%slug%') { fullPrefix += postProperties.slug ?? postProperties.id } else if (pattern === '%category%' && postProperties?.category) { - fullPrefix += postProperties.category + let categoryPrefix = postProperties.category + // 允许映射分类名,通常用来将中文分类映射成英文,美化url. + if (POST_URL_PREFIX_MAPPING_CATEGORY[postProperties?.category]) { + categoryPrefix = + POST_URL_PREFIX_MAPPING_CATEGORY[postProperties?.category] + } + fullPrefix += categoryPrefix } else if (!pattern.includes('%')) { fullPrefix += pattern } else { diff --git a/lib/notion/mapPageUrl.js b/lib/notion/mapPageUrl.js new file mode 100644 index 00000000..80fe70d8 --- /dev/null +++ b/lib/notion/mapPageUrl.js @@ -0,0 +1,40 @@ +import { uuidToId } from 'notion-utils' +import { checkStrIsNotionId, getLastPartOfUrl, isBrowser } from '../utils' + +/** + * 处理页面内连接跳转: + * 1. 若是本站域名,则在当前窗口打开、不开新窗口 + * 2. 若是Notion笔记中的内链,尝试转换成博客中现有的文章地址 + */ +export const mapPageUrl = allPages => { + if (isBrowser) { + const currentURL = window.location.origin + window.location.pathname + const allAnchorTags = document.getElementsByTagName('a') // 或者使用 document.querySelectorAll('a') 获取 NodeList + for (const anchorTag of allAnchorTags) { + // 检查url + if (anchorTag?.href) { + // 如果url是一个Notion_id,尝试匹配成博客的文章内链 + const slug = getLastPartOfUrl(anchorTag.href) + if (checkStrIsNotionId(slug)) { + const slugPage = allPages?.find(page => uuidToId(page.id) === slug) + if (slugPage) { + anchorTag.href = slugPage?.href + } + } + } + + if (anchorTag?.target === '_blank') { + const hrefWithoutQueryHash = anchorTag.href.split('?')[0].split('#')[0] + const hrefWithRelativeHash = + currentURL.split('#')[0] + anchorTag.href.split('#')[1] + + if ( + currentURL === hrefWithoutQueryHash || + currentURL === hrefWithRelativeHash + ) { + anchorTag.target = '_self' + } + } + } + } +} diff --git a/lib/utils/index.js b/lib/utils/index.js index b0652494..b8d78f30 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -61,6 +61,26 @@ export function sliceUrlFromHttp(str) { } } +/** + * 将相对路径的url test 转为绝对路径 /test + * 判断url如果不是以 /开头,则拼接一个 / + * 同时如果开头有重复的多个 // ,则只保留一个 + * @param {*} str + */ +export function convertUrlStartWithOneSlash(str) { + if (!str) { + return '#' + } + // 判断url是否以 / 开头 + if (!str.startsWith('/')) { + // 如果不是,则在前面拼接一个 / + str = '/' + str + } + // 移除开头的多个连续斜杠,只保留一个 + str = str.replace(/\/+/g, '/') + return str +} + // 检查是否外链 export function checkContainHttp(str) { // 检查字符串是否包含http @@ -73,6 +93,35 @@ export function checkContainHttp(str) { } } +// 检查一个字符串是否notionid : 32位,仅由数字英文构成 +export function checkStrIsNotionId(str) { + if (!str) { + return false + } + // 使用正则表达式进行匹配 + const regex = /^[a-zA-Z0-9]{32}$/ + return regex.test(str) +} + +// 截取url中最后一个 / 后面的内容 +export function getLastPartOfUrl(url) { + if (!url) { + return '' + } + // 找到最后一个斜杠的位置 + const lastSlashIndex = url.lastIndexOf('/') + + // 如果找不到斜杠,则返回整个字符串 + if (lastSlashIndex === -1) { + return url + } + + // 截取最后一个斜杠后面的内容 + const lastPart = url.substring(lastSlashIndex + 1) + + return lastPart +} + /** * 加载外部资源 * @param url 地址 例如 https://xx.com/xx.js diff --git a/package.json b/package.json index 768f8344..3d24cecd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "notion-next", - "version": "4.5.1", + "version": "4.5.2", "homepage": "https://github.com/tangly1024/NotionNext.git", "license": "MIT", "repository": {