mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
getting rid of attributes like data-note-path in favor of the whole nav state in URLs
This commit is contained in:
parent
291f0e79d9
commit
f85209a72f
@ -9,6 +9,7 @@ import TabManager from "./tab_manager.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import Component from "./component.js";
|
||||
import keyboardActionsService from "../services/keyboard_actions.js";
|
||||
import linkService from "../services/link.js";
|
||||
import MobileScreenSwitcherExecutor from "./mobile_screen_switcher.js";
|
||||
import MainTreeExecutors from "./main_tree_executors.js";
|
||||
import toast from "../services/toast.js";
|
||||
@ -158,14 +159,9 @@ $(window).on('beforeunload', () => {
|
||||
});
|
||||
|
||||
$(window).on('hashchange', function() {
|
||||
if (treeService.isNotePathInAddress()) {
|
||||
const {notePath, ntxId, viewScope} = treeService.parseNavigationStateFromAddress();
|
||||
|
||||
if (!notePath && !ntxId) {
|
||||
console.log(`Invalid hash value "${document.location.hash}", ignoring.`);
|
||||
return;
|
||||
}
|
||||
const {notePath, ntxId, viewScope} = linkService.parseNavigationStateFromUrl(window.location.href);
|
||||
|
||||
if (notePath || ntxId) {
|
||||
appContext.tabManager.switchToNoteContext(ntxId, notePath, viewScope);
|
||||
}
|
||||
});
|
||||
|
@ -52,14 +52,13 @@ export default class TabManager extends Component {
|
||||
|
||||
async loadTabs() {
|
||||
try {
|
||||
const noteContextsToOpen = appContext.isMainWindow
|
||||
? (options.getJson('openNoteContexts') || [])
|
||||
: [];
|
||||
const noteContextsToOpen = (appContext.isMainWindow && options.getJson('openNoteContexts')) || [];
|
||||
|
||||
// preload all notes at once
|
||||
await froca.getNotes([
|
||||
...noteContextsToOpen.map(tab => treeService.getNoteIdFromNotePath(tab.notePath)),
|
||||
...noteContextsToOpen.map(tab => tab.hoistedNoteId),
|
||||
...noteContextsToOpen.flatMap(tab =>
|
||||
[ treeService.getNoteIdFromNotePath(tab.notePath), tab.hoistedNoteId]
|
||||
),
|
||||
], true);
|
||||
|
||||
const filteredNoteContexts = noteContextsToOpen.filter(openTab => {
|
||||
@ -81,7 +80,7 @@ export default class TabManager extends Component {
|
||||
});
|
||||
|
||||
// resolve before opened tabs can change this
|
||||
const parsedFromUrl = treeService.parseNavigationStateFromAddress();
|
||||
const parsedFromUrl = linkService.parseNavigationStateFromUrl(window.location.href);
|
||||
|
||||
if (filteredNoteContexts.length === 0) {
|
||||
parsedFromUrl.ntxId = parsedFromUrl.ntxId || NoteContext.generateNtxId(); // generate already here, so that we later know which one to activate
|
||||
@ -109,8 +108,8 @@ export default class TabManager extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
// if there's notePath in the URL, make sure it's open and active
|
||||
// (useful, for e.g. opening clipped notes from clipper or opening link in an extra window)
|
||||
// if there's a notePath in the URL, make sure it's open and active
|
||||
// (useful, for e.g., opening clipped notes from clipper or opening link in an extra window)
|
||||
if (parsedFromUrl.notePath) {
|
||||
await appContext.tabManager.switchToNoteContext(
|
||||
parsedFromUrl.ntxId,
|
||||
|
@ -56,8 +56,7 @@ async function createNoteLink(noteId) {
|
||||
|
||||
return $("<a>", {
|
||||
href: `#root/${noteId}`,
|
||||
class: 'reference-link',
|
||||
'data-note-path': noteId
|
||||
class: 'reference-link'
|
||||
})
|
||||
.text(note.title);
|
||||
}
|
||||
|
@ -19,21 +19,17 @@ async function createNoteLink(notePath, options = {}) {
|
||||
|
||||
if (!notePath.startsWith("root")) {
|
||||
// all note paths should start with "root/" (except for "root" itself)
|
||||
// used e.g., to find internal links
|
||||
// used, e.g., to find internal links
|
||||
notePath = `root/${notePath}`;
|
||||
}
|
||||
|
||||
let noteTitle = options.title;
|
||||
const showTooltip = options.showTooltip === undefined ? true : options.showTooltip;
|
||||
const showNotePath = options.showNotePath === undefined ? false : options.showNotePath;
|
||||
const showNoteIcon = options.showNoteIcon === undefined ? false : options.showNoteIcon;
|
||||
const referenceLink = options.referenceLink === undefined ? false : options.referenceLink;
|
||||
|
||||
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(notePath);
|
||||
|
||||
if (!noteTitle) {
|
||||
noteTitle = await treeService.getNoteTitle(noteId, parentNoteId);
|
||||
}
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromNotePath(notePath);
|
||||
const noteTitle = options.title || await treeService.getNoteTitle(noteId, parentNoteId);
|
||||
|
||||
const $container = $("<span>");
|
||||
|
||||
@ -45,11 +41,15 @@ async function createNoteLink(notePath, options = {}) {
|
||||
.append(" ");
|
||||
}
|
||||
|
||||
const hash = calculateHash({
|
||||
notePath,
|
||||
viewScope: options.viewScope
|
||||
});
|
||||
|
||||
const $noteLink = $("<a>", {
|
||||
href: `#${notePath}`,
|
||||
href: hash,
|
||||
text: noteTitle
|
||||
}).attr('data-action', 'note')
|
||||
.attr('data-note-path', notePath);
|
||||
});
|
||||
|
||||
if (!showTooltip) {
|
||||
$noteLink.addClass("no-tooltip-preview");
|
||||
@ -78,27 +78,6 @@ async function createNoteLink(notePath, options = {}) {
|
||||
return $container;
|
||||
}
|
||||
|
||||
function parseNotePathAndScope($link) {
|
||||
let notePath = $link.attr("data-note-path");
|
||||
|
||||
if (!notePath) {
|
||||
const url = $link.attr('href');
|
||||
|
||||
notePath = url ? getNotePathFromUrl(url) : null;
|
||||
}
|
||||
|
||||
const viewScope = {
|
||||
viewMode: $link.attr('data-view-mode') || 'default',
|
||||
attachmentId: $link.attr('data-attachment-id'),
|
||||
};
|
||||
|
||||
return {
|
||||
notePath,
|
||||
noteId: treeService.getNoteIdFromNotePath(notePath),
|
||||
viewScope
|
||||
};
|
||||
}
|
||||
|
||||
function calculateHash({notePath, ntxId, hoistedNoteId, viewScope = {}}) {
|
||||
notePath = notePath || "";
|
||||
const params = [
|
||||
@ -128,9 +107,50 @@ function calculateHash({notePath, ntxId, hoistedNoteId, viewScope = {}}) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
function parseNavigationStateFromUrl(url) {
|
||||
const hashIdx = url?.indexOf('#');
|
||||
if (hashIdx === -1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const hash = url?.substr(hashIdx + 1); // strip also the initial '#'
|
||||
const [notePath, paramString] = hash.split("?");
|
||||
const viewScope = {
|
||||
viewMode: 'default'
|
||||
};
|
||||
let ntxId = null;
|
||||
let hoistedNoteId = null;
|
||||
|
||||
if (paramString) {
|
||||
for (const pair of paramString.split("&")) {
|
||||
let [name, value] = pair.split("=");
|
||||
name = decodeURIComponent(name);
|
||||
value = decodeURIComponent(value);
|
||||
|
||||
if (name === 'ntxId') {
|
||||
ntxId = value;
|
||||
} else if (name === 'hoistedNoteId') {
|
||||
hoistedNoteId = value;
|
||||
} else if (['viewMode', 'attachmentId'].includes(name)) {
|
||||
viewScope[name] = value;
|
||||
} else {
|
||||
console.warn(`Unrecognized hash parameter '${name}'.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
notePath,
|
||||
noteId: treeService.getNoteIdFromNotePath(notePath),
|
||||
ntxId,
|
||||
hoistedNoteId,
|
||||
viewScope
|
||||
};
|
||||
}
|
||||
|
||||
function goToLink(evt) {
|
||||
const $link = $(evt.target).closest("a,.block-link");
|
||||
const hrefLink = $link.attr('href');
|
||||
const hrefLink = $link.attr('href') || $link.attr('data-href');
|
||||
|
||||
if (hrefLink?.startsWith("data:")) {
|
||||
return true;
|
||||
@ -139,7 +159,7 @@ function goToLink(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
const { notePath, viewScope } = parseNotePathAndScope($link);
|
||||
const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink);
|
||||
|
||||
const ctrlKey = utils.isCtrlKey(evt);
|
||||
const isLeftClick = evt.which === 1;
|
||||
@ -186,8 +206,9 @@ function goToLink(evt) {
|
||||
|
||||
function linkContextMenu(e) {
|
||||
const $link = $(e.target).closest("a");
|
||||
const url = $link.attr("href") || $link.attr("data-href");
|
||||
|
||||
const { notePath, viewScope } = parseNotePathAndScope($link);
|
||||
const { notePath, viewScope } = parseNavigationStateFromUrl(url);
|
||||
|
||||
if (!notePath) {
|
||||
return;
|
||||
@ -252,6 +273,6 @@ export default {
|
||||
createNoteLink,
|
||||
goToLink,
|
||||
loadReferenceLinkTitle,
|
||||
parseNotePathAndScope,
|
||||
calculateHash
|
||||
calculateHash,
|
||||
parseNavigationStateFromUrl
|
||||
};
|
||||
|
@ -114,8 +114,7 @@ function initNoteAutocomplete($el, options) {
|
||||
.prop("title", "Show recent notes");
|
||||
|
||||
const $goToSelectedNoteButton = $("<a>")
|
||||
.addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right")
|
||||
.attr("data-action", "note");
|
||||
.addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
|
||||
|
||||
const $sideButtons = $("<div>")
|
||||
.addClass("input-group-append")
|
||||
|
@ -54,9 +54,9 @@ async function getRenderedContent(note, options = {}) {
|
||||
}
|
||||
}
|
||||
else if (type === 'code') {
|
||||
const fullNote = await server.get(`notes/${note.noteId}`);
|
||||
const blob = await note.getBlob({ preview: options.trim });
|
||||
|
||||
$renderedContent.append($("<pre>").text(trim(fullNote.content, options.trim)));
|
||||
$renderedContent.append($("<pre>").text(trim(blob.content, options.trim)));
|
||||
}
|
||||
else if (type === 'image') {
|
||||
const sanitizedTitle = note.title.replace(/[^a-z0-9-.]/gi, "");
|
||||
|
@ -268,7 +268,7 @@ class NoteListRenderer {
|
||||
|
||||
const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note);
|
||||
const notePath = this.parentNote.type === 'search'
|
||||
? note.noteId // for search note parent we want to display non-search path
|
||||
? note.noteId // for search note parent, we want to display a non-search path
|
||||
: `${this.parentNote.noteId}/${note.noteId}`;
|
||||
|
||||
const $card = $('<div class="note-book-card">')
|
||||
@ -288,7 +288,7 @@ class NoteListRenderer {
|
||||
if (this.viewType === 'grid') {
|
||||
$card
|
||||
.addClass("block-link")
|
||||
.attr("data-note-path", notePath)
|
||||
.attr("data-href", `#${notePath}`)
|
||||
.on('click', e => linkService.goToLink(e));
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,8 @@ async function mouseEnterHandler() {
|
||||
return;
|
||||
}
|
||||
|
||||
const { notePath, noteId, viewScope } = linkService.parseNotePathAndScope($link);
|
||||
const url = $link.attr("href") || $link.attr("data-href");
|
||||
const { notePath, noteId, viewScope } = linkService.parseNavigationStateFromUrl(url);
|
||||
|
||||
if (!notePath || viewScope.viewMode !== 'default') {
|
||||
return;
|
||||
|
@ -279,50 +279,6 @@ async function getNoteTitleWithPathAsSuffix(notePath) {
|
||||
return $titleWithPath;
|
||||
}
|
||||
|
||||
function parseNavigationStateFromAddress() {
|
||||
const str = document.location.hash?.substr(1) || ""; // strip initial #
|
||||
|
||||
const [notePath, paramString] = str.split("?");
|
||||
const viewScope = {
|
||||
viewMode: 'default'
|
||||
};
|
||||
let ntxId = null;
|
||||
let hoistedNoteId = null;
|
||||
|
||||
if (paramString) {
|
||||
for (const pair of paramString.split("&")) {
|
||||
let [name, value] = pair.split("=");
|
||||
name = decodeURIComponent(name);
|
||||
value = decodeURIComponent(value);
|
||||
|
||||
if (name === 'ntxId') {
|
||||
ntxId = value;
|
||||
} else if (name === 'hoistedNoteId') {
|
||||
hoistedNoteId = value;
|
||||
} else if (['viewMode', 'attachmentId'].includes(name)) {
|
||||
viewScope[name] = value;
|
||||
} else {
|
||||
console.warn(`Unrecognized hash parameter '${name}'.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
notePath,
|
||||
ntxId,
|
||||
hoistedNoteId,
|
||||
viewScope
|
||||
};
|
||||
}
|
||||
|
||||
function isNotePathInAddress() {
|
||||
const {notePath, ntxId} = parseNavigationStateFromAddress();
|
||||
|
||||
return notePath.startsWith("root")
|
||||
// empty string is for empty/uninitialized tab
|
||||
|| (notePath === '' && !!ntxId);
|
||||
}
|
||||
|
||||
function isNotePathInHiddenSubtree(notePath) {
|
||||
return notePath?.includes("root/_hidden");
|
||||
}
|
||||
@ -338,7 +294,5 @@ export default {
|
||||
getNoteTitle,
|
||||
getNotePathTitle,
|
||||
getNoteTitleWithPathAsSuffix,
|
||||
parseNavigationStateFromAddress,
|
||||
isNotePathInAddress,
|
||||
isNotePathInHiddenSubtree
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ import BasicWidget from "./basic_widget.js";
|
||||
import server from "../services/server.js";
|
||||
import options from "../services/options.js";
|
||||
import imageService from "../services/image.js";
|
||||
import linkService from "../services/link.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="attachment-detail">
|
||||
@ -15,6 +16,7 @@ const TPL = `
|
||||
.attachment-title-line {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.attachment-details {
|
||||
@ -54,10 +56,10 @@ const TPL = `
|
||||
|
||||
<div class="attachment-detail-wrapper">
|
||||
<div class="attachment-title-line">
|
||||
<div class="attachment-actions-container"></div>
|
||||
<h4 class="attachment-title"></h4>
|
||||
<div class="attachment-details"></div>
|
||||
<div style="flex: 1 1;"></div>
|
||||
<div class="attachment-actions-container"></div>
|
||||
</div>
|
||||
|
||||
<div class="attachment-deletion-warning alert alert-info"></div>
|
||||
@ -84,7 +86,7 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
async refresh() {
|
||||
this.$widget.find('.attachment-detail-wrapper')
|
||||
.empty()
|
||||
.append(
|
||||
@ -97,11 +99,13 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
||||
|
||||
if (!this.isFullDetail) {
|
||||
this.$wrapper.find('.attachment-title').append(
|
||||
$('<a href="javascript:">')
|
||||
.attr("data-note-path", this.attachment.parentId)
|
||||
.attr("data-view-mode", "attachments")
|
||||
.attr("data-attachment-id", this.attachment.attachmentId)
|
||||
.text(this.attachment.title)
|
||||
await linkService.createNoteLink(this.attachment.parentId, {
|
||||
title: this.attachment.title,
|
||||
viewScope: {
|
||||
viewMode: 'attachments',
|
||||
attachmentId: this.attachment.attachmentId
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.$wrapper.find('.attachment-title')
|
||||
|
@ -701,9 +701,8 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
createNoteLink(noteId) {
|
||||
return $("<a>", {
|
||||
href: `#${noteId}`,
|
||||
class: 'reference-link',
|
||||
'data-note-path': noteId
|
||||
href: `#root/${noteId}`,
|
||||
class: 'reference-link'
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
|
||||
|
||||
if (dateNoteId) {
|
||||
$newDay.addClass('calendar-date-exists');
|
||||
$newDay.attr("data-note-path", dateNoteId);
|
||||
$newDay.attr("href", `#root/dateNoteId`);
|
||||
}
|
||||
|
||||
if (this.isEqual(this.date, this.activeDate)) {
|
||||
|
@ -55,7 +55,7 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
|
||||
for (const idx in this.webContents.history) {
|
||||
const url = this.webContents.history[idx];
|
||||
const [_, notePathWithTab] = url.split('#');
|
||||
// broken: use treeService.parseNavigationStateFromAddress();
|
||||
// broken: use linkService.parseNavigationStateFromUrl();
|
||||
const [notePath, ntxId] = notePathWithTab.split('-');
|
||||
|
||||
const title = await treeService.getNotePathTitle(notePath);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import server from "../../services/server.js";
|
||||
import AttachmentDetailWidget from "../attachment_detail.js";
|
||||
import linkService from "../../services/link.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="attachment-detail note-detail-printable">
|
||||
@ -10,6 +11,8 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="links-wrapper"></div>
|
||||
|
||||
<div class="attachment-wrapper"></div>
|
||||
</div>`;
|
||||
|
||||
@ -29,6 +32,8 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
||||
this.$wrapper.empty();
|
||||
this.children = [];
|
||||
|
||||
linkService.createNoteLink(this.noteId, {});
|
||||
|
||||
const attachment = await server.get(`attachments/${this.attachmentId}/?includeContent=true`);
|
||||
|
||||
if (!attachment) {
|
||||
|
@ -33,7 +33,7 @@ function sanitize(dirtyHtml) {
|
||||
'en-media' // for ENEX import
|
||||
],
|
||||
allowedAttributes: {
|
||||
'a': [ 'href', 'class', 'data-note-path' ],
|
||||
'a': [ 'href', 'class' ],
|
||||
'img': [ 'src' ],
|
||||
'section': [ 'class', 'data-note-id' ],
|
||||
'figure': [ 'class' ],
|
||||
|
@ -376,20 +376,6 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
return `href="#root/${target.noteId}"`;
|
||||
});
|
||||
|
||||
content = content.replace(/data-note-path="([^"]*)"/g, (match, notePath) => {
|
||||
const noteId = notePath.split("/").pop();
|
||||
|
||||
let targetNoteId;
|
||||
|
||||
if (noteId === 'root' || noteId.startsWith("_")) { // named noteIds stay identical across instances
|
||||
targetNoteId = noteId;
|
||||
} else {
|
||||
targetNoteId = noteIdMap[noteId];
|
||||
}
|
||||
|
||||
return `data-note-path="root/${targetNoteId}"`;
|
||||
});
|
||||
|
||||
if (noteMeta) {
|
||||
const includeNoteLinks = (noteMeta.attributes || [])
|
||||
.filter(attr => attr.type === 'relation' && attr.name === 'includeNoteLink');
|
||||
|
Loading…
x
Reference in New Issue
Block a user