continuing refactoring

This commit is contained in:
zadam 2020-01-15 21:36:01 +01:00
parent f98a20928c
commit 7963de0abc
12 changed files with 138 additions and 118 deletions

View file

@ -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]);

View file

@ -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,15 +36,18 @@ class Attributes {
return this.attributePromise;
}
eventReceived(name, data) {
if (!this.ctx.note) {
return;
syncDataListener({data}) {
if (this.tabContext.note && data.find(sd => sd.entityName === 'attributes' && sd.noteId === this.tabContext.note.noteId)) {
this.reloadAttributes();
}
}
if (name === 'syncData') {
if (data.find(sd => sd.entityName === 'attributes' && sd.noteId === this.ctx.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();
}
}
}

View file

@ -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());

View file

@ -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]);

View file

@ -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;

View file

@ -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))

View file

@ -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);
}

View 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);
}
}

View file

@ -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() {

View file

@ -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),

View file

@ -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);

View file

@ -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>