mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	widgetizing tree WIP
This commit is contained in:
		
							parent
							
								
									d1f679ab90
								
							
						
					
					
						commit
						b12e38c231
					
				@ -1,6 +1,5 @@
 | 
			
		||||
import cloning from './services/cloning.js';
 | 
			
		||||
import contextMenu from './services/tree_context_menu.js';
 | 
			
		||||
import dragAndDropSetup from './services/drag_and_drop.js';
 | 
			
		||||
import link from './services/link.js';
 | 
			
		||||
import ws from './services/ws.js';
 | 
			
		||||
import noteDetailService from './services/note_detail.js';
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import treeService from "./services/tree.js";
 | 
			
		||||
import noteDetailService from "./services/note_detail.js";
 | 
			
		||||
import dragAndDropSetup from "./services/drag_and_drop.js";
 | 
			
		||||
import treeCache from "./services/tree_cache.js";
 | 
			
		||||
import treeBuilder from "./services/tree_builder.js";
 | 
			
		||||
import contextMenuWidget from "./services/context_menu.js";
 | 
			
		||||
 | 
			
		||||
@ -68,6 +68,7 @@ async function moveToNode(branchIdsToMove, newParentNoteId) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FIXME used for finding a next note to activate after a delete
 | 
			
		||||
async function getNextNode(nodes) {
 | 
			
		||||
    // following code assumes that nodes contain only top-most selected nodes - getSelectedNodes has been
 | 
			
		||||
    // called with stopOnParent=true
 | 
			
		||||
@ -84,10 +85,10 @@ async function getNextNode(nodes) {
 | 
			
		||||
    return treeUtils.getNotePath(next);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function deleteNodes(nodes) {
 | 
			
		||||
    nodes = await filterRootNote(nodes);
 | 
			
		||||
async function deleteNodes(branchIdsToDelete) {
 | 
			
		||||
    branchIdsToDelete = await filterRootNote(branchIdsToDelete);
 | 
			
		||||
 | 
			
		||||
    if (nodes.length === 0) {
 | 
			
		||||
    if (branchIdsToDelete.length === 0) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -96,7 +97,15 @@ async function deleteNodes(nodes) {
 | 
			
		||||
        .append($('<label for="delete-clones-checkbox">')
 | 
			
		||||
                    .text("delete also all note clones")
 | 
			
		||||
                    .attr("title", "all clones of selected notes will be deleted and as such the whole note will be deleted."));
 | 
			
		||||
    const $nodeTitles = $("<ul>").append(...nodes.map(node => $("<li>").text(node.title)));
 | 
			
		||||
 | 
			
		||||
    const $nodeTitles = $("<ul>");
 | 
			
		||||
 | 
			
		||||
    for (const branchId of branchIdsToDelete) {
 | 
			
		||||
        const note = await treeCache.getBranch(branchId).getNote();
 | 
			
		||||
 | 
			
		||||
        $nodeTitles.append($("<li>").text(note.title));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const $confirmText = $("<div>")
 | 
			
		||||
        .append($("<p>").text('This will delete the following notes and their sub-notes: '))
 | 
			
		||||
        .append($nodeTitles)
 | 
			
		||||
@ -114,31 +123,31 @@ async function deleteNodes(nodes) {
 | 
			
		||||
 | 
			
		||||
    let counter = 0;
 | 
			
		||||
 | 
			
		||||
    for (const node of nodes) {
 | 
			
		||||
    for (const branchIdToDelete of branchIdsToDelete) {
 | 
			
		||||
        counter++;
 | 
			
		||||
 | 
			
		||||
        const last = counter === nodes.length;
 | 
			
		||||
        const last = counter === branchIdsToDelete.length;
 | 
			
		||||
        const query = `?taskId=${taskId}&last=${last ? 'true' : 'false'}`;
 | 
			
		||||
 | 
			
		||||
        if (deleteClones) {
 | 
			
		||||
            await server.remove(`notes/${node.data.noteId}` + query);
 | 
			
		||||
        const branch = treeCache.getBranch(branchIdToDelete);
 | 
			
		||||
 | 
			
		||||
            noteDetailService.noteDeleted(node.data.noteId);
 | 
			
		||||
        if (deleteClones) {
 | 
			
		||||
            await server.remove(`notes/${branch.noteId}` + query);
 | 
			
		||||
 | 
			
		||||
            noteDetailService.noteDeleted(branch.noteId);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            const {noteDeleted} = await server.remove(`branches/${node.data.branchId}` + query);
 | 
			
		||||
            const {noteDeleted} = await server.remove(`branches/${branchIdToDelete}` + query);
 | 
			
		||||
 | 
			
		||||
            if (noteDeleted) {
 | 
			
		||||
                noteDetailService.noteDeleted(node.data.noteId);
 | 
			
		||||
                noteDetailService.noteDeleted(branch.noteId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const nextNotePath = await getNextNode(nodes);
 | 
			
		||||
 | 
			
		||||
    const noteIds = Array.from(new Set(nodes.map(node => node.getParent().data.noteId)));
 | 
			
		||||
 | 
			
		||||
    await treeService.reloadNotes(noteIds, nextNotePath);
 | 
			
		||||
    await treeService.reloadNotes(noteIds);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,75 +0,0 @@
 | 
			
		||||
import treeService from './tree.js';
 | 
			
		||||
import treeChangesService from './branches.js';
 | 
			
		||||
import hoistedNoteService from './hoisted_note.js';
 | 
			
		||||
 | 
			
		||||
const dragAndDropSetup = {
 | 
			
		||||
    autoExpandMS: 600,
 | 
			
		||||
    dragStart: (node, data) => {
 | 
			
		||||
        // don't allow dragging root node
 | 
			
		||||
        if (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise()
 | 
			
		||||
            || node.getParent().data.noteType === 'search') {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        node.setSelected(true);
 | 
			
		||||
 | 
			
		||||
        const notes = treeService.getSelectedNodes().map(node => { return {
 | 
			
		||||
            noteId: node.data.noteId,
 | 
			
		||||
            title: node.title
 | 
			
		||||
        }});
 | 
			
		||||
 | 
			
		||||
        data.dataTransfer.setData("text", JSON.stringify(notes));
 | 
			
		||||
 | 
			
		||||
        // This function MUST be defined to enable dragging for the tree.
 | 
			
		||||
        // Return false to cancel dragging of node.
 | 
			
		||||
        return true;
 | 
			
		||||
    },
 | 
			
		||||
    dragEnter: (node, data) => true, // allow drop on any node
 | 
			
		||||
    dragOver: (node, data) => true,
 | 
			
		||||
    dragDrop: async (node, data) => {
 | 
			
		||||
        if ((data.hitMode === 'over' && node.data.noteType === 'search') ||
 | 
			
		||||
            (['after', 'before'].includes(data.hitMode)
 | 
			
		||||
                && (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise() || node.getParent().data.noteType === 'search'))) {
 | 
			
		||||
 | 
			
		||||
            const infoDialog = await import('../dialogs/info.js');
 | 
			
		||||
 | 
			
		||||
            await infoDialog.info("Dropping notes into this location is not allowed.");
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const dataTransfer = data.dataTransfer;
 | 
			
		||||
 | 
			
		||||
        if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
 | 
			
		||||
            const files = [...dataTransfer.files]; // chrome has issue that dataTransfer.files empties after async operation
 | 
			
		||||
 | 
			
		||||
            const importService = await import('./import.js');
 | 
			
		||||
 | 
			
		||||
            importService.uploadFiles(node.data.noteId, files, {
 | 
			
		||||
                safeImport: true,
 | 
			
		||||
                shrinkImages: true,
 | 
			
		||||
                textImportedAsText: true,
 | 
			
		||||
                codeImportedAsCode: true,
 | 
			
		||||
                explodeArchives: true
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            // This function MUST be defined to enable dropping of items on the tree.
 | 
			
		||||
            // data.hitMode is 'before', 'after', or 'over'.
 | 
			
		||||
 | 
			
		||||
            const selectedNodes = treeService.getSelectedNodes();
 | 
			
		||||
 | 
			
		||||
            if (data.hitMode === "before") {
 | 
			
		||||
                treeChangesService.moveBeforeNode(selectedNodes, node);
 | 
			
		||||
            } else if (data.hitMode === "after") {
 | 
			
		||||
                treeChangesService.moveAfterNode(selectedNodes, node);
 | 
			
		||||
            } else if (data.hitMode === "over") {
 | 
			
		||||
                treeChangesService.moveToNode(selectedNodes, node);
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new Error("Unknown hitMode=" + data.hitMode);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default dragAndDropSetup;
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
import contextMenuWidget from './context_menu.js';
 | 
			
		||||
import dragAndDropSetup from './drag_and_drop.js';
 | 
			
		||||
import ws from './ws.js';
 | 
			
		||||
import noteDetailService from './note_detail.js';
 | 
			
		||||
import protectedSessionHolder from './protected_session_holder.js';
 | 
			
		||||
@ -9,10 +7,8 @@ import server from './server.js';
 | 
			
		||||
import treeCache from './tree_cache.js';
 | 
			
		||||
import toastService from "./toast.js";
 | 
			
		||||
import treeBuilder from "./tree_builder.js";
 | 
			
		||||
import treeKeyBindingService from "./tree_keybindings.js";
 | 
			
		||||
import hoistedNoteService from '../services/hoisted_note.js';
 | 
			
		||||
import optionsService from "../services/options.js";
 | 
			
		||||
import TreeContextMenu from "./tree_context_menu.js";
 | 
			
		||||
import bundle from "./bundle.js";
 | 
			
		||||
import keyboardActionService from "./keyboard_actions.js";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -133,7 +133,7 @@ class TreeContextMenu {
 | 
			
		||||
            protectedSessionService.protectSubtree(this.node.data.noteId, false);
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "copy") {
 | 
			
		||||
            clipboard.copy(this.treeWidget.getSelectedOrActiveNodes(this.node));
 | 
			
		||||
            clipboard.copy(this.getSelectedOrActiveBranchIds());
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "cloneTo") {
 | 
			
		||||
            const nodes = this.treeWidget.getSelectedOrActiveNodes(this.node);
 | 
			
		||||
@ -142,7 +142,7 @@ class TreeContextMenu {
 | 
			
		||||
            import("../dialogs/clone_to.js").then(d => d.showDialog(noteIds));
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "cut") {
 | 
			
		||||
            clipboard.cut(this.treeWidget.getSelectedOrActiveNodes(this.node));
 | 
			
		||||
            clipboard.cut(this.getSelectedOrActiveBranchIds());
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "moveTo") {
 | 
			
		||||
            const nodes = this.treeWidget.getSelectedOrActiveNodes(this.node);
 | 
			
		||||
@ -150,13 +150,13 @@ class TreeContextMenu {
 | 
			
		||||
            import("../dialogs/move_to.js").then(d => d.showDialog(nodes));
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "pasteAfter") {
 | 
			
		||||
            clipboard.pasteAfter(this.treeWidget, this.node);
 | 
			
		||||
            clipboard.pasteAfter(this.node.data.branchId);
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "pasteInto") {
 | 
			
		||||
            clipboard.pasteInto(this.node);
 | 
			
		||||
            clipboard.pasteInto(this.node.data.noteId);
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "delete") {
 | 
			
		||||
            treeChangesService.deleteNodes(this.treeWidget.getSelectedOrActiveNodes(this.node));
 | 
			
		||||
            treeChangesService.deleteNodes(this.getSelectedOrActiveBranchIds());
 | 
			
		||||
        }
 | 
			
		||||
        else if (cmd === "export") {
 | 
			
		||||
            const exportDialog = await import('../dialogs/export.js');
 | 
			
		||||
@ -193,6 +193,12 @@ class TreeContextMenu {
 | 
			
		||||
            ws.logError("Unknown command: " + cmd);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getSelectedOrActiveBranchIds() {
 | 
			
		||||
        const nodes = this.treeWidget.getSelectedOrActiveNodes(this.node);
 | 
			
		||||
 | 
			
		||||
        return nodes.map(node => node.data.branchId);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default TreeContextMenu;
 | 
			
		||||
@ -6,161 +6,186 @@ import clipboard from "./clipboard.js";
 | 
			
		||||
import utils from "./utils.js";
 | 
			
		||||
import keyboardActionService from "./keyboard_actions.js";
 | 
			
		||||
 | 
			
		||||
const fixedKeyBindings = {
 | 
			
		||||
    // code below shouldn't be necessary normally, however there's some problem with interaction with context menu plugin
 | 
			
		||||
    // after opening context menu, standard shortcuts don't work, but they are detected here
 | 
			
		||||
    // so we essentially takeover the standard handling with our implementation.
 | 
			
		||||
    "left": node => {
 | 
			
		||||
        node.navigate($.ui.keyCode.LEFT, true).then(treeService.clearSelectedNodes);
 | 
			
		||||
/**
 | 
			
		||||
 * @param {NoteTreeWidget} treeWidget
 | 
			
		||||
 */
 | 
			
		||||
function getFixedKeyBindings(treeWidget) {
 | 
			
		||||
    return {
 | 
			
		||||
        // code below shouldn't be necessary normally, however there's some problem with interaction with context menu plugin
 | 
			
		||||
        // after opening context menu, standard shortcuts don't work, but they are detected here
 | 
			
		||||
        // so we essentially takeover the standard handling with our implementation.
 | 
			
		||||
        "left": node => {
 | 
			
		||||
            node.navigate($.ui.keyCode.LEFT, true).then(treeWidget.clearSelectedNodes);
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "right": node => {
 | 
			
		||||
        node.navigate($.ui.keyCode.RIGHT, true).then(treeService.clearSelectedNodes);
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "right": node => {
 | 
			
		||||
            node.navigate($.ui.keyCode.RIGHT, true).then(treeWidget.clearSelectedNodes);
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "up": node => {
 | 
			
		||||
        node.navigate($.ui.keyCode.UP, true).then(treeService.clearSelectedNodes);
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "up": node => {
 | 
			
		||||
            node.navigate($.ui.keyCode.UP, true).then(treeWidget.clearSelectedNodes);
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "down": node => {
 | 
			
		||||
        node.navigate($.ui.keyCode.DOWN, true).then(treeService.clearSelectedNodes);
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "down": node => {
 | 
			
		||||
            node.navigate($.ui.keyCode.DOWN, true).then(treeWidget.clearSelectedNodes);
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const templates = {
 | 
			
		||||
    "DeleteNotes": node => {
 | 
			
		||||
        treeChangesService.deleteNodes(treeService.getSelectedOrActiveNodes(node));
 | 
			
		||||
    },
 | 
			
		||||
    "MoveNoteUp": node => {
 | 
			
		||||
        const beforeNode = node.getPrevSibling();
 | 
			
		||||
 | 
			
		||||
        if (beforeNode !== null) {
 | 
			
		||||
            treeChangesService.moveBeforeNode([node], beforeNode);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "MoveNoteDown": node => {
 | 
			
		||||
        const afterNode = node.getNextSibling();
 | 
			
		||||
        if (afterNode !== null) {
 | 
			
		||||
            treeChangesService.moveAfterNode([node], afterNode);
 | 
			
		||||
        }
 | 
			
		||||
/**
 | 
			
		||||
 * @param {NoteTreeWidget} treeWidget
 | 
			
		||||
 * @param {FancytreeNode} node
 | 
			
		||||
 */
 | 
			
		||||
function getSelectedOrActiveBranchIds(treeWidget, node) {
 | 
			
		||||
    const nodes = treeWidget.getSelectedOrActiveNodes(node);
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "MoveNoteUpInHierarchy": node => {
 | 
			
		||||
        treeChangesService.moveNodeUpInHierarchy(node);
 | 
			
		||||
    return nodes.map(node => node.data.branchId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "MoveNoteDownInHierarchy": node => {
 | 
			
		||||
        const toNode = node.getPrevSibling();
 | 
			
		||||
/**
 | 
			
		||||
 * @param {NoteTreeWidget} treeWidget
 | 
			
		||||
 */
 | 
			
		||||
function getTemplates(treeWidget) {
 | 
			
		||||
    return {
 | 
			
		||||
        "DeleteNotes": node => {
 | 
			
		||||
            treeChangesService.deleteNodes(getSelectedOrActiveBranchIds(treeWidget, node));
 | 
			
		||||
        },
 | 
			
		||||
        "MoveNoteUp": node => {
 | 
			
		||||
            const beforeNode = node.getPrevSibling();
 | 
			
		||||
 | 
			
		||||
        if (toNode !== null) {
 | 
			
		||||
            treeChangesService.moveToNode([node], toNode);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "AddNoteAboveToSelection": () => {
 | 
			
		||||
        const node = treeService.getFocusedNode();
 | 
			
		||||
 | 
			
		||||
        if (!node) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (node.isActive()) {
 | 
			
		||||
            node.setSelected(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const prevSibling = node.getPrevSibling();
 | 
			
		||||
 | 
			
		||||
        if (prevSibling) {
 | 
			
		||||
            prevSibling.setActive(true, {noEvents: true});
 | 
			
		||||
 | 
			
		||||
            if (prevSibling.isSelected()) {
 | 
			
		||||
                node.setSelected(false);
 | 
			
		||||
            if (beforeNode !== null) {
 | 
			
		||||
                treeChangesService.moveBeforeNode([node], beforeNode);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            prevSibling.setSelected(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "AddNoteBelowToSelection": () => {
 | 
			
		||||
        const node = treeService.getFocusedNode();
 | 
			
		||||
 | 
			
		||||
        if (!node) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (node.isActive()) {
 | 
			
		||||
            node.setSelected(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const nextSibling = node.getNextSibling();
 | 
			
		||||
 | 
			
		||||
        if (nextSibling) {
 | 
			
		||||
            nextSibling.setActive(true, {noEvents: true});
 | 
			
		||||
 | 
			
		||||
            if (nextSibling.isSelected()) {
 | 
			
		||||
                node.setSelected(false);
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "MoveNoteDown": node => {
 | 
			
		||||
            const afterNode = node.getNextSibling();
 | 
			
		||||
            if (afterNode !== null) {
 | 
			
		||||
                treeChangesService.moveAfterNode([node], afterNode);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            nextSibling.setSelected(true);
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "MoveNoteUpInHierarchy": node => {
 | 
			
		||||
            treeChangesService.moveNodeUpInHierarchy(node);
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "MoveNoteDownInHierarchy": node => {
 | 
			
		||||
            const toNode = node.getPrevSibling();
 | 
			
		||||
 | 
			
		||||
            if (toNode !== null) {
 | 
			
		||||
                treeChangesService.moveToNode([node], toNode);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "AddNoteAboveToSelection": () => {
 | 
			
		||||
            const node = treeService.getFocusedNode();
 | 
			
		||||
 | 
			
		||||
            if (!node) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (node.isActive()) {
 | 
			
		||||
                node.setSelected(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const prevSibling = node.getPrevSibling();
 | 
			
		||||
 | 
			
		||||
            if (prevSibling) {
 | 
			
		||||
                prevSibling.setActive(true, {noEvents: true});
 | 
			
		||||
 | 
			
		||||
                if (prevSibling.isSelected()) {
 | 
			
		||||
                    node.setSelected(false);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                prevSibling.setSelected(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "AddNoteBelowToSelection": () => {
 | 
			
		||||
            const node = treeWidget.getFocusedNode();
 | 
			
		||||
 | 
			
		||||
            if (!node) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (node.isActive()) {
 | 
			
		||||
                node.setSelected(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const nextSibling = node.getNextSibling();
 | 
			
		||||
 | 
			
		||||
            if (nextSibling) {
 | 
			
		||||
                nextSibling.setActive(true, {noEvents: true});
 | 
			
		||||
 | 
			
		||||
                if (nextSibling.isSelected()) {
 | 
			
		||||
                    node.setSelected(false);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                nextSibling.setSelected(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "CollapseSubtree": node => {
 | 
			
		||||
            treeWidget.collapseTree(node);
 | 
			
		||||
        },
 | 
			
		||||
        "SortChildNotes": node => {
 | 
			
		||||
            treeService.sortAlphabetically(node.data.noteId);
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "SelectAllNotesInParent": node => {
 | 
			
		||||
            for (const child of node.getParent().getChildren()) {
 | 
			
		||||
                child.setSelected(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "CopyNotesToClipboard": node => {
 | 
			
		||||
            clipboard.copy(getSelectedOrActiveBranchIds(treeWidget, node));
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "CutNotesToClipboard": node => {
 | 
			
		||||
            clipboard.cut(getSelectedOrActiveBranchIds(treeWidget, node));
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "PasteNotesFromClipboard": node => {
 | 
			
		||||
            clipboard.pasteInto(node.data.noteId);
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "EditNoteTitle": node => {
 | 
			
		||||
            noteDetailService.focusOnTitle();
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        },
 | 
			
		||||
        "ActivateParentNote": async node => {
 | 
			
		||||
            if (!await hoistedNoteService.isRootNode(node)) {
 | 
			
		||||
                node.getParent().setActive().then(treeWidget.clearSelectedNodes);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "CollapseSubtree": node => {
 | 
			
		||||
        treeService.collapseTree(node);
 | 
			
		||||
    },
 | 
			
		||||
    "SortChildNotes": node => {
 | 
			
		||||
        treeService.sortAlphabetically(node.data.noteId);
 | 
			
		||||
/**
 | 
			
		||||
 * @param {NoteTreeWidget} treeWidget
 | 
			
		||||
 */
 | 
			
		||||
async function getKeyboardBindings(treeWidget) {
 | 
			
		||||
    const bindings = Object.assign({}, getFixedKeyBindings(treeWidget));
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "SelectAllNotesInParent": node => {
 | 
			
		||||
        for (const child of node.getParent().getChildren()) {
 | 
			
		||||
            child.setSelected(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "CopyNotesToClipboard": node => {
 | 
			
		||||
        clipboard.copy(treeService.getSelectedOrActiveNodes(node));
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "CutNotesToClipboard": node => {
 | 
			
		||||
        clipboard.cut(treeService.getSelectedOrActiveNodes(node));
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "PasteNotesFromClipboard": node => {
 | 
			
		||||
        clipboard.pasteInto(node);
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "EditNoteTitle": node => {
 | 
			
		||||
        noteDetailService.focusOnTitle();
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    },
 | 
			
		||||
    "ActivateParentNote": async node => {
 | 
			
		||||
        if (!await hoistedNoteService.isRootNode(node)) {
 | 
			
		||||
            node.getParent().setActive().then(treeService.clearSelectedNodes);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
async function getKeyboardBindings() {
 | 
			
		||||
    const bindings = Object.assign({}, fixedKeyBindings);
 | 
			
		||||
    const templates = getTemplates(treeWidget);
 | 
			
		||||
 | 
			
		||||
    for (const actionName in templates) {
 | 
			
		||||
        const action = await keyboardActionService.getAction(actionName);
 | 
			
		||||
 | 
			
		||||
@ -8,10 +8,10 @@ import noteDetailService from "../services/note_detail.js";
 | 
			
		||||
import utils from "../services/utils.js";
 | 
			
		||||
import contextMenuWidget from "../services/context_menu.js";
 | 
			
		||||
import treeKeyBindingService from "../services/tree_keybindings.js";
 | 
			
		||||
import dragAndDropSetup from "../services/drag_and_drop.js";
 | 
			
		||||
import treeCache from "../services/tree_cache.js";
 | 
			
		||||
import treeBuilder from "../services/tree_builder.js";
 | 
			
		||||
import TreeContextMenu from "../services/tree_context_menu.js";
 | 
			
		||||
import treeChangesService from "../services/branches.js";
 | 
			
		||||
 | 
			
		||||
const TPL = `
 | 
			
		||||
<style>
 | 
			
		||||
@ -110,9 +110,77 @@ export default class NoteTreeWidget extends BasicWidget {
 | 
			
		||||
            collapse: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, false),
 | 
			
		||||
            init: (event, data) => treeService.treeInitialized(),
 | 
			
		||||
            hotkeys: {
 | 
			
		||||
                keydown: await treeKeyBindingService.getKeyboardBindings()
 | 
			
		||||
                keydown: await treeKeyBindingService.getKeyboardBindings(this)
 | 
			
		||||
            },
 | 
			
		||||
            dnd5: {
 | 
			
		||||
                autoExpandMS: 600,
 | 
			
		||||
                dragStart: (node, data) => {
 | 
			
		||||
                    // don't allow dragging root node
 | 
			
		||||
                    if (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise()
 | 
			
		||||
                        || node.getParent().data.noteType === 'search') {
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    node.setSelected(true);
 | 
			
		||||
 | 
			
		||||
                    const notes = this.getSelectedNodes().map(node => { return {
 | 
			
		||||
                        noteId: node.data.noteId,
 | 
			
		||||
                        title: node.title
 | 
			
		||||
                    }});
 | 
			
		||||
 | 
			
		||||
                    data.dataTransfer.setData("text", JSON.stringify(notes));
 | 
			
		||||
 | 
			
		||||
                    // This function MUST be defined to enable dragging for the tree.
 | 
			
		||||
                    // Return false to cancel dragging of node.
 | 
			
		||||
                    return true;
 | 
			
		||||
                },
 | 
			
		||||
                dragEnter: (node, data) => true, // allow drop on any node
 | 
			
		||||
                dragOver: (node, data) => true,
 | 
			
		||||
                dragDrop: async (node, data) => {
 | 
			
		||||
                    if ((data.hitMode === 'over' && node.data.noteType === 'search') ||
 | 
			
		||||
                        (['after', 'before'].includes(data.hitMode)
 | 
			
		||||
                            && (node.data.noteId === hoistedNoteService.getHoistedNoteNoPromise() || node.getParent().data.noteType === 'search'))) {
 | 
			
		||||
 | 
			
		||||
                        const infoDialog = await import('../dialogs/info.js');
 | 
			
		||||
 | 
			
		||||
                        await infoDialog.info("Dropping notes into this location is not allowed.");
 | 
			
		||||
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    const dataTransfer = data.dataTransfer;
 | 
			
		||||
 | 
			
		||||
                    if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
 | 
			
		||||
                        const files = [...dataTransfer.files]; // chrome has issue that dataTransfer.files empties after async operation
 | 
			
		||||
 | 
			
		||||
                        const importService = await import('./import.js');
 | 
			
		||||
 | 
			
		||||
                        importService.uploadFiles(node.data.noteId, files, {
 | 
			
		||||
                            safeImport: true,
 | 
			
		||||
                            shrinkImages: true,
 | 
			
		||||
                            textImportedAsText: true,
 | 
			
		||||
                            codeImportedAsCode: true,
 | 
			
		||||
                            explodeArchives: true
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        // This function MUST be defined to enable dropping of items on the tree.
 | 
			
		||||
                        // data.hitMode is 'before', 'after', or 'over'.
 | 
			
		||||
 | 
			
		||||
                        const selectedBranchIds = this.getSelectedNodes().map(node => node.data.branchId);
 | 
			
		||||
 | 
			
		||||
                        if (data.hitMode === "before") {
 | 
			
		||||
                            treeChangesService.moveBeforeNode(selectedBranchIds, node.data.branchId);
 | 
			
		||||
                        } else if (data.hitMode === "after") {
 | 
			
		||||
                            treeChangesService.moveAfterNode(selectedBranchIds, node.data.branchId);
 | 
			
		||||
                        } else if (data.hitMode === "over") {
 | 
			
		||||
                            treeChangesService.moveToNode(selectedBranchIds, node.data.noteId);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            throw new Error("Unknown hitMode=" + data.hitMode);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            dnd5: dragAndDropSetup,
 | 
			
		||||
            lazyLoad: function(event, data) {
 | 
			
		||||
                const noteId = data.node.data.noteId;
 | 
			
		||||
 | 
			
		||||
@ -195,6 +263,21 @@ export default class NoteTreeWidget extends BasicWidget {
 | 
			
		||||
        node.visit(node => node.setExpanded(false));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * focused & not active node can happen during multiselection where the node is selected but not activated
 | 
			
		||||
     * (its content is not displayed in the detail)
 | 
			
		||||
     * @return {FancytreeNode|null}
 | 
			
		||||
     */
 | 
			
		||||
    getFocusedNode() {
 | 
			
		||||
        return this.tree.getFocusNode();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    clearSelectedNodes() {
 | 
			
		||||
        for (const selectedNode of this.getSelectedNodes()) {
 | 
			
		||||
            selectedNode.setSelected(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createTopLevelNoteListener() { treeService.createNewTopLevelNote(); }
 | 
			
		||||
 | 
			
		||||
    collapseTreeListener() { this.collapseTree(); }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user