mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
moved tree initialization into the widget
This commit is contained in:
parent
9e031dcd60
commit
0f8a7bad06
@ -4,10 +4,11 @@ const $contextMenuContainer = $("#context-menu-container");
|
|||||||
let dateContextMenuOpenedMs = 0;
|
let dateContextMenuOpenedMs = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {NoteTreeWidget} treeWidget
|
||||||
* @param event - originating click event (used to get coordinates to display menu at position)
|
* @param event - originating click event (used to get coordinates to display menu at position)
|
||||||
* @param {object} contextMenu - needs to have getContextMenuItems() and selectContextMenuItem(e, cmd)
|
* @param {object} contextMenu - needs to have getContextMenuItems() and selectContextMenuItem(e, cmd)
|
||||||
*/
|
*/
|
||||||
async function initContextMenu(event, contextMenu) {
|
async function initContextMenu(treeWidget, event, contextMenu) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
$contextMenuContainer.empty();
|
$contextMenuContainer.empty();
|
||||||
|
@ -18,6 +18,10 @@ import keyboardActionService from "./keyboard_actions.js";
|
|||||||
|
|
||||||
let tree;
|
let tree;
|
||||||
|
|
||||||
|
function setTree(treeInstance) {
|
||||||
|
tree = treeInstance;
|
||||||
|
}
|
||||||
|
|
||||||
let setFrontendAsLoaded;
|
let setFrontendAsLoaded;
|
||||||
const frontendLoaded = new Promise(resolve => { setFrontendAsLoaded = resolve; });
|
const frontendLoaded = new Promise(resolve => { setFrontendAsLoaded = resolve; });
|
||||||
|
|
||||||
@ -424,104 +428,6 @@ async function treeInitialized() {
|
|||||||
setFrontendAsLoaded();
|
setFrontendAsLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initFancyTree($tree, treeData) {
|
|
||||||
utils.assertArguments(treeData);
|
|
||||||
|
|
||||||
$tree.fancytree({
|
|
||||||
autoScroll: true,
|
|
||||||
keyboard: false, // we takover keyboard handling in the hotkeys plugin
|
|
||||||
extensions: ["hotkeys", "dnd5", "clones"],
|
|
||||||
source: treeData,
|
|
||||||
scrollParent: $tree,
|
|
||||||
minExpandLevel: 2, // root can't be collapsed
|
|
||||||
click: (event, data) => {
|
|
||||||
const targetType = data.targetType;
|
|
||||||
const node = data.node;
|
|
||||||
|
|
||||||
if (targetType === 'title' || targetType === 'icon') {
|
|
||||||
if (event.shiftKey) {
|
|
||||||
node.setSelected(!node.isSelected());
|
|
||||||
node.setFocus(true);
|
|
||||||
}
|
|
||||||
else if (event.ctrlKey) {
|
|
||||||
noteDetailService.loadNoteDetail(node.data.noteId, { newTab: true });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
node.setActive();
|
|
||||||
|
|
||||||
clearSelectedNodes();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
activate: async (event, data) => {
|
|
||||||
// click event won't propagate so let's close context menu manually
|
|
||||||
contextMenuWidget.hideContextMenu();
|
|
||||||
|
|
||||||
const notePath = await treeUtils.getNotePath(data.node);
|
|
||||||
|
|
||||||
noteDetailService.switchToNote(notePath);
|
|
||||||
},
|
|
||||||
expand: (event, data) => setExpandedToServer(data.node.data.branchId, true),
|
|
||||||
collapse: (event, data) => setExpandedToServer(data.node.data.branchId, false),
|
|
||||||
init: (event, data) => treeInitialized(), // don't collapse to short form
|
|
||||||
hotkeys: {
|
|
||||||
keydown: await treeKeyBindingService.getKeyboardBindings()
|
|
||||||
},
|
|
||||||
dnd5: dragAndDropSetup,
|
|
||||||
lazyLoad: function(event, data) {
|
|
||||||
const noteId = data.node.data.noteId;
|
|
||||||
|
|
||||||
data.result = treeCache.getNote(noteId).then(note => treeBuilder.prepareBranch(note));
|
|
||||||
},
|
|
||||||
clones: {
|
|
||||||
highlightActiveClones: true
|
|
||||||
},
|
|
||||||
enhanceTitle: async function (event, data) {
|
|
||||||
const node = data.node;
|
|
||||||
const $span = $(node.span);
|
|
||||||
|
|
||||||
if (node.data.noteId !== 'root'
|
|
||||||
&& node.data.noteId === await hoistedNoteService.getHoistedNoteId()
|
|
||||||
&& $span.find('.unhoist-button').length === 0) {
|
|
||||||
|
|
||||||
const unhoistButton = $('<span> (<a class="unhoist-button">unhoist</a>)</span>');
|
|
||||||
|
|
||||||
$span.append(unhoistButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
const note = await treeCache.getNote(node.data.noteId);
|
|
||||||
|
|
||||||
if (note.type === 'search' && $span.find('.refresh-search-button').length === 0) {
|
|
||||||
const refreshSearchButton = $('<span> <span class="refresh-search-button bx bx-recycle" title="Refresh saved search results"></span></span>');
|
|
||||||
|
|
||||||
$span.append(refreshSearchButton);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// this is done to automatically lazy load all expanded search notes after tree load
|
|
||||||
loadChildren: (event, data) => {
|
|
||||||
data.node.visit((subNode) => {
|
|
||||||
// Load all lazy/unloaded child nodes
|
|
||||||
// (which will trigger `loadChildren` recursively)
|
|
||||||
if (subNode.isUndefined() && subNode.isExpanded()) {
|
|
||||||
subNode.load();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$tree.on('contextmenu', '.fancytree-node', function(e) {
|
|
||||||
const node = $.ui.fancytree.getNode(e);
|
|
||||||
|
|
||||||
contextMenuWidget.initContextMenu(e, new TreeContextMenu(node));
|
|
||||||
|
|
||||||
return false; // blocks default browser right click menu
|
|
||||||
});
|
|
||||||
|
|
||||||
tree = $.ui.fancytree.getTree("#tree");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function reload() {
|
async function reload() {
|
||||||
const notes = await loadTreeData();
|
const notes = await loadTreeData();
|
||||||
|
|
||||||
@ -745,12 +651,6 @@ async function sortAlphabetically(noteId) {
|
|||||||
await reload();
|
await reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showTree($tree) {
|
|
||||||
const treeData = await loadTreeData();
|
|
||||||
|
|
||||||
await initFancyTree($tree, treeData);
|
|
||||||
}
|
|
||||||
|
|
||||||
ws.subscribeToMessages(message => {
|
ws.subscribeToMessages(message => {
|
||||||
if (message.type === 'refresh-tree') {
|
if (message.type === 'refresh-tree') {
|
||||||
reload();
|
reload();
|
||||||
@ -908,7 +808,6 @@ export default {
|
|||||||
getSelectedOrActiveNodes,
|
getSelectedOrActiveNodes,
|
||||||
clearSelectedNodes,
|
clearSelectedNodes,
|
||||||
sortAlphabetically,
|
sortAlphabetically,
|
||||||
showTree,
|
|
||||||
loadTreeData,
|
loadTreeData,
|
||||||
treeInitialized,
|
treeInitialized,
|
||||||
setExpandedToServer,
|
setExpandedToServer,
|
||||||
@ -923,5 +822,6 @@ export default {
|
|||||||
scrollToActiveNote,
|
scrollToActiveNote,
|
||||||
createNewTopLevelNote,
|
createNewTopLevelNote,
|
||||||
duplicateNote,
|
duplicateNote,
|
||||||
getNodeByKey
|
getNodeByKey,
|
||||||
|
setTree
|
||||||
};
|
};
|
@ -12,7 +12,12 @@ import protectedSessionHolder from "./protected_session_holder.js";
|
|||||||
import searchNotesService from "./search_notes.js";
|
import searchNotesService from "./search_notes.js";
|
||||||
|
|
||||||
class TreeContextMenu {
|
class TreeContextMenu {
|
||||||
constructor(node) {
|
/**
|
||||||
|
* @param {NoteTreeWidget} treeWidget
|
||||||
|
* @param {FancytreeNode} node
|
||||||
|
*/
|
||||||
|
constructor(treeWidget, node) {
|
||||||
|
this.treeWidget = treeWidget;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +42,7 @@ class TreeContextMenu {
|
|||||||
// some actions don't support multi-note so they are disabled when notes are selected
|
// some actions don't support multi-note so they are disabled when notes are selected
|
||||||
// the only exception is when the only selected note is the one that was right-clicked, then
|
// the only exception is when the only selected note is the one that was right-clicked, then
|
||||||
// it's clear what the user meant to do.
|
// it's clear what the user meant to do.
|
||||||
const selNodes = treeService.getSelectedNodes();
|
const selNodes = this.treeWidget.getSelectedNodes();
|
||||||
const noSelectedNotes = selNodes.length === 0
|
const noSelectedNotes = selNodes.length === 0
|
||||||
|| (selNodes.length === 1 && selNodes[0] === this.node);
|
|| (selNodes.length === 1 && selNodes[0] === this.node);
|
||||||
|
|
||||||
@ -128,19 +133,19 @@ class TreeContextMenu {
|
|||||||
protectedSessionService.protectSubtree(this.node.data.noteId, false);
|
protectedSessionService.protectSubtree(this.node.data.noteId, false);
|
||||||
}
|
}
|
||||||
else if (cmd === "copy") {
|
else if (cmd === "copy") {
|
||||||
clipboard.copy(treeService.getSelectedOrActiveNodes(this.node));
|
clipboard.copy(this.treeWidget.getSelectedOrActiveNodes(this.node));
|
||||||
}
|
}
|
||||||
else if (cmd === "cloneTo") {
|
else if (cmd === "cloneTo") {
|
||||||
const nodes = treeService.getSelectedOrActiveNodes(this.node);
|
const nodes = this.treeWidget.getSelectedOrActiveNodes(this.node);
|
||||||
const noteIds = nodes.map(node => node.data.noteId);
|
const noteIds = nodes.map(node => node.data.noteId);
|
||||||
|
|
||||||
import("../dialogs/clone_to.js").then(d => d.showDialog(noteIds));
|
import("../dialogs/clone_to.js").then(d => d.showDialog(noteIds));
|
||||||
}
|
}
|
||||||
else if (cmd === "cut") {
|
else if (cmd === "cut") {
|
||||||
clipboard.cut(treeService.getSelectedOrActiveNodes(this.node));
|
clipboard.cut(this.treeWidget.getSelectedOrActiveNodes(this.node));
|
||||||
}
|
}
|
||||||
else if (cmd === "moveTo") {
|
else if (cmd === "moveTo") {
|
||||||
const nodes = treeService.getSelectedOrActiveNodes(this.node);
|
const nodes = this.treeWidget.getSelectedOrActiveNodes(this.node);
|
||||||
|
|
||||||
import("../dialogs/move_to.js").then(d => d.showDialog(nodes));
|
import("../dialogs/move_to.js").then(d => d.showDialog(nodes));
|
||||||
}
|
}
|
||||||
@ -151,7 +156,7 @@ class TreeContextMenu {
|
|||||||
clipboard.pasteInto(this.node);
|
clipboard.pasteInto(this.node);
|
||||||
}
|
}
|
||||||
else if (cmd === "delete") {
|
else if (cmd === "delete") {
|
||||||
treeChangesService.deleteNodes(treeService.getSelectedOrActiveNodes(this.node));
|
treeChangesService.deleteNodes(this.treeWidget.getSelectedOrActiveNodes(this.node));
|
||||||
}
|
}
|
||||||
else if (cmd === "export") {
|
else if (cmd === "export") {
|
||||||
const exportDialog = await import('../dialogs/export.js');
|
const exportDialog = await import('../dialogs/export.js');
|
||||||
@ -162,7 +167,7 @@ class TreeContextMenu {
|
|||||||
importDialog.showDialog(this.node);
|
importDialog.showDialog(this.node);
|
||||||
}
|
}
|
||||||
else if (cmd === "collapseSubtree") {
|
else if (cmd === "collapseSubtree") {
|
||||||
treeService.collapseTree(this.node);
|
this.treeWidget.collapseTree(this.node);
|
||||||
}
|
}
|
||||||
else if (cmd === "forceNoteSync") {
|
else if (cmd === "forceNoteSync") {
|
||||||
syncService.forceNoteSync(this.node.data.noteId);
|
syncService.forceNoteSync(this.node.data.noteId);
|
||||||
|
@ -5,6 +5,13 @@ import keyboardActionService from "../services/keyboard_actions.js";
|
|||||||
import treeService from "../services/tree.js";
|
import treeService from "../services/tree.js";
|
||||||
import treeUtils from "../services/tree_utils.js";
|
import treeUtils from "../services/tree_utils.js";
|
||||||
import noteDetailService from "../services/note_detail.js";
|
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";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<style>
|
<style>
|
||||||
@ -22,16 +29,25 @@ const TPL = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export default class NoteTreeWidget extends BasicWidget {
|
export default class NoteTreeWidget extends BasicWidget {
|
||||||
|
constructor(appContext) {
|
||||||
|
super(appContext);
|
||||||
|
|
||||||
|
this.tree = null;
|
||||||
|
}
|
||||||
|
|
||||||
async doRender($widget) {
|
async doRender($widget) {
|
||||||
$widget.append($(TPL));
|
$widget.append($(TPL));
|
||||||
|
|
||||||
const $tree = $widget.find('#tree');
|
const $tree = $widget.find('#tree');
|
||||||
|
|
||||||
await treeService.showTree($tree);
|
const treeData = await treeService.loadTreeData();
|
||||||
|
|
||||||
|
await this.initFancyTree($tree, treeData);
|
||||||
|
|
||||||
$tree.on("click", ".unhoist-button", hoistedNoteService.unhoist);
|
$tree.on("click", ".unhoist-button", hoistedNoteService.unhoist);
|
||||||
$tree.on("click", ".refresh-search-button", searchNotesService.refreshSearch);
|
$tree.on("click", ".refresh-search-button", searchNotesService.refreshSearch);
|
||||||
|
|
||||||
|
// this does not belong here ...
|
||||||
keyboardActionService.setGlobalActionHandler('CollapseTree', () => treeService.collapseTree()); // don't use shortened form since collapseTree() accepts argument
|
keyboardActionService.setGlobalActionHandler('CollapseTree', () => treeService.collapseTree()); // don't use shortened form since collapseTree() accepts argument
|
||||||
|
|
||||||
// fancytree doesn't support middle click so this is a way to support it
|
// fancytree doesn't support middle click so this is a way to support it
|
||||||
@ -51,15 +67,137 @@ export default class NoteTreeWidget extends BasicWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createTopLevelNoteListener() {
|
async initFancyTree($tree, treeData) {
|
||||||
treeService.createNewTopLevelNote();
|
utils.assertArguments(treeData);
|
||||||
|
|
||||||
|
$tree.fancytree({
|
||||||
|
autoScroll: true,
|
||||||
|
keyboard: false, // we takover keyboard handling in the hotkeys plugin
|
||||||
|
extensions: ["hotkeys", "dnd5", "clones"],
|
||||||
|
source: treeData,
|
||||||
|
scrollParent: $tree,
|
||||||
|
minExpandLevel: 2, // root can't be collapsed
|
||||||
|
click: (event, data) => {
|
||||||
|
const targetType = data.targetType;
|
||||||
|
const node = data.node;
|
||||||
|
|
||||||
|
if (targetType === 'title' || targetType === 'icon') {
|
||||||
|
if (event.shiftKey) {
|
||||||
|
node.setSelected(!node.isSelected());
|
||||||
|
node.setFocus(true);
|
||||||
|
}
|
||||||
|
else if (event.ctrlKey) {
|
||||||
|
noteDetailService.loadNoteDetail(node.data.noteId, { newTab: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node.setActive();
|
||||||
|
|
||||||
|
treeService.clearSelectedNodes();
|
||||||
}
|
}
|
||||||
|
|
||||||
collapseTreeListener() {
|
return false;
|
||||||
treeService.collapseTree();
|
}
|
||||||
|
},
|
||||||
|
activate: async (event, data) => {
|
||||||
|
// click event won't propagate so let's close context menu manually
|
||||||
|
contextMenuWidget.hideContextMenu();
|
||||||
|
|
||||||
|
const notePath = await treeUtils.getNotePath(data.node);
|
||||||
|
|
||||||
|
noteDetailService.switchToNote(notePath);
|
||||||
|
},
|
||||||
|
expand: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, true),
|
||||||
|
collapse: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, false),
|
||||||
|
init: (event, data) => treeService.treeInitialized(),
|
||||||
|
hotkeys: {
|
||||||
|
keydown: await treeKeyBindingService.getKeyboardBindings()
|
||||||
|
},
|
||||||
|
dnd5: dragAndDropSetup,
|
||||||
|
lazyLoad: function(event, data) {
|
||||||
|
const noteId = data.node.data.noteId;
|
||||||
|
|
||||||
|
data.result = treeCache.getNote(noteId).then(note => treeBuilder.prepareBranch(note));
|
||||||
|
},
|
||||||
|
clones: {
|
||||||
|
highlightActiveClones: true
|
||||||
|
},
|
||||||
|
enhanceTitle: async function (event, data) {
|
||||||
|
const node = data.node;
|
||||||
|
const $span = $(node.span);
|
||||||
|
|
||||||
|
if (node.data.noteId !== 'root'
|
||||||
|
&& node.data.noteId === await hoistedNoteService.getHoistedNoteId()
|
||||||
|
&& $span.find('.unhoist-button').length === 0) {
|
||||||
|
|
||||||
|
const unhoistButton = $('<span> (<a class="unhoist-button">unhoist</a>)</span>');
|
||||||
|
|
||||||
|
$span.append(unhoistButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToActiveNoteListener() {
|
const note = await treeCache.getNote(node.data.noteId);
|
||||||
treeService.scrollToActiveNote();
|
|
||||||
|
if (note.type === 'search' && $span.find('.refresh-search-button').length === 0) {
|
||||||
|
const refreshSearchButton = $('<span> <span class="refresh-search-button bx bx-recycle" title="Refresh saved search results"></span></span>');
|
||||||
|
|
||||||
|
$span.append(refreshSearchButton);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// this is done to automatically lazy load all expanded search notes after tree load
|
||||||
|
loadChildren: (event, data) => {
|
||||||
|
data.node.visit((subNode) => {
|
||||||
|
// Load all lazy/unloaded child nodes
|
||||||
|
// (which will trigger `loadChildren` recursively)
|
||||||
|
if (subNode.isUndefined() && subNode.isExpanded()) {
|
||||||
|
subNode.load();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$tree.on('contextmenu', '.fancytree-node', e => {
|
||||||
|
const node = $.ui.fancytree.getNode(e);
|
||||||
|
|
||||||
|
contextMenuWidget.initContextMenu(this, e, new TreeContextMenu(this, node));
|
||||||
|
|
||||||
|
return false; // blocks default browser right click menu
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tree = $.ui.fancytree.getTree("#tree");
|
||||||
|
|
||||||
|
treeService.setTree(this.tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return {FancytreeNode[]} */
|
||||||
|
getSelectedNodes(stopOnParents = false) {
|
||||||
|
return this.tree.getSelectedNodes(stopOnParents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return {FancytreeNode[]} */
|
||||||
|
getSelectedOrActiveNodes(node) {
|
||||||
|
let notes = this.getSelectedNodes(true);
|
||||||
|
|
||||||
|
if (notes.length === 0) {
|
||||||
|
notes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async collapseTree(node = null) {
|
||||||
|
if (!node) {
|
||||||
|
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||||
|
|
||||||
|
node = getNodesByNoteId(hoistedNoteId)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
node.setExpanded(false);
|
||||||
|
|
||||||
|
node.visit(node => node.setExpanded(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
createTopLevelNoteListener() { treeService.createNewTopLevelNote(); }
|
||||||
|
|
||||||
|
collapseTreeListener() { this.collapseTree(); }
|
||||||
|
|
||||||
|
scrollToActiveNoteListener() { treeService.scrollToActiveNote(); }
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user