From f37dc6607407ed087baf8b662171863581c7d288 Mon Sep 17 00:00:00 2001 From: zadam Date: Fri, 8 Sep 2023 21:53:57 +0200 Subject: [PATCH 1/4] add support for storing canvas libraries in note attachments plus storing exported SVG in attachment --- .eslintrc.js | 1 - package-lock.json | 1 - src/becca/entities/bnote.js | 28 +++++- src/public/app/entities/fblob.js | 21 ++++ src/public/app/entities/fnote.js | 6 ++ src/public/app/services/content_renderer.js | 29 +----- .../app/widgets/buttons/note_actions.js | 2 +- src/public/app/widgets/note_detail.js | 4 +- .../widgets/type_widgets/attachment_list.js | 4 +- src/public/app/widgets/type_widgets/canvas.js | 87 +++++++++++----- src/routes/api/image.js | 27 ++--- src/routes/api/notes.js | 4 +- src/services/notes.js | 19 +++- src/services/utils.js | 11 +++ src/share/canvas_share.js | 99 ------------------- src/share/content_renderer.js | 37 +------ src/share/routes.js | 29 +++--- src/share/shaca/entities/snote.js | 5 + 18 files changed, 195 insertions(+), 219 deletions(-) delete mode 100644 src/share/canvas_share.js diff --git a/.eslintrc.js b/.eslintrc.js index b4c5e067c..03ed3439b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -75,7 +75,6 @@ module.exports = { glob: true, log: true, EditorWatchdog: true, - // \src\share\canvas_share.js React: true, appState: true, ExcalidrawLib: true, diff --git a/package-lock.json b/package-lock.json index 2b340a013..a80b6d6f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "trilium", "version": "0.61.6-beta", "hasInstallScript": true, "license": "AGPL-3.0-only", diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 65ac7126a..9f344279d 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -211,7 +211,9 @@ class BNote extends AbstractBeccaEntity { return this._getContent(); } - /** @returns {*} */ + /** + * @returns {*} + * @throws Error in case of invalid JSON */ getJsonContent() { const content = this.getContent(); @@ -222,6 +224,16 @@ class BNote extends AbstractBeccaEntity { 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] @@ -1125,7 +1137,7 @@ class BNote extends AbstractBeccaEntity { } /** @returns {BAttachment[]} */ - getAttachmentByRole(role) { + getAttachmentsByRole(role) { return sql.getRows(` SELECT attachments.* FROM attachments @@ -1136,6 +1148,18 @@ class BNote extends AbstractBeccaEntity { .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.noteId, title]) + .map(row => new BAttachment(row))[0]; + } + /** * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) * diff --git a/src/public/app/entities/fblob.js b/src/public/app/entities/fblob.js index 865afd53c..e335d7cb8 100644 --- a/src/public/app/entities/fblob.js +++ b/src/public/app/entities/fblob.js @@ -15,4 +15,25 @@ export default class FBlob { /** @type {string} */ this.utcDateModified = row.utcDateModified; } + + /** + * @returns {*} + * @throws Error in case of invalid JSON */ + getJsonContent() { + if (!this.content || !this.content.trim()) { + return null; + } + + return JSON.parse(this.content); + } + + /** @returns {*|null} valid object or null if the content cannot be parsed as JSON */ + getJsonContentSafely() { + try { + return this.getJsonContent(); + } + catch (e) { + return null; + } + } } diff --git a/src/public/app/entities/fnote.js b/src/public/app/entities/fnote.js index f7c1768c9..c6c4bc1f5 100644 --- a/src/public/app/entities/fnote.js +++ b/src/public/app/entities/fnote.js @@ -236,6 +236,12 @@ class FNote { return this.attachments; } + /** @returns {Promise} */ + async getAttachmentsByRole(role) { + return (await this.getAttachments()) + .filter(attachment => attachment.role === role); + } + /** @returns {Promise} */ async getAttachmentById(attachmentId) { const attachments = await this.getAttachments(); diff --git a/src/public/app/services/content_renderer.js b/src/public/app/services/content_renderer.js index 87871169c..6f66adfb0 100644 --- a/src/public/app/services/content_renderer.js +++ b/src/public/app/services/content_renderer.js @@ -33,7 +33,7 @@ async function getRenderedContent(entity, options = {}) { else if (type === 'code') { await renderCode(entity, $renderedContent); } - else if (type === 'image') { + else if (type === 'image' || type === 'canvas') { renderImage(entity, $renderedContent, options); } else if (!options.tooltip && ['file', 'pdf', 'audio', 'video'].includes(type)) { @@ -49,9 +49,6 @@ async function getRenderedContent(entity, options = {}) { $renderedContent.append($content); } - else if (type === 'canvas') { - await renderCanvas(entity, $renderedContent); - } else if (!options.tooltip && type === 'protectedSession') { const $button = $(``) .on('click', protectedSessionService.enterProtectedSession); @@ -125,7 +122,7 @@ function renderImage(entity, $renderedContent, options = {}) { let url; if (entity instanceof FNote) { - url = `api/images/${entity.noteId}/${sanitizedTitle}?${entity.utcDateModified}`; + url = `api/images/${entity.noteId}/${sanitizedTitle}?${Math.random()}`; } else if (entity instanceof FAttachment) { url = `api/attachments/${entity.attachmentId}/image/${sanitizedTitle}?${entity.utcDateModified}">`; } @@ -236,28 +233,6 @@ async function renderMermaid(note, $renderedContent) { } } -async function renderCanvas(note, $renderedContent) { - // make sure surrounding container has size of what is visible. Then image is shrinked to its boundaries - $renderedContent.css({height: "100%", width: "100%"}); - - const blob = await note.getBlob(); - const content = blob.content || ""; - - try { - const placeHolderSVG = ""; - const data = JSON.parse(content) - const svg = data.svg || placeHolderSVG; - /** - * maxWidth: size down to 100% (full) width of container but do not enlarge! - * height:auto to ensure that height scales with width - */ - $renderedContent.append($(svg).css({maxWidth: "100%", maxHeight: "100%", height: "auto", width: "auto"})); - } catch (err) { - console.error("error parsing content as JSON", content, err); - $renderedContent.append($("
").text("Error parsing content. Please check console.error() for more details.")); - } -} - /** * @param {jQuery} $renderedContent * @param {FNote} note diff --git a/src/public/app/widgets/buttons/note_actions.js b/src/public/app/widgets/buttons/note_actions.js index a985261ae..4d48cff41 100644 --- a/src/public/app/widgets/buttons/note_actions.js +++ b/src/public/app/widgets/buttons/note_actions.js @@ -98,7 +98,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { this.toggleDisabled(this.$findInTextButton, ['text', 'code', 'book'].includes(note.type)); - this.toggleDisabled(this.$showSourceButton, ['text', 'relationMap', 'mermaid'].includes(note.type)); + this.toggleDisabled(this.$showSourceButton, ['text', 'code', 'relationMap', 'mermaid', 'canvas'].includes(note.type)); this.toggleDisabled(this.$printActiveNoteButton, ['text', 'code'].includes(note.type)); diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js index 3b0fdbbf7..9d535aed0 100644 --- a/src/public/app/widgets/note_detail.js +++ b/src/public/app/widgets/note_detail.js @@ -86,6 +86,8 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { protectedSessionHolder.touchProtectedSessionIfNecessary(note); await server.put(`notes/${noteId}/data`, data, this.componentId); + + this.getTypeWidget().dataSaved?.(); }); appContext.addBeforeUnloadListener(this); @@ -167,7 +169,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { let type = note.type; const viewScope = this.noteContext.viewScope; - if (type === 'text' && viewScope.viewMode === 'source') { + if (viewScope.viewMode === 'source') { type = 'readOnlyCode'; } else if (viewScope.viewMode === 'attachments') { type = viewScope.attachmentId ? 'attachmentDetail' : 'attachmentList'; diff --git a/src/public/app/widgets/type_widgets/attachment_list.js b/src/public/app/widgets/type_widgets/attachment_list.js index 6b7ea0259..8f6aa9f9e 100644 --- a/src/public/app/widgets/type_widgets/attachment_list.js +++ b/src/public/app/widgets/type_widgets/attachment_list.js @@ -42,10 +42,12 @@ export default class AttachmentListTypeWidget extends TypeWidget { const $helpButton = $(''); utils.initHelpButtons($helpButton); + const noteLink = await linkService.createLink(this.noteId); // do separately to avoid race condition between empty() and .append() + this.$linksWrapper.empty().append( $('
').append( "Owning note: ", - await linkService.createLink(this.noteId), + noteLink, ), $('
').append( $('