Approve-with-comments follow-ups: - breadcrumb: fix the reverse regression where navigating A->B to a page absent from the lazily-built tree (before its ancestors load) left the previous page's clickable chain on screen. New pure computeBreadcrumbState clears a stale chain that doesn't end at the current page, while keeping one that does (no blank flash for an already-resolved page); unit-tested for the navigated-to-absent-page case. - share.service: getShareAncestorPage no longer swallows DB errors silently — now a live public-share path (isPageReachableThroughShare), so a transient error is logged with ancestor/child ids and still fails closed (caller 404s) instead of becoming a traceless misleading "not found". - i18n: register the new "Connecting… (read-only)" key (U+2026 ellipsis) in en-US (source of truth) and ru-RU (Подключение… (только чтение)). - share.service: correct the FUTURE note — 3 callers pass no shareId (share-alias.controller/.service, share-seo.controller); the two ai-chat callers already pass a real shareId. - CHANGELOG: add Unreleased Changed/Fixed/Security entries for #216 opt-in sub-pages default, #218 trimmed page-info payload + forged-shareId 404, #204 export internal-link name, #206/#218 breadcrumb, #192 callout paste, #218 editor pre-sync read-only gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
62 lines
2.5 KiB
TypeScript
62 lines
2.5 KiB
TypeScript
import { IPage } from "@/features/page/types/page.types.ts";
|
|
import { SpaceTreeNode } from "@/features/page/tree/types.ts";
|
|
import { findBreadcrumbPath, pageToTreeNode } from "@/features/page/tree/utils";
|
|
|
|
/**
|
|
* Pure selection/mapping for the breadcrumb nodes (#218). Three branches:
|
|
* 1. tree-hit — the lazily-built sidebar tree already contains this page's
|
|
* ancestor chain, so prefer it (stays live with sidebar renames/moves).
|
|
* 2. tree-miss — fall back to the page's own ancestor data so a deep page
|
|
* resolves immediately instead of rendering a blank breadcrumb for seconds
|
|
* while the tree backfills. Mapped through the canonical `pageToTreeNode`
|
|
* (title -> name, hasChildren defaulted to false).
|
|
* 3. neither — no data yet, return null (the caller decides whether to keep
|
|
* a prior chain via computeBreadcrumbState).
|
|
*/
|
|
export function resolveBreadcrumbNodes(
|
|
treeData: SpaceTreeNode[] | null | undefined,
|
|
ancestors: IPage[] | null | undefined,
|
|
pageId: string,
|
|
): SpaceTreeNode[] | null {
|
|
if (treeData && treeData.length > 0) {
|
|
const breadcrumb = findBreadcrumbPath(treeData, pageId);
|
|
if (breadcrumb) {
|
|
return breadcrumb;
|
|
}
|
|
}
|
|
|
|
if (ancestors && ancestors.length > 0) {
|
|
return ancestors.map((page) =>
|
|
pageToTreeNode(page, { hasChildren: page.hasChildren ?? false }),
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Decide the next breadcrumb state, given the previous one. When a chain
|
|
* resolves (#218) it always wins. When nothing resolves yet, a stale chain from
|
|
* a previously-viewed page must be CLEARED rather than left showing the wrong,
|
|
* clickable trail (the reverse regression of the original blank-breadcrumb fix
|
|
* when navigating A -> B to a deep page not yet in the lazily-built tree). The
|
|
* one chain we keep through a transient miss is one that already ends at the
|
|
* current page — that means we already resolved THIS page, so keeping it avoids
|
|
* a needless blank flash without ever showing the previous page's chain.
|
|
*/
|
|
export function computeBreadcrumbState(
|
|
treeData: SpaceTreeNode[] | null | undefined,
|
|
ancestors: IPage[] | null | undefined,
|
|
pageId: string,
|
|
previous: SpaceTreeNode[] | null,
|
|
): SpaceTreeNode[] | null {
|
|
const resolved = resolveBreadcrumbNodes(treeData, ancestors, pageId);
|
|
if (resolved) {
|
|
return resolved;
|
|
}
|
|
|
|
const previousEndsAtCurrentPage =
|
|
previous != null && previous[previous.length - 1]?.id === pageId;
|
|
return previousEndsAtCurrentPage ? previous : null;
|
|
}
|