diff --git a/packages/site-kit/src/lib/markdown/utils.ts b/packages/site-kit/src/lib/markdown/utils.ts index 559d507456..6df21e78a1 100644 --- a/packages/site-kit/src/lib/markdown/utils.ts +++ b/packages/site-kit/src/lib/markdown/utils.ts @@ -42,31 +42,53 @@ export const slugify = (str: string) => { .replace(/-$/, ''); }; +/** + * Replace dumb quotes with smart quotes. This isn't a perfect algorithm — it + * wouldn't correctly handle `That '70s show` or `My country 'tis of thee` but + * a) it's very unlikely they'll occur in our docs, and + * b) they can be dealt with manually + */ export function smart_quotes( str: string, - { first = true, html = false }: { first?: boolean; html?: boolean } = {} + { + first = true, + html = false + }: { + /** True if the string is the entire sentence or false if it's a substring. @default true */ + first?: boolean; + /** True if the string has HTML entities. @default false */ + html?: boolean; + } = {} ) { - // replace dumb quotes with smart quotes. This isn't a perfect algorithm — it - // wouldn't correctly handle `That '70s show` or `My country 'tis of thee` - // but a) it's very unlikely they'll occur in our docs, and - // b) they can be dealt with manually - return str.replace( - html ? /(.|^)('|")(.|$)/g : /(.|^)('|")(.|$)/g, - (m, before, quote, after) => { - const left = (first && before === '') || [' ', '\n', '('].includes(before); - let replacement = ''; - - if (html) { - const double = quote === '"'; - replacement = `&${left ? 'l' : 'r'}${double ? 'd' : 's'}quo;`; + let open_quote = false; + let res = ''; + const len = str.length; + for (let index = 0; index < len; index++) { + let char = str.charAt(index); + if (html && char === '&') { + if (str.slice(index, index + 5) === ''') { + let left: boolean = (first && !open_quote) || (index > 1 && str.charAt(index - 1) === '='); + open_quote = left; + res += `&${left ? 'l' : 'r'}squo;`; + index += 4; + } else if (str.slice(index, index + 6) === '"') { + let left: boolean = (first && !open_quote) || (index > 1 && str.charAt(index - 1) === '='); + open_quote = left; + res += `&${left ? 'l' : 'r'}dquo`; + index += 5; } else { - const double = quote === '"'; - replacement = double ? (left ? '“' : '”') : left ? '‘' : '’'; + res += '&'; } - - return (before ?? '') + replacement + (after ?? ''); + } else if (!html && (char === '"' || char === "'")) { + let left: boolean = (first && !open_quote) || (index > 1 && str.charAt(index - 1) === '='); + open_quote = left; + let double = char === '"'; + res += double ? (left ? '“' : '”') : left ? '‘' : '’'; + } else { + res += char; } - ); + } + return res; } const tokenizer: TokenizerObject = {