11import React , { FC } from 'react' ;
2- import { Helmet } from 'react-helmet' ;
32import { useSiteMetadata } from './useSiteMetadataHook' ;
43
54type SeoProps = {
@@ -9,6 +8,20 @@ type SeoProps = {
98 children ?: React . ReactNode ;
109} ;
1110
11+ // Asciidoctor converts typographic characters to HTML entities (e.g. ' → ’).
12+ // React escapes & in text nodes, so entities double-encode and show literally in the tab.
13+ const decodeHtmlEntities = ( str : string ) : string =>
14+ str
15+ . replace ( / & # ( \d + ) ; / g, ( _ , code ) => String . fromCharCode ( Number ( code ) ) )
16+ . replace ( / & # x ( [ 0 - 9 a - f A - F ] + ) ; / g, ( _ , hex ) =>
17+ String . fromCharCode ( parseInt ( hex , 16 ) ) ,
18+ )
19+ . replace ( / & a m p ; / g, '&' )
20+ . replace ( / & l t ; / g, '<' )
21+ . replace ( / & g t ; / g, '>' )
22+ . replace ( / & q u o t ; / g, '"' )
23+ . replace ( / & a p o s ; / g, "'" ) ;
24+
1225export const Seo : FC < SeoProps > = ( {
1326 title,
1427 description,
@@ -23,7 +36,7 @@ export const Seo: FC<SeoProps> = ({
2336 } = useSiteMetadata ( ) ;
2437
2538 const seo = {
26- title : title || defaultTitle ,
39+ title : decodeHtmlEntities ( title || defaultTitle ) ,
2740 description : description || defaultDescription ,
2841 image : `${ siteUrl } ${ image } ` ,
2942 url : `${ siteUrl } ${ pathname || '' } ` ,
0 commit comments