mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-29 11:44:21 +08:00 
			
		
		
		
	saving / viewing canvas revisions
This commit is contained in:
		
							parent
							
								
									62ccf798ee
								
							
						
					
					
						commit
						5be61e6142
					
				| @ -1607,16 +1607,12 @@ class BNote extends AbstractBeccaEntity { | |||||||
| 
 | 
 | ||||||
|             revision.save(); // to generate revisionId, which is then used to save attachments
 |             revision.save(); // to generate revisionId, which is then used to save attachments
 | ||||||
| 
 | 
 | ||||||
|             if (this.type === 'text') { |             for (const noteAttachment of this.getAttachments()) { | ||||||
|                 for (const noteAttachment of this.getAttachments()) { |                 const revisionAttachment = noteAttachment.copy(); | ||||||
|                     if (noteAttachment.utcDateScheduledForErasureSince) { |                 revisionAttachment.ownerId = revision.revisionId; | ||||||
|                         continue; |                 revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true}); | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     const revisionAttachment = noteAttachment.copy(); |  | ||||||
|                     revisionAttachment.ownerId = revision.revisionId; |  | ||||||
|                     revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true}); |  | ||||||
| 
 | 
 | ||||||
|  |                 if (this.type === 'text') { | ||||||
|                     // content is rewritten to point to the revision attachments
 |                     // content is rewritten to point to the revision attachments
 | ||||||
|                     noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`, |                     noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`, | ||||||
|                         `attachments/${revisionAttachment.attachmentId}`); |                         `attachments/${revisionAttachment.attachmentId}`); | ||||||
|  | |||||||
| @ -86,6 +86,29 @@ class BRevision extends AbstractBeccaEntity { | |||||||
|         return this._getContent(); |         return this._getContent(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @returns {*} | ||||||
|  |      * @throws Error in case of invalid JSON */ | ||||||
|  |     getJsonContent() { | ||||||
|  |         const content = this.getContent(); | ||||||
|  | 
 | ||||||
|  |         if (!content || !content.trim()) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return JSON.parse(content); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** @returns {*|null} valid object or null if the content cannot be parsed as JSON */ | ||||||
|  |     getJsonContentSafely() { | ||||||
|  |         try { | ||||||
|  |             return this.getJsonContent(); | ||||||
|  |         } | ||||||
|  |         catch (e) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * @param content |      * @param content | ||||||
|      * @param {object} [opts] |      * @param {object} [opts] | ||||||
| @ -105,6 +128,45 @@ class BRevision extends AbstractBeccaEntity { | |||||||
|             .map(row => new BAttachment(row)); |             .map(row => new BAttachment(row)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** @returns {BAttachment|null} */ | ||||||
|  |     getAttachmentById(attachmentId, opts = {}) { | ||||||
|  |         opts.includeContentLength = !!opts.includeContentLength; | ||||||
|  | 
 | ||||||
|  |         const query = opts.includeContentLength | ||||||
|  |             ? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
 | ||||||
|  |                FROM attachments  | ||||||
|  |                JOIN blobs USING (blobId)  | ||||||
|  |                WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0` | ||||||
|  |             : `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`; | ||||||
|  | 
 | ||||||
|  |         return sql.getRows(query, [this.revisionId, attachmentId]) | ||||||
|  |             .map(row => new BAttachment(row))[0]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** @returns {BAttachment[]} */ | ||||||
|  |     getAttachmentsByRole(role) { | ||||||
|  |         return sql.getRows(` | ||||||
|  |                 SELECT attachments.* | ||||||
|  |                 FROM attachments  | ||||||
|  |                 WHERE ownerId = ?  | ||||||
|  |                   AND role = ? | ||||||
|  |                   AND isDeleted = 0 | ||||||
|  |                 ORDER BY position`, [this.revisionId, role])
 | ||||||
|  |             .map(row => new BAttachment(row)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** @returns {BAttachment} */ | ||||||
|  |     getAttachmentByTitle(title) { | ||||||
|  |         return sql.getRows(` | ||||||
|  |                 SELECT attachments.* | ||||||
|  |                 FROM attachments  | ||||||
|  |                 WHERE ownerId = ?  | ||||||
|  |                   AND title = ? | ||||||
|  |                   AND isDeleted = 0 | ||||||
|  |                 ORDER BY position`, [this.revisionId, title])
 | ||||||
|  |             .map(row => new BAttachment(row))[0]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     beforeSaving() { |     beforeSaving() { | ||||||
|         super.beforeSaving(); |         super.beforeSaving(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -274,26 +274,11 @@ 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, ""); | ||||||
|              * FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com |  | ||||||
|              *        REMOVE external dependency!!!! This is defined in the svg in defs.style |  | ||||||
|              */ |  | ||||||
|             const content = fullRevision.content; |  | ||||||
| 
 | 
 | ||||||
|             try { |             this.$content.html($("<img>") | ||||||
|                 const data = JSON.parse(content) |                 .attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`) | ||||||
|                 const svg = data.svg || "no svg present." |                 .css("max-width", "100%")); | ||||||
| 
 |  | ||||||
|                 /** |  | ||||||
|                  * maxWidth: 100% use full width of container but do not enlarge! |  | ||||||
|                  * height:auto to ensure that height scales with width |  | ||||||
|                  */ |  | ||||||
|                 const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"}); |  | ||||||
|                 this.$content.html($('<div>').append($svgHtml)); |  | ||||||
|             } catch (err) { |  | ||||||
|                 console.error("error parsing fullRevision.content as JSON", fullRevision.content, err); |  | ||||||
|                 this.$content.html($("<div>").text("Error parsing content. Please check console.error() for more details.")); |  | ||||||
|             } |  | ||||||
|         } else { |         } else { | ||||||
|             this.$content.text("Preview isn't available for this note type."); |             this.$content.text("Preview isn't available for this note type."); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -5,14 +5,27 @@ const becca = require('../../becca/becca'); | |||||||
| const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; | const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR; | ||||||
| const fs = require('fs'); | const fs = require('fs'); | ||||||
| 
 | 
 | ||||||
| function returnImage(req, res) { | function returnImageFromNote(req, res) { | ||||||
|     const image = becca.getNote(req.params.noteId); |     const image = becca.getNote(req.params.noteId); | ||||||
| 
 | 
 | ||||||
|  |     return returnImageInt(image, res); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function returnImageFromRevision(req, res) { | ||||||
|  |     const image = becca.getRevision(req.params.revisionId); | ||||||
|  | 
 | ||||||
|  |     return returnImageInt(image, res); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @param {BNote|BRevision} image | ||||||
|  |  * @param res | ||||||
|  |  */ | ||||||
|  | function returnImageInt(image, res) { | ||||||
|     if (!image) { |     if (!image) { | ||||||
|         res.set('Content-Type', 'image/png'); |         res.set('Content-Type', 'image/png'); | ||||||
|         return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`)); |         return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`)); | ||||||
|     } |     } else if (!["image", "canvas"].includes(image.type)) { | ||||||
|     else if (!["image", "canvas"].includes(image.type)){ |  | ||||||
|         return res.sendStatus(400); |         return res.sendStatus(400); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -24,6 +37,8 @@ function returnImage(req, res) { | |||||||
|         let svgString = '<svg/>' |         let svgString = '<svg/>' | ||||||
|         const attachment = image.getAttachmentByTitle('canvas-export.svg'); |         const attachment = image.getAttachmentByTitle('canvas-export.svg'); | ||||||
| 
 | 
 | ||||||
|  |         console.log(attachment); | ||||||
|  | 
 | ||||||
|         if (attachment) { |         if (attachment) { | ||||||
|             svgString = attachment.getContent(); |             svgString = attachment.getContent(); | ||||||
|         } else { |         } else { | ||||||
| @ -84,7 +99,8 @@ function updateImage(req) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     returnImage, |     returnImageFromNote, | ||||||
|  |     returnImageFromRevision, | ||||||
|     returnAttachedImage, |     returnAttachedImage, | ||||||
|     updateImage |     updateImage | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -181,6 +181,8 @@ function register(app) { | |||||||
|     apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob); |     apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob); | ||||||
|     apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision); |     apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision); | ||||||
|     apiRoute(PST, '/api/revisions/:revisionId/restore', revisionsApiRoute.restoreRevision); |     apiRoute(PST, '/api/revisions/:revisionId/restore', revisionsApiRoute.restoreRevision); | ||||||
|  |     route(GET, '/api/revisions/:revisionId/image/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImageFromRevision); | ||||||
|  | 
 | ||||||
|     route(GET, '/api/revisions/:revisionId/download', [auth.checkApiAuthOrElectron], revisionsApiRoute.downloadRevision); |     route(GET, '/api/revisions/:revisionId/download', [auth.checkApiAuthOrElectron], revisionsApiRoute.downloadRevision); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -200,7 +202,7 @@ function register(app) { | |||||||
|     apiRoute(GET, '/api/attribute-values/:attributeName', attributesRoute.getValuesForAttribute); |     apiRoute(GET, '/api/attribute-values/:attributeName', attributesRoute.getValuesForAttribute); | ||||||
| 
 | 
 | ||||||
|     // :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
 |     // :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
 | ||||||
|     route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage); |     route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImageFromNote); | ||||||
|     route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler); |     route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler); | ||||||
| 
 | 
 | ||||||
|     apiRoute(GET, '/api/options', optionsApiRoute.getOptions); |     apiRoute(GET, '/api/options', optionsApiRoute.getOptions); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam