mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	refactoring of "some path" WIP
This commit is contained in:
		| @@ -120,7 +120,7 @@ function getNoteTitleForPath(notePathArray) { | |||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Returns notePath for noteId from cache. Note hoisting is respected. |  * Returns notePath for noteId. Note hoisting is respected. | ||||||
|  * Archived (and hidden) notes are also returned, but non-archived paths are preferred if available |  * Archived (and hidden) notes are also returned, but non-archived paths are preferred if available | ||||||
|  * - this means that archived paths is returned only if there's no non-archived path |  * - this means that archived paths is returned only if there's no non-archived path | ||||||
|  * - you can check whether returned path is archived using isArchived |  * - you can check whether returned path is archived using isArchived | ||||||
| @@ -136,20 +136,20 @@ function getSomePath(note, path = []) { | |||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @param {BNote} note |  * @param {BNote} note | ||||||
|  * @param {string[]} path |  * @param {string[]} parentPath | ||||||
|  * @param {boolean}respectHoisting |  * @param {boolean} respectHoisting | ||||||
|  * @returns {string[]|false} |  * @returns {string[]|false} | ||||||
|  */ |  */ | ||||||
| function getSomePathInner(note, path, respectHoisting) { | function getSomePathInner(note, parentPath, respectHoisting) { | ||||||
|  |     const childPath = [...parentPath, note.noteId]; | ||||||
|     if (note.isRoot()) { |     if (note.isRoot()) { | ||||||
|         const foundPath = [...path, note.noteId]; |         childPath.reverse(); | ||||||
|         foundPath.reverse(); |  | ||||||
|  |  | ||||||
|         if (respectHoisting && !foundPath.includes(cls.getHoistedNoteId())) { |         if (respectHoisting && !childPath.includes(cls.getHoistedNoteId())) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return foundPath; |         return childPath; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const parents = note.parents; |     const parents = note.parents; | ||||||
| @@ -159,15 +159,35 @@ function getSomePathInner(note, path, respectHoisting) { | |||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     for (const parentNote of parents) { |     const completeNotePaths = parents.map(parentNote => getSomePathInner(parentNote, childPath, respectHoisting)); | ||||||
|         const retPath = getSomePathInner(parentNote, [...path, note.noteId], respectHoisting); |  | ||||||
|  |  | ||||||
|         if (retPath) { |  | ||||||
|             return retPath; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |     if (completeNotePaths.length === 0) { | ||||||
|         return false; |         return false; | ||||||
|  |     } else if (completeNotePaths.length === 1) { | ||||||
|  |         return completeNotePaths[0]; | ||||||
|  |     } else { | ||||||
|  |         completeNotePaths.sort((a, b) => { | ||||||
|  |             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { | ||||||
|  |                 return a.isInHoistedSubTree ? -1 : 1; | ||||||
|  |             } else if (a.isSearch !== b.isSearch) { | ||||||
|  |                 return a.isSearch ? 1 : -1; | ||||||
|  |             } else if (a.isArchived !== b.isArchived) { | ||||||
|  |                 return a.isArchived ? 1 : -1; | ||||||
|  |             } else if (a.isHidden !== b.isHidden) { | ||||||
|  |                 return a.isHidden ? 1 : -1; | ||||||
|  |             } else { | ||||||
|  |                 return a.notePath.length - b.notePath.length; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // if there are multiple valid paths, take the shortest one | ||||||
|  |         const shortestNotePath = completeNotePaths.reduce((shortestPath, nextPath) => | ||||||
|  |             nextPath.length < shortestPath.length | ||||||
|  |                 ? nextPath | ||||||
|  |                 : shortestPath, completeNotePaths[0]); | ||||||
|  |  | ||||||
|  |         return shortestNotePath; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function getNotePath(noteId) { | function getNotePath(noteId) { | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ const TaskContext = require("../../services/task_context"); | |||||||
| const dayjs = require("dayjs"); | const dayjs = require("dayjs"); | ||||||
| const utc = require('dayjs/plugin/utc'); | const utc = require('dayjs/plugin/utc'); | ||||||
| const eventService = require("../../services/events"); | const eventService = require("../../services/events"); | ||||||
|  | const froca = require("../../public/app/services/froca.js"); | ||||||
| dayjs.extend(utc); | dayjs.extend(utc); | ||||||
|  |  | ||||||
| const LABEL = 'label'; | const LABEL = 'label'; | ||||||
| @@ -1150,6 +1151,8 @@ class BNote extends AbstractBeccaEntity { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|  |      * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) | ||||||
|  |      * | ||||||
|      * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) |      * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) | ||||||
|      */ |      */ | ||||||
|     getAllNotePaths() { |     getAllNotePaths() { | ||||||
| @@ -1157,18 +1160,73 @@ class BNote extends AbstractBeccaEntity { | |||||||
|             return [['root']]; |             return [['root']]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const notePaths = []; |         const parentNotes = this.getParentNotes(); | ||||||
|  |         let notePaths = []; | ||||||
|  |  | ||||||
|         for (const parentNote of this.getParentNotes()) { |         if (parentNotes.length === 1) { // optimization for most common case | ||||||
|             for (const parentPath of parentNote.getAllNotePaths()) { |             notePaths = parentNotes[0].getAllNotePaths(); | ||||||
|                 parentPath.push(this.noteId); |         } else { | ||||||
|                 notePaths.push(parentPath); |             notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         for (const notePath of notePaths) { | ||||||
|  |             notePath.push(this.noteId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return notePaths; |         return notePaths; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} | ||||||
|  |      */ | ||||||
|  |     getSortedNotePathRecords(hoistedNoteId = 'root') { | ||||||
|  |         const isHoistedRoot = hoistedNoteId === 'root'; | ||||||
|  |  | ||||||
|  |         const notePaths = this.getAllNotePaths().map(path => ({ | ||||||
|  |             notePath: path, | ||||||
|  |             isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), | ||||||
|  |             isArchived: path.some(noteId => froca.notes[noteId].isArchived), | ||||||
|  |             isHidden: path.includes('_hidden') | ||||||
|  |         })); | ||||||
|  |  | ||||||
|  |         notePaths.sort((a, b) => { | ||||||
|  |             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { | ||||||
|  |                 return a.isInHoistedSubTree ? -1 : 1; | ||||||
|  |             } else if (a.isArchived !== b.isArchived) { | ||||||
|  |                 return a.isArchived ? 1 : -1; | ||||||
|  |             } else if (a.isHidden !== b.isHidden) { | ||||||
|  |                 return a.isHidden ? 1 : -1; | ||||||
|  |             } else { | ||||||
|  |                 return a.notePath.length - b.notePath.length; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         return notePaths; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string[]} array of noteIds constituting the particular note path | ||||||
|  |      */ | ||||||
|  |     getBestNotePath(hoistedNoteId = 'root') { | ||||||
|  |         return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string} serialized note path (e.g. 'root/a1h315/js725h') | ||||||
|  |      */ | ||||||
|  |     getBestNotePathString(hoistedNoteId = 'root') { | ||||||
|  |         const notePath = this.getBestNotePath(hoistedNoteId); | ||||||
|  |  | ||||||
|  |         return notePath?.join("/"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree |      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -247,6 +247,11 @@ class FNote { | |||||||
|         return this.__filterAttrs(this.__getCachedAttributes([]), type, name); |         return this.__filterAttrs(this.__getCachedAttributes([]), type, name); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param {string[]} path | ||||||
|  |      * @return {FAttribute[]} | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|     __getCachedAttributes(path) { |     __getCachedAttributes(path) { | ||||||
|         // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates |         // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates | ||||||
|         // when template instance is a parent of template itself |         // when template instance is a parent of template itself | ||||||
| @@ -299,63 +304,49 @@ class FNote { | |||||||
|         return this.noteId === 'root'; |         return this.noteId === 'root'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getAllNotePaths(encounteredNoteIds = null) { |     /** | ||||||
|  |      * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) | ||||||
|  |      * | ||||||
|  |      * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) | ||||||
|  |      */ | ||||||
|  |     getAllNotePaths() { | ||||||
|         if (this.noteId === 'root') { |         if (this.noteId === 'root') { | ||||||
|             return [['root']]; |             return [['root']]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!encounteredNoteIds) { |  | ||||||
|             encounteredNoteIds = new Set(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         encounteredNoteIds.add(this.noteId); |  | ||||||
|  |  | ||||||
|         const parentNotes = this.getParentNotes(); |         const parentNotes = this.getParentNotes(); | ||||||
|         let paths; |         let notePaths = []; | ||||||
|  |  | ||||||
|         if (parentNotes.length === 1) { // optimization for the most common case |         if (parentNotes.length === 1) { // optimization for most common case | ||||||
|             if (encounteredNoteIds.has(parentNotes[0].noteId)) { |             notePaths = parentNotes[0].getAllNotePaths(); | ||||||
|                 return []; |         } else { | ||||||
|             } |             notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); | ||||||
|             else { |  | ||||||
|                 paths = parentNotes[0].getAllNotePaths(encounteredNoteIds); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             paths = []; |  | ||||||
|  |  | ||||||
|             for (const parentNote of parentNotes) { |  | ||||||
|                 if (encounteredNoteIds.has(parentNote.noteId)) { |  | ||||||
|                     continue; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|                 const newSet = new Set(encounteredNoteIds); |         for (const notePath of notePaths) { | ||||||
|  |             notePath.push(this.noteId); | ||||||
|                 paths.push(...parentNote.getAllNotePaths(newSet)); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         for (const path of paths) { |         return notePaths; | ||||||
|             path.push(this.noteId); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         return paths; |     /** | ||||||
|     } |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} | ||||||
|  |      */ | ||||||
|  |     getSortedNotePathRecords(hoistedNoteId = 'root') { | ||||||
|  |         const isHoistedRoot = hoistedNoteId === 'root'; | ||||||
|  |  | ||||||
|     getSortedNotePaths(hoistedNotePath = 'root') { |  | ||||||
|         const notePaths = this.getAllNotePaths().map(path => ({ |         const notePaths = this.getAllNotePaths().map(path => ({ | ||||||
|             notePath: path, |             notePath: path, | ||||||
|             isInHoistedSubTree: path.includes(hoistedNotePath), |             isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), | ||||||
|             isArchived: path.find(noteId => froca.notes[noteId].isArchived), |             isArchived: path.some(noteId => froca.notes[noteId].isArchived), | ||||||
|             isSearch: path.find(noteId => froca.notes[noteId].type === 'search'), |  | ||||||
|             isHidden: path.includes('_hidden') |             isHidden: path.includes('_hidden') | ||||||
|         })); |         })); | ||||||
|  |  | ||||||
|         notePaths.sort((a, b) => { |         notePaths.sort((a, b) => { | ||||||
|             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { |             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { | ||||||
|                 return a.isInHoistedSubTree ? -1 : 1; |                 return a.isInHoistedSubTree ? -1 : 1; | ||||||
|             } else if (a.isSearch !== b.isSearch) { |  | ||||||
|                 return a.isSearch ? 1 : -1; |  | ||||||
|             } else if (a.isArchived !== b.isArchived) { |             } else if (a.isArchived !== b.isArchived) { | ||||||
|                 return a.isArchived ? 1 : -1; |                 return a.isArchived ? 1 : -1; | ||||||
|             } else if (a.isHidden !== b.isHidden) { |             } else if (a.isHidden !== b.isHidden) { | ||||||
| @@ -368,6 +359,28 @@ class FNote { | |||||||
|         return notePaths; |         return notePaths; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string[]} array of noteIds constituting the particular note path | ||||||
|  |      */ | ||||||
|  |     getBestNotePath(hoistedNoteId = 'root') { | ||||||
|  |         return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string} serialized note path (e.g. 'root/a1h315/js725h') | ||||||
|  |      */ | ||||||
|  |     getBestNotePathString(hoistedNoteId = 'root') { | ||||||
|  |         const notePath = this.getBestNotePath(hoistedNoteId); | ||||||
|  |  | ||||||
|  |         return notePath?.join("/"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree |      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree | ||||||
|      */ |      */ | ||||||
| @@ -391,6 +404,13 @@ class FNote { | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param {FAttribute[]} attributes | ||||||
|  |      * @param {string} type | ||||||
|  |      * @param {string} name | ||||||
|  |      * @return {FAttribute[]} | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|     __filterAttrs(attributes, type, name) { |     __filterAttrs(attributes, type, name) { | ||||||
|         this.__validateTypeName(type, name); |         this.__validateTypeName(type, name); | ||||||
|  |  | ||||||
| @@ -527,7 +547,9 @@ class FNote { | |||||||
|      * @returns {boolean} true if note has an attribute with given type and name (including inherited) |      * @returns {boolean} true if note has an attribute with given type and name (including inherited) | ||||||
|      */ |      */ | ||||||
|     hasAttribute(type, name) { |     hasAttribute(type, name) { | ||||||
|         return !!this.getAttribute(type, name); |         const attributes = this.getAttributes(); | ||||||
|  |  | ||||||
|  |         return attributes.some(attr => attr.name === name && attr.type === type); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -227,7 +227,7 @@ async function cloneNoteToBranch(childNoteId, parentBranchId, prefix) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function cloneNoteToNote(childNoteId, parentNoteId, prefix) { | async function cloneNoteToParentNote(childNoteId, parentNoteId, prefix) { | ||||||
|     const resp = await server.put(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, { |     const resp = await server.put(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, { | ||||||
|         prefix: prefix |         prefix: prefix | ||||||
|     }); |     }); | ||||||
| @@ -254,5 +254,5 @@ export default { | |||||||
|     moveNodeUpInHierarchy, |     moveNodeUpInHierarchy, | ||||||
|     cloneNoteAfter, |     cloneNoteAfter, | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToParentNote, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ import server from "./server.js"; | |||||||
| import appContext from "../components/app_context.js"; | import appContext from "../components/app_context.js"; | ||||||
| import utils from './utils.js'; | import utils from './utils.js'; | ||||||
| import noteCreateService from './note_create.js'; | import noteCreateService from './note_create.js'; | ||||||
| import treeService from './tree.js'; |  | ||||||
| import froca from "./froca.js"; | import froca from "./froca.js"; | ||||||
|  |  | ||||||
| // this key needs to have this value, so it's hit by the tooltip | // this key needs to have this value, so it's hit by the tooltip | ||||||
| @@ -188,7 +187,8 @@ function initNoteAutocomplete($el, options) { | |||||||
|                 templateNoteId: templateNoteId |                 templateNoteId: templateNoteId | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|             suggestion.notePath = treeService.getSomeNotePath(note); |             const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|  |             suggestion.notePath = note.getBestNotePathString(hoistedNoteId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $el.setSelectedNotePath(suggestion.notePath); |         $el.setSelectedNotePath(suggestion.notePath); | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import froca from "./froca.js"; | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import attributeRenderer from "./attribute_renderer.js"; | import attributeRenderer from "./attribute_renderer.js"; | ||||||
| import noteContentRenderer from "./note_content_renderer.js"; | import noteContentRenderer from "./note_content_renderer.js"; | ||||||
|  | import appContext from "../components/app_context.js"; | ||||||
|  |  | ||||||
| function setupGlobalTooltip() { | function setupGlobalTooltip() { | ||||||
|     $(document).on("mouseenter", "a", mouseEnterHandler); |     $(document).on("mouseenter", "a", mouseEnterHandler); | ||||||
| @@ -83,13 +84,14 @@ async function renderTooltip(note) { | |||||||
|         return '<div>Note has been deleted.</div>'; |         return '<div>Note has been deleted.</div>'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const someNotePath = treeService.getSomeNotePath(note); |     const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|  |     const bestNotePath = note.getBestNotePathString(hoistedNoteId); | ||||||
|  |  | ||||||
|     if (!someNotePath) { |     if (!bestNotePath) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(someNotePath)).prop('outerHTML')}</h5>`; |     let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(bestNotePath)).prop('outerHTML')}</h5>`; | ||||||
|  |  | ||||||
|     const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note); |     const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -79,14 +79,10 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr | |||||||
|                         You can ignore this message as it is mostly harmless.`); |                         You can ignore this message as it is mostly harmless.`); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 const someNotePath = getSomeNotePath(child, hoistedNoteId); |                 const bestNotePath = child.getBestNotePath(hoistedNoteId); | ||||||
|  |  | ||||||
|                 if (someNotePath) { // in case it's root the path may be empty |                 if (bestNotePath) { | ||||||
|                     const pathToRoot = someNotePath.split("/").reverse().slice(1); |                     const pathToRoot = bestNotePath.reverse().slice(1); | ||||||
|  |  | ||||||
|                     if (!pathToRoot.includes("root")) { |  | ||||||
|                         pathToRoot.push('root'); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     for (const noteId of pathToRoot) { |                     for (const noteId of pathToRoot) { | ||||||
|                         effectivePathSegments.push(noteId); |                         effectivePathSegments.push(noteId); | ||||||
| @@ -109,31 +105,17 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr | |||||||
|     else { |     else { | ||||||
|         const note = await froca.getNote(getNoteIdFromNotePath(notePath)); |         const note = await froca.getNote(getNoteIdFromNotePath(notePath)); | ||||||
|  |  | ||||||
|         const someNotePathSegments = getSomeNotePathSegments(note, hoistedNoteId); |         const bestNotePath = note.getBestNotePath(hoistedNoteId); | ||||||
|  |  | ||||||
|         if (!someNotePathSegments) { |         if (!bestNotePath) { | ||||||
|             throw new Error(`Did not find any path segments for ${note.toString()}, hoisted note ${hoistedNoteId}`); |             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 |         // if there isn't actually any note path with hoisted note then return the original resolved note path | ||||||
|         return someNotePathSegments.includes(hoistedNoteId) ? someNotePathSegments : effectivePathSegments; |         return bestNotePath.includes(hoistedNoteId) ? bestNotePath : 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 => { | ws.subscribeToMessages(message => { | ||||||
|    if (message.type === 'openNote') { |    if (message.type === 'openNote') { | ||||||
|        appContext.tabManager.activateOrOpenNote(message.noteId); |        appContext.tabManager.activateOrOpenNote(message.noteId); | ||||||
| @@ -311,16 +293,6 @@ function isNotePathInAddress() { | |||||||
|         || (notePath === '' && !!ntxId); |         || (notePath === '' && !!ntxId); | ||||||
| } | } | ||||||
|  |  | ||||||
| function parseNotePath(notePath) { |  | ||||||
|     let noteIds = notePath.split('/'); |  | ||||||
|  |  | ||||||
|     if (noteIds[0] !== 'root') { |  | ||||||
|         noteIds = ['root'].concat(noteIds); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return noteIds; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function isNotePathInHiddenSubtree(notePath) { | function isNotePathInHiddenSubtree(notePath) { | ||||||
|     return notePath?.includes("root/_hidden"); |     return notePath?.includes("root/_hidden"); | ||||||
| } | } | ||||||
| @@ -328,8 +300,6 @@ function isNotePathInHiddenSubtree(notePath) { | |||||||
| export default { | export default { | ||||||
|     resolveNotePath, |     resolveNotePath, | ||||||
|     resolveNotePathToSegments, |     resolveNotePathToSegments, | ||||||
|     getSomeNotePath, |  | ||||||
|     getSomeNotePathSegments, |  | ||||||
|     getParentProtectedStatus, |     getParentProtectedStatus, | ||||||
|     getNotePath, |     getNotePath, | ||||||
|     getNoteIdFromNotePath, |     getNoteIdFromNotePath, | ||||||
| @@ -340,6 +310,5 @@ export default { | |||||||
|     getNoteTitleWithPathAsSuffix, |     getNoteTitleWithPathAsSuffix, | ||||||
|     getHashValueFromAddress, |     getHashValueFromAddress, | ||||||
|     isNotePathInAddress, |     isNotePathInAddress, | ||||||
|     parseNotePath, |  | ||||||
|     isNotePathInHiddenSubtree |     isNotePathInHiddenSubtree | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import linkService from "../../services/link.js"; | import linkService from "../../services/link.js"; | ||||||
| import attributeAutocompleteService from "../../services/attribute_autocomplete.js"; | import attributeAutocompleteService from "../../services/attribute_autocomplete.js"; | ||||||
| import noteAutocompleteService from "../../services/note_autocomplete.js"; | import noteAutocompleteService from "../../services/note_autocomplete.js"; | ||||||
| @@ -9,6 +8,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js"; | |||||||
| import SpacedUpdate from "../../services/spaced_update.js"; | import SpacedUpdate from "../../services/spaced_update.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import shortcutService from "../../services/shortcuts.js"; | import shortcutService from "../../services/shortcuts.js"; | ||||||
|  | import appContext from "../../components/app_context.js"; | ||||||
|  |  | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="attr-detail"> | <div class="attr-detail"> | ||||||
| @@ -598,9 +598,10 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|             const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES); |             const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES); | ||||||
|             const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId)); |             const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId)); | ||||||
|  |             const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|  |  | ||||||
|             for (const note of displayedNotes) { |             for (const note of displayedNotes) { | ||||||
|                 const notePath = treeService.getSomeNotePath(note); |                 const notePath = note.getBestNotePathString(hoistedNoteId); | ||||||
|                 const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true}); |                 const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true}); | ||||||
|  |  | ||||||
|                 this.$relatedNotesList.append( |                 this.$relatedNotesList.append( | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import libraryLoader from "../../services/library_loader.js"; | |||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import attributeRenderer from "../../services/attribute_renderer.js"; | import attributeRenderer from "../../services/attribute_renderer.js"; | ||||||
| import noteCreateService from "../../services/note_create.js"; | import noteCreateService from "../../services/note_create.js"; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import attributeService from "../../services/attributes.js"; | import attributeService from "../../services/attributes.js"; | ||||||
|  |  | ||||||
| const HELP_TEXT = ` | const HELP_TEXT = ` | ||||||
| @@ -503,7 +502,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget { | |||||||
|             title: title |             title: title | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         return treeService.getSomeNotePath(note); |         return note.getBestNotePathString(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async updateAttributeList(attributes) { |     async updateAttributeList(attributes) { | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import linkService from '../../services/link.js'; | import linkService from '../../services/link.js'; | ||||||
| import utils from '../../services/utils.js'; | import utils from '../../services/utils.js'; | ||||||
| import server from '../../services/server.js'; | import server from '../../services/server.js'; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import appContext from "../../components/app_context.js"; | import appContext from "../../components/app_context.js"; | ||||||
| import hoistedNoteService from "../../services/hoisted_note.js"; | import hoistedNoteService from "../../services/hoisted_note.js"; | ||||||
| @@ -108,7 +107,7 @@ export default class RecentChangesDialog extends BasicWidget { | |||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     const note = await froca.getNote(change.noteId); |                     const note = await froca.getNote(change.noteId); | ||||||
|                     const notePath = treeService.getSomeNotePath(note); |                     const notePath = note.getBestNotePathString(); | ||||||
|  |  | ||||||
|                     if (notePath) { |                     if (notePath) { | ||||||
|                         $noteLink = await linkService.createNoteLink(notePath, { |                         $noteLink = await linkService.createNoteLink(notePath, { | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const sortedNotePaths = this.note.getSortedNotePaths(this.hoistedNoteId) |         const sortedNotePaths = this.note.getSortedNotePathRecords(this.hoistedNoteId) | ||||||
|             .filter(notePath => !notePath.isHidden); |             .filter(notePath => !notePath.isHidden); | ||||||
|  |  | ||||||
|         if (sortedNotePaths.length > 0) { |         if (sortedNotePaths.length > 0) { | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ export default class SharedSwitchWidget extends SwitchWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async switchOn() { |     async switchOn() { | ||||||
|         await branchService.cloneNoteToNote(this.noteId, '_share'); |         await branchService.cloneNoteToParentNote(this.noteId, '_share'); | ||||||
|  |  | ||||||
|         syncService.syncNow(true); |         syncService.syncNow(true); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import mimeTypesService from '../../services/mime_types.js'; | |||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import keyboardActionService from "../../services/keyboard_actions.js"; | import keyboardActionService from "../../services/keyboard_actions.js"; | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import noteCreateService from "../../services/note_create.js"; | import noteCreateService from "../../services/note_create.js"; | ||||||
| import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | ||||||
| import link from "../../services/link.js"; | import link from "../../services/link.js"; | ||||||
| @@ -378,7 +377,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return treeService.getSomeNotePath(resp.note); |         return resp.note.getBestNotePathString(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async refreshIncludedNoteEvent({noteId}) { |     async refreshIncludedNoteEvent({noteId}) { | ||||||
|   | |||||||
| @@ -9,11 +9,11 @@ function cloneNoteToBranch(req) { | |||||||
|     return cloningService.cloneNoteToBranch(noteId, parentBranchId, prefix); |     return cloningService.cloneNoteToBranch(noteId, parentBranchId, prefix); | ||||||
| } | } | ||||||
|  |  | ||||||
| function cloneNoteToNote(req) { | function cloneNoteToParentNote(req) { | ||||||
|     const {noteId, parentNoteId} = req.params; |     const {noteId, parentNoteId} = req.params; | ||||||
|     const {prefix} = req.body; |     const {prefix} = req.body; | ||||||
|  |  | ||||||
|     return cloningService.cloneNoteToNote(noteId, parentNoteId, prefix); |     return cloningService.cloneNoteToParentNote(noteId, parentNoteId, prefix); | ||||||
| } | } | ||||||
|  |  | ||||||
| function cloneNoteAfter(req) { | function cloneNoteAfter(req) { | ||||||
| @@ -30,7 +30,7 @@ function toggleNoteInParent(req) { | |||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToParentNote, | ||||||
|     cloneNoteAfter, |     cloneNoteAfter, | ||||||
|     toggleNoteInParent |     toggleNoteInParent | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -143,7 +143,7 @@ function register(app) { | |||||||
|  |  | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch); |     apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/toggle-in-parent/:parentNoteId/:present', cloningApiRoute.toggleNoteInParent); |     apiRoute(PUT, '/api/notes/:noteId/toggle-in-parent/:parentNoteId/:present', cloningApiRoute.toggleNoteInParent); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToNote); |     apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToParentNote); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); |     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); | ||||||
|  |  | ||||||
|     route(GET, '/api/notes/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); |     route(GET, '/api/notes/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ const ACTION_HANDLERS = { | |||||||
|         let res; |         let res; | ||||||
|  |  | ||||||
|         if (note.getParentBranches().length > 1) { |         if (note.getParentBranches().length > 1) { | ||||||
|             res = cloningService.cloneNoteToNote(note.noteId, action.targetParentNoteId); |             res = cloningService.cloneNoteToParentNote(note.noteId, action.targetParentNoteId); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             res = branchService.moveBranchToNote(note.getParentBranches()[0], action.targetParentNoteId); |             res = branchService.moveBranchToNote(note.getParentBranches()[0], action.targetParentNoteId); | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ const becca = require("../becca/becca"); | |||||||
| const beccaService = require("../becca/becca_service"); | const beccaService = require("../becca/becca_service"); | ||||||
| const log = require("./log"); | const log = require("./log"); | ||||||
|  |  | ||||||
| function cloneNoteToNote(noteId, parentNoteId, prefix) { | function cloneNoteToParentNote(noteId, parentNoteId, prefix) { | ||||||
|     const parentNote = becca.getNote(parentNoteId); |     const parentNote = becca.getNote(parentNoteId); | ||||||
|  |  | ||||||
|     if (parentNote.type === 'search') { |     if (parentNote.type === 'search') { | ||||||
| @@ -19,7 +19,7 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) { |     if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) { | ||||||
|         return { success: false, message: 'Note is deleted.' }; |         return { success: false, message: 'Note cannot be cloned because either the cloned note or the intended parent is deleted.' }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const validationResult = treeService.validateParentChild(parentNoteId, noteId); |     const validationResult = treeService.validateParentChild(parentNoteId, noteId); | ||||||
| @@ -35,7 +35,7 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) { | |||||||
|         isExpanded: 0 |         isExpanded: 0 | ||||||
|     }).save(); |     }).save(); | ||||||
|  |  | ||||||
|     log.info(`Cloned note '${noteId}' to new parent note '${parentNoteId}' with prefix '${prefix}'`); |     log.info(`Cloned note '${noteId}' to a new parent note '${parentNoteId}' with prefix '${prefix}'`); | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         success: true, |         success: true, | ||||||
| @@ -51,7 +51,7 @@ function cloneNoteToBranch(noteId, parentBranchId, prefix) { | |||||||
|         return { success: false, message: `Parent branch ${parentBranchId} does not exist.` }; |         return { success: false, message: `Parent branch ${parentBranchId} does not exist.` }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const ret = cloneNoteToNote(noteId, parentBranch.noteId, prefix); |     const ret = cloneNoteToParentNote(noteId, parentBranch.noteId, prefix); | ||||||
|  |  | ||||||
|     parentBranch.isExpanded = true; // the new target should be expanded, so it immediately shows up to the user |     parentBranch.isExpanded = true; // the new target should be expanded, so it immediately shows up to the user | ||||||
|     parentBranch.save(); |     parentBranch.save(); | ||||||
| @@ -182,7 +182,7 @@ function isNoteDeleted(noteId) { | |||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToParentNote, | ||||||
|     ensureNoteIsPresentInParent, |     ensureNoteIsPresentInParent, | ||||||
|     ensureNoteIsAbsentFromParent, |     ensureNoteIsAbsentFromParent, | ||||||
|     toggleNoteInParent, |     toggleNoteInParent, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user