mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/stable'
This commit is contained in:
		
						commit
						c737a3adc9
					
				@ -38,7 +38,7 @@ class Branch extends Entity {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeSaving() {
 | 
					    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]);
 | 
					            const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]);
 | 
				
			||||||
            this.notePosition = maxNotePos === null ? 0 : maxNotePos + 10;
 | 
					            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}`);
 | 
					    const {note} = await server.post(`notes/${noteId}/duplicate/${parentNoteId}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await ws.waitForMaxKnownEntityChangeId();
 | 
					    await ws.waitForMaxKnownEntityChangeId();
 | 
				
			||||||
@ -102,5 +102,5 @@ async function duplicateNote(noteId, parentNoteId) {
 | 
				
			|||||||
export default {
 | 
					export default {
 | 
				
			||||||
    createNote,
 | 
					    createNote,
 | 
				
			||||||
    createNewTopLevelNote,
 | 
					    createNewTopLevelNote,
 | 
				
			||||||
    duplicateNote
 | 
					    duplicateSubtree
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -95,7 +95,7 @@ class TreeContextMenu {
 | 
				
			|||||||
                enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
 | 
					                enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
 | 
				
			||||||
            { title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "paste",
 | 
					            { title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "paste",
 | 
				
			||||||
                enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
 | 
					                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 },
 | 
					                enabled: parentNotSearch && isNotRoot && !isHoisted },
 | 
				
			||||||
            { title: "----" },
 | 
					            { title: "----" },
 | 
				
			||||||
            { title: "Export", command: "exportNote", uiIcon: "empty",
 | 
					            { title: "Export", command: "exportNote", uiIcon: "empty",
 | 
				
			||||||
 | 
				
			|||||||
@ -1341,7 +1341,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
 | 
				
			|||||||
        protectedSessionService.protectNote(node.data.noteId, false, true);
 | 
					        protectedSessionService.protectNote(node.data.noteId, false, true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    duplicateNoteCommand({node}) {
 | 
					    duplicateSubtreeCommand({node}) {
 | 
				
			||||||
        const nodesToDuplicate = this.getSelectedOrActiveNodes(node);
 | 
					        const nodesToDuplicate = this.getSelectedOrActiveNodes(node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const nodeToDuplicate of nodesToDuplicate) {
 | 
					        for (const nodeToDuplicate of nodesToDuplicate) {
 | 
				
			||||||
@ -1353,7 +1353,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            const branch = treeCache.getBranch(nodeToDuplicate.data.branchId);
 | 
					            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;
 | 
					    return note;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function duplicateNote(req) {
 | 
					function duplicateSubtree(req) {
 | 
				
			||||||
    const {noteId, parentNoteId} = req.params;
 | 
					    const {noteId, parentNoteId} = req.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return noteService.duplicateNote(noteId, parentNoteId);
 | 
					    return noteService.duplicateSubtree(noteId, parentNoteId);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
@ -204,5 +204,5 @@ module.exports = {
 | 
				
			|||||||
    setNoteTypeMime,
 | 
					    setNoteTypeMime,
 | 
				
			||||||
    getRelationMap,
 | 
					    getRelationMap,
 | 
				
			||||||
    changeTitle,
 | 
					    changeTitle,
 | 
				
			||||||
    duplicateNote
 | 
					    duplicateSubtree
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -154,7 +154,7 @@ function register(app) {
 | 
				
			|||||||
    apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision);
 | 
					    apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision);
 | 
				
			||||||
    apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
 | 
					    apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
 | 
				
			||||||
    apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
 | 
					    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);
 | 
					    apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
const eventService = require('./events');
 | 
					const eventService = require('./events');
 | 
				
			||||||
const scriptService = require('./script');
 | 
					const scriptService = require('./script');
 | 
				
			||||||
const treeService = require('./tree');
 | 
					const treeService = require('./tree');
 | 
				
			||||||
const log = require('./log');
 | 
					const noteService = require('./notes');
 | 
				
			||||||
const repository = require('./repository');
 | 
					const repository = require('./repository');
 | 
				
			||||||
const Attribute = require('../entities/attribute');
 | 
					const Attribute = require('../entities/attribute');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -58,17 +58,21 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) =>
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const targetNote = repository.getNote(entity.value);
 | 
					            const templateNote = repository.getNote(entity.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!targetNote || !targetNote.isStringNote()) {
 | 
					            if (!templateNote) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const targetNoteContent = targetNote.getContent();
 | 
					            if (templateNote.isStringNote()) {
 | 
				
			||||||
 | 
					                const templateNoteContent = templateNote.getContent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (targetNoteContent) {
 | 
					                if (templateNoteContent) {
 | 
				
			||||||
                note.setContent(targetNoteContent);
 | 
					                    note.setContent(templateNoteContent);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            noteService.duplicateSubtreeWithoutRoot(templateNote.noteId, note.noteId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (entity.type === 'label' && entity.name === 'sorted') {
 | 
					        else if (entity.type === 'label' && entity.name === 'sorted') {
 | 
				
			||||||
            treeService.sortNotesAlphabetically(entity.noteId);
 | 
					            treeService.sortNotesAlphabetically(entity.noteId);
 | 
				
			||||||
 | 
				
			|||||||
@ -310,7 +310,13 @@ function importEnex(taskContext, file, parentNote) {
 | 
				
			|||||||
        updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
 | 
					        updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    saxStream.on("closetag", tag => path.pop());
 | 
					    saxStream.on("closetag", tag => {
 | 
				
			||||||
 | 
					        path.pop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (tag === 'note') {
 | 
				
			||||||
 | 
					            saveNote();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    saxStream.on("opencdata", () => {
 | 
					    saxStream.on("opencdata", () => {
 | 
				
			||||||
        //console.log("opencdata");
 | 
					        //console.log("opencdata");
 | 
				
			||||||
 | 
				
			|||||||
@ -719,26 +719,68 @@ function eraseDeletedNotes() {
 | 
				
			|||||||
    log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
 | 
					    log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function duplicateNote(noteId, parentNoteId) {
 | 
					// do a replace in str - all keys should be replaced by the corresponding values
 | 
				
			||||||
    const origNote = repository.getNote(noteId);
 | 
					function replaceByMap(str, mapObj) {
 | 
				
			||||||
 | 
					    const re = new RegExp(Object.keys(mapObj).join("|"),"g");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return str.replace(re, matched => mapObj[matched]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function duplicateSubtree(origNoteId, newParentNoteId) {
 | 
				
			||||||
 | 
					    if (origNoteId === 'root') {
 | 
				
			||||||
 | 
					        throw new Error('Duplicating root is not possible');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 = getNoteIdMapping(origNote);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const res = duplicateSubtreeInner(origNote, origBranch, newParentNoteId, noteIdMapping);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.note.title += " (dup)";
 | 
				
			||||||
 | 
					    res.note.save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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()) {
 | 
					    if (origNote.isProtected && !protectedSessionService.isProtectedSessionAvailable()) {
 | 
				
			||||||
        throw new Error(`Cannot duplicate note=${origNote.noteId} because it is protected and protected session is not available`);
 | 
					        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);
 | 
					    const newNote = new Note(origNote);
 | 
				
			||||||
    newNote.noteId = undefined; // force creation of new note
 | 
					    newNote.noteId = noteIdMapping[origNote.noteId];
 | 
				
			||||||
    newNote.title += " (dup)";
 | 
					    newNote.dateCreated = dateUtils.localNowDateTime();
 | 
				
			||||||
 | 
					    newNote.utcDateCreated = dateUtils.utcNowDateTime();
 | 
				
			||||||
    newNote.save();
 | 
					    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({
 | 
					    const newBranch = new Branch({
 | 
				
			||||||
        noteId: newNote.noteId,
 | 
					        noteId: newNote.noteId,
 | 
				
			||||||
        parentNoteId: parentNoteId,
 | 
					        parentNoteId: newParentNoteId,
 | 
				
			||||||
        // here increasing just by 1 to make sure it's directly after original
 | 
					        // here increasing just by 1 to make sure it's directly after original
 | 
				
			||||||
        notePosition: origBranch ? origBranch.notePosition + 1 : null
 | 
					        notePosition: origBranch ? origBranch.notePosition + 1 : null
 | 
				
			||||||
    }).save();
 | 
					    }).save();
 | 
				
			||||||
@ -746,17 +788,38 @@ function duplicateNote(noteId, parentNoteId) {
 | 
				
			|||||||
    for (const attribute of origNote.getOwnedAttributes()) {
 | 
					    for (const attribute of origNote.getOwnedAttributes()) {
 | 
				
			||||||
        const attr = new Attribute(attribute);
 | 
					        const attr = new Attribute(attribute);
 | 
				
			||||||
        attr.attributeId = undefined; // force creation of new attribute
 | 
					        attr.attributeId = undefined; // force creation of new attribute
 | 
				
			||||||
 | 
					        attr.utcDateCreated = dateUtils.utcNowDateTime();
 | 
				
			||||||
        attr.noteId = newNote.noteId;
 | 
					        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();
 | 
					        attr.save();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const childBranch of origNote.getChildBranches()) {
 | 
				
			||||||
 | 
					        duplicateSubtreeInner(childBranch.getNote(), childBranch, newNote.noteId, noteIdMapping);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        note: newNote,
 | 
					        note: newNote,
 | 
				
			||||||
        branch: newBranch
 | 
					        branch: newBranch
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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(() => {
 | 
					sqlInit.dbReady.then(() => {
 | 
				
			||||||
    // first cleanup kickoff 5 minutes after startup
 | 
					    // first cleanup kickoff 5 minutes after startup
 | 
				
			||||||
    setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000);
 | 
					    setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000);
 | 
				
			||||||
@ -772,7 +835,8 @@ module.exports = {
 | 
				
			|||||||
    undeleteNote,
 | 
					    undeleteNote,
 | 
				
			||||||
    protectNoteRecursively,
 | 
					    protectNoteRecursively,
 | 
				
			||||||
    scanForLinks,
 | 
					    scanForLinks,
 | 
				
			||||||
    duplicateNote,
 | 
					    duplicateSubtree,
 | 
				
			||||||
 | 
					    duplicateSubtreeWithoutRoot,
 | 
				
			||||||
    getUndeletedParentBranches,
 | 
					    getUndeletedParentBranches,
 | 
				
			||||||
    triggerNoteTitleChanged
 | 
					    triggerNoteTitleChanged
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user