feat(markdown): use different approach for reference links

This commit is contained in:
Elian Doran 2025-04-05 02:31:52 +03:00
parent 4f22850ea9
commit 6a69e9b208
No known key found for this signature in database
4 changed files with 60 additions and 19 deletions

View File

@ -238,9 +238,15 @@ describe("Markdown export", () => {
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("exports reference links as normal links", () => {
it("exports normal links verbatim", () => {
const html = /*html*/`<p><a href="https://www.google.com">Google</a></p>`;
const expected = `[Google](https://www.google.com)`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});
it("exports reference links verbatim", () => {
const html = /*html*/`<p><a class="reference-link" href="../../Canvas.html">Canvas</a></p>`;
const expected = `[Canvas](../../Canvas.html)`;
const expected = `<a class="reference-link" href="../../Canvas.html">Canvas</a>`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});

View File

@ -1,7 +1,8 @@
"use strict";
import TurndownService from "turndown";
import TurndownService, { type Rule } from "turndown";
import { gfm } from "../../../packages/turndown-plugin-gfm/src/gfm.js";
import type { DOMElement } from "react";
let instance: TurndownService | null = null;
@ -43,6 +44,7 @@ function toMarkdown(content: string) {
instance.addRule("fencedCodeBlock", fencedCodeBlockFilter);
instance.addRule("img", buildImageFilter());
instance.addRule("admonition", buildAdmonitionFilter());
instance.addRule("inlineLink", buildInlineLinkFilter());
instance.use(gfm);
instance.keep([ "kbd" ]);
}
@ -93,10 +95,6 @@ function buildImageFilter() {
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) {
@ -153,6 +151,48 @@ function buildAdmonitionFilter() {
return admonitionFilter;
}
/**
* Variation of the original ruleset: https://github.com/mixmark-io/turndown/blob/master/src/commonmark-rules.js.
*
* Detects if the URL is a Trilium reference link and returns it verbatim if that's the case.
*
* @returns
*/
function buildInlineLinkFilter(): Rule {
return {
filter: function (node, options) {
return (
options.linkStyle === 'inlined' &&
node.nodeName === 'A' &&
!!node.getAttribute('href')
)
},
replacement: function (content, _node) {
const node = _node as HTMLElement;
// Return reference links verbatim.
if (node.classList.contains("reference-link")) {
return node.outerHTML;
}
// Otherwise treat as normal.
// TODO: Call super() somehow instead of duplicating the implementation.
var href = node.getAttribute('href')
if (href) href = href.replace(/([()])/g, '\\$1')
var title = cleanAttribute(node.getAttribute('title'))
if (title) title = ' "' + title.replace(/"/g, '\\"') + '"'
return '[' + content + '](' + href + title + ')'
}
}
}
// Taken from upstream since it's not exposed.
// https://github.com/mixmark-io/turndown/blob/master/src/commonmark-rules.js
function cleanAttribute(attribute: string | null | undefined) {
return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : ''
}
export default {
toMarkdown
};

View File

@ -151,8 +151,14 @@ second line 2</code></pre><ul><li>Hello</li><li>world</li></ul><ol><li>Hello</li
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
});
it("imports back to reference links", () => {
it("does not touch relative links", () => {
const input = `[Canvas](../../Canvas.html)`;
const expected = /*html*/`<p><a href="../../Canvas.html">Canvas</a></p>`;
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
});
it("imports back to reference links", () => {
const input = `<a class="reference-link" href="../../Canvas.html">Canvas</a>`;
const expected = /*html*/`<p><a class="reference-link" href="../../Canvas.html">Canvas</a></p>`;
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
});

View File

@ -64,17 +64,6 @@ class CustomMarkdownRenderer extends Renderer {
return `<blockquote>${body}</blockquote>`;
}
link(data: Tokens.Link): string {
let html = super.link(data);
// Rewrite local/relative links back to reference links.
if (data.href.indexOf("://") === -1) {
html = html.replace(/^<a /, `<a class="reference-link" `);
}
return html;
}
}
const renderer = new CustomMarkdownRenderer({ async: false });