refactoring of note detail API

This commit is contained in:
zadam 2020-01-24 17:54:47 +01:00
parent 4b66765cc1
commit 606d5afcab
18 changed files with 107 additions and 219 deletions

View File

@ -174,8 +174,7 @@ function AttributesModel() {
appContext.getActiveTabContext().attributes.refreshAttributes(); appContext.getActiveTabContext().attributes.refreshAttributes();
// reload // FIXME detail should be also reloaded
noteDetailService.reload();
appContext.trigger('reloadTree'); appContext.trigger('reloadTree');
}; };

View File

@ -62,7 +62,6 @@ async function showTree() {
const notePath = await treeUtils.getNotePath(node); const notePath = await treeUtils.getNotePath(node);
noteDetailService.switchToNote(notePath);
}, },
expand: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, true), expand: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, true),
collapse: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, false), collapse: (event, data) => treeService.setExpandedToServer(data.node.data.branchId, false),

View File

@ -216,20 +216,11 @@ class AppContext {
} }
async switchToTab(tabId, notePath) { async switchToTab(tabId, notePath) {
const tabContext = this.tabContexts.find(tc => tc.tabId === tabId); const tabContext = this.tabContexts.find(tc => tc.tabId === tabId)
|| this.openEmptyTab();
if (!tabContext) { this.activateTab(tabContext.tabId);
await noteDetailService.loadNoteDetail(notePath, { await tabContext.setNote(notePath);
newTab: true,
activate: true
});
} else {
await this.activateTab(tabContext.tabId);
if (notePath && tabContext.notePath !== notePath) {
await tabContext.setNote(notePath);
}
}
} }
/** /**
@ -252,18 +243,6 @@ class AppContext {
} }
} }
async reloadAllTabs() {
for (const tabContext of this.tabContexts) {
await this.reloadTab(tabContext);
}
}
async reloadTab(tc) {
if (tc.note) {
noteDetailService.reloadNote(tc);
}
}
async openAndActivateEmptyTab() { async openAndActivateEmptyTab() {
const tabContext = this.openEmptyTab(); const tabContext = this.openEmptyTab();
@ -278,6 +257,20 @@ class AppContext {
return tabContext; return tabContext;
} }
async activateOrOpenNote(noteId) {
for (const tabContext of this.getTabContexts()) {
if (tabContext.note && tabContext.note.noteId === noteId) {
await tabContext.activate();
return;
}
}
// if no tab with this note has been found we'll create new tab
const tabContext = this.openEmptyTab();
await tabContext.setNote(noteId);
}
async filterTabs(noteId) { async filterTabs(noteId) {
for (const tc of this.tabContexts) { for (const tc of this.tabContexts) {
if (tc.notePath && !tc.notePath.split("/").includes(noteId)) { if (tc.notePath && !tc.notePath.split("/").includes(noteId)) {
@ -318,7 +311,7 @@ class AppContext {
} }
} }
openTabsChanged() { openTabsChangedListener() {
// we don't want to send too many requests with tab changes so we always schedule task to do this in 1 seconds, // we don't want to send too many requests with tab changes so we always schedule task to do this in 1 seconds,
// but if there's any change in between, we cancel the old one and schedule new one // but if there's any change in between, we cancel the old one and schedule new one
// so effectively we kind of wait until user stopped e.g. quickly switching tabs // so effectively we kind of wait until user stopped e.g. quickly switching tabs
@ -327,7 +320,7 @@ class AppContext {
this.tabsChangedTaskId = setTimeout(() => this.saveOpenTabs(), 1000); this.tabsChangedTaskId = setTimeout(() => this.saveOpenTabs(), 1000);
} }
async activateTab(tabId) { activateTab(tabId) {
this.activeTabId = tabId; this.activeTabId = tabId;
this.trigger('activeTabChanged', { tabId: this.activeTabId }); this.trigger('activeTabChanged', { tabId: this.activeTabId });
@ -364,11 +357,11 @@ class AppContext {
this.tabContexts = this.tabContexts.filter(tc => tc.tabId === tabId); this.tabContexts = this.tabContexts.filter(tc => tc.tabId === tabId);
this.openTabsChanged(); this.openTabsChangedListener();
} }
tabReorderListener() { tabReorderListener() {
this.openTabsChanged(); this.openTabsChangedListener();
} }
noteChangesSavedListener() { noteChangesSavedListener() {

View File

@ -78,7 +78,9 @@ export default class Entrypoints extends Component {
await treeService.expandToNote(note.noteId); await treeService.expandToNote(note.noteId);
await noteDetailService.openInTab(note.noteId, true); const tabContext = appContext.openEmptyTab();
appContext.activateTab(tabContext.tabId);
await tabContext.setNote(note.noteId);
noteDetailService.focusAndSelectTitle(); noteDetailService.focusAndSelectTitle();
} }

View File

@ -272,22 +272,6 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
*/ */
this.refreshTree = treeService.reload; this.refreshTree = treeService.reload;
/**
* Refresh active tab
*
* @method
* @returns {Promise<void>}
*/
this.refreshActiveTab = noteDetailService.reload;
/**
* Refresh current tab
*
* @method
* @returns {Promise<void>}
*/
this.refreshAllTabs = appContext.reloadAllTabs;
/** /**
* Create note link (jQuery object) for given note. * Create note link (jQuery object) for given note.
* *

View File

@ -79,10 +79,13 @@ function goToLink(e) {
if (notePath) { if (notePath) {
if ((e.which === 1 && e.ctrlKey) || e.which === 2) { if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
noteDetailService.openInTab(notePath, false); const tabContext = appContext.openEmptyTab();
appContext.activateTab(tabContext.tabId);
tabContext.setNote(notePath);
} }
else if (e.which === 1) { else if (e.which === 1) {
treeService.activateNote(notePath); const activeTabContext = appContext.getActiveTabContext();
activeTabContext.setNote(notePath)
} }
else { else {
return false; return false;
@ -118,7 +121,9 @@ function newTabContextMenu(e) {
}, },
selectContextMenuItem: (e, cmd) => { selectContextMenuItem: (e, cmd) => {
if (cmd === 'openNoteInNewTab') { if (cmd === 'openNoteInNewTab') {
noteDetailService.loadNoteDetail(notePath.split("/").pop(), { newTab: true }); const tabContext = appContext.openEmptyTab();
tabContext.setNote(notePath);
appContext.activateTab(tabContext.tabId);
} }
} }
}); });
@ -138,7 +143,9 @@ $(document).on('mousedown', '.note-detail-text a', function (e) {
e.preventDefault(); e.preventDefault();
if (notePath) { if (notePath) {
noteDetailService.loadNoteDetail(notePath, {newTab: true}); const tabContext = appContext.openEmptyTab();
tabContext.setNote(notePath);
appContext.activateTab(tabContext.tabId);
} }
else { else {
const address = $link.attr('href'); const address = $link.attr('href');

View File

@ -1,35 +1,9 @@
import treeService from './tree.js';
import TabContext from './tab_context.js';
import server from './server.js'; import server from './server.js';
import ws from "./ws.js"; import ws from "./ws.js";
import treeCache from "./tree_cache.js"; import treeCache from "./tree_cache.js";
import NoteFull from "../entities/note_full.js"; import NoteFull from "../entities/note_full.js";
import treeUtils from "./tree_utils.js";
import tabRow from "../widgets/tab_row.js";
import appContext from "./app_context.js"; import appContext from "./app_context.js";
let detailLoadedListeners = [];
async function reload() {
// no saving here
await loadNoteDetail(appContext.getActiveTabNotePath());
}
async function reloadNote(tabContext) {
await loadNoteDetailToContext(tabContext, tabContext.notePath);
}
async function openInTab(notePath, activate) {
await loadNoteDetail(notePath, { newTab: true, activate });
}
async function switchToNote(notePath) {
await loadNoteDetail(notePath);
appContext.openTabsChanged();
}
function getActiveEditor() { function getActiveEditor() {
const activeTabContext = appContext.getActiveTabContext(); const activeTabContext = appContext.getActiveTabContext();
@ -41,73 +15,6 @@ function getActiveEditor() {
} }
} }
async function activateOrOpenNote(noteId) {
for (const tabContext of appContext.getTabContexts()) {
if (tabContext.note && tabContext.note.noteId === noteId) {
await tabContext.activate();
return;
}
}
// if no tab with this note has been found we'll create new tab
await loadNoteDetail(noteId, {
newTab: true,
activate: true
});
}
/**
* @param {TabContext} ctx
* @param {string} notePath
*/
async function loadNoteDetailToContext(ctx, notePath) {
await ctx.setNote(notePath);
appContext.openTabsChanged();
fireDetailLoaded();
}
async function loadNoteDetail(origNotePath, options = {}) {
const newTab = !!options.newTab;
const activate = !!options.activate;
let notePath = await treeService.resolveNotePath(origNotePath);
if (!notePath) {
console.error(`Cannot resolve note path ${origNotePath}`);
// fallback to display something
notePath = 'root';
}
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
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
// this is useful when user quickly switches notes (by e.g. holding down arrow) so that we don't
// 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 !== noteId) {
return;
}
const loadPromise = loadNoteDetailToContext(ctx, notePath).then(() => {
if (activate) {
return appContext.activateTab(ctx.tabId);
}
else {
return Promise.resolve();
}
});
if (!options.async) {
await loadPromise;
}
}
async function loadNote(noteId) { async function loadNote(noteId) {
const row = await server.get('notes/' + noteId); const row = await server.get('notes/' + noteId);
@ -116,49 +23,12 @@ async function loadNote(noteId) {
return new NoteFull(treeCache, row, noteShort); return new NoteFull(treeCache, row, noteShort);
} }
async function noteDeleted(noteId) {
for (const tc of appContext.getTabContexts()) {
// not removing active even if it contains deleted note since that one will move to another note (handled by deletion logic)
// and we would lose tab context state (e.g. sidebar visibility)
if (!tc.isActive() && tc.notePath && tc.notePath.split("/").includes(noteId)) {
tabRow.removeTab(tc.tabId);
}
}
}
function focusOnTitle() { function focusOnTitle() {
appContext.getActiveTabContext().$noteTitle.trigger('focus'); appContext.trigger('focusOnTitle');
} }
function focusAndSelectTitle() { function focusAndSelectTitle() {
appContext.getActiveTabContext() appContext.trigger('focusAndSelectTitle');
.$noteTitle
.trigger('focus')
.trigger('select');
}
/**
* Since detail loading may take some time and user might just browse through the notes using UP-DOWN keys,
* we intentionally decouple activation of the note in the tree and full load of the note so just avaiting on
* fancytree's activate() won't wait for the full load.
*
* This causes an issue where in some cases you want to do some action after detail is loaded. For this reason
* we provide the listeners here which will be triggered after the detail is loaded and if the loaded note
* is the one registered in the listener.
*/
function addDetailLoadedListener(noteId, callback) {
detailLoadedListeners.push({ noteId, callback });
}
function fireDetailLoaded() {
for (const {noteId, callback} of detailLoadedListeners) {
if (noteId === appContext.getActiveTabNoteId()) {
callback();
}
}
// all the listeners are one time only
detailLoadedListeners = [];
} }
ws.subscribeToOutsideSyncMessages(syncData => { ws.subscribeToOutsideSyncMessages(syncData => {
@ -199,17 +69,9 @@ $(window).on('beforeunload', () => {
}); });
export default { export default {
reload,
openInTab,
switchToNote,
loadNote, loadNote,
loadNoteDetail,
focusOnTitle, focusOnTitle,
focusAndSelectTitle, focusAndSelectTitle,
addDetailLoadedListener,
getActiveEditor, getActiveEditor,
activateOrOpenNote, noteChanged
noteDeleted,
noteChanged,
reloadNote
}; };

View File

@ -109,17 +109,12 @@ async function unprotectNoteAndSendToServer() {
await appContext.getActiveTabContext().saveNote(); await appContext.getActiveTabContext().saveNote();
treeService.setProtected(activeNote.noteId, activeNote.isProtected); treeService.setProtected(activeNote.noteId, activeNote.isProtected);
await noteDetailService.reload();
} }
async function protectSubtree(noteId, protect) { async function protectSubtree(noteId, protect) {
await enterProtectedSession(); await enterProtectedSession();
await server.put('notes/' + noteId + "/protect/" + (protect ? 1 : 0)); await server.put('notes/' + noteId + "/protect/" + (protect ? 1 : 0));
treeService.reload();
noteDetailService.reload();
} }
function makeToast(message, protectingLabel, text) { function makeToast(message, protectingLabel, text) {

View File

@ -8,6 +8,7 @@ import appContext from "./app_context.js";
import treeUtils from "./tree_utils.js"; import treeUtils from "./tree_utils.js";
import noteDetailService from "./note_detail.js"; import noteDetailService from "./note_detail.js";
import Component from "../widgets/component.js"; import Component from "../widgets/component.js";
import treeService from "./tree.js";
let showSidebarInNewTab = true; let showSidebarInNewTab = true;
@ -35,7 +36,19 @@ class TabContext extends Component {
this.trigger('tabOpened', {tabId: this.tabId}); this.trigger('tabOpened', {tabId: this.tabId});
} }
async setNote(notePath) { async setNote(inputNotePath) {
const notePath = await treeService.resolveNotePath(inputNotePath);
if (!notePath) {
console.error(`Cannot resolve note path ${inputNotePath}`);
return;
}
if (notePath === this.notePath) {
console.log(`Setting existing notePath ${notePath} so ignoring ...`);
return;
}
await this.trigger('beforeNoteSwitch', {tabId: this.tabId}, true); await this.trigger('beforeNoteSwitch', {tabId: this.tabId}, true);
this.notePath = notePath; this.notePath = notePath;
@ -64,6 +77,7 @@ class TabContext extends Component {
} }
this.trigger('tabNoteSwitched', {tabId: this.tabId}); this.trigger('tabNoteSwitched', {tabId: this.tabId});
this.trigger('openTabsChanged');
} }
async remove() { async remove() {
@ -113,7 +127,16 @@ class TabContext extends Component {
} }
stateChanged() { stateChanged() {
appContext.openTabsChanged(); appContext.openTabsChangedListener();
}
noteDeletedListener({noteId}) {
if (this.note && noteId === this.note.noteId) {
this.note = null;
this.notePath = null;
this.trigger('tabNoteSwitched', {tabId: this.tabId});
}
} }
} }

View File

@ -263,12 +263,12 @@ async function treeInitialized() {
} }
for (const tab of filteredTabs) { for (const tab of filteredTabs) {
await noteDetailService.loadNoteDetail(tab.notePath, { const tabContext = appContext.openEmptyTab();
state: tab, tabContext.setNote(tab.notePath);
newTab: true,
activate: tab.active, if (tab.active) {
async: true // faster initial load appContext.activateTab(tabContext.tabId);
}); }
} }
// previous opening triggered task to save tab changes but these are bogus changes (this is init) // previous opening triggered task to save tab changes but these are bogus changes (this is init)
@ -446,7 +446,7 @@ ws.subscribeToMessages(message => {
reload(); reload();
} }
else if (message.type === 'open-note') { else if (message.type === 'open-note') {
noteDetailService.activateOrOpenNote(message.noteId); appContext.activateOrOpenNote(message.noteId);
if (utils.isElectron()) { if (utils.isElectron()) {
const currentWindow = require("electron").remote.getCurrentWindow(); const currentWindow = require("electron").remote.getCurrentWindow();

View File

@ -104,7 +104,9 @@ class TreeContextMenu {
const notePath = await treeUtils.getNotePath(this.node); const notePath = await treeUtils.getNotePath(this.node);
if (cmd === 'openInTab') { if (cmd === 'openInTab') {
noteDetailService.openInTab(notePath, false); const tabContext = appContext.openEmptyTab();
appContext.activateTab(tabContext.tabId);
tabContext.setNote(notePath);
} }
else if (cmd.startsWith("insertNoteAfter")) { else if (cmd.startsWith("insertNoteAfter")) {
const parentNoteId = this.node.data.parentNoteId; const parentNoteId = this.node.data.parentNoteId;

View File

@ -4,6 +4,7 @@ import protectedSessionHolder from "../services/protected_session_holder.js";
import treeCache from "../services/tree_cache.js"; import treeCache from "../services/tree_cache.js";
import server from "../services/server.js"; import server from "../services/server.js";
import SpacedUpdate from "../services/spaced_update.js"; import SpacedUpdate from "../services/spaced_update.js";
import appContext from "../services/app_context.js";
const TPL = ` const TPL = `
<div class="note-title-container"> <div class="note-title-container">
@ -107,4 +108,18 @@ export default class NoteTitleWidget extends TabAwareWidget {
await this.spacedUpdate.updateNowIfNecessary(); await this.spacedUpdate.updateNowIfNecessary();
} }
} }
focusOnTitleListener() {
if (this.tabContext && this.tabContext.isActive()) {
this.$noteTitle.trigger('focus');
}
}
focusAndSelectTitleListener() {
if (this.tabContext && this.tabContext.isActive()) {
this.$noteTitle
.trigger('focus')
.trigger('select');
}
}
} }

View File

@ -53,7 +53,8 @@ export default class NoteTreeWidget extends TabAwareWidget {
treeUtils.getNotePath(node).then(notePath => { treeUtils.getNotePath(node).then(notePath => {
if (notePath) { if (notePath) {
noteDetailService.openInTab(notePath, false); const tabContext = appContext.openEmptyTab();
tabContext.setNote(notePath);
} }
}); });
@ -87,7 +88,9 @@ export default class NoteTreeWidget extends TabAwareWidget {
node.setFocus(true); node.setFocus(true);
} }
else if (event.ctrlKey) { else if (event.ctrlKey) {
noteDetailService.loadNoteDetail(node.data.noteId, { newTab: true }); const tabContext = appContext.openEmptyTab();
treeUtils.getNotePath(node).then(notePath => tabContext.setNote(notePath));
appContext.activateTab(tabContext.tabId);
} }
else { else {
node.setActive(); node.setActive();

View File

@ -130,9 +130,6 @@ export default class NoteTypeWidget extends TabAwareWidget {
await noteDetailService.reload(); await noteDetailService.reload();
// for the note icon to be updated in the tree
await treeService.reload();
this.update(); this.update();
} }

View File

@ -85,7 +85,7 @@ export default class SearchBoxWidget extends BasicWidget {
this.$saveSearchButton.on('click', () => this.saveSearch()); this.$saveSearchButton.on('click', () => this.saveSearch());
this.$closeSearchButton.on('click', () => this.hideSearchListener()); this.$closeSearchButton.on('click', () => this.trigger('hideSearch'));
return this.$widget; return this.$widget;
} }
@ -159,6 +159,8 @@ export default class SearchBoxWidget extends BasicWidget {
this.resetSearchListener(); this.resetSearchListener();
this.$searchBox.slideUp(); this.$searchBox.slideUp();
this.trigger('hideSearchResults');
} }
toggleSearchListener() { toggleSearchListener() {
@ -167,7 +169,6 @@ export default class SearchBoxWidget extends BasicWidget {
} }
else { else {
this.hideSearchListener(); this.hideSearchListener();
this.trigger('hideSearchResults');
} }
} }

