WIP per-tab hoisting

This commit is contained in:
zadam 2020-11-23 22:52:48 +01:00
parent 167b6974fe
commit 52b8162d01
16 changed files with 64 additions and 74 deletions

View File

@ -0,0 +1,15 @@
DELETE FROM options WHERE name IN (
'noteInfoWidget',
'attributesWidget',
'linkMapWidget',
'noteRevisionsWidget',
'whatLinksHereWidget',
'codeNotesMimeTypes',
'similarNotesWidget',
'editedNotesWidget',
'calendarWidget',
'sidebarMinWidth',
'sidebarWidthPercent',
'showSidebarInNewTab',
'hoistedNoteId'
);

View File

@ -10,7 +10,6 @@ const FileStore = require('session-file-store')(session);
const sessionSecret = require('./services/session_secret'); const sessionSecret = require('./services/session_secret');
const dataDir = require('./services/data_dir'); const dataDir = require('./services/data_dir');
require('./services/handlers'); require('./services/handlers');
require('./services/hoisted_note_loader');
require('./services/note_cache/note_cache_loader'); require('./services/note_cache/note_cache_loader');
const app = express(); const app = express();

View File

@ -91,7 +91,11 @@ export default class Entrypoints extends Component {
} }
async unhoistCommand() { async unhoistCommand() {
hoistedNoteService.unhoist(); const activeTabContext = appContext.tabManager.getActiveTabContext();
if (activeTabContext) {
activeTabContext.unhoist();
}
} }
copyWithoutFormattingCommand() { copyWithoutFormattingCommand() {

View File

@ -379,13 +379,19 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.getYearNote = dateNotesService.getYearNote; this.getYearNote = dateNotesService.getYearNote;
/** /**
* Hoist note. See https://github.com/zadam/trilium/wiki/Note-hoisting * Hoist note in the current tab. See https://github.com/zadam/trilium/wiki/Note-hoisting
* *
* @method * @method
* @param {string} noteId - set hoisted note. 'root' will effectively unhoist * @param {string} noteId - set hoisted note. 'root' will effectively unhoist
* @return {Promise} * @return {Promise}
*/ */
this.setHoistedNoteId = hoistedNoteService.setHoistedNoteId; this.setHoistedNoteId = (noteId) => {
const activeTabContext = appContext.tabManager.getActiveTabContext();
if (activeTabContext) {
activeTabContext.setHoistedNoteId(noteId);
}
};
/** /**
* @method * @method

View File

@ -1,23 +1,18 @@
import options from './options.js';
import appContext from "./app_context.js"; import appContext from "./app_context.js";
import treeService from "./tree.js"; import treeService from "./tree.js";
function getHoistedNoteId() { function getHoistedNoteId() {
return options.get('hoistedNoteId'); const activeTabContext = appContext.tabManager.getActiveTabContext();
}
async function setHoistedNoteId(noteId) { return activeTabContext ? activeTabContext.hoistedNoteId : 'root';
if (getHoistedNoteId() === noteId) {
return;
}
await options.save('hoistedNoteId', noteId);
appContext.triggerEvent('hoistedNoteChanged', {noteId});
} }
async function unhoist() { async function unhoist() {
await setHoistedNoteId('root'); const activeTabContext = appContext.tabManager.getActiveTabContext();
if (activeTabContext) {
await activeTabContext.unhoist();
}
} }
function isTopLevelNode(node) { function isTopLevelNode(node) {
@ -58,7 +53,6 @@ async function checkNoteAccess(notePath) {
export default { export default {
getHoistedNoteId, getHoistedNoteId,
setHoistedNoteId,
unhoist, unhoist,
isTopLevelNode, isTopLevelNode,
isRootNode, isRootNode,

View File

@ -2,13 +2,16 @@ import utils from './utils.js';
const REQUEST_LOGGING_ENABLED = false; const REQUEST_LOGGING_ENABLED = false;
function getHeaders(headers) { async function getHeaders(headers) {
const appContext = (await import('./app_context.js')).default;
const activeTabContext = appContext.tabManager ? appContext.tabManager.getActiveTabContext() : null;
// headers need to be lowercase because node.js automatically converts them to lower case // headers need to be lowercase because node.js automatically converts them to lower case
// so hypothetical protectedSessionId becomes protectedsessionid on the backend
// also avoiding using underscores instead of dashes since nginx filters them out by default // also avoiding using underscores instead of dashes since nginx filters them out by default
const allHeaders = { const allHeaders = {
'trilium-source-id': glob.sourceId, 'trilium-source-id': glob.sourceId,
'trilium-local-now-datetime': utils.localNowDateTime(), 'trilium-local-now-datetime': utils.localNowDateTime(),
'trilium-hoisted-note-id': activeTabContext ? activeTabContext.hoistedNoteId : null,
'x-csrf-token': glob.csrfToken 'x-csrf-token': glob.csrfToken
}; };
@ -52,6 +55,8 @@ async function call(method, url, data, headers = {}) {
const start = Date.now(); const start = Date.now();
headers = await getHeaders(headers);
if (utils.isElectron()) { if (utils.isElectron()) {
const ipc = utils.dynamicRequire('electron').ipcRenderer; const ipc = utils.dynamicRequire('electron').ipcRenderer;
const requestId = i++; const requestId = i++;
@ -65,7 +70,7 @@ async function call(method, url, data, headers = {}) {
ipc.send('server-request', { ipc.send('server-request', {
requestId: requestId, requestId: requestId,
headers: getHeaders(headers), headers: headers,
method: method, method: method,
url: "/" + baseApiUrl + url, url: "/" + baseApiUrl + url,
data: data data: data
@ -96,7 +101,7 @@ function ajax(url, method, data, headers) {
const options = { const options = {
url: baseApiUrl + url, url: baseApiUrl + url,
type: method, type: method,
headers: getHeaders(headers), headers: headers,
timeout: 60000, timeout: 60000,
success: (body, textStatus, jqXhr) => { success: (body, textStatus, jqXhr) => {
const respHeaders = {}; const respHeaders = {};

View File

@ -193,8 +193,8 @@ export default class TabManager extends Component {
return tabContext; return tabContext;
} }
async openTabWithNote(notePath, activate, tabId = null, hoistedNoteId = 'root') { async openTabWithNote(notePath, activate, tabId, hoistedNoteId) {
const tabContext = await this.openEmptyTab(tabId); const tabContext = await this.openEmptyTab(tabId, hoistedNoteId);
if (notePath) { if (notePath) {
await tabContext.setNote(notePath, !activate); // if activate is false then send normal noteSwitched event await tabContext.setNote(notePath, !activate); // if activate is false then send normal noteSwitched event

View File

@ -500,8 +500,6 @@ export default class NoteTreeWidget extends TabAwareWidget {
} }
prepareRootNode() { prepareRootNode() {
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
return this.prepareNode(treeCache.getBranch('root')); return this.prepareNode(treeCache.getBranch('root'));
} }
@ -532,16 +530,6 @@ export default class NoteTreeWidget extends TabAwareWidget {
return noteList; return noteList;
} }
getIcon(note, isFolder) {
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
if (note.noteId !== 'root' && note.noteId === hoistedNoteId) {
return "bx bxs-arrow-from-bottom";
}
return note.getIcon(isFolder);
}
updateNode(node) { updateNode(node) {
const note = treeCache.getNoteFromCache(node.data.noteId); const note = treeCache.getNoteFromCache(node.data.noteId);
const branch = treeCache.getBranch(node.data.branchId); const branch = treeCache.getBranch(node.data.branchId);
@ -552,7 +540,7 @@ export default class NoteTreeWidget extends TabAwareWidget {
node.data.isProtected = note.isProtected; node.data.isProtected = note.isProtected;
node.data.noteType = note.type; node.data.noteType = note.type;
node.folder = isFolder; node.folder = isFolder;
node.icon = this.getIcon(note, isFolder); node.icon = note.getIcon(isFolder);
node.extraClasses = this.getExtraClasses(note); node.extraClasses = this.getExtraClasses(note);
node.title = utils.escapeHtml(title); node.title = utils.escapeHtml(title);
@ -574,7 +562,6 @@ export default class NoteTreeWidget extends TabAwareWidget {
} }
const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title; const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
const isFolder = this.isFolder(note); const isFolder = this.isFolder(note);
@ -586,11 +573,11 @@ export default class NoteTreeWidget extends TabAwareWidget {
noteType: note.type, noteType: note.type,
title: utils.escapeHtml(title), title: utils.escapeHtml(title),
extraClasses: this.getExtraClasses(note), extraClasses: this.getExtraClasses(note),
icon: this.getIcon(note, isFolder), icon: note.getIcon(isFolder),
refKey: note.noteId, refKey: note.noteId,
lazy: true, lazy: true,
folder: isFolder, folder: isFolder,
expanded: (branch.isExpanded || hoistedNoteId === note.noteId) && note.type !== 'search', expanded: branch.isExpanded && note.type !== 'search',
key: utils.randomString(12) // this should prevent some "duplicate key" errors key: utils.randomString(12) // this should prevent some "duplicate key" errors
}; };

View File

@ -88,7 +88,8 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
const result = cls.init(() => { const result = cls.init(() => {
cls.set('sourceId', req.headers['trilium-source-id']); cls.set('sourceId', req.headers['trilium-source-id']);
cls.set('localNowDateTime', req.headers['`trilium-local-now-datetime`']); cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']);
cls.set('hoistedNoteId', req.headers['trilium-hoisted-note-id'] || 'root');
protectedSessionService.setProtectedSessionId(req); protectedSessionService.setProtectedSessionId(req);
const cb = () => routeHandler(req, res, next); const cb = () => routeHandler(req, res, next);

View File

@ -4,7 +4,7 @@ const build = require('./build');
const packageJson = require('../../package'); const packageJson = require('../../package');
const {TRILIUM_DATA_DIR} = require('./data_dir'); const {TRILIUM_DATA_DIR} = require('./data_dir');
const APP_DB_VERSION = 170; const APP_DB_VERSION = 171;
const SYNC_VERSION = 16; const SYNC_VERSION = 16;
const CLIPPER_PROTOCOL_VERSION = "1.0"; const CLIPPER_PROTOCOL_VERSION = "1.0";

View File

@ -24,6 +24,10 @@ function set(key, value) {
namespace.set(key, value); namespace.set(key, value);
} }
function getHoistedNoteId() {
return namespace.get('hoistedNoteId');
}
function getSourceId() { function getSourceId() {
return namespace.get('sourceId'); return namespace.get('sourceId');
} }
@ -74,6 +78,7 @@ module.exports = {
get, get,
set, set,
namespace, namespace,
getHoistedNoteId,
getSourceId, getSourceId,
getLocalNowDateTime, getLocalNowDateTime,
disableEntityEvents, disableEntityEvents,

View File

@ -1,6 +0,0 @@
let hoistedNoteId = 'root';
module.exports = {
getHoistedNoteId: () => hoistedNoteId,
setHoistedNoteId(noteId) { hoistedNoteId = noteId; }
};

View File

@ -1,14 +0,0 @@
const optionService = require('./options');
const sqlInit = require('./sql_init');
const eventService = require('./events');
const hoistedNote = require('./hoisted_note');
eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
if (entityName === 'options' && entity.name === 'hoistedNoteId') {
hoistedNote.setHoistedNoteId(entity.value);
}
});
sqlInit.dbReady.then(() => {
hoistedNote.setHoistedNoteId(optionService.getOption('hoistedNoteId'));
});

View File

@ -1,7 +1,7 @@
"use strict"; "use strict";
const noteCache = require('./note_cache'); const noteCache = require('./note_cache');
const hoistedNoteService = require('../hoisted_note'); const cls = require('../cls');
const protectedSessionService = require('../protected_session'); const protectedSessionService = require('../protected_session');
const log = require('../log'); const log = require('../log');
@ -88,10 +88,6 @@ function getNoteTitle(childNoteId, parentNoteId) {
function getNoteTitleArrayForPath(notePathArray) { function getNoteTitleArrayForPath(notePathArray) {
const titles = []; const titles = [];
if (notePathArray[0] === hoistedNoteService.getHoistedNoteId() && notePathArray.length === 1) {
return [ getNoteTitle(hoistedNoteService.getHoistedNoteId()) ];
}
let parentNoteId = 'root'; let parentNoteId = 'root';
let hoistedNotePassed = false; let hoistedNotePassed = false;
@ -103,7 +99,7 @@ function getNoteTitleArrayForPath(notePathArray) {
titles.push(title); titles.push(title);
} }
if (noteId === hoistedNoteService.getHoistedNoteId()) { if (noteId === cls.getHoistedNoteId()) {
hoistedNotePassed = true; hoistedNotePassed = true;
} }
@ -130,7 +126,7 @@ function getSomePath(note, path = []) {
path.push(note.noteId); path.push(note.noteId);
path.reverse(); path.reverse();
if (!path.includes(hoistedNoteService.getHoistedNoteId())) { if (!path.includes(cls.getHoistedNoteId())) {
return false; return false;
} }

View File

@ -7,10 +7,8 @@ const eventService = require('./events');
const repository = require('./repository'); const repository = require('./repository');
const cls = require('../services/cls'); const cls = require('../services/cls');
const Note = require('../entities/note'); const Note = require('../entities/note');
const NoteRevision = require('../entities/note_revision');
const Branch = require('../entities/branch'); const Branch = require('../entities/branch');
const Attribute = require('../entities/attribute'); const Attribute = require('../entities/attribute');
const hoistedNoteService = require('../services/hoisted_note');
const protectedSessionService = require('../services/protected_session'); const protectedSessionService = require('../services/protected_session');
const log = require('../services/log'); const log = require('../services/log');
const utils = require('../services/utils'); const utils = require('../services/utils');
@ -524,7 +522,7 @@ function deleteBranch(branch, deleteId, taskContext) {
if (branch.branchId === 'root' if (branch.branchId === 'root'
|| branch.noteId === 'root' || branch.noteId === 'root'
|| branch.noteId === hoistedNoteService.getHoistedNoteId()) { || branch.noteId === cls.getHoistedNoteId()) {
throw new Error("Can't delete root branch/note"); throw new Error("Can't delete root branch/note");
} }

View File

@ -8,15 +8,15 @@ const SearchResult = require("../search_result.js");
const SearchContext = require("../search_context.js"); const SearchContext = require("../search_context.js");
const noteCache = require('../../note_cache/note_cache.js'); const noteCache = require('../../note_cache/note_cache.js');
const noteCacheService = require('../../note_cache/note_cache_service.js'); const noteCacheService = require('../../note_cache/note_cache_service.js');
const hoistedNoteService = require('../../hoisted_note.js');
const utils = require('../../utils.js'); const utils = require('../../utils.js');
const cls = require('../../cls.js');
/** /**
* @param {Expression} expression * @param {Expression} expression
* @return {SearchResult[]} * @return {SearchResult[]}
*/ */
function findNotesWithExpression(expression) { function findNotesWithExpression(expression) {
const hoistedNote = noteCache.notes[hoistedNoteService.getHoistedNoteId()]; const hoistedNote = noteCache.notes[cls.getHoistedNoteId()];
let allNotes = (hoistedNote && hoistedNote.noteId !== 'root') let allNotes = (hoistedNote && hoistedNote.noteId !== 'root')
? hoistedNote.subtreeNotes ? hoistedNote.subtreeNotes
: Object.values(noteCache.notes); : Object.values(noteCache.notes);
@ -35,7 +35,7 @@ function findNotesWithExpression(expression) {
const searchResults = noteSet.notes const searchResults = noteSet.notes
.map(note => searchContext.noteIdToNotePath[note.noteId] || noteCacheService.getSomePath(note)) .map(note => searchContext.noteIdToNotePath[note.noteId] || noteCacheService.getSomePath(note))
.filter(notePathArray => notePathArray.includes(hoistedNoteService.getHoistedNoteId())) .filter(notePathArray => notePathArray.includes(cls.getHoistedNoteId()))
.map(notePathArray => new SearchResult(notePathArray)); .map(notePathArray => new SearchResult(notePathArray));
if (!noteSet.sorted) { if (!noteSet.sorted) {