mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Compare commits
	
		
			21 Commits
		
	
	
		
			v0.61.10-b
			...
			v0.61.12
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 803b6df40c | ||
|  | 1ebdb0f5e1 | ||
|  | df5951ce46 | ||
|  | d3a477b8f2 | ||
|  | 01093d05d7 | ||
|  | a9b63111ae | ||
|  | ed1a731950 | ||
|  | ef974ab1f5 | ||
|  | 1cd391a132 | ||
|  | 1c15527d95 | ||
|  | 7af79ec33b | ||
|  | 4294c043d8 | ||
|  | e5b925abf8 | ||
|  | 90c0a4a437 | ||
|  | 692f7868bc | ||
|  | 5282af55f6 | ||
|  | aefc4c6bd2 | ||
|  | b39ba76505 | ||
|  | 9d918e7a54 | ||
|  | 38db7f9db7 | ||
|  | 5163e50e7d | 
							
								
								
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -3,8 +3,12 @@ module.exports = () => { | ||||
|     const becca = require("../../src/becca/becca"); | ||||
|     const cls = require("../../src/services/cls"); | ||||
|     const log = require("../../src/services/log"); | ||||
|     const sql = require("../../src/services/sql"); | ||||
|  | ||||
|     cls.init(() => { | ||||
|         // emergency disabling of image compression since it appears to make problems in migration to 0.61 | ||||
|         sql.execute(`UPDATE options SET value = 'false' WHERE name = 'compressImages'`); | ||||
|  | ||||
|         beccaLoader.load(); | ||||
|  | ||||
|         for (const note of Object.values(becca.notes)) { | ||||
|   | ||||
							
								
								
									
										2
									
								
								db/migrations/0227__disable_image_compression.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								db/migrations/0227__disable_image_compression.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| -- emergency disabling of image compression since it appears to make problems in migration to 0.61 | ||||
| UPDATE options SET value = 'false' WHERE name = 'compressImages'; | ||||
| @@ -5,8 +5,8 @@ | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * CKEditor 5 (v39.0.2) content styles. | ||||
|  * Generated on Wed, 06 Sep 2023 07:32:15 GMT. | ||||
|  * CKEditor 5 (v40.0.0) content styles. | ||||
|  * Generated on Thu, 19 Oct 2023 13:45:23 GMT. | ||||
|  * For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html | ||||
|  */ | ||||
|  | ||||
| @@ -42,6 +42,18 @@ | ||||
|     overflow-wrap: break-word; | ||||
|     position: relative; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-table/theme/tablecaption.css */ | ||||
| .ck-content .table > figcaption { | ||||
|     display: table-caption; | ||||
|     caption-side: top; | ||||
|     word-break: break-word; | ||||
|     text-align: center; | ||||
|     color: var(--ck-color-selector-caption-text); | ||||
|     background-color: var(--ck-color-selector-caption-background); | ||||
|     padding: .6em; | ||||
|     font-size: .75em; | ||||
|     outline-offset: -1px; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-table/theme/table.css */ | ||||
| .ck-content .table { | ||||
|     margin: 0.9em auto; | ||||
| @@ -75,18 +87,6 @@ | ||||
| .ck-content[dir="ltr"] .table th { | ||||
|     text-align: left; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-table/theme/tablecaption.css */ | ||||
| .ck-content .table > figcaption { | ||||
|     display: table-caption; | ||||
|     caption-side: top; | ||||
|     word-break: break-word; | ||||
|     text-align: center; | ||||
|     color: var(--ck-color-selector-caption-text); | ||||
|     background-color: var(--ck-color-selector-caption-background); | ||||
|     padding: .6em; | ||||
|     font-size: .75em; | ||||
|     outline-offset: -1px; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */ | ||||
| .ck-content .page-break { | ||||
|     position: relative; | ||||
| @@ -136,6 +136,7 @@ | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-content .todo-list li { | ||||
|     position: relative; | ||||
|     margin-bottom: 5px; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| @@ -157,6 +158,13 @@ | ||||
|     margin-left: 0; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-content[dir=rtl] .todo-list .todo-list__label > input { | ||||
|     left: 0; | ||||
|     margin-right: 0; | ||||
|     right: -25px; | ||||
|     margin-left: -15px; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-content .todo-list .todo-list__label > input::before { | ||||
|     display: block; | ||||
|     position: absolute; | ||||
| @@ -166,7 +174,7 @@ | ||||
|     height: 100%; | ||||
|     border: 1px solid hsl(0, 0%, 20%); | ||||
|     border-radius: 2px; | ||||
|     transition: 250ms ease-in-out box-shadow, 250ms ease-in-out background, 250ms ease-in-out border; | ||||
|     transition: 250ms ease-in-out box-shadow; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-content .todo-list .todo-list__label > input::after { | ||||
| @@ -197,19 +205,80 @@ | ||||
| .ck-content .todo-list .todo-list__label .todo-list__label__description { | ||||
|     vertical-align: middle; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized { | ||||
|     max-width: 100%; | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] { | ||||
|     position: absolute; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-editor__editable.ck-content .todo-list .todo-list__label > input, | ||||
| .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input { | ||||
|     cursor: pointer; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-editor__editable.ck-content .todo-list .todo-list__label > input:hover::before, .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input:hover::before { | ||||
|     box-shadow: 0 0 0 5px hsla(0, 0%, 0%, 0.1); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input { | ||||
|     -webkit-appearance: none; | ||||
|     display: inline-block; | ||||
|     position: relative; | ||||
|     width: var(--ck-todo-list-checkmark-size); | ||||
|     height: var(--ck-todo-list-checkmark-size); | ||||
|     vertical-align: middle; | ||||
|     border: 0; | ||||
|     left: -25px; | ||||
|     margin-right: -15px; | ||||
|     right: 0; | ||||
|     margin-left: 0; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-editor__editable.ck-content[dir=rtl] .todo-list .todo-list__label > span[contenteditable=false] > input { | ||||
|     left: 0; | ||||
|     margin-right: 0; | ||||
|     right: -25px; | ||||
|     margin-left: -15px; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::before { | ||||
|     display: block; | ||||
|     position: absolute; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized img { | ||||
|     content: ''; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     border: 1px solid hsl(0, 0%, 20%); | ||||
|     border-radius: 2px; | ||||
|     transition: 250ms ease-in-out box-shadow; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized > figcaption { | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input::after { | ||||
|     display: block; | ||||
|     position: absolute; | ||||
|     box-sizing: content-box; | ||||
|     pointer-events: none; | ||||
|     content: ''; | ||||
|     left: calc( var(--ck-todo-list-checkmark-size) / 3 ); | ||||
|     top: calc( var(--ck-todo-list-checkmark-size) / 5.3 ); | ||||
|     width: calc( var(--ck-todo-list-checkmark-size) / 5.3 ); | ||||
|     height: calc( var(--ck-todo-list-checkmark-size) / 2.6 ); | ||||
|     border-style: solid; | ||||
|     border-color: transparent; | ||||
|     border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0; | ||||
|     transform: rotate(45deg); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::before { | ||||
|     background: hsl(126, 64%, 41%); | ||||
|     border-color: hsl(126, 64%, 41%); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable=false] > input[checked]::after { | ||||
|     border-color: hsl(0, 0%, 100%); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/todolist.css */ | ||||
| .ck-editor__editable.ck-content .todo-list .todo-list__label.todo-list__label_without-description input[type=checkbox] { | ||||
|     position: absolute; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/image.css */ | ||||
| .ck-content .image { | ||||
| @@ -225,6 +294,7 @@ | ||||
|     margin: 0 auto; | ||||
|     max-width: 100%; | ||||
|     min-width: 100%; | ||||
|     height: auto; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/image.css */ | ||||
| .ck-content .image-inline { | ||||
| @@ -259,6 +329,50 @@ | ||||
|     font-size: .75em; | ||||
|     outline-offset: -1px; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content img.image_resized { | ||||
|     height: auto; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized { | ||||
|     max-width: 100%; | ||||
|     display: block; | ||||
|     box-sizing: border-box; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized img { | ||||
|     width: 100%; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/imageresize.css */ | ||||
| .ck-content .image.image_resized > figcaption { | ||||
|     display: block; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-yellow { | ||||
|     background-color: var(--ck-highlight-marker-yellow); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-green { | ||||
|     background-color: var(--ck-highlight-marker-green); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-pink { | ||||
|     background-color: var(--ck-highlight-marker-pink); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-blue { | ||||
|     background-color: var(--ck-highlight-marker-blue); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .pen-red { | ||||
|     color: var(--ck-highlight-pen-red); | ||||
|     background-color: transparent; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .pen-green { | ||||
|     color: var(--ck-highlight-pen-green); | ||||
|     background-color: transparent; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-list/theme/list.css */ | ||||
| .ck-content ol { | ||||
|     list-style-type: decimal; | ||||
| @@ -295,32 +409,6 @@ | ||||
| .ck-content ul ul ul ul { | ||||
|     list-style-type: square; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-yellow { | ||||
|     background-color: var(--ck-highlight-marker-yellow); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-green { | ||||
|     background-color: var(--ck-highlight-marker-green); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-pink { | ||||
|     background-color: var(--ck-highlight-marker-pink); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .marker-blue { | ||||
|     background-color: var(--ck-highlight-marker-blue); | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .pen-red { | ||||
|     color: var(--ck-highlight-pen-red); | ||||
|     background-color: transparent; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-highlight/theme/highlight.css */ | ||||
| .ck-content .pen-green { | ||||
|     color: var(--ck-highlight-pen-green); | ||||
|     background-color: transparent; | ||||
| } | ||||
| /* @ckeditor/ckeditor5-image/theme/imagestyle.css */ | ||||
| .ck-content .image-style-block-align-left, | ||||
| .ck-content .image-style-block-align-right { | ||||
|   | ||||
							
								
								
									
										4
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								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
											
										
									
								
							
							
								
								
									
										395
									
								
								libraries/mermaid.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										395
									
								
								libraries/mermaid.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "version": "0.61.9-beta", | ||||
|   "version": "0.61.10-beta", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "trilium", | ||||
|       "version": "0.61.9-beta", | ||||
|       "version": "0.61.10-beta", | ||||
|       "hasInstallScript": true, | ||||
|       "license": "AGPL-3.0-only", | ||||
|       "dependencies": { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   "name": "trilium", | ||||
|   "productName": "Trilium Notes", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.61.10-beta", | ||||
|   "version": "0.61.12", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "bin": { | ||||
| @@ -15,12 +15,16 @@ | ||||
|   "scripts": { | ||||
|     "start-server": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www", | ||||
|     "start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www", | ||||
|     "qstart-server": "npm run qswitch-server && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www", | ||||
|     "start-electron": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", | ||||
|     "start-electron-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .", | ||||
|     "qstart-electron": "npm run qswitch-electron && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", | ||||
|     "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install", | ||||
|     "switch-electron": "./node_modules/.bin/electron-rebuild", | ||||
|     "qswitch-server": "rm -rf ./node_modules/better-sqlite3/bin ; mkdir -p ./node_modules/better-sqlite3/build ; cp ./bin/better-sqlite3/linux-server-better_sqlite3.node ./node_modules/better-sqlite3/build/better_sqlite3.node", | ||||
|     "qswitch-electron": "rm -rf ./node_modules/better-sqlite3/bin ; mkdir -p ./node_modules/better-sqlite3/build ; cp ./bin/better-sqlite3/linux-desktop-better_sqlite3.node ./node_modules/better-sqlite3/build/better_sqlite3.node", | ||||
|     "build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js", | ||||
|     "build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/right_panel_widget.js", | ||||
|     "build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js", | ||||
|     "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", | ||||
|     "webpack": "webpack -c webpack.config.js", | ||||
|     "test-jasmine": "jasmine", | ||||
|   | ||||
							
								
								
									
										5
									
								
								spec/etapi/notes.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								spec/etapi/notes.spec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| describe("Notes", () => { | ||||
|     it("zzz", () => { | ||||
|  | ||||
|     }); | ||||
| }); | ||||
| @@ -1635,15 +1635,24 @@ class BNote extends AbstractBeccaEntity { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param {string} matchBy - choose by which property we detect if to update an existing attachment. | ||||
|  *                               Supported values are either 'attachmentId' (default) or 'title' | ||||
|      * @returns {BAttachment} | ||||
|      */ | ||||
|     saveAttachment({attachmentId, role, mime, title, content, position}) { | ||||
|     saveAttachment({attachmentId, role, mime, title, content, position}, matchBy = 'attachmentId') { | ||||
|         if (!['attachmentId', 'title'].includes(matchBy)) { | ||||
|             throw new Error(`Unsupported value '${matchBy}' for matchBy param, has to be either 'attachmentId' or 'title'.`); | ||||
|         } | ||||
|  | ||||
|         let attachment; | ||||
|  | ||||
|         if (attachmentId) { | ||||
|         if (matchBy === 'title') { | ||||
|             attachment = this.getAttachmentByTitle(title); | ||||
|         } else if (matchBy === 'attachmentId' && attachmentId) { | ||||
|             attachment = this.becca.getAttachmentOrThrow(attachmentId); | ||||
|         } else { | ||||
|             attachment = new BAttachment({ | ||||
|         } | ||||
|  | ||||
|         attachment = attachment || new BAttachment({ | ||||
|             ownerId: this.noteId, | ||||
|             title, | ||||
|             role, | ||||
| @@ -1651,7 +1660,6 @@ class BNote extends AbstractBeccaEntity { | ||||
|             isProtected: this.isProtected, | ||||
|             position | ||||
|         }); | ||||
|         } | ||||
|  | ||||
|         content = content || ""; | ||||
|         attachment.setContent(content, {forceSave: true}); | ||||
|   | ||||
| @@ -78,7 +78,7 @@ import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating | ||||
| import ScriptExecutorWidget from "../widgets/ribbon_widgets/script_executor.js"; | ||||
| import MovePaneButton from "../widgets/buttons/move_pane_button.js"; | ||||
| import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js"; | ||||
| import CanvasPropertiesWidget from "../widgets/ribbon_widgets/canvas_properties.js"; | ||||
| import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js"; | ||||
|  | ||||
| export default class DesktopLayout { | ||||
|     constructor(customWidgets) { | ||||
| @@ -145,7 +145,6 @@ export default class DesktopLayout { | ||||
|                                             .ribbon(new NotePropertiesWidget()) | ||||
|                                             .ribbon(new FilePropertiesWidget()) | ||||
|                                             .ribbon(new ImagePropertiesWidget()) | ||||
|                                             .ribbon(new CanvasPropertiesWidget()) | ||||
|                                             .ribbon(new BasicPropertiesWidget()) | ||||
|                                             .ribbon(new OwnedAttributeListWidget()) | ||||
|                                             .ribbon(new InheritedAttributesWidget()) | ||||
| @@ -162,6 +161,7 @@ export default class DesktopLayout { | ||||
|                                         .child(new EditButton()) | ||||
|                                         .child(new CodeButtonsWidget()) | ||||
|                                         .child(new RelationMapButtons()) | ||||
|                                         .child(new CopyImageReferenceButton()) | ||||
|                                         .child(new MermaidExportButton()) | ||||
|                                         .child(new BacklinksWidget()) | ||||
|                                         .child(new HideFloatingButtonsButton()) | ||||
|   | ||||
| @@ -18,7 +18,7 @@ const TPL = ` | ||||
|     .global-menu-button { | ||||
|         background-image: url("${window.glob.assetPath}/images/icon-black.svg"); | ||||
|         background-repeat: no-repeat; | ||||
|         background-position: 50% 80%; | ||||
|         background-position: 40% 50%; | ||||
|         background-size: 45px; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|   | ||||
| @@ -88,7 +88,7 @@ const TPL = ` | ||||
|         display: none; | ||||
|         border-bottom: 1px solid var(--main-border-color); | ||||
|         margin-left: 10px; | ||||
|         margin-right: 10px; | ||||
|         margin-right: 5px; /* needs to have this value so that the bottom border is the same width as the top one */ | ||||
|     } | ||||
|      | ||||
|     .ribbon-body.active { | ||||
|   | ||||
| @@ -242,7 +242,7 @@ export default class RevisionsDialog extends BasicWidget { | ||||
|  | ||||
|                 renderMathInElement(this.$content[0], {trust: true}); | ||||
|             } | ||||
|         } else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') { | ||||
|         } else if (revisionItem.type === 'code') { | ||||
|             this.$content.html($("<pre>").text(fullRevision.content)); | ||||
|         } else if (revisionItem.type === 'image') { | ||||
|             this.$content.html($("<img>") | ||||
| @@ -279,6 +279,14 @@ export default class RevisionsDialog extends BasicWidget { | ||||
|             this.$content.html($("<img>") | ||||
|                 .attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`) | ||||
|                 .css("max-width", "100%")); | ||||
|         } else if (revisionItem.type === 'mermaid') { | ||||
|             const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, ""); | ||||
|  | ||||
|             this.$content.html($("<img>") | ||||
|                 .attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`) | ||||
|                 .css("max-width", "100%")); | ||||
|  | ||||
|             this.$content.append($("<pre>").text(fullRevision.content)); | ||||
|         } else { | ||||
|             this.$content.text("Preview isn't available for this note type."); | ||||
|         } | ||||
|   | ||||
| @@ -0,0 +1,40 @@ | ||||
| import NoteContextAwareWidget from "../note_context_aware_widget.js"; | ||||
| import utils from "../../services/utils.js"; | ||||
| import imageService from "../../services/image.js"; | ||||
|  | ||||
| const TPL = ` | ||||
| <button type="button" | ||||
|         class="copy-image-reference-button" | ||||
|         title="Copy image reference to the clipboard, can be pasted into a text note."> | ||||
|         <span class="bx bx-copy"></span> | ||||
|          | ||||
|         <div class="hidden-image-copy"></div> | ||||
| </button>`; | ||||
|  | ||||
| export default class CopyImageReferenceButton extends NoteContextAwareWidget { | ||||
|     isEnabled() { | ||||
|         return super.isEnabled() | ||||
|             && ['mermaid', 'canvas'].includes(this.note?.type) | ||||
|             && this.note.isContentAvailable() | ||||
|             && this.noteContext?.viewScope.viewMode === 'default'; | ||||
|     } | ||||
|  | ||||
|     doRender() { | ||||
|         super.doRender(); | ||||
|  | ||||
|         this.$widget = $(TPL); | ||||
|         this.$hiddenImageCopy = this.$widget.find(".hidden-image-copy"); | ||||
|  | ||||
|         this.$widget.on('click', () => { | ||||
|             this.$hiddenImageCopy.empty().append( | ||||
|                 $("<img>") | ||||
|                     .attr("src", utils.createImageSrcUrl(this.note)) | ||||
|             ); | ||||
|  | ||||
|             imageService.copyImageReferenceToClipboard(this.$hiddenImageCopy); | ||||
|  | ||||
|             this.$hiddenImageCopy.empty(); | ||||
|         }); | ||||
|         this.contentSized(); | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| import libraryLoader from "../services/library_loader.js"; | ||||
| import NoteContextAwareWidget from "./note_context_aware_widget.js"; | ||||
| import server from "../services/server.js"; | ||||
|  | ||||
| const TPL = `<div class="mermaid-widget"> | ||||
|     <style> | ||||
| @@ -18,6 +19,10 @@ const TPL = `<div class="mermaid-widget"> | ||||
|             height: 100%; | ||||
|             text-align: center; | ||||
|         } | ||||
|          | ||||
|         .mermaid-render svg { | ||||
|             width: 95%; /* https://github.com/zadam/trilium/issues/4340 */ | ||||
|         } | ||||
|     </style> | ||||
|  | ||||
|     <div class="mermaid-error alert alert-warning"> | ||||
| @@ -77,6 +82,20 @@ export default class MermaidWidget extends NoteContextAwareWidget { | ||||
|         try { | ||||
|             const svg = await this.renderSvg(); | ||||
|  | ||||
|             if (this.dirtyAttachment) { | ||||
|                 const payload = { | ||||
|                     role: 'image', | ||||
|                     title: 'mermaid-export.svg', | ||||
|                     mime: 'image/svg+xml', | ||||
|                     content: svg, | ||||
|                     position: 0 | ||||
|                 }; | ||||
|  | ||||
|                 server.post(`notes/${this.noteId}/attachments?matchBy=title`, payload).then(() => { | ||||
|                     this.dirtyAttachment = false; | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             this.$display.html(svg); | ||||
|  | ||||
|             await wheelZoomLoaded; | ||||
| @@ -85,8 +104,8 @@ export default class MermaidWidget extends NoteContextAwareWidget { | ||||
|  | ||||
|             WZoom.create(`#mermaid-render-${idCounter}`, { | ||||
|                 type: 'html', | ||||
|                 maxScale: 10, | ||||
|                 speed: 20, | ||||
|                 maxScale: 50, | ||||
|                 speed: 1.3, | ||||
|                 zoomOnClick: false | ||||
|             }); | ||||
|         } catch (e) { | ||||
| @@ -107,6 +126,8 @@ export default class MermaidWidget extends NoteContextAwareWidget { | ||||
|  | ||||
|     async entitiesReloadedEvent({loadResults}) { | ||||
|         if (loadResults.isNoteContentReloaded(this.noteId)) { | ||||
|             this.dirtyAttachment = true; | ||||
|  | ||||
|             await this.refresh(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -17,7 +17,8 @@ const TPL = ` | ||||
|          | ||||
|         .basic-properties-widget > * { | ||||
|             margin-right: 30px; | ||||
|             margin-top: 12px; | ||||
|             margin-top: 9px; | ||||
|             margin-bottom: 2px; | ||||
|         } | ||||
|          | ||||
|         .note-type-container, .editability-select-container { | ||||
|   | ||||
| @@ -1,56 +0,0 @@ | ||||
| import NoteContextAwareWidget from "../note_context_aware_widget.js"; | ||||
| import utils from "../../services/utils.js"; | ||||
| import imageService from "../../services/image.js"; | ||||
|  | ||||
| const TPL = ` | ||||
| <div class="image-properties"> | ||||
|     <div style="display: flex; justify-content: space-evenly; margin: 10px;"> | ||||
|         <button class="canvas-copy-reference-to-clipboard btn btn-sm btn-primary"  | ||||
|                 title="Pasting this reference into a text note will insert the canvas note as image" | ||||
|                 type="button">Copy reference to clipboard</button> | ||||
|     </div> | ||||
|      | ||||
|     <div class="hidden-canvas-copy"></div> | ||||
| </div>`; | ||||
|  | ||||
| export default class CanvasPropertiesWidget extends NoteContextAwareWidget { | ||||
|     get name() { | ||||
|         return "canvasProperties"; | ||||
|     } | ||||
|  | ||||
|     get toggleCommand() { | ||||
|         return "toggleRibbonTabCanvasProperties"; | ||||
|     } | ||||
|  | ||||
|     isEnabled() { | ||||
|         return this.note && this.note.type === 'canvas'; | ||||
|     } | ||||
|  | ||||
|     getTitle() { | ||||
|         return { | ||||
|             show: this.isEnabled(), | ||||
|             activate: false, | ||||
|             title: 'Canvas', | ||||
|             icon: 'bx bx-pen' | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     doRender() { | ||||
|         this.$widget = $(TPL); | ||||
|         this.contentSized(); | ||||
|  | ||||
|         this.$hiddenCanvasCopy = this.$widget.find('.hidden-canvas-copy'); | ||||
|  | ||||
|         this.$copyReferenceToClipboardButton = this.$widget.find(".canvas-copy-reference-to-clipboard"); | ||||
|         this.$copyReferenceToClipboardButton.on('click', () => { | ||||
|             this.$hiddenCanvasCopy.empty().append( | ||||
|                 $("<img>") | ||||
|                     .attr("src", utils.createImageSrcUrl(this.note)) | ||||
|             ); | ||||
|  | ||||
|             imageService.copyImageReferenceToClipboard(this.$hiddenCanvasCopy); | ||||
|  | ||||
|             this.$hiddenCanvasCopy.empty(); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -14,7 +14,7 @@ const TPL = ` | ||||
|         color: var(--muted-text-color); | ||||
|         max-height: 200px; | ||||
|         overflow: auto; | ||||
|         padding: 12px 12px 11px 12px; | ||||
|         padding: 14px 12px 13px 12px; | ||||
|     } | ||||
|     </style> | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,8 @@ const TPL = ` | ||||
|         .attribute-list { | ||||
|             margin-left: 7px; | ||||
|             margin-right: 7px; | ||||
|             margin-top: 3px; | ||||
|             margin-top: 5px; | ||||
|             margin-bottom: 2px; | ||||
|             position: relative; | ||||
|         } | ||||
|          | ||||
|   | ||||
| @@ -23,6 +23,7 @@ const TPL = ` | ||||
|                 <option value="eq7">is exactly 7</option> | ||||
|                 <option value="eq8">is exactly 8</option> | ||||
|                 <option value="eq9">is exactly 9</option> | ||||
|                 <option value="gt0">is greater than 0</option> | ||||
|                 <option value="gt1">is greater than 1</option> | ||||
|                 <option value="gt2">is greater than 2</option> | ||||
|                 <option value="gt3">is greater than 3</option> | ||||
| @@ -32,6 +33,7 @@ const TPL = ` | ||||
|                 <option value="gt7">is greater than 7</option> | ||||
|                 <option value="gt8">is greater than 8</option> | ||||
|                 <option value="gt9">is greater than 9</option> | ||||
|                 <option value="lt2">is less than 2</option> | ||||
|                 <option value="lt3">is less than 3</option> | ||||
|                 <option value="lt4">is less than 4</option> | ||||
|                 <option value="lt5">is less than 5</option> | ||||
|   | ||||
| @@ -22,6 +22,7 @@ const Draggabilly = window.Draggabilly; | ||||
|  | ||||
| const TAB_CONTAINER_MIN_WIDTH = 24; | ||||
| const TAB_CONTAINER_MAX_WIDTH = 240; | ||||
| const TAB_CONTAINER_LEFT_PADDING = 5; | ||||
| const NEW_TAB_WIDTH = 32; | ||||
| const MIN_FILLER_WIDTH = 50; | ||||
| const MARGIN_WIDTH = 5; | ||||
| @@ -330,7 +331,7 @@ export default class TabRowWidget extends BasicWidget { | ||||
|     getTabPositions() { | ||||
|         const tabPositions = []; | ||||
|  | ||||
|         let position = 0; | ||||
|         let position = TAB_CONTAINER_LEFT_PADDING; | ||||
|         this.tabWidths.forEach(width => { | ||||
|             tabPositions.push(position); | ||||
|             position += width + MARGIN_WIDTH; | ||||
|   | ||||
| @@ -49,8 +49,8 @@ class ImageTypeWidget extends TypeWidget { | ||||
|  | ||||
|         libraryLoader.requireLibrary(libraryLoader.WHEEL_ZOOM).then(() => { | ||||
|             WZoom.create(`#${this.$imageView.attr("id")}`, { | ||||
|                 maxScale: 10, | ||||
|                 speed: 20, | ||||
|                 maxScale: 50, | ||||
|                 speed: 1.3, | ||||
|                 zoomOnClick: false | ||||
|             }); | ||||
|         }); | ||||
|   | ||||
| @@ -84,11 +84,11 @@ export default class BackupOptions extends OptionsWidget { | ||||
|             this.$existingBackupList.empty(); | ||||
|  | ||||
|             if (!backupFiles.length) { | ||||
|                 backupFiles = [{filePath: "no backup yet", ctime: ''}]; | ||||
|                 backupFiles = [{filePath: "no backup yet", mtime: ''}]; | ||||
|             } | ||||
|  | ||||
|             for (const {filePath, ctime} of backupFiles) { | ||||
|                 this.$existingBackupList.append($("<li>").text(`${filePath} ${ctime ? ` - ${ctime}` : ''}`)); | ||||
|             for (const {filePath, mtime} of backupFiles) { | ||||
|                 this.$existingBackupList.append($("<li>").text(`${filePath} ${mtime ? ` - ${mtime}` : ''}`)); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|   | ||||
| @@ -21,7 +21,8 @@ const TPL = ` | ||||
|             <label>Sync proxy server (optional)</label> | ||||
|             <input class="sync-proxy form-control" placeholder="https://<host>:<port>"> | ||||
|      | ||||
|             <p><strong>Note:</strong> If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only)</p> | ||||
|             <p><strong>Note:</strong> If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only).</p> | ||||
|             <p>Another special value is <code>noproxy</code> which forces ignoring even the system proxy and respectes <code>NODE_TLS_REJECT_UNAUTHORIZED</code>.</p> | ||||
|         </div> | ||||
|      | ||||
|         <div style="display: flex; justify-content: space-between;"> | ||||
|   | ||||
| @@ -32,9 +32,10 @@ function getAllAttachments(req) { | ||||
| function saveAttachment(req) { | ||||
|     const {noteId} = req.params; | ||||
|     const {attachmentId, role, mime, title, content} = req.body; | ||||
|     const {matchBy} = req.query; | ||||
|  | ||||
|     const note = becca.getNoteOrThrow(noteId); | ||||
|     note.saveAttachment({attachmentId, role, mime, title, content}); | ||||
|     note.saveAttachment({attachmentId, role, mime, title, content}, matchBy); | ||||
| } | ||||
|  | ||||
| function uploadAttachment(req) { | ||||
|   | ||||
| @@ -25,17 +25,24 @@ function returnImageInt(image, res) { | ||||
|     if (!image) { | ||||
|         res.set('Content-Type', 'image/png'); | ||||
|         return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`)); | ||||
|     } else if (!["image", "canvas"].includes(image.type)) { | ||||
|     } else if (!["image", "canvas", "mermaid"].includes(image.type)) { | ||||
|         return res.sendStatus(400); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * special "image" type. the canvas is actually type application/json | ||||
|      * to avoid bitrot and enable usage as referenced image the svg is included. | ||||
|      */ | ||||
|     if (image.type === 'canvas') { | ||||
|         renderSvgAttachment(image, res, 'canvas-export.svg'); | ||||
|     } else if (image.type === 'mermaid') { | ||||
|         renderSvgAttachment(image, res, 'mermaid-export.svg'); | ||||
|     } else { | ||||
|         res.set('Content-Type', image.mime); | ||||
|         res.set("Cache-Control", "no-cache, no-store, must-revalidate"); | ||||
|         res.send(image.getContent()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| function renderSvgAttachment(image, res, attachmentName) { | ||||
|     let svgString = '<svg/>' | ||||
|         const attachment = image.getAttachmentByTitle('canvas-export.svg'); | ||||
|     const attachment = image.getAttachmentByTitle(attachmentName); | ||||
|  | ||||
|     if (attachment) { | ||||
|         svgString = attachment.getContent(); | ||||
| @@ -52,13 +59,9 @@ function returnImageInt(image, res) { | ||||
|     res.set('Content-Type', "image/svg+xml"); | ||||
|     res.set("Cache-Control", "no-cache, no-store, must-revalidate"); | ||||
|     res.send(svg); | ||||
|     } else { | ||||
|         res.set('Content-Type', image.mime); | ||||
|         res.set("Cache-Control", "no-cache, no-store, must-revalidate"); | ||||
|         res.send(image.getContent()); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| function returnAttachedImage(req, res) { | ||||
|     const attachment = becca.getAttachment(req.params.attachmentId); | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,7 @@ const build = require('./build'); | ||||
| const packageJson = require('../../package'); | ||||
| const {TRILIUM_DATA_DIR} = require('./data_dir'); | ||||
|  | ||||
| const APP_DB_VERSION = 226; | ||||
| const APP_DB_VERSION = 227; | ||||
| const SYNC_VERSION = 31; | ||||
| const CLIPPER_PROTOCOL_VERSION = "1.0"; | ||||
|  | ||||
|   | ||||
| @@ -21,7 +21,7 @@ function getExistingBackups() { | ||||
|             const filePath = path.resolve(dataDir.BACKUP_DIR, fileName); | ||||
|             const stat = fs.statSync(filePath) | ||||
|  | ||||
|             return {fileName, filePath, ctime: stat.ctime}; | ||||
|             return {fileName, filePath, mtime: stat.mtime}; | ||||
|         }); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { buildDate:"2023-10-19T09:33:51+02:00", buildRevision: "b01fe5ead9268784fb133a8cfa53670927ba0e3b" }; | ||||
| module.exports = { buildDate:"2023-11-04T00:16:19+01:00", buildRevision: "1ebdb0f5e1a5cc1b7f8c36af5e1f750141ab062b" }; | ||||
|   | ||||
| @@ -28,22 +28,14 @@ function sanitize(dirtyHtml) { | ||||
|         allowedTags: [ | ||||
|             'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', | ||||
|             'li', 'b', 'i', 'strong', 'em', 'strike', 's', 'del', 'abbr', 'code', 'hr', 'br', 'div', | ||||
|             'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'section', 'img', | ||||
|             'figure', 'figcaption', 'span', 'label', 'input', | ||||
|             'table', 'thead', 'caption', 'tbody', 'tfoot', 'tr', 'th', 'td', 'pre', 'section', 'img', | ||||
|             'figure', 'figcaption', 'span', 'label', 'input', 'details', 'summary', 'address', 'aside', 'footer', | ||||
|             'header', 'hgroup', 'main', 'nav', 'dl', 'dt', 'menu', 'bdi', 'bdo', 'dfn', 'kbd', 'mark', 'q', 'time', | ||||
|             'var', 'wbr', 'area', 'map', 'track', 'video', 'audio', 'picture', 'del', 'ins', | ||||
|             'en-media' // for ENEX import | ||||
|         ], | ||||
|         allowedAttributes: { | ||||
|             'a': [ 'href', 'class' ], | ||||
|             'img': [ 'src' ], | ||||
|             'section': [ 'class', 'data-note-id' ], | ||||
|             'figure': [ 'class' ], | ||||
|             'span': [ 'class', 'style' ], | ||||
|             'label': [ 'class' ], | ||||
|             'input': [ 'class', 'type', 'disabled' ], | ||||
|             'code': [ 'class' ], | ||||
|             'ul': [ 'class' ], | ||||
|             'table': [ 'class' ], | ||||
|             'en-media': [ 'hash' ] | ||||
|             '*': [ 'class', 'style', 'title', 'src', 'href', 'hash', 'disabled', 'align', 'alt', 'center', 'data-*' ] | ||||
|         }, | ||||
|         allowedSchemes: [ | ||||
|             'http', 'https', 'ftp', 'ftps', 'mailto', 'data', 'evernote', 'file', 'facetime', 'irc', 'gemini', 'git', | ||||
|   | ||||
| @@ -121,7 +121,11 @@ function importMarkdown(taskContext, file, parentNote) { | ||||
|     const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces); | ||||
|  | ||||
|     const markdownContent = file.buffer.toString("utf-8"); | ||||
|     const htmlContent = markdownService.renderToHtml(markdownContent, title); | ||||
|     let htmlContent = markdownService.renderToHtml(markdownContent, title); | ||||
|  | ||||
|     if (taskContext.data.safeImport) { | ||||
|         htmlContent = htmlSanitizer.sanitize(htmlContent); | ||||
|     } | ||||
|  | ||||
|     const {note} = noteService.createNewNote({ | ||||
|         parentNoteId: parentNote.noteId, | ||||
| @@ -141,7 +145,10 @@ function importHtml(taskContext, file, parentNote) { | ||||
|     const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces); | ||||
|     let content = file.buffer.toString("utf-8"); | ||||
|  | ||||
|     if (taskContext.data.safeImport) { | ||||
|         content = htmlSanitizer.sanitize(content); | ||||
|     } | ||||
|  | ||||
|     content = importUtils.handleH1(content, title); | ||||
|  | ||||
|     const {note} = noteService.createNewNote({ | ||||
|   | ||||
| @@ -321,7 +321,9 @@ async function importZip(taskContext, fileBuffer, importRootNote) { | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         if (taskContext.data.safeImport) { | ||||
|             content = htmlSanitizer.sanitize(content); | ||||
|         } | ||||
|  | ||||
|         content = content.replace(/<html.*<body[^>]*>/gis, ""); | ||||
|         content = content.replace(/<\/body>.*<\/html>/gis, ""); | ||||
|   | ||||
| @@ -63,7 +63,7 @@ const defaultOptions = [ | ||||
|     { name: 'autoFixConsistencyIssues', value: 'true', isSynced: false }, | ||||
|     { name: 'vimKeymapEnabled', value: 'false', isSynced: false }, | ||||
|     { name: 'codeLineWrapEnabled', value: 'true', isSynced: false }, | ||||
|     { name: 'codeNotesMimeTypes', value: '["text/x-csrc","text/x-c++src","text/x-csharp","text/css","text/x-go","text/x-groovy","text/x-haskell","text/html","message/http","text/x-java","application/javascript;env=frontend","application/javascript;env=backend","application/json","text/x-kotlin","text/x-markdown","text/x-perl","text/x-php","text/x-python","text/x-ruby",null,"text/x-sql","text/x-sqlite;schema=trilium","text/x-swift","text/xml","text/x-yaml"]', isSynced: true }, | ||||
|     { name: 'codeNotesMimeTypes', value: '["text/x-csrc","text/x-c++src","text/x-csharp","text/css","text/x-go","text/x-groovy","text/x-haskell","text/html","message/http","text/x-java","application/javascript;env=frontend","application/javascript;env=backend","application/json","text/x-kotlin","text/x-markdown","text/x-perl","text/x-php","text/x-python","text/x-ruby",null,"text/x-sql","text/x-sqlite;schema=trilium","text/x-swift","text/xml","text/x-yaml","text/x-sh"]', isSynced: true }, | ||||
|     { name: 'leftPaneWidth', value: '25', isSynced: false }, | ||||
|     { name: 'leftPaneVisible', value: 'true', isSynced: false }, | ||||
|     { name: 'rightPaneWidth', value: '25', isSynced: false }, | ||||
|   | ||||
| @@ -292,7 +292,9 @@ async function syncRequest(syncContext, method, requestPath, body) { | ||||
|     return response; | ||||
| } | ||||
|  | ||||
| function getEntityChangeRow(entityName, entityId) { | ||||
| function getEntityChangeRow(entityChange) { | ||||
|     const {entityName, entityId} = entityChange; | ||||
|  | ||||
|     if (entityName === 'note_reordering') { | ||||
|         return sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [entityId]); | ||||
|     } | ||||
| @@ -300,13 +302,13 @@ function getEntityChangeRow(entityName, entityId) { | ||||
|         const primaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName; | ||||
|  | ||||
|         if (!primaryKey) { | ||||
|             throw new Error(`Unknown entity '${entityName}'`); | ||||
|             throw new Error(`Unknown entity for entity change ${JSON.stringify(entityChange)}`); | ||||
|         } | ||||
|  | ||||
|         const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]); | ||||
|  | ||||
|         if (!entityRow) { | ||||
|             throw new Error(`Entity ${entityName} '${entityId}' not found.`); | ||||
|             throw new Error(`Cannot find entity for entity change ${JSON.stringify(entityChange)}`); | ||||
|         } | ||||
|  | ||||
|         if (entityName === 'blobs' && entityRow.content !== null) { | ||||
| @@ -332,7 +334,7 @@ function getEntityChangeRecords(entityChanges) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const entity = getEntityChangeRow(entityChange.entityName, entityChange.entityId); | ||||
|         const entity = getEntityChangeRow(entityChange); | ||||
|  | ||||
|         const record = { entityChange, entity }; | ||||
|  | ||||
|   | ||||
| @@ -24,7 +24,7 @@ function getContent(note) { | ||||
|     } else if (note.type === 'code') { | ||||
|         renderCode(result); | ||||
|     } else if (note.type === 'mermaid') { | ||||
|         renderMermaid(result); | ||||
|         renderMermaid(result, note); | ||||
|     } else if (note.type === 'image' || note.type === 'canvas') { | ||||
|         renderImage(result, note); | ||||
|     } else if (note.type === 'file') { | ||||
| @@ -126,15 +126,14 @@ function renderCode(result) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function renderMermaid(result) { | ||||
| function renderMermaid(result, note) { | ||||
|     result.content = ` | ||||
| <div class="mermaid">${escapeHtml(result.content)}</div> | ||||
| <img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}"> | ||||
| <hr> | ||||
| <details> | ||||
|     <summary>Chart source</summary> | ||||
|     <pre>${escapeHtml(result.content)}</pre> | ||||
| </details>` | ||||
|     result.header += `<script src="../../${assetPath}/libraries/mermaid.min.js"></script>`; | ||||
| } | ||||
|  | ||||
| function renderImage(result, note) { | ||||
|   | ||||
| @@ -105,6 +105,27 @@ function checkNoteAccess(noteId, req, res) { | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| function renderImageAttachment(image, res, attachmentName) { | ||||
|     let svgString = '<svg/>' | ||||
|     const attachment = image.getAttachmentByTitle(attachmentName); | ||||
|  | ||||
|     if (attachment) { | ||||
|         svgString = attachment.getContent(); | ||||
|     } else { | ||||
|         // backwards compatibility, before attachments, the SVG was stored in the main note content as a separate key | ||||
|         const contentSvg = image.getJsonContentSafely()?.svg; | ||||
|  | ||||
|         if (contentSvg) { | ||||
|             svgString = contentSvg; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const svg = svgString | ||||
|     res.set('Content-Type', "image/svg+xml"); | ||||
|     res.set("Cache-Control", "no-cache, no-store, must-revalidate"); | ||||
|     res.send(svg); | ||||
| } | ||||
|  | ||||
| function register(router) { | ||||
|     function renderNote(note, req, res) { | ||||
|         if (!note) { | ||||
| @@ -209,37 +230,18 @@ function register(router) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (!["image", "canvas"].includes(image.type)) { | ||||
|             return res.status(400) | ||||
|                 .json({ message: "Requested note is not a shareable image" }); | ||||
|         } else if (image.type === "canvas") { | ||||
|             /** | ||||
|              * special "image" type. the canvas is actually type application/json | ||||
|              * to avoid bitrot and enable usage as referenced image the svg is included. | ||||
|              */ | ||||
|             let svgString = '<svg/>' | ||||
|             const attachment = image.getAttachmentByTitle('canvas-export.svg'); | ||||
|  | ||||
|             if (attachment) { | ||||
|                 svgString = attachment.getContent(); | ||||
|             } else { | ||||
|                 // backwards compatibility, before attachments, the SVG was stored in the main note content as a separate key | ||||
|                 const contentSvg = image.getJsonContentSafely()?.svg; | ||||
|  | ||||
|                 if (contentSvg) { | ||||
|                     svgString = contentSvg; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             const svg = svgString | ||||
|             res.set('Content-Type', "image/svg+xml"); | ||||
|             res.set("Cache-Control", "no-cache, no-store, must-revalidate"); | ||||
|             res.send(svg); | ||||
|         } else { | ||||
|         if (image.type === 'image') { | ||||
|             // normal image | ||||
|             res.set('Content-Type', image.mime); | ||||
|             addNoIndexHeader(image, res); | ||||
|             res.send(image.getContent()); | ||||
|         } else if (image.type === "canvas") { | ||||
|             renderImageAttachment(image, res, 'canvas-export.svg'); | ||||
|         } else if (image.type === 'mermaid') { | ||||
|             renderImageAttachment(image, res, 'mermaid-export.svg'); | ||||
|         } else { | ||||
|             return res.status(400) | ||||
|                 .json({ message: "Requested note is not a shareable image" }); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user