@@ -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