mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Compare commits
	
		
			28 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | f4266754d8 | ||
|  | dc288fb18c | ||
|  | 26dfa1ffdb | ||
|  | 153de63f4d | ||
|  | a89629b3de | ||
|  | eec850c11f | ||
|  | e8d63b5647 | ||
|  | bd8b83898f | ||
|  | 97109efb6c | ||
|  | 3e89855aa3 | ||
|  | 3b148eb6f8 | ||
|  | 4e8d1dac67 | ||
|  | 7779fd1dfe | ||
|  | 960d7dede3 | ||
|  | 224fbdc8cd | ||
|  | 8561201abc | ||
|  | 3c1a809276 | ||
|  | 4b101baf00 | ||
|  | 782127dd91 | ||
|  | 4fc8bace94 | ||
|  | 47a22f6e8d | ||
|  | 17d7ff3ff1 | ||
|  | 3582013a33 | ||
|  | 95bbdb3b6b | ||
|  | 8a57960c6e | ||
|  | 5f4a84d967 | ||
|  | 099e90ed64 | ||
|  | 3d324b954d | 
							
								
								
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										4
									
								
								db/migrations/0159__fix_isSynced_in_sync_rows.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								db/migrations/0159__fix_isSynced_in_sync_rows.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| UPDATE sync SET isSynced = 1 WHERE entityName != 'options' OR ( | ||||
|         entityName = 'options' | ||||
|         AND 1 = (SELECT isSynced FROM options WHERE name = sync.entityId) | ||||
|     ) | ||||
| @@ -1,10 +1,18 @@ | ||||
| /* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */ | ||||
|  | ||||
| .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */ | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * CKEditor 5 (v19.1.1) content styles. | ||||
|  * Generated on Fri, 19 Jun 2020 01:26:44 GMT. | ||||
|  * CKEditor 5 (v21.0.0) content styles. | ||||
|  * Generated on Wed, 29 Jul 2020 12:14:43 GMT. | ||||
|  * For more information, check out https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html | ||||
|  */ | ||||
|  | ||||
| :root { | ||||
|     --ck-color-mention-background: hsla(341, 100%, 30%, 0.1); | ||||
|     --ck-color-mention-text: hsl(341, 100%, 30%); | ||||
|     --ck-highlight-marker-blue: hsl(201, 97%, 72%); | ||||
|     --ck-highlight-marker-green: hsl(120, 93%, 68%); | ||||
|     --ck-highlight-marker-pink: hsl(345, 96%, 73%); | ||||
| @@ -15,6 +23,81 @@ | ||||
|     --ck-todo-list-checkmark-size: 16px; | ||||
| } | ||||
|  | ||||
| /* ckeditor5-image/theme/image.css */ | ||||
| .ck-content .image { | ||||
|     display: table; | ||||
|     clear: both; | ||||
|     text-align: center; | ||||
|     margin: 1em auto; | ||||
| } | ||||
| /* ckeditor5-image/theme/image.css */ | ||||
| .ck-content .image img { | ||||
|     display: block; | ||||
|     margin: 0 auto; | ||||
|     max-width: 100%; | ||||
|     min-width: 50px; | ||||
| } | ||||
| /* ckeditor5-image/theme/imagecaption.css */ | ||||
| .ck-content .image > figcaption { | ||||
|     display: table-caption; | ||||
|     caption-side: bottom; | ||||
|     word-break: break-word; | ||||
|     color: hsl(0, 0%, 20%); | ||||
|     background-color: hsl(0, 0%, 97%); | ||||
|     padding: .6em; | ||||
|     font-size: .75em; | ||||
|     outline-offset: -1px; | ||||
| } | ||||
| /* ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized { | ||||
|     max-width: 100%; | ||||
|     display: block; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
| /* ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized img { | ||||
|     width: 100%; | ||||
| } | ||||
| /* ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized > figcaption { | ||||
|     display: block; | ||||
| } | ||||
| /* ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-side { | ||||
|     float: right; | ||||
|     margin-left: var(--ck-image-style-spacing); | ||||
|     max-width: 50%; | ||||
| } | ||||
| /* ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-align-left { | ||||
|     float: left; | ||||
|     margin-right: var(--ck-image-style-spacing); | ||||
| } | ||||
| /* ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-align-center { | ||||
|     margin-left: auto; | ||||
|     margin-right: auto; | ||||
| } | ||||
| /* ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-align-right { | ||||
|     float: right; | ||||
|     margin-left: var(--ck-image-style-spacing); | ||||
| } | ||||
| /* ckeditor5-block-quote/theme/blockquote.css */ | ||||
| .ck-content blockquote { | ||||
|     overflow: hidden; | ||||
|     padding-right: 1.5em; | ||||
|     padding-left: 1.5em; | ||||
|     margin-left: 0; | ||||
|     margin-right: 0; | ||||
|     font-style: italic; | ||||
|     border-left: solid 5px hsl(0, 0%, 80%); | ||||
| } | ||||
| /* ckeditor5-block-quote/theme/blockquote.css */ | ||||
| .ck-content[dir="rtl"] blockquote { | ||||
|     border-left: 0; | ||||
|     border-right: solid 5px hsl(0, 0%, 80%); | ||||
| } | ||||
| /* ckeditor5-list/theme/todolist.css */ | ||||
| .ck-content .todo-list { | ||||
|     list-style: none; | ||||
| @@ -82,12 +165,54 @@ | ||||
| .ck-content .todo-list .todo-list__label .todo-list__label__description { | ||||
|     vertical-align: middle; | ||||
| } | ||||
| /* ckeditor5-media-embed/theme/mediaembed.css */ | ||||
| .ck-content .media { | ||||
|     clear: both; | ||||
|     margin: 1em 0; | ||||
|     display: block; | ||||
|     min-width: 15em; | ||||
| /* ckeditor5-horizontal-line/theme/horizontalline.css */ | ||||
| .ck-content hr { | ||||
|     margin: 15px 0; | ||||
|     height: 4px; | ||||
|     background: hsl(0, 0%, 87%); | ||||
|     border: 0; | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-yellow { | ||||
|     background-color: var(--ck-highlight-marker-yellow); | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-green { | ||||
|     background-color: var(--ck-highlight-marker-green); | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-pink { | ||||
|     background-color: var(--ck-highlight-marker-pink); | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-blue { | ||||
|     background-color: var(--ck-highlight-marker-blue); | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .pen-red { | ||||
|     color: var(--ck-highlight-pen-red); | ||||
|     background-color: transparent; | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .pen-green { | ||||
|     color: var(--ck-highlight-pen-green); | ||||
|     background-color: transparent; | ||||
| } | ||||
| /* ckeditor5-font/theme/fontsize.css */ | ||||
| .ck-content .text-tiny { | ||||
|     font-size: .7em; | ||||
| } | ||||
| /* ckeditor5-font/theme/fontsize.css */ | ||||
| .ck-content .text-small { | ||||
|     font-size: .85em; | ||||
| } | ||||
| /* ckeditor5-font/theme/fontsize.css */ | ||||
| .ck-content .text-big { | ||||
|     font-size: 1.4em; | ||||
| } | ||||
| /* ckeditor5-font/theme/fontsize.css */ | ||||
| .ck-content .text-huge { | ||||
|     font-size: 1.8em; | ||||
| } | ||||
| /* ckeditor5-basic-styles/theme/code.css */ | ||||
| .ck-content code { | ||||
| @@ -95,21 +220,6 @@ | ||||
|     padding: .15em; | ||||
|     border-radius: 2px; | ||||
| } | ||||
| /* ckeditor5-block-quote/theme/blockquote.css */ | ||||
| .ck-content blockquote { | ||||
|     overflow: hidden; | ||||
|     padding-right: 1.5em; | ||||
|     padding-left: 1.5em; | ||||
|     margin-left: 0; | ||||
|     margin-right: 0; | ||||
|     font-style: italic; | ||||
|     border-left: solid 5px hsl(0, 0%, 80%); | ||||
| } | ||||
| /* ckeditor5-block-quote/theme/blockquote.css */ | ||||
| .ck-content[dir="rtl"] blockquote { | ||||
|     border-left: 0; | ||||
|     border-right: solid 5px hsl(0, 0%, 80%); | ||||
| } | ||||
| /* ckeditor5-table/theme/table.css */ | ||||
| .ck-content .table { | ||||
|     margin: 1em auto; | ||||
| @@ -143,98 +253,6 @@ | ||||
| .ck-content[dir="ltr"] .table th { | ||||
|     text-align: left; | ||||
| } | ||||
| /* ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized { | ||||
|     max-width: 100%; | ||||
|     display: block; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
| /* ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized img { | ||||
|     width: 100%; | ||||
| } | ||||
| /* ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized > figcaption { | ||||
|     display: block; | ||||
| } | ||||
| /* ckeditor5-image/theme/image.css */ | ||||
| .ck-content .image { | ||||
|     display: table; | ||||
|     clear: both; | ||||
|     text-align: center; | ||||
|     margin: 1em auto; | ||||
| } | ||||
| /* ckeditor5-image/theme/image.css */ | ||||
| .ck-content .image img { | ||||
|     display: block; | ||||
|     margin: 0 auto; | ||||
|     max-width: 100%; | ||||
|     min-width: 50px; | ||||
| } | ||||
| /* ckeditor5-image/theme/imagecaption.css */ | ||||
| .ck-content .image > figcaption { | ||||
|     display: table-caption; | ||||
|     caption-side: bottom; | ||||
|     word-break: break-word; | ||||
|     color: hsl(0, 0%, 20%); | ||||
|     background-color: hsl(0, 0%, 97%); | ||||
|     padding: .6em; | ||||
|     font-size: .75em; | ||||
|     outline-offset: -1px; | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-yellow { | ||||
|     background-color: var(--ck-highlight-marker-yellow); | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-green { | ||||
|     background-color: var(--ck-highlight-marker-green); | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-pink { | ||||
|     background-color: var(--ck-highlight-marker-pink); | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-blue { | ||||
|     background-color: var(--ck-highlight-marker-blue); | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .pen-red { | ||||
|     color: var(--ck-highlight-pen-red); | ||||
|     background-color: transparent; | ||||
| } | ||||
| /* ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .pen-green { | ||||
|     color: var(--ck-highlight-pen-green); | ||||
|     background-color: transparent; | ||||
| } | ||||
| /* ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-side, | ||||
| .ck-content .image-style-align-left, | ||||
| .ck-content .image-style-align-center, | ||||
| .ck-content .image-style-align-right { | ||||
|     max-width: 50%; | ||||
| } | ||||
| /* ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-side { | ||||
|     float: right; | ||||
|     margin-left: var(--ck-image-style-spacing); | ||||
| } | ||||
| /* ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-align-left { | ||||
|     float: left; | ||||
|     margin-right: var(--ck-image-style-spacing); | ||||
| } | ||||
| /* ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-align-center { | ||||
|     margin-left: auto; | ||||
|     margin-right: auto; | ||||
| } | ||||
| /* ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-align-right { | ||||
|     float: right; | ||||
|     margin-left: var(--ck-image-style-spacing); | ||||
| } | ||||
| /* ckeditor5-page-break/theme/pagebreak.css */ | ||||
| .ck-content .page-break { | ||||
|     position: relative; | ||||
| @@ -271,21 +289,12 @@ | ||||
|     -ms-user-select: none; | ||||
|     user-select: none; | ||||
| } | ||||
| /* ckeditor5-font/theme/fontsize.css */ | ||||
| .ck-content .text-tiny { | ||||
|     font-size: .7em; | ||||
| } | ||||
| /* ckeditor5-font/theme/fontsize.css */ | ||||
| .ck-content .text-small { | ||||
|     font-size: .85em; | ||||
| } | ||||
| /* ckeditor5-font/theme/fontsize.css */ | ||||
| .ck-content .text-big { | ||||
|     font-size: 1.4em; | ||||
| } | ||||
| /* ckeditor5-font/theme/fontsize.css */ | ||||
| .ck-content .text-huge { | ||||
|     font-size: 1.8em; | ||||
| /* ckeditor5-media-embed/theme/mediaembed.css */ | ||||
| .ck-content .media { | ||||
|     clear: both; | ||||
|     margin: 1em 0; | ||||
|     display: block; | ||||
|     min-width: 15em; | ||||
| } | ||||
| /* ckeditor5-code-block/theme/codeblock.css */ | ||||
| .ck-content pre { | ||||
| @@ -307,12 +316,10 @@ | ||||
|     padding: 0; | ||||
|     border-radius: 0; | ||||
| } | ||||
| /* ckeditor5-horizontal-line/theme/horizontalline.css */ | ||||
| .ck-content hr { | ||||
|     margin: 15px 0; | ||||
|     height: 4px; | ||||
|     background: hsl(0, 0%, 87%); | ||||
|     border: 0; | ||||
| /* ckeditor5-mention/theme/mention.css */ | ||||
| .ck-content .mention { | ||||
|     background: var(--ck-color-mention-background); | ||||
|     color: var(--ck-color-mention-text); | ||||
| } | ||||
| @media print { | ||||
|     /* ckeditor5-page-break/theme/pagebreak.css */ | ||||
| @@ -324,9 +331,3 @@ | ||||
|         display: none; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* !!!!!! TRILIUM CUSTOM CHANGES !!!!!! */ | ||||
|  | ||||
| .ck-widget__type-around { /* gets rid of triangles: https://github.com/zadam/trilium/issues/1129 */ | ||||
|     display: none; | ||||
| } | ||||
|   | ||||
							
								
								
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "version": "0.43.0-beta", | ||||
|   "version": "0.43.3", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
| @@ -7923,9 +7923,9 @@ | ||||
|       "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" | ||||
|     }, | ||||
|     "node-abi": { | ||||
|       "version": "2.16.0", | ||||
|       "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.16.0.tgz", | ||||
|       "integrity": "sha512-+sa0XNlWDA6T+bDLmkCUYn6W5k5W6BPRL6mqzSCs6H/xUgtl4D5x2fORKDzopKiU6wsyn/+wXlRXwXeSp+mtoA==", | ||||
|       "version": "2.18.0", | ||||
|       "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.18.0.tgz", | ||||
|       "integrity": "sha512-yi05ZoiuNNEbyT/xXfSySZE+yVnQW6fxPZuFbLyS1s6b5Kw3HzV2PHOM4XR+nsjzkHxByK+2Wg+yCQbe35l8dw==", | ||||
|       "requires": { | ||||
|         "semver": "^5.4.1" | ||||
|       }, | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   "name": "trilium", | ||||
|   "productName": "Trilium Notes", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.43.2", | ||||
|   "version": "0.43.4", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "bin": { | ||||
| @@ -54,7 +54,7 @@ | ||||
|     "jimp": "0.10.3", | ||||
|     "mime-types": "2.1.27", | ||||
|     "multer": "1.4.2", | ||||
|     "node-abi": "2.16.0", | ||||
|     "node-abi": "2.18.0", | ||||
|     "open": "7.0.3", | ||||
|     "portscanner": "2.2.0", | ||||
|     "rand-token": "1.0.1", | ||||
|   | ||||
| @@ -52,7 +52,17 @@ class Attribute { | ||||
|      *         3. attribute is owned by some note's ancestor and is inheritable | ||||
|      */ | ||||
|     isAffecting(affectedNote) { | ||||
|         if (!affectedNote) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         const attrNote = this.getNote(); | ||||
|  | ||||
|         if (!attrNote) { | ||||
|             // the note (owner of the attribute) is not even loaded into the cache so it should not affect anything else | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         const owningNotes = [affectedNote, ...affectedNote.getTemplateNotes()]; | ||||
|  | ||||
|         for (const owningNote of owningNotes) { | ||||
|   | ||||
| @@ -83,6 +83,8 @@ function setupGlobs() { | ||||
|  | ||||
|     $("body").on("click", "a.external", function () { | ||||
|         window.open($(this).attr("href"), '_blank'); | ||||
|  | ||||
|         return false; | ||||
|     }); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -107,8 +107,8 @@ export default class LoadResults { | ||||
|      *          notably changes in note itself should not have any effect on attributes | ||||
|      */ | ||||
|     hasAttributeRelatedChanges() { | ||||
|         return this.branches.length === 0 | ||||
|             && this.attributes.length === 0; | ||||
|         return this.branches.length > 0 | ||||
|             || this.attributes.length > 0; | ||||
|     } | ||||
|  | ||||
|     isEmpty() { | ||||
|   | ||||
| @@ -24,7 +24,7 @@ async function getRenderedContent(note) { | ||||
|             .attr("src", `api/images/${note.noteId}/${note.title}`) | ||||
|             .css("max-width", "100%"); | ||||
|     } | ||||
|     else if (type === 'file') { | ||||
|     else if (type === 'file' || type === 'pdf') { | ||||
|         function getFileUrl() { | ||||
|             return utils.getUrlForDownload("api/notes/" + note.noteId + "/download"); | ||||
|         } | ||||
| @@ -47,19 +47,21 @@ async function getRenderedContent(note) { | ||||
|         // open doesn't work for protected notes since it works through browser which isn't in protected session | ||||
|         $openButton.toggle(!note.isProtected); | ||||
|  | ||||
|         $rendered = $('<div>'); | ||||
|         $rendered = $('<div style="display: flex; flex-direction: column; height: 100%;">'); | ||||
|  | ||||
|         if (note.mime === 'application/pdf' && utils.isElectron()) { | ||||
|             const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; height: 100%; flex-grow: 100;"></iframe>'); | ||||
|         if (type === 'pdf') { | ||||
|             const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; flex-grow: 100;"></iframe>'); | ||||
|             $pdfPreview.attr("src", utils.getUrlForDownload("api/notes/" + note.noteId + "/open")); | ||||
|  | ||||
|             $rendered.append($pdfPreview); | ||||
|         } | ||||
|  | ||||
|         $rendered | ||||
|             .append($downloadButton) | ||||
|             .append('   ') | ||||
|             .append($openButton); | ||||
|         $rendered.append( | ||||
|             $("<div>") | ||||
|                 .append($downloadButton) | ||||
|                 .append('   ') | ||||
|                 .append($openButton) | ||||
|         ); | ||||
|     } | ||||
|     else if (type === 'render') { | ||||
|         $rendered = $('<div>'); | ||||
| @@ -90,6 +92,10 @@ async function getRenderedContent(note) { | ||||
| function getRenderingType(note) { | ||||
|     let type = note.type; | ||||
|  | ||||
|     if (type === 'file' && note.mime === 'application/pdf') { | ||||
|         type = 'pdf'; | ||||
|     } | ||||
|  | ||||
|     if (note.isProtected) { | ||||
|         if (protectedSessionHolder.isProtectedSessionAvailable()) { | ||||
|             protectedSessionHolder.touchProtectedSession(); | ||||
| @@ -104,4 +110,4 @@ function getRenderingType(note) { | ||||
|  | ||||
| export default { | ||||
|     getRenderedContent | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -273,6 +273,12 @@ class TreeCache { | ||||
|     async getBranchId(parentNoteId, childNoteId) { | ||||
|         const child = await this.getNote(childNoteId); | ||||
|  | ||||
|         if (!child) { | ||||
|             console.error(`Could not find branchId for parent=${parentNoteId}, child=${childNoteId} since child does not exist`); | ||||
|  | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         return child.parentToBranch[parentNoteId]; | ||||
|     } | ||||
|  | ||||
| @@ -282,6 +288,13 @@ class TreeCache { | ||||
|     async getNoteComplement(noteId) { | ||||
|         if (!this.noteComplementPromises[noteId]) { | ||||
|             this.noteComplementPromises[noteId] = server.get('notes/' + noteId).then(row => new NoteComplement(row)); | ||||
|  | ||||
|             // we don't want to keep large payloads forever in memory so we clean that up quite quickly | ||||
|             // this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components) | ||||
|             // this is also a work around for missing invalidation after change | ||||
|             this.noteComplementPromises[noteId].then( | ||||
|                 () => setTimeout(() => this.noteComplementPromises[noteId] = null, 1000) | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         return await this.noteComplementPromises[noteId]; | ||||
|   | ||||
| @@ -328,6 +328,9 @@ function dynamicRequire(moduleName) { | ||||
| } | ||||
|  | ||||
| function timeLimit(promise, limitMs) { | ||||
|     // better stack trace if created outside of promise | ||||
|     const error = new Error('Process exceeded time limit ' + limitMs); | ||||
|  | ||||
|     return new Promise((res, rej) => { | ||||
|         let resolved = false; | ||||
|  | ||||
| @@ -339,7 +342,7 @@ function timeLimit(promise, limitMs) { | ||||
|  | ||||
|         setTimeout(() => { | ||||
|             if (!resolved) { | ||||
|                 rej(new Error('Process exceeded time limit ' + limitMs)); | ||||
|                 rej(error); | ||||
|             } | ||||
|         }, limitMs); | ||||
|     }); | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import CollapsibleWidget from "../collapsible_widget.js"; | ||||
| import treeCache from "../../services/tree_cache.js"; | ||||
|  | ||||
| let linkMapContainerIdCtr = 1; | ||||
|  | ||||
| @@ -89,5 +90,19 @@ export default class LinkMapWidget extends CollapsibleWidget { | ||||
|         if (loadResults.getAttributes().find(attr => attr.type === 'relation' && (attr.noteId === this.noteId || attr.value === this.noteId))) { | ||||
|             this.noteSwitched(); | ||||
|         } | ||||
|  | ||||
|         const changedNoteIds = loadResults.getNoteIds(); | ||||
|  | ||||
|         if (changedNoteIds.length > 0) { | ||||
|             const $linkMapContainer = this.$body.find('.link-map-container'); | ||||
|  | ||||
|             for (const noteId of changedNoteIds) { | ||||
|                 const note = treeCache.notes[noteId]; | ||||
|  | ||||
|                 if (note) { | ||||
|                     $linkMapContainer.find(`a[data-note-path="${noteId}"]`).text(note.title); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -79,6 +79,7 @@ const TPL = ` | ||||
|                 <span class="slider checked"></span> | ||||
|             </span> | ||||
|         </div> | ||||
|         <a data-trigger-command="findInText" class="dropdown-item">Search in note <kbd data-command="findInText"></a> | ||||
|         <a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a> | ||||
|         <a data-trigger-command="showAttributes" class="dropdown-item show-attributes-button"><kbd data-command="showAttributes"></kbd> Attributes</a> | ||||
|         <a data-trigger-command="showLinkMap" class="dropdown-item show-link-map-button"><kbd data-command="showLinkMap"></kbd> Link map</a> | ||||
| @@ -140,4 +141,4 @@ export default class NoteActionsWidget extends TabAwareWidget { | ||||
|             this.refresh(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -331,4 +331,9 @@ export default class NoteDetailWidget extends TabAwareWidget { | ||||
|             saveSelection: true | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     // used by cutToNote in CKEditor build | ||||
|     async saveNoteDetailNowCommand() { | ||||
|         await this.spacedUpdate.updateNowIfNecessary(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -32,6 +32,7 @@ const TPL = ` | ||||
|     .tree { | ||||
|         height: 100%; | ||||
|         overflow: auto; | ||||
|         padding-bottom: 20px; | ||||
|     } | ||||
|      | ||||
|     .refresh-search-button { | ||||
| @@ -303,10 +304,13 @@ export default class NoteTreeWidget extends TabAwareWidget { | ||||
|  | ||||
|         this.$tree.fancytree({ | ||||
|             titlesTabbable: true, | ||||
|             autoScroll: true, | ||||
|             keyboard: false, // we takover keyboard handling in the hotkeys plugin | ||||
|             extensions: utils.isMobile() ? ["dnd5", "clones"] : ["hotkeys", "dnd5", "clones"], | ||||
|             source: treeData, | ||||
|             scrollOfs: { | ||||
|                 top: 100, | ||||
|                 bottom: 100 | ||||
|             }, | ||||
|             scrollParent: this.$tree, | ||||
|             minExpandLevel: 2, // root can't be collapsed | ||||
|             click: (event, data) => { | ||||
|   | ||||
| @@ -20,8 +20,8 @@ const TPL = ` | ||||
|     } | ||||
|     </style> | ||||
|      | ||||
|     <button class="hide-left-pane-button btn btn-sm icon-button bx bx-chevrons-left" title="Show sidebar"></button> | ||||
|     <button class="show-left-pane-button btn btn-sm icon-button bx bx-chevrons-right" title="Hide sidebar"></button> | ||||
|     <button class="hide-left-pane-button btn btn-sm icon-button bx bx-chevrons-left" title="Hide sidebar"></button> | ||||
|     <button class="show-left-pane-button btn btn-sm icon-button bx bx-chevrons-right" title="Show sidebar"></button> | ||||
|              | ||||
|     <button class="hide-right-pane-button btn btn-sm icon-button bx bx-chevrons-right" title="Hide sidebar"></button> | ||||
|     <button class="show-right-pane-button btn btn-sm icon-button bx bx-chevrons-left" title="Show sidebar"></button> | ||||
|   | ||||
| @@ -36,10 +36,10 @@ export default class AbstractTextTypeWidget extends TypeWidget { | ||||
|                     .append($link) | ||||
|             ); | ||||
|  | ||||
|             const {renderedContent} = await noteContentRenderer.getRenderedContent(note); | ||||
|             const {renderedContent, type} = await noteContentRenderer.getRenderedContent(note); | ||||
|  | ||||
|             $el.append( | ||||
|                 $('<div class="include-note-content">') | ||||
|                 $(`<div class="include-note-content type-${type}">`) | ||||
|                     .append(renderedContent) | ||||
|             ); | ||||
|         } | ||||
|   | ||||
| @@ -121,10 +121,10 @@ export default class FileTypeWidget extends TypeWidget { | ||||
|         this.$pdfPreview.attr('src', '').empty().hide(); | ||||
|  | ||||
|         if (noteComplement.content) { | ||||
|             this.$previewContent.show(); | ||||
|             this.$previewContent.show().scrollTop(0); | ||||
|             this.$previewContent.text(noteComplement.content); | ||||
|         } | ||||
|         else if (note.mime === 'application/pdf' && utils.isElectron()) { | ||||
|         else if (note.mime === 'application/pdf') { | ||||
|             this.$pdfPreview.show(); | ||||
|             this.$pdfPreview.attr("src", utils.getUrlForDownload("api/notes/" + this.noteId + "/open")); | ||||
|         } | ||||
|   | ||||
							
								
								
									
										14
									
								
								src/public/manifest.webmanifest
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/public/manifest.webmanifest
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| { | ||||
|   "name": "Trilium", | ||||
|   "short_name": "Trilium", | ||||
|   "theme_color": "DarkGray", | ||||
|   "background_color": "DarkGray", | ||||
|   "display": "standalone", | ||||
|   "scope": "/", | ||||
|   "start_url": "/", | ||||
|   "icons": [{ | ||||
|     "src": "images/app-icons/ios/apple-touch-icon.png", | ||||
|     "sizes": "180x180", | ||||
|     "type": "image/png" | ||||
|   }] | ||||
| } | ||||
| @@ -693,11 +693,23 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href | ||||
|     overflow: auto; | ||||
| } | ||||
|  | ||||
| .include-note.box-size-small .include-note-content.type-pdf { | ||||
|     height: 10em; /* PDF is rendered in iframe and must be sized absolutely */ | ||||
| } | ||||
|  | ||||
| .include-note.box-size-medium .include-note-content { | ||||
|     max-height: 20em; | ||||
|     overflow: auto; | ||||
| } | ||||
|  | ||||
| .include-note.box-size-medium .include-note-content.type-pdf { | ||||
|     height: 20em; /* PDF is rendered in iframe and must be sized absolutely */ | ||||
| } | ||||
|  | ||||
| .include-note.box-size-full .include-note-content.type-pdf { | ||||
|     height: 50em; /* PDF is rendered in iframe and it's not possible to put full height so at least a large height */ | ||||
| } | ||||
|  | ||||
| .alert-warning { | ||||
|     color: var(--main-text-color) !important; | ||||
|     background-color: var(--accented-background-color) !important; | ||||
|   | ||||
| @@ -49,7 +49,7 @@ async function loginSync(req) { | ||||
|  | ||||
|     return { | ||||
|         sourceId: sourceIdService.getCurrentSourceId(), | ||||
|         maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync WHERE isSynced = 1") | ||||
|         maxSyncId: await sql.getValue("SELECT COALESCE(MAX(id), 0) FROM sync WHERE isSynced = 1") | ||||
|     }; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -17,8 +17,9 @@ async function getNote(req) { | ||||
|     if (note.isStringNote()) { | ||||
|         note.content = await note.getContent(); | ||||
|  | ||||
|         if (note.type === 'file') { | ||||
|             note.content = note.content.substr(0, 10000); | ||||
|         if (note.type === 'file' && note.content.length > 10000) { | ||||
|             note.content = note.content.substr(0, 10000) | ||||
|                 + `\r\n\r\n... and ${note.content.length - 10000} more characters.`; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -50,7 +50,7 @@ async function getStats() { | ||||
| async function checkSync() { | ||||
|     return { | ||||
|         entityHashes: await contentHashService.getEntityHashes(), | ||||
|         maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1') | ||||
|         maxSyncId: await sql.getValue('SELECT COALESCE(MAX(id), 0) FROM sync WHERE isSynced = 1') | ||||
|     }; | ||||
| } | ||||
|  | ||||
| @@ -124,7 +124,7 @@ async function getChanged(req) { | ||||
|  | ||||
|     const ret = { | ||||
|         syncs: await syncService.getSyncRecords(syncs), | ||||
|         maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1') | ||||
|         maxSyncId: await sql.getValue('SELECT COALESCE(MAX(id), 0) FROM sync WHERE isSynced = 1') | ||||
|     }; | ||||
|  | ||||
|     if (ret.syncs.length > 0) { | ||||
|   | ||||
| @@ -23,7 +23,7 @@ async function index(req, res) { | ||||
|         treeFontSize: parseInt(options.treeFontSize), | ||||
|         detailFontSize: parseInt(options.detailFontSize), | ||||
|         sourceId: await sourceIdService.generateSourceId(), | ||||
|         maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"), | ||||
|         maxSyncIdAtLoad: await sql.getValue("SELECT COALESCE(MAX(id), 0) FROM sync"), | ||||
|         instanceName: config.General ? config.General.instanceName : null, | ||||
|         appCssNoteIds: await getAppCssNoteIds(), | ||||
|         isDev: env.isDev(), | ||||
|   | ||||
| @@ -4,7 +4,7 @@ const build = require('./build'); | ||||
| const packageJson = require('../../package'); | ||||
| const {TRILIUM_DATA_DIR} = require('./data_dir'); | ||||
|  | ||||
| const APP_DB_VERSION = 158; | ||||
| const APP_DB_VERSION = 159; | ||||
| const SYNC_VERSION = 14; | ||||
| const CLIPPER_PROTOCOL_VERSION = "1.0"; | ||||
|  | ||||
| @@ -16,4 +16,4 @@ module.exports = { | ||||
|     buildRevision: build.buildRevision, | ||||
|     dataDirectory: TRILIUM_DATA_DIR, | ||||
|     clipperProtocolVersion: CLIPPER_PROTOCOL_VERSION | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { buildDate:"2020-07-11T23:58:59+02:00", buildRevision: "08edc521e48ea7c6de96c19290134b6552844313" }; | ||||
| module.exports = { buildDate:"2020-08-27T23:58:58+02:00", buildRevision: "dc288fb18c7622f6e08c574888dc0e8c90e544c2" }; | ||||
|   | ||||
| @@ -98,7 +98,17 @@ async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSw | ||||
|  | ||||
| async function shrinkImage(buffer, originalName) { | ||||
|     // we do resizing with max (100) quality which will be trimmed during optimization step next | ||||
|     const resizedImage = await resize(buffer, 100); | ||||
|     let resizedImage; | ||||
|  | ||||
|     try { | ||||
|         resizedImage = await resize(buffer, 100); | ||||
|     } | ||||
|     catch (e) { | ||||
|         log.error("Failed to resize image '" + originalName + "'\nStack: " + e.stack); | ||||
|  | ||||
|         resizedImage = buffer; | ||||
|     } | ||||
|  | ||||
|     let finalImageBuffer; | ||||
|  | ||||
|     const jpegQuality = await optionService.getOptionInt('imageJpegQuality'); | ||||
| @@ -107,7 +117,15 @@ async function shrinkImage(buffer, originalName) { | ||||
|         finalImageBuffer = await optimize(resizedImage, jpegQuality); | ||||
|     } catch (e) { | ||||
|         log.error("Failed to optimize image '" + originalName + "'\nStack: " + e.stack); | ||||
|         finalImageBuffer = await resize(buffer, jpegQuality); | ||||
|  | ||||
|         try { | ||||
|             finalImageBuffer = await resize(buffer, jpegQuality); | ||||
|         } | ||||
|         catch (e) { | ||||
|             log.error("Failed to resize image '" + originalName + "'\nStack: " + e.stack); | ||||
|  | ||||
|             finalImageBuffer = buffer; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // if resizing & shrinking did not help with size then save the original | ||||
|   | ||||
| @@ -279,20 +279,30 @@ const downloadImagePromises = {}; | ||||
| function replaceUrl(content, url, imageNote) { | ||||
|     const quotedUrl = utils.quoteRegex(url); | ||||
|  | ||||
|     return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "g"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`); | ||||
|     return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "ig"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`); | ||||
| } | ||||
|  | ||||
| async function downloadImages(noteId, content) { | ||||
|     const re = /<img[^>]*?\ssrc=['"]([^'">]+)['"]/ig; | ||||
|     let match; | ||||
|     const imageRe = /<img[^>]*?\ssrc=['"]([^'">]+)['"]/ig; | ||||
|     let imageMatch; | ||||
|  | ||||
|     const origContent = content; | ||||
|     while (imageMatch = imageRe.exec(content)) { | ||||
|         const url = imageMatch[1]; | ||||
|         const inlineImageMatch = /^data:image\/[a-z]+;base64,/.exec(url); | ||||
|  | ||||
|     while (match = re.exec(origContent)) { | ||||
|         const url = match[1]; | ||||
|         if (inlineImageMatch) { | ||||
|             const imageBase64 = url.substr(inlineImageMatch[0].length); | ||||
|             const imageBuffer = Buffer.from(imageBase64, 'base64'); | ||||
|  | ||||
|         if (!url.includes('api/images/') | ||||
|             // this is and exception for the web clipper's "imageId" | ||||
|             const imageService = require('../services/image'); | ||||
|             const {note} = await imageService.saveImage(noteId, imageBuffer, "inline image", true); | ||||
|  | ||||
|             content = content.substr(0, imageMatch.index) | ||||
|                 + `<img src="api/images/${note.noteId}/${note.title}"` | ||||
|                 + content.substr(imageMatch.index + imageMatch[0].length); | ||||
|         } | ||||
|         else if (!url.includes('api/images/') | ||||
|             // this is an exception for the web clipper's "imageId" | ||||
|             && (url.length !== 20 || url.toLowerCase().startsWith('http'))) { | ||||
|  | ||||
|             if (url in imageUrlToNoteIdMapping) { | ||||
| @@ -303,7 +313,6 @@ async function downloadImages(noteId, content) { | ||||
|                 } | ||||
|                 else { | ||||
|                     content = replaceUrl(content, url, imageNote); | ||||
|  | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| @@ -315,7 +324,6 @@ async function downloadImages(noteId, content) { | ||||
|                 imageUrlToNoteIdMapping[url] = existingImage.noteId; | ||||
|  | ||||
|                 content = replaceUrl(content, url, existingImage); | ||||
|  | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -51,7 +51,7 @@ async function initNotSyncedOptions(initialized, startNotePath = 'root', opts = | ||||
|     await optionService.createOption('theme', opts.theme || 'white', false); | ||||
|  | ||||
|     await optionService.createOption('syncServerHost', opts.syncServerHost || '', false); | ||||
|     await optionService.createOption('syncServerTimeout', '5000', false); | ||||
|     await optionService.createOption('syncServerTimeout', '60000', false); | ||||
|     await optionService.createOption('syncProxy', opts.syncProxy || '', false); | ||||
| } | ||||
|  | ||||
| @@ -116,4 +116,4 @@ module.exports = { | ||||
|     initSyncedOptions, | ||||
|     initNotSyncedOptions, | ||||
|     initStartupOptions | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -84,10 +84,11 @@ function exec(opts) { | ||||
| } | ||||
|  | ||||
| async function getImage(imageUrl) { | ||||
|     const proxyConf = await syncOptions.getSyncProxy(); | ||||
|     const opts = { | ||||
|         method: 'GET', | ||||
|         url: imageUrl, | ||||
|         proxy: await syncOptions.getSyncProxy() | ||||
|         proxy: proxyConf !== "noproxy" ? proxyConf : null | ||||
|     }; | ||||
|  | ||||
|     const client = getClient(opts); | ||||
|   | ||||
| @@ -123,7 +123,7 @@ async function doLogin() { | ||||
| } | ||||
|  | ||||
| async function pullSync(syncContext) { | ||||
|     let appliedPulls = 0; | ||||
|     let atLeastOnePullApplied = false; | ||||
|  | ||||
|     while (true) { | ||||
|         const lastSyncedPull = await getLastSyncedPull(); | ||||
| @@ -132,6 +132,9 @@ async function pullSync(syncContext) { | ||||
|         const startDate = Date.now(); | ||||
|  | ||||
|         const resp = await syncRequest(syncContext, 'GET', changesUri); | ||||
|  | ||||
|         const pulledDate = Date.now(); | ||||
|  | ||||
|         stats.outstandingPulls = resp.maxSyncId - lastSyncedPull; | ||||
|  | ||||
|         if (stats.outstandingPulls < 0) { | ||||
| @@ -147,10 +150,10 @@ async function pullSync(syncContext) { | ||||
|         await sql.transactional(async () => { | ||||
|             for (const {sync, entity} of rows) { | ||||
|                 if (!sourceIdService.isLocalSourceId(sync.sourceId)) { | ||||
|                     if (appliedPulls === 0 && sync.entity !== 'recent_notes') { // send only for first | ||||
|                     if (!atLeastOnePullApplied && sync.entity !== 'recent_notes') { // send only for first | ||||
|                         ws.syncPullInProgress(); | ||||
|  | ||||
|                         appliedPulls++; | ||||
|                         atLeastOnePullApplied = true; | ||||
|                     } | ||||
|  | ||||
|                     await syncUpdateService.updateEntity(sync, entity, syncContext.sourceId); | ||||
| @@ -162,10 +165,10 @@ async function pullSync(syncContext) { | ||||
|             await setLastSyncedPull(rows[rows.length - 1].sync.id); | ||||
|         }); | ||||
|  | ||||
|         log.info(`Pulled and updated ${rows.length} changes from ${changesUri} in ${Date.now() - startDate}ms`); | ||||
|         log.info(`Pulled ${rows.length} changes starting at syncId=${lastSyncedPull} in ${pulledDate - startDate}ms and applied them in ${Date.now() - pulledDate}ms, ${stats.outstandingPulls} outstanding pulls`); | ||||
|     } | ||||
|  | ||||
|     if (appliedPulls > 0) { | ||||
|     if (atLeastOnePullApplied) { | ||||
|         ws.syncPullFinished(); | ||||
|     } | ||||
|  | ||||
| @@ -365,7 +368,7 @@ async function updatePushStats() { | ||||
| } | ||||
|  | ||||
| async function getMaxSyncId() { | ||||
|     return await sql.getValue('SELECT MAX(id) FROM sync'); | ||||
|     return await sql.getValue('SELECT COALESCE(MAX(id), 0) FROM sync'); | ||||
| } | ||||
|  | ||||
| sqlInit.dbReady.then(async () => { | ||||
|   | ||||
| @@ -82,7 +82,8 @@ async function fillSyncRows(entityName, entityPrimaryKey, condition = '') { | ||||
|                     entityName: entityName, | ||||
|                     entityId: entityId, | ||||
|                     sourceId: "SYNC_FILL", | ||||
|                     utcSyncDate: dateUtils.utcNowDateTime() | ||||
|                     utcSyncDate: dateUtils.utcNowDateTime(), | ||||
|                     isSynced: true | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
| @@ -127,4 +128,4 @@ module.exports = { | ||||
|     fillAllSyncRows, | ||||
|     addEntitySyncsForSector, | ||||
|     getMaxSyncId: () => maxSyncId | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -159,7 +159,11 @@ function getContentDisposition(filename) { | ||||
|     return `file; filename="${sanitizedFilename}"; filename*=UTF-8''${sanitizedFilename}`; | ||||
| } | ||||
|  | ||||
| const STRING_MIME_TYPES = ["application/x-javascript", "image/svg+xml"]; | ||||
| const STRING_MIME_TYPES = [ | ||||
|     "application/javascript", | ||||
|     "application/x-javascript", | ||||
|     "image/svg+xml" | ||||
| ]; | ||||
|  | ||||
| function isStringNote(type, mime) { | ||||
|     return ["text", "code", "relation-map", "search"].includes(type) | ||||
| @@ -218,6 +222,9 @@ function formatDownloadTitle(filename, type, mime) { | ||||
| } | ||||
|  | ||||
| function timeLimit(promise, limitMs) { | ||||
|     // better stack trace if created outside of promise | ||||
|     const error = new Error('Process exceeded time limit ' + limitMs); | ||||
|  | ||||
|     return new Promise((res, rej) => { | ||||
|         let resolved = false; | ||||
|  | ||||
| @@ -229,7 +236,7 @@ function timeLimit(promise, limitMs) { | ||||
|  | ||||
|         setTimeout(() => { | ||||
|             if (!resolved) { | ||||
|                 rej(new Error('Process exceeded time limit ' + limitMs)); | ||||
|                 rej(error); | ||||
|             } | ||||
|         }, limitMs); | ||||
|     }); | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|     <link rel="shortcut icon" href="favicon.ico"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> | ||||
|     <title>Trilium Notes</title> | ||||
|     <link rel="apple-touch-icon" sizes="180x180" href="images/app-icons/ios/apple-touch-icon.png"> | ||||
|     <link rel="manifest" href="manifest.webmanifest"> | ||||
|  | ||||
|     <style> | ||||
|         .lds-roller { | ||||
| @@ -140,4 +140,4 @@ | ||||
| <link rel="stylesheet" type="text/css" href="libraries/boxicons/css/boxicons.min.css"> | ||||
|  | ||||
| </body> | ||||
| </html> | ||||
| </html> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user