mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	protected session in a dialog now works, proper reloading
This commit is contained in:
		| @@ -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'; | ||||
|   | ||||
							
								
								
									
										33
									
								
								src/public/javascripts/dialogs/protected_session.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/public/javascripts/dialogs/protected_session.js
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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; | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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(""); | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user