@@ -2,6 +2,8 @@ import { replaceSlur } from './slur-replace';
22import { log } from './logger' ;
33import repository from './repository' ;
44const { getPreferenceData } = repository ;
5+ import { createRoot } from 'react-dom/client' ;
6+ import HoverSlurMetadata from './ui-components/atoms/HoverSlurMetadata' ;
57
68// Traverse dom nodes to find leaf node that are text nodes and process
79function bft ( node ) {
@@ -49,19 +51,6 @@ function setCaretPosition(element, offset) {
4951 sel . addRange ( range ) ;
5052}
5153
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-
6554const processNewlyAddedNodesGeneral = function ( firstBody ) {
6655 log ( 'processing new nodes' ) ;
6756 const config = { attributes : true , childList : true , subtree : true } ;
@@ -85,6 +74,16 @@ const processNewlyAddedNodesGeneral = function (firstBody) {
8574 observer . observe ( firstBody , config ) ;
8675} ;
8776
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+
8887function checkFalseTextNode ( text , actualLengthOfText ) {
8988 let totalNewlineAndWhitespaces = 0 ;
9089 for ( let i = 0 ; i < text . length ; i ++ ) {
@@ -95,7 +94,6 @@ function checkFalseTextNode(text, actualLengthOfText) {
9594 return totalNewlineAndWhitespaces === actualLengthOfText ;
9695}
9796
98- // Function to recursively get all text nodes for a given node
9997function getAllTextNodes ( node , uliStore ) {
10098 if ( node . nodeType === 3 ) {
10199 if ( ! checkFalseTextNode ( node . data , node . length ) ) {
@@ -125,188 +123,86 @@ function findPositions(word, text) {
125123}
126124
127125function 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 => {
133127 let textnode = store . node ;
134- let text = store . node . textContent ;
135128 let tempParent = document . createElement ( 'span' ) ;
136- tempParent . textContent = text ;
137- let slurs = [ ] ;
129+ tempParent . textContent = textnode . textContent ;
138130 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+ } ) ;
156143 slurPresentInTempParent = true ;
157- // }
158144 }
159145 } ) ;
160- uliStore [ i ] . nodeToParent = tempParent ;
161- uliStore [ i ] . slurs = slurs ;
162146
163- //O(1) complexity
164147 if ( slurPresentInTempParent ) {
165148 textnode . replaceWith ( tempParent ) ;
166149 }
167-
168- // console.log("TEMPParent: ",tempParent)
169- }
170- return uliStore ;
150+ } ) ;
171151}
172152
173153function addMetaData ( targetWords , jsonData ) {
174154 targetWords . forEach ( ( targetWord ) => {
175- const className = `slur-container-${ targetWord } ` ;
155+ const sanitizedTargetWord = targetWord . replace ( / \s + / g, '-' ) ;
156+ const className = `slur-container-${ sanitizedTargetWord } ` ;
176157 const elements = Array . from ( document . querySelectorAll ( `.${ className } ` ) ) ;
177- // console.log("ELEMENTS are: ",elements)
178- elements . forEach ( ( element ) => {
179- // console.log("ELements InnerHTML:",element.innerHTML)
180158
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 ;
266162
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' ;
270167
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+ }
272178
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 ] ] : { } ;
278184
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` ;
280189
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+ } ;
287194
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+ } ;
296198
297- slur . addEventListener ( 'mouseout' , function ( ) {
298- metabox . style . display = 'none' ;
299- } ) ;
199+ slur . addEventListener ( 'mouseover' , handleMouseOver ) ;
200+ slur . addEventListener ( 'mouseout' , handleMouseOut ) ;
300201 } ) ;
301202 } ) ;
302203}
303204
304205export default {
305206 processNewlyAddedNodesGeneral,
306207 processNewlyAddedNodesGeneral2,
307- addMetaData,
308- locateSlur,
309- findPositions,
310- getAllTextNodes,
311- checkFalseTextNode
312- } ;
208+ } ;
0 commit comments