2020-01-11 21:19:56 +01:00
import hoistedNoteService from "../services/hoisted_note.js" ;
import treeService from "../services/tree.js" ;
2020-01-12 09:12:13 +01:00
import utils from "../services/utils.js" ;
2020-02-29 11:28:30 +01:00
import contextMenu from "../services/context_menu.js" ;
2020-01-12 09:12:13 +01:00
import treeCache from "../services/tree_cache.js" ;
import treeBuilder from "../services/tree_builder.js" ;
import TreeContextMenu from "../services/tree_context_menu.js" ;
2020-02-17 19:42:52 +01:00
import branchService from "../services/branches.js" ;
2020-01-12 11:15:23 +01:00
import ws from "../services/ws.js" ;
2020-01-18 18:01:16 +01:00
import TabAwareWidget from "./tab_aware_widget.js" ;
2020-01-24 15:44:24 +01:00
import server from "../services/server.js" ;
2020-02-03 20:07:34 +01:00
import noteCreateService from "../services/note_create.js" ;
2020-02-14 20:18:09 +01:00
import toastService from "../services/toast.js" ;
2020-02-16 18:11:32 +01:00
import appContext from "../services/app_context.js" ;
2020-02-16 22:14:28 +01:00
import keyboardActionsService from "../services/keyboard_actions.js" ;
import clipboard from "../services/clipboard.js" ;
2020-02-16 22:56:40 +01:00
import protectedSessionService from "../services/protected_session.js" ;
import syncService from "../services/sync.js" ;
2020-01-11 21:19:56 +01:00
const TPL = `
2020-01-12 20:15:05 +01:00
< div class = "tree" >
< style >
. tree {
overflow : auto ;
flex - grow : 1 ;
flex - shrink : 1 ;
flex - basis : 60 % ;
font - family : var ( -- tree - font - family ) ;
font - size : var ( -- tree - font - size ) ;
}
2020-03-01 19:06:26 +01:00
. refresh - search - button {
cursor : pointer ;
position : relative ;
top : - 1 px ;
border : 1 px solid transparent ;
padding : 2 px ;
border - radius : 2 px ;
}
. refresh - search - button : hover {
border - color : var ( -- button - border - color ) ;
}
2020-01-12 20:15:05 +01:00
< / s t y l e >
< / d i v >
2020-01-11 21:19:56 +01:00
` ;
2020-02-16 22:56:40 +01:00
export default class NoteTreeWidget extends TabAwareWidget {
2020-01-12 20:15:05 +01:00
doRender ( ) {
2020-02-08 21:54:39 +01:00
this . $widget = $ ( TPL ) ;
2020-01-11 21:19:56 +01:00
2020-02-08 21:54:39 +01:00
this . $widget . on ( "click" , ".unhoist-button" , hoistedNoteService . unhoist ) ;
2020-02-14 20:18:09 +01:00
this . $widget . on ( "click" , ".refresh-search-button" , ( ) => this . refreshSearch ( ) ) ;
2020-01-11 21:19:56 +01:00
// fancytree doesn't support middle click so this is a way to support it
2020-02-08 21:54:39 +01:00
this . $widget . on ( 'mousedown' , '.fancytree-title' , e => {
2020-01-11 21:19:56 +01:00
if ( e . which === 2 ) {
const node = $ . ui . fancytree . getNode ( e ) ;
2020-02-10 20:57:56 +01:00
const notePath = treeService . getNotePath ( node ) ;
if ( notePath ) {
2020-02-29 16:26:46 +01:00
appContext . tabManager . openTabWithNote ( notePath ) ;
2020-02-10 20:57:56 +01:00
}
2020-01-11 21:19:56 +01:00
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
}
} ) ;
2020-01-12 20:15:05 +01:00
2020-03-31 20:52:41 +02:00
this . initialized = this . initFancyTree ( ) ;
2020-01-12 20:15:05 +01:00
2020-02-08 21:54:39 +01:00
return this . $widget ;
2020-01-11 21:19:56 +01:00
}
2020-03-31 20:52:41 +02:00
async initFancyTree ( ) {
const treeData = [ await treeBuilder . prepareRootNode ( ) ] ;
2020-01-12 09:12:13 +01:00
2020-02-08 21:54:39 +01:00
this . $widget . fancytree ( {
2020-01-12 09:12:13 +01:00
autoScroll : true ,
keyboard : false , // we takover keyboard handling in the hotkeys plugin
2020-03-01 15:19:16 +01:00
extensions : utils . isMobile ( ) ? [ "dnd5" , "clones" ] : [ "hotkeys" , "dnd5" , "clones" ] ,
2020-01-12 09:12:13 +01:00
source : treeData ,
2020-02-08 21:54:39 +01:00
scrollParent : this . $widget ,
2020-01-12 09:12:13 +01:00
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 ) {
2020-02-10 20:57:56 +01:00
const notePath = treeService . getNotePath ( node ) ;
2020-02-29 16:26:46 +01:00
appContext . tabManager . openTabWithNote ( notePath ) ;
2020-01-12 09:12:13 +01:00
}
2020-03-01 15:19:16 +01:00
else if ( data . node . isActive ( ) ) {
// this is important for single column mobile view, otherwise it's not possible to see again previously displayed note
this . tree . reactivate ( true ) ;
}
2020-01-12 09:12:13 +01:00
else {
node . setActive ( ) ;
2020-01-12 11:15:23 +01:00
this . clearSelectedNodes ( ) ;
2020-01-12 09:12:13 +01:00
}
return false ;
}
} ,
activate : async ( event , data ) => {
// click event won't propagate so let's close context menu manually
2020-02-29 11:28:30 +01:00
contextMenu . hide ( ) ;
2020-01-12 09:12:13 +01:00
2020-02-10 20:57:56 +01:00
const notePath = treeService . getNotePath ( data . node ) ;
2020-01-12 09:12:13 +01:00
2020-02-16 18:11:32 +01:00
const activeTabContext = appContext . tabManager . getActiveTabContext ( ) ;
2020-01-24 21:15:40 +01:00
await activeTabContext . setNote ( notePath ) ;
2020-03-01 15:19:16 +01:00
if ( utils . isMobile ( ) ) {
this . triggerCommand ( 'setActiveScreen' , { screen : 'detail' } ) ;
}
2020-01-12 09:12:13 +01:00
} ,
2020-01-24 15:44:24 +01:00
expand : ( event , data ) => this . setExpandedToServer ( data . node . data . branchId , true ) ,
collapse : ( event , data ) => this . setExpandedToServer ( data . node . data . branchId , false ) ,
2020-03-01 11:53:02 +01:00
hotkeys : utils . isMobile ( ) ? undefined : { keydown : await this . getHotKeys ( ) } ,
2020-01-12 10:35:33 +01:00
dnd5 : {
autoExpandMS : 600 ,
dragStart : ( node , data ) => {
// don't allow dragging root node
2020-02-05 22:08:45 +01:00
if ( node . data . noteId === hoistedNoteService . getHoistedNoteId ( )
2020-01-12 10:35:33 +01:00
|| node . getParent ( ) . data . noteType === 'search' ) {
return false ;
}
2020-04-11 15:09:38 +02:00
const notes = this . getSelectedOrActiveNodes ( node ) . map ( node => ( {
2020-01-12 10:35:33 +01:00
noteId : node . data . noteId ,
title : node . title
2020-02-28 22:07:08 +01:00
} ) ) ;
2020-01-12 10:35:33 +01:00
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 )
2020-02-05 22:08:45 +01:00
&& ( node . data . noteId === hoistedNoteService . getHoistedNoteId ( ) || node . getParent ( ) . data . noteType === 'search' ) ) ) {
2020-01-12 10:35:33 +01:00
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
2020-01-12 12:30:30 +01:00
const importService = await import ( '../services/import.js' ) ;
2020-01-12 10:35:33 +01:00
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" ) {
2020-02-17 19:42:52 +01:00
branchService . moveBeforeBranch ( selectedBranchIds , node . data . branchId ) ;
2020-01-12 10:35:33 +01:00
} else if ( data . hitMode === "after" ) {
2020-02-17 19:42:52 +01:00
branchService . moveAfterBranch ( selectedBranchIds , node . data . branchId ) ;
2020-01-12 10:35:33 +01:00
} else if ( data . hitMode === "over" ) {
2020-02-17 19:42:52 +01:00
branchService . moveToParentNote ( selectedBranchIds , node . data . noteId ) ;
2020-01-12 10:35:33 +01:00
} else {
throw new Error ( "Unknown hitMode=" + data . hitMode ) ;
}
}
}
2020-01-12 09:12:13 +01:00
} ,
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'
2020-02-10 20:57:56 +01:00
&& node . data . noteId === hoistedNoteService . getHoistedNoteId ( )
2020-01-12 09:12:13 +01:00
&& $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 ) {
2020-02-15 09:16:23 +01:00
const refreshSearchButton = $ ( '<span> <span class="refresh-search-button bx bx-refresh" title="Refresh saved search results"></span></span>' ) ;
2020-01-12 09:12:13 +01:00
$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 ( ) ;
}
} ) ;
}
} ) ;
2020-02-08 21:54:39 +01:00
this . $widget . on ( 'contextmenu' , '.fancytree-node' , e => {
2020-01-12 09:12:13 +01:00
const node = $ . ui . fancytree . getNode ( e ) ;
2020-02-29 13:03:05 +01:00
const treeContextMenu = new TreeContextMenu ( this , node ) ;
treeContextMenu . show ( e ) ;
2020-01-12 09:12:13 +01:00
return false ; // blocks default browser right click menu
} ) ;
2020-02-08 21:54:39 +01:00
this . tree = $ . ui . fancytree . getTree ( this . $widget ) ;
2020-01-12 09:12:13 +01:00
}
/** @return {FancytreeNode[]} */
getSelectedNodes ( stopOnParents = false ) {
return this . tree . getSelectedNodes ( stopOnParents ) ;
2020-01-11 21:19:56 +01:00
}
2020-01-12 09:12:13 +01:00
/** @return {FancytreeNode[]} */
2020-01-12 11:15:23 +01:00
getSelectedOrActiveNodes ( node = null ) {
2020-02-17 22:14:39 +01:00
const notes = this . getSelectedNodes ( true ) ;
2020-01-12 09:12:13 +01:00
if ( notes . length === 0 ) {
2020-01-12 11:15:23 +01:00
notes . push ( node ? node : this . getActiveNode ( ) ) ;
2020-01-12 09:12:13 +01:00
}
return notes ;
2020-01-11 21:19:56 +01:00
}
2020-02-10 20:57:56 +01:00
collapseTree ( node = null ) {
2020-01-12 09:12:13 +01:00
if ( ! node ) {
2020-02-10 20:57:56 +01:00
const hoistedNoteId = hoistedNoteService . getHoistedNoteId ( ) ;
2020-01-12 09:12:13 +01:00
2020-01-21 22:08:41 +01:00
node = this . getNodesByNoteId ( hoistedNoteId ) [ 0 ] ;
2020-01-12 09:12:13 +01:00
}
node . setExpanded ( false ) ;
node . visit ( node => node . setExpanded ( false ) ) ;
2020-01-11 21:19:56 +01:00
}
2020-01-12 09:12:13 +01:00
2020-01-12 11:15:23 +01:00
/ * *
* @ return { FancytreeNode | null }
* /
getActiveNode ( ) {
return this . tree . getActiveNode ( ) ;
}
2020-01-12 10:35:33 +01:00
/ * *
2020-02-17 22:14:39 +01:00
* focused & not active node can happen during multiselection where the node is selected
* but not activated ( its content is not displayed in the detail )
2020-01-12 10:35:33 +01:00
* @ return { FancytreeNode | null }
* /
getFocusedNode ( ) {
return this . tree . getFocusNode ( ) ;
}
clearSelectedNodes ( ) {
for ( const selectedNode of this . getSelectedNodes ( ) ) {
selectedNode . setSelected ( false ) ;
}
}
2020-02-16 19:23:49 +01:00
async scrollToActiveNoteEvent ( ) {
2020-02-16 18:11:32 +01:00
const activeContext = appContext . tabManager . getActiveTabContext ( ) ;
2020-01-12 11:15:23 +01:00
if ( activeContext && activeContext . notePath ) {
this . tree . setFocus ( ) ;
const node = await this . expandToNote ( activeContext . notePath ) ;
await node . makeVisible ( { scrollIntoView : true } ) ;
node . setFocus ( ) ;
}
}
/** @return {FancytreeNode} */
async getNodeFromPath ( notePath , expand = false , expandOpts = { } ) {
utils . assertArguments ( notePath ) ;
2020-02-10 20:57:56 +01:00
const hoistedNoteId = hoistedNoteService . getHoistedNoteId ( ) ;
2020-01-12 11:15:23 +01:00
/** @var {FancytreeNode} */
let parentNode = null ;
const runPath = await treeService . getRunPath ( notePath ) ;
if ( ! runPath ) {
console . error ( "Could not find run path for notePath:" , notePath ) ;
return ;
}
for ( const childNoteId of runPath ) {
if ( childNoteId === hoistedNoteId ) {
// there must be exactly one node with given hoistedNoteId
parentNode = this . getNodesByNoteId ( childNoteId ) [ 0 ] ;
continue ;
}
// we expand only after hoisted note since before then nodes are not actually present in the tree
if ( parentNode ) {
if ( ! parentNode . isLoaded ( ) ) {
await parentNode . load ( ) ;
}
if ( expand ) {
await parentNode . setExpanded ( true , expandOpts ) ;
}
2020-03-18 22:42:29 +01:00
this . updateNode ( parentNode ) ;
2020-01-12 11:15:23 +01:00
let foundChildNode = this . findChildNode ( parentNode , childNoteId ) ;
if ( ! foundChildNode ) { // note might be recently created so we'll force reload and try again
await parentNode . load ( true ) ;
foundChildNode = this . findChildNode ( parentNode , childNoteId ) ;
if ( ! foundChildNode ) {
ws . logError ( ` Can't find node for child node of noteId= ${ childNoteId } for parent of noteId= ${ parentNode . data . noteId } and hoistedNoteId= ${ hoistedNoteId } , requested path is ${ notePath } ` ) ;
return ;
}
}
parentNode = foundChildNode ;
}
}
return parentNode ;
}
/** @return {FancytreeNode} */
findChildNode ( parentNode , childNoteId ) {
let foundChildNode = null ;
for ( const childNode of parentNode . getChildren ( ) ) {
if ( childNode . data . noteId === childNoteId ) {
foundChildNode = childNode ;
break ;
}
}
return foundChildNode ;
}
/** @return {FancytreeNode} */
async expandToNote ( notePath , expandOpts ) {
return this . getNodeFromPath ( notePath , true , expandOpts ) ;
}
2020-03-18 22:42:29 +01:00
updateNode ( node ) {
2020-02-12 20:07:04 +01:00
const note = treeCache . getNoteFromCache ( node . data . noteId ) ;
2020-01-29 21:38:58 +01:00
const branch = treeCache . getBranch ( node . data . branchId ) ;
2020-01-12 11:15:23 +01:00
2020-01-29 21:38:58 +01:00
node . data . isProtected = note . isProtected ;
node . data . noteType = note . type ;
2020-01-12 11:15:23 +01:00
node . folder = note . type === 'search' || note . getChildNoteIds ( ) . length > 0 ;
2020-03-18 22:42:29 +01:00
node . icon = treeBuilder . getIcon ( note ) ;
node . extraClasses = treeBuilder . getExtraClasses ( note ) ;
2020-01-29 21:38:58 +01:00
node . title = ( branch . prefix ? ( branch . prefix + " - " ) : "" ) + note . title ;
2020-01-12 11:15:23 +01:00
node . renderTitle ( ) ;
}
/** @return {FancytreeNode[]} */
2020-02-02 22:32:44 +01:00
getNodesByBranchId ( branchId ) {
2020-01-12 11:15:23 +01:00
utils . assertArguments ( branchId ) ;
const branch = treeCache . getBranch ( branchId ) ;
return this . getNodesByNoteId ( branch . noteId ) . filter ( node => node . data . branchId === branchId ) ;
}
/** @return {FancytreeNode[]} */
getNodesByNoteId ( noteId ) {
utils . assertArguments ( noteId ) ;
const list = this . tree . getNodesByRef ( noteId ) ;
return list ? list : [ ] ; // if no nodes with this refKey are found, fancy tree returns null
}
2020-03-31 20:52:41 +02:00
async reload ( ) {
const rootNode = await treeBuilder . prepareRootNode ( ) ;
await this . tree . reload ( [ rootNode ] ) ;
2020-01-12 11:15:23 +01:00
}
2020-03-15 18:57:03 +01:00
// must be event since it's triggered from outside the tree
collapseTreeEvent ( ) { this . collapseTree ( ) ; }
2020-01-12 12:30:30 +01:00
2020-02-09 21:13:05 +01:00
isEnabled ( ) {
2020-03-06 23:34:39 +01:00
return ! ! this . tabContext ;
2020-02-09 21:13:05 +01:00
}
2020-01-18 20:49:49 +01:00
async refresh ( ) {
2020-03-06 22:17:07 +01:00
this . toggleInt ( this . isEnabled ( ) ) ;
2020-02-08 21:54:39 +01:00
2020-01-18 18:01:16 +01:00
const oldActiveNode = this . getActiveNode ( ) ;
if ( oldActiveNode ) {
oldActiveNode . setActive ( false ) ;
2020-01-19 21:12:53 +01:00
oldActiveNode . setFocus ( false ) ;
2020-01-18 18:01:16 +01:00
}
2020-03-23 20:18:54 +01:00
if ( this . tabContext && this . tabContext . notePath && ! this . tabContext . note . isDeleted ) {
2020-01-18 18:01:16 +01:00
const newActiveNode = await this . getNodeFromPath ( this . tabContext . notePath ) ;
if ( newActiveNode ) {
if ( ! newActiveNode . isVisible ( ) ) {
await this . expandToNote ( this . tabContext . notePath ) ;
}
newActiveNode . setActive ( true , { noEvents : true } ) ;
2020-02-12 22:25:52 +01:00
newActiveNode . makeVisible ( { scrollIntoView : true } ) ;
2020-01-18 18:01:16 +01:00
}
}
}
2020-02-14 20:18:09 +01:00
async refreshSearch ( ) {
const activeNode = this . getActiveNode ( ) ;
activeNode . load ( true ) ;
activeNode . setExpanded ( true ) ;
toastService . showMessage ( "Saved search note refreshed." ) ;
}
2020-02-16 19:23:49 +01:00
async entitiesReloadedEvent ( { loadResults } ) {
2020-03-29 22:54:14 +02:00
const activeNode = this . getActiveNode ( ) ;
const activeNotePath = activeNode ? treeService . getNotePath ( activeNode ) : null ;
const activeNoteId = activeNode ? activeNode . data . noteId : null ;
2020-01-29 21:38:58 +01:00
const noteIdsToUpdate = new Set ( ) ;
const noteIdsToReload = new Set ( ) ;
2020-01-26 11:41:40 +01:00
2020-01-29 21:38:58 +01:00
for ( const attr of loadResults . getAttributes ( ) ) {
if ( attr . type === 'label' && [ 'iconClass' , 'cssClass' ] . includes ( attr . name ) ) {
if ( attr . isInheritable ) {
noteIdsToReload . add ( attr . noteId ) ;
}
else {
noteIdsToUpdate . add ( attr . noteId ) ;
}
}
else if ( attr . type === 'relation' && attr . name === 'template' ) {
// missing handling of things inherited from template
noteIdsToReload . add ( attr . noteId ) ;
}
}
for ( const branch of loadResults . getBranches ( ) ) {
for ( const node of this . getNodesByBranchId ( branch . branchId ) ) {
if ( branch . isDeleted ) {
2020-02-10 20:57:56 +01:00
if ( node . isActive ( ) ) {
2020-02-12 22:25:52 +01:00
const newActiveNode = node . getNextSibling ( )
|| node . getPrevSibling ( )
|| node . getParent ( ) ;
2020-01-29 21:38:58 +01:00
2020-02-12 22:25:52 +01:00
if ( newActiveNode ) {
newActiveNode . setActive ( true , { noEvents : true , noFocus : true } ) ;
}
2020-01-29 21:38:58 +01:00
}
2020-01-12 12:30:30 +01:00
2020-02-12 22:25:52 +01:00
node . remove ( ) ;
noteIdsToUpdate . add ( branch . parentNoteId ) ;
2020-01-12 12:30:30 +01:00
}
else {
2020-01-29 21:38:58 +01:00
noteIdsToUpdate . add ( branch . noteId ) ;
}
}
if ( ! branch . isDeleted ) {
for ( const parentNode of this . getNodesByNoteId ( branch . parentNoteId ) ) {
2020-02-17 22:47:50 +01:00
if ( parentNode . isFolder ( ) && ! parentNode . isLoaded ( ) ) {
2020-01-29 21:38:58 +01:00
continue ;
}
2020-02-17 22:47:50 +01:00
const found = ( parentNode . getChildren ( ) || [ ] ) . find ( child => child . data . noteId === branch . noteId ) ;
2020-01-12 12:30:30 +01:00
2020-01-29 21:38:58 +01:00
if ( ! found ) {
noteIdsToReload . add ( branch . parentNoteId ) ;
}
}
}
}
2020-02-02 22:32:44 +01:00
for ( const noteId of loadResults . getNoteIds ( ) ) {
2020-02-02 22:33:50 +01:00
noteIdsToUpdate . add ( noteId ) ;
2020-02-02 22:32:44 +01:00
}
2020-01-29 21:38:58 +01:00
for ( const noteId of noteIdsToReload ) {
for ( const node of this . getNodesByNoteId ( noteId ) ) {
await node . load ( true ) ;
2020-03-18 22:42:29 +01:00
this . updateNode ( node ) ;
2020-01-29 21:38:58 +01:00
}
}
2020-02-02 22:32:44 +01:00
for ( const noteId of noteIdsToUpdate ) {
2020-01-29 21:38:58 +01:00
for ( const node of this . getNodesByNoteId ( noteId ) ) {
2020-03-18 22:42:29 +01:00
this . updateNode ( node ) ;
2020-01-29 21:38:58 +01:00
}
}
2020-02-17 22:14:39 +01:00
for ( const parentNoteId of loadResults . getNoteReorderings ( ) ) {
2020-01-29 21:38:58 +01:00
for ( const node of this . getNodesByNoteId ( parentNoteId ) ) {
if ( node . isLoaded ( ) ) {
node . sortChildren ( ( nodeA , nodeB ) => {
const branchA = treeCache . branches [ nodeA . data . branchId ] ;
const branchB = treeCache . branches [ nodeB . data . branchId ] ;
if ( ! branchA || ! branchB ) {
return 0 ;
}
return branchA . notePosition - branchB . notePosition ;
} ) ;
2020-01-12 12:30:30 +01:00
}
}
}
2020-02-10 20:57:56 +01:00
if ( activeNotePath ) {
2020-03-29 22:54:14 +02:00
let node = await this . expandToNote ( activeNotePath ) ;
2020-04-02 22:55:11 +02:00
if ( node && node . data . noteId !== activeNoteId ) {
2020-03-29 22:54:14 +02:00
// if the active note has been moved elsewhere then it won't be found by the path
// so we switch to the alternative of trying to find it by noteId
const notesById = this . getNodesByNoteId ( activeNoteId ) ;
2020-03-18 10:08:16 +01:00
2020-03-29 22:54:14 +02:00
// if there are multiple clones then we'd rather not activate any one
node = notesById . length === 1 ? notesById [ 0 ] : null ;
}
if ( node ) {
node . setActive ( true , { noEvents : true } ) ;
}
2020-01-12 12:30:30 +01:00
}
}
2020-01-19 18:05:06 +01:00
2020-01-24 15:44:24 +01:00
async setExpandedToServer ( branchId , isExpanded ) {
utils . assertArguments ( branchId ) ;
const expandedNum = isExpanded ? 1 : 0 ;
await server . put ( 'branches/' + branchId + '/expanded/' + expandedNum ) ;
}
2020-02-01 22:29:32 +01:00
async reloadTreeFromCache ( ) {
2020-01-24 15:44:24 +01:00
const activeNode = this . getActiveNode ( ) ;
2020-02-10 20:57:56 +01:00
const activeNotePath = activeNode !== null ? treeService . getNotePath ( activeNode ) : null ;
2020-01-24 15:44:24 +01:00
2020-03-31 20:52:41 +02:00
await this . reload ( ) ;
2020-01-24 15:44:24 +01:00
if ( activeNotePath ) {
const node = await this . getNodeFromPath ( activeNotePath , true ) ;
await node . setActive ( true , { noEvents : true } ) ;
}
}
2020-02-16 19:23:49 +01:00
hoistedNoteChangedEvent ( ) {
2020-02-01 22:29:32 +01:00
this . reloadTreeFromCache ( ) ;
2020-01-24 15:44:24 +01:00
}
2020-02-16 19:23:49 +01:00
treeCacheReloadedEvent ( ) {
2020-02-01 22:29:32 +01:00
this . reloadTreeFromCache ( ) ;
2020-01-24 15:44:24 +01:00
}
2020-02-15 10:41:21 +01:00
2020-02-16 22:14:28 +01:00
async getHotKeys ( ) {
const actions = await keyboardActionsService . getActionsForScope ( 'note-tree' ) ;
const hotKeyMap = {
// 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 => {
2020-02-17 22:38:46 +01:00
node . navigate ( $ . ui . keyCode . LEFT , true ) ;
this . clearSelectedNodes ( ) ;
2020-02-16 22:14:28 +01:00
return false ;
} ,
"right" : node => {
2020-02-17 22:38:46 +01:00
node . navigate ( $ . ui . keyCode . RIGHT , true ) ;
this . clearSelectedNodes ( ) ;
2020-02-16 22:14:28 +01:00
return false ;
} ,
"up" : node => {
2020-02-17 22:38:46 +01:00
node . navigate ( $ . ui . keyCode . UP , true ) ;
this . clearSelectedNodes ( ) ;
2020-02-16 22:14:28 +01:00
return false ;
} ,
"down" : node => {
2020-02-17 22:38:46 +01:00
node . navigate ( $ . ui . keyCode . DOWN , true ) ;
this . clearSelectedNodes ( ) ;
2020-02-16 22:14:28 +01:00
return false ;
}
} ;
for ( const action of actions ) {
for ( const shortcut of action . effectiveShortcuts ) {
2020-03-17 22:49:43 +01:00
hotKeyMap [ utils . normalizeShortcut ( shortcut ) ] = node => {
this . triggerCommand ( action . actionName , { node } ) ;
return false ;
}
2020-02-16 22:14:28 +01:00
}
}
2020-02-17 22:38:46 +01:00
return hotKeyMap ;
2020-02-16 22:14:28 +01:00
}
/ * *
* @ param { FancytreeNode } node
* /
getSelectedOrActiveBranchIds ( node ) {
const nodes = this . getSelectedOrActiveNodes ( node ) ;
return nodes . map ( node => node . data . branchId ) ;
}
2020-02-16 22:56:40 +01:00
async deleteNotesCommand ( { node } ) {
2020-02-16 22:14:28 +01:00
const branchIds = this . getSelectedOrActiveBranchIds ( node ) ;
2020-02-17 19:42:52 +01:00
await branchService . deleteNotes ( branchIds ) ;
2020-02-16 22:56:40 +01:00
this . clearSelectedNodes ( ) ;
2020-02-16 22:14:28 +01:00
}
moveNoteUpCommand ( { node } ) {
const beforeNode = node . getPrevSibling ( ) ;
if ( beforeNode !== null ) {
2020-02-17 19:42:52 +01:00
branchService . moveBeforeBranch ( [ node . data . branchId ] , beforeNode . data . branchId ) ;
2020-02-16 22:14:28 +01:00
}
}
moveNoteDownCommand ( { node } ) {
const afterNode = node . getNextSibling ( ) ;
if ( afterNode !== null ) {
2020-02-17 19:42:52 +01:00
branchService . moveAfterBranch ( [ node . data . branchId ] , afterNode . data . branchId ) ;
2020-02-16 22:14:28 +01:00
}
}
moveNoteUpInHierarchyCommand ( { node } ) {
2020-02-17 19:42:52 +01:00
branchService . moveNodeUpInHierarchy ( node ) ;
2020-02-16 22:14:28 +01:00
}
moveNoteDownInHierarchyCommand ( { node } ) {
const toNode = node . getPrevSibling ( ) ;
if ( toNode !== null ) {
2020-02-17 19:42:52 +01:00
branchService . moveToParentNote ( [ node . data . branchId ] , toNode . data . noteId ) ;
2020-02-16 22:14:28 +01:00
}
}
addNoteAboveToSelectionCommand ( ) {
const node = this . 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 ) ;
}
}
addNoteBelowToSelectionCommand ( ) {
const node = this . 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 ) ;
}
}
collapseSubtreeCommand ( { node } ) {
this . collapseTree ( node ) ;
}
sortChildNotesCommand ( { node } ) {
treeService . sortAlphabetically ( node . data . noteId ) ;
}
2020-03-29 19:43:04 +02:00
async recentChangesInSubtreeCommand ( { node } ) {
const recentChangesDialog = await import ( '../dialogs/recent_changes.js' ) ;
recentChangesDialog . showDialog ( node . data . noteId ) ;
}
2020-02-16 22:14:28 +01:00
selectAllNotesInParentCommand ( { node } ) {
for ( const child of node . getParent ( ) . getChildren ( ) ) {
child . setSelected ( true ) ;
}
}
copyNotesToClipboardCommand ( { node } ) {
clipboard . copy ( this . getSelectedOrActiveBranchIds ( node ) ) ;
}
cutNotesToClipboardCommand ( { node } ) {
clipboard . cut ( this . getSelectedOrActiveBranchIds ( node ) ) ;
}
pasteNotesFromClipboardCommand ( { node } ) {
clipboard . pasteInto ( node . data . noteId ) ;
}
2020-02-16 22:56:40 +01:00
pasteNotesAfterFromClipboard ( { node } ) {
clipboard . pasteAfter ( node . data . branchId ) ;
}
async exportNoteCommand ( { node } ) {
const exportDialog = await import ( '../dialogs/export.js' ) ;
const notePath = treeService . getNotePath ( node ) ;
exportDialog . showDialog ( notePath , "subtree" ) ;
}
async importIntoNoteCommand ( { node } ) {
const importDialog = await import ( '../dialogs/import.js' ) ;
importDialog . showDialog ( node . data . noteId ) ;
}
forceNoteSyncCommand ( { node } ) {
2020-04-08 20:38:50 +02:00
syncService . forceNoteSync ( node . data . noteId ) ;
2020-02-16 22:56:40 +01:00
}
2020-02-16 22:14:28 +01:00
editNoteTitleCommand ( { node } ) {
2020-02-28 00:11:34 +01:00
appContext . triggerCommand ( 'focusOnTitle' ) ;
2020-02-16 22:14:28 +01:00
}
activateParentNoteCommand ( { node } ) {
if ( ! hoistedNoteService . isRootNode ( node ) ) {
node . getParent ( ) . setActive ( ) . then ( this . clearSelectedNodes ) ;
}
}
2020-02-16 22:56:40 +01:00
protectSubtreeCommand ( { node } ) {
2020-02-26 16:37:17 +01:00
protectedSessionService . protectNote ( node . data . noteId , true , true ) ;
2020-02-16 22:56:40 +01:00
}
unprotectSubtreeCommand ( { node } ) {
2020-02-26 16:37:17 +01:00
protectedSessionService . protectNote ( node . data . noteId , false , true ) ;
2020-02-16 22:56:40 +01:00
}
duplicateNoteCommand ( { node } ) {
const branch = treeCache . getBranch ( node . data . branchId ) ;
2020-03-15 21:52:04 +01:00
noteCreateService . duplicateNote ( node . data . noteId , branch . parentNoteId ) ;
2020-02-16 22:56:40 +01:00
}
2020-01-11 21:19:56 +01:00
}