From 314e0a453fc31a109c5a1871e1b01011489b6a74 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 19 Nov 2020 14:06:32 +0100 Subject: [PATCH] "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 };