From 8ec476ba9637119a57b3550a54960c8e6534da33 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 19 Nov 2020 13:30:39 +0100 Subject: [PATCH 1/3] fix ENEX import note saving --- src/services/import/enex.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/services/import/enex.js b/src/services/import/enex.js index 498fa3043..f1520b0a1 100644 --- a/src/services/import/enex.js +++ b/src/services/import/enex.js @@ -310,7 +310,13 @@ function importEnex(taskContext, file, parentNote) { updateDates(noteEntity.noteId, utcDateCreated, utcDateModified); } - saxStream.on("closetag", tag => path.pop()); + saxStream.on("closetag", tag => { + path.pop(); + + if (tag === 'note') { + saveNote(); + } + }); saxStream.on("opencdata", () => { //console.log("opencdata"); From 314e0a453fc31a109c5a1871e1b01011489b6a74 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 19 Nov 2020 14:06:32 +0100 Subject: [PATCH 2/3] "duplicate note" now duplicates whole note subtree instead of just individual note --- src/entities/branch.js | 2 +- src/public/app/services/note_create.js | 4 +- src/public/app/services/tree_context_menu.js | 2 +- src/public/app/widgets/note_tree.js | 4 +- src/routes/api/notes.js | 6 +- src/routes/routes.js | 2 +- src/services/notes.js | 65 +++++++++++++++++--- 7 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/entities/branch.js b/src/entities/branch.js index 5717c1b1e..5fb832241 100644 --- a/src/entities/branch.js +++ b/src/entities/branch.js @@ -38,7 +38,7 @@ class Branch extends Entity { } beforeSaving() { - if (this.notePosition === undefined) { + if (this.notePosition === undefined || this.notePosition === null) { const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]); this.notePosition = maxNotePos === null ? 0 : maxNotePos + 10; } diff --git a/src/public/app/services/note_create.js b/src/public/app/services/note_create.js index ef2805d72..09aca2e9a 100644 --- a/src/public/app/services/note_create.js +++ b/src/public/app/services/note_create.js @@ -88,7 +88,7 @@ function parseSelectedHtml(selectedHtml) { } } -async function duplicateNote(noteId, parentNoteId) { +async function duplicateSubtree(noteId, parentNoteId) { const {note} = await server.post(`notes/${noteId}/duplicate/${parentNoteId}`); await ws.waitForMaxKnownEntityChangeId(); @@ -102,5 +102,5 @@ async function duplicateNote(noteId, parentNoteId) { export default { createNote, createNewTopLevelNote, - duplicateNote + duplicateSubtree }; diff --git a/src/public/app/services/tree_context_menu.js b/src/public/app/services/tree_context_menu.js index 0cc68e0ff..058dac8e6 100644 --- a/src/public/app/services/tree_context_menu.js +++ b/src/public/app/services/tree_context_menu.js @@ -95,7 +95,7 @@ class TreeContextMenu { enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes }, { title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "paste", enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes }, - { title: "Duplicate note(s) here", command: "duplicateNote", uiIcon: "empty", + { title: "Duplicate subtree(s) here", command: "duplicateSubtree", uiIcon: "empty", enabled: parentNotSearch && isNotRoot && !isHoisted }, { title: "----" }, { title: "Export", command: "exportNote", uiIcon: "empty", diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js index 469e6ad4b..e6a1bcc5f 100644 --- a/src/public/app/widgets/note_tree.js +++ b/src/public/app/widgets/note_tree.js @@ -1341,7 +1341,7 @@ export default class NoteTreeWidget extends TabAwareWidget { protectedSessionService.protectNote(node.data.noteId, false, true); } - duplicateNoteCommand({node}) { + duplicateSubtreeCommand({node}) { const nodesToDuplicate = this.getSelectedOrActiveNodes(node); for (const nodeToDuplicate of nodesToDuplicate) { @@ -1353,7 +1353,7 @@ export default class NoteTreeWidget extends TabAwareWidget { const branch = treeCache.getBranch(nodeToDuplicate.data.branchId); - noteCreateService.duplicateNote(nodeToDuplicate.data.noteId, branch.parentNoteId); + noteCreateService.duplicateSubtree(nodeToDuplicate.data.noteId, branch.parentNoteId); } } } diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index 1220bda31..4f03a5223 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -187,10 +187,10 @@ function changeTitle(req) { return note; } -function duplicateNote(req) { +function duplicateSubtree(req) { const {noteId, parentNoteId} = req.params; - return noteService.duplicateNote(noteId, parentNoteId); + return noteService.duplicateSubtree(noteId, parentNoteId); } module.exports = { @@ -204,5 +204,5 @@ module.exports = { setNoteTypeMime, getRelationMap, changeTitle, - duplicateNote + duplicateSubtree }; diff --git a/src/routes/routes.js b/src/routes/routes.js index f52424ee0..cfa2e62d4 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -154,7 +154,7 @@ function register(app) { apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision); apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap); apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle); - apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateNote); + apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree); apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate); diff --git a/src/services/notes.js b/src/services/notes.js index 3fa4a0711..567211692 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -719,26 +719,60 @@ function eraseDeletedNotes() { log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`); } -function duplicateNote(noteId, parentNoteId) { - const origNote = repository.getNote(noteId); +// do a replace in str - all keys should be replaced by the corresponding values +function replaceByMap(str, mapObj) { + const re = new RegExp(Object.keys(mapObj).join("|"),"g"); + return str.replace(re, matched => mapObj[matched]); +} + +function duplicateSubtree(noteId, newParentNoteId) { + if (noteId === 'root') { + throw new Error('Duplicating root is not possible'); + } + + const origNote = repository.getNote(noteId); + // might be null if orig note is not in the target newParentNoteId + const origBranch = origNote.getBranches().find(branch => branch.parentNoteId === newParentNoteId); + + const noteIdMapping = {}; + + // pregenerate new noteIds since we'll need to fix relation references even for not yet created notes + for (const origNoteId of origNote.getDescendantNoteIds()) { + noteIdMapping[origNoteId] = utils.newEntityId(); + } + + const res = duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapping); + + res.note.title += " (dup)"; + res.note.save(); + + return res; +} + +function duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapping) { if (origNote.isProtected && !protectedSessionService.isProtectedSessionAvailable()) { throw new Error(`Cannot duplicate note=${origNote.noteId} because it is protected and protected session is not available`); } - // might be null if orig note is not in the target parentNoteId - const origBranch = origNote.getBranches().find(branch => branch.parentNoteId === parentNoteId); - const newNote = new Note(origNote); - newNote.noteId = undefined; // force creation of new note - newNote.title += " (dup)"; + newNote.noteId = noteIdMapping[origNote.noteId]; + newNote.dateCreated = dateUtils.localNowDateTime(); + newNote.utcDateCreated = dateUtils.utcNowDateTime(); newNote.save(); - newNote.setContent(origNote.getContent()); + let content = origNote.getContent(); + + if (['text', 'relation-map', 'search'].includes(origNote.type)) { + // fix links in the content + content = replaceByMap(content, noteIdMapping); + } + + newNote.setContent(content); const newBranch = new Branch({ noteId: newNote.noteId, - parentNoteId: parentNoteId, + parentNoteId: newParentNoteId, // here increasing just by 1 to make sure it's directly after original notePosition: origBranch ? origBranch.notePosition + 1 : null }).save(); @@ -746,11 +780,22 @@ function duplicateNote(noteId, parentNoteId) { for (const attribute of origNote.getOwnedAttributes()) { const attr = new Attribute(attribute); attr.attributeId = undefined; // force creation of new attribute + attr.utcDateCreated = dateUtils.utcNowDateTime(); attr.noteId = newNote.noteId; + // if relation points to within the duplicated tree then replace the target to the duplicated note + // if it points outside of duplicated tree then keep the original target + if (attr.type === 'relation' && attr.value in noteIdMapping) { + attr.value = noteIdMapping[attr.value]; + } + attr.save(); } + for (const childBranch of origNote.getChildBranches()) { + duplicateSubtreeInner(childBranch.getNote(), childBranch, newNote.noteId, noteIdMapping); + } + return { note: newNote, branch: newBranch @@ -772,7 +817,7 @@ module.exports = { undeleteNote, protectNoteRecursively, scanForLinks, - duplicateNote, + duplicateSubtree, getUndeletedParentBranches, triggerNoteTitleChanged }; From 1047aecfbdc7527b461a53d0dba978f0b4751dd1 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 19 Nov 2020 14:29:26 +0100 Subject: [PATCH 3/3] template subtree is now deep-duplicated on template assignment --- src/services/handlers.js | 16 ++++++++++------ src/services/notes.js | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/services/handlers.js b/src/services/handlers.js index 035a027e1..08fc66577 100644 --- a/src/services/handlers.js +++ b/src/services/handlers.js @@ -1,7 +1,7 @@ const eventService = require('./events'); const scriptService = require('./script'); const treeService = require('./tree'); -const log = require('./log'); +const noteService = require('./notes'); const repository = require('./repository'); const Attribute = require('../entities/attribute'); @@ -58,17 +58,21 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => return; } - const targetNote = repository.getNote(entity.value); + const templateNote = repository.getNote(entity.value); - if (!targetNote || !targetNote.isStringNote()) { + if (!templateNote) { return; } - const targetNoteContent = targetNote.getContent(); + if (templateNote.isStringNote()) { + const templateNoteContent = templateNote.getContent(); - if (targetNoteContent) { - note.setContent(targetNoteContent); + if (templateNoteContent) { + note.setContent(templateNoteContent); + } } + + noteService.duplicateSubtreeWithoutRoot(templateNote.noteId, note.noteId); } else if (entity.type === 'label' && entity.name === 'sorted') { treeService.sortNotesAlphabetically(entity.noteId); diff --git a/src/services/notes.js b/src/services/notes.js index 567211692..3f4eddf56 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -726,21 +726,16 @@ function replaceByMap(str, mapObj) { return str.replace(re, matched => mapObj[matched]); } -function duplicateSubtree(noteId, newParentNoteId) { - if (noteId === 'root') { +function duplicateSubtree(origNoteId, newParentNoteId) { + if (origNoteId === 'root') { throw new Error('Duplicating root is not possible'); } - const origNote = repository.getNote(noteId); + const origNote = repository.getNote(origNoteId); // might be null if orig note is not in the target newParentNoteId const origBranch = origNote.getBranches().find(branch => branch.parentNoteId === newParentNoteId); - const noteIdMapping = {}; - - // pregenerate new noteIds since we'll need to fix relation references even for not yet created notes - for (const origNoteId of origNote.getDescendantNoteIds()) { - noteIdMapping[origNoteId] = utils.newEntityId(); - } + const noteIdMapping = getNoteIdMapping(origNote); const res = duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapping); @@ -750,6 +745,19 @@ function duplicateSubtree(noteId, newParentNoteId) { return res; } +function duplicateSubtreeWithoutRoot(origNoteId, newNoteId) { + if (origNoteId === 'root') { + throw new Error('Duplicating root is not possible'); + } + + const origNote = repository.getNote(origNoteId); + const noteIdMapping = getNoteIdMapping(origNote); + + for (const childBranch of origNote.getChildBranches()) { + duplicateSubtreeInner(childBranch.getNote(), childBranch, newNoteId, noteIdMapping); + } +} + function duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapping) { if (origNote.isProtected && !protectedSessionService.isProtectedSessionAvailable()) { throw new Error(`Cannot duplicate note=${origNote.noteId} because it is protected and protected session is not available`); @@ -802,6 +810,16 @@ function duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapp }; } +function getNoteIdMapping(origNote) { + const noteIdMapping = {}; + + // pregenerate new noteIds since we'll need to fix relation references even for not yet created notes + for (const origNoteId of origNote.getDescendantNoteIds()) { + noteIdMapping[origNoteId] = utils.newEntityId(); + } + return noteIdMapping; +} + sqlInit.dbReady.then(() => { // first cleanup kickoff 5 minutes after startup setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000); @@ -818,6 +836,7 @@ module.exports = { protectNoteRecursively, scanForLinks, duplicateSubtree, + duplicateSubtreeWithoutRoot, getUndeletedParentBranches, triggerNoteTitleChanged };