All files / owid-grapher/site formatGlossary.tsx

100% Statements 72/72
100% Branches 11/11
100% Functions 5/5
100% Lines 72/72

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 951x 1x 1x 1x 1x 1x 1x 1x 1x 1x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 1x 1x 1x 1x 1x 1x 1x 18x 18x 18x 18x 18x 38x 38x 27x 27x 27x 27x 3x 3x 38x 18x 1x 1x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 27x 14x 14x 27x 27x 9x 9x 14x 9x                                              
import * as React from "react"
import * as ReactDOMServer from "react-dom/server"
import { ExpandableInlineBlock_name } from "./ExpandableInlineBlock"
import { GlossaryExcerpt_name } from "./GlossaryExcerpt"
import { GlossaryItem } from "./glossary"
 
// Do not replace glossary terms within these tags
export const FORBIDDEN_TAGS = ["a", "h2", "h3", "h4", "h5", "h6"]
 
export const GlossaryLink = ({
    slug,
    excerpt,
    match,
}: {
    slug: string
    excerpt: string
    match: string
}) => (
    <span>
        <script
            data-type={ExpandableInlineBlock_name}
            data-block={GlossaryExcerpt_name}
            data-label={match}
            type="component/props"
            dangerouslySetInnerHTML={{
                __html: JSON.stringify({ slug, excerpt }),
            }}
        ></script>
        <a className="expandable-block-button" href={`/glossary#${slug}`}>
            {match}
        </a>
    </span>
)
 
export const formatGlossaryTerms = (
    $: CheerioStatic,
    $contents: Cheerio,
    mutableGlossary: GlossaryItem[]
) => {
    $contents.each((i, el) => {
        if (FORBIDDEN_TAGS.includes(el.tagName)) return
        if (el.type === "text") {
            $(el).replaceWith(
                _linkGlossaryTermsInText(el.data, mutableGlossary)
            )
        } else {
            formatGlossaryTerms($, $(el).contents(), mutableGlossary)
        }
    })
}
 
export const _linkGlossaryTermsInText = (
    srcText: string = "",
    glossary: GlossaryItem[]
) => {
    let textWithGlossaryLinks = srcText
 
    // Include periods in matched text to prevent inelegant next line wrapping
    const regex = new RegExp(
        `\\b(${glossary.map((item) => item.term).join("|")})\\b\\.?`,
        "ig"
    )
 
    const trimLastCharIfPeriod = (text: string) => {
        return text.replace(/\.$/, "")
    }
 
    const _getGlossaryLink = (match: string) => {
        const idx = glossary.findIndex(
            (item) =>
                item.term.toLowerCase() ===
                trimLastCharIfPeriod(match.toLowerCase())
        )
        if (idx === -1) return match
 
        const slug = glossary[idx].slug
        const excerpt = glossary[idx].excerpt
 
        // Remove element in-place so that glossary items are only matched and
        // linked once per recursive traversal (at the moment, this is set to
        // once per page section)
        glossary.splice(idx, 1)
 
        return ReactDOMServer.renderToStaticMarkup(
            <GlossaryLink slug={slug} excerpt={excerpt} match={match} />
        )
    }
 
    textWithGlossaryLinks = textWithGlossaryLinks.replace(
        regex,
        _getGlossaryLink
    )
    return textWithGlossaryLinks
}