protected session in a dialog now works, proper reloading

This commit is contained in:
zadam 2019-05-05 20:45:07 +02:00
parent 61696f0287
commit d36bff2a97
6 changed files with 130 additions and 127 deletions

View File

@ -9,6 +9,7 @@ import sqlConsoleDialog from './dialogs/sql_console.js';
import markdownImportDialog from './dialogs/markdown_import.js'; import markdownImportDialog from './dialogs/markdown_import.js';
import exportDialog from './dialogs/export.js'; import exportDialog from './dialogs/export.js';
import importDialog from './dialogs/import.js'; import importDialog from './dialogs/import.js';
import protectedSessionDialog from './dialogs/protected_session.js';
import cloning from './services/cloning.js'; import cloning from './services/cloning.js';
import contextMenu from './services/tree_context_menu.js'; import contextMenu from './services/tree_context_menu.js';

View File

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

View File

@ -15,6 +15,7 @@ import noteDetailSearch from "./note_detail_search.js";
import noteDetailRender from "./note_detail_render.js"; import noteDetailRender from "./note_detail_render.js";
import noteDetailRelationMap from "./note_detail_relation_map.js"; import noteDetailRelationMap from "./note_detail_relation_map.js";
import noteDetailProtectedSession from "./note_detail_protected_session.js"; import noteDetailProtectedSession from "./note_detail_protected_session.js";
import protectedSessionService from "./protected_session.js";
const $noteTabContentsContainer = $("#note-tab-container"); const $noteTabContentsContainer = $("#note-tab-container");
@ -32,26 +33,24 @@ const componentClasses = {
let tabIdCounter = 1; let tabIdCounter = 1;
class NoteContext { class NoteContext {
constructor(chromeTabs, note, openOnBackground) { constructor(chromeTabs, openOnBackground) {
this.tabId = tabIdCounter++; this.tabId = tabIdCounter++;
this.chromeTabs = chromeTabs; this.chromeTabs = chromeTabs;
/** @type {NoteFull} */ this.tab = this.chromeTabs.addTab({
this.note = note; title: '', // will be set later
this.noteId = note.noteId; id: this.tabId
}, {
background: openOnBackground
});
this.$noteTabContent = $(".note-tab-content-template").clone(); this.$noteTabContent = $(".note-tab-content-template").clone();
this.$noteTabContent.removeClass('note-tab-content-template'); this.$noteTabContent.removeClass('note-tab-content-template');
this.$noteTabContent.attr('data-note-id', this.noteId);
this.$noteTabContent.attr('data-tab-id', this.tabId); this.$noteTabContent.attr('data-tab-id', this.tabId);
$noteTabContentsContainer.append(this.$noteTabContent); $noteTabContentsContainer.append(this.$noteTabContent);
console.log(`Creating note tab ${this.tabId} for ${this.noteId}`);
this.$noteTitle = this.$noteTabContent.find(".note-title"); this.$noteTitle = this.$noteTabContent.find(".note-title");
this.$noteDetailComponents = this.$noteTabContent.find(".note-detail-component"); 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.$childrenOverview = this.$noteTabContent.find(".children-overview");
this.$scriptArea = this.$noteTabContent.find(".note-detail-script-area"); this.$scriptArea = this.$noteTabContent.find(".note-detail-script-area");
this.$savedIndicator = this.$noteTabContent.find(".saved-indicator"); this.$savedIndicator = this.$noteTabContent.find(".saved-indicator");
@ -69,25 +68,40 @@ class NoteContext {
treeService.setNoteTitle(this.noteId, title); treeService.setNoteTitle(this.noteId, title);
}); });
this.tab = this.chromeTabs.addTab({ this.$protectButton = this.$noteTabContent.find(".protect-button");
title: note.title, this.$protectButton.click(protectedSessionService.protectNoteAndSendToServer);
id: this.tabId
}, {
background: openOnBackground
});
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) { setNote(note) {
this.noteId = note.noteId; this.noteId = note.noteId;
this.note = note; this.note = note;
this.tab.setAttribute('data-note-id', this.noteId);
this.$noteTabContent.attr('data-note-id', note.noteId); this.$noteTabContent.attr('data-note-id', note.noteId);
this.chromeTabs.updateTab(this.tab, {title: note.title}); this.chromeTabs.updateTab(this.tab, {title: note.title});
this.attributes.invalidateAttributes(); 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}`); console.log(`Switched tab ${this.tabId} to ${this.noteId}`);
} }
@ -183,23 +197,6 @@ class NoteContext {
this.$childrenOverview.show(); 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; export default NoteContext;

View File

@ -1,8 +1,5 @@
import treeService from './tree.js'; import treeService from './tree.js';
import NoteContext from './note_context.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 server from './server.js';
import messagingService from "./messaging.js"; import messagingService from "./messaging.js";
import infoService from "./info.js"; import infoService from "./info.js";
@ -21,6 +18,7 @@ const $savedIndicator = $(".saved-indicator");
let detailLoadedListeners = []; let detailLoadedListeners = [];
/** @return {NoteFull} */
function getActiveNote() { function getActiveNote() {
const activeContext = getActiveContext(); const activeContext = getActiveContext();
return activeContext ? activeContext.note : null; return activeContext ? activeContext.note : null;
@ -44,6 +42,14 @@ async function reload() {
await loadNoteDetail(getActiveNoteId()); 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) { async function openInTab(noteId) {
await loadNoteDetail(noteId, true); await loadNoteDetail(noteId, true);
} }
@ -76,18 +82,6 @@ async function saveNotesIfChanged() {
/** @type {NoteContext[]} */ /** @type {NoteContext[]} */
let noteContexts = []; 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} */ /** @returns {NoteContext} */
function getActiveContext() { function getActiveContext() {
for (const ctx of noteContexts) { for (const ctx of noteContexts) {
@ -105,44 +99,21 @@ function showTab(tabId) {
} }
} }
async function loadNoteDetail(noteId, newTab = false) { /**
const loadedNote = await loadNote(noteId); * @param {NoteContext} ctx
let ctx; * @param {NoteFull} note
*/
if (noteContexts.length === 0 || newTab) { async function loadNoteDetailToContext(ctx, note) {
// if it's a new tab explicitly by user then it's in background ctx.setNote(note);
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;
}
if (utils.isDesktop()) { if (utils.isDesktop()) {
// needs to happen after loading the note itself because it references active noteId // needs to happen after loading the note itself because it references active noteId
ctx.attributes.refreshAttributes(); ctx.attributes.refreshAttributes();
} } else {
else {
// mobile usually doesn't need attributes so we just invalidate // mobile usually doesn't need attributes so we just invalidate
ctx.attributes.invalidateAttributes(); ctx.attributes.invalidateAttributes();
} }
ctx.updateNoteView();
ctx.noteChangeDisabled = true; ctx.noteChangeDisabled = true;
try { try {
@ -164,12 +135,11 @@ async function loadNoteDetail(noteId, newTab = false) {
ctx.$noteTitle.removeAttr("readonly"); // this can be set by protected session service ctx.$noteTitle.removeAttr("readonly"); // this can be set by protected session service
await ctx.getComponent().show(ctx); await ctx.getComponent().show(ctx);
} } finally {
finally {
ctx.noteChangeDisabled = false; ctx.noteChangeDisabled = false;
} }
treeService.setBranchBackgroundBasedOnProtectedStatus(noteId); treeService.setBranchBackgroundBasedOnProtectedStatus(note.noteId);
// after loading new note make sure editor is scrolled to the top // after loading new note make sure editor is scrolled to the top
ctx.getComponent().scrollToTop(); 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) { async function loadNote(noteId) {
const row = await server.get('notes/' + noteId); const row = await server.get('notes/' + noteId);
@ -282,6 +281,7 @@ setInterval(saveNotesIfChanged, 3000);
export default { export default {
reload, reload,
reloadAllTabs,
openInTab, openInTab,
switchToNote, switchToNote,
loadNote, loadNote,

View File

@ -10,12 +10,6 @@ class NoteDetailProtectedSession {
this.$passwordForm = ctx.$noteTabContent.find(".protected-session-password-form"); this.$passwordForm = ctx.$noteTabContent.find(".protected-session-password-form");
this.$passwordInput = ctx.$noteTabContent.find(".protected-session-password"); 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(() => { this.$passwordForm.submit(() => {
const password = this.$passwordInput.val(); const password = this.$passwordInput.val();
this.$passwordInput.val(""); this.$passwordInput.val("");

View File

@ -4,18 +4,13 @@ import utils from './utils.js';
import server from './server.js'; import server from './server.js';
import protectedSessionHolder from './protected_session_holder.js'; import protectedSessionHolder from './protected_session_holder.js';
import infoService from "./info.js"; import infoService from "./info.js";
import protectedSessionDialog from "../dialogs/protected_session.js";
const $enterProtectedSessionButton = $("#enter-protected-session-button"); const $enterProtectedSessionButton = $("#enter-protected-session-button");
const $leaveProtectedSessionButton = $("#leave-protected-session-button"); const $leaveProtectedSessionButton = $("#leave-protected-session-button");
let protectedSessionDeferred = null; let protectedSessionDeferred = null;
async function enterProtectedSession() {
if (!protectedSessionHolder.isProtectedSessionAvailable()) {
await ensureProtectedSession(true, true);
}
}
async function leaveProtectedSession() { async function leaveProtectedSession() {
if (protectedSessionHolder.isProtectedSessionAvailable()) { if (protectedSessionHolder.isProtectedSessionAvailable()) {
utils.reloadApp(); 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 */ /** 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(); 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 // using deferred instead of promise because it allows resolving from outside
protectedSessionDeferred = dfd; protectedSessionDeferred = dfd;
if (modal) { protectedSessionDialog.show();
$dialog.modal();
}
else {
$component.show();
}
}
else {
dfd.resolve(false);
} }
return dfd.promise(); return dfd.promise();
@ -56,12 +46,12 @@ async function setupProtectedSession(password) {
await treeService.reload(); await treeService.reload();
// it's important that tree has been already reloaded at this point // it's important that tree has been already reloaded at this point since detail also uses tree cache (for children overview)
// since detail also uses tree cache (for children overview) // children overview is the reason why we need to reload all tabs
await noteDetailService.reload(); await noteDetailService.reloadAllTabs();
if (protectedSessionDeferred !== null) { if (protectedSessionDeferred !== null) {
ensureDialogIsClosed(); protectedSessionDialog.close();
protectedSessionDeferred.resolve(true); protectedSessionDeferred.resolve(true);
protectedSessionDeferred = null; protectedSessionDeferred = null;
@ -73,16 +63,6 @@ async function setupProtectedSession(password) {
infoService.showMessage("Protected session has been started."); 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) { async function enterProtectedSessionOnServer(password) {
return await server.post('login/protected', { return await server.post('login/protected', {
password: password password: password
@ -94,16 +74,16 @@ async function protectNoteAndSendToServer() {
return; return;
} }
await ensureProtectedSession(true, true); await enterProtectedSession();
const note = noteDetailService.getActiveNote(); const note = noteDetailService.getActiveNote();
note.isProtected = true; note.isProtected = true;
await noteDetailService.saveNote(note); await noteDetailService.getActiveContext().saveNote();
treeService.setProtected(note.noteId, note.isProtected); treeService.setProtected(note.noteId, note.isProtected);
noteDetailService.updateNoteView(); await noteDetailService.reload();
} }
async function unprotectNoteAndSendToServer() { async function unprotectNoteAndSendToServer() {
@ -117,7 +97,7 @@ async function unprotectNoteAndSendToServer() {
if (!protectedSessionHolder.isProtectedSessionAvailable()) { if (!protectedSessionHolder.isProtectedSessionAvailable()) {
console.log("Unprotecting notes outside of protected session is not allowed."); 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 // 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. // 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; activeNote.isProtected = false;
await noteDetailService.saveNote(activeNote); await noteDetailService.getActiveContext().saveNote();
treeService.setProtected(activeNote.noteId, activeNote.isProtected); treeService.setProtected(activeNote.noteId, activeNote.isProtected);
noteDetailService.updateNoteView(); await noteDetailService.reload();
} }
async function protectSubtree(noteId, protect) { async function protectSubtree(noteId, protect) {
await ensureProtectedSession(true, true); await enterProtectedSession();
await server.put('notes/' + noteId + "/protect/" + (protect ? 1 : 0)); await server.put('notes/' + noteId + "/protect/" + (protect ? 1 : 0));
@ -145,9 +125,7 @@ async function protectSubtree(noteId, protect) {
} }
export default { export default {
ensureProtectedSession,
protectSubtree, protectSubtree,
ensureDialogIsClosed,
enterProtectedSession, enterProtectedSession,
leaveProtectedSession, leaveProtectedSession,
protectNoteAndSendToServer, protectNoteAndSendToServer,