mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 04:51:31 +08:00 
			
		
		
		
	wip attachment widget
This commit is contained in:
		
							parent
							
								
									53aebf1448
								
							
						
					
					
						commit
						2bc78ccafb
					
				| @ -35,13 +35,12 @@ CREATE TABLE IF NOT EXISTS "notes" ( | |||||||
|                                        `isProtected`	INT NOT NULL DEFAULT 0, |                                        `isProtected`	INT NOT NULL DEFAULT 0, | ||||||
|                                        `type` TEXT NOT NULL DEFAULT 'text', |                                        `type` TEXT NOT NULL DEFAULT 'text', | ||||||
|                                        `mime` TEXT NOT NULL DEFAULT 'text/html', |                                        `mime` TEXT NOT NULL DEFAULT 'text/html', | ||||||
|                                        `blobId` TEXT DEFAULT NULL, |  | ||||||
|                                        `isDeleted`	INT NOT NULL DEFAULT 0, |                                        `isDeleted`	INT NOT NULL DEFAULT 0, | ||||||
|                                        `deleteId`   TEXT DEFAULT NULL, |                                        `deleteId`   TEXT DEFAULT NULL, | ||||||
|                                        `dateCreated`	TEXT NOT NULL, |                                        `dateCreated`	TEXT NOT NULL, | ||||||
|                                        `dateModified`	TEXT NOT NULL, |                                        `dateModified`	TEXT NOT NULL, | ||||||
|                                        `utcDateCreated`	TEXT NOT NULL, |                                        `utcDateCreated`	TEXT NOT NULL, | ||||||
|                                        `utcDateModified`	TEXT NOT NULL |                                        `utcDateModified`	TEXT NOT NULL, blobId TEXT DEFAULT NULL, | ||||||
|                                        PRIMARY KEY(`noteId`)); |                                        PRIMARY KEY(`noteId`)); | ||||||
| CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId`	TEXT NOT NULL PRIMARY KEY, | CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId`	TEXT NOT NULL PRIMARY KEY, | ||||||
|                                              `noteId`	TEXT NOT NULL, |                                              `noteId`	TEXT NOT NULL, | ||||||
| @ -49,12 +48,11 @@ CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId`	TEXT NOT NULL PRIM | |||||||
|                                              mime TEXT DEFAULT '' NOT NULL, |                                              mime TEXT DEFAULT '' NOT NULL, | ||||||
|                                              `title`	TEXT NOT NULL, |                                              `title`	TEXT NOT NULL, | ||||||
|                                              `isProtected`	INT NOT NULL DEFAULT 0, |                                              `isProtected`	INT NOT NULL DEFAULT 0, | ||||||
|                                              `blobId` TEXT DEFAULT NULL, |  | ||||||
|                                              `utcDateLastEdited` TEXT NOT NULL, |                                              `utcDateLastEdited` TEXT NOT NULL, | ||||||
|                                              `utcDateCreated` TEXT NOT NULL, |                                              `utcDateCreated` TEXT NOT NULL, | ||||||
|                                              `utcDateModified` TEXT NOT NULL, |                                              `utcDateModified` TEXT NOT NULL, | ||||||
|                                              `dateLastEdited` TEXT NOT NULL, |                                              `dateLastEdited` TEXT NOT NULL, | ||||||
|                                              `dateCreated` TEXT NOT NULL); |                                              `dateCreated` TEXT NOT NULL, blobId TEXT DEFAULT NULL); | ||||||
| CREATE TABLE IF NOT EXISTS "options" | CREATE TABLE IF NOT EXISTS "options" | ||||||
| ( | ( | ||||||
|     name TEXT not null PRIMARY KEY, |     name TEXT not null PRIMARY KEY, | ||||||
| @ -104,6 +102,13 @@ CREATE TABLE IF NOT EXISTS "recent_notes" | |||||||
|     notePath TEXT not null, |     notePath TEXT not null, | ||||||
|     utcDateCreated TEXT not null |     utcDateCreated TEXT not null | ||||||
| ); | ); | ||||||
|  | CREATE TABLE IF NOT EXISTS "blobs" ( | ||||||
|  |                                                `blobId`	TEXT NOT NULL, | ||||||
|  |                                                `content`	TEXT NULL DEFAULT NULL, | ||||||
|  |                                                `dateModified` TEXT NOT NULL, | ||||||
|  |                                                `utcDateModified` TEXT NOT NULL, | ||||||
|  |                                                PRIMARY KEY(`blobId`) | ||||||
|  | ); | ||||||
| CREATE TABLE IF NOT EXISTS "attachments" | CREATE TABLE IF NOT EXISTS "attachments" | ||||||
| ( | ( | ||||||
|     attachmentId      TEXT not null primary key, |     attachmentId      TEXT not null primary key, | ||||||
|  | |||||||
| @ -123,7 +123,7 @@ class Becca { | |||||||
| 
 | 
 | ||||||
|     /** @returns {BAttachment|null} */ |     /** @returns {BAttachment|null} */ | ||||||
|     getAttachment(attachmentId) { |     getAttachment(attachmentId) { | ||||||
|         const row = sql.getRow("SELECT * FROM attachments WHERE attachmentId = ?", [attachmentId]); |         const row = sql.getRow("SELECT * FROM attachments WHERE attachmentId = ? AND isDeleted = 0", [attachmentId]); | ||||||
| 
 | 
 | ||||||
|         const BAttachment = require("./entities/battachment"); // avoiding circular dependency problems
 |         const BAttachment = require("./entities/battachment"); // avoiding circular dependency problems
 | ||||||
|         return row ? new BAttachment(row) : null; |         return row ? new BAttachment(row) : null; | ||||||
|  | |||||||
| @ -1352,7 +1352,6 @@ class BNote extends AbstractBeccaEntity { | |||||||
|      * |      * | ||||||
|      * @returns {BAttachment|null} - null if note is not eligible for conversion |      * @returns {BAttachment|null} - null if note is not eligible for conversion | ||||||
|      */ |      */ | ||||||
| 
 |  | ||||||
|     convertToParentAttachment(opts = {force: false}) { |     convertToParentAttachment(opts = {force: false}) { | ||||||
|         if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) { |         if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) { | ||||||
|             return null; |             return null; | ||||||
| @ -1394,6 +1393,61 @@ class BNote extends AbstractBeccaEntity { | |||||||
|         return attachment; |         return attachment; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @param attachmentId | ||||||
|  |      * @returns {{note: BNote, branch: BBranch}} | ||||||
|  |      */ | ||||||
|  |     convertAttachmentToChildNote(attachmentId) { | ||||||
|  |         if (this.type === 'search') { | ||||||
|  |             throw new Error(`Note of type search cannot have child notes`); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const attachment = this.getAttachmentById(attachmentId); | ||||||
|  | 
 | ||||||
|  |         if (!attachment) { | ||||||
|  |             throw new NotFoundError(`Attachment '${attachmentId} of note '${this.noteId}' doesn't exist.`); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const attachmentRoleToNoteTypeMapping = { | ||||||
|  |             'image': 'image' | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         if (!(attachment.role in attachmentRoleToNoteTypeMapping)) { | ||||||
|  |             throw new Error(`Mapping from attachment role '${attachment.role}' to note's type is not defined`); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!this.isContentAvailable()) { // isProtected is same for attachment
 | ||||||
