@@ -10,89 +10,141 @@ const GlossaryInjector: React.FC<GlossaryInjectorProps> = ({ children }) => {
1010 const [ glossary , setGlossary ] = useState < { [ key : string ] : string } > ( { } ) ;
1111
1212 useEffect ( ( ) => {
13- // Fetch glossary.json on component mount.
14- fetch ( '/glossary.json' )
15- . then ( ( res ) => res . json ( ) )
13+ const url = window . location . pathname ;
14+ let glossaryPath = '/docs/glossary.json' ; // Use the English version as the default glossary.
15+
16+ if ( process . env . NODE_ENV === 'production' ) { // The glossary tooltip works only in production environments.
17+ glossaryPath = url . startsWith ( '/ja-jp/docs' ) ? '/ja-jp/glossary.json' : '/docs/glossary.json' ;
18+ } else {
19+ glossaryPath = url . startsWith ( '/ja-jp/docs' ) ? '/ja-jp/glossary.json' : '/docs/glossary.json' ;
20+ }
21+
22+ fetch ( glossaryPath )
23+ . then ( ( res ) => {
24+ if ( ! res . ok ) {
25+ throw new Error ( `HTTP error! status: ${ res . status } ` ) ;
26+ }
27+ return res . json ( ) ;
28+ } )
1629 . then ( setGlossary )
1730 . catch ( ( err ) => console . error ( 'Failed to load glossary:' , err ) ) ;
1831 } , [ ] ) ;
1932
2033 useEffect ( ( ) => {
2134 if ( Object . keys ( glossary ) . length === 0 ) return ;
2235
23- const terms = Object . keys ( glossary ) ;
36+ // Sort terms in descending order by length to prioritize multi-word terms.
37+ const terms = Object . keys ( glossary ) . sort ( ( a , b ) => b . length - a . length ) ;
38+ const processedTerms = new Set < string > ( ) ; // Set to track processed terms.
2439
2540 const wrapTermsInTooltips = ( node : HTMLElement ) => {
2641 const textNodes = document . createTreeWalker ( node , NodeFilter . SHOW_TEXT , null , false ) ;
2742 let currentNode : Node | null ;
2843
29- // Store modifications for later processing.
3044 const modifications : { originalNode : Node ; newNodes : Node [ ] } [ ] = [ ] ;
3145
3246 while ( ( currentNode = textNodes . nextNode ( ) ) ) {
3347 const parentElement = currentNode . parentElement ;
3448
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.
49+ // Check if the parent element is a tab title.
50+ const isTabTitle = parentElement && parentElement . closest ( '.tabs__item' ) ; // Adjust the selector as necessary.
3951
40- terms . forEach ( ( term ) => {
41- const regex = new RegExp ( `\\b(${ term } )\\b` , 'gi' ) ; // Case-insensitive match
42- let match : RegExpExecArray | null ;
52+ // Check if the parent element is a code block.
53+ const isCodeBlock = parentElement && parentElement . closest ( '.prism-code' ) ; // Adjust the selector as necessary.
4354
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- }
55+ // Check if the parent element is a Card.
56+ const isCard = parentElement && parentElement . closest ( '.card__body' ) ; // Adjust the selector as necessary.
5057
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.
58+ // Check if the parent element is a Mermaid diagram.
59+ const isMermaidDiagram = parentElement && parentElement . closest ( '.docusaurus-mermaid-container' ) ; // Adjust the selector as necessary.
60+
61+ // Only wrap terms in tooltips if the parent is within the target div and not in headings or tab titles.
62+ if (
63+ parentElement &&
64+ parentElement . closest ( '.theme-doc-markdown.markdown' ) &&
65+ ! / ^ H [ 1 - 6 ] $ / . test ( parentElement . tagName ) && // Skip headings (H1 to H6).
66+ ! isTabTitle && // Skip tab titles.
67+ ! isCodeBlock && // Skip code blocks.
68+ ! isCard && // Skip Cards.
69+ ! isMermaidDiagram // Skip Mermaid diagrams.
70+ ) {
71+ let currentText = currentNode . textContent ! ;
72+ const newNodes : Node [ ] = [ ] ;
73+ let hasReplacements = false ;
5574
56- // Get the definition for the matched term.
57- const definition = glossary [ term ] ; // Access the definition from the glossary.
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' ) ;
5878
59- // Render the Tooltip with the definition text.
60- ReactDOM . render ( < GlossaryTooltip term = { term } definition = { definition } > { match [ 0 ] } </ GlossaryTooltip > , tooltipWrapper ) ;
79+ let lastIndex = 0 ;
80+ let match : RegExpExecArray | null ;
6181
62- newNodes . push ( tooltipWrapper ) ; // Push tooltip wrapper.
82+ while ( ( match = regex . exec ( currentText ) ) ) {
83+ const matchedTerm = match [ 0 ] ;
6384
64- // Update lastIndex to the end of the match.
65- lastIndex = match . index + match [ 0 ] . length ;
85+ if ( lastIndex < match . index ) {
86+ newNodes . push ( document . createTextNode ( currentText . slice ( lastIndex , match . index ) ) ) ;
6687 }
67- } ) ;
6888
69- // Add any remaining text after the last match.
70- if ( lastIndex < currentNode . textContent ! . length ) {
71- newNodes . push ( document . createTextNode ( currentNode . textContent ! . slice ( lastIndex ) ) ) ;
89+ const isFirstMention = ! processedTerms . has ( matchedTerm ) ;
90+ const isLink = parentElement && parentElement . tagName === 'A' ; // Check if the parent is a link.
91+
92+ if ( isFirstMention && ! isLink ) {
93+ // Create a tooltip only if it's the first mention and not a link.
94+ const tooltipWrapper = document . createElement ( 'span' ) ;
95+ tooltipWrapper . setAttribute ( 'data-term' , matchedTerm ) ;
96+ tooltipWrapper . className = 'glossary-term' ;
97+
98+ const definition = glossary [ matchedTerm ] ; // Exact match from glossary.
99+
100+ ReactDOM . render (
101+ < GlossaryTooltip term = { matchedTerm } definition = { definition } >
102+ { matchedTerm }
103+ </ GlossaryTooltip > ,
104+ tooltipWrapper
105+ ) ;
106+
107+ newNodes . push ( tooltipWrapper ) ;
108+ processedTerms . add ( matchedTerm ) ; // Mark this term as processed.
109+ } else if ( isLink ) {
110+ // If it's a link, we skip this mention but do not mark it as processed.
111+ newNodes . push ( document . createTextNode ( matchedTerm ) ) ;
112+ } else {
113+ // If it's not the first mention, just add the plain text.
114+ newNodes . push ( document . createTextNode ( matchedTerm ) ) ;
115+ }
116+
117+ lastIndex = match . index + matchedTerm . length ;
118+ hasReplacements = true ;
119+ }
120+
121+ if ( lastIndex < currentText . length ) {
122+ newNodes . push ( document . createTextNode ( currentText . slice ( lastIndex ) ) ) ;
72123 }
73124
74- // If new nodes are created, we need to store the modification.
75- if ( newNodes . length > 0 ) {
125+ if ( hasReplacements ) {
76126 modifications . push ( { originalNode : currentNode , newNodes } ) ;
77127 }
78128 }
79129 }
80130
81- // Apply the modifications outside of the tree walker iteration .
131+ // Replace the original nodes with new nodes .
82132 modifications . forEach ( ( { originalNode, newNodes } ) => {
83133 const parentElement = originalNode . parentElement ;
84134 if ( parentElement ) {
85135 newNodes . forEach ( ( newNode ) => {
86136 parentElement . insertBefore ( newNode , originalNode ) ;
87137 } ) ;
88- parentElement . removeChild ( originalNode ) ; // Remove the original text node.
138+ parentElement . removeChild ( originalNode ) ;
89139 }
90140 } ) ;
91141 } ;
92142
93- // Process <p> and <li> elements in the document body.
94- const paragraphsAndLists = document . querySelectorAll ( 'p, li' ) ;
95- paragraphsAndLists . forEach ( ( node ) => wrapTermsInTooltips ( node ) ) ;
143+ // Target the specific div with the class "theme-doc-markdown markdown".
144+ const targetDiv = document . querySelector ( '.theme-doc-markdown.markdown' ) ;
145+ if ( targetDiv ) {
146+ wrapTermsInTooltips ( targetDiv ) ;
147+ }
96148 } , [ glossary ] ) ;
97149
98150 return < > { children } </ > ;
0 commit comments