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, | ||||
|                                        `type` TEXT NOT NULL DEFAULT 'text', | ||||
|                                        `mime` TEXT NOT NULL DEFAULT 'text/html', | ||||
|                                        `blobId` TEXT DEFAULT NULL, | ||||
|                                        `isDeleted`	INT NOT NULL DEFAULT 0, | ||||
|                                        `deleteId`   TEXT DEFAULT NULL, | ||||
|                                        `dateCreated`	TEXT NOT NULL, | ||||
|                                        `dateModified`	TEXT NOT NULL, | ||||
|                                        `utcDateCreated`	TEXT NOT NULL, | ||||
|                                        `utcDateModified`	TEXT NOT NULL | ||||
|                                        `utcDateModified`	TEXT NOT NULL, blobId TEXT DEFAULT NULL, | ||||
|                                        PRIMARY KEY(`noteId`)); | ||||
| CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId`	TEXT NOT NULL PRIMARY KEY, | ||||
|                                              `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, | ||||
|                                              `title`	TEXT NOT NULL, | ||||
|                                              `isProtected`	INT NOT NULL DEFAULT 0, | ||||
|                                              `blobId` TEXT DEFAULT NULL, | ||||
|                                              `utcDateLastEdited` TEXT NOT NULL, | ||||
|                                              `utcDateCreated` TEXT NOT NULL, | ||||
|                                              `utcDateModified` 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" | ||||
| ( | ||||
|     name TEXT not null PRIMARY KEY, | ||||
| @ -104,6 +102,13 @@ CREATE TABLE IF NOT EXISTS "recent_notes" | ||||
|     notePath 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" | ||||
| ( | ||||
|     attachmentId      TEXT not null primary key, | ||||
|  | ||||
| @ -123,7 +123,7 @@ class Becca { | ||||
| 
 | ||||
|     /** @returns {BAttachment|null} */ | ||||
|     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
 | ||||
|         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 | ||||
|      */ | ||||
| 
 | ||||
|     convertToParentAttachment(opts = {force: false}) { | ||||
|         if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) { | ||||
|             return null; | ||||
| @ -1394,6 +1393,61 @@ class BNote extends AbstractBeccaEntity { | ||||
|         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. | ||||
|      * | ||||
|  | ||||
| @ -33,8 +33,9 @@ async function processEntityChanges(entityChanges) { | ||||
|                 options.set(ec.entity.name, ec.entity.value); | ||||
| 
 | ||||
|                 loadResults.addOption(ec.entity.name); | ||||
|             } | ||||
|             else if (['etapi_tokens', 'attachments'].includes(ec.entityName)) { | ||||
|             } else if (ec.entityName === 'attachments') { | ||||
|                 loadResults.addAttachment(ec.entity); | ||||
|             } else if (ec.entityName === 'etapi_tokens') { | ||||
|                 // NOOP
 | ||||
|             } | ||||
|             else { | ||||
|  | ||||
| @ -23,6 +23,8 @@ export default class LoadResults { | ||||
|         this.contentNoteIdToComponentId = []; | ||||
| 
 | ||||
|         this.options = []; | ||||
| 
 | ||||
|         this.attachments = []; | ||||
|     } | ||||
| 
 | ||||
|     getEntity(entityName, entityId) { | ||||
| @ -116,6 +118,14 @@ export default class LoadResults { | ||||
|         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) | ||||
|      *          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.noteRevisions.length === 0 | ||||
|             && this.contentNoteIdToComponentId.length === 0 | ||||
|             && this.options.length === 0; | ||||
|             && this.options.length === 0 | ||||
|             && this.attachments.length === 0; | ||||
|     } | ||||
| 
 | ||||
|     isEmptyForTree() { | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import utils from "../../services/utils.js"; | ||||
| import AttachmentActionsWidget from "../buttons/attachments_actions.js"; | ||||
| import utils from "../services/utils.js"; | ||||
| import AttachmentActionsWidget from "./buttons/attachments_actions.js"; | ||||
| import BasicWidget from "./basic_widget.js"; | ||||
| import server from "../services/server.js"; | ||||
| 
 | ||||
| const TPL = ` | ||||
| <div class="attachment-detail"> | ||||
| @ -38,7 +39,7 @@ const TPL = ` | ||||
|         <div class="attachment-title-line"> | ||||
|             <h4 class="attachment-title"></h4>                 | ||||
|             <div class="attachment-details"></div> | ||||
|             <div style="flex: 1 1;"> | ||||
|             <div style="flex: 1 1;"></div> | ||||
|             <div class="attachment-actions-container"></div> | ||||
|         </div> | ||||
|          | ||||
| @ -50,6 +51,7 @@ export default class AttachmentDetailWidget extends BasicWidget { | ||||
|     constructor(attachment) { | ||||
|         super(); | ||||
| 
 | ||||
|         this.contentSized(); | ||||
|         this.attachment = attachment; | ||||
|         this.attachmentActionsWidget = new AttachmentActionsWidget(attachment); | ||||
|         this.child(this.attachmentActionsWidget); | ||||
| @ -57,14 +59,25 @@ export default class AttachmentDetailWidget extends BasicWidget { | ||||
| 
 | ||||
|     doRender() { | ||||
|         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.find('.attachment-title').text(this.attachment.title); | ||||
|         this.$wrapper.find('.attachment-details') | ||||
|             .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-content').append(this.renderContent()); | ||||
| 
 | ||||
|         super.doRender(); | ||||
|     } | ||||
| 
 | ||||
|     renderContent() { | ||||
| @ -76,4 +89,20 @@ export default class AttachmentDetailWidget extends BasicWidget { | ||||
|             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 server from "../../services/server.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 = ` | ||||
| <div class="dropdown attachment-actions"> | ||||
| @ -11,7 +14,7 @@ const TPL = ` | ||||
|     } | ||||
|      | ||||
|     .attachment-actions .dropdown-menu { | ||||
|         width: 15em; | ||||
|         width: 20em; | ||||
|     } | ||||
|      | ||||
|     .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> | ||||
| 
 | ||||
|     <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="pullAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Pull 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="deleteAttachment" class="dropdown-item">Delete attachment</a> | ||||
|         <a data-trigger-command="convertAttachmentIntoNote" class="dropdown-item">Convert attachment into note</a> | ||||
|         <a data-trigger-command="convertAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Copy into clipboard</a> | ||||
|     </div> | ||||
| </div>`; | ||||
| 
 | ||||
| @ -46,6 +49,20 @@ export default class AttachmentActionsWidget extends BasicWidget { | ||||
|     async deleteAttachmentCommand() { | ||||
|         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}`); | ||||
| 
 | ||||
|             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 server from "../../services/server.js"; | ||||
| import utils from "../../services/utils.js"; | ||||
| import AttachmentActionsWidget from "../buttons/attachments_actions.js"; | ||||
| import AttachmentDetailWidget from "../attachment_detail.js"; | ||||
| 
 | ||||
| const TPL = ` | ||||
| @ -16,7 +14,9 @@ const TPL = ` | ||||
| </div>`; | ||||
| 
 | ||||
| export default class AttachmentsTypeWidget extends TypeWidget { | ||||
|     static getType() { return "attachments"; } | ||||
|     static getType() { | ||||
|         return "attachments"; | ||||
|     } | ||||
| 
 | ||||
|     doRender() { | ||||
|         this.$widget = $(TPL); | ||||
| @ -28,6 +28,7 @@ export default class AttachmentsTypeWidget extends TypeWidget { | ||||
|     async doRefresh(note) { | ||||
|         this.$list.empty(); | ||||
|         this.children = []; | ||||
|         this.renderedAttachmentIds = new Set(); | ||||
| 
 | ||||
|         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); | ||||
|             this.child(attachmentDetailWidget); | ||||
| 
 | ||||
|             this.renderedAttachmentIds.add(attachment.attachmentId); | ||||
| 
 | ||||
|             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(); | ||||
| } | ||||
| 
 | ||||
| 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) { | ||||
|     const {relationMapNoteId, noteIds} = req.body; | ||||
| 
 | ||||
| @ -404,8 +340,5 @@ module.exports = { | ||||
|     eraseDeletedNotesNow, | ||||
|     getDeleteNotesPreview, | ||||
|     uploadModifiedFile, | ||||
|     forceSaveNoteRevision, | ||||
|     getAttachments, | ||||
|     saveAttachment, | ||||
|     deleteAttachment | ||||
|     forceSaveNoteRevision | ||||
| }; | ||||
|  | ||||
| @ -25,6 +25,7 @@ const indexRoute = require('./index'); | ||||
| const treeApiRoute = require('./api/tree'); | ||||
| const notesApiRoute = require('./api/notes'); | ||||
| const branchesApiRoute = require('./api/branches'); | ||||
| const attachmentsApiRoute = require('./api/attachments'); | ||||
| const autocompleteApiRoute = require('./api/autocomplete'); | ||||
| const cloningApiRoute = require('./api/cloning'); | ||||
| 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/protect/:isProtected', notesApiRoute.protectNote); | ||||
|     apiRoute(PUT, '/api/notes/:noteId/type', notesApiRoute.setNoteTypeMime); | ||||
|     apiRoute(GET, '/api/notes/:noteId/attachments', notesApiRoute.getAttachments); | ||||
|     apiRoute(POST, '/api/notes/:noteId/attachments', notesApiRoute.saveAttachment); | ||||
|     apiRoute(DELETE, '/api/notes/:noteId/attachments/:attachmentId', notesApiRoute.deleteAttachment); | ||||
|     apiRoute(GET, '/api/notes/:noteId/attachments', attachmentsApiRoute.getAttachments); | ||||
|     apiRoute(GET, '/api/notes/:noteId/attachments/:attachmentId', attachmentsApiRoute.getAttachment); | ||||
|     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(DELETE, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.eraseAllNoteRevisions); | ||||
|     apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision); | ||||
|  | ||||
| @ -137,6 +137,8 @@ function fillInAdditionalProperties(entityChange) { | ||||
|         } | ||||
|     } else if (entityChange.entityName === 'blobs') { | ||||
|         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) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam