mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			282 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			282 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import protectedSessionHolder from "../services/protected_session_holder.js";
 | |
| import server from "../services/server.js";
 | |
| import utils from "../services/utils.js";
 | |
| import appContext from "./app_context.js";
 | |
| import treeService from "../services/tree.js";
 | |
| import Component from "./component.js";
 | |
| import froca from "../services/froca.js";
 | |
| import hoistedNoteService from "../services/hoisted_note.js";
 | |
| import options from "../services/options.js";
 | |
| 
 | |
| class NoteContext extends Component {
 | |
|     /**
 | |
|      * @param {string|null} ntxId
 | |
|      */
 | |
|     constructor(ntxId = null, hoistedNoteId = 'root', mainNtxId = null) {
 | |
|         super();
 | |
| 
 | |
|         this.ntxId = ntxId || utils.randomString(4);
 | |
|         this.hoistedNoteId = hoistedNoteId;
 | |
|         this.mainNtxId = mainNtxId;
 | |
|     }
 | |
| 
 | |
|     setEmpty() {
 | |
|         this.notePath = null;
 | |
|         this.noteId = null;
 | |
|         this.parentNoteId = null;
 | |
|         this.hoistedNoteId = 'root';
 | |
| 
 | |
|         this.triggerEvent('noteSwitched', {
 | |
|             noteContext: this,
 | |
|             notePath: this.notePath
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     isEmpty() {
 | |
|         return !this.noteId;
 | |
|     }
 | |
| 
 | |
|     async setNote(inputNotePath, triggerSwitchEvent = true) {
 | |
|         const resolvedNotePath = await this.getResolvedNotePath(inputNotePath);
 | |
| 
 | |
|         if (!resolvedNotePath) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         await this.triggerEvent('beforeNoteSwitch', {noteContext: this});
 | |
| 
 | |
|         utils.closeActiveDialog();
 | |
| 
 | |
|         this.notePath = resolvedNotePath;
 | |
|         ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath));
 | |
| 
 | |
|         this.readOnlyTemporarilyDisabled = false;
 | |
| 
 | |
|         this.saveToRecentNotes(resolvedNotePath);
 | |
| 
 | |
|         protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
 | |
| 
 | |
|         if (triggerSwitchEvent) {
 | |
|             await this.triggerEvent('noteSwitched', {
 | |
|                 noteContext: this,
 | |
|                 notePath: this.notePath
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         if (this.hoistedNoteId === 'root'
 | |
|             && this.notePath.startsWith("root/hidden")
 | |
|             && !this.note.hasLabel("keepCurrentHoisting")
 | |
|         ) {
 | |
|             // hidden subtree displays only when hoisted so it doesn't make sense to keep root as hoisted note
 | |
| 
 | |
|             let hoistedNoteId = 'hidden';
 | |
| 
 | |
|             if (this.note.isLaunchBarConfig()) {
 | |
|                 hoistedNoteId = 'lbRoot';
 | |
|             } else if (this.note.isOptions()) {
 | |
|                 hoistedNoteId = 'options';
 | |
|             }
 | |
| 
 | |
|             await this.setHoistedNoteId(hoistedNoteId);
 | |
|         }
 | |
| 
 | |
|         if (utils.isMobile()) {
 | |
|             this.triggerCommand('setActiveScreen', {screen: 'detail'});
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     getSubContexts() {
 | |
|         return appContext.tabManager.noteContexts.filter(nc => nc.ntxId === this.ntxId || nc.mainNtxId === this.ntxId);
 | |
|     }
 | |
| 
 | |
|     isMainContext() {
 | |
|         // if null then this is a main context
 | |
|         return !this.mainNtxId;
 | |
|     }
 | |
| 
 | |
|     getMainContext() {
 | |
|         if (this.mainNtxId) {
 | |
|             try {
 | |
|                 return appContext.tabManager.getNoteContextById(this.mainNtxId);
 | |
|             }
 | |
|             catch (e) {
 | |
|                 this.mainNtxId = null;
 | |
|                 return this;
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             return this;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     saveToRecentNotes(resolvedNotePath) {
 | |
|         setTimeout(async () => {
 | |
|             // we include the note into recent list only if the user stayed on the note at least 5 seconds
 | |
|             if (resolvedNotePath && resolvedNotePath === this.notePath) {
 | |
|                 await server.post('recent-notes', {
 | |
|                     noteId: this.note.noteId,
 | |
|                     notePath: this.notePath
 | |
|                 });
 | |
|             }
 | |
|         }, 5000);
 | |
|     }
 | |
| 
 | |
|     async getResolvedNotePath(inputNotePath) {
 | |
|         const resolvedNotePath = await treeService.resolveNotePath(inputNotePath, this.hoistedNoteId);
 | |
| 
 | |
|         if (!resolvedNotePath) {
 | |
|             logError(`Cannot resolve note path ${inputNotePath}`);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (resolvedNotePath === this.notePath) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (await hoistedNoteService.checkNoteAccess(resolvedNotePath, this) === false) {
 | |
|             return; // note is outside of hoisted subtree and user chose not to unhoist
 | |
|         }
 | |
| 
 | |
|         return resolvedNotePath;
 | |
|     }
 | |
| 
 | |
|     /** @property {NoteShort} */
 | |
|     get note() {
 | |
|         if (!this.noteId || !(this.noteId in froca.notes)) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         return froca.notes[this.noteId];
 | |
|     }
 | |
| 
 | |
|     /** @property {string[]} */
 | |
|     get notePathArray() {
 | |
|         return this.notePath ? this.notePath.split('/') : [];
 | |
|     }
 | |
| 
 | |
|     /** @returns {NoteComplement} */
 | |
|     async getNoteComplement() {
 | |
|         if (!this.noteId) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         return await froca.getNoteComplement(this.noteId);
 | |
|     }
 | |
| 
 | |
|     isActive() {
 | |
|         return appContext.tabManager.activeNtxId === this.ntxId;
 | |
|     }
 | |
| 
 | |
|     getTabState() {
 | |
|         if (!this.notePath && this.hoistedNoteId === 'root') {
 | |
|             // keeping empty hoisted tab is esp. important for mobile (e.g. opened launcher config)
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         return {
 | |
|             ntxId: this.ntxId,
 | |
|             mainNtxId: this.mainNtxId,
 | |
|             notePath: this.notePath,
 | |
|             hoistedNoteId: this.hoistedNoteId,
 | |
|             active: this.isActive()
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     async unhoist() {
 | |
|         await this.setHoistedNoteId('root');
 | |
|     }
 | |
| 
 | |
|     async setHoistedNoteId(noteIdToHoist) {
 | |
|         this.hoistedNoteId = noteIdToHoist;
 | |
| 
 | |
|         if (!this.notePathArray?.includes(noteIdToHoist)) {
 | |
|             await this.setNote(noteIdToHoist);
 | |
|         }
 | |
| 
 | |
|         await this.triggerEvent('hoistedNoteChanged', {
 | |
|             noteId: noteIdToHoist,
 | |
|             ntxId: this.ntxId
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     async isReadOnly() {
 | |
|         if (this.readOnlyTemporarilyDisabled) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // "readOnly" is a state valid only for text/code notes
 | |
|         if (!this.note || (this.note.type !== 'text' && this.note.type !== 'code')) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         if (this.note.hasLabel('readOnly')) {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         const noteComplement = await this.getNoteComplement();
 | |
| 
 | |
|         const sizeLimit = this.note.type === 'text' ?
 | |
|             options.getInt('autoReadonlySizeText')
 | |
|                 : options.getInt('autoReadonlySizeCode');
 | |
| 
 | |
|         return noteComplement.content
 | |
|             && noteComplement.content.length > sizeLimit
 | |
|             && !this.note.hasLabel('autoReadOnlyDisabled');
 | |
|     }
 | |
| 
 | |
|     async entitiesReloadedEvent({loadResults}) {
 | |
|         if (loadResults.isNoteReloaded(this.noteId)) {
 | |
|             const note = loadResults.getEntity('notes', this.noteId);
 | |
| 
 | |
|             if (note.isDeleted) {
 | |
|                 this.noteId = null;
 | |
|                 this.notePath = null;
 | |
| 
 | |
|                 this.triggerEvent('noteSwitched', {
 | |
|                     noteContext: this,
 | |
|                     notePath: this.notePath
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     hasNoteList() {
 | |
|         return this.note
 | |
|             && this.note.hasChildren()
 | |
|             && ['book', 'text', 'code'].includes(this.note.type)
 | |
|             && this.note.mime !== 'text/x-sqlite;schema=trilium'
 | |
|             && !this.note.hasLabel('hideChildrenOverview');
 | |
|     }
 | |
| 
 | |
|     async getTextEditor(callback) {
 | |
|         return new Promise(resolve => appContext.triggerCommand('executeWithTextEditor', {
 | |
|             callback,
 | |
|             resolve,
 | |
|             ntxId: this.ntxId
 | |
|         }));
 | |
|     }
 | |
| 
 | |
|     async getCodeEditor() {
 | |
|         return new Promise(resolve => appContext.triggerCommand('executeWithCodeEditor', {
 | |
|             resolve,
 | |
|             ntxId: this.ntxId
 | |
|         }));
 | |
|     }
 | |
| 
 | |
|     async getContentElement() {
 | |
|         return new Promise(resolve => appContext.triggerCommand('executeWithContentElement', {
 | |
|             resolve,
 | |
|             ntxId: this.ntxId
 | |
|         }));
 | |
|     }
 | |
| 
 | |
|     async getTypeWidget() {
 | |
|         return new Promise(resolve => appContext.triggerCommand('executeWithTypeWidget', {
 | |
|             resolve,
 | |
|             ntxId: this.ntxId
 | |
|         }));
 | |
|     }
 | |
| }
 | |
| 
 | |
| export default NoteContext;
 |