mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 04:16:17 +01:00 
			
		
		
		
	Merge branch 'develop' into renovate/typescript-5.x
This commit is contained in:
		@@ -11,6 +11,6 @@ When notes are exported, their note ID is kept in the metadata of the export. Ho
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Since the Note ID is a fixed-width randomly generated number, due to the [pigeonhole principle](https://en.wikipedia.org/wiki/Pigeonhole_principle), there is a possibility that a newly created note will have the same ID as an existing note.
 | 
					Since the Note ID is a fixed-width randomly generated number, due to the [pigeonhole principle](https://en.wikipedia.org/wiki/Pigeonhole_principle), there is a possibility that a newly created note will have the same ID as an existing note.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Since the note ID is alphanumeric and the length is 12 we have \\(62^{12}\\) unique IDs. However since we are generating them randomly, we can use a collision calculator such as the one for [Nano ID](https://alex7kom.github.io/nano-nanoid-cc/?alphabet=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&size=12&speed=1000&speedUnit=hour) to determine that we'd need to create 1000 notes per hour every hour for 9 centuries in order to have at least 1% probability of a note collision.
 | 
					Since the note ID is alphanumeric and the length is 12 we have $62^{12}$ unique IDs. However since we are generating them randomly, we can use a collision calculator such as the one for [Nano ID](https://alex7kom.github.io/nano-nanoid-cc/?alphabet=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&size=12&speed=1000&speedUnit=hour) to determine that we'd need to create 1000 notes per hour every hour for 9 centuries in order to have at least 1% probability of a note collision.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
As such, Trilium does not take any explicit action against potential note collisions, similar to other software that makes uses of unique hashes such as [Git](https://stackoverflow.com/questions/10434326/hash-collision-in-git). If one would theoretically occur, what would most likely happen is that the existing note will be replaced by the new one.
 | 
					As such, Trilium does not take any explicit action against potential note collisions, similar to other software that makes uses of unique hashes such as [Git](https://stackoverflow.com/questions/10434326/hash-collision-in-git). If one would theoretically occur, what would most likely happen is that the existing note will be replaced by the new one.
 | 
				
			||||||
@@ -176,10 +176,7 @@ describe("Markdown export", () => {
 | 
				
			|||||||
            > [!IMPORTANT]
 | 
					            > [!IMPORTANT]
 | 
				
			||||||
            > This is a very important information.
 | 
					            > This is a very important information.
 | 
				
			||||||
            >${space}
 | 
					            >${space}
 | 
				
			||||||
            > |     |     |
 | 
					            > <figure class="table"><table><tbody><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr></tbody></table></figure>
 | 
				
			||||||
            > | --- | --- |
 | 
					 | 
				
			||||||
            > | 1   | 2   |
 | 
					 | 
				
			||||||
            > | 3   | 4   |
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            > [!CAUTION]
 | 
					            > [!CAUTION]
 | 
				
			||||||
            > This is a caution.
 | 
					            > This is a caution.
 | 
				
			||||||
@@ -279,4 +276,16 @@ describe("Markdown export", () => {
 | 
				
			|||||||
        expect(markdownExportService.toMarkdown(html)).toBe(expected);
 | 
					        expect(markdownExportService.toMarkdown(html)).toBe(expected);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("converts inline math expressions into proper Markdown syntax", () => {
 | 
				
			||||||
 | 
					        const html = /*html*/`<p>The equation is <span class="math-tex">\\(e=mc^{2}\\)</span>.</p>`;
 | 
				
			||||||
 | 
					        const expected = `The equation is\u00a0$e=mc^{2}$.`;
 | 
				
			||||||
 | 
					        expect(markdownExportService.toMarkdown(html)).toBe(expected);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("converts display math expressions into proper Markdown syntax", () => {
 | 
				
			||||||
 | 
					        const html = /*html*/`<span class="math-tex">\\[\sqrt{x^{2}+1}\\]</span>`;
 | 
				
			||||||
 | 
					        const expected = `$$\sqrt{x^{2}+1}$$`;
 | 
				
			||||||
 | 
					        expect(markdownExportService.toMarkdown(html)).toBe(expected);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,6 +46,7 @@ function toMarkdown(content: string) {
 | 
				
			|||||||
        instance.addRule("admonition", buildAdmonitionFilter());
 | 
					        instance.addRule("admonition", buildAdmonitionFilter());
 | 
				
			||||||
        instance.addRule("inlineLink", buildInlineLinkFilter());
 | 
					        instance.addRule("inlineLink", buildInlineLinkFilter());
 | 
				
			||||||
        instance.addRule("figure", buildFigureFilter());
 | 
					        instance.addRule("figure", buildFigureFilter());
 | 
				
			||||||
 | 
					        instance.addRule("math", buildMathFilter());
 | 
				
			||||||
        instance.use(gfm);
 | 
					        instance.use(gfm);
 | 
				
			||||||
        instance.keep([ "kbd" ]);
 | 
					        instance.keep([ "kbd" ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -207,6 +208,28 @@ function buildFigureFilter(): Rule {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function buildMathFilter(): Rule {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        filter(node) {
 | 
				
			||||||
 | 
					            return node.nodeName === "SPAN" && node.classList.contains("math-tex");
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        replacement(content) {
 | 
				
			||||||
 | 
					            // Inline math
 | 
				
			||||||
 | 
					            if (content.startsWith("\\\\(") && content.endsWith("\\\\)")) {
 | 
				
			||||||
 | 
					                return `$${content.substring(3, content.length - 3)}$`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Display math
 | 
				
			||||||
 | 
					            if (content.startsWith(String.raw`\\\[`) && content.endsWith(String.raw`\\\]`)) {
 | 
				
			||||||
 | 
					                return `$$${content.substring(4, content.length - 4)}$$`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Unknown.
 | 
				
			||||||
 | 
					            return content;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Taken from upstream since it's not exposed.
 | 
					// Taken from upstream since it's not exposed.
 | 
				
			||||||
// https://github.com/mixmark-io/turndown/blob/master/src/commonmark-rules.js
 | 
					// https://github.com/mixmark-io/turndown/blob/master/src/commonmark-rules.js
 | 
				
			||||||
function cleanAttribute(attribute: string | null | undefined) {
 | 
					function cleanAttribute(attribute: string | null | undefined) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -149,7 +149,8 @@ function sanitize(dirtyHtml: string) {
 | 
				
			|||||||
        allowedTags,
 | 
					        allowedTags,
 | 
				
			||||||
        allowedAttributes: {
 | 
					        allowedAttributes: {
 | 
				
			||||||
            "*": ["class", "style", "title", "src", "href", "hash", "disabled", "align", "alt", "center", "data-*"],
 | 
					            "*": ["class", "style", "title", "src", "href", "hash", "disabled", "align", "alt", "center", "data-*"],
 | 
				
			||||||
            input: ["type", "checked"]
 | 
					            input: ["type", "checked"],
 | 
				
			||||||
 | 
					            img: ["width", "height"]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        allowedStyles: {
 | 
					        allowedStyles: {
 | 
				
			||||||
            "*": {
 | 
					            "*": {
 | 
				
			||||||
@@ -161,6 +162,9 @@ function sanitize(dirtyHtml: string) {
 | 
				
			|||||||
                width: sizeRegex,
 | 
					                width: sizeRegex,
 | 
				
			||||||
                height: sizeRegex
 | 
					                height: sizeRegex
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            img: {
 | 
				
			||||||
 | 
					                "aspect-ratio": [ /^\d+\/\d+$/ ],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            table: {
 | 
					            table: {
 | 
				
			||||||
                "border-color": colorRegex,
 | 
					                "border-color": colorRegex,
 | 
				
			||||||
                "border-style": [/^\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*$/]
 | 
					                "border-style": [/^\s*(none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)\s*$/]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -163,4 +163,32 @@ second line 2</code></pre><ul><li>Hello</li><li>world</li></ul><ol><li>Hello</li
 | 
				
			|||||||
        expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
 | 
					        expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("preserves figures", () => {
 | 
				
			||||||
 | 
					        const input = `<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:991/403;" src="Jump to Note_image.png" width="991" height="403"></figure>`;
 | 
				
			||||||
 | 
					        const expected = /*html*/`<figure class="image image-style-align-center image_resized" style="width:50%;"><img style="aspect-ratio:991/403;" src="Jump to Note_image.png" width="991" height="403"></figure>`;
 | 
				
			||||||
 | 
					        expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("converts inline math expressions into Mathtex format", () => {
 | 
				
			||||||
 | 
					        const input = `The equation is\u00a0$e=mc^{2}$.`;
 | 
				
			||||||
 | 
					        const expected = /*html*/`<p>The equation is <span class="math-tex">\\(e=mc^{2}\\)</span>.</p>`;
 | 
				
			||||||
 | 
					        expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("converts display math expressions into Mathtex format", () => {
 | 
				
			||||||
 | 
					        const input = `$$\sqrt{x^{2}+1}$$`;
 | 
				
			||||||
 | 
					        const expected = /*html*/`<p><span class="math-tex">\\[\sqrt{x^{2}+1}\\]</span></p>`;
 | 
				
			||||||
 | 
					        expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("preserves escaped math expressions", () => {
 | 
				
			||||||
 | 
					        const scenarios = [
 | 
				
			||||||
 | 
					            "\\$\\$\sqrt{x^{2}+1}\\$\\$",
 | 
				
			||||||
 | 
					            "The equation is \\$e=mc^{2}\\$."
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					        for (const scenario of scenarios) {
 | 
				
			||||||
 | 
					            expect(markdownService.renderToHtml(scenario, "Title")).toStrictEqual(`<p>${scenario}</p>`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,19 @@ class CustomMarkdownRenderer extends Renderer {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    paragraph(data: Tokens.Paragraph): string {
 | 
					    paragraph(data: Tokens.Paragraph): string {
 | 
				
			||||||
        return super.paragraph(data).trimEnd();
 | 
					        let text = super.paragraph(data).trimEnd();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (text.includes("$")) {
 | 
				
			||||||
 | 
					            // Display math
 | 
				
			||||||
 | 
					            text = text.replaceAll(/(?<!\\)\$\$(.+)\$\$/g,
 | 
				
			||||||
 | 
					                `<span class="math-tex">\\\[$1\\\]</span>`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Inline math
 | 
				
			||||||
 | 
					            text = text.replaceAll(/(?<!\\)\$(.+)\$/g,
 | 
				
			||||||
 | 
					                `<span class="math-tex">\\\($1\\\)</span>`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return text;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    code({ text, lang }: Tokens.Code): string {
 | 
					    code({ text, lang }: Tokens.Code): string {
 | 
				
			||||||
@@ -75,6 +87,9 @@ import { ADMONITION_TYPE_MAPPINGS } from "../export/markdown.js";
 | 
				
			|||||||
import utils from "../utils.js";
 | 
					import utils from "../utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function renderToHtml(content: string, title: string) {
 | 
					function renderToHtml(content: string, title: string) {
 | 
				
			||||||
 | 
					    // Double-escape slashes in math expression because they are otherwise consumed by the parser somewhere.
 | 
				
			||||||
 | 
					    content = content.replaceAll("\\$", "\\\\$");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let html = parse(content, {
 | 
					    let html = parse(content, {
 | 
				
			||||||
        async: false,
 | 
					        async: false,
 | 
				
			||||||
        renderer: renderer
 | 
					        renderer: renderer
 | 
				
			||||||
@@ -84,6 +99,9 @@ function renderToHtml(content: string, title: string) {
 | 
				
			|||||||
    html = importUtils.handleH1(html, title);
 | 
					    html = importUtils.handleH1(html, title);
 | 
				
			||||||
    html = htmlSanitizer.sanitize(html);
 | 
					    html = htmlSanitizer.sanitize(html);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Add a trailing semicolon to CSS styles.
 | 
				
			||||||
 | 
					    html = html.replaceAll(/(<(img|figure).*?style=".*?)"/g, "$1;\"");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Remove slash for self-closing tags to match CKEditor's approach.
 | 
					    // Remove slash for self-closing tags to match CKEditor's approach.
 | 
				
			||||||
    html = html.replace(/<(\w+)([^>]*)\s+\/>/g, "<$1$2>");
 | 
					    html = html.replace(/<(\w+)([^>]*)\s+\/>/g, "<$1$2>");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user