mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Merge branch 'develop' into renovate/electron-35.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 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. | ||||
| @@ -176,10 +176,7 @@ describe("Markdown export", () => { | ||||
|             > [!IMPORTANT] | ||||
|             > This is a very important information. | ||||
|             >${space} | ||||
|             > |     |     | | ||||
|             > | --- | --- | | ||||
|             > | 1   | 2   | | ||||
|             > | 3   | 4   | | ||||
|             > <figure class="table"><table><tbody><tr><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr></tbody></table></figure> | ||||
|  | ||||
|             > [!CAUTION] | ||||
|             > This is a caution. | ||||
| @@ -279,4 +276,16 @@ describe("Markdown export", () => { | ||||
|         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("inlineLink", buildInlineLinkFilter()); | ||||
|         instance.addRule("figure", buildFigureFilter()); | ||||
|         instance.addRule("math", buildMathFilter()); | ||||
|         instance.use(gfm); | ||||
|         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. | ||||
| // https://github.com/mixmark-io/turndown/blob/master/src/commonmark-rules.js | ||||
| function cleanAttribute(attribute: string | null | undefined) { | ||||
|   | ||||
| @@ -149,7 +149,8 @@ function sanitize(dirtyHtml: string) { | ||||
|         allowedTags, | ||||
|         allowedAttributes: { | ||||
|             "*": ["class", "style", "title", "src", "href", "hash", "disabled", "align", "alt", "center", "data-*"], | ||||
|             input: ["type", "checked"] | ||||
|             input: ["type", "checked"], | ||||
|             img: ["width", "height"] | ||||
|         }, | ||||
|         allowedStyles: { | ||||
|             "*": { | ||||
| @@ -161,6 +162,9 @@ function sanitize(dirtyHtml: string) { | ||||
|                 width: sizeRegex, | ||||
|                 height: sizeRegex | ||||
|             }, | ||||
|             img: { | ||||
|                 "aspect-ratio": [ /^\d+\/\d+$/ ], | ||||
|             }, | ||||
|             table: { | ||||
|                 "border-color": colorRegex, | ||||
|                 "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); | ||||
|     }); | ||||
|  | ||||
|     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 { | ||||
|         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 { | ||||
| @@ -75,6 +87,9 @@ import { ADMONITION_TYPE_MAPPINGS } from "../export/markdown.js"; | ||||
| import utils from "../utils.js"; | ||||
|  | ||||
| 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, { | ||||
|         async: false, | ||||
|         renderer: renderer | ||||
| @@ -84,6 +99,9 @@ function renderToHtml(content: string, title: string) { | ||||
|     html = importUtils.handleH1(html, title); | ||||
|     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. | ||||
|     html = html.replace(/<(\w+)([^>]*)\s+\/>/g, "<$1$2>"); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user