mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-29 02:52:27 +08:00
feat(import/markdown): basic support for admonitions
This commit is contained in:
parent
6d67e69e2f
commit
535233fec8
@ -5,6 +5,17 @@ import turndownPluginGfm from "@joplin/turndown-plugin-gfm";
|
||||
|
||||
let instance: TurndownService | null = null;
|
||||
|
||||
// TODO: Move this to a dedicated file someday.
|
||||
export const ADMONITION_TYPE_MAPPINGS: Record<string, string> = {
|
||||
note: "NOTE",
|
||||
tip: "TIP",
|
||||
important: "IMPORTANT",
|
||||
caution: "CAUTION",
|
||||
warning: "WARNING"
|
||||
};
|
||||
|
||||
export const DEFAULT_ADMONITION_TYPE = ADMONITION_TYPE_MAPPINGS.note;
|
||||
|
||||
const fencedCodeBlockFilter: TurndownService.Rule = {
|
||||
filter: function (node, options) {
|
||||
return options.codeBlockStyle === "fenced" && node.nodeName === "PRE" && node.firstChild !== null && node.firstChild.nodeName === "CODE";
|
||||
@ -102,19 +113,9 @@ function buildImageFilter() {
|
||||
}
|
||||
|
||||
function buildAdmonitionFilter() {
|
||||
const admonitionTypeMappings: Record<string, string> = {
|
||||
note: "NOTE",
|
||||
tip: "TIP",
|
||||
important: "IMPORTANT",
|
||||
caution: "CAUTION",
|
||||
warning: "WARNING"
|
||||
};
|
||||
|
||||
const defaultAdmonitionType = admonitionTypeMappings.note;
|
||||
|
||||
function parseAdmonitionType(_node: Node) {
|
||||
if (!("getAttribute" in _node)) {
|
||||
return defaultAdmonitionType;
|
||||
return DEFAULT_ADMONITION_TYPE;
|
||||
}
|
||||
|
||||
const node = _node as Element;
|
||||
@ -125,13 +126,13 @@ function buildAdmonitionFilter() {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mappedType = admonitionTypeMappings[className];
|
||||
const mappedType = ADMONITION_TYPE_MAPPINGS[className];
|
||||
if (mappedType) {
|
||||
return mappedType;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultAdmonitionType;
|
||||
return DEFAULT_ADMONITION_TYPE;
|
||||
}
|
||||
|
||||
const admonitionFilter: TurndownService.Rule = {
|
||||
|
@ -74,4 +74,31 @@ second line 2</code></pre>`;
|
||||
expect(markdownService.renderToHtml(input, "Troubleshooting")).toBe(expected);
|
||||
});
|
||||
|
||||
it("imports admonitions properly", () => {
|
||||
const space = " "; // editor config trimming space.
|
||||
const input = trimIndentation`\
|
||||
Before
|
||||
|
||||
> [!NOTE]
|
||||
> This is a note.
|
||||
|
||||
> [!TIP]
|
||||
> This is a tip.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This is a very important information.
|
||||
|
||||
> [!CAUTION]
|
||||
> This is a caution.
|
||||
|
||||
> [!WARNING]
|
||||
> ## Title goes here
|
||||
>${space}
|
||||
> This is a warning.
|
||||
|
||||
After`;
|
||||
const expected = `<p>Before</p><aside class="admonition note"><p>This is a note.</p></aside><aside class="admonition tip"><p>This is a tip.</p></aside><aside class="admonition important"><p>This is a very important information.</p></aside><aside class="admonition caution"><p>This is a caution.</p></aside><aside class="admonition warning"><h2>Title goes here</h2><p>This is a warning.</p></aside><p>After</p>`;
|
||||
expect(markdownService.renderToHtml(input, "Title")).toBe(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { parse, Renderer, type Tokens } from "marked";
|
||||
import { minify as minifyHtml } from "html-minifier";
|
||||
|
||||
// Keep renderer code up to date with https://github.com/markedjs/marked/blob/master/src/Renderer.ts.
|
||||
const renderer = new Renderer({ async: false });
|
||||
renderer.code = ({ text, lang, escaped }: Tokens.Code) => {
|
||||
if (!text) {
|
||||
@ -12,10 +13,29 @@ renderer.code = ({ text, lang, escaped }: Tokens.Code) => {
|
||||
const ckEditorLanguage = getNormalizedMimeFromMarkdownLanguage(lang);
|
||||
return `<pre><code class="language-${ckEditorLanguage}">${text}</code></pre>`;
|
||||
};
|
||||
renderer.blockquote = ({ tokens }: Tokens.Blockquote) => {
|
||||
const body = renderer.parser.parse(tokens);
|
||||
|
||||
const admonitionMatch = /^<p>\[\!([A-Z]+)\]/.exec(body);
|
||||
if (Array.isArray(admonitionMatch) && admonitionMatch.length === 2) {
|
||||
const type = admonitionMatch[1].toLowerCase();
|
||||
|
||||
if (ADMONITION_TYPE_MAPPINGS[type]) {
|
||||
const bodyWithoutHeader = body
|
||||
.replace(/^<p>\[\!([A-Z]+)\]/, "<p>")
|
||||
.replace(/^<p><\/p>/, ""); // Having a heading will generate an empty paragraph that we need to remove.
|
||||
|
||||
return `<aside class="admonition ${type}">\n${bodyWithoutHeader}</aside>\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return `<blockquote>\n${body}</blockquote>\n`;
|
||||
};
|
||||
|
||||
import htmlSanitizer from "../html_sanitizer.js";
|
||||
import importUtils from "./utils.js";
|
||||
import { getMimeTypeFromHighlightJs, MIME_TYPE_AUTO, normalizeMimeTypeForCKEditor } from "./mime_type_definitions.js";
|
||||
import { ADMONITION_TYPE_MAPPINGS } from "../export/markdown.js";
|
||||
|
||||
function renderToHtml(content: string, title: string) {
|
||||
let html = parse(content, {
|
||||
|
Loading…
x
Reference in New Issue
Block a user