mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 13:01:31 +08:00 
			
		
		
		
	Merge branch 'develop' into renovate/typescript-5.x
This commit is contained in:
		
						commit
						cfcc4740a1
					
				| @ -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>"); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Elian Doran
						Elian Doran