1/** 2 * è½¬ä¹æ£å表达å¼ä¸çç¹æ®å符 3 * @param {string} string éè¦è½¬ä¹çå符串 4 * @returns {string} 转ä¹åçå符串 5 */ 6function escapeRegExp(string) { 7 return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 8} 9 10/** 11 * å°Nodeææ¬ä¸çæå®æ ç¾å 容转æ¢ä¸ºå¸¦ææå®ç±»åçspan 12 * @param regex 13 * @param node 14 * @param className 15 */ 16function convertTextToSpoilerSpan(regex, node, className) { 17 // ä½¿ç¨ textContent æ¿ä»£ wholeText 以确ä¿ç±»åå®å ¨ 18 const textContent = node.textContent 19 let outerSpan = document.createElement('span') 20 const fragments = [] 21 let lastIndex = 0 22 let match 23 while ((match = regex.exec(textContent)) !== null) { 24 console.log('符åè¦æ±çæå' + textContent) 25 // æ·»å å颿ªå¹é çé¨å 26 if (match.index > lastIndex) { 27 outerSpan.appendChild( 28 document.createTextNode(textContent.slice(lastIndex, match.index)) 29 ) 30 } 31 32 // å建 span å 裹çå 容 33 const span = document.createElement('span') 34 span.textContent = match[1] // æåå¹é çå 容 35 if (className) { 36 span.className = className 37 } 38 outerSpan.appendChild(span) 39 // 设置lastIndex 40 lastIndex = regex.lastIndex 41 } 42 if (outerSpan.childNodes.length) { 43 // æ·»å å©ä½æªå¹é çé¨å 44 if (lastIndex < textContent.length) { 45 outerSpan.appendChild( 46 document.createTextNode(textContent.slice(lastIndex)) 47 ) 48 } 49 node.replaceWith(outerSpan) 50 } 51} 52 53/** 54 * æ¶éå¹¶å¤çæå®èç¹ä¸çææææ¬èç¹ 55 * @param root 56 * @param className 57 * @param spoilerTag 58 */ 59function processTextNodes(root, className, spoilerTag) { 60 const regex = new RegExp(`${spoilerTag}(.*?)${spoilerTag}`, 'g') 61 const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { 62 acceptNode: function (node) { 63 if (node.nodeType === Node.TEXT_NODE) { 64 return regex.test(node.textContent) 65 ? NodeFilter.FILTER_ACCEPT 66 : NodeFilter.FILTER_REJECT 67 } 68 return NodeFilter.FILTER_REJECT 69 } 70 }) 71 const waitProcessNodes = [] 72 while (walker.nextNode()) { 73 const node = walker.currentNode 74 waitProcessNodes.push(node) 75 } 76 for (const waitProcessNode of waitProcessNodes) { 77 convertTextToSpoilerSpan(regex, waitProcessNode, className) 78 } 79 80 // å¤çè·¨èç¹ç spoiler æ è®° 81 processCrossNodeSpoilers(root, className, spoilerTag) 82} 83 84/** 85 * å¤çè·¨èç¹ç spoiler æ è®° 86 * @param {Element} root è¦å¤ççæ ¹å ç´ 87 * @param {string} className åºç¨äº spoiler å 容çç±»å 88 * @param {string} spoilerTag spoiler æ è®°ç¬¦å· 89 */ 90function processCrossNodeSpoilers(root, className, spoilerTag) { 91 if (root.nodeType !== Node.ELEMENT_NODE) return 92 93 const html = root.innerHTML 94 95 // å¤çåå§æ ç¾ï¼å¦ææ¯å·²ç»è½¬ä¹è¿çï¼åå»é¤è½¬ä¹ 96 let originalTag = spoilerTag 97 if (spoilerTag.startsWith('\\') || spoilerTag.includes('\\[')) { 98 originalTag = spoilerTag.replace(/\\/g, '') 99 } 100 101 // å建æ£å表达å¼ï¼ç´æ¥å¹é åå§æ ç¾ 102 const regex = new RegExp(`\\${originalTag}([\\s\\S]*?)\\${originalTag}`, 'g') 103 104 const hasMatch = regex.test(html) 105 106 if (!hasMatch) return 107 108 // éç½®æ£åè¡¨è¾¾å¼ 109 regex.lastIndex = 0 110 111 // æ¿æ¢å¹é 项 112 const newHtml = html.replace(regex, function (match, content) { 113 return `<span class="${className}">${content}</span>` 114 }) 115 116 // 妿å 容æååï¼æ´æ° DOM 117 if (newHtml !== html) { 118 root.innerHTML = newHtml 119 } 120} 121 122/** 123 * å®ä½å°ç®æ å¤çä½ç½®ï¼å¼å§è¿è¡ææ¬å°spoilerçè½¬æ¢ 124 * @param spoilerTag 125 */ 126function textToSpoiler(spoilerTag) { 127 const intervalID = setInterval(() => { 128 const articleElement = document.querySelector( 129 '#article-wrapper #notion-article main' 130 ) 131 if (articleElement) { 132 setTimeout(() => { 133 processTextNodes(articleElement, 'spoiler-text', spoilerTag) 134 clearInterval(intervalID) 135 }, 300) 136 } 137 }, 1000) 138} 139 140window.textToSpoiler = textToSpoiler
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.