From 7aa26580ba8b0efb2687343f5ed00ab070fb6278 Mon Sep 17 00:00:00 2001 From: zadam Date: Sat, 15 Apr 2023 00:06:13 +0200 Subject: [PATCH] refactoring of "some path" WIP --- src/becca/becca_service.js | 50 +++++++--- src/becca/entities/bnote.js | 70 +++++++++++-- src/public/app/entities/fnote.js | 98 ++++++++++++------- src/public/app/services/branches.js | 4 +- src/public/app/services/note_autocomplete.js | 4 +- src/public/app/services/note_tooltip.js | 8 +- src/public/app/services/tree.js | 45 ++------- .../attribute_widgets/attribute_detail.js | 5 +- .../attribute_widgets/attribute_editor.js | 3 +- .../app/widgets/dialogs/recent_changes.js | 3 +- .../app/widgets/ribbon_widgets/note_paths.js | 2 +- src/public/app/widgets/shared_switch.js | 2 +- .../app/widgets/type_widgets/editable_text.js | 3 +- src/routes/api/cloning.js | 6 +- src/routes/routes.js | 2 +- src/services/bulk_actions.js | 2 +- src/services/cloning.js | 10 +- 17 files changed, 193 insertions(+), 124 deletions(-) diff --git a/src/becca/becca_service.js b/src/becca/becca_service.js index 80942c564..47e7d39e2 100644 --- a/src/becca/becca_service.js +++ b/src/becca/becca_service.js @@ -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 * - 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 @@ -136,20 +136,20 @@ function getSomePath(note, path = []) { /** * @param {BNote} note - * @param {string[]} path - * @param {boolean}respectHoisting + * @param {string[]} parentPath + * @param {boolean} respectHoisting * @returns {string[]|false} */ -function getSomePathInner(note, path, respectHoisting) { +function getSomePathInner(note, parentPath, respectHoisting) { + const childPath = [...parentPath, note.noteId]; if (note.isRoot()) { - const foundPath = [...path, note.noteId]; - foundPath.reverse(); + childPath.reverse(); - if (respectHoisting && !foundPath.includes(cls.getHoistedNoteId())) { + if (respectHoisting && !childPath.includes(cls.getHoistedNoteId())) { return false; } - return foundPath; + return childPath; } const parents = note.parents; @@ -159,15 +159,35 @@ function getSomePathInner(note, path, respectHoisting) { return false; } - for (const parentNote of parents) { - const retPath = getSomePathInner(parentNote, [...path, note.noteId], respectHoisting); + const completeNotePaths = parents.map(parentNote => getSomePathInner(parentNote, childPath, respectHoisting)); - if (retPath) { - return retPath; - } + if (completeNotePaths.length === 0) { + 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; } - - return false; } function getNotePath(noteId) { diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 2e9febadb..625ba1f4b 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -12,6 +12,7 @@ const TaskContext = require("../../services/task_context"); const dayjs = require("dayjs"); const utc = require('dayjs/plugin/utc'); const eventService = require("../../services/events"); +const froca = require("../../public/app/services/froca.js"); dayjs.extend(utc); 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) */ getAllNotePaths() { @@ -1157,18 +1160,73 @@ class BNote extends AbstractBeccaEntity { return [['root']]; } - const notePaths = []; + const parentNotes = this.getParentNotes(); + let notePaths = []; - for (const parentNote of this.getParentNotes()) { - for (const parentPath of parentNote.getAllNotePaths()) { - parentPath.push(this.noteId); - notePaths.push(parentPath); - } + if (parentNotes.length === 1) { // optimization for most common case + notePaths = parentNotes[0].getAllNotePaths(); + } else { + notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); + } + + for (const notePath of notePaths) { + notePath.push(this.noteId); } 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 */ diff --git a/src/public/app/entities/fnote.js b/src/public/app/entities/fnote.js index 1066a9341..ad442e999 100644 --- a/src/public/app/entities/fnote.js +++ b/src/public/app/entities/fnote.js @@ -247,6 +247,11 @@ class FNote { return this.__filterAttrs(this.__getCachedAttributes([]), type, name); } + /** + * @param {string[]} path + * @return {FAttribute[]} + * @private + */ __getCachedAttributes(path) { // 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 @@ -299,63 +304,49 @@ class FNote { 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') { return [['root']]; } - if (!encounteredNoteIds) { - encounteredNoteIds = new Set(); - } - - encounteredNoteIds.add(this.noteId); - const parentNotes = this.getParentNotes(); - let paths; + let notePaths = []; - if (parentNotes.length === 1) { // optimization for the most common case - if (encounteredNoteIds.has(parentNotes[0].noteId)) { - return []; - } - else { - paths = parentNotes[0].getAllNotePaths(encounteredNoteIds); - } - } - else { - paths = []; - - for (const parentNote of parentNotes) { - if (encounteredNoteIds.has(parentNote.noteId)) { - continue; - } - - const newSet = new Set(encounteredNoteIds); - - paths.push(...parentNote.getAllNotePaths(newSet)); - } + if (parentNotes.length === 1) { // optimization for most common case + notePaths = parentNotes[0].getAllNotePaths(); + } else { + notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); } - for (const path of paths) { - path.push(this.noteId); + for (const notePath of notePaths) { + notePath.push(this.noteId); } - return paths; + return notePaths; } - getSortedNotePaths(hoistedNotePath = 'root') { + /** + * @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: path.includes(hoistedNotePath), - isArchived: path.find(noteId => froca.notes[noteId].isArchived), - isSearch: path.find(noteId => froca.notes[noteId].type === 'search'), + 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.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) { @@ -368,6 +359,28 @@ class FNote { 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 */ @@ -391,6 +404,13 @@ class FNote { return true; } + /** + * @param {FAttribute[]} attributes + * @param {string} type + * @param {string} name + * @return {FAttribute[]} + * @private + */ __filterAttrs(attributes, 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) */ hasAttribute(type, name) { - return !!this.getAttribute(type, name); + const attributes = this.getAttributes(); + + return attributes.some(attr => attr.name === name && attr.type === type); } /** diff --git a/src/public/app/services/branches.js b/src/public/app/services/branches.js index c414cf257..b0cf129a2 100644 --- a/src/public/app/services/branches.js +++ b/src/public/app/services/branches.js @@ -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}`, { prefix: prefix }); @@ -254,5 +254,5 @@ export default { moveNodeUpInHierarchy, cloneNoteAfter, cloneNoteToBranch, - cloneNoteToNote, + cloneNoteToParentNote, }; diff --git a/src/public/app/services/note_autocomplete.js b/src/public/app/services/note_autocomplete.js index 07c75d447..c548d6f0f 100644 --- a/src/public/app/services/note_autocomplete.js +++ b/src/public/app/services/note_autocomplete.js @@ -2,7 +2,6 @@ import server from "./server.js"; import appContext from "../components/app_context.js"; import utils from './utils.js'; import noteCreateService from './note_create.js'; -import treeService from './tree.js'; import froca from "./froca.js"; // this key needs to have this value, so it's hit by the tooltip @@ -188,7 +187,8 @@ function initNoteAutocomplete($el, options) { templateNoteId: templateNoteId }); - suggestion.notePath = treeService.getSomeNotePath(note); + const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; + suggestion.notePath = note.getBestNotePathString(hoistedNoteId); } $el.setSelectedNotePath(suggestion.notePath); diff --git a/src/public/app/services/note_tooltip.js b/src/public/app/services/note_tooltip.js index b83296cd8..ceff12b49 100644 --- a/src/public/app/services/note_tooltip.js +++ b/src/public/app/services/note_tooltip.js @@ -4,6 +4,7 @@ import froca from "./froca.js"; import utils from "./utils.js"; import attributeRenderer from "./attribute_renderer.js"; import noteContentRenderer from "./note_content_renderer.js"; +import appContext from "../components/app_context.js"; function setupGlobalTooltip() { $(document).on("mouseenter", "a", mouseEnterHandler); @@ -83,13 +84,14 @@ async function renderTooltip(note) { return '
Note has been deleted.
'; } - const someNotePath = treeService.getSomeNotePath(note); + const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; + const bestNotePath = note.getBestNotePathString(hoistedNoteId); - if (!someNotePath) { + if (!bestNotePath) { return; } - let content = `
${(await treeService.getNoteTitleWithPathAsSuffix(someNotePath)).prop('outerHTML')}
`; + let content = `
${(await treeService.getNoteTitleWithPathAsSuffix(bestNotePath)).prop('outerHTML')}
`; const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note); diff --git a/src/public/app/services/tree.js b/src/public/app/services/tree.js index 3c4925f3e..88f13cdde 100644 --- a/src/public/app/services/tree.js +++ b/src/public/app/services/tree.js @@ -79,14 +79,10 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr 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 - const pathToRoot = someNotePath.split("/").reverse().slice(1); - - if (!pathToRoot.includes("root")) { - pathToRoot.push('root'); - } + if (bestNotePath) { + const pathToRoot = bestNotePath.reverse().slice(1); for (const noteId of pathToRoot) { effectivePathSegments.push(noteId); @@ -109,31 +105,17 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr else { const note = await froca.getNote(getNoteIdFromNotePath(notePath)); - const someNotePathSegments = getSomeNotePathSegments(note, hoistedNoteId); + const bestNotePath = note.getBestNotePath(hoistedNoteId); - if (!someNotePathSegments) { - throw new Error(`Did not find any path segments for ${note.toString()}, hoisted note ${hoistedNoteId}`); + 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 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 => { if (message.type === 'openNote') { appContext.tabManager.activateOrOpenNote(message.noteId); @@ -311,16 +293,6 @@ function isNotePathInAddress() { || (notePath === '' && !!ntxId); } -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"); } @@ -328,8 +300,6 @@ function isNotePathInHiddenSubtree(notePath) { export default { resolveNotePath, resolveNotePathToSegments, - getSomeNotePath, - getSomeNotePathSegments, getParentProtectedStatus, getNotePath, getNoteIdFromNotePath, @@ -340,6 +310,5 @@ export default { getNoteTitleWithPathAsSuffix, getHashValueFromAddress, isNotePathInAddress, - parseNotePath, isNotePathInHiddenSubtree }; diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.js b/src/public/app/widgets/attribute_widgets/attribute_detail.js index 80efc31ba..dba64da70 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_detail.js +++ b/src/public/app/widgets/attribute_widgets/attribute_detail.js @@ -1,6 +1,5 @@ import server from "../../services/server.js"; import froca from "../../services/froca.js"; -import treeService from "../../services/tree.js"; import linkService from "../../services/link.js"; import attributeAutocompleteService from "../../services/attribute_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 utils from "../../services/utils.js"; import shortcutService from "../../services/shortcuts.js"; +import appContext from "../../components/app_context.js"; const TPL = `
@@ -598,9 +598,10 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES); const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId)); + const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; for (const note of displayedNotes) { - const notePath = treeService.getSomeNotePath(note); + const notePath = note.getBestNotePathString(hoistedNoteId); const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true}); this.$relatedNotesList.append( diff --git a/src/public/app/widgets/attribute_widgets/attribute_editor.js b/src/public/app/widgets/attribute_widgets/attribute_editor.js index 217717f05..88d3e837a 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_editor.js +++ b/src/public/app/widgets/attribute_widgets/attribute_editor.js @@ -7,7 +7,6 @@ import libraryLoader from "../../services/library_loader.js"; import froca from "../../services/froca.js"; import attributeRenderer from "../../services/attribute_renderer.js"; import noteCreateService from "../../services/note_create.js"; -import treeService from "../../services/tree.js"; import attributeService from "../../services/attributes.js"; const HELP_TEXT = ` @@ -503,7 +502,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget { title: title }); - return treeService.getSomeNotePath(note); + return note.getBestNotePathString(); } async updateAttributeList(attributes) { diff --git a/src/public/app/widgets/dialogs/recent_changes.js b/src/public/app/widgets/dialogs/recent_changes.js index 75b477276..21d38d016 100644 --- a/src/public/app/widgets/dialogs/recent_changes.js +++ b/src/public/app/widgets/dialogs/recent_changes.js @@ -1,7 +1,6 @@ import linkService from '../../services/link.js'; import utils from '../../services/utils.js'; import server from '../../services/server.js'; -import treeService from "../../services/tree.js"; import froca from "../../services/froca.js"; import appContext from "../../components/app_context.js"; import hoistedNoteService from "../../services/hoisted_note.js"; @@ -108,7 +107,7 @@ export default class RecentChangesDialog extends BasicWidget { } } else { const note = await froca.getNote(change.noteId); - const notePath = treeService.getSomeNotePath(note); + const notePath = note.getBestNotePathString(); if (notePath) { $noteLink = await linkService.createNoteLink(notePath, { diff --git a/src/public/app/widgets/ribbon_widgets/note_paths.js b/src/public/app/widgets/ribbon_widgets/note_paths.js index 3270e0eb4..7a6977b17 100644 --- a/src/public/app/widgets/ribbon_widgets/note_paths.js +++ b/src/public/app/widgets/ribbon_widgets/note_paths.js @@ -72,7 +72,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget { return; } - const sortedNotePaths = this.note.getSortedNotePaths(this.hoistedNoteId) + const sortedNotePaths = this.note.getSortedNotePathRecords(this.hoistedNoteId) .filter(notePath => !notePath.isHidden); if (sortedNotePaths.length > 0) { diff --git a/src/public/app/widgets/shared_switch.js b/src/public/app/widgets/shared_switch.js index 61dd140db..a7aaa1e52 100644 --- a/src/public/app/widgets/shared_switch.js +++ b/src/public/app/widgets/shared_switch.js @@ -25,7 +25,7 @@ export default class SharedSwitchWidget extends SwitchWidget { } async switchOn() { - await branchService.cloneNoteToNote(this.noteId, '_share'); + await branchService.cloneNoteToParentNote(this.noteId, '_share'); syncService.syncNow(true); } diff --git a/src/public/app/widgets/type_widgets/editable_text.js b/src/public/app/widgets/type_widgets/editable_text.js index aa1015242..2c6cf55f2 100644 --- a/src/public/app/widgets/type_widgets/editable_text.js +++ b/src/public/app/widgets/type_widgets/editable_text.js @@ -4,7 +4,6 @@ import mimeTypesService from '../../services/mime_types.js'; import utils from "../../services/utils.js"; import keyboardActionService from "../../services/keyboard_actions.js"; import froca from "../../services/froca.js"; -import treeService from "../../services/tree.js"; import noteCreateService from "../../services/note_create.js"; import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; import link from "../../services/link.js"; @@ -378,7 +377,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { return; } - return treeService.getSomeNotePath(resp.note); + return resp.note.getBestNotePathString(); } async refreshIncludedNoteEvent({noteId}) { diff --git a/src/routes/api/cloning.js b/src/routes/api/cloning.js index da557715c..75a42e675 100644 --- a/src/routes/api/cloning.js +++ b/src/routes/api/cloning.js @@ -9,11 +9,11 @@ function cloneNoteToBranch(req) { return cloningService.cloneNoteToBranch(noteId, parentBranchId, prefix); } -function cloneNoteToNote(req) { +function cloneNoteToParentNote(req) { const {noteId, parentNoteId} = req.params; const {prefix} = req.body; - return cloningService.cloneNoteToNote(noteId, parentNoteId, prefix); + return cloningService.cloneNoteToParentNote(noteId, parentNoteId, prefix); } function cloneNoteAfter(req) { @@ -30,7 +30,7 @@ function toggleNoteInParent(req) { module.exports = { cloneNoteToBranch, - cloneNoteToNote, + cloneNoteToParentNote, cloneNoteAfter, toggleNoteInParent }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 9cc87bb37..a52a067f5 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -143,7 +143,7 @@ function register(app) { 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/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); route(GET, '/api/notes/:branchId/export/:type/:format/:version/:taskId', [auth.checkApiAuthOrElectron], exportRoute.exportBranch); diff --git a/src/services/bulk_actions.js b/src/services/bulk_actions.js index eaa57a1fb..c51ba1d5e 100644 --- a/src/services/bulk_actions.js +++ b/src/services/bulk_actions.js @@ -83,7 +83,7 @@ const ACTION_HANDLERS = { let res; if (note.getParentBranches().length > 1) { - res = cloningService.cloneNoteToNote(note.noteId, action.targetParentNoteId); + res = cloningService.cloneNoteToParentNote(note.noteId, action.targetParentNoteId); } else { res = branchService.moveBranchToNote(note.getParentBranches()[0], action.targetParentNoteId); diff --git a/src/services/cloning.js b/src/services/cloning.js index dad3d3906..b08b3d6b3 100644 --- a/src/services/cloning.js +++ b/src/services/cloning.js @@ -8,7 +8,7 @@ const becca = require("../becca/becca"); const beccaService = require("../becca/becca_service"); const log = require("./log"); -function cloneNoteToNote(noteId, parentNoteId, prefix) { +function cloneNoteToParentNote(noteId, parentNoteId, prefix) { const parentNote = becca.getNote(parentNoteId); if (parentNote.type === 'search') { @@ -19,7 +19,7 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) { } 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); @@ -35,7 +35,7 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) { isExpanded: 0 }).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 { success: true, @@ -51,7 +51,7 @@ function cloneNoteToBranch(noteId, parentBranchId, prefix) { 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.save(); @@ -182,7 +182,7 @@ function isNoteDeleted(noteId) { module.exports = { cloneNoteToBranch, - cloneNoteToNote, + cloneNoteToParentNote, ensureNoteIsPresentInParent, ensureNoteIsAbsentFromParent, toggleNoteInParent,