From 6a69e9b208e8a0ad6677960725f0f06f0e133056 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 5 Apr 2025 02:31:52 +0300 Subject: [PATCH] feat(markdown): use different approach for reference links --- src/services/export/markdown.spec.ts | 10 ++++-- src/services/export/markdown.ts | 50 +++++++++++++++++++++++++--- src/services/import/markdown.spec.ts | 8 ++++- src/services/import/markdown.ts | 11 ------ 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/services/export/markdown.spec.ts b/src/services/export/markdown.spec.ts index 5f14314fd..7de3dfab2 100644 --- a/src/services/export/markdown.spec.ts +++ b/src/services/export/markdown.spec.ts @@ -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*/`

Google

`; + const expected = `[Google](https://www.google.com)`; + expect(markdownExportService.toMarkdown(html)).toBe(expected); + }); + + it("exports reference links verbatim", () => { const html = /*html*/`

Canvas

`; - const expected = `[Canvas](../../Canvas.html)`; + const expected = `Canvas`; expect(markdownExportService.toMarkdown(html)).toBe(expected); }); diff --git a/src/services/export/markdown.ts b/src/services/export/markdown.ts index 03ac1b744..c148b9e2e 100644 --- a/src/services/export/markdown.ts +++ b/src/services/export/markdown.ts @@ -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 }; diff --git a/src/services/import/markdown.spec.ts b/src/services/import/markdown.spec.ts index 9ef6233e5..061e22f21 100644 --- a/src/services/import/markdown.spec.ts +++ b/src/services/import/markdown.spec.ts @@ -151,8 +151,14 @@ second line 2
  1. Hello
  2. { + it("does not touch relative links", () => { const input = `[Canvas](../../Canvas.html)`; + const expected = /*html*/`

    Canvas

    `; + expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected); + }); + + it("imports back to reference links", () => { + const input = `Canvas`; const expected = /*html*/`

    Canvas

    `; expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected); }); diff --git a/src/services/import/markdown.ts b/src/services/import/markdown.ts index 3dcd2784c..57bf58dd6 100644 --- a/src/services/import/markdown.ts +++ b/src/services/import/markdown.ts @@ -64,17 +64,6 @@ class CustomMarkdownRenderer extends Renderer { return `
    ${body}
    `; } - 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(/^