work on hash & history

This commit is contained in:
zadam 2019-05-14 22:29:47 +02:00
parent 2178f82324
commit dd1fc23fe8
12 changed files with 123 additions and 78 deletions

View File

@ -1,4 +1,4 @@
INSERT INTO options (name, value, utcDateCreated, utcDateModified, isSynced) INSERT INTO options (name, value, utcDateCreated, utcDateModified, isSynced)
SELECT 'openTabs', '[{"notePath":"' || value || '","active": true}]', '2019-05-01T18:31:00.874Z', '2019-05-01T18:31:00.874Z', 0 FROM options WHERE name = 'startNotePath'; SELECT 'openTabs', '[{"notePath":"' || value || '","active": true,"tabId":"1111"}]', '2019-05-01T18:31:00.874Z', '2019-05-01T18:31:00.874Z', 0 FROM options WHERE name = 'startNotePath';
DELETE FROM options WHERE name = 'startNotePath'; DELETE FROM options WHERE name = 'startNotePath';

View File

@ -47,8 +47,6 @@ async function showTree() {
treeService.clearSelectedNodes(); treeService.clearSelectedNodes();
treeService.setCurrentNotePathToHash(node);
showDetailPane(); showDetailPane();
const notePath = await treeUtils.getNotePath(node); const notePath = await treeUtils.getNotePath(node);

View File

@ -103,8 +103,6 @@ async function deleteNodes(nodes) {
if (next) { if (next) {
// activate next element after this one is deleted so we don't lose focus // activate next element after this one is deleted so we don't lose focus
next.setActive(); next.setActive();
treeService.setCurrentNotePathToHash(next);
} }
await treeService.loadTreeCache(); await treeService.loadTreeCache();
@ -163,8 +161,6 @@ async function changeNode(func, node, beforeNoteId = null, afterNoteId = null) {
await treeCache.moveNote(childNoteId, thisOldParentNode.data.noteId, thisNewParentNode.data.noteId, beforeNoteId, afterNoteId); await treeCache.moveNote(childNoteId, thisOldParentNode.data.noteId, thisNewParentNode.data.noteId, beforeNoteId, afterNoteId);
treeService.setCurrentNotePathToHash(node);
await treeService.checkFolderStatus(thisOldParentNode); await treeService.checkFolderStatus(thisOldParentNode);
await treeService.checkFolderStatus(thisNewParentNode); await treeService.checkFolderStatus(thisNewParentNode);

View File

@ -268,9 +268,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) {
/** /**
* @method * @method
* @returns {Promise<string>} returns note path of active note * @returns {Promise<string|null>} returns note path of active note or null if there isn't active note
*/ */
this.getActiveNotePath = treeService.getActiveNotePath; this.getActiveNotePath = () => {
const activeTabContext = noteDetailService.getActiveTabContext();
return activeTabContext ? activeTabContext.notePath : null;
};
/** /**
* This method checks whether user navigated away from the note from which the scripts has been started. * This method checks whether user navigated away from the note from which the scripts has been started.

View File

@ -102,9 +102,27 @@ function getActiveTabContext() {
return tabContexts.find(tc => tc.tabId === tabId); return tabContexts.find(tc => tc.tabId === tabId);
} }
function isActive(tabContext) {
return tabContext.$tab[0] === tabRow.activateTab;
}
async function activateTabContext(tabContext) {
await tabRow.activateTab(tabContext.$tab[0]);
}
/** @returns {TabContext} */
function getTabContext(tabId) {
return tabContexts.find(tc => tc.tabId === tabId);
}
async function showTab(tabId) { async function showTab(tabId) {
for (const ctx of tabContexts) { for (const ctx of tabContexts) {
ctx.$tabContent.toggle(ctx.tabId === tabId); if (ctx.tabId === tabId) {
ctx.show();
}
else {
ctx.hide();
}
} }
const oldActiveNode = treeService.getActiveNode(); const oldActiveNode = treeService.getActiveNode();
@ -207,9 +225,9 @@ async function loadNoteDetail(origNotePath, options = {}) {
const loadedNote = await loadNote(noteId); const loadedNote = await loadNote(noteId);
let ctx; let ctx;
if (tabContexts.length === 0 || newTab) { if (!getActiveTabContext() || newTab) {
// if it's a new tab explicitly by user then it's in background // if it's a new tab explicitly by user then it's in background
ctx = new TabContext(tabRow); ctx = new TabContext(tabRow, options.tabId);
tabContexts.push(ctx); tabContexts.push(ctx);
} }
else { else {
@ -229,7 +247,7 @@ async function loadNoteDetail(origNotePath, options = {}) {
if (activate) { if (activate) {
// will also trigger showTab via event // will also trigger showTab via event
tabRow.setCurrentTab(ctx.$tab[0]); await tabRow.activateTab(ctx.$tab[0]);
} }
} }
@ -316,7 +334,7 @@ async function openEmptyTab() {
await renderComponent(ctx); await renderComponent(ctx);
await tabRow.setCurrentTab(ctx.tab); await tabRow.activateTab(ctx.$tab[0]);
} }
tabRow.addListener('newTab', openEmptyTab); tabRow.addListener('newTab', openEmptyTab);
@ -380,7 +398,7 @@ if (utils.isElectron()) {
const nextTab = tabRow.nextTabEl; const nextTab = tabRow.nextTabEl;
if (nextTab) { if (nextTab) {
tabRow.setCurrentTab(nextTab); tabRow.activateTab(nextTab);
} }
}); });
@ -388,7 +406,7 @@ if (utils.isElectron()) {
const prevTab = tabRow.previousTabEl; const prevTab = tabRow.previousTabEl;
if (prevTab) { if (prevTab) {
tabRow.setCurrentTab(prevTab); tabRow.activateTab(prevTab);
} }
}); });
} }
@ -424,6 +442,7 @@ async function saveOpenTabs() {
if (tabContext && tabContext.notePath) { if (tabContext && tabContext.notePath) {
openTabs.push({ openTabs.push({
tabId: tabContext.tabId,
notePath: tabContext.notePath, notePath: tabContext.notePath,
active: activeTabEl === tabEl active: activeTabEl === tabEl
}); });
@ -457,8 +476,11 @@ export default {
saveNotesIfChanged, saveNotesIfChanged,
onNoteChange, onNoteChange,
addDetailLoadedListener, addDetailLoadedListener,
getTabContext,
getTabContexts, getTabContexts,
getActiveTabContext, getActiveTabContext,
isActive,
activateTabContext,
getActiveComponent, getActiveComponent,
clearOpenTabsTask, clearOpenTabsTask,
filterTabs filterTabs

View File

@ -141,7 +141,7 @@ async function refreshSearch() {
} }
function init() { function init() {
const hashValue = treeService.getHashValueFromAddress(); const hashValue = document.location.hash ? document.location.hash.substr(1) : ""; // strip initial #
if (hashValue.startsWith("search=")) { if (hashValue.startsWith("search=")) {
showSearch(); showSearch();

View File

@ -36,10 +36,10 @@ class TabContext {
/** /**
* @param {TabRow} tabRow * @param {TabRow} tabRow
*/ */
constructor(tabRow) { constructor(tabRow, tabId = null) {
this.tabRow = tabRow; this.tabRow = tabRow;
this.$tab = $(this.tabRow.addTab()); this.tabId = tabId || utils.randomString(4);
this.tabId = this.$tab.attr('data-tab-id'); this.$tab = $(this.tabRow.addTab(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');
@ -96,9 +96,40 @@ class TabContext {
this.setupClasses(); this.setupClasses();
this.setCurrentNotePathToHash();
setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds
if (notePath && notePath === await this.notePath) {
await server.post('recent-notes', { notePath });
}
}, 5000);
console.log(`Switched tab ${this.tabId} to ${this.noteId}`); console.log(`Switched tab ${this.tabId} to ${this.noteId}`);
} }
show() {
this.$tabContent.show();
this.setCurrentNotePathToHash();
document.title = "Trilium Notes";
if (this.note) {
// it helps navigating in history if note title is included in the title
document.title += " - " + this.note.title;
}
}
hide() {
this.$tabContent.hide();
}
setCurrentNotePathToHash() {
if (this.$tab[0] === this.tabRow.activeTabEl) {
document.location.hash = (this.notePath || "") + "-" + this.tabId;
}
}
setupClasses() { setupClasses() {
for (const clazz of Array.from(this.$tab[0].classList)) { // create copy to safely iterate over while removing classes for (const clazz of Array.from(this.$tab[0].classList)) { // create copy to safely iterate over while removing classes
if (clazz !== 'note-tab') { if (clazz !== 'note-tab') {
@ -205,13 +236,11 @@ class TabContext {
this.$childrenOverview.empty(); this.$childrenOverview.empty();
const notePath = await treeService.getActiveNotePath();
for (const childBranch of await this.note.getChildBranches()) { for (const childBranch of await this.note.getChildBranches()) {
const link = $('<a>', { const link = $('<a>', {
href: 'javascript:', href: 'javascript:',
text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId) text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId)
}).attr('data-action', 'note').attr('data-note-path', notePath + '/' + childBranch.noteId); }).attr('data-action', 'note').attr('data-note-path', this.notePath + '/' + childBranch.noteId);
const childEl = $('<div class="child-overview-item">').html(link); const childEl = $('<div class="child-overview-item">').html(link);
this.$childrenOverview.append(childEl); this.$childrenOverview.append(childEl);

File diff suppressed because one or more lines are too long

View File

@ -38,10 +38,6 @@ function getActiveNode() {
return $tree.fancytree("getActiveNode"); return $tree.fancytree("getActiveNode");
} }
async function getActiveNotePath() {
return getHashValueFromAddress();
}
async function getNodesByBranchId(branchId) { async function getNodesByBranchId(branchId) {
utils.assertArguments(branchId); utils.assertArguments(branchId);
@ -135,6 +131,12 @@ async function activateNote(notePath, noteLoadedListener) {
// notePath argument can contain only noteId which is not good when hoisted since // notePath argument can contain only noteId which is not good when hoisted since
// then we need to check the whole note path // then we need to check the whole note path
const runNotePath = await getRunPath(notePath); const runNotePath = await getRunPath(notePath);
if (!runNotePath) {
console.log("Cannot activate " + notePath);
return;
}
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId(); const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
if (hoistedNoteId !== 'root' && !runNotePath.includes(hoistedNoteId)) { if (hoistedNoteId !== 'root' && !runNotePath.includes(hoistedNoteId)) {
@ -172,7 +174,7 @@ async function activateNote(notePath, noteLoadedListener) {
async function resolveNotePath(notePath) { async function resolveNotePath(notePath) {
const runPath = await getRunPath(notePath); const runPath = await getRunPath(notePath);
return runPath.join("/"); return runPath ? runPath.join("/") : null;
} }
/** /**
@ -205,7 +207,8 @@ async function getRunPath(notePath) {
const child = await treeCache.getNote(childNoteId); const child = await treeCache.getNote(childNoteId);
if (!child) { if (!child) {
console.log("Can't find " + childNoteId); console.log("Can't find note " + childNoteId);
return;
} }
const parents = await child.getParentNotes(); const parents = await child.getParentNotes();
@ -331,26 +334,6 @@ async function setExpandedToServer(branchId, isExpanded) {
await server.put('branches/' + branchId + '/expanded/' + expandedNum); await server.put('branches/' + branchId + '/expanded/' + expandedNum);
} }
function addRecentNote(branchId, notePath) {
setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds
if (notePath && notePath === await getActiveNotePath()) {
await server.post('recent-notes', { branchId, notePath });
}
}, 1500);
}
async function setCurrentNotePathToHash(node) {
utils.assertArguments(node);
const activeNotePath = await treeUtils.getNotePath(node);
const currentBranchId = node.data.branchId;
document.location.hash = activeNotePath;
addRecentNote(currentBranchId, activeNotePath);
}
function getSelectedNodes(stopOnParents = false) { function getSelectedNodes(stopOnParents = false) {
return getTree().getSelectedNodes(stopOnParents); return getTree().getSelectedNodes(stopOnParents);
} }
@ -402,8 +385,13 @@ async function treeInitialized() {
}); });
} }
if (!filteredTabs.find(tab => tab.active)) {
filteredTabs[0].active = true;
}
for (const tab of filteredTabs) { for (const tab of filteredTabs) {
await noteDetailService.loadNoteDetail(tab.notePath, { await noteDetailService.loadNoteDetail(tab.notePath, {
tabId: tab.tabId,
newTab: true, newTab: true,
activate: tab.active activate: tab.active
}); });
@ -465,8 +453,6 @@ function initFancyTree(tree) {
// click event won't propagate so let's close context menu manually // click event won't propagate so let's close context menu manually
contextMenuWidget.hideContextMenu(); contextMenuWidget.hideContextMenu();
await setCurrentNotePathToHash(node);
const notePath = await treeUtils.getNotePath(node); const notePath = await treeUtils.getNotePath(node);
noteDetailService.switchToNote(notePath); noteDetailService.switchToNote(notePath);
@ -554,11 +540,17 @@ async function reload() {
} }
function isNotePathInAddress() { function isNotePathInAddress() {
return getHashValueFromAddress().startsWith("root"); const [notePath, tabId] = getHashValueFromAddress();
return notePath.startsWith("root")
// empty string is for empty/uninitialized tab
|| (notePath === '' && !!tabId);
} }
function getHashValueFromAddress() { function getHashValueFromAddress() {
return document.location.hash ? document.location.hash.substr(1) : ""; // strip initial # const str = document.location.hash ? document.location.hash.substr(1) : ""; // strip initial #
return str.split("-");
} }
async function loadTreeCache() { async function loadTreeCache() {
@ -835,13 +827,27 @@ utils.bindShortcut('ctrl+.', scrollToActiveNote);
$(window).bind('hashchange', async function() { $(window).bind('hashchange', async function() {
if (isNotePathInAddress()) { if (isNotePathInAddress()) {
const notePath = getHashValueFromAddress(); const [notePath, tabId] = getHashValueFromAddress();
const noteId = notePath.split("/").pop();
if (noteId !== '-' && noteId !== getActiveNode().data.noteId) { console.debug(`Switching to ${notePath} on tab ${tabId} because of hash change`);
console.debug("Switching to " + notePath + " because of hash change");
activateNote(notePath); let tabContext = noteDetailService.getTabContext(tabId);
if (!tabContext) {
noteDetailService.loadNoteDetail(notePath, {
newTab: true,
tabId: tabId,
activate: true
});
}
else {
if (!noteDetailService.isActive(tabContext)) {
noteDetailService.activateTabContext(tabContext);
}
if (notePath && tabContext.notePath !== notePath) {
noteDetailService.loadNoteDetail(notePath);
}
} }
} }
}); });
@ -860,8 +866,6 @@ export default {
activateNote, activateNote,
getFocusedNode, getFocusedNode,
getActiveNode, getActiveNode,
getActiveNotePath,
setCurrentNotePathToHash,
setNoteTitle, setNoteTitle,
setPrefix, setPrefix,
createNote, createNote,

View File

@ -134,11 +134,6 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
border-style: solid; border-style: solid;
} }
.note-tab-content {
font-family: var(--detail-font-family);
font-size: var(--detail-font-size);
}
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 12px; width: 12px;
} }

View File

@ -118,6 +118,8 @@ ul.fancytree-container {
flex-direction: column; flex-direction: column;
min-height: 200px; min-height: 200px;
word-break: break-all; /* otherwise CKEditor fails miserably on super long lines */ word-break: break-all; /* otherwise CKEditor fails miserably on super long lines */
font-family: var(--detail-font-family);
font-size: var(--detail-font-size);
} }
.note-detail-component { .note-detail-component {

View File

@ -1,6 +1,5 @@
"use strict"; "use strict";
const optionService = require('../../services/options');
const RecentNote = require('../../entities/recent_note'); const RecentNote = require('../../entities/recent_note');
async function addRecentNote(req) { async function addRecentNote(req) {