import ws from './ws.js'; import utils from './utils.js'; import server from './server.js'; import froca from './froca.js'; import hoistedNoteService from '../services/hoisted_note.js'; import appContext from "../components/app_context.js"; /** * @return {string|null} */ async function resolveNotePath(notePath, 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. * * @return {string[]} */ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logErrors = true) { utils.assertArguments(notePath); // we might get notePath with the ntxId suffix, remove it if present notePath = notePath.split("-")[0].trim(); if (notePath.length === 0) { return; } const path = notePath.split("/").reverse(); if (!path.includes("root")) { path.push('root'); } const effectivePathSegments = []; let childNoteId = null; let i = 0; while (true) { if (i >= path.length) { break; } 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; } child.resortParents(); const parents = child.getParentNotes(); if (!parents.length) { if (logErrors) { ws.logError(`No parents found for note ${childNoteId} (${child.title}) for path ${notePath}`); } return; } if (!parents.some(p => p.noteId === parentNoteId)) { 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 someNotePath = getSomeNotePath(child, hoistedNoteId); if (someNotePath) { // in case it's root the path may be empty const pathToRoot = someNotePath.split("/").reverse().slice(1); if (!pathToRoot.includes("root")) { pathToRoot.push('root'); } for (const noteId of pathToRoot) { effectivePathSegments.push(noteId); } } break; } } effectivePathSegments.push(parentNoteId); childNoteId = parentNoteId; } effectivePathSegments.reverse(); if (effectivePathSegments.includes(hoistedNoteId)) { return effectivePathSegments; } else { const note = await froca.getNote(getNoteIdFromNotePath(notePath)); const someNotePathSegments = getSomeNotePathSegments(note, hoistedNoteId); if (!someNotePathSegments) { 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 someNotePathSegments.includes(hoistedNoteId) ? someNotePathSegments : effectivePathSegments; } } function getSomeNotePathSegments(note, hoistedNotePath = 'root') { utils.assertArguments(note); const notePaths = note.getSortedNotePaths(hoistedNotePath); return notePaths.length > 0 ? notePaths[0].notePath : null; } function getSomeNotePath(note, hoistedNotePath = 'root') { const notePath = getSomeNotePathSegments(note, hoistedNotePath); return notePath === null ? null : notePath.join('/'); } 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) { return hoistedNoteService.isHoistedNode(node) ? 0 : node.getParent().data.isProtected; } function getNoteIdFromNotePath(notePath) { if (!notePath) { return null; } const path = notePath.split("/"); const lastSegment = path[path.length - 1]; // path could have also ntxId suffix return lastSegment.split("-")[0]; } async function getBranchIdFromNotePath(notePath) { const {noteId, parentNoteId} = getNoteIdAndParentIdFromNotePath(notePath); return await froca.getBranchId(parentNoteId, noteId); } function getNoteIdAndParentIdFromNotePath(notePath) { if (notePath === 'root') { return { noteId: 'root', parentNoteId: 'none' }; } let parentNoteId = 'root'; let noteId = ''; if (notePath) { const path = notePath.split("/"); const lastSegment = path[path.length - 1]; // path could have also ntxId suffix noteId = lastSegment.split("-")[0]; if (path.length > 1) { parentNoteId = path[path.length - 2]; } } return { parentNoteId, noteId }; } function getNotePath(node) { if (!node) { logError("Node is null"); return ""; } const path = []; while (node) { if (node.data.noteId) { path.push(node.data.noteId); } node = node.getParent(); } return path.reverse().join("/"); } async function getNoteTitle(noteId, parentNoteId = 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 && branch.prefix) { title = `${branch.prefix} - ${title}`; } } } return title; } async function getNotePathTitleComponents(notePath) { const titleComponents = []; 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) { utils.assertArguments(notePath); const titlePath = await getNotePathTitleComponents(notePath); return titlePath.join(' / '); } async function getNoteTitleWithPathAsSuffix(notePath) { 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 = $('') .append($('').text(title)); if (path.length > 0) { $titleWithPath .append($('').text(' (' + path.join(' / ') + ')')); } return $titleWithPath; } function getHashValueFromAddress() { const str = document.location.hash ? document.location.hash.substr(1) : ""; // strip initial # return str.split("-"); } function parseNotePath(notePath) { let noteIds = notePath.split('/'); if (noteIds[0] !== 'root') { noteIds = ['root'].concat(noteIds); } return noteIds; } function isNotePathInHiddenSubtree(notePath) { return notePath?.includes("root/hidden"); } export default { resolveNotePath, resolveNotePathToSegments, getSomeNotePath, getSomeNotePathSegments, getParentProtectedStatus, getNotePath, getNoteIdFromNotePath, getNoteIdAndParentIdFromNotePath, getBranchIdFromNotePath, getNoteTitle, getNotePathTitle, getNoteTitleWithPathAsSuffix, getHashValueFromAddress, parseNotePath, isNotePathInHiddenSubtree };