mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	saving / viewing canvas revisions
This commit is contained in:
		| @@ -1607,16 +1607,12 @@ class BNote extends AbstractBeccaEntity { | ||||
|  | ||||
|             revision.save(); // to generate revisionId, which is then used to save attachments | ||||
|  | ||||
|             if (this.type === 'text') { | ||||
|             for (const noteAttachment of this.getAttachments()) { | ||||
|                     if (noteAttachment.utcDateScheduledForErasureSince) { | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                 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 | ||||
|                     noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`, | ||||
|                         `attachments/${revisionAttachment.attachmentId}`); | ||||
|   | ||||
| @@ -86,6 +86,29 @@ class BRevision extends AbstractBeccaEntity { | ||||
|         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 {object} [opts] | ||||
| @@ -105,6 +128,45 @@ class BRevision extends AbstractBeccaEntity { | ||||
|             .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() { | ||||
|         super.beforeSaving(); | ||||
|  | ||||
|   | ||||
| @@ -274,26 +274,11 @@ export default class RevisionsDialog extends BasicWidget { | ||||
|  | ||||
|             this.$content.html($table); | ||||
|         } else if (revisionItem.type === 'canvas') { | ||||
|             /** | ||||
|              * 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; | ||||
|             const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, ""); | ||||
|  | ||||
|             try { | ||||
|                 const data = JSON.parse(content) | ||||
|                 const svg = data.svg || "no svg present." | ||||
|  | ||||
|                 /** | ||||
|                  * 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.")); | ||||
|             } | ||||
|             this.$content.html($("<img>") | ||||
|                 .attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`) | ||||
|                 .css("max-width", "100%")); | ||||
|         } else { | ||||
|             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 fs = require('fs'); | ||||
|  | ||||
| function returnImage(req, res) { | ||||
| function returnImageFromNote(req, res) { | ||||
|     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) { | ||||
|         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"].includes(image.type)) { | ||||
|         return res.sendStatus(400); | ||||
|     } | ||||
|  | ||||
| @@ -24,6 +37,8 @@ function returnImage(req, res) { | ||||
|         let svgString = '<svg/>' | ||||
|         const attachment = image.getAttachmentByTitle('canvas-export.svg'); | ||||
|  | ||||
|         console.log(attachment); | ||||
|  | ||||
|         if (attachment) { | ||||
|             svgString = attachment.getContent(); | ||||
|         } else { | ||||
| @@ -84,7 +99,8 @@ function updateImage(req) { | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     returnImage, | ||||
|     returnImageFromNote, | ||||
|     returnImageFromRevision, | ||||
|     returnAttachedImage, | ||||
|     updateImage | ||||
| }; | ||||
|   | ||||
| @@ -181,6 +181,8 @@ function register(app) { | ||||
|     apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob); | ||||
|     apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision); | ||||
|     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); | ||||
|  | ||||
|  | ||||
| @@ -200,7 +202,7 @@ function register(app) { | ||||
|     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 | ||||
|     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); | ||||
|  | ||||
|     apiRoute(GET, '/api/options', optionsApiRoute.getOptions); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user