|  |             throw new Error(`Cannot convert protected attachment outside of protected session`); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         const noteService = require('../../services/notes'); | ||||||
|  | 
 | ||||||
|  |         const {note, branch} = noteService.createNewNote({ | ||||||
|  |             parentNoteId: this.noteId, | ||||||
|  |             title: attachment.title, | ||||||
|  |             type: attachmentRoleToNoteTypeMapping[attachment.role], | ||||||
|  |             mime: attachment.mime, | ||||||
|  |             content: attachment.getContent(), | ||||||
|  |             isProtected: this.isProtected | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         attachment.markAsDeleted(); | ||||||
|  | 
 | ||||||
|  |         if (attachment.role === 'image' && this.type === 'text') { | ||||||
|  |             const origContent = this.getContent(); | ||||||
|  |             const oldAttachmentUrl = `api/notes/${this.noteId}/images/${attachment.attachmentId}/`; | ||||||
|  |             const newNoteUrl = `api/images/${note.noteId}/`; | ||||||
|  | 
 | ||||||
|  |             const fixedContent = utils.replaceAll(origContent, oldAttachmentUrl, newNoteUrl); | ||||||
|  | 
 | ||||||
|  |             if (origContent !== fixedContent) { | ||||||
|  |                 this.setContent(fixedContent); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return { note, branch }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * (Soft) delete a note and all its descendants. |      * (Soft) delete a note and all its descendants. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -33,8 +33,9 @@ async function processEntityChanges(entityChanges) { | |||||||
|                 options.set(ec.entity.name, ec.entity.value); |                 options.set(ec.entity.name, ec.entity.value); | ||||||
| 
 | 
 | ||||||
|                 loadResults.addOption(ec.entity.name); |                 loadResults.addOption(ec.entity.name); | ||||||
|             } |             } else if (ec.entityName === 'attachments') { | ||||||
|             else if (['etapi_tokens', 'attachments'].includes(ec.entityName)) { |                 loadResults.addAttachment(ec.entity); | ||||||
|  |             } else if (ec.entityName === 'etapi_tokens') { | ||||||
|                 // NOOP
 |                 // NOOP
 | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|  | |||||||
| @ -23,6 +23,8 @@ export default class LoadResults { | |||||||
|         this.contentNoteIdToComponentId = []; |         this.contentNoteIdToComponentId = []; | ||||||
| 
 | 
 | ||||||
|         this.options = []; |         this.options = []; | ||||||
|  | 
 | ||||||
|  |         this.attachments = []; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getEntity(entityName, entityId) { |     getEntity(entityName, entityId) { | ||||||
| @ -116,6 +118,14 @@ export default class LoadResults { | |||||||
|         return this.options.includes(name); |         return this.options.includes(name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     addAttachment(attachment) { | ||||||
|  |         this.attachments.push(attachment); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     getAttachments() { | ||||||
|  |         return this.attachments; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * @returns {boolean} true if there are changes which could affect the attributes (including inherited ones) |      * @returns {boolean} true if there are changes which could affect the attributes (including inherited ones) | ||||||
|      *          notably changes in note itself should not have any effect on attributes |      *          notably changes in note itself should not have any effect on attributes | ||||||
| @ -132,7 +142,8 @@ export default class LoadResults { | |||||||
|             && this.noteReorderings.length === 0 |             && this.noteReorderings.length === 0 | ||||||
|             && this.noteRevisions.length === 0 |             && this.noteRevisions.length === 0 | ||||||
|             && this.contentNoteIdToComponentId.length === 0 |             && this.contentNoteIdToComponentId.length === 0 | ||||||
|             && this.options.length === 0; |             && this.options.length === 0 | ||||||
|  |             && this.attachments.length === 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     isEmptyForTree() { |     isEmptyForTree() { | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import utils from "../../services/utils.js"; | import utils from "../services/utils.js"; | ||||||
| import AttachmentActionsWidget from "../buttons/attachments_actions.js"; | import AttachmentActionsWidget from "./buttons/attachments_actions.js"; | ||||||
| import BasicWidget from "./basic_widget.js"; | import BasicWidget from "./basic_widget.js"; | ||||||
|  | import server from "../services/server.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="attachment-detail"> | <div class="attachment-detail"> | ||||||
| @ -38,7 +39,7 @@ const TPL = ` | |||||||
|         <div class="attachment-title-line"> |         <div class="attachment-title-line"> | ||||||
|             <h4 class="attachment-title"></h4>                 |             <h4 class="attachment-title"></h4>                 | ||||||
|             <div class="attachment-details"></div> |             <div class="attachment-details"></div> | ||||||
|             <div style="flex: 1 1;"> |             <div style="flex: 1 1;"></div> | ||||||
|             <div class="attachment-actions-container"></div> |             <div class="attachment-actions-container"></div> | ||||||
|         </div> |         </div> | ||||||
|          |          | ||||||
| @ -50,6 +51,7 @@ export default class AttachmentDetailWidget extends BasicWidget { | |||||||
|     constructor(attachment) { |     constructor(attachment) { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|  |         this.contentSized(); | ||||||
|         this.attachment = attachment; |         this.attachment = attachment; | ||||||
|         this.attachmentActionsWidget = new AttachmentActionsWidget(attachment); |         this.attachmentActionsWidget = new AttachmentActionsWidget(attachment); | ||||||
|         this.child(this.attachmentActionsWidget); |         this.child(this.attachmentActionsWidget); | ||||||
| @ -57,14 +59,25 @@ export default class AttachmentDetailWidget extends BasicWidget { | |||||||
| 
 | 
 | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|  |         this.refresh(); | ||||||
|  | 
 | ||||||
|  |         super.doRender(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     refresh() { | ||||||
|  |         this.$widget.find('.attachment-detail-wrapper') | ||||||
|  |             .empty() | ||||||
|  |             .append( | ||||||
|  |                 $(TPL) | ||||||
|  |                     .find('.attachment-detail-wrapper') | ||||||
|  |                     .html() | ||||||
|  |             ); | ||||||
|         this.$wrapper = this.$widget.find('.attachment-detail-wrapper'); |         this.$wrapper = this.$widget.find('.attachment-detail-wrapper'); | ||||||
|         this.$wrapper.find('.attachment-title').text(this.attachment.title); |         this.$wrapper.find('.attachment-title').text(this.attachment.title); | ||||||
|         this.$wrapper.find('.attachment-details') |         this.$wrapper.find('.attachment-details') | ||||||
|             .text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`); |             .text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`); | ||||||
|         this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render()); |         this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render()); | ||||||
|         this.$wrapper.find('.attachment-content').append(this.renderContent()); |         this.$wrapper.find('.attachment-content').append(this.renderContent()); | ||||||
| 
 |  | ||||||
|         super.doRender(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     renderContent() { |     renderContent() { | ||||||
| @ -76,4 +89,20 @@ export default class AttachmentDetailWidget extends BasicWidget { | |||||||
|             return ''; |             return ''; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     async entitiesReloadedEvent({loadResults}) { | ||||||
|  |         console.log("AttachmentDetailWidget: entitiesReloadedEvent"); | ||||||
|  | 
 | ||||||
|  |         const attachmentChange = loadResults.getAttachments().find(att => att.attachmentId === this.attachment.attachmentId); | ||||||
|  | 
 | ||||||
|  |         if (attachmentChange) { | ||||||
|  |             if (attachmentChange.isDeleted) { | ||||||
|  |                 this.toggleInt(false); | ||||||
|  |             } else { | ||||||
|  |                 this.attachment = await server.get(`notes/${this.attachment.parentId}/attachments/${this.attachment.attachmentId}?includeContent=true`); | ||||||
|  | 
 | ||||||
|  |                 this.refresh(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,9 @@ | |||||||
| import BasicWidget from "../basic_widget.js"; | import BasicWidget from "../basic_widget.js"; | ||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
| import dialogService from "../../services/dialog.js"; | import dialogService from "../../services/dialog.js"; | ||||||
|  | import toastService from "../../services/toast.js"; | ||||||
|  | import ws from "../../services/ws.js"; | ||||||
|  | import appContext from "../../components/app_context.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="dropdown attachment-actions"> | <div class="dropdown attachment-actions"> | ||||||
| @ -11,7 +14,7 @@ const TPL = ` | |||||||
|     } |     } | ||||||
|      |      | ||||||
|     .attachment-actions .dropdown-menu { |     .attachment-actions .dropdown-menu { | ||||||
|         width: 15em; |         width: 20em; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     .attachment-actions .dropdown-item[disabled], .attachment-actions .dropdown-item[disabled]:hover { |     .attachment-actions .dropdown-item[disabled], .attachment-actions .dropdown-item[disabled]:hover { | ||||||
| @ -25,9 +28,9 @@ const TPL = ` | |||||||
|         aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"></button> |         aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"></button> | ||||||
| 
 | 
 | ||||||
|     <div class="dropdown-menu dropdown-menu-right"> |     <div class="dropdown-menu dropdown-menu-right"> | ||||||
|         <a data-trigger-command="deleteAttachment" class="dropdown-item delete-attachment-button">Delete attachment</a> |         <a data-trigger-command="deleteAttachment" class="dropdown-item">Delete attachment</a> | ||||||
|         <a data-trigger-command="pullAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Pull attachment into note</a> |         <a data-trigger-command="convertAttachmentIntoNote" class="dropdown-item">Convert attachment into note</a> | ||||||
|         <a data-trigger-command="pullAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Copy into clipboard</a> |         <a data-trigger-command="convertAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Copy into clipboard</a> | ||||||
|     </div> |     </div> | ||||||
| </div>`; | </div>`; | ||||||
| 
 | 
 | ||||||
| @ -46,6 +49,20 @@ export default class AttachmentActionsWidget extends BasicWidget { | |||||||
|     async deleteAttachmentCommand() { |     async deleteAttachmentCommand() { | ||||||
|         if (await dialogService.confirm(`Are you sure you want to delete attachment '${this.attachment.title}'?`)) { |         if (await dialogService.confirm(`Are you sure you want to delete attachment '${this.attachment.title}'?`)) { | ||||||
|             await server.remove(`notes/${this.attachment.parentId}/attachments/${this.attachment.attachmentId}`); |             await server.remove(`notes/${this.attachment.parentId}/attachments/${this.attachment.attachmentId}`); | ||||||
|  | 
 | ||||||
|  |             toastService.showMessage(`Attachment '${this.attachment.title}' has been deleted.`); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async convertAttachmentIntoNoteCommand() { | ||||||
|  |         if (await dialogService.confirm(`Are you sure you want to convert attachment '${this.attachment.title}' into a separate note?`)) { | ||||||
|  |             const {note: newNote} = await server.post(`notes/${this.attachment.parentId}/attachments/${this.attachment.attachmentId}/convert-to-note`) | ||||||
|  | 
 | ||||||
|  |             toastService.showMessage(`Attachment '${this.attachment.title}' has been converted to note.`); | ||||||
|  | 
 | ||||||
|  |             await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  | 
 | ||||||
|  |             await appContext.tabManager.getActiveContext().setNote(newNote.noteId); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,5 @@ | |||||||
| import TypeWidget from "./type_widget.js"; | import TypeWidget from "./type_widget.js"; | ||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
| import utils from "../../services/utils.js"; |  | ||||||
| import AttachmentActionsWidget from "../buttons/attachments_actions.js"; |  | ||||||
| import AttachmentDetailWidget from "../attachment_detail.js"; | import AttachmentDetailWidget from "../attachment_detail.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| @ -16,7 +14,9 @@ const TPL = ` | |||||||
| </div>`; | </div>`; | ||||||
| 
 | 
 | ||||||
| export default class AttachmentsTypeWidget extends TypeWidget { | export default class AttachmentsTypeWidget extends TypeWidget { | ||||||
|     static getType() { return "attachments"; } |     static getType() { | ||||||
|  |         return "attachments"; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
| @ -28,6 +28,7 @@ export default class AttachmentsTypeWidget extends TypeWidget { | |||||||
|     async doRefresh(note) { |     async doRefresh(note) { | ||||||
|         this.$list.empty(); |         this.$list.empty(); | ||||||
|         this.children = []; |         this.children = []; | ||||||
|  |         this.renderedAttachmentIds = new Set(); | ||||||
| 
 | 
 | ||||||
|         const attachments = await server.get(`notes/${this.noteId}/attachments?includeContent=true`); |         const attachments = await server.get(`notes/${this.noteId}/attachments?includeContent=true`); | ||||||
| 
 | 
 | ||||||
| @ -41,7 +42,19 @@ export default class AttachmentsTypeWidget extends TypeWidget { | |||||||
|             const attachmentDetailWidget = new AttachmentDetailWidget(attachment); |             const attachmentDetailWidget = new AttachmentDetailWidget(attachment); | ||||||
|             this.child(attachmentDetailWidget); |             this.child(attachmentDetailWidget); | ||||||
| 
 | 
 | ||||||
|  |             this.renderedAttachmentIds.add(attachment.attachmentId); | ||||||
|  | 
 | ||||||
|             this.$list.append(attachmentDetailWidget.render()); |             this.$list.append(attachmentDetailWidget.render()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     async entitiesReloadedEvent({loadResults}) { | ||||||
|  |         // updates and deletions are handled by the detail, for new attachments the whole list has to be refreshed
 | ||||||
|  |         const attachmentsAdded = loadResults.getAttachments() | ||||||
|  |             .find(att => this.renderedAttachmentIds.has(att.attachmentId)); | ||||||
|  | 
 | ||||||
|  |         if (attachmentsAdded) { | ||||||
|  |             this.refresh(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										108
									
								
								src/routes/api/attachments.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/routes/api/attachments.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | |||||||
|  | const becca = require("../../becca/becca"); | ||||||
|  | const NotFoundError = require("../../errors/not_found_error"); | ||||||
|  | const utils = require("../../services/utils"); | ||||||
|  | const noteService = require("../../services/notes"); | ||||||
|  | 
 | ||||||
|  | function getAttachments(req) { | ||||||
|  |     const includeContent = req.query.includeContent === 'true'; | ||||||
|  |     const {noteId} = req.params; | ||||||
|  | 
 | ||||||
|  |     const note = becca.getNote(noteId); | ||||||
|  | 
 | ||||||
|  |     if (!note) { | ||||||
|  |         throw new NotFoundError(`Note '${noteId}' doesn't exist.`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return note.getAttachments() | ||||||
|  |         .map(attachment => processAttachment(attachment, includeContent)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function getAttachment(req) { | ||||||
|  |     const includeContent = req.query.includeContent === 'true'; | ||||||
|  |     const {noteId, attachmentId} = req.params; | ||||||
|  | 
 | ||||||
|  |     const note = becca.getNote(noteId); | ||||||
|  | 
 | ||||||
|  |     if (!note) { | ||||||
|  |         throw new NotFoundError(`Note '${noteId}' doesn't exist.`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const attachment = note.getAttachmentById(attachmentId); | ||||||
|  | 
 | ||||||
|  |     if (!attachment) { | ||||||
|  |         throw new NotFoundError(`Attachment '${attachmentId} of note '${noteId}' doesn't exist.`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return processAttachment(attachment, includeContent); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function processAttachment(attachment, includeContent) { | ||||||
|  |     const pojo = attachment.getPojo(); | ||||||
|  | 
 | ||||||
|  |     if (includeContent) { | ||||||
|  |         if (utils.isStringNote(null, attachment.mime)) { | ||||||
|  |             pojo.content = attachment.getContent()?.toString(); | ||||||
|  |             pojo.contentLength = pojo.content.length; | ||||||
|  | 
 | ||||||
|  |             const MAX_ATTACHMENT_LENGTH = 1_000_000; | ||||||
|  | 
 | ||||||
|  |             if (pojo.content.length > MAX_ATTACHMENT_LENGTH) { | ||||||
|  |                 pojo.content = pojo.content.substring(0, MAX_ATTACHMENT_LENGTH); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             const content = attachment.getContent(); | ||||||
|  |             pojo.contentLength = content?.length; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return pojo; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function saveAttachment(req) { | ||||||
|  |     const {noteId} = req.params; | ||||||
|  |     const {attachmentId, role, mime, title, content} = req.body; | ||||||
|  | 
 | ||||||
|  |     const note = becca.getNote(noteId); | ||||||
|  | 
 | ||||||
|  |     if (!note) { | ||||||
|  |         throw new NotFoundError(`Note '${noteId}' doesn't exist.`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     note.saveAttachment({attachmentId, role, mime, title, content}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function deleteAttachment(req) { | ||||||
|  |     const {noteId, attachmentId} = req.params; | ||||||
|  | 
 | ||||||
|  |     const note = becca.getNote(noteId); | ||||||
|  | 
 | ||||||
|  |     if (!note) { | ||||||
|  |         throw new NotFoundError(`Note '${noteId}' doesn't exist.`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const attachment = note.getAttachmentById(attachmentId); | ||||||
|  | 
 | ||||||
|  |     if (attachment) { | ||||||
|  |         attachment.markAsDeleted(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function convertAttachmentToNote(req) { | ||||||
|  |     const {noteId, attachmentId} = req.params; | ||||||
|  | 
 | ||||||
|  |     const note = becca.getNote(noteId); | ||||||
|  | 
 | ||||||
|  |     if (!note) { | ||||||
|  |         throw new NotFoundError(`Note '${noteId}' doesn't exist.`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return note.convertAttachmentToChildNote(attachmentId); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |     getAttachments, | ||||||
|  |     getAttachment, | ||||||
|  |     saveAttachment, | ||||||
|  |     deleteAttachment, | ||||||
|  |     convertAttachmentToNote | ||||||
|  | }; | ||||||
| @ -127,70 +127,6 @@ function setNoteTypeMime(req) { | |||||||
|     note.save(); |     note.save(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getAttachments(req) { |  | ||||||
|     const includeContent = req.query.includeContent === 'true'; |  | ||||||
|     const {noteId} = req.params; |  | ||||||
| 
 |  | ||||||
|     const note = becca.getNote(noteId); |  | ||||||
| 
 |  | ||||||
|     if (!note) { |  | ||||||
|         throw new NotFoundError(`Note '${noteId}' doesn't exist.`); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const attachments = note.getAttachments(); |  | ||||||
| 
 |  | ||||||
|     return attachments.map(attachment => { |  | ||||||
|        const pojo = attachment.getPojo(); |  | ||||||
| 
 |  | ||||||
|        if (includeContent) { |  | ||||||
|            if (utils.isStringNote(null, attachment.mime)) { |  | ||||||
|                pojo.content = attachment.getContent()?.toString(); |  | ||||||
|                pojo.contentLength = pojo.content.length; |  | ||||||
| 
 |  | ||||||
|                const MAX_ATTACHMENT_LENGTH = 1_000_000; |  | ||||||
| 
 |  | ||||||
|                if (pojo.content.length > MAX_ATTACHMENT_LENGTH) { |  | ||||||
|                    pojo.content = pojo.content.substring(0, MAX_ATTACHMENT_LENGTH); |  | ||||||
|                } |  | ||||||
|            } else { |  | ||||||
|                const content = attachment.getContent(); |  | ||||||
|                pojo.contentLength = content?.length; |  | ||||||
|            } |  | ||||||
|        } |  | ||||||
| 
 |  | ||||||
|        return pojo; |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function saveAttachment(req) { |  | ||||||
|     const {noteId} = req.params; |  | ||||||
|     const {attachmentId, role, mime, title, content} = req.body; |  | ||||||
| 
 |  | ||||||
|     const note = becca.getNote(noteId); |  | ||||||
| 
 |  | ||||||
|     if (!note) { |  | ||||||
|         throw new NotFoundError(`Note '${noteId}' doesn't exist.`); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     note.saveAttachment({attachmentId, role, mime, title, content}); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function deleteAttachment(req) { |  | ||||||
|     const {noteId, attachmentId} = req.params; |  | ||||||
| 
 |  | ||||||
|     const note = becca.getNote(noteId); |  | ||||||
| 
 |  | ||||||
|     if (!note) { |  | ||||||
|         throw new NotFoundError(`Note '${noteId}' doesn't exist.`); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const attachment = note.getAttachmentById(attachmentId); |  | ||||||
| 
 |  | ||||||
|     if (attachment) { |  | ||||||
|         attachment.markAsDeleted(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function getRelationMap(req) { | function getRelationMap(req) { | ||||||
|     const {relationMapNoteId, noteIds} = req.body; |     const {relationMapNoteId, noteIds} = req.body; | ||||||
| 
 | 
 | ||||||
| @ -404,8 +340,5 @@ module.exports = { | |||||||
|     eraseDeletedNotesNow, |     eraseDeletedNotesNow, | ||||||
|     getDeleteNotesPreview, |     getDeleteNotesPreview, | ||||||
|     uploadModifiedFile, |     uploadModifiedFile, | ||||||
|     forceSaveNoteRevision, |     forceSaveNoteRevision | ||||||
|     getAttachments, |  | ||||||
|     saveAttachment, |  | ||||||
|     deleteAttachment |  | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ const indexRoute = require('./index'); | |||||||
| const treeApiRoute = require('./api/tree'); | const treeApiRoute = require('./api/tree'); | ||||||
| const notesApiRoute = require('./api/notes'); | const notesApiRoute = require('./api/notes'); | ||||||
| const branchesApiRoute = require('./api/branches'); | const branchesApiRoute = require('./api/branches'); | ||||||
|  | const attachmentsApiRoute = require('./api/attachments'); | ||||||
| const autocompleteApiRoute = require('./api/autocomplete'); | const autocompleteApiRoute = require('./api/autocomplete'); | ||||||
| const cloningApiRoute = require('./api/cloning'); | const cloningApiRoute = require('./api/cloning'); | ||||||
| const noteRevisionsApiRoute = require('./api/note_revisions'); | const noteRevisionsApiRoute = require('./api/note_revisions'); | ||||||
| @ -126,9 +127,11 @@ function register(app) { | |||||||
|     apiRoute(PUT, '/api/notes/:noteId/sort-children', notesApiRoute.sortChildNotes); |     apiRoute(PUT, '/api/notes/:noteId/sort-children', notesApiRoute.sortChildNotes); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectNote); |     apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectNote); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/type', notesApiRoute.setNoteTypeMime); |     apiRoute(PUT, '/api/notes/:noteId/type', notesApiRoute.setNoteTypeMime); | ||||||
|     apiRoute(GET, '/api/notes/:noteId/attachments', notesApiRoute.getAttachments); |     apiRoute(GET, '/api/notes/:noteId/attachments', attachmentsApiRoute.getAttachments); | ||||||
|     apiRoute(POST, '/api/notes/:noteId/attachments', notesApiRoute.saveAttachment); |     apiRoute(GET, '/api/notes/:noteId/attachments/:attachmentId', attachmentsApiRoute.getAttachment); | ||||||
|     apiRoute(DELETE, '/api/notes/:noteId/attachments/:attachmentId', notesApiRoute.deleteAttachment); |     apiRoute(POST, '/api/notes/:noteId/attachments', attachmentsApiRoute.saveAttachment); | ||||||
|  |     apiRoute(POST, '/api/notes/:noteId/attachments/:attachmentId/convert-to-note', attachmentsApiRoute.convertAttachmentToNote); | ||||||
|  |     apiRoute(DELETE, '/api/notes/:noteId/attachments/:attachmentId', attachmentsApiRoute.deleteAttachment); | ||||||
|     apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions); |     apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions); | ||||||
|     apiRoute(DELETE, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.eraseAllNoteRevisions); |     apiRoute(DELETE, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.eraseAllNoteRevisions); | ||||||
|     apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision); |     apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision); | ||||||
|  | |||||||
| @ -137,6 +137,8 @@ function fillInAdditionalProperties(entityChange) { | |||||||
|         } |         } | ||||||
|     } else if (entityChange.entityName === 'blobs') { |     } else if (entityChange.entityName === 'blobs') { | ||||||
|         entityChange.noteIds = sql.getColumn("SELECT noteId FROM notes WHERE blobId = ? AND isDeleted = 0", [entityChange.entityId]); |         entityChange.noteIds = sql.getColumn("SELECT noteId FROM notes WHERE blobId = ? AND isDeleted = 0", [entityChange.entityId]); | ||||||
|  |     } else if (entityChange.entityName === 'attachments') { | ||||||
|  |         entityChange.entity = sql.getRow(`SELECT * FROM attachments WHERE attachmentId = ?`, [entityChange.entityId]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (entityChange.entity instanceof AbstractBeccaEntity) { |     if (entityChange.entity instanceof AbstractBeccaEntity) { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam