refactor(server): rename md -> markdown for consistency

This commit is contained in:
Elian Doran
2025-03-14 16:25:10 +02:00
parent 875e65284c
commit 80421bb220
4 changed files with 3 additions and 3 deletions

View File

@@ -0,0 +1,104 @@
"use strict";
import TurndownService from "turndown";
import turndownPluginGfm from "@joplin/turndown-plugin-gfm";
let instance: TurndownService | null = null;
const fencedCodeBlockFilter: TurndownService.Rule = {
filter: function (node, options) {
return options.codeBlockStyle === "fenced" && node.nodeName === "PRE" && node.firstChild !== null && node.firstChild.nodeName === "CODE";
},
replacement: function (content, node, options) {
if (!node.firstChild || !("getAttribute" in node.firstChild) || typeof node.firstChild.getAttribute !== "function") {
return content;
}
const className = node.firstChild.getAttribute("class") || "";
const language = rewriteLanguageTag((className.match(/language-(\S+)/) || [null, ""])[1]);
return "\n\n" + options.fence + language + "\n" + node.firstChild.textContent + "\n" + options.fence + "\n\n";
}
};
function toMarkdown(content: string) {
if (instance === null) {
instance = new TurndownService({
headingStyle: "atx",
codeBlockStyle: "fenced"
});
// Filter is heavily based on: https://github.com/mixmark-io/turndown/issues/274#issuecomment-458730974
instance.addRule("fencedCodeBlock", fencedCodeBlockFilter);
instance.addRule("img", buildImageFilter());
instance.use(turndownPluginGfm.gfm);
}
return instance.turndown(content);
}
function rewriteLanguageTag(source: string) {
if (!source) {
return source;
}
switch (source) {
case "text-x-trilium-auto":
return "";
case "application-javascript-env-frontend":
case "application-javascript-env-backend":
return "javascript";
case "text-x-nginx-conf":
return "nginx";
default:
return source.split("-").at(-1);
}
}
// TODO: Remove once upstream delivers a fix for https://github.com/mixmark-io/turndown/issues/467.
function buildImageFilter() {
const ESCAPE_PATTERNS = {
before: /([\\*`[\]_]|(?:^[-+>])|(?:^~~~)|(?:^#{1-6}))/g,
after: /((?:^\d+(?=\.)))/
}
const escapePattern = new RegExp('(?:' + ESCAPE_PATTERNS.before.source + '|' + ESCAPE_PATTERNS.after.source + ')', 'g');
function escapeMarkdown (content: string) {
return content.replace(escapePattern, function (match, before, after) {
return before ? '\\' + before : after + '\\'
})
}
function escapeLinkDestination(destination: string) {
return destination
.replace(/([()])/g, '\\$1')
.replace(/ /g, "%20");
}
function escapeLinkTitle (title: string) {
return title.replace(/"/g, '\\"')
}
function cleanAttribute (attribute: string) {
return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''
}
const imageFilter: TurndownService.Rule = {
filter: "img",
replacement(content, node) {
const untypedNode = (node as any);
const alt = escapeMarkdown(cleanAttribute(untypedNode.getAttribute('alt')))
const src = escapeLinkDestination(untypedNode.getAttribute('src') || '')
const title = cleanAttribute(untypedNode.getAttribute('title'))
const titlePart = title ? ' "' + escapeLinkTitle(title) + '"' : ''
return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''
}
};
return imageFilter;
}
export default {
toMarkdown
};