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} */
|
||||
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