@@ -2,6 +2,8 @@ import { replaceSlur } from './slur-replace';
2
2
import { log } from './logger' ;
3
3
import repository from './repository' ;
4
4
const { getPreferenceData } = repository ;
5
+ import { createRoot } from 'react-dom/client' ;
6
+ import HoverSlurMetadata from './ui-components/atoms/HoverSlurMetadata' ;
5
7
6
8
// Traverse dom nodes to find leaf node that are text nodes and process
7
9
function bft ( node ) {
@@ -49,19 +51,6 @@ function setCaretPosition(element, offset) {
49
51
sel . addRange ( range ) ;
50
52
}
51
53
52
- const processNewlyAddedNodesGeneral2 = function ( firstBody , jsonData ) {
53
- let targetWords = [ ] ;
54
- jsonData . forEach ( ( slur ) => {
55
- const slurWord = Object . keys ( slur ) [ 0 ] ;
56
- targetWords . push ( slurWord ) ;
57
- // targetWords.push(slurWord.charAt(0).toUpperCase() + slurWord.slice(1));
58
- } ) ;
59
- let uliStore = [ ] ;
60
- getAllTextNodes ( firstBody , uliStore ) ;
61
- abc = locateSlur ( uliStore , targetWords ) ;
62
- addMetaData ( targetWords , jsonData ) ;
63
- } ;
64
-
65
54
const processNewlyAddedNodesGeneral = function ( firstBody ) {
66
55
log ( 'processing new nodes' ) ;
67
56
const config = { attributes : true , childList : true , subtree : true } ;
@@ -85,6 +74,16 @@ const processNewlyAddedNodesGeneral = function (firstBody) {
85
74
observer . observe ( firstBody , config ) ;
86
75
} ;
87
76
77
+ const processNewlyAddedNodesGeneral2 = function ( firstBody , jsonData ) {
78
+ let targetWords = jsonData . map ( slur => Object . keys ( slur ) [ 0 ] ) ;
79
+ targetWords . sort ( ( a , b ) => b . length - a . length ) ;
80
+
81
+ let uliStore = [ ] ;
82
+ getAllTextNodes ( firstBody , uliStore ) ;
83
+ locateSlur ( uliStore , targetWords ) ;
84
+ addMetaData ( targetWords , jsonData ) ;
85
+ } ;
86
+
88
87
function checkFalseTextNode ( text , actualLengthOfText ) {
89
88
let totalNewlineAndWhitespaces = 0 ;
90
89
for ( let i = 0 ; i < text . length ; i ++ ) {
@@ -95,7 +94,6 @@ function checkFalseTextNode(text, actualLengthOfText) {
95
94
return totalNewlineAndWhitespaces === actualLengthOfText ;
96
95
}
97
96
98
- // Function to recursively get all text nodes for a given node
99
97
function getAllTextNodes ( node , uliStore ) {
100
98
if ( node . nodeType === 3 ) {
101
99
if ( ! checkFalseTextNode ( node . data , node . length ) ) {
@@ -125,188 +123,86 @@ function findPositions(word, text) {
125
123
}
126
124
127
125
function locateSlur ( uliStore , targetWords ) {
128
- let n = uliStore . length ;
129
-
130
- for ( let i = 0 ; i < n ; i ++ ) {
131
- let store = uliStore [ i ] ;
132
- let parentNode = store . parent ;
126
+ uliStore . forEach ( store => {
133
127
let textnode = store . node ;
134
- let text = store . node . textContent ;
135
128
let tempParent = document . createElement ( 'span' ) ;
136
- tempParent . textContent = text ;
137
- let slurs = [ ] ;
129
+ tempParent . textContent = textnode . textContent ;
138
130
let slurPresentInTempParent = false ;
139
- targetWords . forEach ( ( targetWord ) => {
140
- let slurWord = targetWord ;
141
- let pos = findPositions ( slurWord , text ) ;
142
- if ( Object . keys ( pos ) . length !== 0 ) {
143
- slurs . push ( pos ) ;
144
- }
145
-
146
- if ( tempParent . innerHTML . includes ( targetWord ) ) {
147
- const className = `icon-container-${ targetWord } ` ;
148
- const slurClass = `slur-container-${ targetWord } ` ;
149
-
150
- // if (!tempParent.innerHTML.includes(`class="${slurClass}"`)) {
151
- const parts = tempParent . innerHTML . split ( targetWord ) ;
152
- const replacedHTML = parts . join (
153
- `<span class="${ slurClass } "><span class="slur">${ targetWord } </span></span>`
154
- ) ;
155
- tempParent . innerHTML = replacedHTML ;
131
+ targetWords . forEach ( targetWord => {
132
+ const sanitizedTargetWord = targetWord . replace ( / \s + / g, '-' ) ;
133
+ const slurClass = `slur-container-${ sanitizedTargetWord } ` ;
134
+
135
+ const escapedTargetWord = targetWord . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' ) ;
136
+ // regex for multi-word and single-word phrases
137
+ const regex = new RegExp ( `(^|\\s|[.,!?])(${ escapedTargetWord } )(?=\\s|$|[.,!?])` , 'giu' ) ;
138
+
139
+ 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
+ } ) ;
156
143
slurPresentInTempParent = true ;
157
- // }
158
144
}
159
145
} ) ;
160
- uliStore [ i ] . nodeToParent = tempParent ;
161
- uliStore [ i ] . slurs = slurs ;
162
146
163
- //O(1) complexity
164
147
if ( slurPresentInTempParent ) {
165
148
textnode . replaceWith ( tempParent ) ;
166
149
}
167
-
168
- // console.log("TEMPParent: ",tempParent)
169
- }
170
- return uliStore ;
150
+ } ) ;
171
151
}
172
152
173
153
function addMetaData ( targetWords , jsonData ) {
174
154
targetWords . forEach ( ( targetWord ) => {
175
- const className = `slur-container-${ targetWord } ` ;
155
+ const sanitizedTargetWord = targetWord . replace ( / \s + / g, '-' ) ;
156
+ const className = `slur-container-${ sanitizedTargetWord } ` ;
176
157
const elements = Array . from ( document . querySelectorAll ( `.${ className } ` ) ) ;
177
- // console.log("ELEMENTS are: ",elements)
178
- elements . forEach ( ( element ) => {
179
- // console.log("ELements InnerHTML:",element.innerHTML)
180
158
181
- // element.innerHTML = element.innerHTML.replace(/<img[^>]*>/g, '')
182
-
183
- let sup = document . createElement ( 'span' ) ;
184
- let img = document . createElement ( 'img' ) ;
185
- img . style . height = '0.6em' ;
186
- img . style . width = '0.6em' ;
187
- img . style . border = '1px solid black' ;
188
- img . style . cursor = 'pointer' ;
189
- img . style . marginBottom = '0.4em' ;
190
-
191
- img . src =
192
- 'https://raw.githubusercontent.com/tattle-made/Uli/main/uli-website/src/images/favicon-32x32.png' ;
193
- img . alt = 'altText' ;
194
-
195
- let span = document . createElement ( 'span' ) ;
196
- span . style . display = 'none' ;
197
- span . style . position = 'absolute' ;
198
- span . style . marginLeft = '2px' ;
199
- span . style . marginTop = '2px' ;
200
- span . style . backgroundColor = 'antiquewhite' ;
201
- span . style . border = '1px solid black' ;
202
- span . style . borderRadius = '12px' ;
203
- span . style . padding = '2px 6px' ;
204
- span . style . width = '16rem' ;
205
- span . style . textAlign = 'justify' ;
206
- span . style . fontWeight = 'lighter' ;
207
- span . style . color = 'black' ;
208
- span . style . zIndex = '1000000000' ; // This ensures it appears above other elements
209
- span . style . fontSize = '14px' ;
210
- span . style . textDecoration = 'none' ;
211
- span . style . fontStyle = 'normal' ;
212
-
213
- span . innerHTML = `${ targetWord } is an offensive word` ;
214
-
215
- jsonData . forEach ( ( slur ) => {
216
- const slurWord = Object . keys ( slur ) [ 0 ] ;
217
- if ( slurWord . toLowerCase ( ) === targetWord . toLowerCase ( ) ) {
218
- const slurDetails = slur [ slurWord ] ;
219
- let levelOfSeverity = slurDetails [ 'Level of Severity' ] ;
220
- let casual = slurDetails [ 'Casual' ] ;
221
- let approapriated = slurDetails [ 'Appropriated' ] ;
222
- let reason =
223
- slurDetails [
224
- 'If, Appropriated, Is it by Community or Others?'
225
- ] ;
226
- let problematic = slurDetails [ 'What Makes it Problematic?' ] ;
227
- let categories = slurDetails [ 'Categories' ] ;
228
- let htmlContent = `` ;
229
- if ( levelOfSeverity ) {
230
- htmlContent += `<p><span class="label"><b>Level of Severity:</b></span> ${ levelOfSeverity } </p>` ;
231
- }
232
- if ( casual ) {
233
- htmlContent += `<p><span class="label"><b>Casual:</b></span> ${ casual } </p>` ;
234
- }
235
- if ( approapriated ) {
236
- htmlContent += `<p><span class="label"><b>Appropriated:</b></span> ${ approapriated } </p>` ;
237
- }
238
- if ( reason ) {
239
- htmlContent += `<p><span class="label"><b>If, Appropriated, Is it by Community or Others?:</b></span> ${ reason } </p>` ;
240
- }
241
- if ( problematic ) {
242
- htmlContent += `<p><span class="label"><b>What Makes it Problematic?:</b></span> ${ problematic } </p>` ;
243
- }
244
- if ( categories . length > 0 ) {
245
- htmlContent += `<p><span class="label"><b>Categories:</b></span> ${ slurDetails [
246
- 'Categories'
247
- ] . join ( ', ' ) } </p>`;
248
- }
249
- span . innerHTML = htmlContent ;
250
- }
251
- } ) ;
252
-
253
-
254
-
255
- // sup.appendChild(span)
256
-
257
- // element.append(sup)
258
- // element.append(img)
259
- // let sups = element.children[0]
260
- // let spans = element.children[0].children[1]
261
- // // const svgs = element.children[0].children[0];
262
- // const svgs = element.children[element.children.length-1];
263
- // svgs.addEventListener('mouseover', function () {
264
- // sups.children[0].style.display = "inline-block"
265
- // });
159
+ elements . forEach ( ( element ) => {
160
+ const slur = element . querySelector ( '.slur' ) ;
161
+ if ( ! slur ) return ;
266
162
267
- // svgs.addEventListener('mouseout', function () {
268
- // sups.children[0].style.display = "none"
269
- // });
163
+ // Add hover styles
164
+ slur . style . backgroundColor = '#ffde2155' ;
165
+ slur . style . boxShadow = '0px 0px 5px #ffde21' ;
166
+ slur . style . cursor = 'pointer' ;
270
167
271
- sup . appendChild ( span ) ;
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 ) ;
177
+ }
272
178
273
- // console.log("Element first child",element.children[0])
274
- // console.log("Element last child",element.children[element.children.length-1])
275
- // console.log("SUP: ",sup )
276
- // console.log("ELEMENT IS: ",element)
277
- // console.log("ELEMENT INNERHTML: ",element.innerHTML)
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 ] ] : { } ;
278
184
279
- element . append ( span ) ;
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` ;
280
189
281
- // console.log("ELEMENT AFTER IS: ",element)
282
- // element.append(img)
283
- let slur = element . children [ 0 ] ;
284
- slur . style . backgroundColor = '#ffde2155' ;
285
- slur . style . boxShadow = '0px 0px 5px #ffde21' ;
286
- slur . style . cursor = 'pointer' ;
190
+ tooltipContainer . root . render (
191
+ < HoverSlurMetadata slurDetails = { slurDetails } />
192
+ ) ;
193
+ } ;
287
194
288
- let metabox = element . children [ 1 ] ;
289
- // console.log("METABOX IS: ",metabox)
290
- let spans = element . children [ 0 ] . children [ 1 ] ;
291
- // const svgs = element.children[0].children[0];
292
- const svgs = element . children [ element . children . length - 1 ] ;
293
- slur . addEventListener ( 'mouseover' , function ( ) {
294
- metabox . style . display = 'inline-block' ;
295
- } ) ;
195
+ const handleMouseOut = ( ) => {
196
+ tooltipContainer . root . render ( null ) ;
197
+ } ;
296
198
297
- slur . addEventListener ( 'mouseout' , function ( ) {
298
- metabox . style . display = 'none' ;
299
- } ) ;
199
+ slur . addEventListener ( 'mouseover' , handleMouseOver ) ;
200
+ slur . addEventListener ( 'mouseout' , handleMouseOut ) ;
300
201
} ) ;
301
202
} ) ;
302
203
}
303
204
304
205
export default {
305
206
processNewlyAddedNodesGeneral,
306
207
processNewlyAddedNodesGeneral2,
307
- addMetaData,
308
- locateSlur,
309
- findPositions,
310
- getAllTextNodes,
311
- checkFalseTextNode
312
- } ;
208
+ } ;
0 commit comments