From 6d67e69e2fb762dab8f646571448658f30166b5a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 15 Mar 2025 11:25:42 +0200 Subject: [PATCH] feat(export/markdown): basic support for admonitions --- src/services/export/markdown.spec.ts | 90 ++++++++++++++++++++++++++++ src/services/export/markdown.ts | 52 ++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/src/services/export/markdown.spec.ts b/src/services/export/markdown.spec.ts index d779a166d..b0285b046 100644 --- a/src/services/export/markdown.spec.ts +++ b/src/services/export/markdown.spec.ts @@ -103,4 +103,94 @@ describe("Markdown export", () => { expect(markdownExportService.toMarkdown(html)).toBe(html); }); + it("exports admonitions properly", () => { + const html = trimIndentation`\ +

+ Before +

+ + + + + +

+ After +

+ `; + + const space = " "; // editor config trimming space. + const expected = trimIndentation`\ + Before + + > [!NOTE] + > This is a note. + + > [!TIP] + > This is a tip. + + > [!IMPORTANT] + > This is a very important information. + >${space} + > | | | + > | --- | --- | + > | 1 | 2 | + > | 3 | 4 | + + > [!CAUTION] + > This is a caution. + + > [!WARNING] + > ## Title goes here + >${space} + > This is a warning. + + After`; + expect(markdownExportService.toMarkdown(html)).toBe(expected); + }); + }); diff --git a/src/services/export/markdown.ts b/src/services/export/markdown.ts index fcbac50a4..da0e104a7 100644 --- a/src/services/export/markdown.ts +++ b/src/services/export/markdown.ts @@ -31,6 +31,7 @@ function toMarkdown(content: string) { // Filter is heavily based on: https://github.com/mixmark-io/turndown/issues/274#issuecomment-458730974 instance.addRule("fencedCodeBlock", fencedCodeBlockFilter); instance.addRule("img", buildImageFilter()); + instance.addRule("admonition", buildAdmonitionFilter()); instance.use(turndownPluginGfm.gfm); instance.keep([ "kbd" ]); } @@ -100,6 +101,57 @@ function buildImageFilter() { return imageFilter; } +function buildAdmonitionFilter() { + const admonitionTypeMappings: Record = { + note: "NOTE", + tip: "TIP", + important: "IMPORTANT", + caution: "CAUTION", + warning: "WARNING" + }; + + const defaultAdmonitionType = admonitionTypeMappings.note; + + function parseAdmonitionType(_node: Node) { + if (!("getAttribute" in _node)) { + return defaultAdmonitionType; + } + + const node = _node as Element; + const classList = node.getAttribute("class")?.split(" ") ?? []; + + for (const className of classList) { + if (className === "admonition") { + continue; + } + + const mappedType = admonitionTypeMappings[className]; + if (mappedType) { + return mappedType; + } + } + + return defaultAdmonitionType; + } + + const admonitionFilter: TurndownService.Rule = { + filter(node, options) { + return node.nodeName === "ASIDE" && node.classList.contains("admonition"); + }, + replacement(content, node) { + // Parse the admonition type. + const admonitionType = parseAdmonitionType(node); + + content = content.replace(/^\n+|\n+$/g, ''); + content = content.replace(/^/gm, '> '); + content = `> [!${admonitionType}]\n` + content; + + return "\n\n" + content + "\n\n"; + } + } + return admonitionFilter; +} + export default { toMarkdown };