@@ -75,13 +75,29 @@ const processNewlyAddedNodesGeneral = function (firstBody) {
75
75
} ;
76
76
77
77
const processNewlyAddedNodesGeneral2 = function ( firstBody , jsonData ) {
78
- let targetWords = jsonData . map ( slur => Object . keys ( slur ) [ 0 ] ) ;
78
+ let targetWords = jsonData . map ( ( slur ) => Object . keys ( slur ) [ 0 ] ) ;
79
79
targetWords . sort ( ( a , b ) => b . length - a . length ) ;
80
80
81
81
let uliStore = [ ] ;
82
82
getAllTextNodes ( firstBody , uliStore ) ;
83
- locateSlur ( uliStore , targetWords ) ;
84
- addMetaData ( targetWords , jsonData ) ;
83
+
84
+ locateSlur ( uliStore , targetWords , jsonData ) ;
85
+
86
+ const ob = new MutationObserver ( async ( mutations ) => {
87
+ setTimeout ( ( ) => {
88
+ mutations . forEach ( ( mutation ) => {
89
+ if ( mutation . type === 'childList' && mutation . addedNodes ) {
90
+ mutation . addedNodes . forEach ( ( node ) => {
91
+ let uliStore = [ ] ;
92
+ getAllTextNodes ( node , uliStore ) ;
93
+ locateSlur ( uliStore , targetWords , jsonData ) ;
94
+ } ) ;
95
+ }
96
+ } ) ;
97
+ } , 10 ) ;
98
+ } ) ;
99
+
100
+ ob . observe ( firstBody , { childList : true , subtree : true } ) ;
85
101
} ;
86
102
87
103
function checkFalseTextNode ( text , actualLengthOfText ) {
@@ -122,87 +138,133 @@ function findPositions(word, text) {
122
138
return positions ;
123
139
}
124
140
125
- function locateSlur ( uliStore , targetWords ) {
126
- uliStore . forEach ( store => {
141
+ function locateSlur ( uliStore , targetWords , jsonData ) {
142
+ if ( uliStore . length === 0 ) return ;
143
+ uliStore . forEach ( ( store ) => {
127
144
let textnode = store . node ;
145
+
146
+ if (
147
+ store . parent ?. classList . contains ( 'slur' ) ||
148
+ store . parent ?. innerHTML . includes ( `class="slur` )
149
+ ) {
150
+ return ;
151
+ }
128
152
let tempParent = document . createElement ( 'span' ) ;
129
153
tempParent . textContent = textnode . textContent ;
130
154
let slurPresentInTempParent = false ;
131
- targetWords . forEach ( targetWord => {
155
+ let foundTargettedWord = '' ;
156
+ targetWords . forEach ( ( targetWord ) => {
132
157
const sanitizedTargetWord = targetWord . replace ( / \s + / g, '-' ) ;
133
158
const slurClass = `slur-container-${ sanitizedTargetWord } ` ;
134
-
135
- const escapedTargetWord = targetWord . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
159
+
160
+ const escapedTargetWord = targetWord . replace (
161
+ / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g,
162
+ '\\$&'
163
+ ) ;
136
164
// regex for multi-word and single-word phrases
137
165
const regex = new RegExp ( `(^|[\\s.,!?()'"\\[\\]{}<>;:@#\\$%\\^&*+=~_“”‘’])(${ escapedTargetWord } )(?=[\\s.,!?()'"\\[\\]{}<>;:@#\\$%\\^&*+=~_“”‘’]|$)` , 'giu' ) ;
138
-
166
+
139
167
if ( regex . test ( tempParent . textContent ) ) {
140
- tempParent . innerHTML = tempParent . innerHTML . replace ( regex , ( match , prefix , word ) => {
141
- return `${ prefix } <span class="${ slurClass } "><span class="slur">${ word } </span></span>` ;
142
- } ) ;
168
+ tempParent . innerHTML = tempParent . innerHTML . replace (
169
+ regex ,
170
+ ( match , prefix , word ) => {
171
+ return `${ prefix } <span class="${ slurClass } "><span class="slur">${ word } </span></span>` ;
172
+ }
173
+ ) ;
143
174
slurPresentInTempParent = true ;
175
+ foundTargettedWord = targetWord ;
144
176
}
145
177
} ) ;
146
178
147
179
if ( slurPresentInTempParent ) {
148
180
textnode . replaceWith ( tempParent ) ;
181
+
182
+ addNodeMetaData ( tempParent , foundTargettedWord , jsonData ) ;
149
183
}
150
184
} ) ;
151
185
}
152
186
153
- function addMetaData ( targetWords , jsonData ) {
154
- targetWords . forEach ( ( targetWord ) => {
155
- const sanitizedTargetWord = targetWord . replace ( / \s + / g, '-' ) ;
156
- const className = `slur-container-${ sanitizedTargetWord } ` ;
157
- const elements = Array . from ( document . querySelectorAll ( `.${ className } ` ) ) ;
158
-
159
- elements . forEach ( ( element ) => {
160
- const slur = element . querySelector ( '.slur' ) ;
161
- if ( ! slur ) return ;
162
-
163
- // Add hover styles
164
- slur . style . backgroundColor = '#ffde2155' ;
165
- slur . style . boxShadow = '0px 0px 5px #ffde21' ;
166
- slur . style . cursor = 'pointer' ;
167
-
168
- // Create a single tooltip container and React root if not already created
169
- let tooltipContainer = document . getElementById ( 'slur-tooltip-container' ) ;
170
- if ( ! tooltipContainer ) {
171
- tooltipContainer = document . createElement ( 'div' ) ;
172
- tooltipContainer . id = 'slur-tooltip-container' ;
173
- tooltipContainer . style . position = 'absolute' ;
174
- tooltipContainer . style . zIndex = '1000000000' ;
175
- document . body . appendChild ( tooltipContainer ) ;
176
- tooltipContainer . root = createRoot ( tooltipContainer ) ;
187
+ function addNodeMetaData ( element , targetWord , jsonData ) {
188
+ const slur = element . querySelector ( '.slur' ) ;
189
+ if ( ! slur ) return ;
190
+
191
+ slur . style . backgroundColor = '#ffde2155' ;
192
+ slur . style . boxShadow = '0px 0px 5px #ffde21' ;
193
+ slur . style . cursor = 'pointer' ;
194
+ slur . style . zIndex = '3' ;
195
+ slur . style . position = 'relative' ;
196
+ slur . style . pointerEvents = 'auto' ;
197
+
198
+ let tooltipContainer = document . getElementById ( 'slur-tooltip-container' ) ;
199
+ if ( ! tooltipContainer ) {
200
+ tooltipContainer = document . createElement ( 'div' ) ;
201
+ tooltipContainer . id = 'slur-tooltip-container' ;
202
+ tooltipContainer . style . position = 'absolute' ;
203
+ tooltipContainer . style . zIndex = '999' ;
204
+ document . body . appendChild ( tooltipContainer ) ;
205
+ tooltipContainer . root = createRoot ( tooltipContainer ) ;
206
+ }
207
+
208
+ // Find the slur details from jsonData
209
+ const matchedSlur = jsonData . find (
210
+ ( slur ) =>
211
+ Object . keys ( slur ) [ 0 ] . toLowerCase ( ) === targetWord . toLowerCase ( )
212
+ ) ;
213
+ const slurDetails = matchedSlur
214
+ ? matchedSlur [ Object . keys ( matchedSlur ) [ 0 ] ]
215
+ : { } ;
216
+
217
+ const handleMouseOver = ( ) => {
218
+ const rect = slur . getBoundingClientRect ( ) ;
219
+ const screenHeight = window . innerHeight ;
220
+ const screenWidth = window . innerWidth ;
221
+
222
+ // Initial position (assume tooltip goes below first)
223
+ let position = {
224
+ x : rect . left + window . scrollX ,
225
+ y : rect . bottom + 10 + window . scrollY
226
+ } ;
227
+
228
+ // Render tooltip first (off-screen)
229
+ tooltipContainer . style . top = `-9999px` ; // Prevent flickering
230
+ tooltipContainer . style . left = `-9999px` ;
231
+ tooltipContainer . style . opacity = '0' ;
232
+
233
+ tooltipContainer . root . render (
234
+ < HoverSlurMetadata slurDetails = { slurDetails } />
235
+ ) ;
236
+
237
+ // Wait for the next render cycle to measure the height
238
+ setTimeout ( ( ) => {
239
+ const tooltipHeight = tooltipContainer . offsetHeight ;
240
+
241
+ const spaceBelow = screenHeight - rect . bottom ;
242
+
243
+ if ( spaceBelow < tooltipHeight + 10 ) {
244
+ position . y = rect . top - tooltipHeight - 10 + window . scrollY ;
177
245
}
178
246
179
- // Find the slur details from jsonData
180
- const matchedSlur = jsonData . find ( slur =>
181
- Object . keys ( slur ) [ 0 ] . toLowerCase ( ) === targetWord . toLowerCase ( )
182
- ) ;
183
- const slurDetails = matchedSlur ? matchedSlur [ Object . keys ( matchedSlur ) [ 0 ] ] : { } ;
247
+ if ( position . x + 290 > screenWidth ) {
248
+ position . x = screenWidth - 320 + window . scrollX ;
249
+ }
184
250
185
- const handleMouseOver = ( ) => {
186
- const rect = slur . getBoundingClientRect ( ) ;
187
- tooltipContainer . style . top = `${ rect . bottom + window . scrollY } px` ;
188
- tooltipContainer . style . left = `${ rect . left + window . scrollX } px` ;
251
+ tooltipContainer . style . top = `${ position . y } px` ;
252
+ tooltipContainer . style . left = `${ position . x } px` ;
253
+ tooltipContainer . style . opacity = '1' ;
254
+ } , 0 ) ; // Short delay to ensure the DOM updates
255
+ } ;
189
256
190
- tooltipContainer . root . render (
191
- < HoverSlurMetadata slurDetails = { slurDetails } />
192
- ) ;
193
- } ;
257
+ const handleMouseOut = ( ) => {
258
+ tooltipContainer . root . render ( null ) ;
259
+ } ;
194
260
195
- const handleMouseOut = ( ) => {
196
- tooltipContainer . root . render ( null ) ;
197
- } ;
261
+ slur . addEventListener ( 'mouseover' , handleMouseOver ) ;
262
+ slur . addEventListener ( 'mouseout' , handleMouseOut ) ;
198
263
199
- slur . addEventListener ( 'mouseover' , handleMouseOver ) ;
200
- slur . addEventListener ( 'mouseout' , handleMouseOut ) ;
201
- } ) ;
202
- } ) ;
264
+ element . classList . add ( 'slur-metadata-added' ) ;
203
265
}
204
266
205
267
export default {
206
268
processNewlyAddedNodesGeneral,
207
- processNewlyAddedNodesGeneral2,
208
- } ;
269
+ processNewlyAddedNodesGeneral2
270
+ } ;
0 commit comments