@uniweb/core 0.5.19 → 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 +7 -7
- package/src/block.js +9 -0
- package/src/page.js +7 -2
- package/src/website.js +40 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/core",
|
|
3
|
-
"version": "0.5.
|
|
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": "
|
|
34
|
-
"@uniweb/theming": "
|
|
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/block.js
CHANGED
|
@@ -167,6 +167,15 @@ export default class Block {
|
|
|
167
167
|
return this.page.getNavRoute()
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Unique key for this block across all pages.
|
|
172
|
+
* Combines the page route with the block's positional id.
|
|
173
|
+
* Use as a React key when cross-page uniqueness matters.
|
|
174
|
+
*/
|
|
175
|
+
get key() {
|
|
176
|
+
return `${this.path}-${this.id}`
|
|
177
|
+
}
|
|
178
|
+
|
|
170
179
|
/**
|
|
171
180
|
* The parent page's URL path, one level up from the current page.
|
|
172
181
|
* Use this for "Back" links in detail pages: /blog/1 → /blog
|
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
|
-
|
|
391
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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
|
-
//
|
|
935
|
-
//
|
|
936
|
-
//
|
|
937
|
-
if (!page.hasContent()
|
|
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
|