mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	"duplicate note" now duplicates whole note subtree instead of just individual note
This commit is contained in:
		| @@ -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; | ||||
|         } | ||||
|   | ||||
| @@ -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 | ||||
| }; | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| }; | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user