diff --git a/README.md b/README.md index 29967c2..2c63c71 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ const md = MarkdownItAsync({ } }) +// Optional, use the custom async rules +md.renderer.asyncRules.rule_key = async () => { + // Your async rule +} + // Note you need to use `renderAsync` instead of `render` const html = await md.renderAsync(markdown) ``` diff --git a/src/index.ts b/src/index.ts index 4b75430..7300f58 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,8 @@ import type { Options, PresetName, } from 'markdown-it' +import type { Renderer as MarkdownItRender, Token } from 'markdown-it/index.js' +import type Renderer from 'markdown-it/lib/renderer.mjs' import MarkdownIt from 'markdown-it' export type PluginSimple = ((md: MarkdownItAsync) => void) @@ -40,7 +42,20 @@ function randStr(): string { export type MarkdownItAsyncPlaceholderMap = Map, str: string, lang: string, attrs: string]> +type AwaitedFunction any> = T | ((...args: Parameters) => Promise>) + + type RenderRuleAsync = AwaitedFunction + +interface RendererAsync extends Renderer { + asyncRules: Partial> + + renderInlineAsync: (this: RendererAsync, tokens: Token[], options: Options, env?: any) => Promise + + renderAsync: (this: RendererAsync, tokens: Token[], options: Options, env?: any) => Promise +} + export class MarkdownItAsync extends MarkdownIt { + declare renderer: RendererAsync placeholderMap: MarkdownItAsyncPlaceholderMap private disableWarn = false @@ -53,6 +68,48 @@ export class MarkdownItAsync extends MarkdownIt { options.highlight = wrapHightlight(options.highlight, map) super(...args as []) this.placeholderMap = map + this.renderer.asyncRules = {} + + this.renderer.renderInlineAsync = async function (this: RendererAsync, tokens: Token[], options: Options, env?: any): Promise { + const codes = await Promise.all( + tokens.map(async (token, i) => { + const type = token.type + + const rule = this.asyncRules[type] ?? this.rules[type] + + if (typeof rule !== 'undefined') { + return rule(tokens, i, options, env, this) + } + else { + return this.renderToken(tokens, i, options) + } + }), + ) + + return codes.join('') + } + + this.renderer.renderAsync = async function (this: RendererAsync, tokens: Token[], options: Options, env?: any): Promise { + const codes = await Promise.all( + tokens.map(async (token, i) => { + const type = token.type + + const rule = this.asyncRules[type] ?? this.rules[type] + + if (type === 'inline') { + return this.renderInlineAsync(token.children ?? [], options, env) + } + else if (typeof rule !== 'undefined') { + return rule(tokens, i, options, env, this) + } + else { + return this.renderToken(tokens, i, options) + } + }), + ) + + return codes.join('') + } } use(plugin: PluginSimple): this @@ -76,7 +133,7 @@ export class MarkdownItAsync extends MarkdownIt { async renderAsync(src: string, env?: any): Promise { this.options.highlight = wrapHightlight(this.options.highlight, this.placeholderMap) this.disableWarn = true - const result = this.render(src, env) + const result = await this.renderer.renderAsync(this.parse(src, env), this.options, env) this.disableWarn = false return replaceAsync(result, placeholderRe, async (match, id) => { if (!this.placeholderMap.has(id)) diff --git a/test/index.test.ts b/test/index.test.ts index e912cbb..ab97841 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -72,4 +72,31 @@ describe('markdown-it-async', async () => { spy.mockRestore() }) + + it('async rules', async () => { + const mda = createMarkdownItAsync({ + async highlight(str, lang) { + return await codeToHtml(str, { + lang, + theme: 'vitesse-light', + }) + }, + }) + + const mock = vi.fn() + + Object.entries(mda.renderer.rules).forEach(([key, rule]) => { + if (typeof rule === 'function') { + mda.renderer.asyncRules[key] = async (tokens, index, options, env?: any) => { + await new Promise(resolve => setTimeout(resolve, 10)) + mock() + return rule(tokens, index, options, env, mda.renderer) + } + } + }) + + expect(expectedResult) + .toEqual(await mda.renderAsync(fixture)) + expect(mock).toHaveBeenCalled() + }) })