diff --git a/src/public/javascripts/services/note_detail.js b/src/public/javascripts/services/note_detail.js index a7ec0cd77..3f6cc3a92 100644 --- a/src/public/javascripts/services/note_detail.js +++ b/src/public/javascripts/services/note_detail.js @@ -91,16 +91,18 @@ function getTabContexts() { /** @returns {TabContext} */ function getActiveTabContext() { - for (const ctx of tabContexts) { - if (ctx.$tabContent.is(":visible")) { - return ctx; - } + const activeTabEl = tabRow.activeTabEl; + + if (!activeTabEl) { + return null; } + + const tabId = activeTabEl.getAttribute('data-tab-id'); + + return tabContexts.find(tc => tc.tabId === tabId); } async function showTab(tabId) { - tabId = parseInt(tabId); - for (const ctx of tabContexts) { ctx.$tabContent.toggle(ctx.tabId === tabId); } @@ -114,14 +116,32 @@ async function showTab(tabId) { treeService.clearSelectedNodes(); const newActiveTabContext = getActiveTabContext(); - const newActiveNode = await treeService.getNodeFromPath(newActiveTabContext.notePath); - if (newActiveNode && newActiveNode.isVisible()) { - newActiveNode.setActive(true, { noEvents: true }); - newActiveNode.setSelected(true); + if (newActiveTabContext && newActiveTabContext.notePath) { + const newActiveNode = await treeService.getNodeFromPath(newActiveTabContext.notePath); + + 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 {NoteFull} note @@ -129,6 +149,8 @@ async function showTab(tabId) { async function loadNoteDetailToContext(ctx, note, notePath) { ctx.setNote(note, notePath); + openTabsChanged(); + if (utils.isDesktop()) { // needs to happen after loading the note itself because it references active noteId ctx.attributes.refreshAttributes(); @@ -147,17 +169,7 @@ async function loadNoteDetailToContext(ctx, note, notePath) { ctx.noteType.mime(ctx.note.mime); } - for (const componentType in ctx.components) { - 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); + await renderComponent(ctx); } finally { 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 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 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 }) => { const tabId = detail.tabEl.getAttribute('data-tab-id'); @@ -302,7 +328,7 @@ tabRow.addListener('activeTabChange', 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); @@ -386,7 +412,7 @@ async function saveOpenTabs() { const openTabs = []; 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); if (tabContext) { diff --git a/src/public/javascripts/services/note_detail_code.js b/src/public/javascripts/services/note_detail_code.js index 1c0880c6a..34bddd332 100644 --- a/src/public/javascripts/services/note_detail_code.js +++ b/src/public/javascripts/services/note_detail_code.js @@ -21,7 +21,7 @@ class NoteDetailCode { this.$executeScriptButton.click(() => this.executeCurrentNote()); } - async show() { + async render() { await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); if (!this.codeEditor) { diff --git a/src/public/javascripts/services/note_detail_file.js b/src/public/javascripts/services/note_detail_file.js index 126a4895f..ad45e963d 100644 --- a/src/public/javascripts/services/note_detail_file.js +++ b/src/public/javascripts/services/note_detail_file.js @@ -1,7 +1,5 @@ import utils from "./utils.js"; import server from "./server.js"; -import protectedSessionHolder from "./protected_session_holder.js"; -import noteDetailService from "./note_detail.js"; class NoteDetailFile { /** @@ -33,7 +31,7 @@ class NoteDetailFile { }); } - async show() { + async render() { const attributes = await server.get('notes/' + this.ctx.note.noteId + '/attributes'); const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); diff --git a/src/public/javascripts/services/note_detail_image.js b/src/public/javascripts/services/note_detail_image.js index 7df383c0f..4439084b6 100644 --- a/src/public/javascripts/services/note_detail_image.js +++ b/src/public/javascripts/services/note_detail_image.js @@ -42,7 +42,7 @@ class NoteDetailImage { }); } - async show() { + async render() { const attributes = await server.get('notes/' + this.ctx.note.noteId + '/attributes'); const attributeMap = utils.toObject(attributes, l => [l.name, l.value]); diff --git a/src/public/javascripts/services/note_detail_protected_session.js b/src/public/javascripts/services/note_detail_protected_session.js index 74405ab46..eced49023 100644 --- a/src/public/javascripts/services/note_detail_protected_session.js +++ b/src/public/javascripts/services/note_detail_protected_session.js @@ -20,7 +20,7 @@ class NoteDetailProtectedSession { }); } - show() { + render() { this.$component.show(); } diff --git a/src/public/javascripts/services/note_detail_relation_map.js b/src/public/javascripts/services/note_detail_relation_map.js index 7bf682234..8125976db 100644 --- a/src/public/javascripts/services/note_detail_relation_map.js +++ b/src/public/javascripts/services/note_detail_relation_map.js @@ -207,7 +207,7 @@ class NoteDetailRelationMap { return id.substr(13); } - async show() { + async render() { this.$component.show(); await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP); diff --git a/src/public/javascripts/services/note_detail_render.js b/src/public/javascripts/services/note_detail_render.js index deeff20bb..3c2503509 100644 --- a/src/public/javascripts/services/note_detail_render.js +++ b/src/public/javascripts/services/note_detail_render.js @@ -14,10 +14,10 @@ class NoteDetailRender { this.$noteDetailRenderContent = ctx.$tabContent.find('.note-detail-render-content'); 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 renderNotes = attributes.filter(attr => attr.type === 'relation' diff --git a/src/public/javascripts/services/note_detail_search.js b/src/public/javascripts/services/note_detail_search.js index e4bd989c4..cbeac81c8 100644 --- a/src/public/javascripts/services/note_detail_search.js +++ b/src/public/javascripts/services/note_detail_search.js @@ -19,7 +19,7 @@ class NoteDetailSearch { }); } - show() { + render() { this.$help.html(searchNotesService.getHelpText()); this.$component.show(); diff --git a/src/public/javascripts/services/note_detail_text.js b/src/public/javascripts/services/note_detail_text.js index f600793b2..7402ddc96 100644 --- a/src/public/javascripts/services/note_detail_text.js +++ b/src/public/javascripts/services/note_detail_text.js @@ -27,7 +27,7 @@ class NoteDetailText { }) } - async show() { + async render() { if (!this.textEditor) { await libraryLoader.requireLibrary(libraryLoader.CKEDITOR); diff --git a/src/public/javascripts/services/tab_context.js b/src/public/javascripts/services/tab_context.js index c8243246b..af87b394a 100644 --- a/src/public/javascripts/services/tab_context.js +++ b/src/public/javascripts/services/tab_context.js @@ -7,8 +7,9 @@ import treeUtils from "./tree_utils.js"; import utils from "./utils.js"; import {NoteTypeContext} from "./note_type.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 noteDetailCode from "./note_detail_code.js"; import noteDetailFile from "./note_detail_file.js"; import noteDetailImage from "./note_detail_image.js"; import noteDetailSearch from "./note_detail_search.js"; @@ -20,8 +21,9 @@ import protectedSessionService from "./protected_session.js"; const $tabContentsContainer = $("#note-tab-container"); const componentClasses = { - 'code': noteDetailCode, + 'empty': noteDetailEmpty, 'text': noteDetailText, + 'code': noteDetailCode, 'file': noteDetailFile, 'image': noteDetailImage, 'search': noteDetailSearch, @@ -30,19 +32,14 @@ const componentClasses = { 'protected-session': noteDetailProtectedSession }; -let tabIdCounter = 1; - class TabContext { /** * @param {TabRow} tabRow */ constructor(tabRow) { - this.tabId = tabIdCounter++; this.tabRow = tabRow; - this.tab = this.tabRow.addTab({ - title: '', // will be set later - id: this.tabId - }); + this.tab = this.tabRow.addTab(); + this.tabId = this.tab.getAttribute('data-tab-id'); this.$tabContent = $(".note-tab-content-template").clone(); this.$tabContent.removeClass('note-tab-content-template'); @@ -51,6 +48,7 @@ class TabContext { $tabContentsContainer.append(this.$tabContent); this.$noteTitle = this.$tabContent.find(".note-title"); + this.$noteTitleRow = this.$tabContent.find(".note-title-row"); this.$noteDetailComponents = this.$tabContent.find(".note-detail-component"); this.$childrenOverview = this.$tabContent.find(".children-overview"); this.$scriptArea = this.$tabContent.find(".note-detail-script-area"); @@ -82,9 +80,6 @@ class TabContext { this.noteId = note.noteId; this.notePath = notePath; 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.attributes.invalidateAttributes(); @@ -108,19 +103,25 @@ class TabContext { } getComponent() { - let type = this.note.type; + let type; - if (this.note.isProtected) { - if (protectedSessionHolder.isProtectedSessionAvailable()) { - protectedSessionHolder.touchProtectedSession(); - } - else { - type = 'protected-session'; + if (this.note) { + type = this.note.type; - // user shouldn't be able to edit note title - this.$noteTitle.prop("readonly", true); + if (this.note.isProtected) { + 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)) { this.components[type] = new componentClasses[type](this); diff --git a/src/public/javascripts/services/tab_row.js b/src/public/javascripts/services/tab_row.js index 3bd6426d8..258f2df68 100644 --- a/src/public/javascripts/services/tab_row.js +++ b/src/public/javascripts/services/tab_row.js @@ -38,6 +38,7 @@ class TabRow { constructor() { this.draggabillies = []; this.eventListeners = {}; + this.tabIdCounter = 1; } init(el) { @@ -164,12 +165,13 @@ class TabRow { const div = document.createElement('div'); div.innerHTML = tabTemplate; const tabEl = div.firstElementChild; + tabEl.setAttribute('data-tab-id', "t" + this.tabIdCounter++); tabEl.classList.add('note-tab-was-just-added'); setTimeout(() => tabEl.classList.remove('note-tab-was-just-added'), 500); tabProperties = Object.assign({}, defaultTapProperties, tabProperties); - this.tabContentEl.appendChild(tabEl); + this.newTabEl.before(tabEl); this.setVisibility(); this.setTabCloseEventListener(tabEl); this.updateTab(tabEl, tabProperties); @@ -266,10 +268,6 @@ class TabRow { updateTab(tabEl, tabProperties) { tabEl.querySelector('.note-tab-title').textContent = tabProperties.title; - - if (tabProperties.id) { - tabEl.setAttribute('data-tab-id', tabProperties.id); - } } cleanUpPreviouslyDraggedTabs() { @@ -374,6 +372,8 @@ class TabRow { this.tabContentEl.appendChild(this.newTabEl); this.layoutTabs(); + + this.newTabEl.addEventListener('click', _ => this.emit('newTab')); } closest(value, array) { diff --git a/src/public/javascripts/services/tree_context_menu.js b/src/public/javascripts/services/tree_context_menu.js index 608aa2657..a12893511 100644 --- a/src/public/javascripts/services/tree_context_menu.js +++ b/src/public/javascripts/services/tree_context_menu.js @@ -78,7 +78,7 @@ class TreeContextMenu { async selectContextMenuItem(event, cmd) { if (cmd === 'openInTab') { - const notePath = treeUtils.getNotePath(this.node); + const notePath = await treeUtils.getNotePath(this.node); noteDetailService.openInTab(notePath); } diff --git a/src/views/tabs.ejs b/src/views/tabs.ejs index e2c19699a..f12a1ddcb 100644 --- a/src/views/tabs.ejs +++ b/src/views/tabs.ejs @@ -15,6 +15,8 @@
+ <% include details/empty.ejs %> + <% include details/search.ejs %> <% include details/render.ejs %>