mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	continuing refactoring
This commit is contained in:
		| @@ -20,8 +20,10 @@ import WhatLinksHereWidget from "../widgets/what_links_here.js"; | ||||
| import AttributesWidget from "../widgets/attributes.js"; | ||||
| import TitleBarButtonsWidget from "../widgets/title_bar_buttons.js"; | ||||
| import GlobalMenuWidget from "../widgets/global_menu.js"; | ||||
| import HorizontalFlexContainer from "../widgets/horizontal_flex_container.js"; | ||||
| import RowFlexContainer from "../widgets/row_flex_container.js"; | ||||
| import StandardTopWidget from "../widgets/standard_top_widget.js"; | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import treeUtils from "./tree_utils.js"; | ||||
|  | ||||
| class AppContext { | ||||
|     constructor() { | ||||
| @@ -38,7 +40,7 @@ class AppContext { | ||||
|         this.tabRow = new TabRowWidget(this); | ||||
|  | ||||
|         const topPaneWidgets = [ | ||||
|             new HorizontalFlexContainer(this, [ | ||||
|             new RowFlexContainer(this, [ | ||||
|                 new GlobalMenuWidget(this), | ||||
|                 this.tabRow, | ||||
|                 new TitleBarButtonsWidget(this) | ||||
| @@ -105,6 +107,10 @@ class AppContext { | ||||
|     trigger(name, data) { | ||||
|         this.eventReceived(name, data); | ||||
|  | ||||
|         for (const tabContext of this.tabContexts) { | ||||
|             tabContext.eventReceived(name, data); | ||||
|         } | ||||
|  | ||||
|         for (const widget of this.widgets) { | ||||
|             widget.eventReceived(name, data); | ||||
|         } | ||||
| @@ -118,6 +124,36 @@ class AppContext { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     activateNote(notePath) { | ||||
|         const activeTabContext = this.getActiveTabContext(); | ||||
|  | ||||
|         activeTabContext.setNote(notePath); | ||||
|  | ||||
|         this._setTitleBar(); | ||||
|         this._setCurrentNotePathToHash(); | ||||
|     } | ||||
|  | ||||
|     _setCurrentNotePathToHash() { | ||||
|         const activeTabContext = this.getActiveTabContext(); | ||||
|  | ||||
|         if (activeTabContext && activeTabContext.notePath) { | ||||
|             document.location.hash = (activeTabContext.notePath || "") + "-" + activeTabContext.tabId; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async _setTitleBar() { | ||||
|         document.title = "Trilium Notes"; | ||||
|  | ||||
|         const activeTabContext = this.getActiveTabContext(); | ||||
|  | ||||
|         if (activeTabContext && activeTabContext.notePath) { | ||||
|             const note = await treeCache.getNote(treeUtils.getNoteIdFromNotePath(activeTabContext.notePath)); | ||||
|  | ||||
|             // it helps navigating in history if note title is included in the title | ||||
|             document.title += " - " + note.title; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** @return {TabContext[]} */ | ||||
|     getTabContexts() { | ||||
|         return this.tabContexts; | ||||
| @@ -219,7 +255,7 @@ class AppContext { | ||||
|     getTab(newTab, state) { | ||||
|         if (!this.getActiveTabContext() || newTab) { | ||||
|             // if it's a new tab explicitly by user then it's in background | ||||
|             const ctx = new TabContext(this.tabRow, state); | ||||
|             const ctx = new TabContext(this, this.tabRow, state); | ||||
|             this.tabContexts.push(ctx); | ||||
|  | ||||
|             return ctx; | ||||
| @@ -249,7 +285,7 @@ class AppContext { | ||||
|     } | ||||
|  | ||||
|     async openEmptyTab() { | ||||
|         const ctx = new TabContext(this.tabRow); | ||||
|         const ctx = new TabContext(this, this.tabRow); | ||||
|         this.tabContexts.push(ctx); | ||||
|  | ||||
|         await this.tabRow.activateTab(ctx.$tab[0]); | ||||
|   | ||||
| @@ -2,13 +2,17 @@ import server from "./server.js"; | ||||
| import ws from "./ws.js"; | ||||
| import treeUtils from "./tree_utils.js"; | ||||
| import noteAutocompleteService from "./note_autocomplete.js"; | ||||
| import Component from "../widgets/component.js"; | ||||
| import utils from "./utils.js"; | ||||
|  | ||||
| class Attributes { | ||||
| class Attributes extends Component { | ||||
|     /** | ||||
|      * @param {TabContext} ctx | ||||
|      * @param {AppContext} appContext | ||||
|      * @param {TabContext} tabContext | ||||
|      */ | ||||
|     constructor(ctx) { | ||||
|         this.ctx = ctx; | ||||
|     constructor(appContext, tabContext) { | ||||
|         super(appContext); | ||||
|         this.tabContext = tabContext; | ||||
|         this.attributePromise = null; | ||||
|     } | ||||
|  | ||||
| @@ -17,7 +21,7 @@ class Attributes { | ||||
|     } | ||||
|  | ||||
|     reloadAttributes() { | ||||
|         this.attributePromise = server.get(`notes/${this.ctx.note.noteId}/attributes`); | ||||
|         this.attributePromise = server.get(`notes/${this.tabContext.note.noteId}/attributes`); | ||||
|     } | ||||
|  | ||||
|     async refreshAttributes() { | ||||
| @@ -32,16 +36,19 @@ class Attributes { | ||||
|         return this.attributePromise; | ||||
|     } | ||||
|  | ||||
|     eventReceived(name, data) { | ||||
|         if (!this.ctx.note) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (name === 'syncData') { | ||||
|             if (data.find(sd => sd.entityName === 'attributes' && sd.noteId === this.ctx.note.noteId)) { | ||||
|     syncDataListener({data}) { | ||||
|         if (this.tabContext.note && data.find(sd => sd.entityName === 'attributes' && sd.noteId === this.tabContext.note.noteId)) { | ||||
|             this.reloadAttributes(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     activeNoteChangedListener() { | ||||
|         if (utils.isDesktop()) { | ||||
|             this.attributes.refreshAttributes(); | ||||
|         } else { | ||||
|             // mobile usually doesn't need attributes so we just invalidate | ||||
|             this.attributes.invalidateAttributes(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -20,9 +20,7 @@ async function reload() { | ||||
| } | ||||
|  | ||||
| async function reloadNote(tabContext) { | ||||
|     const note = await loadNote(tabContext.note.noteId); | ||||
|  | ||||
|     await loadNoteDetailToContext(tabContext, note, tabContext.notePath); | ||||
|     await loadNoteDetailToContext(tabContext, tabContext.notePath); | ||||
| } | ||||
|  | ||||
| async function openInTab(notePath, activate) { | ||||
| @@ -79,11 +77,10 @@ async function activateOrOpenNote(noteId) { | ||||
|  | ||||
| /** | ||||
|  * @param {TabContext} ctx | ||||
|  * @param {NoteFull} note | ||||
|  * @param {string} notePath | ||||
|  */ | ||||
| async function loadNoteDetailToContext(ctx, note, notePath) { | ||||
|     await ctx.setNote(note, notePath); | ||||
| async function loadNoteDetailToContext(ctx, notePath) { | ||||
|     await ctx.setNote(notePath); | ||||
|  | ||||
|     appContext.openTabsChanged(); | ||||
|  | ||||
| @@ -104,7 +101,6 @@ async function loadNoteDetail(origNotePath, options = {}) { | ||||
|     } | ||||
|  | ||||
|     const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||
|     const loadedNote = await loadNote(noteId); | ||||
|     const ctx = appContext.getTab(newTab, options.state); | ||||
|  | ||||
|     // we will try to render the new note only if it's still the active one in the tree | ||||
| @@ -112,11 +108,11 @@ async function loadNoteDetail(origNotePath, options = {}) { | ||||
|     // try to render all those loaded notes one after each other. This only guarantees that correct note | ||||
|     // will be displayed independent of timing | ||||
|     const currentTreeNode = appContext.getMainNoteTree().getActiveNode(); | ||||
|     if (!newTab && currentTreeNode && currentTreeNode.data.noteId !== loadedNote.noteId) { | ||||
|     if (!newTab && currentTreeNode && currentTreeNode.data.noteId !== noteId) { | ||||
|         return; | ||||
|     } | ||||
|      | ||||
|     const loadPromise = loadNoteDetailToContext(ctx, loadedNote, notePath).then(() => { | ||||
|     const loadPromise = loadNoteDetailToContext(ctx, notePath).then(() => { | ||||
|         if (activate) { | ||||
|             return appContext.activateTab(ctx); | ||||
|         } | ||||
| @@ -201,9 +197,7 @@ ws.subscribeToOutsideSyncMessages(syncData => { | ||||
| }); | ||||
|  | ||||
| ws.subscribeToAllSyncMessages(syncData => { | ||||
|     for (const tc of appContext.getTabContexts()) { | ||||
|         tc.eventReceived('syncData', syncData); | ||||
|     } | ||||
|     appContext.trigger('syncData', {data: syncData}); | ||||
| }); | ||||
|  | ||||
| $tabContentsContainer.on("dragover", e => e.preventDefault()); | ||||
|   | ||||
| @@ -43,7 +43,7 @@ async function mouseEnterHandler() { | ||||
|     const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||
|  | ||||
|     const notePromise = noteDetailService.loadNote(noteId); | ||||
|     const attributePromise = server.get('notes/' + noteId + '/attributes'); | ||||
|     const attributePromise = server.get(`notes/${noteId}/attributes`); | ||||
|  | ||||
|     const [note, attributes] = await Promise.all([notePromise, attributePromise]); | ||||
|  | ||||
|   | ||||
| @@ -6,6 +6,9 @@ import Attributes from "./attributes.js"; | ||||
| import utils from "./utils.js"; | ||||
| import optionsService from "./options.js"; | ||||
| import appContext from "./app_context.js"; | ||||
| import treeUtils from "./tree_utils.js"; | ||||
| import noteDetailService from "./note_detail.js"; | ||||
| import Component from "../widgets/component.js"; | ||||
|  | ||||
| let showSidebarInNewTab = true; | ||||
|  | ||||
| @@ -13,12 +16,15 @@ optionsService.addLoadListener(options => { | ||||
|     showSidebarInNewTab = options.is('showSidebarInNewTab'); | ||||
| }); | ||||
|  | ||||
| class TabContext { | ||||
| class TabContext extends Component { | ||||
|     /** | ||||
|      * @param {AppContext} appContext | ||||
|      * @param {TabRowWidget} tabRow | ||||
|      * @param {object} state | ||||
|      */ | ||||
|     constructor(tabRow, state = {}) { | ||||
|     constructor(appContext, tabRow, state = {}) { | ||||
|         super(appContext); | ||||
|  | ||||
|         this.tabRow = tabRow; | ||||
|         this.tabId = state.tabId || utils.randomString(4); | ||||
|         this.$tab = $(this.tabRow.addTab(this.tabId)); | ||||
| @@ -37,40 +43,26 @@ class TabContext { | ||||
|  | ||||
|         this.noteChangeDisabled = false; | ||||
|         this.isNoteChanged = false; | ||||
|         this.attributes = new Attributes(this); | ||||
|         this.attributes = new Attributes(this.appContext, this); | ||||
|  | ||||
|         this.children.push(this.attributes); | ||||
|     } | ||||
|  | ||||
|     async setNote(note, notePath) { | ||||
|         /** @property {NoteFull} */ | ||||
|         this.note = note; | ||||
|     async setNote(notePath) { | ||||
|         this.notePath = notePath; | ||||
|         const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||
|  | ||||
|         /** @property {NoteFull} */ | ||||
|         this.note = await noteDetailService.loadNote(noteId); | ||||
|  | ||||
|         this.tabRow.updateTab(this.$tab[0], {title: this.note.title}); | ||||
|  | ||||
|         if (!this.initialized) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         if (utils.isDesktop()) { | ||||
|             this.attributes.refreshAttributes(); | ||||
|         } else { | ||||
|             // mobile usually doesn't need attributes so we just invalidate | ||||
|             this.attributes.invalidateAttributes(); | ||||
|         } | ||||
|  | ||||
|         this.setupClasses(); | ||||
|  | ||||
|         this.setCurrentNotePathToHash(); | ||||
|  | ||||
|         this.noteChangeDisabled = true; | ||||
|  | ||||
|         try { | ||||
|  | ||||
|         } finally { | ||||
|             this.noteChangeDisabled = false; | ||||
|         } | ||||
|  | ||||
|         this.setTitleBar(); | ||||
|  | ||||
|         this.cleanup(); // esp. on windows autocomplete is not getting closed automatically | ||||
|  | ||||
|         setTimeout(async () => { | ||||
| @@ -96,8 +88,8 @@ class TabContext { | ||||
|         if (!this.initialized) { | ||||
|             await this.initTabContent(); | ||||
|  | ||||
|             if (this.note) { | ||||
|                 await this.setNote(this.note, this.notePath); | ||||
|             if (this.notePath) { | ||||
|                 await this.setNote(this.notePath); | ||||
|             } | ||||
|             else { | ||||
|                 // FIXME | ||||
| @@ -106,38 +98,18 @@ class TabContext { | ||||
|         } | ||||
|  | ||||
|         this.setCurrentNotePathToHash(); | ||||
|         this.setTitleBar(); | ||||
|     } | ||||
|  | ||||
|     async renderComponent(disableAutoBook = false) { | ||||
|         // FIXME | ||||
|     } | ||||
|  | ||||
|     setTitleBar() { | ||||
|         if (!this.$tabContent.is(":visible")) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         document.title = "Trilium Notes"; | ||||
|  | ||||
|         if (this.note) { | ||||
|             // it helps navigating in history if note title is included in the title | ||||
|             document.title += " - " + this.note.title; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     hide() { | ||||
|         if (this.initialized) { | ||||
|             this.$tabContent.hide(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     setCurrentNotePathToHash() { | ||||
|         if (this.isActive()) { | ||||
|             document.location.hash = (this.notePath || "") + "-" + this.tabId; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     isActive() { | ||||
|         return this.$tab[0] === this.tabRow.activeTabEl; | ||||
|     } | ||||
| @@ -149,21 +121,9 @@ class TabContext { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for (const clazz of Array.from(this.$tabContent[0].classList)) { // create copy to safely iterate over while removing classes | ||||
|             if (clazz !== 'note-tab-content') { | ||||
|                 this.$tabContent.removeClass(clazz); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this.$tab.addClass(this.note.cssClass); | ||||
|         this.$tab.addClass(utils.getNoteTypeClass(this.note.type)); | ||||
|         this.$tab.addClass(utils.getMimeTypeClass(this.note.mime)); | ||||
|  | ||||
|         this.$tabContent.addClass(this.note.cssClass); | ||||
|         this.$tabContent.addClass(utils.getNoteTypeClass(this.note.type)); | ||||
|         this.$tabContent.addClass(utils.getMimeTypeClass(this.note.mime)); | ||||
|  | ||||
|         this.$tabContent.toggleClass("protected", this.note.isProtected); | ||||
|     } | ||||
|  | ||||
|     getComponent() { | ||||
| @@ -247,14 +207,6 @@ class TabContext { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     eventReceived(name, data) { | ||||
|         if (!this.initialized) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.attributes.eventReceived(name, data); | ||||
|     } | ||||
|  | ||||
|     getTabState() { | ||||
|         if (!this.notePath) { | ||||
|             return null; | ||||
|   | ||||
| @@ -172,6 +172,10 @@ class TreeCache { | ||||
|         return (await this.getNotes([noteId], silentNotFoundError))[0]; | ||||
|     } | ||||
|  | ||||
|     getNoteFromCache(noteId) { | ||||
|         return this.notes[noteId]; | ||||
|     } | ||||
|  | ||||
|     getBranches(branchIds) { | ||||
|         return branchIds | ||||
|             .map(branchId => this.getBranch(branchId)) | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| class BasicWidget { | ||||
|     /** | ||||
|      * @param {AppContext} appContext | ||||
|      */ | ||||
| import Component from "./component.js"; | ||||
|  | ||||
| class BasicWidget extends Component { | ||||
|     constructor(appContext) { | ||||
|         this.appContext = appContext; | ||||
|         super(appContext); | ||||
|         this.widgetId = `widget-${this.constructor.name}`; | ||||
|     } | ||||
|  | ||||
| @@ -25,20 +24,6 @@ class BasicWidget { | ||||
|      */ | ||||
|     doRender() {} | ||||
|  | ||||
|     eventReceived(name, data) { | ||||
|         console.log("received", name, "to", this.widgetId); | ||||
|  | ||||
|         const fun = this[name + 'Listener']; | ||||
|  | ||||
|         if (typeof fun === 'function') { | ||||
|             fun.call(this, data); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     trigger(name, data) { | ||||
|         this.appContext.trigger(name, data); | ||||
|     } | ||||
|  | ||||
|     toggle(show) { | ||||
|         this.$widget.toggle(show); | ||||
|     } | ||||
|   | ||||
							
								
								
									
										24
									
								
								src/public/javascripts/widgets/component.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/public/javascripts/widgets/component.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| export default class Component { | ||||
|     /** @param {AppContext} appContext */ | ||||
|     constructor(appContext) { | ||||
|         this.appContext = appContext; | ||||
|         /** @type Component[] */ | ||||
|         this.children = []; | ||||
|     } | ||||
|  | ||||
|     eventReceived(name, data) { | ||||
|         const fun = this[name + 'Listener']; | ||||
|  | ||||
|         if (typeof fun === 'function') { | ||||
|             fun.call(this, data); | ||||
|         } | ||||
|  | ||||
|         for (const child of this.children) { | ||||
|             child.eventReceived(name, data); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     trigger(name, data) { | ||||
|         this.appContext.trigger(name, data); | ||||
|     } | ||||
| } | ||||
| @@ -58,6 +58,24 @@ export default class NoteDetailWidget extends TabAwareWidget { | ||||
|  | ||||
|         this.getComponent().show(); | ||||
|         await this.getComponent().render(); | ||||
|  | ||||
|         this.setupClasses(); | ||||
|     } | ||||
|  | ||||
|     setupClasses() { | ||||
|         const note = this.tabContext.note; | ||||
|  | ||||
|         for (const clazz of Array.from(this.$widget[0].classList)) { // create copy to safely iterate over while removing classes | ||||
|             if (clazz !== 'note-detail') { | ||||
|                 this.$widget.removeClass(clazz); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         this.$widget.addClass(note.cssClass); | ||||
|         this.$widget.addClass(utils.getNoteTypeClass(note.type)); | ||||
|         this.$widget.addClass(utils.getMimeTypeClass(note.mime)); | ||||
|  | ||||
|         this.$widget.toggleClass("protected", note.isProtected); | ||||
|     } | ||||
|  | ||||
|     getComponent() { | ||||
|   | ||||
| @@ -101,7 +101,7 @@ export default class NoteTreeWidget extends BasicWidget { | ||||
|  | ||||
|                 const notePath = await treeUtils.getNotePath(data.node); | ||||
|  | ||||
|                 noteDetailService.switchToNote(notePath); | ||||
|                 this.appContext.activateNote(notePath); | ||||
|             }, | ||||
|             expand: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, true), | ||||
|             collapse: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, false), | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import BasicWidget from "./basic_widget.js"; | ||||
| 
 | ||||
| export default class HorizontalFlexContainer extends BasicWidget { | ||||
| export default class RowFlexContainer extends BasicWidget { | ||||
|     constructor(appContext, widgets) { | ||||
|         super(appContext); | ||||
| 
 | ||||
| @@ -11,7 +11,7 @@ | ||||
| <div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div> | ||||
|  | ||||
| <div id="container" style="display: none;"> | ||||
|     <div id="top-pane" class="hide-in-zen-mode"></div> | ||||
|     <div id="top-pane"></div> | ||||
|  | ||||
|     <div style="display: flex; flex-grow: 1; flex-shrink: 1; min-height: 0;"> | ||||
|         <div id="left-pane" class="hide-in-zen-mode"></div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user