1
+ /**
2
+ * 转义正则表达式中的特殊字符
3
+ * @param {string } string 需要转义的字符串
4
+ * @returns {string } 转义后的字符串
5
+ */
6
+ function escapeRegExp ( string ) {
7
+ return string . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' )
8
+ }
9
+
1
10
/**
2
11
* 将Node文本中的指定标签内容转换为带有指定类名的span
3
12
* @param regex
4
13
* @param node
5
14
* @param className
6
15
*/
7
16
function convertTextToSpoilerSpan ( regex , node , className ) {
8
- const wholeText = node . wholeText
17
+ // 使用 textContent 替代 wholeText 以确保类型安全
18
+ const textContent = node . textContent
9
19
let outerSpan = document . createElement ( 'span' )
10
20
const fragments = [ ]
11
21
let lastIndex = 0
12
22
let match
13
- while ( ( match = regex . exec ( wholeText ) ) !== null ) {
14
- console . log ( '符合要求的文字' + wholeText )
23
+ while ( ( match = regex . exec ( textContent ) ) !== null ) {
24
+ console . log ( '符合要求的文字' + textContent )
15
25
// 添加前面未匹配的部分
16
26
if ( match . index > lastIndex ) {
17
27
outerSpan . appendChild (
18
- document . createTextNode ( wholeText . slice ( lastIndex , match . index ) )
28
+ document . createTextNode ( textContent . slice ( lastIndex , match . index ) )
19
29
)
20
30
}
21
31
@@ -31,8 +41,10 @@ function convertTextToSpoilerSpan(regex, node, className) {
31
41
}
32
42
if ( outerSpan . childNodes . length ) {
33
43
// 添加剩余未匹配的部分
34
- if ( lastIndex < wholeText . length ) {
35
- outerSpan . appendChild ( document . createTextNode ( wholeText . slice ( lastIndex ) ) )
44
+ if ( lastIndex < textContent . length ) {
45
+ outerSpan . appendChild (
46
+ document . createTextNode ( textContent . slice ( lastIndex ) )
47
+ )
36
48
}
37
49
node . replaceWith ( outerSpan )
38
50
}
@@ -48,9 +60,12 @@ function processTextNodes(root, className, spoilerTag) {
48
60
const regex = new RegExp ( `${ spoilerTag } (.*?)${ spoilerTag } ` , 'g' )
49
61
const walker = document . createTreeWalker ( root , NodeFilter . SHOW_TEXT , {
50
62
acceptNode : function ( node ) {
51
- return regex . test ( node . wholeText )
52
- ? NodeFilter . FILTER_ACCEPT
53
- : NodeFilter . FILTER_REJECT
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
54
69
}
55
70
} )
56
71
const waitProcessNodes = [ ]
@@ -61,6 +76,47 @@ function processTextNodes(root, className, spoilerTag) {
61
76
for ( const waitProcessNode of waitProcessNodes ) {
62
77
convertTextToSpoilerSpan ( regex , waitProcessNode , className )
63
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
+ */
90
+ function 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
+ }
64
120
}
65
121
66
122
/**
0 commit comments