mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	fixes, allowing conversion of note into an attachment
This commit is contained in:
		
							
								
								
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										28
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -15,7 +15,7 @@ | |||||||
|         "@excalidraw/excalidraw": "0.15.2", |         "@excalidraw/excalidraw": "0.15.2", | ||||||
|         "archiver": "5.3.1", |         "archiver": "5.3.1", | ||||||
|         "async-mutex": "0.4.0", |         "async-mutex": "0.4.0", | ||||||
|         "axios": "1.3.6", |         "axios": "1.4.0", | ||||||
|         "better-sqlite3": "7.4.5", |         "better-sqlite3": "7.4.5", | ||||||
|         "chokidar": "3.5.3", |         "chokidar": "3.5.3", | ||||||
|         "cls-hooked": "4.2.2", |         "cls-hooked": "4.2.2", | ||||||
| @@ -99,7 +99,7 @@ | |||||||
|         "nodemon": "2.0.22", |         "nodemon": "2.0.22", | ||||||
|         "prettier": "2.8.8", |         "prettier": "2.8.8", | ||||||
|         "rcedit": "3.0.1", |         "rcedit": "3.0.1", | ||||||
|         "webpack": "5.80.0", |         "webpack": "5.81.0", | ||||||
|         "webpack-cli": "5.0.2" |         "webpack-cli": "5.0.2" | ||||||
|       }, |       }, | ||||||
|       "optionalDependencies": { |       "optionalDependencies": { | ||||||
| @@ -2299,9 +2299,9 @@ | |||||||
|       "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" |       "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" | ||||||
|     }, |     }, | ||||||
|     "node_modules/axios": { |     "node_modules/axios": { | ||||||
|       "version": "1.3.6", |       "version": "1.4.0", | ||||||
|       "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", |       "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", | ||||||
|       "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", |       "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "follow-redirects": "^1.15.0", |         "follow-redirects": "^1.15.0", | ||||||
|         "form-data": "^4.0.0", |         "form-data": "^4.0.0", | ||||||
| @@ -12666,9 +12666,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/webpack": { |     "node_modules/webpack": { | ||||||
|       "version": "5.80.0", |       "version": "5.81.0", | ||||||
|       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz", |       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz", | ||||||
|       "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==", |       "integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@types/eslint-scope": "^3.7.3", |         "@types/eslint-scope": "^3.7.3", | ||||||
| @@ -14890,9 +14890,9 @@ | |||||||
|       "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" |       "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" | ||||||
|     }, |     }, | ||||||
|     "axios": { |     "axios": { | ||||||
|       "version": "1.3.6", |       "version": "1.4.0", | ||||||
|       "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz", |       "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", | ||||||
|       "integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==", |       "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "follow-redirects": "^1.15.0", |         "follow-redirects": "^1.15.0", | ||||||
|         "form-data": "^4.0.0", |         "form-data": "^4.0.0", | ||||||
| @@ -22757,9 +22757,9 @@ | |||||||
|       "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" |       "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" | ||||||
|     }, |     }, | ||||||
|     "webpack": { |     "webpack": { | ||||||
|       "version": "5.80.0", |       "version": "5.81.0", | ||||||
|       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz", |       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz", | ||||||
|       "integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==", |       "integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@types/eslint-scope": "^3.7.3", |         "@types/eslint-scope": "^3.7.3", | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ | |||||||
|     "@excalidraw/excalidraw": "0.15.2", |     "@excalidraw/excalidraw": "0.15.2", | ||||||
|     "archiver": "5.3.1", |     "archiver": "5.3.1", | ||||||
|     "async-mutex": "0.4.0", |     "async-mutex": "0.4.0", | ||||||
|     "axios": "1.3.6", |     "axios": "1.4.0", | ||||||
|     "better-sqlite3": "7.4.5", |     "better-sqlite3": "7.4.5", | ||||||
|     "chokidar": "3.5.3", |     "chokidar": "3.5.3", | ||||||
|     "cls-hooked": "4.2.2", |     "cls-hooked": "4.2.2", | ||||||
| @@ -117,7 +117,7 @@ | |||||||
|     "prettier": "2.8.8", |     "prettier": "2.8.8", | ||||||
|     "nodemon": "2.0.22", |     "nodemon": "2.0.22", | ||||||
|     "rcedit": "3.0.1", |     "rcedit": "3.0.1", | ||||||
|     "webpack": "5.80.0", |     "webpack": "5.81.0", | ||||||
|     "webpack-cli": "5.0.2" |     "webpack-cli": "5.0.2" | ||||||
|   }, |   }, | ||||||
|   "optionalDependencies": { |   "optionalDependencies": { | ||||||
|   | |||||||
| @@ -2,9 +2,9 @@ | |||||||
|  |  | ||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
| const dateUtils = require('../../services/date_utils'); | const dateUtils = require('../../services/date_utils'); | ||||||
| const becca = require('../becca'); |  | ||||||
| const AbstractBeccaEntity = require("./abstract_becca_entity"); | const AbstractBeccaEntity = require("./abstract_becca_entity"); | ||||||
| const sql = require("../../services/sql"); | const sql = require("../../services/sql"); | ||||||
|  | const protectedSessionService = require("../../services/protected_session.js"); | ||||||
|  |  | ||||||
| const attachmentRoleToNoteTypeMapping = { | const attachmentRoleToNoteTypeMapping = { | ||||||
|     'image': 'image' |     'image': 'image' | ||||||
| @@ -72,7 +72,7 @@ class BAttachment extends AbstractBeccaEntity { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     getNote() { |     getNote() { | ||||||
|         return becca.notes[this.parentId]; |         return this.becca.notes[this.parentId]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @returns {boolean} true if the note has string content (not binary) */ |     /** @returns {boolean} true if the note has string content (not binary) */ | ||||||
| @@ -80,6 +80,12 @@ class BAttachment extends AbstractBeccaEntity { | |||||||
|         return utils.isStringNote(this.type, this.mime); |         return utils.isStringNote(this.type, this.mime); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     isContentAvailable() { | ||||||
|  |         return !this.attachmentId // new attachment which was not encrypted yet | ||||||
|  |             || !this.isProtected | ||||||
|  |             || protectedSessionService.isProtectedSessionAvailable() | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** @returns {*} */ |     /** @returns {*} */ | ||||||
|     getContent() { |     getContent() { | ||||||
|         return this._getContent(); |         return this._getContent(); | ||||||
| @@ -129,15 +135,17 @@ class BAttachment extends AbstractBeccaEntity { | |||||||
|  |  | ||||||
|         this.markAsDeleted(); |         this.markAsDeleted(); | ||||||
|  |  | ||||||
|         if (this.role === 'image' && this.type === 'text') { |         const parentNote = this.getNote(); | ||||||
|             const origContent = this.getContent(); |  | ||||||
|             const oldAttachmentUrl = `api/attachment/${this.attachmentId}/image/`; |         if (this.role === 'image' && parentNote.type === 'text') { | ||||||
|  |             const origContent = parentNote.getContent(); | ||||||
|  |             const oldAttachmentUrl = `api/attachments/${this.attachmentId}/image/`; | ||||||
|             const newNoteUrl = `api/images/${note.noteId}/`; |             const newNoteUrl = `api/images/${note.noteId}/`; | ||||||
|  |  | ||||||
|             const fixedContent = utils.replaceAll(origContent, oldAttachmentUrl, newNoteUrl); |             const fixedContent = utils.replaceAll(origContent, oldAttachmentUrl, newNoteUrl); | ||||||
|  |  | ||||||
|             if (origContent !== fixedContent) { |             if (fixedContent !== origContent) { | ||||||
|                 this.setContent(fixedContent); |                 parentNote.setContent(fixedContent); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1436,6 +1436,28 @@ class BNote extends AbstractBeccaEntity { | |||||||
|  |  | ||||||
|         return cloningService.cloneNoteToBranch(this.noteId, branch.branchId); |         return cloningService.cloneNoteToBranch(this.noteId, branch.branchId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     isEligibleForConversionToAttachment() { | ||||||
|  |         if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink'); | ||||||
|  |  | ||||||
|  |         if (targetRelations.length !== 1) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const parentNote = this.getParentNotes()[0]; // at this point note can have only one parent | ||||||
|  |         const referencingNote = targetRelations[0].getNote(); | ||||||
|  |  | ||||||
|  |         if (parentNote !== referencingNote || parentNote.type !== 'text' || !parentNote.isContentAvailable()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Some notes are eligible for conversion into an attachment of its parent, note must have these properties: |      * Some notes are eligible for conversion into an attachment of its parent, note must have these properties: | ||||||
|      * - it has exactly one target relation |      * - it has exactly one target relation | ||||||
| @@ -1456,25 +1478,13 @@ 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.isEligibleForConversionToAttachment()) { | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink'); |  | ||||||
|  |  | ||||||
|         if (targetRelations.length !== 1) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         const parentNote = this.getParentNotes()[0]; // at this point note can have only one parent |  | ||||||
|         const referencingNote = targetRelations[0].note; |  | ||||||
|  |  | ||||||
|         if (parentNote !== referencingNote || parentNote.type !== 'text' || !parentNote.isContentAvailable()) { |  | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const content = this.getContent(); |         const content = this.getContent(); | ||||||
|  |  | ||||||
|  |         const parentNote = this.getParentNotes()[0]; | ||||||
|         const attachment = parentNote.saveAttachment({ |         const attachment = parentNote.saveAttachment({ | ||||||
|             role: 'image', |             role: 'image', | ||||||
|             mime: this.mime, |             mime: this.mime, | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import server from '../services/server.js'; | import server from '../services/server.js'; | ||||||
| import noteAttributeCache from "../services/note_attribute_cache.js"; | import noteAttributeCache from "../services/note_attribute_cache.js"; | ||||||
| import ws from "../services/ws.js"; | import ws from "../services/ws.js"; | ||||||
| import options from "../services/options.js"; |  | ||||||
| import froca from "../services/froca.js"; | import froca from "../services/froca.js"; | ||||||
| import protectedSessionHolder from "../services/protected_session_holder.js"; | import protectedSessionHolder from "../services/protected_session_holder.js"; | ||||||
| import cssClassManager from "../services/css_class_manager.js"; | import cssClassManager from "../services/css_class_manager.js"; | ||||||
| @@ -246,6 +245,27 @@ class FNote { | |||||||
|         return attachments.find(att => att.attachmentId === attachmentId); |         return attachments.find(att => att.attachmentId === attachmentId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     isEligibleForConversionToAttachment() { | ||||||
|  |         if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink'); | ||||||
|  |  | ||||||
|  |         if (targetRelations.length !== 1) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const parentNote = this.getParentNotes()[0]; // at this point note can have only one parent | ||||||
|  |         const referencingNote = targetRelations[0].getNote(); | ||||||
|  |  | ||||||
|  |         if (parentNote !== referencingNote || parentNote.type !== 'text' || !parentNote.isContentAvailable()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param {string} [type] - (optional) attribute type to filter |      * @param {string} [type] - (optional) attribute type to filter | ||||||
|      * @param {string} [name] - (optional) attribute name to filter |      * @param {string} [name] - (optional) attribute name to filter | ||||||
|   | |||||||
| @@ -30,7 +30,6 @@ const TPL = ` | |||||||
|     <div class="dropdown-menu dropdown-menu-right"> |     <div class="dropdown-menu dropdown-menu-right"> | ||||||
|         <a data-trigger-command="deleteAttachment" class="dropdown-item">Delete attachment</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">Convert attachment into note</a> | ||||||
|         <a data-trigger-command="convertAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Copy into clipboard</a> |  | ||||||
|     </div> |     </div> | ||||||
| </div>`; | </div>`; | ||||||
|  |  | ||||||
| @@ -47,22 +46,22 @@ 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(`attachments/${this.attachment.attachmentId}`); |             return; | ||||||
|  |  | ||||||
|             toastService.showMessage(`Attachment '${this.attachment.title}' has been deleted.`); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         await server.remove(`attachments/${this.attachment.attachmentId}`); | ||||||
|  |         toastService.showMessage(`Attachment '${this.attachment.title}' has been deleted.`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async convertAttachmentIntoNoteCommand() { |     async convertAttachmentIntoNoteCommand() { | ||||||
|         if (await dialogService.confirm(`Are you sure you want to convert attachment '${this.attachment.title}' into a separate note?`)) { |         if (!await dialogService.confirm(`Are you sure you want to convert attachment '${this.attachment.title}' into a separate note?`)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         const {note: newNote} = await server.post(`attachments/${this.attachment.attachmentId}/convert-to-note`) |         const {note: newNote} = await server.post(`attachments/${this.attachment.attachmentId}/convert-to-note`) | ||||||
|  |  | ||||||
|         toastService.showMessage(`Attachment '${this.attachment.title}' has been converted to note.`); |         toastService.showMessage(`Attachment '${this.attachment.title}' has been converted to note.`); | ||||||
|  |  | ||||||
|         await ws.waitForMaxKnownEntityChangeId(); |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |  | ||||||
|         await appContext.tabManager.getActiveContext().setNote(newNote.noteId); |         await appContext.tabManager.getActiveContext().setNote(newNote.noteId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,6 +1,11 @@ | |||||||
| import NoteContextAwareWidget from "../note_context_aware_widget.js"; | import NoteContextAwareWidget from "../note_context_aware_widget.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import branchService from "../../services/branches.js"; | import branchService from "../../services/branches.js"; | ||||||
|  | import dialogService from "../../services/dialog.js"; | ||||||
|  | import server from "../../services/server.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 note-actions"> | <div class="dropdown note-actions"> | ||||||
| @@ -25,6 +30,7 @@ const TPL = ` | |||||||
|         aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button> |         aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button> | ||||||
|  |  | ||||||
|     <div class="dropdown-menu dropdown-menu-right"> |     <div class="dropdown-menu dropdown-menu-right"> | ||||||
|  |         <a data-trigger-command="convertNoteIntoAttachment" class="dropdown-item">Convert into attachment</a> | ||||||
|         <a data-trigger-command="renderActiveNote" class="dropdown-item render-note-button"><kbd data-command="renderActiveNote"></kbd> Re-render note</a> |         <a data-trigger-command="renderActiveNote" class="dropdown-item render-note-button"><kbd data-command="renderActiveNote"></kbd> Re-render note</a> | ||||||
|         <a data-trigger-command="findInText" class="dropdown-item find-in-text-button">Search in note <kbd data-command="findInText"></a> |         <a data-trigger-command="findInText" class="dropdown-item find-in-text-button">Search in note <kbd data-command="findInText"></a> | ||||||
|         <a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a> |         <a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a> | ||||||
| @@ -45,6 +51,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { | |||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|  |  | ||||||
|  |         this.$convertNoteIntoAttachmentButton = this.$widget.find("[data-trigger-command='convertNoteIntoAttachment']"); | ||||||
|         this.$findInTextButton = this.$widget.find('.find-in-text-button'); |         this.$findInTextButton = this.$widget.find('.find-in-text-button'); | ||||||
|         this.$printActiveNoteButton = this.$widget.find('.print-active-note-button'); |         this.$printActiveNoteButton = this.$widget.find('.print-active-note-button'); | ||||||
|         this.$showSourceButton = this.$widget.find('.show-source-button'); |         this.$showSourceButton = this.$widget.find('.show-source-button'); | ||||||
| @@ -80,6 +87,8 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     refreshWithNote(note) { |     refreshWithNote(note) { | ||||||
|  |         this.$convertNoteIntoAttachmentButton.toggle(note.isEligibleForConversionToAttachment()); | ||||||
|  |  | ||||||
|         this.toggleDisabled(this.$findInTextButton, ['text', 'code', 'book', 'search'].includes(note.type)); |         this.toggleDisabled(this.$findInTextButton, ['text', 'code', 'book', 'search'].includes(note.type)); | ||||||
|  |  | ||||||
|         this.toggleDisabled(this.$showSourceButton, ['text', 'relationMap', 'mermaid'].includes(note.type)); |         this.toggleDisabled(this.$showSourceButton, ['text', 'relationMap', 'mermaid'].includes(note.type)); | ||||||
| @@ -91,6 +100,28 @@ export default class NoteActionsWidget extends NoteContextAwareWidget { | |||||||
|         this.$openNoteExternallyButton.toggle(utils.isElectron()); |         this.$openNoteExternallyButton.toggle(utils.isElectron()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async convertNoteIntoAttachmentCommand() { | ||||||
|  |         if (!await dialogService.confirm(`Are you sure you want to convert note '${this.note.title}' into an attachment of the parent note?`)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const {attachment: newAttachment} = await server.post(`notes/${this.noteId}/convert-to-attachment`); | ||||||
|  |  | ||||||
|  |         if (!newAttachment) { | ||||||
|  |             toastService.showMessage(`Converting note '${this.note.title}' failed.`); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         toastService.showMessage(`Note '${newAttachment.title}' has been converted to attachment.`); | ||||||
|  |         await ws.waitForMaxKnownEntityChangeId(); | ||||||
|  |         await appContext.tabManager.getActiveContext().setNote(newAttachment.parentId, { | ||||||
|  |             viewScope: { | ||||||
|  |                 viewMode: 'attachments', | ||||||
|  |                 attachmentId: newAttachment.attachmentId | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     toggleDisabled($el, enable) { |     toggleDisabled($el, enable) { | ||||||
|         if (enable) { |         if (enable) { | ||||||
|             $el.removeAttr('disabled'); |             $el.removeAttr('disabled'); | ||||||
|   | |||||||
| @@ -267,6 +267,19 @@ function forceSaveNoteRevision(req) { | |||||||
|     note.saveNoteRevision(); |     note.saveNoteRevision(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function convertNoteToAttachment(req) { | ||||||
|  |     const {noteId} = req.params; | ||||||
|  |     const note = becca.getNote(noteId); | ||||||
|  |  | ||||||
|  |     if (!note) { | ||||||
|  |         throw new NotFoundError(`Note '${noteId}' not found.`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         attachment: note.convertToParentAttachment({ force: true }) | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getNote, |     getNote, | ||||||
|     updateNoteData, |     updateNoteData, | ||||||
| @@ -282,5 +295,6 @@ module.exports = { | |||||||
|     eraseUnusedAttachmentsNow, |     eraseUnusedAttachmentsNow, | ||||||
|     getDeleteNotesPreview, |     getDeleteNotesPreview, | ||||||
|     uploadModifiedFile, |     uploadModifiedFile, | ||||||
|     forceSaveNoteRevision |     forceSaveNoteRevision, | ||||||
|  |     convertNoteToAttachment | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -138,6 +138,7 @@ function register(app) { | |||||||
|     // this "hacky" path is used for easier referencing of CSS resources |     // this "hacky" path is used for easier referencing of CSS resources | ||||||
|     route(GET, '/api/notes/download/:noteId', [auth.checkApiAuthOrElectron], filesRoute.downloadFile); |     route(GET, '/api/notes/download/:noteId', [auth.checkApiAuthOrElectron], filesRoute.downloadFile); | ||||||
|     apiRoute(PST, '/api/notes/:noteId/save-to-tmp-dir', filesRoute.saveToTmpDir); |     apiRoute(PST, '/api/notes/:noteId/save-to-tmp-dir', filesRoute.saveToTmpDir); | ||||||
|  |     apiRoute(PST, '/api/notes/:noteId/convert-to-attachment', notesApiRoute.convertNoteToAttachment); | ||||||
|  |  | ||||||
|     apiRoute(PUT, '/api/branches/:branchId/move-to/:parentBranchId', branchesApiRoute.moveBranchToParent); |     apiRoute(PUT, '/api/branches/:branchId/move-to/:parentBranchId', branchesApiRoute.moveBranchToParent); | ||||||
|     apiRoute(PUT, '/api/branches/:branchId/move-before/:beforeBranchId', branchesApiRoute.moveBranchBeforeNote); |     apiRoute(PUT, '/api/branches/:branchId/move-before/:beforeBranchId', branchesApiRoute.moveBranchBeforeNote); | ||||||
|   | |||||||
| @@ -370,6 +370,8 @@ function checkImageAttachments(note, content) { | |||||||
|         newAttachment.setContent(unknownAttachment.getContent(), { forceSave: true }); |         newAttachment.setContent(unknownAttachment.getContent(), { forceSave: true }); | ||||||
|  |  | ||||||
|         content = content.replace(`api/attachments/${unknownAttachment.attachmentId}/image`, `api/attachments/${newAttachment.attachmentId}/image`); |         content = content.replace(`api/attachments/${unknownAttachment.attachmentId}/image`, `api/attachments/${newAttachment.attachmentId}/image`); | ||||||
|  |  | ||||||
|  |         log.info(`Copied attachment '${unknownAttachment.attachmentId}' to new '${newAttachment.attachmentId}'`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return content; |     return content; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user