diff --git a/packages/docusaurus-mdx-loader/src/processor.ts b/packages/docusaurus-mdx-loader/src/processor.ts index 29b0f8170a..f5120150ee 100644 --- a/packages/docusaurus-mdx-loader/src/processor.ts +++ b/packages/docusaurus-mdx-loader/src/processor.ts @@ -95,7 +95,7 @@ async function createProcessorFactory() { headings, {anchorsMaintainCase: options.markdownConfig.anchors.maintainCase}, ], - emoji, + ...(options.markdownConfig.emoji ? [emoji] : []), toc, ]; } diff --git a/packages/docusaurus-types/src/markdown.d.ts b/packages/docusaurus-types/src/markdown.d.ts index fe5e1c3694..e7904f46bc 100644 --- a/packages/docusaurus-types/src/markdown.d.ts +++ b/packages/docusaurus-types/src/markdown.d.ts @@ -136,6 +136,16 @@ export type MarkdownConfig = { */ 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. * A good escape hatch that can be used to handle edge cases. diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap index 29b20ea9e9..b868fc8330 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/config.test.ts.snap @@ -41,6 +41,7 @@ exports[`loadSiteConfig website with .cjs siteConfig 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", @@ -119,6 +120,7 @@ exports[`loadSiteConfig website with ts + js config 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", @@ -197,6 +199,7 @@ exports[`loadSiteConfig website with valid JS CJS config 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", @@ -275,6 +278,7 @@ exports[`loadSiteConfig website with valid JS ESM config 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", @@ -353,6 +357,7 @@ exports[`loadSiteConfig website with valid TypeScript CJS config 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", @@ -431,6 +436,7 @@ exports[`loadSiteConfig website with valid TypeScript ESM config 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", @@ -509,6 +515,7 @@ exports[`loadSiteConfig website with valid async config 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", @@ -589,6 +596,7 @@ exports[`loadSiteConfig website with valid async config creator function 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", @@ -669,6 +677,7 @@ exports[`loadSiteConfig website with valid config creator function 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", @@ -752,6 +761,7 @@ exports[`loadSiteConfig website with valid siteConfig 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", diff --git a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap index 696ef93444..40c87e4b93 100644 --- a/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap +++ b/packages/docusaurus/src/server/__tests__/__snapshots__/site.test.ts.snap @@ -125,6 +125,7 @@ exports[`load loads props for site with custom i18n path 1`] = ` "anchors": { "maintainCase": false, }, + "emoji": true, "format": "mdx", "hooks": { "onBrokenMarkdownImages": "throw", diff --git a/packages/docusaurus/src/server/__tests__/configValidation.test.ts b/packages/docusaurus/src/server/__tests__/configValidation.test.ts index 7a56519fe1..aaad1d3bae 100644 --- a/packages/docusaurus/src/server/__tests__/configValidation.test.ts +++ b/packages/docusaurus/src/server/__tests__/configValidation.test.ts @@ -17,17 +17,17 @@ import { DEFAULT_STORAGE_CONFIG, validateConfig, } from '../configValidation'; -import type { - MarkdownConfig, - MarkdownHooks, -} from '@docusaurus/types/src/markdown'; import type { FasterConfig, FutureConfig, FutureV4Config, StorageConfig, -} from '@docusaurus/types/src/config'; -import type {Config, DocusaurusConfig, PluginConfig} from '@docusaurus/types'; + MarkdownConfig, + MarkdownHooks, + Config, + DocusaurusConfig, + PluginConfig, +} from '@docusaurus/types'; import type {DeepPartial} from 'utility-types'; const baseConfig = { @@ -36,7 +36,7 @@ const baseConfig = { url: 'https://mysite.com', } as Config; -const normalizeConfig = (config: DeepPartial) => +const normalizeConfig = (config: DeepPartial): DocusaurusConfig => validateConfig({...baseConfig, ...config}, 'docusaurus.config.js'); describe('normalizeConfig', () => { @@ -99,6 +99,7 @@ describe('normalizeConfig', () => { markdown: { format: 'md', mermaid: true, + emoji: false, parseFrontMatter: async (params) => params.defaultParseFrontMatter(params), preprocessor: ({fileContent}) => fileContent, @@ -366,7 +367,9 @@ describe('onBrokenLinks', () => { }); describe('markdown', () => { - function normalizeMarkdown(markdown: DeepPartial) { + function normalizeMarkdown( + markdown: DeepPartial, + ): MarkdownConfig { return normalizeConfig({markdown}).markdown; } it('accepts undefined object', () => { @@ -381,6 +384,7 @@ describe('markdown', () => { const markdown: Config['markdown'] = { format: 'md', mermaid: true, + emoji: false, parseFrontMatter: async (params) => params.defaultParseFrontMatter(params), 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', () => { function normalizeHooks(hooks: DeepPartial): MarkdownHooks { return normalizeMarkdown({ diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index 1731068e08..d62e7b14c8 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -91,6 +91,7 @@ export const DEFAULT_MARKDOWN_HOOKS: MarkdownHooks = { export const DEFAULT_MARKDOWN_CONFIG: MarkdownConfig = { format: 'mdx', // TODO change this to "detect" in Docusaurus v4? mermaid: false, + emoji: true, preprocessor: undefined, parseFrontMatter: DEFAULT_PARSE_FRONT_MATTER, mdx1Compat: { @@ -435,6 +436,7 @@ export const ConfigSchema = Joi.object({ () => DEFAULT_CONFIG.markdown.parseFrontMatter, ), mermaid: Joi.boolean().default(DEFAULT_CONFIG.markdown.mermaid), + emoji: Joi.boolean().default(DEFAULT_CONFIG.markdown.emoji), preprocessor: Joi.function() .arity(1) .optional() diff --git a/website/docs/api/docusaurus.config.js.mdx b/website/docs/api/docusaurus.config.js.mdx index 8cdbe3080e..1ac4d1e07b 100644 --- a/website/docs/api/docusaurus.config.js.mdx +++ b/website/docs/api/docusaurus.config.js.mdx @@ -541,6 +541,7 @@ type MarkdownHooks = { type MarkdownConfig = { format: 'mdx' | 'md' | 'detect'; mermaid: boolean; + emoji: boolean; preprocessor?: MarkdownPreprocessor; parseFrontMatter?: ParseFrontMatter; mdx1Compat: MDX1CompatOptions; @@ -557,6 +558,7 @@ export default { markdown: { format: 'mdx', mermaid: true, + emoji: true, preprocessor: ({filePath, fileContent}) => { 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`. | | `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. | | `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+. |