2022-12-01 13:07:23 +01:00
import Component from "./component.js" ;
import SpacedUpdate from "../services/spaced_update.js" ;
import server from "../services/server.js" ;
import options from "../services/options.js" ;
import froca from "../services/froca.js" ;
import treeService from "../services/tree.js" ;
import utils from "../services/utils.js" ;
2021-05-22 12:35:41 +02:00
import NoteContext from "./note_context.js" ;
2020-04-25 23:52:13 +02:00
import appContext from "./app_context.js" ;
2022-02-09 21:21:17 +01:00
import Mutex from "../utils/mutex.js" ;
2023-04-11 21:41:55 +02:00
import linkService from "../services/link.js" ;
2020-02-07 21:08:55 +01:00
export default class TabManager extends Component {
2020-02-27 10:03:14 +01:00
constructor ( ) {
super ( ) ;
2020-02-07 21:08:55 +01:00
2023-05-05 23:17:23 +02:00
/** @property {NoteContext[]} */
this . children = [ ] ;
2022-02-09 21:21:17 +01:00
this . mutex = new Mutex ( ) ;
2021-05-22 13:04:08 +02:00
this . activeNtxId = null ;
2020-02-07 21:08:55 +01:00
2023-06-03 05:54:33 +08:00
// elements are arrays of {contexts, position}, storing note contexts for each tab (one main context + subcontexts [splits]), and the original position of the tab
2021-10-09 21:20:12 +02:00
this . recentlyClosedTabs = [ ] ;
2020-02-07 21:08:55 +01:00
this . tabsUpdate = new SpacedUpdate ( async ( ) => {
2020-04-25 23:52:13 +02:00
if ( ! appContext . isMainWindow ) {
return ;
}
2023-04-11 22:00:04 +02:00
const openNoteContexts = this . noteContexts
. map ( nc => nc . getPojoState ( ) )
2020-02-07 21:08:55 +01:00
. filter ( t => ! ! t ) ;
await server . put ( 'options' , {
2023-04-11 22:00:04 +02:00
openNoteContexts : JSON . stringify ( openNoteContexts )
2020-02-07 21:08:55 +01:00
} ) ;
} ) ;
2021-02-27 23:39:02 +01:00
appContext . addBeforeUnloadListener ( this ) ;
2020-02-07 21:08:55 +01:00
}
2023-05-05 23:17:23 +02:00
/** @returns {NoteContext[]} */
2021-05-22 12:26:45 +02:00
get noteContexts ( ) {
2020-02-09 21:13:05 +01:00
return this . children ;
}
2021-05-22 12:26:45 +02:00
/** @type {NoteContext[]} */
get mainNoteContexts ( ) {
return this . noteContexts . filter ( nc => ! nc . mainNtxId )
2021-05-20 23:13:34 +02:00
}
2020-04-25 23:52:13 +02:00
async loadTabs ( ) {
2022-12-14 10:00:33 +01:00
try {
2023-05-07 21:18:21 +02:00
const noteContextsToOpen = ( appContext . isMainWindow && options . getJson ( 'openNoteContexts' ) ) || [ ] ;
2022-12-14 10:00:33 +01:00
// preload all notes at once
await froca . getNotes ( [
2023-05-07 21:18:21 +02:00
... noteContextsToOpen . flatMap ( tab =>
2023-05-29 22:37:19 +02:00
[ treeService . getNoteIdFromUrl ( tab . notePath ) , tab . hoistedNoteId ]
2023-05-07 21:18:21 +02:00
) ,
2022-12-14 10:00:33 +01:00
] , true ) ;
2023-04-11 22:00:04 +02:00
const filteredNoteContexts = noteContextsToOpen . filter ( openTab => {
2023-04-11 21:41:55 +02:00
if ( utils . isMobile ( ) ) { // mobile frontend doesn't have tabs so show only the active tab
return ! ! openTab . active ;
}
2023-05-29 22:37:19 +02:00
const noteId = treeService . getNoteIdFromUrl ( openTab . notePath ) ;
2023-04-11 21:41:55 +02:00
if ( ! ( noteId in froca . notes ) ) {
2022-12-14 10:00:33 +01:00
// note doesn't exist so don't try to open tab for it
2023-04-11 21:41:55 +02:00
return false ;
2022-12-14 10:00:33 +01:00
}
if ( ! ( openTab . hoistedNoteId in froca . notes ) ) {
openTab . hoistedNoteId = 'root' ;
}
2023-04-11 21:41:55 +02:00
return true ;
} ) ;
2022-12-13 21:45:57 +01:00
2023-01-31 23:20:11 +01:00
// resolve before opened tabs can change this
2023-05-07 21:18:21 +02:00
const parsedFromUrl = linkService . parseNavigationStateFromUrl ( window . location . href ) ;
2022-12-22 23:38:57 +01:00
2023-04-11 22:00:04 +02:00
if ( filteredNoteContexts . length === 0 ) {
2023-04-11 21:41:55 +02:00
parsedFromUrl . ntxId = parsedFromUrl . ntxId || NoteContext . generateNtxId ( ) ; // generate already here, so that we later know which one to activate
2023-04-11 22:00:04 +02:00
filteredNoteContexts . push ( {
2023-04-11 21:41:55 +02:00
notePath : parsedFromUrl . notePath || 'root' ,
ntxId : parsedFromUrl . ntxId ,
2022-12-14 10:00:33 +01:00
active : true ,
2023-04-11 21:41:55 +02:00
hoistedNoteId : parsedFromUrl . hoistedNoteId || 'root' ,
viewScope : parsedFromUrl . viewScope || { }
2022-12-14 10:00:33 +01:00
} ) ;
2023-04-11 22:00:04 +02:00
} else if ( ! filteredNoteContexts . find ( tab => tab . active ) ) {
filteredNoteContexts [ 0 ] . active = true ;
2022-12-14 10:00:33 +01:00
}
2020-02-07 21:08:55 +01:00
2022-12-14 10:00:33 +01:00
await this . tabsUpdate . allowUpdateWithoutChange ( async ( ) => {
2023-04-11 22:00:04 +02:00
for ( const tab of filteredNoteContexts ) {
2023-02-14 16:06:49 +01:00
await this . openContextWithNote ( tab . notePath , {
activate : tab . active ,
ntxId : tab . ntxId ,
mainNtxId : tab . mainNtxId ,
hoistedNoteId : tab . hoistedNoteId ,
2023-04-03 23:47:24 +02:00
viewScope : tab . viewScope
2023-02-14 16:06:49 +01:00
} ) ;
2022-12-14 10:00:33 +01:00
}
2020-02-07 21:08:55 +01:00
} ) ;
2023-05-07 21:18:21 +02:00
// if there's a notePath in the URL, make sure it's open and active
// (useful, for e.g., opening clipped notes from clipper or opening link in an extra window)
2023-04-11 21:41:55 +02:00
if ( parsedFromUrl . notePath ) {
await appContext . tabManager . switchToNoteContext (
parsedFromUrl . ntxId ,
parsedFromUrl . notePath ,
parsedFromUrl . viewScope ,
parsedFromUrl . hoistedNoteId
) ;
2023-07-15 10:59:30 +02:00
} else if ( parsedFromUrl . searchString ) {
await appContext . triggerCommand ( 'searchNotes' , {
searchString : parsedFromUrl . searchString
} ) ;
2020-02-07 21:08:55 +01:00
}
2022-12-14 10:00:33 +01:00
}
catch ( e ) {
2023-04-11 22:00:04 +02:00
logError ( ` Loading note contexts ' ${ options . get ( 'openNoteContexts' ) } ' failed: ${ e . message } ${ e . stack } ` ) ;
2022-12-13 16:57:46 +01:00
2022-12-14 10:00:33 +01:00
// try to recover
await this . openEmptyTab ( ) ;
2022-12-13 16:57:46 +01:00
}
2020-02-07 21:08:55 +01:00
}
2021-05-22 12:35:41 +02:00
noteSwitchedEvent ( { noteContext } ) {
2021-05-22 12:26:45 +02:00
if ( noteContext . isActive ( ) ) {
2023-04-11 21:41:55 +02:00
this . setCurrentNavigationStateToHash ( ) ;
2020-02-07 21:08:55 +01:00
}
2020-02-08 21:23:42 +01:00
this . tabsUpdate . scheduleUpdate ( ) ;
2020-02-07 21:08:55 +01:00
}
2023-04-11 21:41:55 +02:00
setCurrentNavigationStateToHash ( ) {
const calculatedHash = this . calculateHash ( ) ;
2020-02-09 21:13:05 +01:00
2023-04-11 21:41:55 +02:00
// update if it's the first history entry or there has been a change
if ( window . history . length === 0 || calculatedHash !== window . location ? . hash ) {
2020-02-09 21:13:05 +01:00
// using pushState instead of directly modifying document.location because it does not trigger hashchange
2023-04-11 21:41:55 +02:00
window . history . pushState ( null , "" , calculatedHash ) ;
2020-10-26 20:11:43 +01:00
}
2020-02-09 21:13:05 +01:00
2023-04-11 21:41:55 +02:00
const activeNoteContext = this . getActiveContext ( ) ;
2022-01-03 19:52:24 +01:00
this . updateDocumentTitle ( activeNoteContext ) ;
2020-03-08 17:17:18 +01:00
this . triggerEvent ( 'activeNoteChanged' ) ; // trigger this even in on popstate event
2020-02-07 21:08:55 +01:00
}
2023-04-11 21:41:55 +02:00
calculateHash ( ) {
const activeNoteContext = this . getActiveContext ( ) ;
if ( ! activeNoteContext ) {
return "" ;
}
return linkService . calculateHash ( {
notePath : activeNoteContext . notePath ,
ntxId : activeNoteContext . ntxId ,
hoistedNoteId : activeNoteContext . hoistedNoteId ,
viewScope : activeNoteContext . viewScope
} ) ;
}
2021-10-24 14:53:45 +02:00
/** @returns {NoteContext[]} */
2021-05-22 12:26:45 +02:00
getNoteContexts ( ) {
return this . noteContexts ;
2020-02-07 21:08:55 +01:00
}
2023-09-13 09:01:55 +02:00
/ * *
* Main context is essentially a tab ( children are splits ) , so this returns tabs .
* @ returns { NoteContext [ ] }
* /
2022-06-29 22:38:35 +02:00
getMainNoteContexts ( ) {
return this . noteContexts . filter ( nc => nc . isMainContext ( ) ) ;
}
2021-05-22 12:26:45 +02:00
/** @returns {NoteContext} */
getNoteContextById ( ntxId ) {
const noteContext = this . noteContexts . find ( nc => nc . ntxId === ntxId ) ;
2021-05-20 23:13:34 +02:00
2021-05-22 12:26:45 +02:00
if ( ! noteContext ) {
throw new Error ( ` Cannot find noteContext id=' ${ ntxId } ' ` ) ;
2021-05-20 23:13:34 +02:00
}
2021-05-22 12:26:45 +02:00
return noteContext ;
2020-02-07 21:08:55 +01:00
}
2023-09-13 09:01:55 +02:00
/ * *
* Get active context which represents the visible split with focus . Active context can , but doesn ' t have to be "main" .
*
* @ returns { NoteContext }
* /
2021-05-22 12:35:41 +02:00
getActiveContext ( ) {
2021-05-22 13:04:08 +02:00
return this . activeNtxId
? this . getNoteContextById ( this . activeNtxId )
: null ;
}
2023-09-13 09:01:55 +02:00
/ * *
* Get active main context which corresponds to the active tab .
*
* @ returns { NoteContext }
* /
2021-05-22 13:04:08 +02:00
getActiveMainContext ( ) {
return this . activeNtxId
? this . getNoteContextById ( this . activeNtxId ) . getMainContext ( )
2021-05-20 23:13:34 +02:00
: null ;
2020-02-07 21:08:55 +01:00
}
/** @returns {string|null} */
2021-05-22 12:35:41 +02:00
getActiveContextNotePath ( ) {
const activeContext = this . getActiveContext ( ) ;
2020-02-07 21:08:55 +01:00
return activeContext ? activeContext . notePath : null ;
}
2023-01-03 13:35:10 +01:00
/** @returns {FNote} */
2021-05-22 12:35:41 +02:00
getActiveContextNote ( ) {
const activeContext = this . getActiveContext ( ) ;
2020-02-07 21:08:55 +01:00
return activeContext ? activeContext . note : null ;
}
2021-10-24 14:53:45 +02:00
/** @returns {string|null} */
2021-05-22 12:35:41 +02:00
getActiveContextNoteId ( ) {
const activeNote = this . getActiveContextNote ( ) ;
2020-02-07 21:08:55 +01:00
return activeNote ? activeNote . noteId : null ;
}
2021-10-24 14:53:45 +02:00
/** @returns {string|null} */
2021-05-22 12:35:41 +02:00
getActiveContextNoteType ( ) {
const activeNote = this . getActiveContextNote ( ) ;
2020-02-07 21:08:55 +01:00
return activeNote ? activeNote . type : null ;
}
2022-09-21 21:41:51 -04:00
/** @returns {string|null} */
getActiveContextNoteMime ( ) {
const activeNote = this . getActiveContextNote ( ) ;
return activeNote ? activeNote . mime : null ;
}
2020-02-07 21:08:55 +01:00
2023-04-11 21:41:55 +02:00
async switchToNoteContext ( ntxId , notePath , viewScope = { } , hoistedNoteId = null ) {
2021-05-22 12:26:45 +02:00
const noteContext = this . noteContexts . find ( nc => nc . ntxId === ntxId )
2020-02-29 16:26:46 +01:00
|| await this . openEmptyTab ( ) ;
2020-02-07 21:08:55 +01:00
2022-12-13 16:57:46 +01:00
await this . activateNoteContext ( noteContext . ntxId ) ;
2023-04-11 21:41:55 +02:00
if ( hoistedNoteId ) {
await noteContext . setHoistedNoteId ( hoistedNoteId ) ;
}
2022-12-13 16:57:46 +01:00
if ( notePath ) {
2023-04-11 21:41:55 +02:00
await noteContext . setNote ( notePath , { viewScope } ) ;
2022-12-13 16:57:46 +01:00
}
2020-02-07 21:08:55 +01:00
}
async openAndActivateEmptyTab ( ) {
2021-05-22 12:26:45 +02:00
const noteContext = await this . openEmptyTab ( ) ;
2020-02-07 21:08:55 +01:00
2021-05-22 12:35:41 +02:00
await this . activateNoteContext ( noteContext . ntxId ) ;
2020-02-28 11:46:35 +01:00
2021-05-22 12:26:45 +02:00
await noteContext . setEmpty ( ) ;
2020-02-07 21:08:55 +01:00
}
2022-01-10 20:44:59 +01:00
async openEmptyTab ( ntxId = null , hoistedNoteId = 'root' , mainNtxId = null ) {
2021-05-22 12:26:45 +02:00
const noteContext = new NoteContext ( ntxId , hoistedNoteId , mainNtxId ) ;
2021-05-21 22:34:40 +02:00
2022-12-18 22:05:06 +01:00
let existingNoteContext ;
2022-12-13 21:45:57 +01:00
if ( utils . isMobile ( ) ) {
// kind of hacky way to enforce a single tab on mobile interface - all requests to create a new one
// are forced to reuse the existing ab instead
existingNoteContext = this . getActiveContext ( ) ;
} else {
existingNoteContext = this . children . find ( nc => nc . ntxId === noteContext . ntxId ) ;
}
2021-05-21 22:34:40 +02:00
2021-05-22 12:26:45 +02:00
if ( existingNoteContext ) {
2022-12-13 21:45:57 +01:00
await existingNoteContext . setHoistedNoteId ( hoistedNoteId ) ;
2021-05-22 12:26:45 +02:00
return existingNoteContext ;
2021-05-21 22:34:40 +02:00
}
2021-05-22 12:26:45 +02:00
this . child ( noteContext ) ;
2020-02-27 10:03:14 +01:00
2021-05-22 12:35:41 +02:00
await this . triggerEvent ( 'newNoteContextCreated' , { noteContext } ) ;
2020-02-27 10:03:14 +01:00
2021-05-22 12:26:45 +02:00
return noteContext ;
2020-02-07 21:08:55 +01:00
}
2022-12-19 23:19:47 +01:00
async openInNewTab ( targetNoteId , hoistedNoteId = null ) {
const noteContext = await this . openEmptyTab ( null , hoistedNoteId || this . getActiveContext ( ) . hoistedNoteId ) ;
await noteContext . setNote ( targetNoteId ) ;
}
async openInSameTab ( targetNoteId , hoistedNoteId = null ) {
const activeContext = this . getActiveContext ( ) ;
await activeContext . setHoistedNoteId ( hoistedNoteId || activeContext . hoistedNoteId ) ;
await activeContext . setNote ( targetNoteId ) ;
}
2020-11-24 23:24:05 +01:00
/ * *
* If the requested notePath is within current note hoisting scope then keep the note hoisting also for the new tab .
* /
2023-04-03 23:47:24 +02:00
async openTabWithNoteWithHoisting ( notePath , opts = { } ) {
2021-05-22 12:35:41 +02:00
const noteContext = this . getActiveContext ( ) ;
2020-11-24 23:24:05 +01:00
let hoistedNoteId = 'root' ;
2021-05-22 12:26:45 +02:00
if ( noteContext ) {
const resolvedNotePath = await treeService . resolveNotePath ( notePath , noteContext . hoistedNoteId ) ;
2020-11-24 23:24:05 +01:00
2022-12-21 16:11:00 +01:00
if ( resolvedNotePath . includes ( noteContext . hoistedNoteId ) || resolvedNotePath . includes ( '_hidden' ) ) {
2021-05-22 12:26:45 +02:00
hoistedNoteId = noteContext . hoistedNoteId ;
2020-11-24 23:24:05 +01:00
}
}
2023-04-03 23:47:24 +02:00
opts . hoistedNoteId = hoistedNoteId ;
return this . openContextWithNote ( notePath , opts ) ;
2020-11-24 23:24:05 +01:00
}
2023-02-14 16:06:49 +01:00
async openContextWithNote ( notePath , opts = { } ) {
const activate = ! ! opts . activate ;
const ntxId = opts . ntxId || null ;
const mainNtxId = opts . mainNtxId || null ;
const hoistedNoteId = opts . hoistedNoteId || 'root' ;
2023-04-03 23:47:24 +02:00
const viewScope = opts . viewScope || { viewMode : "default" } ;
2023-02-14 16:06:49 +01:00
2021-05-22 12:26:45 +02:00
const noteContext = await this . openEmptyTab ( ntxId , hoistedNoteId , mainNtxId ) ;
2020-02-27 12:26:42 +01:00
2020-05-05 19:30:03 +02:00
if ( notePath ) {
2023-02-14 16:06:49 +01:00
await noteContext . setNote ( notePath , {
2023-06-30 11:18:34 +02:00
// if activate is false, then send normal noteSwitched event
2023-02-14 16:06:49 +01:00
triggerSwitchEvent : ! activate ,
2023-04-03 23:47:24 +02:00
viewScope : viewScope
2023-02-14 16:06:49 +01:00
} ) ;
2020-05-05 19:30:03 +02:00
}
2020-02-27 12:26:42 +01:00
if ( activate ) {
2021-05-22 12:35:41 +02:00
this . activateNoteContext ( noteContext . ntxId , false ) ;
2020-02-27 12:26:42 +01:00
2021-05-22 12:35:41 +02:00
await this . triggerEvent ( 'noteSwitchedAndActivated' , {
2021-05-22 12:26:45 +02:00
noteContext ,
notePath : noteContext . notePath // resolved note path
2020-02-28 00:31:12 +01:00
} ) ;
2020-02-27 12:26:42 +01:00
}
2020-05-08 23:39:46 +02:00
2021-05-22 12:26:45 +02:00
return noteContext ;
2020-02-27 12:26:42 +01:00
}
2020-02-07 21:08:55 +01:00
async activateOrOpenNote ( noteId ) {
2021-05-22 12:26:45 +02:00
for ( const noteContext of this . getNoteContexts ( ) ) {
if ( noteContext . note && noteContext . note . noteId === noteId ) {
2021-05-22 12:35:41 +02:00
this . activateNoteContext ( noteContext . ntxId ) ;
2020-04-05 15:35:01 +02:00
2020-02-07 21:08:55 +01:00
return ;
}
}
// if no tab with this note has been found we'll create new tab
2023-02-14 16:06:49 +01:00
await this . openContextWithNote ( noteId , { activate : true } ) ;
2020-02-07 21:08:55 +01:00
}
2021-05-24 22:29:49 +02:00
async activateNoteContext ( ntxId , triggerEvent = true ) {
2021-05-22 13:04:08 +02:00
if ( ntxId === this . activeNtxId ) {
2020-02-07 21:08:55 +01:00
return ;
}
2021-05-22 13:04:08 +02:00
this . activeNtxId = ntxId ;
2020-02-07 21:08:55 +01:00
2020-02-27 12:26:42 +01:00
if ( triggerEvent ) {
2021-05-24 22:29:49 +02:00
await this . triggerEvent ( 'activeContextChanged' , {
2021-05-22 12:26:45 +02:00
noteContext : this . getNoteContextById ( ntxId )
2020-03-07 13:40:46 +01:00
} ) ;
2020-02-27 12:26:42 +01:00
}
2020-02-08 21:23:42 +01:00
this . tabsUpdate . scheduleUpdate ( ) ;
2020-05-22 19:30:21 +02:00
2023-04-11 21:41:55 +02:00
this . setCurrentNavigationStateToHash ( ) ;
2020-02-07 21:08:55 +01:00
}
2022-06-29 22:38:35 +02:00
/ * *
* @ param ntxId
* @ returns { Promise < boolean > } true if note context has been removed , false otherwise
* /
2021-05-22 12:35:41 +02:00
async removeNoteContext ( ntxId ) {
2023-06-30 11:18:34 +02:00
// removing note context is an async process which can take some time, if users presses CTRL-W quickly, two
2022-02-09 21:21:17 +01:00
// close events could interleave which would then lead to attempting to activate already removed context.
2022-06-29 22:38:35 +02:00
return await this . mutex . runExclusively ( async ( ) => {
let noteContextToRemove ;
try {
noteContextToRemove = this . getNoteContextById ( ntxId ) ;
}
catch {
// note context not found
return false ;
}
2022-02-09 21:21:17 +01:00
if ( noteContextToRemove . isMainContext ( ) ) {
const mainNoteContexts = this . getNoteContexts ( ) . filter ( nc => nc . isMainContext ( ) ) ;
if ( mainNoteContexts . length === 1 ) {
2022-12-10 14:35:58 +01:00
if ( noteContextToRemove . isEmpty ( ) ) {
// this is already the empty note context, no point in closing it and replacing with another
// empty tab
return false ;
}
2022-06-02 22:28:25 +02:00
2022-12-10 14:35:58 +01:00
await this . openEmptyTab ( ) ;
2022-02-09 21:21:17 +01:00
}
2021-10-28 22:27:21 +02:00
}
2020-05-22 19:30:21 +02:00
2022-02-09 21:21:17 +01:00
// close dangling autocompletes after closing the tab
$ ( ".aa-input" ) . autocomplete ( "close" ) ;
2021-05-20 23:13:34 +02:00
2022-02-09 21:21:17 +01:00
const noteContextsToRemove = noteContextToRemove . getSubContexts ( ) ;
const ntxIdsToRemove = noteContextsToRemove . map ( nc => nc . ntxId ) ;
2020-02-07 21:08:55 +01:00
2022-06-02 22:28:25 +02:00
await this . triggerEvent ( 'beforeNoteContextRemove' , { ntxIds : ntxIdsToRemove } ) ;
2020-03-07 13:57:31 +01:00
2022-02-09 21:21:17 +01:00
if ( ! noteContextToRemove . isMainContext ( ) ) {
2023-04-13 21:16:10 +08:00
const siblings = noteContextToRemove . getMainContext ( ) . getSubContexts ( ) ;
const idx = siblings . findIndex ( nc => nc . ntxId === noteContextToRemove . ntxId ) ;
const contextToActivateIdx = idx === siblings . length - 1 ? idx - 1 : idx + 1 ;
const contextToActivate = siblings [ contextToActivateIdx ] ;
await this . activateNoteContext ( contextToActivate . ntxId ) ;
2020-03-07 13:57:31 +01:00
}
2022-02-09 21:21:17 +01:00
else if ( this . mainNoteContexts . length <= 1 ) {
await this . openAndActivateEmptyTab ( ) ;
}
else if ( ntxIdsToRemove . includes ( this . activeNtxId ) ) {
const idx = this . mainNoteContexts . findIndex ( nc => nc . ntxId === noteContextToRemove . ntxId ) ;
if ( idx === this . mainNoteContexts . length - 1 ) {
await this . activatePreviousTabCommand ( ) ;
}
else {
await this . activateNextTabCommand ( ) ;
}
2020-03-07 13:57:31 +01:00
}
2020-02-07 21:08:55 +01:00
2022-06-02 22:28:25 +02:00
this . removeNoteContexts ( noteContextsToRemove ) ;
2022-06-29 22:38:35 +02:00
return true ;
2022-06-02 22:28:25 +02:00
} ) ;
}
2020-02-07 21:08:55 +01:00
2022-06-02 22:28:25 +02:00
removeNoteContexts ( noteContextsToRemove ) {
const ntxIdsToRemove = noteContextsToRemove . map ( nc => nc . ntxId ) ;
2023-06-03 05:54:33 +08:00
const position = this . noteContexts . findIndex ( nc => ntxIdsToRemove . includes ( nc . ntxId ) ) ;
2022-06-02 22:28:25 +02:00
this . children = this . children . filter ( nc => ! ntxIdsToRemove . includes ( nc . ntxId ) ) ;
2023-06-03 05:54:33 +08:00
this . addToRecentlyClosedTabs ( noteContextsToRemove , position ) ;
2022-06-02 22:28:25 +02:00
this . triggerEvent ( 'noteContextRemoved' , { ntxIds : ntxIdsToRemove } ) ;
this . tabsUpdate . scheduleUpdate ( ) ;
2020-02-07 21:08:55 +01:00
}
2023-06-03 05:54:33 +08:00
addToRecentlyClosedTabs ( noteContexts , position ) {
2022-12-10 14:35:58 +01:00
if ( noteContexts . length === 1 && noteContexts [ 0 ] . isEmpty ( ) ) {
return ;
}
2023-06-03 05:54:33 +08:00
this . recentlyClosedTabs . push ( { contexts : noteContexts , position : position } ) ;
2022-12-10 14:35:58 +01:00
}
2021-05-22 12:26:45 +02:00
tabReorderEvent ( { ntxIdsInOrder } ) {
2020-02-07 21:08:55 +01:00
const order = { } ;
2021-05-24 21:05:44 +02:00
let i = 0 ;
2021-05-24 21:43:24 +02:00
for ( const ntxId of ntxIdsInOrder ) {
for ( const noteContext of this . getNoteContextById ( ntxId ) . getSubContexts ( ) ) {
2021-05-24 21:05:44 +02:00
order [ noteContext . ntxId ] = i ++ ;
}
}
this . children . sort ( ( a , b ) => order [ a . ntxId ] < order [ b . ntxId ] ? - 1 : 1 ) ;
this . tabsUpdate . scheduleUpdate ( ) ;
}
2023-06-01 00:48:37 +08:00
noteContextReorderEvent ( { ntxIdsInOrder , oldMainNtxId , newMainNtxId } ) {
2023-05-31 01:53:55 +08:00
const order = Object . fromEntries ( ntxIdsInOrder . map ( ( v , i ) => [ v , i ] ) ) ;
2020-02-07 21:08:55 +01:00
2021-05-22 12:26:45 +02:00
this . children . sort ( ( a , b ) => order [ a . ntxId ] < order [ b . ntxId ] ? - 1 : 1 ) ;
2020-02-07 21:08:55 +01:00
2023-06-01 00:48:37 +08:00
if ( oldMainNtxId && newMainNtxId ) {
this . children . forEach ( c => {
if ( c . ntxId === newMainNtxId ) {
// new main context has null mainNtxId
c . mainNtxId = null ;
} else if ( c . ntxId === oldMainNtxId || c . mainNtxId === oldMainNtxId ) {
// old main context or subcontexts all have the new mainNtxId
c . mainNtxId = newMainNtxId ;
}
} ) ;
2023-05-31 01:53:55 +08:00
}
2020-02-08 21:23:42 +01:00
this . tabsUpdate . scheduleUpdate ( ) ;
2020-02-07 21:08:55 +01:00
}
2021-07-05 15:50:21 +02:00
async activateNextTabCommand ( ) {
2021-07-24 22:51:12 +02:00
const activeMainNtxId = this . getActiveMainContext ( ) . ntxId ;
2020-02-07 21:08:55 +01:00
2021-07-24 22:51:12 +02:00
const oldIdx = this . mainNoteContexts . findIndex ( nc => nc . ntxId === activeMainNtxId ) ;
const newActiveNtxId = this . mainNoteContexts [ oldIdx === this . mainNoteContexts . length - 1 ? 0 : oldIdx + 1 ] . ntxId ;
2021-07-20 13:29:11 +02:00
await this . activateNoteContext ( newActiveNtxId ) ;
2020-02-07 21:08:55 +01:00
}
2021-07-05 15:50:21 +02:00
async activatePreviousTabCommand ( ) {
2021-07-24 22:51:12 +02:00
const activeMainNtxId = this . getActiveMainContext ( ) . ntxId ;
2021-07-20 13:29:11 +02:00
2021-07-24 22:51:12 +02:00
const oldIdx = this . mainNoteContexts . findIndex ( nc => nc . ntxId === activeMainNtxId ) ;
const newActiveNtxId = this . mainNoteContexts [ oldIdx === 0 ? this . mainNoteContexts . length - 1 : oldIdx - 1 ] . ntxId ;
2020-02-07 21:08:55 +01:00
2021-07-20 13:29:11 +02:00
await this . activateNoteContext ( newActiveNtxId ) ;
2020-02-07 21:08:55 +01:00
}
2021-07-05 15:50:21 +02:00
async closeActiveTabCommand ( ) {
await this . removeNoteContext ( this . activeNtxId ) ;
2020-02-07 21:08:55 +01:00
}
2020-02-16 19:23:49 +01:00
beforeUnloadEvent ( ) {
2020-02-08 20:53:07 +01:00
this . tabsUpdate . updateNowIfNecessary ( ) ;
2021-02-27 23:39:02 +01:00
return true ; // don't block closing the tab, this metadata is not that important
2020-02-08 20:53:07 +01:00
}
2020-02-16 19:54:11 +01:00
openNewTabCommand ( ) {
2020-02-07 21:08:55 +01:00
this . openAndActivateEmptyTab ( ) ;
}
2023-01-08 13:58:51 +01:00
async closeAllTabsCommand ( ) {
2021-10-03 23:01:22 +02:00
for ( const ntxIdToRemove of this . mainNoteContexts . map ( nc => nc . ntxId ) ) {
2021-05-22 12:35:41 +02:00
await this . removeNoteContext ( ntxIdToRemove ) ;
2020-02-09 21:13:05 +01:00
}
2020-02-07 21:08:55 +01:00
}
2024-11-01 20:02:22 +08:00
2023-01-08 13:58:51 +01:00
async closeOtherTabsCommand ( { ntxId } ) {
2021-10-03 23:01:22 +02:00
for ( const ntxIdToRemove of this . mainNoteContexts . map ( nc => nc . ntxId ) ) {
2021-05-22 12:26:45 +02:00
if ( ntxIdToRemove !== ntxId ) {
2021-05-22 12:35:41 +02:00
await this . removeNoteContext ( ntxIdToRemove ) ;
2020-02-09 21:13:05 +01:00
}
}
}
2024-11-01 20:02:22 +08:00
async closeRightTabsCommand ( { ntxId } ) {
const ntxIds = this . mainNoteContexts . map ( nc => nc . ntxId ) ;
const index = ntxIds . indexOf ( ntxId ) ;
if ( index !== - 1 ) {
const idsToRemove = ntxIds . slice ( index + 1 ) ;
for ( const ntxIdToRemove of idsToRemove ) {
await this . removeNoteContext ( ntxIdToRemove ) ;
}
}
}
2023-01-08 13:58:51 +01:00
async closeTabCommand ( { ntxId } ) {
await this . removeNoteContext ( ntxId ) ;
}
2022-06-29 22:38:35 +02:00
async moveTabToNewWindowCommand ( { ntxId } ) {
2021-05-22 12:26:45 +02:00
const { notePath , hoistedNoteId } = this . getNoteContextById ( ntxId ) ;
2020-04-26 23:11:52 +02:00
2022-06-29 22:38:35 +02:00
const removed = await this . removeNoteContext ( ntxId ) ;
2020-04-26 23:11:52 +02:00
2022-06-29 22:38:35 +02:00
if ( removed ) {
this . triggerCommand ( 'openInWindow' , { notePath , hoistedNoteId } ) ;
}
2020-04-26 23:11:52 +02:00
}
2020-11-24 22:32:22 +01:00
2024-11-22 17:24:06 +08:00
async copyTabToNewWindowCommand ( { ntxId } ) {
const { notePath , hoistedNoteId } = this . getNoteContextById ( ntxId ) ;
this . triggerCommand ( 'openInWindow' , { notePath , hoistedNoteId } ) ;
}
2021-10-09 21:20:12 +02:00
async reopenLastTabCommand ( ) {
2022-12-10 14:35:58 +01:00
let closeLastEmptyTab = null ;
await this . mutex . runExclusively ( async ( ) => {
if ( this . recentlyClosedTabs . length === 0 ) {
return ;
}
if ( this . noteContexts . length === 1 && this . noteContexts [ 0 ] . isEmpty ( ) ) {
// new empty tab is created after closing the last tab, this reverses the empty tab creation
closeLastEmptyTab = this . noteContexts [ 0 ] ;
}
2023-06-03 05:54:33 +08:00
const lastClosedTab = this . recentlyClosedTabs . pop ( ) ;
const noteContexts = lastClosedTab . contexts ;
2021-10-09 22:03:24 +02:00
for ( const noteContext of noteContexts ) {
this . child ( noteContext ) ;
await this . triggerEvent ( 'newNoteContextCreated' , { noteContext } ) ;
}
2023-06-03 05:54:33 +08:00
// restore last position of contexts stored in tab manager
const ntxsInOrder = [
... this . noteContexts . slice ( 0 , lastClosedTab . position ) ,
... this . noteContexts . slice ( - noteContexts . length ) ,
... this . noteContexts . slice ( lastClosedTab . position , - noteContexts . length ) ,
]
await this . noteContextReorderEvent ( { ntxIdsInOrder : ntxsInOrder . map ( nc => nc . ntxId ) } ) ;
let mainNtx = noteContexts . find ( nc => nc . isMainContext ( ) ) ;
if ( mainNtx ) {
// reopened a tab, need to reorder new tab widget in tab row
await this . triggerEvent ( 'contextsReopened' , {
mainNtxId : mainNtx . ntxId ,
tabPosition : ntxsInOrder . filter ( nc => nc . isMainContext ( ) ) . findIndex ( nc => nc . ntxId === mainNtx . ntxId )
} ) ;
} else {
// reopened a single split, need to reorder the pane widget in split note container
await this . triggerEvent ( 'contextsReopened' , {
ntxId : ntxsInOrder [ lastClosedTab . position ] . ntxId ,
// this is safe since lastClosedTab.position can never be 0 in this case
afterNtxId : ntxsInOrder [ lastClosedTab . position - 1 ] . ntxId
} ) ;
}
2021-10-09 22:03:24 +02:00
const noteContextToActivate = noteContexts . length === 1
? noteContexts [ 0 ]
: noteContexts . find ( nc => nc . isMainContext ( ) ) ;
2022-12-10 14:35:58 +01:00
await this . activateNoteContext ( noteContextToActivate . ntxId ) ;
2021-10-09 21:20:12 +02:00
await this . triggerEvent ( 'noteSwitched' , {
2021-10-09 22:03:24 +02:00
noteContext : noteContextToActivate ,
notePath : noteContextToActivate . notePath
2021-10-09 21:20:12 +02:00
} ) ;
2022-12-10 14:35:58 +01:00
} ) ;
if ( closeLastEmptyTab ) {
await this . removeNoteContext ( closeLastEmptyTab . ntxId ) ;
2021-10-09 21:20:12 +02:00
}
}
2020-11-24 22:32:22 +01:00
hoistedNoteChangedEvent ( ) {
this . tabsUpdate . scheduleUpdate ( ) ;
}
2022-01-03 19:52:24 +01:00
2023-04-11 21:41:55 +02:00
async updateDocumentTitle ( activeNoteContext ) {
2022-01-03 19:52:24 +01:00
const titleFragments = [
2022-07-05 22:40:41 +02:00
// it helps to navigate in history if note title is included in the title
2023-04-11 21:41:55 +02:00
await activeNoteContext . getNavigationTitle ( ) ,
2024-07-14 21:20:42 +03:00
"TriliumNext Notes"
2022-01-03 19:52:24 +01:00
] . filter ( Boolean ) ;
document . title = titleFragments . join ( " - " ) ;
}
2023-04-11 21:41:55 +02:00
async entitiesReloadedEvent ( { loadResults } ) {
2022-01-03 19:52:24 +01:00
const activeContext = this . getActiveContext ( ) ;
if ( activeContext && loadResults . isNoteReloaded ( activeContext . noteId ) ) {
2023-04-11 21:41:55 +02:00
await this . updateDocumentTitle ( activeContext ) ;
2022-01-03 19:52:24 +01:00
}
}
2023-09-08 00:28:00 +02:00
async frocaReloadedEvent ( ) {
const activeContext = this . getActiveContext ( ) ;
if ( activeContext ) {
await this . updateDocumentTitle ( activeContext ) ;
}
}
2020-05-22 19:30:21 +02:00
}