Skip to content

Commit c78a4af

Browse files
committed
Ensure tooltips show for plural forms of glossary terms
1 parent 3de804b commit c78a4af

File tree

1 file changed

+33
-13
lines changed

1 file changed

+33
-13
lines changed

src/components/GlossaryInjector.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,47 +76,67 @@ const GlossaryInjector: React.FC<GlossaryInjectorProps> = ({ children }) => {
7676
const newNodes: Node[] = [];
7777
let hasReplacements = false;
7878

79-
// Create a regex pattern to match all terms as whole words (case-insensitive).
79+
// Create a regex pattern to match both exact terms and their plural forms.
8080
const regexPattern = terms.map(term => {
8181
const escapedTerm = term.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
82-
// Match exact terms at word boundaries.
83-
return `(\\b${escapedTerm}\\b)`;
82+
// Match exact term or term followed by 's' or 'es' at word boundary.
83+
return `(\\b${escapedTerm}(s|es)?\\b)`;
8484
}).join('|');
8585
const regex = new RegExp(regexPattern, 'gi'); // The 'i' flag is for case-insensitive matching.
8686

8787
let lastIndex = 0;
8888
let match: RegExpExecArray | null;
8989

9090
while ((match = regex.exec(currentText))) {
91-
const matchedText = match[0]; // The actual text as it appears in the content.
92-
const matchedTerm = terms.find(term =>
93-
term.toLowerCase() === matchedText.toLowerCase()
94-
) || matchedText; // Find the canonical term in the glossary.
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+
}
95104

96105
if (lastIndex < match.index) {
97106
newNodes.push(document.createTextNode(currentText.slice(lastIndex, match.index)));
98107
}
99108

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

103112
if (isFirstMention && !isLink) {
104113
// Create a tooltip only if it's the first mention and not a link.
105114
const tooltipWrapper = document.createElement('span');
106-
tooltipWrapper.setAttribute('data-term', matchedTerm);
115+
tooltipWrapper.setAttribute('data-term', baseTerm);
107116
tooltipWrapper.className = 'glossary-term';
108117

109-
const definition = glossary[matchedTerm]; // Get definition using the canonical term.
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+
}
110130

111131
ReactDOM.render(
112-
<GlossaryTooltip term={matchedTerm} definition={definition}>
113-
{matchedText}{/* No space after the term. */}
132+
<GlossaryTooltip term={baseTerm} definition={definition}>
133+
{textToUnderline}{suffix && <span className="no-underline">{suffix}</span>}
114134
</GlossaryTooltip>,
115135
tooltipWrapper
116136
);
117137

118138
newNodes.push(tooltipWrapper);
119-
processedTerms.add(matchedTerm.toLowerCase()); // Mark this term as processed (case-insensitive).
139+
processedTerms.add(baseTerm.toLowerCase());
120140
} else if (isLink) {
121141
// If it's a link, we skip this mention but do not mark it as processed.
122142
newNodes.push(document.createTextNode(matchedText));

0 commit comments

Comments
 (0)