mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
new tab infrastructure
This commit is contained in:
parent
637547a3fa
commit
14c420b782
@ -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,6 +116,8 @@ async function showTab(tabId) {
|
|||||||
treeService.clearSelectedNodes();
|
treeService.clearSelectedNodes();
|
||||||
|
|
||||||
const newActiveTabContext = getActiveTabContext();
|
const newActiveTabContext = getActiveTabContext();
|
||||||
|
|
||||||
|
if (newActiveTabContext && newActiveTabContext.notePath) {
|
||||||
const newActiveNode = await treeService.getNodeFromPath(newActiveTabContext.notePath);
|
const newActiveNode = await treeService.getNodeFromPath(newActiveTabContext.notePath);
|
||||||
|
|
||||||
if (newActiveNode && newActiveNode.isVisible()) {
|
if (newActiveNode && newActiveNode.isVisible()) {
|
||||||
@ -121,6 +125,22 @@ async function showTab(tabId) {
|
|||||||
newActiveNode.setSelected(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
|
||||||
@ -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) {
|
||||||
|
type = this.note.type;
|
||||||
|
|
||||||
if (this.note.isProtected) {
|
if (this.note.isProtected) {
|
||||||
if (protectedSessionHolder.isProtectedSessionAvailable()) {
|
if (protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||||
protectedSessionHolder.touchProtectedSession();
|
protectedSessionHolder.touchProtectedSession();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
type = 'protected-session';
|
type = 'protected-session';
|
||||||
|
|
||||||
// user shouldn't be able to edit note title
|
// user shouldn't be able to edit note title
|
||||||
this.$noteTitle.prop("readonly", true);
|
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 %>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user