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)
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';

View File

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

View File

@ -103,8 +103,6 @@ async function deleteNodes(nodes) {
if (next) {
// activate next element after this one is deleted so we don't lose focus
next.setActive();
treeService.setCurrentNotePathToHash(next);
}
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);
treeService.setCurrentNotePathToHash(node);
await treeService.checkFolderStatus(thisOldParentNode);
await treeService.checkFolderStatus(thisNewParentNode);

View File

@ -268,9 +268,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) {
/**
* @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.

View File

@ -102,9 +102,27 @@ function getActiveTabContext() {
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) {
for (const ctx of tabContexts) {
ctx.$tabContent.toggle(ctx.tabId === tabId);
if (ctx.tabId === tabId) {
ctx.show();
}
else {
ctx.hide();
}
}
const oldActiveNode = treeService.getActiveNode();
@ -207,9 +225,9 @@ async function loadNoteDetail(origNotePath, options = {}) {
const loadedNote = await loadNote(noteId);
let ctx;
if (tabContexts.length === 0 || newTab) {
if (!getActiveTabContext() || newTab) {
// 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);
}
else {
@ -229,7 +247,7 @@ async function loadNoteDetail(origNotePath, options = {}) {
if (activate) {
// 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 tabRow.setCurrentTab(ctx.tab);
await tabRow.activateTab(ctx.$tab[0]);
}
tabRow.addListener('newTab', openEmptyTab);
@ -380,7 +398,7 @@ if (utils.isElectron()) {
const nextTab = tabRow.nextTabEl;
if (nextTab) {
tabRow.setCurrentTab(nextTab);
tabRow.activateTab(nextTab);
}
});
@ -388,7 +406,7 @@ if (utils.isElectron()) {
const prevTab = tabRow.previousTabEl;
if (prevTab) {
tabRow.setCurrentTab(prevTab);
tabRow.activateTab(prevTab);
}
});
}
@ -424,6 +442,7 @@ async function saveOpenTabs() {
if (tabContext && tabContext.notePath) {
openTabs.push({
tabId: tabContext.tabId,
notePath: tabContext.notePath,
active: activeTabEl === tabEl
});
@ -457,8 +476,11 @@ export default {
saveNotesIfChanged,
onNoteChange,
addDetailLoadedListener,
getTabContext,
getTabContexts,
getActiveTabContext,
isActive,
activateTabContext,
getActiveComponent,
clearOpenTabsTask,
filterTabs

View File

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

View File

@ -36,10 +36,10 @@ class TabContext {
/**
* @param {TabRow} tabRow
*/
constructor(tabRow) {
constructor(tabRow, tabId = null) {
this.tabRow = tabRow;
this.$tab = $(this.tabRow.addTab());
this.tabId = this.$tab.attr('data-tab-id');
this.tabId = tabId || utils.randomString(4);
this.$tab = $(this.tabRow.addTab(this.tabId));
this.$tabContent = $(".note-tab-content-template").clone();
this.$tabContent.removeClass('note-tab-content-template');
@ -96,9 +96,40 @@ class TabContext {
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}`);
}
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() {
for (const clazz of Array.from(this.$tab[0].classList)) { // create copy to safely iterate over while removing classes
if (clazz !== 'note-tab') {
@ -205,13 +236,11 @@ class TabContext {
this.$childrenOverview.empty();
const notePath = await treeService.getActiveNotePath();
for (const childBranch of await this.note.getChildBranches()) {
const link = $('<a>', {
href: 'javascript:',
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);
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");
}
async function getActiveNotePath() {
return getHashValueFromAddress();
}
async function getNodesByBranchId(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
// then we need to check the whole note path
const runNotePath = await getRunPath(notePath);
if (!runNotePath) {
console.log("Cannot activate " + notePath);
return;
}
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
if (hoistedNoteId !== 'root' && !runNotePath.includes(hoistedNoteId)) {
@ -172,7 +174,7 @@ async function activateNote(notePath, noteLoadedListener) {
async function resolveNotePath(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);
if (!child) {
console.log("Can't find " + childNoteId);
console.log("Can't find note " + childNoteId);
return;
}
const parents = await child.getParentNotes();
@ -331,26 +334,6 @@ async function setExpandedToServer(branchId, isExpanded) {
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) {
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) {
await noteDetailService.loadNoteDetail(tab.notePath, {
tabId: tab.tabId,
newTab: true,
activate: tab.active
});
@ -465,8 +453,6 @@ function initFancyTree(tree) {
// click event won't propagate so let's close context menu manually
contextMenuWidget.hideContextMenu();
await setCurrentNotePathToHash(node);
const notePath = await treeUtils.getNotePath(node);
noteDetailService.switchToNote(notePath);
@ -554,11 +540,17 @@ async function reload() {
}
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() {
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() {
@ -835,13 +827,27 @@ utils.bindShortcut('ctrl+.', scrollToActiveNote);
$(window).bind('hashchange', async function() {
if (isNotePathInAddress()) {
const notePath = getHashValueFromAddress();
const noteId = notePath.split("/").pop();
const [notePath, tabId] = getHashValueFromAddress();
if (noteId !== '-' && noteId !== getActiveNode().data.noteId) {
console.debug("Switching to " + notePath + " because of hash change");
console.debug(`Switching to ${notePath} on tab ${tabId} 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,
getFocusedNode,
getActiveNode,
getActiveNotePath,
setCurrentNotePathToHash,
setNoteTitle,
setPrefix,
createNote,

View File

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

View File

@ -118,6 +118,8 @@ ul.fancytree-container {
flex-direction: column;
min-height: 200px;
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 {

View File

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