Skip to content
60 changes: 41 additions & 19 deletions packages/site-kit/src/lib/markdown/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) === '&#39;') {
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) === '&quot;') {
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 = {
Expand Down