mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	new tab infrastructure
This commit is contained in:
		
							parent
							
								
									637547a3fa
								
							
						
					
					
						commit
						14c420b782
					
				@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ class NoteDetailCode {
 | 
			
		||||
        this.$executeScriptButton.click(() => this.executeCurrentNote());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async show() {
 | 
			
		||||
    async render() {
 | 
			
		||||
        await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
 | 
			
		||||
 | 
			
		||||
        if (!this.codeEditor) {
 | 
			
		||||
 | 
			
		||||
@ -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]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ class NoteDetailProtectedSession {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show() {
 | 
			
		||||
    render() {
 | 
			
		||||
        this.$component.show();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -207,7 +207,7 @@ class NoteDetailRelationMap {
 | 
			
		||||
        return id.substr(13);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async show() {
 | 
			
		||||
    async render() {
 | 
			
		||||
        this.$component.show();
 | 
			
		||||
 | 
			
		||||
        await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP);
 | 
			
		||||
 | 
			
		||||
@ -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'
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ class NoteDetailSearch {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show() {
 | 
			
		||||
    render() {
 | 
			
		||||
        this.$help.html(searchNotesService.getHelpText());
 | 
			
		||||
 | 
			
		||||
        this.$component.show();
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ class NoteDetailText {
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async show() {
 | 
			
		||||
    async render() {
 | 
			
		||||
        if (!this.textEditor) {
 | 
			
		||||
            await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,8 @@
 | 
			
		||||
 | 
			
		||||
            <div class="note-detail-code note-detail-component"></div>
 | 
			
		||||
 | 
			
		||||
            <% include details/empty.ejs %>
 | 
			
		||||
 | 
			
		||||
            <% include details/search.ejs %>
 | 
			
		||||
 | 
			
		||||
            <% include details/render.ejs %>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user