From 0e0a02fe1d05b457d8ccd3102f7729f1f10be005 Mon Sep 17 00:00:00 2001 From: Josh Wong <23216828+josh-wong@users.noreply.github.com> Date: Thu, 22 May 2025 17:10:41 +0900 Subject: [PATCH 1/3] Make glossary terms in backticks not show tooltip --- src/components/GlossaryInjector.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/GlossaryInjector.tsx b/src/components/GlossaryInjector.tsx index 18ab7518..5be4cb0a 100644 --- a/src/components/GlossaryInjector.tsx +++ b/src/components/GlossaryInjector.tsx @@ -58,6 +58,9 @@ const GlossaryInjector: React.FC = ({ children }) => { // Check if the parent element is a Mermaid diagram. const isMermaidDiagram = parentElement && parentElement.closest('.docusaurus-mermaid-container'); // Adjust the selector as necessary. + // Check if the parent element is an inline code block (text in backticks). + const isInlineCode = parentElement && (parentElement.tagName === 'CODE' || parentElement.classList.contains('inlineCode')); + // Only wrap terms in tooltips if the parent is within the target div and not in headings or tab titles. if ( parentElement && @@ -66,7 +69,8 @@ const GlossaryInjector: React.FC = ({ children }) => { !isTabTitle && // Skip tab titles. !isCodeBlock && // Skip code blocks. !isCard && // Skip Cards. - !isMermaidDiagram // Skip Mermaid diagrams. + !isMermaidDiagram && // Skip Mermaid diagrams. + !isInlineCode // Skip inline code (text in backticks). ) { let currentText = currentNode.textContent!; const newNodes: Node[] = []; From 3de804bc665df61e6cde56c3fef51af387f1672d Mon Sep 17 00:00:00 2001 From: Josh Wong <23216828+josh-wong@users.noreply.github.com> Date: Thu, 22 May 2025 17:10:45 +0900 Subject: [PATCH 2/3] Make glossary terms not case-sensitive --- src/components/GlossaryInjector.tsx | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/components/GlossaryInjector.tsx b/src/components/GlossaryInjector.tsx index 5be4cb0a..e760a0f3 100644 --- a/src/components/GlossaryInjector.tsx +++ b/src/components/GlossaryInjector.tsx @@ -76,21 +76,28 @@ const GlossaryInjector: React.FC = ({ children }) => { const newNodes: Node[] = []; let hasReplacements = false; - // Create a regex pattern to match all terms (case-sensitive). - const regexPattern = terms.map(term => `(${term})`).join('|'); - const regex = new RegExp(regexPattern, 'g'); + // Create a regex pattern to match all terms as whole words (case-insensitive). + const regexPattern = terms.map(term => { + const escapedTerm = term.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + // Match exact terms at word boundaries. + return `(\\b${escapedTerm}\\b)`; + }).join('|'); + const regex = new RegExp(regexPattern, 'gi'); // The 'i' flag is for case-insensitive matching. let lastIndex = 0; let match: RegExpExecArray | null; while ((match = regex.exec(currentText))) { - const matchedTerm = match[0]; + const matchedText = match[0]; // The actual text as it appears in the content. + const matchedTerm = terms.find(term => + term.toLowerCase() === matchedText.toLowerCase() + ) || matchedText; // Find the canonical term in the glossary. if (lastIndex < match.index) { newNodes.push(document.createTextNode(currentText.slice(lastIndex, match.index))); } - const isFirstMention = !processedTerms.has(matchedTerm); + const isFirstMention = !processedTerms.has(matchedTerm.toLowerCase()); const isLink = parentElement && parentElement.tagName === 'A'; // Check if the parent is a link. if (isFirstMention && !isLink) { @@ -99,26 +106,26 @@ const GlossaryInjector: React.FC = ({ children }) => { tooltipWrapper.setAttribute('data-term', matchedTerm); tooltipWrapper.className = 'glossary-term'; - const definition = glossary[matchedTerm]; // Exact match from glossary. + const definition = glossary[matchedTerm]; // Get definition using the canonical term. ReactDOM.render( - {matchedTerm} + {matchedText}{/* No space after the term. */} , tooltipWrapper ); newNodes.push(tooltipWrapper); - processedTerms.add(matchedTerm); // Mark this term as processed. + processedTerms.add(matchedTerm.toLowerCase()); // Mark this term as processed (case-insensitive). } else if (isLink) { // If it's a link, we skip this mention but do not mark it as processed. - newNodes.push(document.createTextNode(matchedTerm)); + newNodes.push(document.createTextNode(matchedText)); } else { // If it's not the first mention, just add the plain text. - newNodes.push(document.createTextNode(matchedTerm)); + newNodes.push(document.createTextNode(matchedText)); } - lastIndex = match.index + matchedTerm.length; + lastIndex = match.index + matchedText.length; hasReplacements = true; } From c78a4af71d08ed2d2df91535424c4d4aa0d3b457 Mon Sep 17 00:00:00 2001 From: Josh Wong <23216828+josh-wong@users.noreply.github.com> Date: Thu, 22 May 2025 17:30:17 +0900 Subject: [PATCH 3/3] Ensure tooltips show for plural forms of glossary terms --- src/components/GlossaryInjector.tsx | 46 +++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/components/GlossaryInjector.tsx b/src/components/GlossaryInjector.tsx index e760a0f3..9778baca 100644 --- a/src/components/GlossaryInjector.tsx +++ b/src/components/GlossaryInjector.tsx @@ -76,11 +76,11 @@ const GlossaryInjector: React.FC = ({ children }) => { const newNodes: Node[] = []; let hasReplacements = false; - // Create a regex pattern to match all terms as whole words (case-insensitive). + // Create a regex pattern to match both exact terms and their plural forms. const regexPattern = terms.map(term => { const escapedTerm = term.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - // Match exact terms at word boundaries. - return `(\\b${escapedTerm}\\b)`; + // Match exact term or term followed by 's' or 'es' at word boundary. + return `(\\b${escapedTerm}(s|es)?\\b)`; }).join('|'); const regex = new RegExp(regexPattern, 'gi'); // The 'i' flag is for case-insensitive matching. @@ -88,35 +88,55 @@ const GlossaryInjector: React.FC = ({ children }) => { let match: RegExpExecArray | null; while ((match = regex.exec(currentText))) { - const matchedText = match[0]; // The actual text as it appears in the content. - const matchedTerm = terms.find(term => - term.toLowerCase() === matchedText.toLowerCase() - ) || matchedText; // Find the canonical term in the glossary. + const matchedText = match[0]; // The full matched text (may include plural suffix). + + // Find the base term from the glossary that matches (without plural). + const baseTerm = terms.find(term => + matchedText.toLowerCase() === term.toLowerCase() || + matchedText.toLowerCase() === `${term.toLowerCase()}s` || + matchedText.toLowerCase() === `${term.toLowerCase()}es` + ); + + if (!baseTerm) { + // Skip if no matching base term found. + continue; + } if (lastIndex < match.index) { newNodes.push(document.createTextNode(currentText.slice(lastIndex, match.index))); } - const isFirstMention = !processedTerms.has(matchedTerm.toLowerCase()); + const isFirstMention = !processedTerms.has(baseTerm.toLowerCase()); const isLink = parentElement && parentElement.tagName === 'A'; // Check if the parent is a link. if (isFirstMention && !isLink) { // Create a tooltip only if it's the first mention and not a link. const tooltipWrapper = document.createElement('span'); - tooltipWrapper.setAttribute('data-term', matchedTerm); + tooltipWrapper.setAttribute('data-term', baseTerm); tooltipWrapper.className = 'glossary-term'; - const definition = glossary[matchedTerm]; // Get definition using the canonical term. + const definition = glossary[baseTerm]; + + // Extract the part to underline (the base term) and the suffix (if plural). + let textToUnderline = matchedText; + let suffix = ''; + + if (matchedText.toLowerCase() !== baseTerm.toLowerCase()) { + // This is a plural form - only underline the base part. + const baseTermLength = baseTerm.length; + textToUnderline = matchedText.substring(0, baseTermLength); + suffix = matchedText.substring(baseTermLength); + } ReactDOM.render( - - {matchedText}{/* No space after the term. */} + + {textToUnderline}{suffix && {suffix}} , tooltipWrapper ); newNodes.push(tooltipWrapper); - processedTerms.add(matchedTerm.toLowerCase()); // Mark this term as processed (case-insensitive). + processedTerms.add(baseTerm.toLowerCase()); } else if (isLink) { // If it's a link, we skip this mention but do not mark it as processed. newNodes.push(document.createTextNode(matchedText));