Files
NotionNext/public/js/spoilerText.js
2025-06-04 13:56:07 +08:00

141 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 转义正则表达式中的特殊字符
* @param {string} string 需要转义的字符串
* @returns {string} 转义后的字符串
*/
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
/**
* 将Node文本中的指定标签内容转换为带有指定类名的span
* @param regex
* @param node
* @param className
*/
function convertTextToSpoilerSpan(regex, node, className) {
// 使用 textContent 替代 wholeText 以确保类型安全
const textContent = node.textContent
let outerSpan = document.createElement('span')
const fragments = []
let lastIndex = 0
let match
while ((match = regex.exec(textContent)) !== null) {
console.log('符合要求的文字' + textContent)
// 添加前面未匹配的部分
if (match.index > lastIndex) {
outerSpan.appendChild(
document.createTextNode(textContent.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 < textContent.length) {
outerSpan.appendChild(
document.createTextNode(textContent.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) {
if (node.nodeType === Node.TEXT_NODE) {
return regex.test(node.textContent)
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT
}
return 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 标记
processCrossNodeSpoilers(root, className, spoilerTag)
}
/**
* 处理跨节点的 spoiler 标记
* @param {Element} root 要处理的根元素
* @param {string} className 应用于 spoiler 内容的类名
* @param {string} spoilerTag spoiler 标记符号
*/
function processCrossNodeSpoilers(root, className, spoilerTag) {
if (root.nodeType !== Node.ELEMENT_NODE) return
const html = root.innerHTML
// 处理原始标签,如果是已经转义过的,则去除转义
let originalTag = spoilerTag
if (spoilerTag.startsWith('\\') || spoilerTag.includes('\\[')) {
originalTag = spoilerTag.replace(/\\/g, '')
}
// 创建正则表达式,直接匹配原始标签
const regex = new RegExp(`\\${originalTag}([\\s\\S]*?)\\${originalTag}`, 'g')
const hasMatch = regex.test(html)
if (!hasMatch) return
// 重置正则表达式
regex.lastIndex = 0
// 替换匹配项
const newHtml = html.replace(regex, function (match, content) {
return `<span class="${className}">${content}</span>`
})
// 如果内容有变化,更新 DOM
if (newHtml !== html) {
root.innerHTML = newHtml
}
}
/**
* 定位到目标处理位置开始进行文本到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