1- import React , { useCallback , useEffect , useMemo , useState } from 'react' ;
1+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
22import Fuse from 'fuse.js' ;
33import clsx from 'clsx' ;
44import { marked } from 'marked' ;
@@ -31,29 +31,34 @@ const slugify = (text: string): string =>
3131 . replace ( / [ ^ a - z 0 - 9 ] + / g, '-' )
3232 . replace ( / ( ^ - + | - + $ ) / g, '' ) ;
3333
34- const sanitizeMarkdown = ( markdown : string , purify : typeof DOMPurify | null ) : string => {
35- const html = marked . parse ( markdown || '' , { async : false } ) as string ;
36- return purify ? purify . sanitize ( html ) : html ;
37- } ;
38-
3934const GlossaryTable : React . FC = ( ) => {
4035 const [ termQuery , setTermQuery ] = useState ( '' ) ;
4136 const [ textQuery , setTextQuery ] = useState ( '' ) ;
4237 const [ activeSlug , setActiveSlug ] = useState ( '' ) ;
38+ const [ entries , setEntries ] = useState < DisplayEntry [ ] > ( [ ] ) ;
39+ const [ aliasToCanonicalSlug , setAliasToCanonicalSlug ] = useState < Map < string , string > > ( new Map ( ) ) ;
40+ const purifyRef = useRef < typeof DOMPurify | null > ( null ) ;
41+ const [ purifyReady , setPurifyReady ] = useState ( false ) ;
4342
44- const purify = useMemo ( ( ) => ( typeof window === 'undefined' ? null : DOMPurify ) , [ ] ) ;
43+ useEffect ( ( ) => {
44+ if ( typeof window !== 'undefined' ) {
45+ purifyRef . current = DOMPurify ;
46+ setPurifyReady ( true ) ;
47+ }
48+ } , [ ] ) ;
4549
46- const { entries , aliasToCanonicalSlug } = useMemo ( ( ) => {
50+ useEffect ( ( ) => {
4751 const glossaryItems = glossaryData as GlossaryItem [ ] ;
4852 const termSet = new Set ( glossaryItems . map ( ( item ) => item . term ) ) ;
4953
5054 const canonicalEntries : DisplayEntry [ ] = glossaryItems . map ( ( item ) => {
5155 const slug = slugify ( item . term ) ;
56+ const html = marked . parse ( item . definition || '' , { async : false } ) as string ;
5257 return {
5358 term : item . term ,
5459 abbreviation : item . abbreviation ,
5560 definition : item . definition ,
56- definitionHtml : sanitizeMarkdown ( item . definition , purify ) ,
61+ definitionHtml : html ,
5762 canonicalTerm : item . term ,
5863 isAlias : false ,
5964 slug,
@@ -70,7 +75,7 @@ const GlossaryTable: React.FC = () => {
7075 term : alias ,
7176 abbreviation : '' ,
7277 definition : `See ${ item . term } ` ,
73- definitionHtml : '' , // Alias entries don't need HTML since they use a link
78+ definitionHtml : '' ,
7479 canonicalTerm : item . term ,
7580 isAlias : true ,
7681 slug : slugify ( alias ) ,
@@ -84,8 +89,19 @@ const GlossaryTable: React.FC = () => {
8489 aliasEntries . forEach ( ( entry ) => aliasMap . set ( entry . slug , entry . targetSlug ) ) ;
8590
8691 const combined = [ ...canonicalEntries , ...aliasEntries ] . sort ( ( a , b ) => a . term . localeCompare ( b . term ) ) ;
87- return { entries : combined , aliasToCanonicalSlug : aliasMap } ;
88- } , [ purify ] ) ;
92+ setEntries ( combined ) ;
93+ setAliasToCanonicalSlug ( aliasMap ) ;
94+ } , [ ] ) ;
95+
96+ useEffect ( ( ) => {
97+ if ( ! purifyReady || ! purifyRef . current ) return ;
98+ setEntries ( ( prev ) =>
99+ prev . map ( ( entry ) => {
100+ if ( entry . isAlias || ! entry . definitionHtml ) return entry ;
101+ return { ...entry , definitionHtml : purifyRef . current ! . sanitize ( entry . definitionHtml ) } ;
102+ } )
103+ ) ;
104+ } , [ purifyReady ] ) ;
89105
90106 const fuse = useMemo (
91107 ( ) =>
0 commit comments