Skip to content

Commit 6058674

Browse files
authored
Refine glossary tooltip feature (support capitalized terms and plural forms, remove tooltip from terms in backticks) (#1228)
* Make glossary terms in backticks not show tooltip * Make glossary terms not case-sensitive * Ensure tooltips show for plural forms of glossary terms
1 parent 0190e74 commit 6058674

File tree

1 file changed

+45
-14
lines changed

1 file changed

+45
-14
lines changed

src/components/GlossaryInjector.tsx

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ const GlossaryInjector: React.FC<GlossaryInjectorProps> = ({ children }) => {
5858
// Check if the parent element is a Mermaid diagram.
5959
const isMermaidDiagram = parentElement && parentElement.closest('.docusaurus-mermaid-container'); // Adjust the selector as necessary.
6060

61+
// Check if the parent element is an inline code block (text in backticks).
62+
const isInlineCode = parentElement && (parentElement.tagName === 'CODE' || parentElement.classList.contains('inlineCode'));
63+
6164
// Only wrap terms in tooltips if the parent is within the target div and not in headings or tab titles.
6265
if (
6366
parentElement &&
@@ -66,55 +69,83 @@ const GlossaryInjector: React.FC<GlossaryInjectorProps> = ({ children }) => {
6669
!isTabTitle && // Skip tab titles.
6770
!isCodeBlock && // Skip code blocks.
6871
!isCard && // Skip Cards.
69-
!isMermaidDiagram // Skip Mermaid diagrams.
72+
!isMermaidDiagram && // Skip Mermaid diagrams.
73+
!isInlineCode // Skip inline code (text in backticks).
7074
) {
7175
let currentText = currentNode.textContent!;
7276
const newNodes: Node[] = [];
7377
let hasReplacements = false;
7478

75-
// Create a regex pattern to match all terms (case-sensitive).
76-
const regexPattern = terms.map(term => `(${term})`).join('|');
77-
const regex = new RegExp(regexPattern, 'g');
79+
// Create a regex pattern to match both exact terms and their plural forms.
80+
const regexPattern = terms.map(term => {
81+
const escapedTerm = term.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
82+
// Match exact term or term followed by 's' or 'es' at word boundary.
83+
return `(\\b${escapedTerm}(s|es)?\\b)`;
84+
}).join('|');
85+
const regex = new RegExp(regexPattern, 'gi'); // The 'i' flag is for case-insensitive matching.
7886

7987
let lastIndex = 0;
8088
let match: RegExpExecArray | null;
8189

8290
while ((match = regex.exec(currentText))) {
83-
const matchedTerm = match[0];
91+
const matchedText = match[0]; // The full matched text (may include plural suffix).
92+
93+
// Find the base term from the glossary that matches (without plural).
94+
const baseTerm = terms.find(term =>
95+
matchedText.toLowerCase() === term.toLowerCase() ||
96+
matchedText.toLowerCase() === `${term.toLowerCase()}s` ||
97+
matchedText.toLowerCase() === `${term.toLowerCase()}es`
98+
);
99+
100+
if (!baseTerm) {
101+
// Skip if no matching base term found.
102+
continue;
103+
}
84104

85105
if (lastIndex < match.index) {
86106
newNodes.push(document.createTextNode(currentText.slice(lastIndex, match.index)));
87107
}
88108

89-
const isFirstMention = !processedTerms.has(matchedTerm);
109+
const isFirstMention = !processedTerms.has(baseTerm.toLowerCase());
90110
const isLink = parentElement && parentElement.tagName === 'A'; // Check if the parent is a link.
91111

92112
if (isFirstMention && !isLink) {
93113
// Create a tooltip only if it's the first mention and not a link.
94114
const tooltipWrapper = document.createElement('span');
95-
tooltipWrapper.setAttribute('data-term', matchedTerm);
115+
tooltipWrapper.setAttribute('data-term', baseTerm);
96116
tooltipWrapper.className = 'glossary-term';
97117

98-
const definition = glossary[matchedTerm]; // Exact match from glossary.
118+
const definition = glossary[baseTerm];
119+
120+
// Extract the part to underline (the base term) and the suffix (if plural).
121+
let textToUnderline = matchedText;
122+
let suffix = '';
123+
124+
if (matchedText.toLowerCase() !== baseTerm.toLowerCase()) {
125+
// This is a plural form - only underline the base part.
126+
const baseTermLength = baseTerm.length;
127+
textToUnderline = matchedText.substring(0, baseTermLength);
128+
suffix = matchedText.substring(baseTermLength);
129+
}
99130

100131
ReactDOM.render(
101-
<GlossaryTooltip term={matchedTerm} definition={definition}>
102-
{matchedTerm}
132+
<GlossaryTooltip term={baseTerm} definition={definition}>
133+
{textToUnderline}{suffix && <span className="no-underline">{suffix}</span>}
103134
</GlossaryTooltip>,
104135
tooltipWrapper
105136
);
106137

107138
newNodes.push(tooltipWrapper);
108-
processedTerms.add(matchedTerm); // Mark this term as processed.
139+
processedTerms.add(baseTerm.toLowerCase());
109140
} else if (isLink) {
110141
// If it's a link, we skip this mention but do not mark it as processed.
111-
newNodes.push(document.createTextNode(matchedTerm));
142+
newNodes.push(document.createTextNode(matchedText));
112143
} else {
113144
// If it's not the first mention, just add the plain text.
114-
newNodes.push(document.createTextNode(matchedTerm));
145+
newNodes.push(document.createTextNode(matchedText));
115146
}
116147

117-
lastIndex = match.index + matchedTerm.length;
148+
lastIndex = match.index + matchedText.length;
118149
hasReplacements = true;
119150
}
120151

0 commit comments

Comments
 (0)