mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Compare commits
	
		
			32 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e76093e75c | ||
|  | 4f8073daa7 | ||
|  | 47fb96faa8 | ||
|  | 6e33553146 | ||
|  | 807941e6a5 | ||
|  | 1e30c0702e | ||
|  | 390ad6d813 | ||
|  | 77800d073f | ||
|  | 1953c7896f | ||
|  | cd43752f61 | ||
|  | d6046efa1b | ||
|  | ee608fcf46 | ||
|  | 894b08a1b8 | ||
|  | 4e549baedc | ||
|  | 6b6e42e9ba | ||
|  | 0404b78fb8 | ||
|  | 439743d2b0 | ||
|  | 0ac397e7ff | ||
|  | d243880099 | ||
|  | 2e23c521c3 | ||
|  | eb761b286f | ||
|  | d0f6ff5f98 | ||
|  | 84feaabc52 | ||
|  | a6036859b8 | ||
|  | 93dcce2217 | ||
|  | 686af0c6a1 | ||
|  | d07f02b95f | ||
|  | ad74952194 | ||
|  | 10f3df3ed4 | ||
|  | 18e2e6779b | ||
|  | ed129c307b | ||
|  | 8742e4bfe9 | 
| @@ -8,3 +8,6 @@ CREATE TABLE IF NOT EXISTS "blobs" ( | |||||||
|  |  | ||||||
| ALTER TABLE notes ADD blobId TEXT DEFAULT NULL; | ALTER TABLE notes ADD blobId TEXT DEFAULT NULL; | ||||||
| ALTER TABLE note_revisions ADD blobId TEXT DEFAULT NULL; | ALTER TABLE note_revisions ADD blobId TEXT DEFAULT NULL; | ||||||
|  |  | ||||||
|  | CREATE INDEX IF NOT EXISTS IDX_notes_blobId on notes (blobId); | ||||||
|  | CREATE INDEX IF NOT EXISTS IDX_note_revisions_blobId on note_revisions (blobId); | ||||||
|   | |||||||
| @@ -21,5 +21,6 @@ CREATE INDEX `IDX_revisions_utcDateCreated` ON `revisions` (`utcDateCreated`); | |||||||
| CREATE INDEX `IDX_revisions_utcDateLastEdited` ON `revisions` (`utcDateLastEdited`); | CREATE INDEX `IDX_revisions_utcDateLastEdited` ON `revisions` (`utcDateLastEdited`); | ||||||
| CREATE INDEX `IDX_revisions_dateCreated` ON `revisions` (`dateCreated`); | CREATE INDEX `IDX_revisions_dateCreated` ON `revisions` (`dateCreated`); | ||||||
| CREATE INDEX `IDX_revisions_dateLastEdited` ON `revisions` (`dateLastEdited`); | CREATE INDEX `IDX_revisions_dateLastEdited` ON `revisions` (`dateLastEdited`); | ||||||
|  | CREATE INDEX IF NOT EXISTS IDX_revisions_blobId on revisions (blobId); | ||||||
|  |  | ||||||
| UPDATE entity_changes SET entityName = 'revisions' WHERE entityName = 'note_revisions'; | UPDATE entity_changes SET entityName = 'revisions' WHERE entityName = 'note_revisions'; | ||||||
|   | |||||||
| @@ -19,3 +19,5 @@ CREATE INDEX IDX_attachments_ownerId_role | |||||||
|  |  | ||||||
| CREATE INDEX IDX_attachments_utcDateScheduledForErasureSince | CREATE INDEX IDX_attachments_utcDateScheduledForErasureSince | ||||||
|     on attachments (utcDateScheduledForErasureSince); |     on attachments (utcDateScheduledForErasureSince); | ||||||
|  |  | ||||||
|  | CREATE INDEX IF NOT EXISTS IDX_attachments_blobId on attachments (blobId); | ||||||
|   | |||||||
							
								
								
									
										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
											
										
									
								
							
							
								
								
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "version": "0.62.1-beta", |   "version": "0.62.5", | ||||||
|   "lockfileVersion": 2, |   "lockfileVersion": 2, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|       "name": "trilium", |       "name": "trilium", | ||||||
|       "version": "0.62.1-beta", |       "version": "0.62.5", | ||||||
|       "hasInstallScript": true, |       "hasInstallScript": true, | ||||||
|       "license": "AGPL-3.0-only", |       "license": "AGPL-3.0-only", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
| @@ -81,7 +81,7 @@ | |||||||
|       }, |       }, | ||||||
|       "devDependencies": { |       "devDependencies": { | ||||||
|         "cross-env": "7.0.3", |         "cross-env": "7.0.3", | ||||||
|         "electron": "25.9.5", |         "electron": "25.9.8", | ||||||
|         "electron-builder": "24.6.4", |         "electron-builder": "24.6.4", | ||||||
|         "electron-packager": "17.1.2", |         "electron-packager": "17.1.2", | ||||||
|         "electron-rebuild": "3.2.9", |         "electron-rebuild": "3.2.9", | ||||||
| @@ -4366,9 +4366,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/electron": { |     "node_modules/electron": { | ||||||
|       "version": "25.9.5", |       "version": "25.9.8", | ||||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.5.tgz", |       "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz", | ||||||
|       "integrity": "sha512-gM7GXUSd3JVRcYbBnNOtZeNnE5MCJjtZTT8QyIxJvpQ0Dh9dz3hTuEL62dOwnMFW/l47ACQ6es/8qi01P4QGZA==", |       "integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==", | ||||||
|       "hasInstallScript": true, |       "hasInstallScript": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@electron/get": "^2.0.0", |         "@electron/get": "^2.0.0", | ||||||
| @@ -16964,9 +16964,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "electron": { |     "electron": { | ||||||
|       "version": "25.9.5", |       "version": "25.9.8", | ||||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.5.tgz", |       "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz", | ||||||
|       "integrity": "sha512-gM7GXUSd3JVRcYbBnNOtZeNnE5MCJjtZTT8QyIxJvpQ0Dh9dz3hTuEL62dOwnMFW/l47ACQ6es/8qi01P4QGZA==", |       "integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@electron/get": "^2.0.0", |         "@electron/get": "^2.0.0", | ||||||
|         "@types/node": "^18.11.18", |         "@types/node": "^18.11.18", | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.62.2", |   "version": "0.62.6", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
| @@ -104,7 +104,7 @@ | |||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "cross-env": "7.0.3", |     "cross-env": "7.0.3", | ||||||
|     "electron": "25.9.5", |     "electron": "25.9.8", | ||||||
|     "electron-builder": "24.6.4", |     "electron-builder": "24.6.4", | ||||||
|     "electron-packager": "17.1.2", |     "electron-packager": "17.1.2", | ||||||
|     "electron-rebuild": "3.2.9", |     "electron-rebuild": "3.2.9", | ||||||
|   | |||||||
| @@ -427,6 +427,116 @@ paths: | |||||||
|             application/json; charset=utf-8: |             application/json; charset=utf-8: | ||||||
|               schema: |               schema: | ||||||
|                 $ref: '#/components/schemas/Error' |                 $ref: '#/components/schemas/Error' | ||||||
|  |   /attachments: | ||||||
|  |     post: | ||||||
|  |       description: create an attachment | ||||||
|  |       operationId: postAttachment | ||||||
|  |       requestBody: | ||||||
|  |         required: true | ||||||
|  |         content: | ||||||
|  |           application/json: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/CreateAttachment' | ||||||
|  |       responses: | ||||||
|  |         '201': | ||||||
|  |           description: attachment created | ||||||
|  |           content: | ||||||
|  |             application/json; charset=utf-8: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Attachment' | ||||||
|  |         default: | ||||||
|  |           description: unexpected error | ||||||
|  |           content: | ||||||
|  |             application/json; charset=utf-8: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Error' | ||||||
|  |   /attachments/{attachmentId}: | ||||||
|  |     parameters: | ||||||
|  |       - name: attachmentId | ||||||
|  |         in: path | ||||||
|  |         required: true | ||||||
|  |         schema: | ||||||
|  |           $ref: '#/components/schemas/EntityId' | ||||||
|  |     get: | ||||||
|  |       description: Returns an attachment identified by its ID | ||||||
|  |       operationId: getAttachmentById | ||||||
|  |       responses: | ||||||
|  |         '200': | ||||||
|  |           description: attachment response | ||||||
|  |           content: | ||||||
|  |             application/json; charset=utf-8: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Attachment' | ||||||
|  |         default: | ||||||
|  |           description: unexpected error | ||||||
|  |           content: | ||||||
|  |             application/json; charset=utf-8: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Error' | ||||||
|  |     patch: | ||||||
|  |       description: patch an attachment identified by the attachmentId with changes in the body. Only role, mime, title, and position are patchable. | ||||||
|  |       operationId: patchAttachmentById | ||||||
|  |       requestBody: | ||||||
|  |         required: true | ||||||
|  |         content: | ||||||
|  |           application/json: | ||||||
|  |             schema: | ||||||
|  |               $ref: '#/components/schemas/Attachment' | ||||||
|  |       responses: | ||||||
|  |         '200': | ||||||
|  |           description: attribute updated | ||||||
|  |           content: | ||||||
|  |             application/json; charset=utf-8: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Attachment' | ||||||
|  |         default: | ||||||
|  |           description: unexpected error | ||||||
|  |           content: | ||||||
|  |             application/json; charset=utf-8: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Error' | ||||||
|  |     delete: | ||||||
|  |       description: deletes an attachment based on the attachmentId supplied. | ||||||
|  |       operationId: deleteAttachmentById | ||||||
|  |       responses: | ||||||
|  |         '204': | ||||||
|  |           description: attachment deleted | ||||||
|  |         default: | ||||||
|  |           description: unexpected error | ||||||
|  |           content: | ||||||
|  |             application/json; charset=utf-8: | ||||||
|  |               schema: | ||||||
|  |                 $ref: '#/components/schemas/Error' | ||||||
|  |   /attachments/{attachmentId}/content: | ||||||
|  |     parameters: | ||||||
|  |       - name: attachmentId | ||||||
|  |         in: path | ||||||
|  |         required: true | ||||||
|  |         schema: | ||||||
|  |           $ref: '#/components/schemas/EntityId' | ||||||
|  |     get: | ||||||
|  |       description: Returns attachment content identified by its ID | ||||||
|  |       operationId: getAttachmentContent | ||||||
|  |       responses: | ||||||
|  |         '200': | ||||||
|  |           description: attachment content response | ||||||
|  |           content: | ||||||
|  |             text/html: | ||||||
|  |               schema: | ||||||
|  |                 type: string | ||||||
|  |     put: | ||||||
|  |       description: Updates attachment content identified by its ID | ||||||
|  |       operationId: putAttachmentContentById | ||||||
|  |       requestBody: | ||||||
|  |         description: html content of attachment | ||||||
|  |         required: true | ||||||
|  |         content: | ||||||
|  |           text/plain: | ||||||
|  |             schema: | ||||||
|  |               type: string | ||||||
|  |       responses: | ||||||
|  |         '204': | ||||||
|  |           description: attachment content updated | ||||||
|   /attributes: |   /attributes: | ||||||
|     post: |     post: | ||||||
|       description: create an attribute for a given note |       description: create an attribute for a given note | ||||||
| @@ -474,7 +584,7 @@ paths: | |||||||
|               schema: |               schema: | ||||||
|                 $ref: '#/components/schemas/Error' |                 $ref: '#/components/schemas/Error' | ||||||
|     patch: |     patch: | ||||||
|       description: patch a attribute identified by the attributeId with changes in the body. For labels, only value and position can be updated. For relations, only position can be updated. If you want to modify other properties, you need to delete the old attribute and create a new one. |       description: patch an attribute identified by the attributeId with changes in the body. For labels, only value and position can be updated. For relations, only position can be updated. If you want to modify other properties, you need to delete the old attribute and create a new one. | ||||||
|       operationId: patchAttributeById |       operationId: patchAttributeById | ||||||
|       requestBody: |       requestBody: | ||||||
|         required: true |         required: true | ||||||
| @@ -496,7 +606,7 @@ paths: | |||||||
|               schema: |               schema: | ||||||
|                 $ref: '#/components/schemas/Error' |                 $ref: '#/components/schemas/Error' | ||||||
|     delete: |     delete: | ||||||
|       description: deletes a attribute based on the attributeId supplied. |       description: deletes an attribute based on the attributeId supplied. | ||||||
|       operationId: deleteAttributeById |       operationId: deleteAttributeById | ||||||
|       responses: |       responses: | ||||||
|         '204': |         '204': | ||||||
| @@ -884,6 +994,57 @@ components: | |||||||
|           $ref: '#/components/schemas/Note' |           $ref: '#/components/schemas/Note' | ||||||
|         branch: |         branch: | ||||||
|           $ref: '#/components/schemas/Branch' |           $ref: '#/components/schemas/Branch' | ||||||
|  |     Attachment: | ||||||
|  |       type: object | ||||||
|  |       description: Attachment is owned by a note, has title and content | ||||||
|  |       properties: | ||||||
|  |         attachmentId: | ||||||
|  |           $ref: '#/components/schemas/EntityId' | ||||||
|  |           readOnly: true | ||||||
|  |         ownerId: | ||||||
|  |           $ref: '#/components/schemas/EntityId' | ||||||
|  |           description: identifies the owner of the attachment, is either noteId or revisionId | ||||||
|  |         role: | ||||||
|  |           type: string | ||||||
|  |         mime: | ||||||
|  |           type: string | ||||||
|  |         title: | ||||||
|  |           type: string | ||||||
|  |         position: | ||||||
|  |           type: integer | ||||||
|  |           format: int32 | ||||||
|  |         blobId: | ||||||
|  |           type: string | ||||||
|  |           description: ID of the blob object which effectively serves as a content hash | ||||||
|  |         dateModified: | ||||||
|  |           $ref: '#/components/schemas/LocalDateTime' | ||||||
|  |           readOnly: true | ||||||
|  |         utcDateModified: | ||||||
|  |           $ref: '#/components/schemas/UtcDateTime' | ||||||
|  |           readOnly: true | ||||||
|  |         utcDateScheduledForErasureSince: | ||||||
|  |           $ref: '#/components/schemas/UtcDateTime' | ||||||
|  |           readOnly: true | ||||||
|  |         contentLength: | ||||||
|  |           type: integer | ||||||
|  |           format: int32 | ||||||
|  |     CreateAttachment: | ||||||
|  |       type: object | ||||||
|  |       properties: | ||||||
|  |         ownerId: | ||||||
|  |           $ref: '#/components/schemas/EntityId' | ||||||
|  |           description: identifies the owner of the attachment, is either noteId or revisionId | ||||||
|  |         role: | ||||||
|  |           type: string | ||||||
|  |         mime: | ||||||
|  |           type: string | ||||||
|  |         title: | ||||||
|  |           type: string | ||||||
|  |         content: | ||||||
|  |           type: string | ||||||
|  |         position: | ||||||
|  |           type: integer | ||||||
|  |           format: int32 | ||||||
|     Attribute: |     Attribute: | ||||||
|       type: object |       type: object | ||||||
|       description: Attribute (Label, Relation) is a key-value record attached to a note. |       description: Attribute (Label, Relation) is a key-value record attached to a note. | ||||||
|   | |||||||
| @@ -117,14 +117,14 @@ async function renderCode(note, $renderedContent) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function renderImage(entity, $renderedContent, options = {}) { | function renderImage(entity, $renderedContent, options = {}) { | ||||||
|     const sanitizedTitle = entity.title.replace(/[^a-z0-9-.]/gi, ""); |     const encodedTitle = encodeURIComponent(entity.title); | ||||||
|  |  | ||||||
|     let url; |     let url; | ||||||
|  |  | ||||||
|     if (entity instanceof FNote) { |     if (entity instanceof FNote) { | ||||||
|         url = `api/images/${entity.noteId}/${sanitizedTitle}?${Math.random()}`; |         url = `api/images/${entity.noteId}/${encodedTitle}?${Math.random()}`; | ||||||
|     } else if (entity instanceof FAttachment) { |     } else if (entity instanceof FAttachment) { | ||||||
|         url = `api/attachments/${entity.attachmentId}/image/${sanitizedTitle}?${entity.utcDateModified}">`; |         url = `api/attachments/${entity.attachmentId}/image/${encodedTitle}?${entity.utcDateModified}">`; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     $renderedContent // styles needed for the zoom to work well |     $renderedContent // styles needed for the zoom to work well | ||||||
|   | |||||||
| @@ -42,6 +42,7 @@ async function createLink(notePath, options = {}) { | |||||||
|     const showNotePath = options.showNotePath === undefined ? false : options.showNotePath; |     const showNotePath = options.showNotePath === undefined ? false : options.showNotePath; | ||||||
|     const showNoteIcon = options.showNoteIcon === undefined ? false : options.showNoteIcon; |     const showNoteIcon = options.showNoteIcon === undefined ? false : options.showNoteIcon; | ||||||
|     const referenceLink = options.referenceLink === undefined ? false : options.referenceLink; |     const referenceLink = options.referenceLink === undefined ? false : options.referenceLink; | ||||||
|  |     const autoConvertToImage = options.autoConvertToImage === undefined ? false : options.autoConvertToImage; | ||||||
|  |  | ||||||
|     const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath); |     const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath); | ||||||
|     const viewScope = options.viewScope || {}; |     const viewScope = options.viewScope || {}; | ||||||
| @@ -58,6 +59,16 @@ async function createLink(notePath, options = {}) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     const note = await froca.getNote(noteId); | ||||||
|  |  | ||||||
|  |     if (autoConvertToImage && ['image', 'canvas', 'mermaid'].includes(note.type) && viewMode === 'default') { | ||||||
|  |         const encodedTitle = encodeURIComponent(linkTitle); | ||||||
|  |  | ||||||
|  |         return $("<img>") | ||||||
|  |             .attr("src", `api/images/${noteId}/${encodedTitle}?${Math.random()}`) | ||||||
|  |             .attr("alt", linkTitle); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const $container = $("<span>"); |     const $container = $("<span>"); | ||||||
|  |  | ||||||
|     if (showNoteIcon) { |     if (showNoteIcon) { | ||||||
|   | |||||||
| @@ -97,7 +97,7 @@ export default class IncludeNoteDialog extends BasicWidget { | |||||||
|  |  | ||||||
|         const boxSize = $("input[name='include-note-box-size']:checked").val(); |         const boxSize = $("input[name='include-note-box-size']:checked").val(); | ||||||
|  |  | ||||||
|         if (note.type === 'image') { |         if (['image', 'canvas', 'mermaid'].includes(note.type)) { | ||||||
|             // there's no benefit to use insert note functionlity for images, |             // there's no benefit to use insert note functionlity for images, | ||||||
|             // so we'll just add an IMG tag |             // so we'll just add an IMG tag | ||||||
|             this.textTypeWidget.addImage(noteId); |             this.textTypeWidget.addImage(noteId); | ||||||
|   | |||||||
| @@ -274,16 +274,16 @@ export default class RevisionsDialog extends BasicWidget { | |||||||
|  |  | ||||||
|             this.$content.html($table); |             this.$content.html($table); | ||||||
|         } else if (revisionItem.type === 'canvas') { |         } else if (revisionItem.type === 'canvas') { | ||||||
|             const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, ""); |             const encodedTitle = encodeURIComponent(revisionItem.title); | ||||||
|  |  | ||||||
|             this.$content.html($("<img>") |             this.$content.html($("<img>") | ||||||
|                 .attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`) |                 .attr("src", `api/revisions/${revisionItem.revisionId}/image/${encodedTitle}?${Math.random()}`) | ||||||
|                 .css("max-width", "100%")); |                 .css("max-width", "100%")); | ||||||
|         } else if (revisionItem.type === 'mermaid') { |         } else if (revisionItem.type === 'mermaid') { | ||||||
|             const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, ""); |             const encodedTitle = encodeURIComponent(revisionItem.title); | ||||||
|  |  | ||||||
|             this.$content.html($("<img>") |             this.$content.html($("<img>") | ||||||
|                 .attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`) |                 .attr("src", `api/revisions/${revisionItem.revisionId}/image/${encodedTitle}?${Math.random()}`) | ||||||
|                 .css("max-width", "100%")); |                 .css("max-width", "100%")); | ||||||
|  |  | ||||||
|             this.$content.append($("<pre>").text(fullRevision.content)); |             this.$content.append($("<pre>").text(fullRevision.content)); | ||||||
|   | |||||||
| @@ -56,7 +56,6 @@ export default class HighlightsListWidget extends RightPanelWidget { | |||||||
|                 .class("icon-action"), |                 .class("icon-action"), | ||||||
|             new OnClickButtonWidget() |             new OnClickButtonWidget() | ||||||
|                 .icon("bx-x") |                 .icon("bx-x") | ||||||
|                 .title("Close Highlights List") |  | ||||||
|                 .titlePlacement("left") |                 .titlePlacement("left") | ||||||
|                 .onClick(widget => widget.triggerCommand("closeHlt")) |                 .onClick(widget => widget.triggerCommand("closeHlt")) | ||||||
|                 .class("icon-action") |                 .class("icon-action") | ||||||
|   | |||||||
| @@ -258,10 +258,11 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|                         .append($("<h2>").text(this.note.title)) |                         .append($("<h2>").text(this.note.title)) | ||||||
|                         .append($promotedAttributes) |                         .append($promotedAttributes) | ||||||
|                         .prop('outerHTML'), |                         .prop('outerHTML'), | ||||||
|  |  | ||||||
|             footer: ` |             footer: ` | ||||||
| <script src="${assetPath}/libraries/katex/katex.min.js"></script> | <script src="${assetPath}/node_modules/katex/dist/katex.min.js"></script> | ||||||
| <script src="${assetPath}/libraries/katex/mhchem.min.js"></script> | <script src="${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script> | ||||||
| <script src="${assetPath}/libraries/katex/auto-render.min.js"></script> | <script src="${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script> | ||||||
| <script> | <script> | ||||||
|     document.body.className += ' ck-content printed-content'; |     document.body.className += ' ck-content printed-content'; | ||||||
|      |      | ||||||
| @@ -273,7 +274,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { | |||||||
|                 `${assetPath}/libraries/codemirror/codemirror.css`, |                 `${assetPath}/libraries/codemirror/codemirror.css`, | ||||||
|                 `${assetPath}/libraries/ckeditor/ckeditor-content.css`, |                 `${assetPath}/libraries/ckeditor/ckeditor-content.css`, | ||||||
|                 `${assetPath}/libraries/bootstrap/css/bootstrap.min.css`, |                 `${assetPath}/libraries/bootstrap/css/bootstrap.min.css`, | ||||||
|                 `${assetPath}/libraries/katex/katex.min.css`, |                 `${assetPath}/node_modules/katex/dist/katex.min.css`, | ||||||
|                 `${assetPath}/stylesheets/print.css`, |                 `${assetPath}/stylesheets/print.css`, | ||||||
|                 `${assetPath}/stylesheets/relation_map.css`, |                 `${assetPath}/stylesheets/relation_map.css`, | ||||||
|                 `${assetPath}/stylesheets/ckeditor-theme.css` |                 `${assetPath}/stylesheets/ckeditor-theme.css` | ||||||
|   | |||||||
| @@ -402,11 +402,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|                     })); |                     })); | ||||||
|  |  | ||||||
|                     if (notes.length === 1) { |                     if (notes.length === 1) { | ||||||
|                         linkService.createLink(notes[0].noteId, {referenceLink: true}) |                         linkService.createLink(notes[0].noteId, {referenceLink: true, autoConvertToImage: true}) | ||||||
|                             .then($link => data.dataTransfer.setData("text/html", $link[0].outerHTML)); |                             .then($link => data.dataTransfer.setData("text/html", $link[0].outerHTML)); | ||||||
|                     } |                     } | ||||||
|                     else { |                     else { | ||||||
|                         Promise.all(notes.map(note => linkService.createLink(note.noteId, {referenceLink: true}))).then(links => { |                         Promise.all(notes.map(note => linkService.createLink(note.noteId, {referenceLink: true, autoConvertToImage: true}))).then(links => { | ||||||
|                             const $list = $("<ul>").append(...links.map($link => $("<li>").append($link))); |                             const $list = $("<ul>").append(...links.map($link => $("<li>").append($link))); | ||||||
|  |  | ||||||
|                             data.dataTransfer.setData("text/html", $list[0].outerHTML); |                             data.dataTransfer.setData("text/html", $list[0].outerHTML); | ||||||
|   | |||||||
| @@ -8,8 +8,14 @@ import options from "../../services/options.js"; | |||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
|  |  | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div> | <div class="promoted-attributes-widget"> | ||||||
|     <style> |     <style> | ||||||
|  |     body.mobile .promoted-attributes-widget { | ||||||
|  |         /* https://github.com/zadam/trilium/issues/4468 */ | ||||||
|  |         flex-shrink: 0.4; | ||||||
|  |         overflow: auto; | ||||||
|  |     } | ||||||
|  |      | ||||||
|     .promoted-attributes-container { |     .promoted-attributes-container { | ||||||
|         margin: auto; |         margin: auto; | ||||||
|         display: flex; |         display: flex; | ||||||
|   | |||||||
| @@ -68,7 +68,6 @@ export default class TocWidget extends RightPanelWidget { | |||||||
|                 .class("icon-action"), |                 .class("icon-action"), | ||||||
|             new OnClickButtonWidget() |             new OnClickButtonWidget() | ||||||
|                 .icon("bx-x") |                 .icon("bx-x") | ||||||
|                 .title("Close Table of Contents") |  | ||||||
|                 .titlePlacement("left") |                 .titlePlacement("left") | ||||||
|                 .onClick(widget => widget.triggerCommand("closeToc")) |                 .onClick(widget => widget.triggerCommand("closeToc")) | ||||||
|                 .class("icon-action") |                 .class("icon-action") | ||||||
|   | |||||||
| @@ -365,12 +365,10 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|         const note = await froca.getNote(noteId); |         const note = await froca.getNote(noteId); | ||||||
|  |  | ||||||
|         this.watchdog.editor.model.change( writer => { |         this.watchdog.editor.model.change( writer => { | ||||||
|             const sanitizedTitle = note.title.replace(/[^a-z0-9-.]/gi, ""); |             const encodedTitle = encodeURIComponent(note.title); | ||||||
|             const src = `api/images/${note.noteId}/${sanitizedTitle}`; |             const src = `api/images/${note.noteId}/${encodedTitle}`; | ||||||
|  |  | ||||||
|             const imageElement = writer.createElement( 'image',  { 'src': src } ); |             this.watchdog.editor.execute( 'insertImage', { source: src } ); | ||||||
|  |  | ||||||
|             this.watchdog.editor.model.insertContent(imageElement, this.watchdog.editor.model.document.selection); |  | ||||||
|         } ); |         } ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -153,8 +153,9 @@ function processContent(images, note, content) { | |||||||
|             const buffer = Buffer.from(dataUrl.split(",")[1], 'base64'); |             const buffer = Buffer.from(dataUrl.split(",")[1], 'base64'); | ||||||
|  |  | ||||||
|             const attachment = imageService.saveImageToAttachment(note.noteId, buffer, filename, true); |             const attachment = imageService.saveImageToAttachment(note.noteId, buffer, filename, true); | ||||||
|             const sanitizedTitle = attachment.title.replace(/[^a-z0-9-.]/gi, ""); |  | ||||||
|             const url = `api/attachments/${attachment.attachmentId}/image/${sanitizedTitle}`; |             const encodedTitle = encodeURIComponent(attachment.title); | ||||||
|  |             const url = `api/attachments/${attachment.attachmentId}/image/${encodedTitle}`; | ||||||
|  |  | ||||||
|             log.info(`Replacing '${imageId}' with '${url}' in note '${note.noteId}'`); |             log.info(`Replacing '${imageId}' with '${url}' in note '${note.noteId}'`); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ const cls = require('../../services/cls'); | |||||||
| const path = require('path'); | const path = require('path'); | ||||||
| const becca = require("../../becca/becca"); | const becca = require("../../becca/becca"); | ||||||
| const blobService = require("../../services/blob"); | const blobService = require("../../services/blob"); | ||||||
|  | const eraseService = require("../../services/erase.js"); | ||||||
|  |  | ||||||
| function getRevisionBlob(req) { | function getRevisionBlob(req) { | ||||||
|     const preview = req.query.preview === 'true'; |     const preview = req.query.preview === 'true'; | ||||||
| @@ -88,11 +89,11 @@ function eraseAllRevisions(req) { | |||||||
|     const revisionIdsToErase = sql.getColumn('SELECT revisionId FROM revisions WHERE noteId = ?', |     const revisionIdsToErase = sql.getColumn('SELECT revisionId FROM revisions WHERE noteId = ?', | ||||||
|         [req.params.noteId]); |         [req.params.noteId]); | ||||||
|  |  | ||||||
|     revisionService.eraseRevisions(revisionIdsToErase); |     eraseService.eraseRevisions(revisionIdsToErase); | ||||||
| } | } | ||||||
|  |  | ||||||
| function eraseRevision(req) { | function eraseRevision(req) { | ||||||
|     revisionService.eraseRevisions([req.params.revisionId]); |     eraseService.eraseRevisions([req.params.revisionId]); | ||||||
| } | } | ||||||
|  |  | ||||||
| function restoreRevision(req) { | function restoreRevision(req) { | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2023-11-21T20:49:24+01:00", buildRevision: "e2b1421bf3d764ffe444a103c118e67d8c563673" }; | module.exports = { buildDate:"2024-01-21T23:49:23+01:00", buildRevision: "4f8073daa7cff1b8b6737ae45792b2e87c2adf33" }; | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ const becca = require("../becca/becca"); | |||||||
| const cloningService = require("./cloning"); | const cloningService = require("./cloning"); | ||||||
| const branchService = require("./branches"); | const branchService = require("./branches"); | ||||||
| const utils = require("./utils"); | const utils = require("./utils"); | ||||||
|  | const eraseService = require("./erase.js"); | ||||||
|  |  | ||||||
| const ACTION_HANDLERS = { | const ACTION_HANDLERS = { | ||||||
|     addLabel: (action, note) => { |     addLabel: (action, note) => { | ||||||
| @@ -18,7 +19,7 @@ const ACTION_HANDLERS = { | |||||||
|         note.deleteNote(deleteId); |         note.deleteNote(deleteId); | ||||||
|     }, |     }, | ||||||
|     deleteRevisions: (action, note) => { |     deleteRevisions: (action, note) => { | ||||||
|         revisionService.eraseRevisions(note.getRevisions().map(rev => rev.revisionId)); |         eraseService.eraseRevisions(note.getRevisions().map(rev => rev.revisionId)); | ||||||
|     }, |     }, | ||||||
|     deleteLabel: (action, note) => { |     deleteLabel: (action, note) => { | ||||||
|         for (const label of note.getOwnedLabels(action.labelName)) { |         for (const label of note.getOwnedLabels(action.labelName)) { | ||||||
|   | |||||||
| @@ -48,6 +48,14 @@ function isEntityEventsDisabled() { | |||||||
|     return !!namespace.get('disableEntityEvents'); |     return !!namespace.get('disableEntityEvents'); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function setMigrationRunning(running) { | ||||||
|  |     namespace.set('migrationRunning', !!running); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function isMigrationRunning() { | ||||||
|  |     return !!namespace.get('migrationRunning'); | ||||||
|  | } | ||||||
|  |  | ||||||
| function disableSlowQueryLogging(disable) { | function disableSlowQueryLogging(disable) { | ||||||
|     namespace.set('disableSlowQueryLogging', disable); |     namespace.set('disableSlowQueryLogging', disable); | ||||||
| } | } | ||||||
| @@ -102,5 +110,7 @@ module.exports = { | |||||||
|     putEntityChange, |     putEntityChange, | ||||||
|     ignoreEntityChangeIds, |     ignoreEntityChangeIds, | ||||||
|     disableSlowQueryLogging, |     disableSlowQueryLogging, | ||||||
|     isSlowQueryLoggingDisabled |     isSlowQueryLoggingDisabled, | ||||||
|  |     setMigrationRunning, | ||||||
|  |     isMigrationRunning | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -467,7 +467,7 @@ class ConsistencyChecks { | |||||||
|                     WHERE blobs.blobId IS NULL`, |                     WHERE blobs.blobId IS NULL`, | ||||||
|             ({revisionId, blobId}) => { |             ({revisionId, blobId}) => { | ||||||
|                 if (this.autoFix) { |                 if (this.autoFix) { | ||||||
|                     revisionService.eraseRevisions([revisionId]); |                     eraseService.eraseRevisions([revisionId]); | ||||||
|  |  | ||||||
|                     this.reloadNeeded = true; |                     this.reloadNeeded = true; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ function eraseNotes(noteIdsToErase) { | |||||||
|     const revisionIdsToErase = sql.getManyRows(`SELECT revisionId FROM revisions WHERE noteId IN (???)`, noteIdsToErase) |     const revisionIdsToErase = sql.getManyRows(`SELECT revisionId FROM revisions WHERE noteId IN (???)`, noteIdsToErase) | ||||||
|         .map(row => row.revisionId); |         .map(row => row.revisionId); | ||||||
|  |  | ||||||
|     revisionService.eraseRevisions(revisionIdsToErase); |     eraseRevisions(revisionIdsToErase); | ||||||
|  |  | ||||||
|     log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`); |     log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`); | ||||||
| } | } | ||||||
| @@ -79,6 +79,18 @@ function eraseAttachments(attachmentIdsToErase) { | |||||||
|     log.info(`Erased attachments: ${JSON.stringify(attachmentIdsToErase)}`); |     log.info(`Erased attachments: ${JSON.stringify(attachmentIdsToErase)}`); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function eraseRevisions(revisionIdsToErase) { | ||||||
|  |     if (revisionIdsToErase.length === 0) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sql.executeMany(`DELETE FROM revisions WHERE revisionId IN (???)`, revisionIdsToErase); | ||||||
|  |  | ||||||
|  |     setEntityChangesAsErased(sql.getManyRows(`SELECT * FROM entity_changes WHERE entityName = 'revisions' AND entityId IN (???)`, revisionIdsToErase)); | ||||||
|  |  | ||||||
|  |     log.info(`Removed revisions: ${JSON.stringify(revisionIdsToErase)}`); | ||||||
|  | } | ||||||
|  |  | ||||||
| function eraseUnusedBlobs() { | function eraseUnusedBlobs() { | ||||||
|     const unusedBlobIds = sql.getColumn(` |     const unusedBlobIds = sql.getColumn(` | ||||||
|         SELECT blobs.blobId |         SELECT blobs.blobId | ||||||
| @@ -184,5 +196,6 @@ module.exports = { | |||||||
|     eraseUnusedAttachmentsNow, |     eraseUnusedAttachmentsNow, | ||||||
|     eraseNotesWithDeleteId, |     eraseNotesWithDeleteId, | ||||||
|     eraseUnusedBlobs, |     eraseUnusedBlobs, | ||||||
|     eraseAttachments |     eraseAttachments, | ||||||
|  |     eraseRevisions | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -303,8 +303,8 @@ function importEnex(taskContext, file, parentNote) { | |||||||
|  |  | ||||||
|                     const attachment = imageService.saveImageToAttachment(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages); |                     const attachment = imageService.saveImageToAttachment(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages); | ||||||
|  |  | ||||||
|                     const sanitizedTitle = attachment.title.replace(/[^a-z0-9-.]/gi, ""); |                     const encodedTitle = encodeURIComponent(attachment.title); | ||||||
|                     const url = `api/attachments/${attachment.attachmentId}/image/${sanitizedTitle}`; |                     const url = `api/attachments/${attachment.attachmentId}/image/${encodedTitle}`; | ||||||
|                     const imageLink = `<img src="${url}">`; |                     const imageLink = `<img src="${url}">`; | ||||||
|  |  | ||||||
|                     content = content.replace(mediaRegex, imageLink); |                     content = content.replace(mediaRegex, imageLink); | ||||||
|   | |||||||
| @@ -302,7 +302,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [ | |||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         actionName: "lastTab", |         actionName: "lastTab", | ||||||
|         defaultShortcuts: ["CommandOrControl+0"], |         defaultShortcuts: [], | ||||||
|         description: "Activates the last tab in the list", |         description: "Activates the last tab in the list", | ||||||
|         scope: "window" |         scope: "window" | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -5,12 +5,13 @@ const log = require('./log'); | |||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const resourceDir = require('./resource_dir'); | const resourceDir = require('./resource_dir'); | ||||||
| const appInfo = require('./app_info'); | const appInfo = require('./app_info'); | ||||||
|  | const cls = require('./cls.js'); | ||||||
|  |  | ||||||
| async function migrate() { | async function migrate() { | ||||||
|     const currentDbVersion = getDbVersion(); |     const currentDbVersion = getDbVersion(); | ||||||
|  |  | ||||||
|     if (currentDbVersion < 214) { |     if (currentDbVersion < 214) { | ||||||
|         log.error("Direct migration from your current version is not supported. Please upgrade to the latest v0.60.X first and only then to this version."); |         log.error("Direct migration from your current version is not supported. Please upgrade to the latest v0.60.4 first and only then to this version."); | ||||||
|  |  | ||||||
|         utils.crash(); |         utils.crash(); | ||||||
|         return; |         return; | ||||||
| @@ -18,7 +19,7 @@ async function migrate() { | |||||||
|  |  | ||||||
|     // backup before attempting migration |     // backup before attempting migration | ||||||
|     await backupService.backupNow( |     await backupService.backupNow( | ||||||
|         // creating a special backup for versions 0.60.X, the changes in 0.61 are major. |         // creating a special backup for version 0.60.4, the changes in 0.61 are major. | ||||||
|         currentDbVersion === 214 |         currentDbVersion === 214 | ||||||
|             ? `before-migration-v060` |             ? `before-migration-v060` | ||||||
|             : 'before-migration' |             : 'before-migration' | ||||||
| @@ -51,6 +52,9 @@ async function migrate() { | |||||||
|     // all migrations are executed in one transaction - upgrade either succeeds, or the user can stay at the old version |     // all migrations are executed in one transaction - upgrade either succeeds, or the user can stay at the old version | ||||||
|     // otherwise if half of the migrations succeed, user can't use any version - DB is too "new" for the old app, |     // otherwise if half of the migrations succeed, user can't use any version - DB is too "new" for the old app, | ||||||
|     // and too old for the new app version. |     // and too old for the new app version. | ||||||
|  |  | ||||||
|  |     cls.setMigrationRunning(true); | ||||||
|  |  | ||||||
|     sql.transactional(() => { |     sql.transactional(() => { | ||||||
|         for (const mig of migrations) { |         for (const mig of migrations) { | ||||||
|             try { |             try { | ||||||
|   | |||||||
| @@ -471,6 +471,8 @@ function findRelationMapLinks(content, foundLinks) { | |||||||
| const imageUrlToAttachmentIdMapping = {}; | const imageUrlToAttachmentIdMapping = {}; | ||||||
|  |  | ||||||
| async function downloadImage(noteId, imageUrl) { | async function downloadImage(noteId, imageUrl) { | ||||||
|  |     const unescapedUrl = utils.unescapeHtml(imageUrl); | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|         let imageBuffer; |         let imageBuffer; | ||||||
|  |  | ||||||
| @@ -487,14 +489,14 @@ async function downloadImage(noteId, imageUrl) { | |||||||
|                 }); |                 }); | ||||||
|             }); |             }); | ||||||
|         } else { |         } else { | ||||||
|             imageBuffer = await request.getImage(imageUrl); |             imageBuffer = await request.getImage(unescapedUrl); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const parsedUrl = url.parse(imageUrl); |         const parsedUrl = url.parse(unescapedUrl); | ||||||
|         const title = path.basename(parsedUrl.pathname); |         const title = path.basename(parsedUrl.pathname); | ||||||
|  |  | ||||||
|         const imageService = require('../services/image'); |         const imageService = require('../services/image'); | ||||||
|         const {attachment} = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true); |         const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true); | ||||||
|  |  | ||||||
|         imageUrlToAttachmentIdMapping[imageUrl] = attachment.attachmentId; |         imageUrlToAttachmentIdMapping[imageUrl] = attachment.attachmentId; | ||||||
|  |  | ||||||
| @@ -511,7 +513,7 @@ const downloadImagePromises = {}; | |||||||
| function replaceUrl(content, url, attachment) { | function replaceUrl(content, url, attachment) { | ||||||
|     const quotedUrl = utils.quoteRegex(url); |     const quotedUrl = utils.quoteRegex(url); | ||||||
|  |  | ||||||
|     return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "ig"), ` src="api/attachments/${encodeURIComponent(attachment.title)}/image"`); |     return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "ig"), ` src="api/attachments/${attachment.attachmentId}/image/${encodeURIComponent(attachment.title)}"`); | ||||||
| } | } | ||||||
|  |  | ||||||
| function downloadImages(noteId, content) { | function downloadImages(noteId, content) { | ||||||
| @@ -529,9 +531,9 @@ function downloadImages(noteId, content) { | |||||||
|             const imageService = require('../services/image'); |             const imageService = require('../services/image'); | ||||||
|             const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, "inline image", true, true); |             const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, "inline image", true, true); | ||||||
|  |  | ||||||
|             const sanitizedTitle = attachment.title.replace(/[^a-z0-9-.]/gi, ""); |             const encodedTitle = encodeURIComponent(attachment.title); | ||||||
|  |  | ||||||
|             content = `${content.substr(0, imageMatch.index)}<img src="api/attachments/${attachment.attachmentId}/image/${sanitizedTitle}"${content.substr(imageMatch.index + imageMatch[0].length)}`; |             content = `${content.substr(0, imageMatch.index)}<img src="api/attachments/${attachment.attachmentId}/image/${encodedTitle}"${content.substr(imageMatch.index + imageMatch[0].length)}`; | ||||||
|         } |         } | ||||||
|         else if (!url.includes('api/images/') && !/api\/attachments\/.+\/image\/?.*/.test(url) |         else if (!url.includes('api/images/') && !/api\/attachments\/.+\/image\/?.*/.test(url) | ||||||
|             // this is an exception for the web clipper's "imageId" |             // this is an exception for the web clipper's "imageId" | ||||||
| @@ -636,6 +638,10 @@ function saveAttachments(note, content) { | |||||||
|         content = `${content.substr(0, attachmentMatch.index)}<a class="reference-link" href="#root/${note.noteId}?viewMode=attachments&attachmentId=${attachment.attachmentId}">${title}</a>${content.substr(attachmentMatch.index + attachmentMatch[0].length)}`; |         content = `${content.substr(0, attachmentMatch.index)}<a class="reference-link" href="#root/${note.noteId}?viewMode=attachments&attachmentId=${attachment.attachmentId}">${title}</a>${content.substr(attachmentMatch.index + attachmentMatch[0].length)}`; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // removing absolute references to server to keep it working between instances, | ||||||
|  |     // we also omit / at the beginning to keep the paths relative | ||||||
|  |     content = content.replace(/src="[^"]*\/api\/attachments\//g, 'src="api/attachments/'); | ||||||
|  |  | ||||||
|     return content; |     return content; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -889,6 +895,15 @@ function scanForLinks(note, content) { | |||||||
|  * Things which have to be executed after updating content, but asynchronously (separate transaction) |  * Things which have to be executed after updating content, but asynchronously (separate transaction) | ||||||
|  */ |  */ | ||||||
| async function asyncPostProcessContent(note, content) { | async function asyncPostProcessContent(note, content) { | ||||||
|  |     if (cls.isMigrationRunning()) { | ||||||
|  |         // this is rarely needed for migrations, but can cause trouble by e.g. triggering downloads | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (note.hasStringContent() && !utils.isString(content)) { | ||||||
|  |         content = content.toString(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     scanForLinks(note, content); |     scanForLinks(note, content); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -46,18 +46,6 @@ function protectRevisions(note) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function eraseRevisions(revisionIdsToErase) { |  | ||||||
|     if (revisionIdsToErase.length === 0) { |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     log.info(`Removing revisions: ${JSON.stringify(revisionIdsToErase)}`); |  | ||||||
|  |  | ||||||
|     sql.executeMany(`DELETE FROM revisions WHERE revisionId IN (???)`, revisionIdsToErase); |  | ||||||
|     sql.executeMany(`UPDATE entity_changes SET isErased = 1, utcDateChanged = '${dateUtils.utcNowDateTime()}' WHERE entityName = 'revisions' AND entityId IN (???)`, revisionIdsToErase); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     protectRevisions, |     protectRevisions | ||||||
|     eraseRevisions |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -209,8 +209,9 @@ function sortNotesIfNeeded(parentNoteId) { | |||||||
| function setNoteToParent(noteId, prefix, parentNoteId) { | function setNoteToParent(noteId, prefix, parentNoteId) { | ||||||
|     const parentNote = becca.getNote(parentNoteId); |     const parentNote = becca.getNote(parentNoteId); | ||||||
|  |  | ||||||
|     if (!parentNote) { |     if (parentNoteId && !parentNote) { | ||||||
|         throw new Error(`Cannot move note to deleted parent note '${parentNoteId}'`); |         // null parentNoteId is a valid value | ||||||
|  |         throw new Error(`Cannot move note to deleted / missing parent note '${parentNoteId}'`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // case where there might be more such branches is ignored. It's expected there should be just one |     // case where there might be more such branches is ignored. It's expected there should be just one | ||||||
|   | |||||||
| @@ -63,6 +63,8 @@ function isElectron() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function hash(text) { | function hash(text) { | ||||||
|  |     text = text.normalize(); | ||||||
|  |  | ||||||
|     return crypto.createHash('sha1').update(text).digest('base64'); |     return crypto.createHash('sha1').update(text).digest('base64'); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -303,6 +305,10 @@ function toMap(list, key) { | |||||||
|     return map; |     return map; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function isString(x) { | ||||||
|  |     return Object.prototype.toString.call(x) === "[object String]"; | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     randomSecureToken, |     randomSecureToken, | ||||||
|     randomString, |     randomString, | ||||||
| @@ -335,4 +341,5 @@ module.exports = { | |||||||
|     normalize, |     normalize, | ||||||
|     hashedBlobId, |     hashedBlobId, | ||||||
|     toMap, |     toMap, | ||||||
|  |     isString | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -105,10 +105,10 @@ function renderText(result, note) { | |||||||
|  |  | ||||||
|         if (result.content.includes(`<span class="math-tex">`)) { |         if (result.content.includes(`<span class="math-tex">`)) { | ||||||
|             result.header += ` |             result.header += ` | ||||||
| <script src="../../${assetPath}/libraries/katex/katex.min.js"></script> | <script src="../../${assetPath}/node_modules/katex/dist/katex.min.js"></script> | ||||||
| <link rel="stylesheet" href="../../${assetPath}/libraries/katex/katex.min.css"> | <link rel="stylesheet" href="../../${assetPath}/node_modules/katex/dist/katex.min.css"> | ||||||
| <script src="../../${assetPath}/libraries/katex/auto-render.min.js"></script> | <script src="../../${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script> | ||||||
| <script src="../../${assetPath}/libraries/katex/mhchem.min.js"></script> | <script src="../../${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script> | ||||||
| <script> | <script> | ||||||
| document.addEventListener("DOMContentLoaded", function() { | document.addEventListener("DOMContentLoaded", function() { | ||||||
|     renderMathInElement(document.getElementById('content')); |     renderMathInElement(document.getElementById('content')); | ||||||
| @@ -137,7 +137,7 @@ function renderCode(result) { | |||||||
|  |  | ||||||
| function renderMermaid(result, note) { | function renderMermaid(result, note) { | ||||||
|     result.content = ` |     result.content = ` | ||||||
| <img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}"> | <img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}"> | ||||||
| <hr> | <hr> | ||||||
| <details> | <details> | ||||||
|     <summary>Chart source</summary> |     <summary>Chart source</summary> | ||||||
| @@ -146,7 +146,7 @@ function renderMermaid(result, note) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function renderImage(result, note) { | function renderImage(result, note) { | ||||||
|     result.content = `<img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}">`; |     result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">`; | ||||||
| } | } | ||||||
|  |  | ||||||
| function renderFile(note, result) { | function renderFile(note, result) { | ||||||
|   | |||||||
| @@ -490,6 +490,10 @@ class SNote extends AbstractShacaEntity { | |||||||
|         return escape(this.title); |         return escape(this.title); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     get encodedTitle() { | ||||||
|  |         return encodeURIComponent(this.title); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     getPojo() { |     getPojo() { | ||||||
|         return { |         return { | ||||||
|             noteId: this.noteId, |             noteId: this.noteId, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user