diff --git a/src/services/hidden_subtree.ts b/src/services/hidden_subtree.ts index e4cdf49bd..211ef1052 100644 --- a/src/services/hidden_subtree.ts +++ b/src/services/hidden_subtree.ts @@ -6,7 +6,7 @@ import noteService from "./notes.js"; import log from "./log.js"; import migrationService from "./migration.js"; import { t } from "i18next"; -import { getHelpHiddenSubtreeData } from "./in_app_help.js"; +import { cleanUpHelp, getHelpHiddenSubtreeData } from "./in_app_help.js"; import buildLaunchBarConfig from "./hidden_subtree_launcherbar.js"; const LBTPL_ROOT = "_lbTplRoot"; @@ -58,7 +58,7 @@ enum Command { let hiddenSubtreeDefinition: HiddenSubtreeItem; -function buildHiddenSubtreeDefinition(): HiddenSubtreeItem { +function buildHiddenSubtreeDefinition(helpSubtree: HiddenSubtreeItem[]): HiddenSubtreeItem { const launchbarConfig = buildLaunchBarConfig(); return { @@ -283,7 +283,7 @@ function buildHiddenSubtreeDefinition(): HiddenSubtreeItem { title: t("hidden-subtree.user-guide"), type: "book", icon: "bx-help-circle", - children: getHelpHiddenSubtreeData(), + children: helpSubtree, isExpanded: true } ] @@ -301,11 +301,19 @@ function checkHiddenSubtree(force = false, extraOpts: CheckHiddenExtraOpts = {}) return; } + const helpSubtree = getHelpHiddenSubtreeData(); if (!hiddenSubtreeDefinition || force) { - hiddenSubtreeDefinition = buildHiddenSubtreeDefinition(); + hiddenSubtreeDefinition = buildHiddenSubtreeDefinition(helpSubtree); } checkHiddenSubtreeRecursively("root", hiddenSubtreeDefinition, extraOpts); + + try { + cleanUpHelp(helpSubtree); + } catch (e) { + // Non-critical operation should something go wrong. + console.error(e); + } } function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtreeItem, extraOpts: CheckHiddenExtraOpts = {}) { diff --git a/src/services/in_app_help.ts b/src/services/in_app_help.ts index fffbf7812..a090993c4 100644 --- a/src/services/in_app_help.ts +++ b/src/services/in_app_help.ts @@ -5,6 +5,8 @@ import type NoteMeta from "./meta/note_meta.js"; import type { NoteMetaFile } from "./meta/note_meta.js"; import { fileURLToPath } from "url"; import { isDev } from "./utils.js"; +import type BNote from "../becca/entities/bnote.js"; +import becca from "../becca/becca.js"; export function getHelpHiddenSubtreeData() { const srcRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."); @@ -98,3 +100,55 @@ function parseNoteMeta(noteMeta: NoteMeta, docNameRoot: string): HiddenSubtreeIt return item; } +/** + * Iterates recursively through the help subtree that the user has and compares it against the definition + * to remove any notes that are no longer present in the latest version of the help. + * + * @param helpDefinition the hidden subtree definition for the help, to compare against the user's structure. + */ +export function cleanUpHelp(helpDefinition: HiddenSubtreeItem[]) { + function getFlatIds(items: HiddenSubtreeItem | HiddenSubtreeItem[]) { + const ids: (string | string[])[] = []; + if (Array.isArray(items)) { + for (const item of items) { + ids.push(getFlatIds(item)); + } + } else { + if (items.children) { + for (const child of items.children) { + ids.push(getFlatIds(child)); + } + } + ids.push(items.id); + } + return ids.flat(); + } + + function getFlatIdsFromNote(note: BNote | null) { + if (!note) { + return []; + } + + const ids: (string | string[])[] = []; + + for (const subnote of note.getChildNotes()) { + ids.push(getFlatIdsFromNote(subnote)); + } + + ids.push(note.noteId); + return ids.flat(); + } + + const definitionHelpIds = new Set(getFlatIds(helpDefinition)); + const realHelpIds = getFlatIdsFromNote(becca.getNote("_help")); + + for (const realHelpId of realHelpIds) { + if (realHelpId === "_help") { + continue; + } + + if (!definitionHelpIds.has(realHelpId)) { + becca.getNote(realHelpId)?.deleteNote(); + } + } +}