From d36bff2a978142896394a106582c20ce8afae566 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 5 May 2019 20:45:07 +0200 Subject: [PATCH] protected session in a dialog now works, proper reloading --- src/public/javascripts/desktop.js | 1 + .../javascripts/dialogs/protected_session.js | 33 +++++++ .../javascripts/services/note_context.js | 63 ++++++------ .../javascripts/services/note_detail.js | 96 +++++++++---------- .../services/note_detail_protected_session.js | 6 -- .../javascripts/services/protected_session.js | 58 ++++------- 6 files changed, 130 insertions(+), 127 deletions(-) create mode 100644 src/public/javascripts/dialogs/protected_session.js diff --git a/src/public/javascripts/desktop.js b/src/public/javascripts/desktop.js index 7e5f31c98..30df2dcd2 100644 --- a/src/public/javascripts/desktop.js +++ b/src/public/javascripts/desktop.js @@ -9,6 +9,7 @@ import sqlConsoleDialog from './dialogs/sql_console.js'; import markdownImportDialog from './dialogs/markdown_import.js'; import exportDialog from './dialogs/export.js'; import importDialog from './dialogs/import.js'; +import protectedSessionDialog from './dialogs/protected_session.js'; import cloning from './services/cloning.js'; import contextMenu from './services/tree_context_menu.js'; diff --git a/src/public/javascripts/dialogs/protected_session.js b/src/public/javascripts/dialogs/protected_session.js new file mode 100644 index 000000000..46cfa66a7 --- /dev/null +++ b/src/public/javascripts/dialogs/protected_session.js @@ -0,0 +1,33 @@ +import protectedSessionService from "../services/protected_session.js"; + +const $dialog = $("#protected-session-password-dialog"); +const $passwordForm = $dialog.find(".protected-session-password-form"); +const $passwordInput = $dialog.find(".protected-session-password"); + +function show() { + $dialog.modal(); + + $passwordInput.focus(); +} + +function close() { + // this may fal if the dialog has not been previously opened (not sure if still true with Bootstrap modal) + try { + $dialog.modal('hide'); + } + catch (e) {} +} + +$passwordForm.submit(() => { + const password = $passwordInput.val(); + $passwordInput.val(""); + + protectedSessionService.setupProtectedSession(password); + + return false; +}); + +export default { + show, + close +} \ No newline at end of file diff --git a/src/public/javascripts/services/note_context.js b/src/public/javascripts/services/note_context.js index f577158d4..dc0342c44 100644 --- a/src/public/javascripts/services/note_context.js +++ b/src/public/javascripts/services/note_context.js @@ -15,6 +15,7 @@ import noteDetailSearch from "./note_detail_search.js"; import noteDetailRender from "./note_detail_render.js"; import noteDetailRelationMap from "./note_detail_relation_map.js"; import noteDetailProtectedSession from "./note_detail_protected_session.js"; +import protectedSessionService from "./protected_session.js"; const $noteTabContentsContainer = $("#note-tab-container"); @@ -32,26 +33,24 @@ const componentClasses = { let tabIdCounter = 1; class NoteContext { - constructor(chromeTabs, note, openOnBackground) { + constructor(chromeTabs, openOnBackground) { this.tabId = tabIdCounter++; this.chromeTabs = chromeTabs; - /** @type {NoteFull} */ - this.note = note; - this.noteId = note.noteId; + this.tab = this.chromeTabs.addTab({ + title: '', // will be set later + id: this.tabId + }, { + background: openOnBackground + }); this.$noteTabContent = $(".note-tab-content-template").clone(); this.$noteTabContent.removeClass('note-tab-content-template'); - this.$noteTabContent.attr('data-note-id', this.noteId); this.$noteTabContent.attr('data-tab-id', this.tabId); $noteTabContentsContainer.append(this.$noteTabContent); - console.log(`Creating note tab ${this.tabId} for ${this.noteId}`); - this.$noteTitle = this.$noteTabContent.find(".note-title"); this.$noteDetailComponents = this.$noteTabContent.find(".note-detail-component"); - this.$protectButton = this.$noteTabContent.find(".protect-button"); - this.$unprotectButton = this.$noteTabContent.find(".unprotect-button"); this.$childrenOverview = this.$noteTabContent.find(".children-overview"); this.$scriptArea = this.$noteTabContent.find(".note-detail-script-area"); this.$savedIndicator = this.$noteTabContent.find(".saved-indicator"); @@ -69,25 +68,40 @@ class NoteContext { treeService.setNoteTitle(this.noteId, title); }); - this.tab = this.chromeTabs.addTab({ - title: note.title, - id: this.tabId - }, { - background: openOnBackground - }); + this.$protectButton = this.$noteTabContent.find(".protect-button"); + this.$protectButton.click(protectedSessionService.protectNoteAndSendToServer); - this.tab.setAttribute('data-note-id', this.noteId); + this.$unprotectButton = this.$noteTabContent.find(".unprotect-button"); + this.$unprotectButton.click(protectedSessionService.unprotectNoteAndSendToServer); + + console.log(`Created note tab ${this.tabId} for ${this.noteId}`); } setNote(note) { this.noteId = note.noteId; this.note = note; + this.tab.setAttribute('data-note-id', this.noteId); this.$noteTabContent.attr('data-note-id', note.noteId); this.chromeTabs.updateTab(this.tab, {title: note.title}); this.attributes.invalidateAttributes(); + this.$noteTabContent.toggleClass("protected", this.note.isProtected); + this.$protectButton.toggleClass("active", this.note.isProtected); + this.$protectButton.prop("disabled", this.note.isProtected); + this.$unprotectButton.toggleClass("active", !this.note.isProtected); + this.$unprotectButton.prop("disabled", !this.note.isProtected || !protectedSessionHolder.isProtectedSessionAvailable()); + + for (const clazz of Array.from(this.$noteTabContent[0].classList)) { // create copy to safely iterate over while removing classes + if (clazz.startsWith("type-") || clazz.startsWith("mime-")) { + this.$noteTabContent.removeClass(clazz); + } + } + + this.$noteTabContent.addClass(utils.getNoteTypeClass(this.note.type)); + this.$noteTabContent.addClass(utils.getMimeTypeClass(this.note.mime)); + console.log(`Switched tab ${this.tabId} to ${this.noteId}`); } @@ -183,23 +197,6 @@ class NoteContext { this.$childrenOverview.show(); } - - updateNoteView() { - this.$noteTabContent.toggleClass("protected", this.note.isProtected); - this.$protectButton.toggleClass("active", this.note.isProtected); - this.$protectButton.prop("disabled", this.note.isProtected); - this.$unprotectButton.toggleClass("active", !this.note.isProtected); - this.$unprotectButton.prop("disabled", !this.note.isProtected || !protectedSessionHolder.isProtectedSessionAvailable()); - - for (const clazz of Array.from(this.$noteTabContent[0].classList)) { // create copy to safely iterate over while removing classes - if (clazz.startsWith("type-") || clazz.startsWith("mime-")) { - this.$noteTabContent.removeClass(clazz); - } - } - - this.$noteTabContent.addClass(utils.getNoteTypeClass(this.note.type)); - this.$noteTabContent.addClass(utils.getMimeTypeClass(this.note.mime)); - } } export default NoteContext; \ No newline at end of file diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index 33ccc88dd..f01454a08 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -1,8 +1,5 @@ import treeService from './tree.js'; import NoteContext from './note_context.js'; -import noteTypeService from './note_type.js'; -import protectedSessionService from './protected_session.js'; -import protectedSessionHolder from './protected_session_holder.js'; import server from './server.js'; import messagingService from "./messaging.js"; import infoService from "./info.js"; @@ -21,6 +18,7 @@ const $savedIndicator = $(".saved-indicator"); let detailLoadedListeners = []; +/** @return {NoteFull} */ function getActiveNote() { const activeContext = getActiveContext(); return activeContext ? activeContext.note : null; @@ -44,6 +42,14 @@ async function reload() { await loadNoteDetail(getActiveNoteId()); } +async function reloadAllTabs() { + for (const noteContext of noteContexts) { + const note = await loadNote(noteContext.note.noteId); + + await loadNoteDetailToContext(noteContext, note); + } +} + async function openInTab(noteId) { await loadNoteDetail(noteId, true); } @@ -76,18 +82,6 @@ async function saveNotesIfChanged() { /** @type {NoteContext[]} */ let noteContexts = []; -/** @returns {NoteContext} */ -function getContext(noteId) { - const noteContext = noteContexts.find(nc => nc.noteId === noteId); - - if (noteContext) { - return noteContext; - } - else { - throw new Error(`Can't find note context for ${noteId}`); - } -} - /** @returns {NoteContext} */ function getActiveContext() { for (const ctx of noteContexts) { @@ -105,44 +99,21 @@ function showTab(tabId) { } } -async function loadNoteDetail(noteId, newTab = false) { - const loadedNote = await loadNote(noteId); - let ctx; - - if (noteContexts.length === 0 || newTab) { - // if it's a new tab explicitly by user then it's in background - ctx = new NoteContext(chromeTabs, loadedNote, newTab); - noteContexts.push(ctx); - - if (!newTab) { - showTab(ctx.tabId); - } - } - else { - ctx = getActiveContext(); - ctx.setNote(loadedNote); - } - - // 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 = treeService.getActiveNode(); - if (!newTab && currentTreeNode && currentTreeNode.data.noteId !== loadedNote.noteId) { - return; - } +/** + * @param {NoteContext} ctx + * @param {NoteFull} note + */ +async function loadNoteDetailToContext(ctx, note) { + ctx.setNote(note); if (utils.isDesktop()) { // needs to happen after loading the note itself because it references active noteId ctx.attributes.refreshAttributes(); - } - else { + } else { // mobile usually doesn't need attributes so we just invalidate ctx.attributes.invalidateAttributes(); } - ctx.updateNoteView(); - ctx.noteChangeDisabled = true; try { @@ -164,12 +135,11 @@ async function loadNoteDetail(noteId, newTab = false) { ctx.$noteTitle.removeAttr("readonly"); // this can be set by protected session service await ctx.getComponent().show(ctx); - } - finally { + } finally { ctx.noteChangeDisabled = false; } - treeService.setBranchBackgroundBasedOnProtectedStatus(noteId); + treeService.setBranchBackgroundBasedOnProtectedStatus(note.noteId); // after loading new note make sure editor is scrolled to the top ctx.getComponent().scrollToTop(); @@ -187,6 +157,35 @@ async function loadNoteDetail(noteId, newTab = false) { } } +async function loadNoteDetail(noteId, newTab = false) { + const loadedNote = await loadNote(noteId); + let ctx; + + if (noteContexts.length === 0 || newTab) { + // if it's a new tab explicitly by user then it's in background + ctx = new NoteContext(chromeTabs, newTab); + noteContexts.push(ctx); + + if (!newTab) { + showTab(ctx.tabId); + } + } + else { + ctx = getActiveContext(); + } + + // 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 = treeService.getActiveNode(); + if (!newTab && currentTreeNode && currentTreeNode.data.noteId !== loadedNote.noteId) { + return; + } + + await loadNoteDetailToContext(ctx, loadedNote); +} + async function loadNote(noteId) { const row = await server.get('notes/' + noteId); @@ -282,6 +281,7 @@ setInterval(saveNotesIfChanged, 3000); export default { reload, + reloadAllTabs, openInTab, switchToNote, loadNote, diff --git a/src/public/javascripts/services/note_detail_protected_session.js b/src/public/javascripts/services/note_detail_protected_session.js index 8a30df0e2..fa030842e 100644 --- a/src/public/javascripts/services/note_detail_protected_session.js +++ b/src/public/javascripts/services/note_detail_protected_session.js @@ -10,12 +10,6 @@ class NoteDetailProtectedSession { this.$passwordForm = ctx.$noteTabContent.find(".protected-session-password-form"); this.$passwordInput = ctx.$noteTabContent.find(".protected-session-password"); - this.$protectButton = ctx.$noteTabContent.find(".protect-button"); - this.$protectButton.click(protectedSessionService.protectNoteAndSendToServer); - - this.$unprotectButton = ctx.$noteTabContent.find(".unprotect-button"); - this.$unprotectButton.click(protectedSessionService.unprotectNoteAndSendToServer); - this.$passwordForm.submit(() => { const password = this.$passwordInput.val(); this.$passwordInput.val(""); diff --git a/src/public/javascripts/services/protected_session.js b/src/public/javascripts/services/protected_session.js index 90450649c..be94c4637 100644 --- a/src/public/javascripts/services/protected_session.js +++ b/src/public/javascripts/services/protected_session.js @@ -4,18 +4,13 @@ import utils from './utils.js'; import server from './server.js'; import protectedSessionHolder from './protected_session_holder.js'; import infoService from "./info.js"; +import protectedSessionDialog from "../dialogs/protected_session.js"; const $enterProtectedSessionButton = $("#enter-protected-session-button"); const $leaveProtectedSessionButton = $("#leave-protected-session-button"); let protectedSessionDeferred = null; -async function enterProtectedSession() { - if (!protectedSessionHolder.isProtectedSessionAvailable()) { - await ensureProtectedSession(true, true); - } -} - async function leaveProtectedSession() { if (protectedSessionHolder.isProtectedSessionAvailable()) { utils.reloadApp(); @@ -23,22 +18,17 @@ async function leaveProtectedSession() { } /** returned promise resolves with true if new protected session was established, false if no action was necessary */ -function ensureProtectedSession(requireProtectedSession, modal) { +function enterProtectedSession() { const dfd = $.Deferred(); - if (requireProtectedSession && !protectedSessionHolder.isProtectedSessionAvailable()) { + if (protectedSessionHolder.isProtectedSessionAvailable()) { + dfd.resolve(false); + } + else { // using deferred instead of promise because it allows resolving from outside protectedSessionDeferred = dfd; - if (modal) { - $dialog.modal(); - } - else { - $component.show(); - } - } - else { - dfd.resolve(false); + protectedSessionDialog.show(); } return dfd.promise(); @@ -56,12 +46,12 @@ async function setupProtectedSession(password) { await treeService.reload(); - // it's important that tree has been already reloaded at this point - // since detail also uses tree cache (for children overview) - await noteDetailService.reload(); + // it's important that tree has been already reloaded at this point since detail also uses tree cache (for children overview) + // children overview is the reason why we need to reload all tabs + await noteDetailService.reloadAllTabs(); if (protectedSessionDeferred !== null) { - ensureDialogIsClosed(); + protectedSessionDialog.close(); protectedSessionDeferred.resolve(true); protectedSessionDeferred = null; @@ -73,16 +63,6 @@ async function setupProtectedSession(password) { infoService.showMessage("Protected session has been started."); } -function ensureDialogIsClosed() { - // this may fal if the dialog has not been previously opened (not sure if still true with Bootstrap modal) - try { - $dialog.modal('hide'); - } - catch (e) {} - - $passwordInputs.val(''); -} - async function enterProtectedSessionOnServer(password) { return await server.post('login/protected', { password: password @@ -94,16 +74,16 @@ async function protectNoteAndSendToServer() { return; } - await ensureProtectedSession(true, true); + await enterProtectedSession(); const note = noteDetailService.getActiveNote(); note.isProtected = true; - await noteDetailService.saveNote(note); + await noteDetailService.getActiveContext().saveNote(); treeService.setProtected(note.noteId, note.isProtected); - noteDetailService.updateNoteView(); + await noteDetailService.reload(); } async function unprotectNoteAndSendToServer() { @@ -117,7 +97,7 @@ async function unprotectNoteAndSendToServer() { if (!protectedSessionHolder.isProtectedSessionAvailable()) { console.log("Unprotecting notes outside of protected session is not allowed."); - // the reason is that it's not easy to handle even with ensureProtectedSession, + // the reason is that it's not easy to handle even with enterProtectedSession, // because we would first have to make sure the note is loaded and only then unprotect // we used to have a bug where we would overwrite the previous note with unprotected content. @@ -126,15 +106,15 @@ async function unprotectNoteAndSendToServer() { activeNote.isProtected = false; - await noteDetailService.saveNote(activeNote); + await noteDetailService.getActiveContext().saveNote(); treeService.setProtected(activeNote.noteId, activeNote.isProtected); - noteDetailService.updateNoteView(); + await noteDetailService.reload(); } async function protectSubtree(noteId, protect) { - await ensureProtectedSession(true, true); + await enterProtectedSession(); await server.put('notes/' + noteId + "/protect/" + (protect ? 1 : 0)); @@ -145,9 +125,7 @@ async function protectSubtree(noteId, protect) { } export default { - ensureProtectedSession, protectSubtree, - ensureDialogIsClosed, enterProtectedSession, leaveProtectedSession, protectNoteAndSendToServer,