-
Notifications
You must be signed in to change notification settings - Fork 300
feat: add remark plugin to strip emojis #14122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+26
to
+32
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+113
to
+121
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @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); | |
| } | |
| * Escape special characters in a string for use in a RegExp. | |
| * @param {string} value | |
| * @returns {string} | |
| */ | |
| function escapeRegex(value) { | |
| return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| } | |
| // Build a single regex that matches any of the replacement keys. | |
| const replacementPattern = Array.from(replacements.keys()) | |
| .sort((a, b) => b.length - a.length) // longer first to prefer longer matches | |
| .map((key) => escapeRegex(key)) | |
| .join('|'); | |
| const replacementsRegex = new RegExp(replacementPattern, 'gu'); | |
| /** | |
| * @param {string} input | |
| */ | |
| function stripEmojis(input) { | |
| let output = input.replace( | |
| replacementsRegex, | |
| (match) => replacements.get(match) ?? match, | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The transform function should return the tree after modifications. While remark plugins that mutate the tree in-place may work without an explicit return, it's a best practice to return the tree to follow the unified plugin specification and ensure compatibility with the remark ecosystem. Add
return tree;after the visit call.