mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	new tab infrastructure
This commit is contained in:
		| @@ -91,16 +91,18 @@ function getTabContexts() { | |||||||
|  |  | ||||||
| /** @returns {TabContext} */ | /** @returns {TabContext} */ | ||||||
| function getActiveTabContext() { | function getActiveTabContext() { | ||||||
|     for (const ctx of tabContexts) { |     const activeTabEl = tabRow.activeTabEl; | ||||||
|         if (ctx.$tabContent.is(":visible")) { |  | ||||||
|             return ctx; |     if (!activeTabEl) { | ||||||
|         } |         return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     const tabId = activeTabEl.getAttribute('data-tab-id'); | ||||||
|  |  | ||||||
|  |     return tabContexts.find(tc => tc.tabId === tabId); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function showTab(tabId) { | async function showTab(tabId) { | ||||||
|     tabId = parseInt(tabId); |  | ||||||
|  |  | ||||||
|     for (const ctx of tabContexts) { |     for (const ctx of tabContexts) { | ||||||
|         ctx.$tabContent.toggle(ctx.tabId === tabId); |         ctx.$tabContent.toggle(ctx.tabId === tabId); | ||||||
|     } |     } | ||||||
| @@ -114,14 +116,32 @@ async function showTab(tabId) { | |||||||
|     treeService.clearSelectedNodes(); |     treeService.clearSelectedNodes(); | ||||||
|  |  | ||||||
|     const newActiveTabContext = getActiveTabContext(); |     const newActiveTabContext = getActiveTabContext(); | ||||||
|     const newActiveNode = await treeService.getNodeFromPath(newActiveTabContext.notePath); |  | ||||||
|  |  | ||||||
|     if (newActiveNode && newActiveNode.isVisible()) { |     if (newActiveTabContext && newActiveTabContext.notePath) { | ||||||
|         newActiveNode.setActive(true, { noEvents: true }); |         const newActiveNode = await treeService.getNodeFromPath(newActiveTabContext.notePath); | ||||||
|         newActiveNode.setSelected(true); |  | ||||||
|  |         if (newActiveNode && newActiveNode.isVisible()) { | ||||||
|  |             newActiveNode.setActive(true, {noEvents: true}); | ||||||
|  |             newActiveNode.setSelected(true); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function renderComponent(ctx) { | ||||||
|  |     for (const componentType in ctx.components) { | ||||||
|  |         if (componentType !== ctx.note.type) { | ||||||
|  |             ctx.components[componentType].cleanup(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ctx.$noteDetailComponents.hide(); | ||||||
|  |  | ||||||
|  |     ctx.$noteTitle.show(); // this can be hidden by empty detail | ||||||
|  |     ctx.$noteTitle.removeAttr("readonly"); // this can be set by protected session service | ||||||
|  |  | ||||||
|  |     await ctx.getComponent().render(); | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @param {TabContext} ctx |  * @param {TabContext} ctx | ||||||
|  * @param {NoteFull} note |  * @param {NoteFull} note | ||||||
| @@ -129,6 +149,8 @@ async function showTab(tabId) { | |||||||
| async function loadNoteDetailToContext(ctx, note, notePath) { | async function loadNoteDetailToContext(ctx, note, notePath) { | ||||||
|     ctx.setNote(note, notePath); |     ctx.setNote(note, notePath); | ||||||
|  |  | ||||||
|  |     openTabsChanged(); | ||||||
|  |  | ||||||
|     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(); | ||||||
| @@ -147,17 +169,7 @@ async function loadNoteDetailToContext(ctx, note, notePath) { | |||||||
|             ctx.noteType.mime(ctx.note.mime); |             ctx.noteType.mime(ctx.note.mime); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         for (const componentType in ctx.components) { |         await renderComponent(ctx); | ||||||
|             if (componentType !== ctx.note.type) { |  | ||||||
|                 ctx.components[componentType].cleanup(); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ctx.$noteDetailComponents.hide(); |  | ||||||
|  |  | ||||||
|         ctx.$noteTitle.removeAttr("readonly"); // this can be set by protected session service |  | ||||||
|  |  | ||||||
|         await ctx.getComponent().show(ctx); |  | ||||||
|     } finally { |     } finally { | ||||||
|         ctx.noteChangeDisabled = false; |         ctx.noteChangeDisabled = false; | ||||||
|     } |     } | ||||||
| @@ -180,11 +192,16 @@ async function loadNoteDetailToContext(ctx, note, notePath) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function loadNoteDetail(notePath, options = {}) { | async function loadNoteDetail(origNotePath, options = {}) { | ||||||
|     const newTab = !!options.newTab; |     const newTab = !!options.newTab; | ||||||
|     const activate = !!options.activate; |     const activate = !!options.activate; | ||||||
|  |  | ||||||
|     notePath = await treeService.resolveNotePath(notePath); |     const notePath = await treeService.resolveNotePath(origNotePath); | ||||||
|  |  | ||||||
|  |     if (!notePath) { | ||||||
|  |         console.error(`Cannot resolve note path ${origNotePath}`); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const noteId = treeUtils.getNoteIdFromNotePath(notePath); |     const noteId = treeUtils.getNoteIdFromNotePath(notePath); | ||||||
|     const loadedNote = await loadNote(noteId); |     const loadedNote = await loadNote(noteId); | ||||||
| @@ -293,6 +310,15 @@ $tabContentsContainer.on("drop", e => { | |||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | tabRow.addListener('newTab', async () => { | ||||||
|  |     const ctx = new TabContext(tabRow); | ||||||
|  |     tabContexts.push(ctx); | ||||||
|  |  | ||||||
|  |     renderComponent(ctx); | ||||||
|  |  | ||||||
|  |     await tabRow.setCurrentTab(ctx.tab); | ||||||
|  | }); | ||||||
|  |  | ||||||
| tabRow.addListener('activeTabChange', async ({ detail }) => { | tabRow.addListener('activeTabChange', async ({ detail }) => { | ||||||
|     const tabId = detail.tabEl.getAttribute('data-tab-id'); |     const tabId = detail.tabEl.getAttribute('data-tab-id'); | ||||||
|  |  | ||||||
| @@ -302,7 +328,7 @@ tabRow.addListener('activeTabChange', async ({ detail }) => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| tabRow.addListener('tabRemove', async ({ detail }) => { | tabRow.addListener('tabRemove', async ({ detail }) => { | ||||||
|     const tabId = parseInt(detail.tabEl.getAttribute('data-tab-id')); |     const tabId = detail.tabEl.getAttribute('data-tab-id'); | ||||||
|  |  | ||||||
|     const tabContentToDelete = tabContexts.find(nc => nc.tabId === tabId); |     const tabContentToDelete = tabContexts.find(nc => nc.tabId === tabId); | ||||||
|  |  | ||||||
| @@ -386,7 +412,7 @@ async function saveOpenTabs() { | |||||||
|     const openTabs = []; |     const openTabs = []; | ||||||
|  |  | ||||||
|     for (const tabEl of tabRow.tabEls) { |     for (const tabEl of tabRow.tabEls) { | ||||||
|         const tabId = parseInt(tabEl.getAttribute('data-tab-id')); |         const tabId = tabEl.getAttribute('data-tab-id'); | ||||||
|         const tabContext = tabContexts.find(tc => tc.tabId === tabId); |         const tabContext = tabContexts.find(tc => tc.tabId === tabId); | ||||||
|  |  | ||||||
|         if (tabContext) { |         if (tabContext) { | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ class NoteDetailCode { | |||||||
|         this.$executeScriptButton.click(() => this.executeCurrentNote()); |         this.$executeScriptButton.click(() => this.executeCurrentNote()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async show() { |     async render() { | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); |         await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); | ||||||
|  |  | ||||||
|         if (!this.codeEditor) { |         if (!this.codeEditor) { | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import server from "./server.js"; | import server from "./server.js"; | ||||||
| import protectedSessionHolder from "./protected_session_holder.js"; |  | ||||||
| import noteDetailService from "./note_detail.js"; |  | ||||||
|  |  | ||||||
| class NoteDetailFile { | class NoteDetailFile { | ||||||
|     /** |     /** | ||||||
| @@ -33,7 +31,7 @@ class NoteDetailFile { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async show() { |     async render() { | ||||||
|         const attributes = await server.get('notes/' + this.ctx.note.noteId + '/attributes'); |         const attributes = await server.get('notes/' + this.ctx.note.noteId + '/attributes'); | ||||||
|         const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); |         const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ class NoteDetailImage { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async show() { |     async render() { | ||||||
|         const attributes = await server.get('notes/' + this.ctx.note.noteId + '/attributes'); |         const attributes = await server.get('notes/' + this.ctx.note.noteId + '/attributes'); | ||||||
|         const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); |         const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class NoteDetailProtectedSession { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     show() { |     render() { | ||||||
|         this.$component.show(); |         this.$component.show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -207,7 +207,7 @@ class NoteDetailRelationMap { | |||||||
|         return id.substr(13); |         return id.substr(13); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async show() { |     async render() { | ||||||
|         this.$component.show(); |         this.$component.show(); | ||||||
|  |  | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP); |         await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP); | ||||||
|   | |||||||
| @@ -14,10 +14,10 @@ class NoteDetailRender { | |||||||
|         this.$noteDetailRenderContent = ctx.$tabContent.find('.note-detail-render-content'); |         this.$noteDetailRenderContent = ctx.$tabContent.find('.note-detail-render-content'); | ||||||
|         this.$renderButton = ctx.$tabContent.find('.render-button'); |         this.$renderButton = ctx.$tabContent.find('.render-button'); | ||||||
|  |  | ||||||
|         this.$renderButton.click(this.show); |         this.$renderButton.click(this.render); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async show() { |     async render() { | ||||||
|         const attributes = await attributeService.getAttributes(); |         const attributes = await attributeService.getAttributes(); | ||||||
|         const renderNotes = attributes.filter(attr => |         const renderNotes = attributes.filter(attr => | ||||||
|             attr.type === 'relation' |             attr.type === 'relation' | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ class NoteDetailSearch { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     show() { |     render() { | ||||||
|         this.$help.html(searchNotesService.getHelpText()); |         this.$help.html(searchNotesService.getHelpText()); | ||||||
|  |  | ||||||
|         this.$component.show(); |         this.$component.show(); | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ class NoteDetailText { | |||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async show() { |     async render() { | ||||||
|         if (!this.textEditor) { |         if (!this.textEditor) { | ||||||
|             await libraryLoader.requireLibrary(libraryLoader.CKEDITOR); |             await libraryLoader.requireLibrary(libraryLoader.CKEDITOR); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,8 +7,9 @@ import treeUtils from "./tree_utils.js"; | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import {NoteTypeContext} from "./note_type.js"; | import {NoteTypeContext} from "./note_type.js"; | ||||||
| import noteDetailService from "./note_detail.js"; | import noteDetailService from "./note_detail.js"; | ||||||
| import noteDetailCode from "./note_detail_code.js"; | import noteDetailEmpty from "./note_detail_empty.js"; | ||||||
| import noteDetailText from "./note_detail_text.js"; | import noteDetailText from "./note_detail_text.js"; | ||||||
|  | import noteDetailCode from "./note_detail_code.js"; | ||||||
| import noteDetailFile from "./note_detail_file.js"; | import noteDetailFile from "./note_detail_file.js"; | ||||||
| import noteDetailImage from "./note_detail_image.js"; | import noteDetailImage from "./note_detail_image.js"; | ||||||
| import noteDetailSearch from "./note_detail_search.js"; | import noteDetailSearch from "./note_detail_search.js"; | ||||||
| @@ -20,8 +21,9 @@ import protectedSessionService from "./protected_session.js"; | |||||||
| const $tabContentsContainer = $("#note-tab-container"); | const $tabContentsContainer = $("#note-tab-container"); | ||||||
|  |  | ||||||
| const componentClasses = { | const componentClasses = { | ||||||
|     'code': noteDetailCode, |     'empty': noteDetailEmpty, | ||||||
|     'text': noteDetailText, |     'text': noteDetailText, | ||||||
|  |     'code': noteDetailCode, | ||||||
|     'file': noteDetailFile, |     'file': noteDetailFile, | ||||||
|     'image': noteDetailImage, |     'image': noteDetailImage, | ||||||
|     'search': noteDetailSearch, |     'search': noteDetailSearch, | ||||||
| @@ -30,19 +32,14 @@ const componentClasses = { | |||||||
|     'protected-session': noteDetailProtectedSession |     'protected-session': noteDetailProtectedSession | ||||||
| }; | }; | ||||||
|  |  | ||||||
| let tabIdCounter = 1; |  | ||||||
|  |  | ||||||
| class TabContext { | class TabContext { | ||||||
|     /** |     /** | ||||||
|      * @param {TabRow} tabRow |      * @param {TabRow} tabRow | ||||||
|      */ |      */ | ||||||
|     constructor(tabRow) { |     constructor(tabRow) { | ||||||
|         this.tabId = tabIdCounter++; |  | ||||||
|         this.tabRow = tabRow; |         this.tabRow = tabRow; | ||||||
|         this.tab = this.tabRow.addTab({ |         this.tab = this.tabRow.addTab(); | ||||||
|             title: '', // will be set later |         this.tabId = this.tab.getAttribute('data-tab-id'); | ||||||
|             id: this.tabId |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         this.$tabContent = $(".note-tab-content-template").clone(); |         this.$tabContent = $(".note-tab-content-template").clone(); | ||||||
|         this.$tabContent.removeClass('note-tab-content-template'); |         this.$tabContent.removeClass('note-tab-content-template'); | ||||||
| @@ -51,6 +48,7 @@ class TabContext { | |||||||
|         $tabContentsContainer.append(this.$tabContent); |         $tabContentsContainer.append(this.$tabContent); | ||||||
|  |  | ||||||
|         this.$noteTitle = this.$tabContent.find(".note-title"); |         this.$noteTitle = this.$tabContent.find(".note-title"); | ||||||
|  |         this.$noteTitleRow = this.$tabContent.find(".note-title-row"); | ||||||
|         this.$noteDetailComponents = this.$tabContent.find(".note-detail-component"); |         this.$noteDetailComponents = this.$tabContent.find(".note-detail-component"); | ||||||
|         this.$childrenOverview = this.$tabContent.find(".children-overview"); |         this.$childrenOverview = this.$tabContent.find(".children-overview"); | ||||||
|         this.$scriptArea = this.$tabContent.find(".note-detail-script-area"); |         this.$scriptArea = this.$tabContent.find(".note-detail-script-area"); | ||||||
| @@ -82,9 +80,6 @@ class TabContext { | |||||||
|         this.noteId = note.noteId; |         this.noteId = note.noteId; | ||||||
|         this.notePath = notePath; |         this.notePath = notePath; | ||||||
|         this.note = note; |         this.note = note; | ||||||
|         this.tab.setAttribute('data-note-id', this.noteId); |  | ||||||
|         this.$tabContent.attr('data-note-id', note.noteId); |  | ||||||
|  |  | ||||||
|         this.tabRow.updateTab(this.tab, {title: note.title}); |         this.tabRow.updateTab(this.tab, {title: note.title}); | ||||||
|  |  | ||||||
|         this.attributes.invalidateAttributes(); |         this.attributes.invalidateAttributes(); | ||||||
| @@ -108,19 +103,25 @@ class TabContext { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     getComponent() { |     getComponent() { | ||||||
|         let type = this.note.type; |         let type; | ||||||
|  |  | ||||||
|         if (this.note.isProtected) { |         if (this.note) { | ||||||
|             if (protectedSessionHolder.isProtectedSessionAvailable()) { |             type = this.note.type; | ||||||
|                 protectedSessionHolder.touchProtectedSession(); |  | ||||||
|             } |  | ||||||
|             else { |  | ||||||
|                 type = 'protected-session'; |  | ||||||
|  |  | ||||||
|                 // user shouldn't be able to edit note title |             if (this.note.isProtected) { | ||||||
|                 this.$noteTitle.prop("readonly", true); |                 if (protectedSessionHolder.isProtectedSessionAvailable()) { | ||||||
|  |                     protectedSessionHolder.touchProtectedSession(); | ||||||
|  |                 } else { | ||||||
|  |                     type = 'protected-session'; | ||||||
|  |  | ||||||
|  |                     // user shouldn't be able to edit note title | ||||||
|  |                     this.$noteTitle.prop("readonly", true); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         else { | ||||||
|  |             type = 'empty'; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (!(type in this.components)) { |         if (!(type in this.components)) { | ||||||
|             this.components[type] = new componentClasses[type](this); |             this.components[type] = new componentClasses[type](this); | ||||||
|   | |||||||
| @@ -38,6 +38,7 @@ class TabRow { | |||||||
|     constructor() { |     constructor() { | ||||||
|         this.draggabillies = []; |         this.draggabillies = []; | ||||||
|         this.eventListeners = {}; |         this.eventListeners = {}; | ||||||
|  |         this.tabIdCounter = 1; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     init(el) { |     init(el) { | ||||||
| @@ -164,12 +165,13 @@ class TabRow { | |||||||
|         const div = document.createElement('div'); |         const div = document.createElement('div'); | ||||||
|         div.innerHTML = tabTemplate; |         div.innerHTML = tabTemplate; | ||||||
|         const tabEl = div.firstElementChild; |         const tabEl = div.firstElementChild; | ||||||
|  |         tabEl.setAttribute('data-tab-id', "t" + this.tabIdCounter++); | ||||||
|  |  | ||||||
|         tabEl.classList.add('note-tab-was-just-added'); |         tabEl.classList.add('note-tab-was-just-added'); | ||||||
|         setTimeout(() => tabEl.classList.remove('note-tab-was-just-added'), 500); |         setTimeout(() => tabEl.classList.remove('note-tab-was-just-added'), 500); | ||||||
|  |  | ||||||
|         tabProperties = Object.assign({}, defaultTapProperties, tabProperties); |         tabProperties = Object.assign({}, defaultTapProperties, tabProperties); | ||||||
|         this.tabContentEl.appendChild(tabEl); |         this.newTabEl.before(tabEl); | ||||||
|         this.setVisibility(); |         this.setVisibility(); | ||||||
|         this.setTabCloseEventListener(tabEl); |         this.setTabCloseEventListener(tabEl); | ||||||
|         this.updateTab(tabEl, tabProperties); |         this.updateTab(tabEl, tabProperties); | ||||||
| @@ -266,10 +268,6 @@ class TabRow { | |||||||
|  |  | ||||||
|     updateTab(tabEl, tabProperties) { |     updateTab(tabEl, tabProperties) { | ||||||
|         tabEl.querySelector('.note-tab-title').textContent = tabProperties.title; |         tabEl.querySelector('.note-tab-title').textContent = tabProperties.title; | ||||||
|  |  | ||||||
|         if (tabProperties.id) { |  | ||||||
|             tabEl.setAttribute('data-tab-id', tabProperties.id); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     cleanUpPreviouslyDraggedTabs() { |     cleanUpPreviouslyDraggedTabs() { | ||||||
| @@ -374,6 +372,8 @@ class TabRow { | |||||||
|  |  | ||||||
|         this.tabContentEl.appendChild(this.newTabEl); |         this.tabContentEl.appendChild(this.newTabEl); | ||||||
|         this.layoutTabs(); |         this.layoutTabs(); | ||||||
|  |  | ||||||
|  |         this.newTabEl.addEventListener('click', _ => this.emit('newTab')); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     closest(value, array) { |     closest(value, array) { | ||||||
|   | |||||||
| @@ -78,7 +78,7 @@ class TreeContextMenu { | |||||||
|  |  | ||||||
|     async selectContextMenuItem(event, cmd) { |     async selectContextMenuItem(event, cmd) { | ||||||
|         if (cmd === 'openInTab') { |         if (cmd === 'openInTab') { | ||||||
|             const notePath = treeUtils.getNotePath(this.node); |             const notePath = await treeUtils.getNotePath(this.node); | ||||||
|  |  | ||||||
|             noteDetailService.openInTab(notePath); |             noteDetailService.openInTab(notePath); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -15,6 +15,8 @@ | |||||||
|  |  | ||||||
|             <div class="note-detail-code note-detail-component"></div> |             <div class="note-detail-code note-detail-component"></div> | ||||||
|  |  | ||||||
|  |             <% include details/empty.ejs %> | ||||||
|  |  | ||||||
|             <% include details/search.ejs %> |             <% include details/search.ejs %> | ||||||
|  |  | ||||||
|             <% include details/render.ejs %> |             <% include details/render.ejs %> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user