@uniweb/core 0.5.20 → 0.5.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/core",
3
- "version": "0.5.20",
3
+ "version": "0.5.21",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
@@ -26,14 +26,14 @@
26
26
  "engines": {
27
27
  "node": ">=20.19"
28
28
  },
29
+ "scripts": {
30
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
31
+ },
29
32
  "devDependencies": {
30
33
  "jest": "^29.7.0"
31
34
  },
32
35
  "dependencies": {
33
- "@uniweb/semantic-parser": "1.1.9",
34
- "@uniweb/theming": "0.1.3"
35
- },
36
- "scripts": {
37
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
36
+ "@uniweb/semantic-parser": "workspace:*",
37
+ "@uniweb/theming": "workspace:*"
38
38
  }
39
- }
39
+ }
package/src/page.js CHANGED
@@ -387,8 +387,13 @@ export default class Page {
387
387
  */
388
388
  getNavigableRoute() {
389
389
  if (this.hasContent()) return this.route
390
- // Content-less containers: find first descendant with content
391
- for (const child of this.children || []) {
390
+ const children = this.children || []
391
+ // Prefer the index child (designated landing page for this folder).
392
+ // Return this folder's own route so the URL stays clean (/Articles, not /Articles/index).
393
+ const indexChild = children.find((c) => c.isIndex)
394
+ if (indexChild) return this.route
395
+ // Fall back to first child with content
396
+ for (const child of children) {
392
397
  const route = child.getNavigableRoute()
393
398
  if (route) return route
394
399
  }
package/src/website.js CHANGED
@@ -238,6 +238,23 @@ export default class Website {
238
238
  }
239
239
  }
240
240
 
241
+ // Fallback: infer parent-child from route structure for unlinked pages.
242
+ // The editor sets parentRoute via buildEnginePreviewPayload(), but published
243
+ // payloads may not include it. Infer from route nesting so children arrays
244
+ // are always populated (needed for nav filtering and getNavigableRoute).
245
+ // Only applies to nested routes (e.g., /Articles/index → parent /Articles).
246
+ // Top-level pages (e.g., /Features) are NOT children of the homepage.
247
+ for (const page of this.pages) {
248
+ if (page.parent || page.route === '/') continue
249
+ const inferredParent = page.route.replace(/\/[^/]+$/, '')
250
+ if (!inferredParent || inferredParent === '/' || inferredParent === page.route) continue
251
+ const parent = pageMap.get(inferredParent)
252
+ if (parent) {
253
+ parent.children.push(page)
254
+ page.parent = parent
255
+ }
256
+ }
257
+
241
258
  // Build page ID map for makeHref() resolution
242
259
  // Supports both explicit IDs and route-based lookup
243
260
  this._pageIdMap = new Map()
@@ -295,7 +312,13 @@ export default class Website {
295
312
  // For file-system sites whose page map uses canonical routes this will
296
313
  // simply fall through to the reverse-translate path below.
297
314
  const directMatch = this.pages.find((page) => page.route === normalizedStripped)
298
- if (directMatch) return directMatch
315
+ if (directMatch) {
316
+ // Folder with index child: always resolve to the index page.
317
+ // The index child is the designated landing page for this folder URL.
318
+ const indexChild = directMatch.children.find((c) => c.isIndex)
319
+ if (indexChild) return indexChild
320
+ return directMatch
321
+ }
299
322
 
300
323
  // Reverse-translate display route to canonical (e.g., '/acerca-de' → '/about')
301
324
  stripped = this.reverseTranslateRoute(stripped)
@@ -306,7 +329,11 @@ export default class Website {
306
329
 
307
330
  // Priority 1b: Exact match on canonical route
308
331
  const exactMatch = this.pages.find((page) => page.route === normalizedRoute)
309
- if (exactMatch) return exactMatch
332
+ if (exactMatch) {
333
+ const indexChild = exactMatch.children.find((c) => c.isIndex)
334
+ if (indexChild) return indexChild
335
+ return exactMatch
336
+ }
310
337
 
311
338
  // Priority 2: Index page nav route match
312
339
  const indexMatch = this.pages.find((page) => page.isIndex && page.getNavRoute() === normalizedRoute)
@@ -715,7 +742,9 @@ export default class Website {
715
742
  * @returns {string}
716
743
  */
717
744
  getLocaleUrl(localeCode, route = null) {
718
- let targetRoute = route || this.activePage.route
745
+ // Use getNavRoute() so index pages return the clean folder URL
746
+ // (e.g., /Articles instead of /Articles/index)
747
+ let targetRoute = route || this.activePage.getNavRoute()
719
748
 
720
749
  // Strip current locale prefix if present in route
721
750
  if (this.activeLocale && this.activeLocale !== this.defaultLocale) {
@@ -930,11 +959,14 @@ export default class Website {
930
959
  if (navType === 'footer' && page.hideInFooter) return false
931
960
  }
932
961
 
933
- // Skip content-less containers that have no visible children.
934
- // Containers WITH visible children stay in the hierarchy as group nodes
935
- // (hasContent: false) navigation components can render them as headers
936
- // or link them via navigableRoute to the first descendant with content.
937
- if (!page.hasContent() && !page.children?.some(isPageVisible)) return false
962
+ // Skip content-less containers that have no visible or navigable children.
963
+ // Folders with an isIndex child are navigable (they link to the index page)
964
+ // even though the index child itself is filtered out of the nav tree above.
965
+ // Containers with other visible children stay as group nodes.
966
+ if (!page.hasContent()) {
967
+ const hasNavigableIndex = page.children?.some((c) => c.isIndex)
968
+ if (!hasNavigableIndex && !page.children?.some(isPageVisible)) return false
969
+ }
938
970
 
939
971
  // Apply custom filter if provided
940
972
  if (customFilter && !customFilter(page)) return false