@roottale/cms-core 0.65.0 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -207,9 +207,17 @@ declare function validateCollections(collections: readonly RouteCollection[]): s
207
207
  * FAQ·도움말·문서처럼 하나의 모델이 여러 단계 분류 허브를 가지는 경우에 쓴다.
208
208
  * 저장소나 프레임워크를 모르며, 공개 API와 ROOT-ADMIN이 같은 계산을 재사용한다.
209
209
  */
210
+ /**
211
+ * 글을 붙일 수 있는 분류.
212
+ * - `deepest`(기본): `categoryDepth` 단계의 말단 분류에만 붙는다.
213
+ * - `leaf`: 하위 분류가 없는 분류면 단계와 무관하게 붙는다(`categoryDepth`는 최대 단계).
214
+ * 하위가 필요한 영역만 나누고 나머지는 상위 분류에 바로 글을 쓸 때 쓴다.
215
+ */
216
+ type CategoryTreeEntryCategory = "deepest" | "leaf";
210
217
  interface CategoryTreePresentation {
211
218
  readonly basePath: string;
212
219
  readonly categoryDepth: number;
220
+ readonly entryCategory?: CategoryTreeEntryCategory;
213
221
  }
214
222
  interface CategoryTreeTerm {
215
223
  readonly id: string;
@@ -224,10 +232,19 @@ interface CategoryTreeEntry {
224
232
  }>;
225
233
  }
226
234
  /**
227
- * 선택한 말단 분류부터 루트까지의 사슬을 루트→말단 순서로 돌려준다.
228
- * 끊긴 부모·순환·중복 id·깊이 불일치·말단이 아닌 선택은 null로 닫는다.
235
+ * 글 주소에 쓰는 분류의 루트→분류 사슬. 주소를 만들 수 없으면 null.
236
+ *
237
+ * - `deepest`: 정확히 `categoryDepth` 단계의 말단이어야 한다.
238
+ * - `leaf`: `categoryDepth` 이하면 된다. 하위가 없는지는 *새로 고를 때*만 본다
239
+ * (`resolveCategoryTreeSelection`) — 글이 붙은 뒤 하위 분류가 생겨도 이미 발행한
240
+ * 글의 주소는 그대로 살아 있어야 한다.
241
+ */
242
+ declare function resolveCategoryTreeChain(presentation: CategoryTreePresentation, termId: string, terms: readonly CategoryTreeTerm[]): CategoryTreeTerm[] | null;
243
+ /**
244
+ * 글에 새로 고를 수 있는 분류의 사슬 — 주소를 만들 수 있고 지금 하위 분류가 없어야
245
+ * 한다. 편집기 선택지와 발행 검사가 같은 규칙을 쓴다.
229
246
  */
230
- declare function resolveCategoryTreeChain(presentation: CategoryTreePresentation, leafId: string, terms: readonly CategoryTreeTerm[]): CategoryTreeTerm[] | null;
247
+ declare function resolveCategoryTreeSelection(presentation: CategoryTreePresentation, termId: string, terms: readonly CategoryTreeTerm[]): CategoryTreeTerm[] | null;
231
248
  /** 루트부터 선택 분류까지 모든 허브 주소. */
232
249
  declare function resolveCategoryTreeHubPaths(presentation: CategoryTreePresentation, termId: string, terms: readonly CategoryTreeTerm[]): string[] | null;
233
250
  /** 계층형 분류 모델 항목의 정규 상세 주소. */
@@ -258,6 +275,7 @@ type PublicPathPresentation = {
258
275
  readonly kind: "category_tree";
259
276
  readonly basePath: string;
260
277
  readonly categoryDepth: number;
278
+ readonly entryCategory?: CategoryTreeEntryCategory;
261
279
  };
