@uniweb/core 0.29.1 → 0.29.3
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 +1 -1
- package/src/block.js +7 -1
- package/src/entity-store.js +1 -1
- package/src/fetch-config.js +13 -5
- package/src/index.js +1 -0
- package/src/route-match.js +30 -0
- package/src/website.js +6 -2
package/package.json
CHANGED
package/src/block.js
CHANGED
|
@@ -104,10 +104,16 @@ export default class Block {
|
|
|
104
104
|
|
|
105
105
|
// Merge fetched data from prerender (if present)
|
|
106
106
|
// Prerender stores fetched data in blockData.parsedContent.data
|
|
107
|
+
//
|
|
108
|
+
// ⭐ THE SECTION'S OWN DATA WINS, as it does when nothing was prerendered: a tagged
|
|
109
|
+
// data block fills its key first, and a fetch fills only what is left. ⛔ The
|
|
110
|
+
// prerendered answer was spread LAST until 2026-09-14, so a key held by both a
|
|
111
|
+
// tagged block and the section's own fetch showed the fetch's records on a
|
|
112
|
+
// prerendered page and the tagged block in the browser.
|
|
107
113
|
if (blockData.parsedContent?.data) {
|
|
108
114
|
this.parsedContent.data = {
|
|
109
|
-
...(this.parsedContent.data || {}),
|
|
110
115
|
...blockData.parsedContent.data,
|
|
116
|
+
...(this.parsedContent.data || {}),
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
|
package/src/entity-store.js
CHANGED
|
@@ -144,7 +144,7 @@ export default class EntityStore {
|
|
|
144
144
|
|
|
145
145
|
/**
|
|
146
146
|
* The ROUTE QUERY of the block's page — the query its URL names one record of —
|
|
147
|
-
* worked out from the same sources `
|
|
147
|
+
* worked out from the same sources `_levels` reads, at the page that
|
|
148
148
|
* captured the URL's variable (`pageRouteQuery`, `./fetch-config.js`): its fetch,
|
|
149
149
|
* its parent's, the site's, and, when none of those declares one, the key its
|
|
150
150
|
* sections share. So what a section receives and what the URL narrows are read
|
package/src/fetch-config.js
CHANGED
|
@@ -195,8 +195,12 @@ function localizeConfig(cfg, locale, defaultLocale) {
|
|
|
195
195
|
* by the URL's value (`buildDetailConfig`);
|
|
196
196
|
* - an external query declaring `record:` → `true`: its own request;
|
|
197
197
|
* - a query declaring `deferred:` → `/data/<query>/{slug}.json`, the per-record
|
|
198
|
-
* file the build emits beside the lean list
|
|
199
|
-
*
|
|
198
|
+
* file the build emits beside the lean list, keyed by the record's slug. ⭐ On a
|
|
199
|
+
* locale other than the default it is `/<locale>/data/<query>/{slug}.json`, the
|
|
200
|
+
* way the list's own path is (`localizeConfig`): a localized build writes a
|
|
201
|
+
* translated copy of every per-record file beside the translated list. ⛔ Until
|
|
202
|
+
* 2026-09-14 the pattern was never localized, so a translated record page asked
|
|
203
|
+
* for the default locale's file and showed an untranslated body.
|
|
200
204
|
*
|
|
201
205
|
* ⛔ `detail:` is no longer AUTHORED (retired 2026-09-13 [Diego]; the build refuses
|
|
202
206
|
* it): its `rest`, `query`, pattern and `{ body, envelope }` forms are an external
|
|
@@ -208,9 +212,11 @@ function localizeConfig(cfg, locale, defaultLocale) {
|
|
|
208
212
|
*
|
|
209
213
|
* @param {Object} cfg
|
|
210
214
|
* @param {Object|null} queries - the site's `config.queries` map
|
|
215
|
+
* @param {string|null} [locale] - the page's locale
|
|
216
|
+
* @param {string|null} [defaultLocale] - the site's default locale
|
|
211
217
|
* @returns {Object} the original config, or a copy carrying `detail`
|
|
212
218
|
*/
|
|
213
|
-
function applyDeferredDetail(cfg, queries) {
|
|
219
|
+
function applyDeferredDetail(cfg, queries, locale = null, defaultLocale = null) {
|
|
214
220
|
if (cfg.detail !== undefined) return cfg
|
|
215
221
|
|
|
216
222
|
// The records service answers a RECORD by the query's set narrowed to it, so
|
|
@@ -239,7 +245,9 @@ function applyDeferredDetail(cfg, queries) {
|
|
|
239
245
|
if (!collConfig || typeof collConfig !== 'object') return cfg
|
|
240
246
|
const deferred = Array.isArray(collConfig.deferred) ? collConfig.deferred : null
|
|
241
247
|
if (!deferred || deferred.length === 0) return cfg
|
|
242
|
-
|
|
248
|
+
const pattern = recordDataUrl(queryName, '{slug}')
|
|
249
|
+
const localized = locale && locale !== defaultLocale && isDataUrl(pattern) ? `/${locale}${pattern}` : pattern
|
|
250
|
+
return { ...cfg, detail: localized }
|
|
243
251
|
}
|
|
244
252
|
|
|
245
253
|
/**
|
|
@@ -549,7 +557,7 @@ export function resolveFetchConfigs(sources, options = {}) {
|
|
|
549
557
|
const sourced = resolveQuerySource(cfg, services, { queries, locale, defaultLocale })
|
|
550
558
|
const localized = localizeConfig(sourced, locale, defaultLocale)
|
|
551
559
|
const bound = dropRootScope(bindRouteVariables(localized, variables))
|
|
552
|
-
configs.set(key, stampDepthAndLocale(applyDeferredDetail(bound, queries), locale, defaultLocale))
|
|
560
|
+
configs.set(key, stampDepthAndLocale(applyDeferredDetail(bound, queries, locale, defaultLocale), locale, defaultLocale))
|
|
553
561
|
}
|
|
554
562
|
}
|
|
555
563
|
|
package/src/index.js
CHANGED
package/src/route-match.js
CHANGED
|
@@ -261,6 +261,36 @@ export function recordHandle(record) {
|
|
|
261
261
|
return record.slug
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* A record's DISPLAY TITLE — what the page about it, and a search result for it, are called.
|
|
266
|
+
*
|
|
267
|
+
* ⭐ ONE RULE, read by every producer of that title (ruled 2026-09-14 [Diego]): the
|
|
268
|
+
* parametric page (`Website._createDynamicPage`, so the SPA and a host's render), the
|
|
269
|
+
* static build's page expansion, and the record search index (`@uniweb/projections`).
|
|
270
|
+
*
|
|
271
|
+
* title → name → the record's handle (`recordHandle`: `$name`, else `slug`)
|
|
272
|
+
*
|
|
273
|
+
* ⛔ Until then a page read `title` alone while the search index read `title || name`,
|
|
274
|
+
* so a record with a `name` and no `title` — a person — titled its search result and
|
|
275
|
+
* left its page carrying the template's own title, which on the site where this was
|
|
276
|
+
* measured was the route token itself: `:slug` (2026-09-14).
|
|
277
|
+
*
|
|
278
|
+
* Only a non-empty string (or a finite number — `title: 2024` in YAML) names a record.
|
|
279
|
+
* A localized map or a rich document is structure this rule cannot render, and
|
|
280
|
+
* `String()` of one is `[object Object]`.
|
|
281
|
+
*
|
|
282
|
+
* @param {Object} record
|
|
283
|
+
* @returns {string|undefined}
|
|
284
|
+
*/
|
|
285
|
+
export function recordTitle(record) {
|
|
286
|
+
if (!record || typeof record !== 'object') return undefined
|
|
287
|
+
for (const value of [record.title, record.name, recordHandle(record)]) {
|
|
288
|
+
if (typeof value === 'string' && value.trim() !== '') return value
|
|
289
|
+
if (typeof value === 'number' && Number.isFinite(value)) return String(value)
|
|
290
|
+
}
|
|
291
|
+
return undefined
|
|
292
|
+
}
|
|
293
|
+
|
|
264
294
|
/**
|
|
265
295
|
* The record field a route param is matched on — ONE MAP, read by the local match
|
|
266
296
|
* (`routeParamValue`, below) and by the records service's record question (`match`,
|
package/src/website.js
CHANGED
|
@@ -11,7 +11,7 @@ import FetcherDispatcher from './fetcher-dispatcher.js'
|
|
|
11
11
|
import ObservableState from './observable-state.js'
|
|
12
12
|
import { normalizeSeo } from './seo.js'
|
|
13
13
|
import { resolveDefaultLocale, localeLabel } from './locale-config.js'
|
|
14
|
-
import { matchDynamicRoute, decodeRouteValue, matchesRouteParam, routeBinding, routeParamName, parentRouteOf } from './route-match.js'
|
|
14
|
+
import { matchDynamicRoute, decodeRouteValue, matchesRouteParam, recordTitle, routeBinding, routeParamName, parentRouteOf } from './route-match.js'
|
|
15
15
|
import { resolveFetchConfigs, pageRouteQuery, recordPages, routeSelection, siteReaches } from './fetch-config.js'
|
|
16
16
|
import { declaredKeys } from './data-keys.js'
|
|
17
17
|
import { buildDetailConfig } from './detail-url.js'
|
|
@@ -679,7 +679,11 @@ export default class Website {
|
|
|
679
679
|
}
|
|
680
680
|
|
|
681
681
|
if (currentItem) {
|
|
682
|
-
|
|
682
|
+
// ⭐ The record names the page by the one rule its search result is named by
|
|
683
|
+
// too (`recordTitle`: `title`, `name`, then its handle). ⛔ Until 2026-09-14 this
|
|
684
|
+
// read `title` alone, so a person record kept the template's title — `:slug`.
|
|
685
|
+
const title = recordTitle(currentItem)
|
|
686
|
+
if (title) pageData.title = title
|
|
683
687
|
if (currentItem.description || currentItem.excerpt) {
|
|
684
688
|
pageData.description = currentItem.description || currentItem.excerpt
|
|
685
689
|
}
|