diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index fd09641c61e..4a73dcbab1c 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -7,6 +7,7 @@ import starlightGitHubAlerts from 'starlight-github-alerts'; import starlightBlog from 'starlight-blog'; import mermaid from 'astro-mermaid'; import { fileURLToPath } from 'node:url'; +import remarkStripEmojis from './src/lib/remark/stripEmojis.js'; /** * Creates blog authors config with GitHub profile pictures @@ -32,6 +33,9 @@ function createAuthors(authors) { export default defineConfig({ site: 'https://github.github.io', base: '/gh-aw/', + markdown: { + remarkPlugins: [remarkStripEmojis], + }, vite: { server: { fs: { diff --git a/docs/src/lib/remark/stripEmojis.js b/docs/src/lib/remark/stripEmojis.js new file mode 100644 index 00000000000..8f3dee2fd4a --- /dev/null +++ b/docs/src/lib/remark/stripEmojis.js @@ -0,0 +1,132 @@ +// @ts-check + +/** + * Strip decorative emojis from rendered markdown for a more professional look. + * + * - Applies to regular text nodes. + * - Applies to code blocks and inline code so rendered pages contain no emojis. + * - Also applies to image/link metadata and raw HTML/MDX JSX attributes. + */ +export default function remarkStripEmojis() { + return function transform(tree) { + visit(tree); + }; +} + +/** + * @param {any} node + */ +function visit(node) { + if (!node || typeof node !== 'object') return; + + if (node.type === 'text' && typeof node.value === 'string') { + node.value = stripEmojis(node.value); + } + + if (node.type === 'inlineCode' && typeof node.value === 'string') { + node.value = stripEmojis(node.value); + } + + if (node.type === 'code' && typeof node.value === 'string') { + node.value = stripEmojis(node.value); + } + + if (node.type === 'html' && typeof node.value === 'string') { + node.value = stripEmojis(node.value); + } + + if (node.type === 'image') { + if (typeof node.alt === 'string') node.alt = stripEmojis(node.alt); + if (typeof node.title === 'string') node.title = stripEmojis(node.title); + } + + if (node.type === 'link' && typeof node.title === 'string') { + node.title = stripEmojis(node.title); + } + + if (node.type === 'definition' && typeof node.title === 'string') { + node.title = stripEmojis(node.title); + } + + // MDX JSX elements can carry emoji in string-valued attributes (e.g., alt/title). + // We keep this conservative and only touch string values. + if ( + (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') && + Array.isArray(node.attributes) + ) { + for (const attr of node.attributes) { + if (!attr || typeof attr !== 'object') continue; + if (typeof attr.value === 'string') { + attr.value = stripEmojis(attr.value); + continue; + } + // Some MDX parsers represent attribute values as objects. + if (attr.value && typeof attr.value === 'object' && typeof attr.value.value === 'string') { + attr.value.value = stripEmojis(attr.value.value); + } + } + } + + const { children } = node; + if (Array.isArray(children)) { + for (const child of children) visit(child); + } +} + +const replacements = new Map([ + // Prefer text symbols over emoji glyphs. + ['✅', '✓'], + ['❌', '✗'], + ['⚠️', '!'], + ['⚠', '!'], + // Common decorative prefixes. + ['🚀', ''], + ['🔍', ''], + ['🤖', ''], + ['🛡️', ''], + ['🛡', ''], + ['🔒', ''], + ['🔐', ''], + ['🔓', ''], + ['📥', ''], + ['📤', ''], + ['🌐', ''], + ['🚫', ''], + ['🐳', ''], + ['💰', ''], + ['⚡', ''], + ['🔗', ''], + ['🏷️', ''], + ['🏷', ''], + ['📊', ''], + ['🔬', ''], + ['🏗️', ''], + ['🏗', ''], + ['🧪', ''], + ['📋', ''], + ['🧩', ''], + ['🎯', ''], + ['🎭', ''], +]); + +/** + * @param {string} input + */ +function stripEmojis(input) { + let output = input; + + for (const [from, to] of replacements.entries()) { + if (output.includes(from)) output = output.split(from).join(to); + } + + // Remove leftover emoji presentation selectors. + output = output.replace(/\uFE0F/gu, ''); + + // Strip any remaining pictographic emoji characters. + // Node 20+ supports Unicode property escapes. + output = output.replace(/\p{Extended_Pictographic}+/gu, ''); + + // Collapse double spaces introduced by removals. + output = output.replace(/[ \t]{2,}/g, ' '); + return output; +}