fixes, allowing conversion of note into an attachment

This commit is contained in:
zadam
2023-05-02 22:46:39 +02:00
parent 330e7ac08e
commit 735ac55bb8
12 changed files with 139 additions and 54 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+14 -14
View File
@@ -15,7 +15,7 @@
"@excalidraw/excalidraw": "0.15.2",
"archiver": "5.3.1",
"async-mutex": "0.4.0",
"axios": "1.3.6",
"axios": "1.4.0",
"better-sqlite3": "7.4.5",
"chokidar": "3.5.3",
"cls-hooked": "4.2.2",
@@ -99,7 +99,7 @@
"nodemon": "2.0.22",
"prettier": "2.8.8",
"rcedit": "3.0.1",
"webpack": "5.80.0",
"webpack": "5.81.0",
"webpack-cli": "5.0.2"
},
"optionalDependencies": {
@@ -2299,9 +2299,9 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
"node_modules/axios": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz",
"integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
@@ -12666,9 +12666,9 @@
}
},
"node_modules/webpack": {
"version": "5.80.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz",
"integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==",
"version": "5.81.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz",
"integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@@ -14890,9 +14890,9 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
"axios": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.6.tgz",
"integrity": "sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
@@ -22757,9 +22757,9 @@
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
},
"webpack": {
"version": "5.80.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.80.0.tgz",
"integrity": "sha512-OIMiq37XK1rWO8mH9ssfFKZsXg4n6klTEDL7S8/HqbAOBBaiy8ABvXvz0dDCXeEF9gqwxSvVk611zFPjS8hJxA==",
"version": "5.81.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz",
"integrity": "sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.3",
+2 -2
View File
@@ -36,7 +36,7 @@
"@excalidraw/excalidraw": "0.15.2",
"archiver": "5.3.1",
"async-mutex": "0.4.0",
"axios": "1.3.6",
"axios": "1.4.0",
"better-sqlite3": "7.4.5",
"chokidar": "3.5.3",
"cls-hooked": "4.2.2",
@@ -117,7 +117,7 @@
"prettier": "2.8.8",
"nodemon": "2.0.22",
"rcedit": "3.0.1",
"webpack": "5.80.0",
"webpack": "5.81.0",
"webpack-cli": "5.0.2"
},
"optionalDependencies": {
+15 -7
View File
@@ -2,9 +2,9 @@
const utils = require('../../services/utils');
const dateUtils = require('../../services/date_utils');
const becca = require('../becca');
const AbstractBeccaEntity = require("./abstract_becca_entity");
const sql = require("../../services/sql");
const protectedSessionService = require("../../services/protected_session.js");
const attachmentRoleToNoteTypeMapping = {
'image': 'image'
@@ -72,7 +72,7 @@ class BAttachment extends AbstractBeccaEntity {
}
getNote() {
return becca.notes[this.parentId];
return this.becca.notes[this.parentId];
}
/** @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);
}
isContentAvailable() {
return !this.attachmentId // new attachment which was not encrypted yet
|| !this.isProtected
|| protectedSessionService.isProtectedSessionAvailable()
}
/** @returns {*} */
getContent() {
return this._getContent();
@@ -129,15 +135,17 @@ class BAttachment extends AbstractBeccaEntity {
this.markAsDeleted();
if (this.role === 'image' && this.type === 'text') {
const origContent = this.getContent();
const oldAttachmentUrl = `api/attachment/${this.attachmentId}/image/`;
const parentNote = this.getNote();
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 fixedContent = utils.replaceAll(origContent, oldAttachmentUrl, newNoteUrl);
if (origContent !== fixedContent) {
this.setContent(fixedContent);
if (fixedContent !== origContent) {
parentNote.setContent(fixedContent);
}
}
+24 -14
View File
@@ -1436,6 +1436,28 @@ class BNote extends AbstractBeccaEntity {
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:
* - 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
*/
convertToParentAttachment(opts = {force: false}) {
if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) {
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()) {
if (!this.isEligibleForConversionToAttachment()) {
return null;
}
const content = this.getContent();
const parentNote = this.getParentNotes()[0];
const attachment = parentNote.saveAttachment({
role: 'image',
mime: this.mime,
+21 -1
View File
@@ -1,7 +1,6 @@
import server from '../services/server.js';
import noteAttributeCache from "../services/note_attribute_cache.js";
import ws from "../services/ws.js";
import options from "../services/options.js";
import froca from "../services/froca.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import cssClassManager from "../services/css_class_manager.js";
@@ -246,6 +245,27 @@ class FNote {
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} [name] - (optional) attribute name to filter
@@ -30,7 +30,6 @@ const TPL = `
<div class="dropdown-menu dropdown-menu-right">
<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>`;
@@ -47,22 +46,22 @@ 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(`attachments/${this.attachment.attachmentId}`);
toastService.showMessage(`Attachment '${this.attachment.title}' has been deleted.`);
if (!await dialogService.confirm(`Are you sure you want to delete attachment '${this.attachment.title}'?`)) {
return;
}
await server.remove(`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(`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);
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`)
toastService.showMessage(`Attachment '${this.attachment.title}' has been converted to note.`);
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(newNote.noteId);
}
}
@@ -1,6 +1,11 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import utils from "../../services/utils.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 = `
<div class="dropdown note-actions">
@@ -25,6 +30,7 @@ const TPL = `
aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button>
<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="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>
@@ -45,6 +51,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.$convertNoteIntoAttachmentButton = this.$widget.find("[data-trigger-command='convertNoteIntoAttachment']");
this.$findInTextButton = this.$widget.find('.find-in-text-button');
this.$printActiveNoteButton = this.$widget.find('.print-active-note-button');
this.$showSourceButton = this.$widget.find('.show-source-button');
@@ -80,6 +87,8 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
}
refreshWithNote(note) {
this.$convertNoteIntoAttachmentButton.toggle(note.isEligibleForConversionToAttachment());
this.toggleDisabled(this.$findInTextButton, ['text', 'code', 'book', 'search'].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());
}
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) {
if (enable) {
$el.removeAttr('disabled');
+15 -1
View File
@@ -267,6 +267,19 @@ function forceSaveNoteRevision(req) {
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 = {
getNote,
updateNoteData,
@@ -282,5 +295,6 @@ module.exports = {
eraseUnusedAttachmentsNow,
getDeleteNotesPreview,
uploadModifiedFile,
forceSaveNoteRevision
forceSaveNoteRevision,
convertNoteToAttachment
};
+1
View File
@@ -138,6 +138,7 @@ function register(app) {
// this "hacky" path is used for easier referencing of CSS resources
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/convert-to-attachment', notesApiRoute.convertNoteToAttachment);
apiRoute(PUT, '/api/branches/:branchId/move-to/:parentBranchId', branchesApiRoute.moveBranchToParent);
apiRoute(PUT, '/api/branches/:branchId/move-before/:beforeBranchId', branchesApiRoute.moveBranchBeforeNote);
+2
View File
@@ -370,6 +370,8 @@ function checkImageAttachments(note, content) {
newAttachment.setContent(unknownAttachment.getContent(), { forceSave: true });
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;