mirror of
https://github.com/zadam/trilium.git
synced 2025-06-05 01:18:44 +02:00
"duplicate note" now duplicates whole note subtree instead of just individual note
This commit is contained in:
parent
8ec476ba96
commit
314e0a453f
@ -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
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user