Skip to content

Commit 4ecd255

Browse files
committed
Create GlossaryInjector.tsx
1 parent 3cd98c7 commit 4ecd255

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React, { useEffect, useState } from 'react';
2+
import ReactDOM from 'react-dom';
3+
import GlossaryTooltip from './GlossaryTooltip';
4+
5+
interface GlossaryInjectorProps {
6+
children: React.ReactNode;
7+
}
8+
9+
const GlossaryInjector: React.FC<GlossaryInjectorProps> = ({ children }) => {
10+
const [glossary, setGlossary] = useState<{ [key: string]: string }>({});
11+
12+
useEffect(() => {
13+
// Fetch glossary.json on component mount.
14+
fetch('/glossary.json')
15+
.then((res) => res.json())
16+
.then(setGlossary)
17+
.catch((err) => console.error('Failed to load glossary:', err));
18+
}, []);
19+
20+
useEffect(() => {
21+
if (Object.keys(glossary).length === 0) return;
22+
23+
const terms = Object.keys(glossary);
24+
25+
const wrapTermsInTooltips = (node: HTMLElement) => {
26+
const textNodes = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, null, false);
27+
let currentNode: Node | null;
28+
29+
// Store modifications for later processing.
30+
const modifications: { originalNode: Node; newNodes: Node[] }[] = [];
31+
32+
while ((currentNode = textNodes.nextNode())) {
33+
const parentElement = currentNode.parentElement;
34+
35+
// Only wrap terms if the parent is a <p> or <li> element.
36+
if (parentElement && (parentElement.tagName.toLowerCase() === 'p' || parentElement.tagName.toLowerCase() === 'li')) {
37+
const newNodes: Node[] = []; // Array to hold the new nodes.
38+
let lastIndex = 0; // Track the last index for proper splitting.
39+
40+
terms.forEach((term) => {
41+
const regex = new RegExp(`\\b(${term})\\b`, 'gi'); // Case-insensitive match
42+
let match: RegExpExecArray | null;
43+
44+
// If a match is found
45+
while ((match = regex.exec(currentNode.textContent || ''))) {
46+
// Add text before the match.
47+
if (lastIndex < match.index) {
48+
newNodes.push(document.createTextNode(currentNode.textContent!.slice(lastIndex, match.index)));
49+
}
50+
51+
// Create and push the GlossaryTooltip for the matched term.
52+
const tooltipWrapper = document.createElement('span');
53+
tooltipWrapper.setAttribute('data-term', term);
54+
tooltipWrapper.className = 'glossary-term'; // Add a class for styling.
55+
56+
// Get the definition for the matched term.
57+
const definition = glossary[term]; // Access the definition from the glossary.
58+
59+
// Render the Tooltip with the definition text.
60+
ReactDOM.render(<GlossaryTooltip term={term} definition={definition}>{match[0]}</GlossaryTooltip>, tooltipWrapper);
61+
62+
newNodes.push(tooltipWrapper); // Push tooltip wrapper.
63+
64+
// Update lastIndex to the end of the match.
65+
lastIndex = match.index + match[0].length;
66+
}
67+
});
68+
69+
// Add any remaining text after the last match.
70+
if (lastIndex < currentNode.textContent!.length) {
71+
newNodes.push(document.createTextNode(currentNode.textContent!.slice(lastIndex)));
72+
}
73+
74+
// If new nodes are created, we need to store the modification.
75+
if (newNodes.length > 0) {
76+
modifications.push({ originalNode: currentNode, newNodes });
77+
}
78+
}
79+
}
80+
81+
// Apply the modifications outside of the tree walker iteration.
82+
modifications.forEach(({ originalNode, newNodes }) => {
83+
const parentElement = originalNode.parentElement;
84+
if (parentElement) {
85+
newNodes.forEach((newNode) => {
86+
parentElement.insertBefore(newNode, originalNode);
87+
});
88+
parentElement.removeChild(originalNode); // Remove the original text node.
89+
}
90+
});
91+
};
92+
93+
// Process <p> and <li> elements in the document body.
94+
const paragraphsAndLists = document.querySelectorAll('p, li');
95+
paragraphsAndLists.forEach((node) => wrapTermsInTooltips(node));
96+
}, [glossary]);
97+
98+
return <>{children}</>;
99+
};
100+
101+
export default GlossaryInjector;

0 commit comments

Comments
 (0)