View File

@ -34,6 +34,8 @@ export default class SearchResultsWidget extends BasicWidget {
this.$searchResults = this.$widget; this.$searchResults = this.$widget;
this.$searchResultsInner = this.$widget.find(".search-results-inner"); this.$searchResultsInner = this.$widget.find(".search-results-inner");
this.toggle(false);
return this.$widget; return this.$widget;
} }

View File

@ -20,6 +20,10 @@ export default class TabCachingWidget extends TabAwareWidget {
widget.toggle(false); widget.toggle(false);
} }
if (!this.tabContext) {
return;
}
let widget = this.widgets[this.tabContext.tabId]; let widget = this.widgets[this.tabContext.tabId];
if (!widget) { if (!widget) {
@ -31,8 +35,6 @@ export default class TabCachingWidget extends TabAwareWidget {
} }
widget.toggle(true); widget.toggle(true);
return false; // stop propagation to children
} }
tabRemovedListener({tabId}) { tabRemovedListener({tabId}) {

View File

@ -7,6 +7,7 @@ import contextMenuWidget from "../../services/context_menu.js";
import toastService from "../../services/toast.js"; import toastService from "../../services/toast.js";
import attributeAutocompleteService from "../../services/attribute_autocomplete.js"; import attributeAutocompleteService from "../../services/attribute_autocomplete.js";
import TypeWidget from "./type_widget.js"; import TypeWidget from "./type_widget.js";
import appContext from "../../services/app_context.js";
const uniDirectionalOverlays = [ const uniDirectionalOverlays = [
[ "Arrow", { [ "Arrow", {
@ -196,7 +197,8 @@ export default class RelationMapTypeWidget extends TypeWidget {
const noteId = this.idToNoteId($noteBox.prop("id")); const noteId = this.idToNoteId($noteBox.prop("id"));
if (cmd === "open-in-new-tab") { if (cmd === "open-in-new-tab") {
noteDetailService.openInTab(noteId, false); const tabContext = appContext.openEmptyTab();
tabContext.setNote(noteId);
} }
else if (cmd === "remove") { else if (cmd === "remove") {
const confirmDialog = await import('../../dialogs/confirm.js'); const confirmDialog = await import('../../dialogs/confirm.js');