diff --git a/blog.config.js b/blog.config.js index 4ca08da4..82fab5a3 100644 --- a/blog.config.js +++ b/blog.config.js @@ -298,6 +298,8 @@ const BLOG = { WIDGET_PET_SWITCH_THEME: process.env.NEXT_PUBLIC_WIDGET_PET_SWITCH_THEME || true, // 点击宠物挂件切换博客主题 + SPOILER_TEXT_TAG: process.env.NEXT_PUBLIC_SPOILER_TEXT_TAG || '', // Spoiler文本隐藏功能,如Notion中 [sp]希望被spoiler的文字[sp],填入[sp] 即可 + // 音乐播放插件 MUSIC_PLAYER: process.env.NEXT_PUBLIC_MUSIC_PLAYER || false, // 是否使用音乐播放插件 MUSIC_PLAYER_VISIBLE: process.env.NEXT_PUBLIC_MUSIC_PLAYER_VISIBLE || true, // 是否在左下角显示播放和切换,如果使用播放器,打开自动播放再隐藏,就会以类似背景音乐的方式播放,无法取消和暂停 diff --git a/components/NotionPage.js b/components/NotionPage.js index a3f1a085..4baf099c 100644 --- a/components/NotionPage.js +++ b/components/NotionPage.js @@ -1,6 +1,6 @@ import { siteConfig } from '@/lib/config' import { compressImage, mapImgUrl } from '@/lib/notion/mapImage' -import { isBrowser } from '@/lib/utils' +import { isBrowser, loadExternalResource } from '@/lib/utils' import mediumZoom from '@fisch0920/medium-zoom' import 'katex/dist/katex.min.css' import dynamic from 'next/dynamic' @@ -17,6 +17,7 @@ const NotionPage = ({ post, className }) => { // 是否关闭数据库和画册的点击跳转 const POST_DISABLE_GALLERY_CLICK = siteConfig('POST_DISABLE_GALLERY_CLICK') const POST_DISABLE_DATABASE_CLICK = siteConfig('POST_DISABLE_DATABASE_CLICK') + const SPOILER_TEXT_TAG = siteConfig('SPOILER_TEXT_TAG') const zoom = isBrowser && @@ -84,6 +85,21 @@ const NotionPage = ({ post, className }) => { } }, [post]) + useEffect(() => { + // Spoiler文本功能 + if (SPOILER_TEXT_TAG) { + import('lodash/escapeRegExp').then(escapeRegExp => { + Promise.all([ + loadExternalResource('/js/spoilerText.js', 'js'), + loadExternalResource('/css/spoiler-text.css', 'css') + ]).then(() => { + window.textToSpoiler && + window.textToSpoiler(escapeRegExp.default(SPOILER_TEXT_TAG)) + }) + }) + } + }, [post]) + return (
lastIndex) { + outerSpan.appendChild( + document.createTextNode(wholeText.slice(lastIndex, match.index)) + ) + } + + // 创建 span 包裹的内容 + const span = document.createElement('span') + span.textContent = match[1] // 提取匹配的内容 + if (className) { + span.className = className + } + outerSpan.appendChild(span) + // 设置lastIndex + lastIndex = regex.lastIndex + } + if (outerSpan.childNodes.length) { + // 添加剩余未匹配的部分 + if (lastIndex < wholeText.length) { + outerSpan.appendChild(document.createTextNode(wholeText.slice(lastIndex))) + } + node.replaceWith(outerSpan) + } +} + +/** + * 收集并处理指定节点下的所有文本节点 + * @param root + * @param className + * @param spoilerTag + */ +function processTextNodes(root, className, spoilerTag) { + const regex = new RegExp(`${spoilerTag}(.*?)${spoilerTag}`, 'g') + const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { + acceptNode: function (node) { + return regex.test(node.wholeText) + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_REJECT + } + }) + const waitProcessNodes = [] + while (walker.nextNode()) { + const node = walker.currentNode + waitProcessNodes.push(node) + } + for (const waitProcessNode of waitProcessNodes) { + convertTextToSpoilerSpan(regex, waitProcessNode, className) + } +} + +/** + * 定位到目标处理位置,开始进行文本到spoiler的转换 + * @param spoilerTag + */ +function textToSpoiler(spoilerTag) { + const intervalID = setInterval(() => { + const articleElement = document.querySelector( + '#article-wrapper #notion-article main' + ) + if (articleElement) { + setTimeout(() => { + processTextNodes(articleElement, 'spoiler-text', spoilerTag) + clearInterval(intervalID) + }, 300) + } + }, 1000) +} + +window.textToSpoiler = textToSpoiler