mirror of
https://github.com/zadam/trilium.git
synced 2025-12-04 22:44:25 +01:00
before there was polysemy in word url that is now resolved by making link hypernym to url and path.
318 lines
9.0 KiB
TypeScript
318 lines
9.0 KiB
TypeScript
import ws from "./ws.js";
|
|
import utils from "./utils.js";
|
|
import froca from "./froca.js";
|
|
import hoistedNoteService from "./hoisted_note.js";
|
|
import appContext from "../components/app_context.js";
|
|
|
|
async function resolveNotePath(notePath: string, hoistedNoteId = "root") {
|
|
const runPath = await resolveNotePathToSegments(notePath, hoistedNoteId);
|
|
|
|
return runPath ? runPath.join("/") : null;
|
|
}
|
|
|
|
/**
|
|
* Accepts notePath which might or might not be valid and returns an existing path as close to the original
|
|
* notePath as possible. Part of the path might not be valid because of note moving (which causes
|
|
* path change) or other corruption, in that case, this will try to get some other valid path to the correct note.
|
|
*/
|
|
async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root", logErrors = true) {
|
|
utils.assertArguments(notePath);
|
|
|
|
// we might get notePath with the params suffix, remove it if present
|
|
notePath = notePath.split("?")[0].trim();
|
|
|
|
if (notePath.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const path = notePath.split("/").reverse();
|
|
const effectivePathSegments: string[] = [];
|
|
let childNoteId: string | null = null;
|
|
let i = 0;
|
|
|
|
for (let i = 0; i < path.length; i++) {
|
|
const parentNoteId = path[i];
|
|
|
|
if (childNoteId !== null) {
|
|
const child = await froca.getNote(childNoteId, !logErrors);
|
|
|
|
if (!child) {
|
|
if (logErrors) {
|
|
ws.logError(`Can't find note ${childNoteId}`);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
child.sortParents();
|
|
|
|
const parents = child.getParentNotes();
|
|
|
|
if (!parents.length) {
|
|
if (logErrors) {
|
|
ws.logError(`No parents found for note ${childNoteId} (${child.title}) for path ${notePath}`);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
if (!parents.some(p => p.noteId === parentNoteId) || (i === path.length - 1 && parentNoteId !== 'root')) {
|
|
if (logErrors) {
|
|
const parent = froca.getNoteFromCache(parentNoteId);
|
|
|
|
console.debug(
|
|
utils.now(),
|
|
`Did not find parent ${parentNoteId} (${parent ? parent.title : "n/a"})
|
|
for child ${childNoteId} (${child.title}), available parents: ${parents.map((p) => `${p.noteId} (${p.title})`)}.
|
|
You can ignore this message as it is mostly harmless.`
|
|
);
|
|
}
|
|
|
|
const activeNotePath = appContext.tabManager.getActiveContextNotePath();
|
|
const bestNotePath = child.getBestNotePath(hoistedNoteId, activeNotePath);
|
|
|
|
if (bestNotePath) {
|
|
const pathToRoot = bestNotePath.reverse().slice(1);
|
|
|
|
for (const noteId of pathToRoot) {
|
|
effectivePathSegments.push(noteId);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
effectivePathSegments.push(parentNoteId);
|
|
childNoteId = parentNoteId;
|
|
}
|
|
|
|
effectivePathSegments.reverse();
|
|
|
|
if (effectivePathSegments.includes(hoistedNoteId) && effectivePathSegments.includes('root')) {
|
|
return effectivePathSegments;
|
|
} else {
|
|
const noteId = getNoteIdFromLink(notePath);
|
|
if (!noteId) {
|
|
throw new Error(`Unable to find note with ID: ${noteId}.`);
|
|
}
|
|
const note = await froca.getNote(noteId);
|
|
if (!note) {
|
|
throw new Error(`Unable to find note: ${notePath}.`);
|
|
}
|
|
|
|
const activeNotePath = appContext.tabManager.getActiveContextNotePath();
|
|
const bestNotePath = note.getBestNotePath(hoistedNoteId, activeNotePath);
|
|
|
|
if (!bestNotePath) {
|
|
throw new Error(`Did not find any path segments for '${note.toString()}', hoisted note '${hoistedNoteId}'`);
|
|
}
|
|
|
|
// if there isn't actually any note path with hoisted note, then return the original resolved note path
|
|
return bestNotePath.includes(hoistedNoteId) ? bestNotePath : effectivePathSegments;
|
|
}
|
|
}
|
|
|
|
ws.subscribeToMessages((message) => {
|
|
if (message.type === "openNote") {
|
|
appContext.tabManager.activateOrOpenNote(message.noteId);
|
|
|
|
if (utils.isElectron()) {
|
|
const currentWindow = utils.dynamicRequire("@electron/remote").getCurrentWindow();
|
|
|
|
currentWindow.show();
|
|
}
|
|
}
|
|
});
|
|
|
|
function getParentProtectedStatus(node: Fancytree.FancytreeNode) {
|
|
return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected;
|
|
}
|
|
|
|
function getNoteIdFromLink(urlOrNotePath: string | null | undefined) {
|
|
if (!urlOrNotePath) {
|
|
return null;
|
|
}
|
|
|
|
const [notePath] = urlOrNotePath.split("?");
|
|
const segments = notePath.split("/");
|
|
|
|
return segments[segments.length - 1];
|
|
}
|
|
|
|
async function getBranchIdFromUrl(urlOrNotePath: string) {
|
|
const { noteId, parentNoteId } = getNoteIdAndParentIdFromUrl(urlOrNotePath);
|
|
if (!parentNoteId) {
|
|
return null;
|
|
}
|
|
|
|
return await froca.getBranchId(parentNoteId, noteId);
|
|
}
|
|
|
|
function getNoteIdAndParentIdFromUrl(urlOrNotePath: string) {
|
|
if (!urlOrNotePath) {
|
|
return {};
|
|
}
|
|
|
|
const [notePath] = urlOrNotePath.split("?");
|
|
|
|
if (notePath === "root") {
|
|
return {
|
|
noteId: "root",
|
|
parentNoteId: "none"
|
|
};
|
|
}
|
|
|
|
let parentNoteId = "root";
|
|
let noteId = "";
|
|
|
|
if (notePath) {
|
|
const segments = notePath.split("/");
|
|
|
|
noteId = segments[segments.length - 1];
|
|
|
|
if (segments.length > 1) {
|
|
parentNoteId = segments[segments.length - 2];
|
|
}
|
|
}
|
|
|
|
return {
|
|
parentNoteId,
|
|
noteId
|
|
};
|
|
}
|
|
|
|
function getNotePath(node: Fancytree.FancytreeNode) {
|
|
if (!node) {
|
|
logError("Node is null");
|
|
return "";
|
|
}
|
|
|
|
const path: string[] = [];
|
|
|
|
while (node) {
|
|
if (node.data.noteId) {
|
|
path.push(node.data.noteId);
|
|
}
|
|
|
|
node = node.getParent();
|
|
}
|
|
|
|
return path.reverse().join("/");
|
|
}
|
|
|
|
async function getNoteTitle(noteId: string, parentNoteId: string | null = null) {
|
|
utils.assertArguments(noteId);
|
|
|
|
const note = await froca.getNote(noteId);
|
|
if (!note) {
|
|
return "[not found]";
|
|
}
|
|
|
|
let { title } = note;
|
|
|
|
if (parentNoteId !== null) {
|
|
const branchId = note.parentToBranch[parentNoteId];
|
|
|
|
if (branchId) {
|
|
const branch = froca.getBranch(branchId);
|
|
|
|
if (branch?.prefix) {
|
|
title = `${branch.prefix} - ${title}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
return title;
|
|
}
|
|
|
|
async function getNotePathTitleComponents(notePath: string) {
|
|
const titleComponents: string[] = [];
|
|
|
|
if (notePath.startsWith("root/")) {
|
|
notePath = notePath.substr(5);
|
|
}
|
|
|
|
// special case when we want just root's title
|
|
if (notePath === "root") {
|
|
titleComponents.push(await getNoteTitle(notePath));
|
|
} else {
|
|
let parentNoteId = "root";
|
|
|
|
for (const noteId of notePath.split("/")) {
|
|
titleComponents.push(await getNoteTitle(noteId, parentNoteId));
|
|
|
|
parentNoteId = noteId;
|
|
}
|
|
}
|
|
|
|
return titleComponents;
|
|
}
|
|
|
|
async function getNotePathTitle(notePath: string) {
|
|
utils.assertArguments(notePath);
|
|
|
|
const titlePath = await getNotePathTitleComponents(notePath);
|
|
|
|
return titlePath.join(" / ");
|
|
}
|
|
|
|
async function getNoteTitleWithPathAsSuffix(notePath: string) {
|
|
utils.assertArguments(notePath);
|
|
|
|
const titleComponents = await getNotePathTitleComponents(notePath);
|
|
|
|
if (!titleComponents || titleComponents.length === 0) {
|
|
return "";
|
|
}
|
|
|
|
const title = titleComponents[titleComponents.length - 1];
|
|
const path = titleComponents.slice(0, titleComponents.length - 1);
|
|
|
|
const $titleWithPath = $('<span class="note-title-with-path">').append($('<span class="note-title">').text(title));
|
|
|
|
$titleWithPath.append(formatNotePath(path));
|
|
|
|
return $titleWithPath;
|
|
}
|
|
|
|
function formatNotePath(path: string[]) {
|
|
const $notePath = $('<span class="note-path">');
|
|
|
|
if (path.length > 0) {
|
|
$notePath.append($(`<span class="path-bracket"> (</span>)`));
|
|
|
|
for (let segmentIndex = 0; segmentIndex < path.length; segmentIndex++) {
|
|
$notePath.append($(`<span>`).text(path[segmentIndex]));
|
|
|
|
if (segmentIndex < path.length - 1) {
|
|
$notePath.append($(`<span class="path-delimiter">`).text(" / "));
|
|
}
|
|
}
|
|
|
|
$notePath.append($(`<span class="path-bracket">)</span>)`));
|
|
}
|
|
|
|
return $notePath;
|
|
}
|
|
|
|
function isNotePathInHiddenSubtree(notePath: string) {
|
|
return notePath?.includes("root/_hidden");
|
|
}
|
|
|
|
export default {
|
|
resolveNotePath,
|
|
resolveNotePathToSegments,
|
|
getParentProtectedStatus,
|
|
getNotePath,
|
|
getNotePathTitleComponents,
|
|
getNoteIdFromLink,
|
|
getNoteIdAndParentIdFromUrl,
|
|
getBranchIdFromUrl,
|
|
getNoteTitle,
|
|
getNotePathTitle,
|
|
getNoteTitleWithPathAsSuffix,
|
|
isNotePathInHiddenSubtree,
|
|
formatNotePath
|
|
};
|