feat(core): add siteConfig.markdown.emoji config option to disable remark-emoji (#11282)

This commit is contained in:
Sébastien Lorber 2025-06-24 16:38:08 +02:00 committed by GitHub
parent 96c38d5fdd
commit e14caf1f78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 87 additions and 9 deletions

View file

@ -95,7 +95,7 @@ async function createProcessorFactory() {
headings, headings,
{anchorsMaintainCase: options.markdownConfig.anchors.maintainCase}, {anchorsMaintainCase: options.markdownConfig.anchors.maintainCase},
], ],
emoji, ...(options.markdownConfig.emoji ? [emoji] : []),
toc, toc,
]; ];
} }

View file

@ -136,6 +136,16 @@ export type MarkdownConfig = {
*/ */
mermaid: boolean; mermaid: boolean;
/**
* Allow remark-emoji to convert emoji shortcodes to Unicode emoji.
* - `true` (default): enables the remark-emoji plugin to convert shortcodes
* - `false`: disables the remark-emoji plugin
*
* @see https://github.com/rhysd/remark-emoji
* @default true
*/
emoji: boolean;
/** /**
* Gives opportunity to preprocess the MDX string content before compiling. * Gives opportunity to preprocess the MDX string content before compiling.
* A good escape hatch that can be used to handle edge cases. * A good escape hatch that can be used to handle edge cases.

View file

@ -41,6 +41,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",
@ -119,6 +120,7 @@ exports[`loadSiteConfig website with ts + js config 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",
@ -197,6 +199,7 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",
@ -275,6 +278,7 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",
@ -353,6 +357,7 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",
@ -431,6 +436,7 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",
@ -509,6 +515,7 @@ exports[`loadSiteConfig website with valid async config 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",
@ -589,6 +596,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",
@ -669,6 +677,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",
@ -752,6 +761,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",

View file

@ -125,6 +125,7 @@ exports[`load loads props for site with custom i18n path 1`] = `
"anchors": { "anchors": {
"maintainCase": false, "maintainCase": false,
}, },
"emoji": true,
"format": "mdx", "format": "mdx",
"hooks": { "hooks": {
"onBrokenMarkdownImages": "throw", "onBrokenMarkdownImages": "throw",

View file

@ -17,17 +17,17 @@ import {
DEFAULT_STORAGE_CONFIG, DEFAULT_STORAGE_CONFIG,
validateConfig, validateConfig,
} from '../configValidation'; } from '../configValidation';
import type {
MarkdownConfig,
MarkdownHooks,
} from '@docusaurus/types/src/markdown';
import type { import type {
FasterConfig, FasterConfig,
FutureConfig, FutureConfig,
FutureV4Config, FutureV4Config,
StorageConfig, StorageConfig,
} from '@docusaurus/types/src/config'; MarkdownConfig,
import type {Config, DocusaurusConfig, PluginConfig} from '@docusaurus/types'; MarkdownHooks,
Config,
DocusaurusConfig,
PluginConfig,
} from '@docusaurus/types';
import type {DeepPartial} from 'utility-types'; import type {DeepPartial} from 'utility-types';
const baseConfig = { const baseConfig = {
@ -36,7 +36,7 @@ const baseConfig = {
url: 'https://mysite.com', url: 'https://mysite.com',
} as Config; } as Config;
const normalizeConfig = (config: DeepPartial<Config>) => const normalizeConfig = (config: DeepPartial<Config>): DocusaurusConfig =>
validateConfig({...baseConfig, ...config}, 'docusaurus.config.js'); validateConfig({...baseConfig, ...config}, 'docusaurus.config.js');
describe('normalizeConfig', () => { describe('normalizeConfig', () => {
@ -99,6 +99,7 @@ describe('normalizeConfig', () => {
markdown: { markdown: {
format: 'md', format: 'md',
mermaid: true, mermaid: true,
emoji: false,
parseFrontMatter: async (params) => parseFrontMatter: async (params) =>
params.defaultParseFrontMatter(params), params.defaultParseFrontMatter(params),
preprocessor: ({fileContent}) => fileContent, preprocessor: ({fileContent}) => fileContent,
@ -366,7 +367,9 @@ describe('onBrokenLinks', () => {
}); });
describe('markdown', () => { describe('markdown', () => {
function normalizeMarkdown(markdown: DeepPartial<MarkdownConfig>) { function normalizeMarkdown(
markdown: DeepPartial<MarkdownConfig>,
): MarkdownConfig {
return normalizeConfig({markdown}).markdown; return normalizeConfig({markdown}).markdown;
} }
it('accepts undefined object', () => { it('accepts undefined object', () => {
@ -381,6 +384,7 @@ describe('markdown', () => {
const markdown: Config['markdown'] = { const markdown: Config['markdown'] = {
format: 'md', format: 'md',
mermaid: true, mermaid: true,
emoji: false,
parseFrontMatter: async (params) => parseFrontMatter: async (params) =>
params.defaultParseFrontMatter(params), params.defaultParseFrontMatter(params),
preprocessor: ({fileContent}) => fileContent, preprocessor: ({fileContent}) => fileContent,
@ -476,6 +480,54 @@ describe('markdown', () => {
`); `);
}); });
describe('emoji', () => {
it('accepts emoji boolean true', () => {
expect(
normalizeMarkdown({
emoji: true,
}).emoji,
).toBe(true);
});
it('accepts emoji boolean false', () => {
expect(
normalizeMarkdown({
emoji: false,
}).emoji,
).toBe(false);
});
it('defaults emoji to true when undefined', () => {
expect(normalizeMarkdown({}).emoji).toBe(true);
});
it('throw for string emoji value', () => {
expect(() =>
normalizeMarkdown({
// @ts-expect-error: bad value
emoji: 'yes',
}),
).toThrowErrorMatchingInlineSnapshot(`
""markdown.emoji" must be a boolean
"
`);
});
it('throw for number emoji value', () => {
expect(() =>
normalizeConfig({
markdown: {
// @ts-expect-error: bad value
emoji: 1,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""markdown.emoji" must be a boolean
"
`);
});
});
describe('hooks', () => { describe('hooks', () => {
function normalizeHooks(hooks: DeepPartial<MarkdownHooks>): MarkdownHooks { function normalizeHooks(hooks: DeepPartial<MarkdownHooks>): MarkdownHooks {
return normalizeMarkdown({ return normalizeMarkdown({

View file

@ -91,6 +91,7 @@ export const DEFAULT_MARKDOWN_HOOKS: MarkdownHooks = {
export const DEFAULT_MARKDOWN_CONFIG: MarkdownConfig = { export const DEFAULT_MARKDOWN_CONFIG: MarkdownConfig = {
format: 'mdx', // TODO change this to "detect" in Docusaurus v4? format: 'mdx', // TODO change this to "detect" in Docusaurus v4?
mermaid: false, mermaid: false,
emoji: true,
preprocessor: undefined, preprocessor: undefined,
parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER, parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER,
mdx1Compat: { mdx1Compat: {
@ -435,6 +436,7 @@ export const ConfigSchema = Joi.object<DocusaurusConfig>({
() => DEFAULT_CONFIG.markdown.parseFrontMatter, () => DEFAULT_CONFIG.markdown.parseFrontMatter,
), ),
mermaid: Joi.boolean().default(DEFAULT_CONFIG.markdown.mermaid), mermaid: Joi.boolean().default(DEFAULT_CONFIG.markdown.mermaid),
emoji: Joi.boolean().default(DEFAULT_CONFIG.markdown.emoji),
preprocessor: Joi.function() preprocessor: Joi.function()
.arity(1) .arity(1)
.optional() .optional()

View file

@ -541,6 +541,7 @@ type MarkdownHooks = {
type MarkdownConfig = { type MarkdownConfig = {
format: 'mdx' | 'md' | 'detect'; format: 'mdx' | 'md' | 'detect';
mermaid: boolean; mermaid: boolean;
emoji: boolean;
preprocessor?: MarkdownPreprocessor; preprocessor?: MarkdownPreprocessor;
parseFrontMatter?: ParseFrontMatter; parseFrontMatter?: ParseFrontMatter;
mdx1Compat: MDX1CompatOptions; mdx1Compat: MDX1CompatOptions;
@ -557,6 +558,7 @@ export default {
markdown: { markdown: {
format: 'mdx', format: 'mdx',
mermaid: true, mermaid: true,
emoji: true,
preprocessor: ({filePath, fileContent}) => { preprocessor: ({filePath, fileContent}) => {
return fileContent.replaceAll('{{MY_VAR}}', 'MY_VALUE'); return fileContent.replaceAll('{{MY_VAR}}', 'MY_VALUE');
}, },
@ -590,6 +592,7 @@ export default {
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `format` | `'mdx' \| 'md' \| 'detect'` | `'mdx'` | The default parser format to use for Markdown content. Using 'detect' will select the appropriate format automatically based on file extensions: `.md` vs `.mdx`. | | `format` | `'mdx' \| 'md' \| 'detect'` | `'mdx'` | The default parser format to use for Markdown content. Using 'detect' will select the appropriate format automatically based on file extensions: `.md` vs `.mdx`. |
| `mermaid` | `boolean` | `false` | When `true`, allows Docusaurus to render Markdown code blocks with `mermaid` language as Mermaid diagrams. | | `mermaid` | `boolean` | `false` | When `true`, allows Docusaurus to render Markdown code blocks with `mermaid` language as Mermaid diagrams. |
| `emoji` | `boolean` | `true` | When `true`, allows Docusaurus to render emoji shortcodes (e.g., `:+1:`) as Unicode emoji (👍). When `false`, emoji shortcodes are left as-is. |
| `preprocessor` | `MarkdownPreprocessor` | `undefined` | Gives you the ability to alter the Markdown content string before parsing. Use it as a last-resort escape hatch or workaround: it is almost always better to implement a Remark/Rehype plugin. | | `preprocessor` | `MarkdownPreprocessor` | `undefined` | Gives you the ability to alter the Markdown content string before parsing. Use it as a last-resort escape hatch or workaround: it is almost always better to implement a Remark/Rehype plugin. |
| `parseFrontMatter` | `ParseFrontMatter` | `undefined` | Gives you the ability to provide your own front matter parser, or to enhance the default parser. Read our [front matter guide](../guides/markdown-features/markdown-features-intro.mdx#front-matter) for details. | | `parseFrontMatter` | `ParseFrontMatter` | `undefined` | Gives you the ability to provide your own front matter parser, or to enhance the default parser. Read our [front matter guide](../guides/markdown-features/markdown-features-intro.mdx#front-matter) for details. |
| `mdx1Compat` | `MDX1CompatOptions` | `{comments: true, admonitions: true, headingIds: true}` | Compatibility options to make it easier to upgrade to Docusaurus v3+. | | `mdx1Compat` | `MDX1CompatOptions` | `{comments: true, admonitions: true, headingIds: true}` | Compatibility options to make it easier to upgrade to Docusaurus v3+. |