262
280
  interface PublicPathPost {
263
281
  readonly slug: string;
@@ -612,4 +630,4 @@ declare function isSectionContentPropPath(sectionType: string, path: readonly (s
612
630
  declare function findSectionLockViolations(beforeDoc: unknown, afterDoc: unknown): SectionLockViolation[];
613
631
  declare function formatSectionLockViolations(violations: readonly SectionLockViolation[]): string;
614
632
 
615
- export { BLOCK_NAME_PATTERN, type BindingResolver, type Block, type BlockBinding, type BlockDefinition, type BlockRegistry, CMS_FONT_SIZES, CMS_IMAGE_WIDTHS, CMS_LINE_HEIGHTS, CMS_PARAGRAPH_SPACINGS, type CategoryTreeEntry, type CategoryTreePresentation, type CategoryTreeTerm, type ClientHydrationSpec, DEFAULT_SITE_PATTERN_PRESENTATION, EMPTY_SITE_PATTERN_PLACEMENTS, type EmbedProvider, FIELD_BINDING_SOURCE, FIELD_BINDING_SOURCE_ALIAS, type FaqEntry, IMAGE_ALT_MAX_LENGTH, INTERNAL_CONTENT_LINK_CLASS, INTERNAL_CONTENT_LINK_PENDING_ATTR, INTERNAL_CONTENT_LINK_PENDING_CLASS, INTERNAL_CONTENT_LINK_TOKEN_SOURCE, type InternalContentLinkResolver, type InternalContentLinkToken, LEGACY_BLOG_DETAIL_PATH, type NormalizedEmbed, PHASE_1_CORE_BLOCKS, PHASE_1_CSP, POST_FOOTER_SLOT, type PatternSlots, type Phase1CoreBlockName, type PreviewTokenPayload, type PublicPathContext, type PublicPathPost, type PublicPathPresentation, type RoutablePost, type RouteCollection, SITE_PATTERN_BUTTON_COLORS_MAX, SITE_PATTERN_KEY_PATTERN, SITE_PATTERN_PLACEMENTS_KEY, SITE_PATTERN_SLOTS, type SectionLock, type SectionLockViolation, type SectionLockViolationCode, type SitePatternLayout, type SitePatternLinkStyle, type SitePatternPlacementRule, type SitePatternPlacements, type SitePatternPresentation, type SitePatternSlotKey, applyBlockBindings, bindingSrcValue, bindingTextValue, blockSchema, boundFieldValue, chainResolvers, cmsFontSize, cmsImageAlign, cmsImageStyle, cmsImageWidth, cmsLineHeight, cmsParagraphSpacing, createFieldBindingResolver, emptyPatternSlots, ensureImportedHtmlDocumentRoots, ensureStableIds, extractFaqEntries, faqPageJsonLd, findInternalContentLinkTokens, findSectionLockViolations, findSitePatternPlacementRule, formatSectionLockViolations, htmlToPlainText, injectStableIdsTree, internalContentKeyFromPath, internalContentLinkResolverFrom, internalContentPathIndex, isAllowedEmbedSrc, isAllowedMapEmbedSrc, isInCollectionMode, isSectionContentPropPath, isSitePatternSlotKey, isValidBlock, isValidSitePatternKey, issuePreviewToken, normalizeEmbed, normalizeHexColor, normalizeImageAlt, parseBlock, postsForCollection, postsInAnyCollection, postsNotInAnyCollection, readSitePatternPlacements, readSitePatternPresentation, referencedSitePatternKeys, renderInternalContentLinksInHtml, renderInternalContentLinksInText, resolveCategoryPath, resolveCategoryTreeChain, resolveCategoryTreeEntryPath, resolveCategoryTreeHubPaths, resolvePatternSlots, resolvePostCategorySlug, resolvePostCollection, resolvePostPath, resolvePostPublicPath, resolvePostUrl, sanitizeHtml, sanitizeImportedCss, sanitizeImportedHtml, scopeImportedCss, serializeBlock, setSitePatternPlacementRule, sitePatternButtonColor, sitePatternPresentationAttributes, validateCollections, verifyPreviewToken };
633
+ export { BLOCK_NAME_PATTERN, type BindingResolver, type Block, type BlockBinding, type BlockDefinition, type BlockRegistry, CMS_FONT_SIZES, CMS_IMAGE_WIDTHS, CMS_LINE_HEIGHTS, CMS_PARAGRAPH_SPACINGS, type CategoryTreeEntry, type CategoryTreeEntryCategory, type CategoryTreePresentation, type CategoryTreeTerm, type ClientHydrationSpec, DEFAULT_SITE_PATTERN_PRESENTATION, EMPTY_SITE_PATTERN_PLACEMENTS, type EmbedProvider, FIELD_BINDING_SOURCE, FIELD_BINDING_SOURCE_ALIAS, type FaqEntry, IMAGE_ALT_MAX_LENGTH, INTERNAL_CONTENT_LINK_CLASS, INTERNAL_CONTENT_LINK_PENDING_ATTR, INTERNAL_CONTENT_LINK_PENDING_CLASS, INTERNAL_CONTENT_LINK_TOKEN_SOURCE, type InternalContentLinkResolver, type InternalContentLinkToken, LEGACY_BLOG_DETAIL_PATH, type NormalizedEmbed, PHASE_1_CORE_BLOCKS, PHASE_1_CSP, POST_FOOTER_SLOT, type PatternSlots, type Phase1CoreBlockName, type PreviewTokenPayload, type PublicPathContext, type PublicPathPost, type PublicPathPresentation, type RoutablePost, type RouteCollection, SITE_PATTERN_BUTTON_COLORS_MAX, SITE_PATTERN_KEY_PATTERN, SITE_PATTERN_PLACEMENTS_KEY, SITE_PATTERN_SLOTS, type SectionLock, type SectionLockViolation, type SectionLockViolationCode, type SitePatternLayout, type SitePatternLinkStyle, type SitePatternPlacementRule, type SitePatternPlacements, type SitePatternPresentation, type SitePatternSlotKey, applyBlockBindings, bindingSrcValue, bindingTextValue, blockSchema, boundFieldValue, chainResolvers, cmsFontSize, cmsImageAlign, cmsImageStyle, cmsImageWidth, cmsLineHeight, cmsParagraphSpacing, createFieldBindingResolver, emptyPatternSlots, ensureImportedHtmlDocumentRoots, ensureStableIds, extractFaqEntries, faqPageJsonLd, findInternalContentLinkTokens, findSectionLockViolations, findSitePatternPlacementRule, formatSectionLockViolations, htmlToPlainText, injectStableIdsTree, internalContentKeyFromPath, internalContentLinkResolverFrom, internalContentPathIndex, isAllowedEmbedSrc, isAllowedMapEmbedSrc, isInCollectionMode, isSectionContentPropPath, isSitePatternSlotKey, isValidBlock, isValidSitePatternKey, issuePreviewToken, normalizeEmbed, normalizeHexColor, normalizeImageAlt, parseBlock, postsForCollection, postsInAnyCollection, postsNotInAnyCollection, readSitePatternPlacements, readSitePatternPresentation, referencedSitePatternKeys, renderInternalContentLinksInHtml, renderInternalContentLinksInText, resolveCategoryPath, resolveCategoryTreeChain, resolveCategoryTreeEntryPath, resolveCategoryTreeHubPaths, resolveCategoryTreeSelection, resolvePatternSlots, resolvePostCategorySlug, resolvePostCollection, resolvePostPath, resolvePostPublicPath, resolvePostUrl, sanitizeHtml, sanitizeImportedCss, sanitizeImportedHtml, scopeImportedCss, serializeBlock, setSitePatternPlacementRule, sitePatternButtonColor, sitePatternPresentationAttributes, validateCollections, verifyPreviewToken };
package/dist/index.d.ts CHANGED
@@ -207,9 +207,17 @@ declare function validateCollections(collections: readonly RouteCollection[]): s
207
207
  * FAQ·도움말·문서처럼 하나의 모델이 여러 단계 분류 허브를 가지는 경우에 쓴다.
208
208
  * 저장소나 프레임워크를 모르며, 공개 API와 ROOT-ADMIN이 같은 계산을 재사용한다.
209
209
  */
210
+ /**
211
+ * 글을 붙일 수 있는 분류.
212
+ * - `deepest`(기본): `categoryDepth` 단계의 말단 분류에만 붙는다.
213
+ * - `leaf`: 하위 분류가 없는 분류면 단계와 무관하게 붙는다(`categoryDepth`는 최대 단계).
214
+ * 하위가 필요한 영역만 나누고 나머지는 상위 분류에 바로 글을 쓸 때 쓴다.
215
+ */
216
+ type CategoryTreeEntryCategory = "deepest" | "leaf";
210
217
  interface CategoryTreePresentation {
211
218
  readonly basePath: string;
212
219
  readonly categoryDepth: number;
220
+ readonly entryCategory?: CategoryTreeEntryCategory;
213
221
  }
214
222
  interface CategoryTreeTerm {
215
223
  readonly id: string;
@@ -224,10 +232,19 @@ interface CategoryTreeEntry {
224
232
  }>;
225
233
  }
226
234
  /**
227
- * 선택한 말단 분류부터 루트까지의 사슬을 루트→말단 순서로 돌려준다.
228
- * 끊긴 부모·순환·중복 id·깊이 불일치·말단이 아닌 선택은 null로 닫는다.
235
+ * 글 주소에 쓰는 분류의 루트→분류 사슬. 주소를 만들 수 없으면 null.
236
+ *
237
+ * - `deepest`: 정확히 `categoryDepth` 단계의 말단이어야 한다.
238
+ * - `leaf`: `categoryDepth` 이하면 된다. 하위가 없는지는 *새로 고를 때*만 본다
239
+ * (`resolveCategoryTreeSelection`) — 글이 붙은 뒤 하위 분류가 생겨도 이미 발행한
240
+ * 글의 주소는 그대로 살아 있어야 한다.
241
+ */
242
+ declare function resolveCategoryTreeChain(presentation: CategoryTreePresentation, termId: string, terms: readonly CategoryTreeTerm[]): CategoryTreeTerm[] | null;
243
+ /**
244
+ * 글에 새로 고를 수 있는 분류의 사슬 — 주소를 만들 수 있고 지금 하위 분류가 없어야
245
+ * 한다. 편집기 선택지와 발행 검사가 같은 규칙을 쓴다.
229
246
  */
230
- declare function resolveCategoryTreeChain(presentation: CategoryTreePresentation, leafId: string, terms: readonly CategoryTreeTerm[]): CategoryTreeTerm[] | null;
247
+ declare function resolveCategoryTreeSelection(presentation: CategoryTreePresentation, termId: string, terms: readonly CategoryTreeTerm[]): CategoryTreeTerm[] | null;
231
248
  /** 루트부터 선택 분류까지 모든 허브 주소. */
232
249
  declare function resolveCategoryTreeHubPaths(presentation: CategoryTreePresentation, termId: string, terms: readonly CategoryTreeTerm[]): string[] | null;
233
250
  /** 계층형 분류 모델 항목의 정규 상세 주소. */
@@ -258,6 +275,7 @@ type PublicPathPresentation = {
258
275
  readonly kind: "category_tree";
259
276
  readonly basePath: string;
260
277
  readonly categoryDepth: number;
278
+ readonly entryCategory?: CategoryTreeEntryCategory;
261
279
  };
262
280
  interface PublicPathPost {
263
281
  readonly slug: string;
@@ -612,4 +630,4 @@ declare function isSectionContentPropPath(sectionType: string, path: readonly (s
612
630
  declare function findSectionLockViolations(beforeDoc: unknown, afterDoc: unknown): SectionLockViolation[];
613
631
  declare function formatSectionLockViolations(violations: readonly SectionLockViolation[]): string;
614
632
 
615
- export { BLOCK_NAME_PATTERN, type BindingResolver, type Block, type BlockBinding, type BlockDefinition, type BlockRegistry, CMS_FONT_SIZES, CMS_IMAGE_WIDTHS, CMS_LINE_HEIGHTS, CMS_PARAGRAPH_SPACINGS, type CategoryTreeEntry, type CategoryTreePresentation, type CategoryTreeTerm, type ClientHydrationSpec, DEFAULT_SITE_PATTERN_PRESENTATION, EMPTY_SITE_PATTERN_PLACEMENTS, type EmbedProvider, FIELD_BINDING_SOURCE, FIELD_BINDING_SOURCE_ALIAS, type FaqEntry, IMAGE_ALT_MAX_LENGTH, INTERNAL_CONTENT_LINK_CLASS, INTERNAL_CONTENT_LINK_PENDING_ATTR, INTERNAL_CONTENT_LINK_PENDING_CLASS, INTERNAL_CONTENT_LINK_TOKEN_SOURCE, type InternalContentLinkResolver, type InternalContentLinkToken, LEGACY_BLOG_DETAIL_PATH, type NormalizedEmbed, PHASE_1_CORE_BLOCKS, PHASE_1_CSP, POST_FOOTER_SLOT, type PatternSlots, type Phase1CoreBlockName, type PreviewTokenPayload, type PublicPathContext, type PublicPathPost, type PublicPathPresentation, type RoutablePost, type RouteCollection, SITE_PATTERN_BUTTON_COLORS_MAX, SITE_PATTERN_KEY_PATTERN, SITE_PATTERN_PLACEMENTS_KEY, SITE_PATTERN_SLOTS, type SectionLock, type SectionLockViolation, type SectionLockViolationCode, type SitePatternLayout, type SitePatternLinkStyle, type SitePatternPlacementRule, type SitePatternPlacements, type SitePatternPresentation, type SitePatternSlotKey, applyBlockBindings, bindingSrcValue, bindingTextValue, blockSchema, boundFieldValue, chainResolvers, cmsFontSize, cmsImageAlign, cmsImageStyle, cmsImageWidth, cmsLineHeight, cmsParagraphSpacing, createFieldBindingResolver, emptyPatternSlots, ensureImportedHtmlDocumentRoots, ensureStableIds, extractFaqEntries, faqPageJsonLd, findInternalContentLinkTokens, findSectionLockViolations, findSitePatternPlacementRule, formatSectionLockViolations, htmlToPlainText, injectStableIdsTree, internalContentKeyFromPath, internalContentLinkResolverFrom, internalContentPathIndex, isAllowedEmbedSrc, isAllowedMapEmbedSrc, isInCollectionMode, isSectionContentPropPath, isSitePatternSlotKey, isValidBlock, isValidSitePatternKey, issuePreviewToken, normalizeEmbed, normalizeHexColor, normalizeImageAlt, parseBlock, postsForCollection, postsInAnyCollection, postsNotInAnyCollection, readSitePatternPlacements, readSitePatternPresentation, referencedSitePatternKeys, renderInternalContentLinksInHtml, renderInternalContentLinksInText, resolveCategoryPath, resolveCategoryTreeChain, resolveCategoryTreeEntryPath, resolveCategoryTreeHubPaths, resolvePatternSlots, resolvePostCategorySlug, resolvePostCollection, resolvePostPath, resolvePostPublicPath, resolvePostUrl, sanitizeHtml, sanitizeImportedCss, sanitizeImportedHtml, scopeImportedCss, serializeBlock, setSitePatternPlacementRule, sitePatternButtonColor, sitePatternPresentationAttributes, validateCollections, verifyPreviewToken };
633
+ export { BLOCK_NAME_PATTERN, type BindingResolver, type Block, type BlockBinding, type BlockDefinition, type BlockRegistry, CMS_FONT_SIZES, CMS_IMAGE_WIDTHS, CMS_LINE_HEIGHTS, CMS_PARAGRAPH_SPACINGS, type CategoryTreeEntry, type CategoryTreeEntryCategory, type CategoryTreePresentation, type CategoryTreeTerm, type ClientHydrationSpec, DEFAULT_SITE_PATTERN_PRESENTATION, EMPTY_SITE_PATTERN_PLACEMENTS, type EmbedProvider, FIELD_BINDING_SOURCE, FIELD_BINDING_SOURCE_ALIAS, type FaqEntry, IMAGE_ALT_MAX_LENGTH, INTERNAL_CONTENT_LINK_CLASS, INTERNAL_CONTENT_LINK_PENDING_ATTR, INTERNAL_CONTENT_LINK_PENDING_CLASS, INTERNAL_CONTENT_LINK_TOKEN_SOURCE, type InternalContentLinkResolver, type InternalContentLinkToken, LEGACY_BLOG_DETAIL_PATH, type NormalizedEmbed, PHASE_1_CORE_BLOCKS, PHASE_1_CSP, POST_FOOTER_SLOT, type PatternSlots, type Phase1CoreBlockName, type PreviewTokenPayload, type PublicPathContext, type PublicPathPost, type PublicPathPresentation, type RoutablePost, type RouteCollection, SITE_PATTERN_BUTTON_COLORS_MAX, SITE_PATTERN_KEY_PATTERN, SITE_PATTERN_PLACEMENTS_KEY, SITE_PATTERN_SLOTS, type SectionLock, type SectionLockViolation, type SectionLockViolationCode, type SitePatternLayout, type SitePatternLinkStyle, type SitePatternPlacementRule, type SitePatternPlacements, type SitePatternPresentation, type SitePatternSlotKey, applyBlockBindings, bindingSrcValue, bindingTextValue, blockSchema, boundFieldValue, chainResolvers, cmsFontSize, cmsImageAlign, cmsImageStyle, cmsImageWidth, cmsLineHeight, cmsParagraphSpacing, createFieldBindingResolver, emptyPatternSlots, ensureImportedHtmlDocumentRoots, ensureStableIds, extractFaqEntries, faqPageJsonLd, findInternalContentLinkTokens, findSectionLockViolations, findSitePatternPlacementRule, formatSectionLockViolations, htmlToPlainText, injectStableIdsTree, internalContentKeyFromPath, internalContentLinkResolverFrom, internalContentPathIndex, isAllowedEmbedSrc, isAllowedMapEmbedSrc, isInCollectionMode, isSectionContentPropPath, isSitePatternSlotKey, isValidBlock, isValidSitePatternKey, issuePreviewToken, normalizeEmbed, normalizeHexColor, normalizeImageAlt, parseBlock, postsForCollection, postsInAnyCollection, postsNotInAnyCollection, readSitePatternPlacements, readSitePatternPresentation, referencedSitePatternKeys, renderInternalContentLinksInHtml, renderInternalContentLinksInText, resolveCategoryPath, resolveCategoryTreeChain, resolveCategoryTreeEntryPath, resolveCategoryTreeHubPaths, resolveCategoryTreeSelection, resolvePatternSlots, resolvePostCategorySlug, resolvePostCollection, resolvePostPath, resolvePostPublicPath, resolvePostUrl, sanitizeHtml, sanitizeImportedCss, sanitizeImportedHtml, scopeImportedCss, serializeBlock, setSitePatternPlacementRule, sitePatternButtonColor, sitePatternPresentationAttributes, validateCollections, verifyPreviewToken };
package/dist/index.js CHANGED
@@ -225,39 +225,51 @@ function validateCollections(collections) {
225
225
  }
226
226
 
227
227
  // src/category-tree.ts
228
- function normalizeBasePath(basePath) {
228
+ function basePathPrefix(basePath) {
229
+ if (!basePath.trim()) return null;
229
230
  return basePath.replace(/\/+$/, "");
230
231
  }
231
- function resolveCategoryTreeChain(presentation, leafId, terms) {
232
- if (!Number.isInteger(presentation.categoryDepth) || presentation.categoryDepth < 1) {
233
- return null;
234
- }
232
+ function categoryTreeAncestry(categoryDepth, termId, terms) {
233
+ if (!Number.isInteger(categoryDepth) || categoryDepth < 1) return null;
235
234
  const termsById = new Map(terms.map((term) => [term.id, term]));
236
235
  if (termsById.size !== terms.length) return null;
237
- if (terms.some((term) => term.parentId === leafId)) return null;
238
236
  const reversed = [];
239
237
  const visited = /* @__PURE__ */ new Set();
240
- let currentId = leafId;
238
+ let currentId = termId;
241
239
  while (currentId) {
242
240
  if (visited.has(currentId)) return null;
243
241
  visited.add(currentId);
244
242
  const current = termsById.get(currentId);
245
243
  if (!current || !current.slug) return null;
246
244
  reversed.push(current);
245
+ if (reversed.length > categoryDepth) return null;
247
246
  currentId = current.parentId ?? null;
248
247
  }
249
- if (reversed.length !== presentation.categoryDepth) return null;
250
248
  return reversed.reverse();
251
249
  }
250
+ function hasChildCategory(termId, terms) {
251
+ return terms.some((term) => term.parentId === termId);
252
+ }
253
+ function resolveCategoryTreeChain(presentation, termId, terms) {
254
+ const chain = categoryTreeAncestry(presentation.categoryDepth, termId, terms);
255
+ if (!chain) return null;
256
+ if (presentation.entryCategory === "leaf") return chain;
257
+ if (hasChildCategory(termId, terms)) return null;
258
+ return chain.length === presentation.categoryDepth ? chain : null;
259
+ }
260
+ function resolveCategoryTreeSelection(presentation, termId, terms) {
261
+ if (hasChildCategory(termId, terms)) return null;
262
+ return resolveCategoryTreeChain(presentation, termId, terms);
263
+ }
252
264
  function resolveCategoryTreeHubPaths(presentation, termId, terms) {
253
265
  const chain = resolveCategoryTreeChain(presentation, termId, terms);
254
- const basePath = normalizeBasePath(presentation.basePath);
255
- if (!chain || !basePath) return null;
256
- const paths = [basePath];
266
+ const prefix = basePathPrefix(presentation.basePath);
267
+ if (!chain || prefix === null) return null;
268
+ const paths = [prefix || "/"];
257
269
  const segments = [];
258
270
  for (const term of chain) {
259
271
  segments.push(encodeURIComponent(term.slug));
260
- paths.push(`${basePath}/${segments.join("/")}`);
272
+ paths.push(`${prefix}/${segments.join("/")}`);
261
273
  }
262
274
  return paths;
263
275
  }
@@ -1912,6 +1924,7 @@ export {
1912
1924
  resolveCategoryTreeChain,
1913
1925
  resolveCategoryTreeEntryPath,
1914
1926
  resolveCategoryTreeHubPaths,
1927
+ resolveCategoryTreeSelection,
1915
1928
  resolvePatternSlots,
1916
1929
  resolvePostCategorySlug,
1917
1930
  resolvePostCollection,