mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
WIP per-tab hoisting
This commit is contained in:
parent
167b6974fe
commit
52b8162d01
15
db/migrations/0171__cleanup_options.sql
Normal file
15
db/migrations/0171__cleanup_options.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
DELETE FROM options WHERE name IN (
|
||||||
|
'noteInfoWidget',
|
||||||
|
'attributesWidget',
|
||||||
|
'linkMapWidget',
|
||||||
|
'noteRevisionsWidget',
|
||||||
|
'whatLinksHereWidget',
|
||||||
|
'codeNotesMimeTypes',
|
||||||
|
'similarNotesWidget',
|
||||||
|
'editedNotesWidget',
|
||||||
|
'calendarWidget',
|
||||||
|
'sidebarMinWidth',
|
||||||
|
'sidebarWidthPercent',
|
||||||
|
'showSidebarInNewTab',
|
||||||
|
'hoistedNoteId'
|
||||||
|
);
|
@ -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();
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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 = {};
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
let hoistedNoteId = 'root';
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getHoistedNoteId: () => hoistedNoteId,
|
|
||||||
setHoistedNoteId(noteId) { hoistedNoteId = noteId; }
|
|
||||||
};
|
|
@ -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'));
|
|
||||||
});
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user