@uniweb/build 1.8.2 → 1.8.4
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 +9 -9
- package/src/i18n/merge.js +22 -28
- package/src/site/content-collector.js +20 -4
- package/src/uwx/site.js +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.4",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -56,15 +56,15 @@
|
|
|
56
56
|
"js-yaml": "^4.1.0",
|
|
57
57
|
"sharp": "^0.35.3",
|
|
58
58
|
"yaml": "^2.5.0",
|
|
59
|
-
"@uniweb/content-reader": "~1.5.
|
|
60
|
-
"@uniweb/
|
|
59
|
+
"@uniweb/content-reader": "~1.5.6",
|
|
60
|
+
"@uniweb/semantic-parser": "~1.13.0",
|
|
61
61
|
"@uniweb/schemas": "~1.6.5",
|
|
62
|
-
"@uniweb/
|
|
63
|
-
"@uniweb/
|
|
64
|
-
"@uniweb/
|
|
62
|
+
"@uniweb/content-writer": "~1.3.0",
|
|
63
|
+
"@uniweb/projections": "~1.1.7",
|
|
64
|
+
"@uniweb/theming": "~1.2.1"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
-
"@uniweb/runtime": "~1.1.
|
|
67
|
+
"@uniweb/runtime": "~1.1.18"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -73,8 +73,8 @@
|
|
|
73
73
|
"@tailwindcss/vite": "^4.0.0",
|
|
74
74
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
75
75
|
"vite-plugin-svgr": "^4.0.0",
|
|
76
|
-
"@uniweb/
|
|
77
|
-
"@uniweb/
|
|
76
|
+
"@uniweb/runtime": "~1.1.18",
|
|
77
|
+
"@uniweb/core": "~1.4.3"
|
|
78
78
|
},
|
|
79
79
|
"peerDependenciesMeta": {
|
|
80
80
|
"vite": {
|
package/src/i18n/merge.js
CHANGED
|
@@ -17,19 +17,16 @@ import { loadFreeformTranslation } from './freeform.js'
|
|
|
17
17
|
import { elementText, blockElements, translationContext, SITE_META_CONTEXT } from './extract.js'
|
|
18
18
|
import { visitDataBlockStrings } from './data-strings.js'
|
|
19
19
|
|
|
20
|
-
// Inline
|
|
20
|
+
// Inline markdown → ProseMirror inline nodes, for resolving a whole-element
|
|
21
21
|
// translation VALUE (which carries marks/links/icons as inline markdown). Same
|
|
22
22
|
// lazy-import-with-fallback pattern as freeform.js / collection-processor.js, so
|
|
23
23
|
// the synchronous merge path has the converter ready at call time.
|
|
24
|
-
let
|
|
24
|
+
let inlineMarkdownToProseMirror
|
|
25
25
|
try {
|
|
26
26
|
const contentReader = await import('@uniweb/content-reader')
|
|
27
|
-
|
|
27
|
+
inlineMarkdownToProseMirror = contentReader.inlineMarkdownToProseMirror
|
|
28
28
|
} catch {
|
|
29
|
-
|
|
30
|
-
type: 'doc',
|
|
31
|
-
content: [{ type: 'paragraph', content: [{ type: 'text', text: markdown.trim() }] }]
|
|
32
|
-
})
|
|
29
|
+
inlineMarkdownToProseMirror = (markdown) => [{ type: 'text', text: markdown.trim() }]
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
/**
|
|
@@ -183,9 +180,6 @@ async function mergeTranslationsAsync(siteContent, translations, options) {
|
|
|
183
180
|
return translated
|
|
184
181
|
}
|
|
185
182
|
|
|
186
|
-
// The blocks that hold words, which `toPlainText` reads as one run each.
|
|
187
|
-
const TEXT_BLOCKS = new Set(['paragraph', 'heading', 'codeBlock'])
|
|
188
|
-
|
|
189
183
|
/**
|
|
190
184
|
* ⭐ A TRANSLATION VALUE IS INLINE MARKDOWN, AND A PLAIN-TEXT SLOT RENDERS ITS TEXT.
|
|
191
185
|
*
|
|
@@ -221,17 +215,19 @@ export function toPlainText(value) {
|
|
|
221
215
|
return value
|
|
222
216
|
}
|
|
223
217
|
try {
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
//
|
|
218
|
+
// Each paragraph's words, read as inline markdown and never as blocks, so `1. *Introduction*`
|
|
219
|
+
// keeps its number; paragraphs are apart. ⛔ Until 2026-10-16 every level joined with nothing,
|
|
220
|
+
// so a value of two paragraphs read as one word at the seam ("…17hFermé…"). ⛔ Until 2026-10-24
|
|
221
|
+
// the value was read as a document, and one that began like a block lost its marker: an ordered
|
|
222
|
+
// list's number, a bullet, a `#`.
|
|
227
223
|
const inline = (nodes) =>
|
|
228
224
|
(nodes || []).map((n) => (n.type === 'text' ? n.text || '' : n.type === 'hardBreak' ? ' ' : inline(n.content))).join('')
|
|
229
|
-
const
|
|
230
|
-
(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
225
|
+
const text = value
|
|
226
|
+
.split(/\n[ \t]*\n/)
|
|
227
|
+
.map((paragraph) => inline(inlineMarkdownToProseMirror(paragraph.trim())))
|
|
228
|
+
.filter((t) => t.trim())
|
|
229
|
+
.join(' ')
|
|
230
|
+
.trim()
|
|
235
231
|
return text || value
|
|
236
232
|
} catch {
|
|
237
233
|
return value
|
|
@@ -639,17 +635,15 @@ export function resolveDocForLocale(sourceDoc, table, context = { page: '', sect
|
|
|
639
635
|
return elements || data ? doc : null
|
|
640
636
|
}
|
|
641
637
|
|
|
642
|
-
// Parse an inline-markdown translation value into a ProseMirror inline fragment
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
//
|
|
638
|
+
// Parse an inline-markdown translation value into a ProseMirror inline fragment: one element's
|
|
639
|
+
// words, read as inline markdown and never as blocks (`inlineMarkdownToProseMirror`).
|
|
640
|
+
// ⛔ Until 2026-10-24 it was read as a document and each block's content taken, so a value that
|
|
641
|
+
// began like a block — `1. Informations que nous collectons`, the French of a numbered heading —
|
|
642
|
+
// was an ordered list, and the heading was handed its list item. It rendered empty, and a pull
|
|
643
|
+
// refused the site, since no file can say a heading holds a list item.
|
|
646
644
|
function inlineMarkdownToFragment(value) {
|
|
647
645
|
if (typeof value !== 'string' || value.trim() === '') return null
|
|
648
|
-
const
|
|
649
|
-
const inline = []
|
|
650
|
-
for (const block of doc?.content || []) {
|
|
651
|
-
if (Array.isArray(block.content)) inline.push(...block.content)
|
|
652
|
-
}
|
|
646
|
+
const inline = inlineMarkdownToProseMirror(value.trim())
|
|
653
647
|
return inline.length ? inline : [{ type: 'text', text: value.trim() }]
|
|
654
648
|
}
|
|
655
649
|
|
|
@@ -241,6 +241,22 @@ function detectVersions(folderNames) {
|
|
|
241
241
|
return versions
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
/**
|
|
245
|
+
* An id an author wrote — a page's `id:` in `page.yml`, a section's in its frontmatter — as the
|
|
246
|
+
* string it names. YAML reads `id: 404` as a number, and an id is never one: a `page:404` link
|
|
247
|
+
* names the string, and a store holds a stable id as text.
|
|
248
|
+
*
|
|
249
|
+
* ⛔ *Until 2026-10-24 the parsed value was kept, so a page with `id: 404` matched no `page:`
|
|
250
|
+
* link, and a push sent its stable id as a number, which a backend refuses (measured that day:
|
|
251
|
+
* HTTP 400, "item.data.stable_id must be a string").*
|
|
252
|
+
*
|
|
253
|
+
* @param {*} value
|
|
254
|
+
* @returns {string|null} null for an id not written, or written empty
|
|
255
|
+
*/
|
|
256
|
+
export function authoredId(value) {
|
|
257
|
+
return value === undefined || value === null || value === '' ? null : String(value)
|
|
258
|
+
}
|
|
259
|
+
|
|
244
260
|
/**
|
|
245
261
|
* `query:` — the shorthand for `fetch: { query }`, at every level that declares
|
|
246
262
|
* data: a section's frontmatter, `page.yml`, `folder.yml` and `site.yml`, on the
|
|
@@ -1168,7 +1184,7 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
|
|
|
1168
1184
|
|
|
1169
1185
|
// Stable ID for scroll targeting: frontmatter id > filename-derived > null
|
|
1170
1186
|
// This ID is stable across reordering (unlike the positional id)
|
|
1171
|
-
const stableId = frontmatterId || defaultStableId || null
|
|
1187
|
+
const stableId = authoredId(frontmatterId) || defaultStableId || null
|
|
1172
1188
|
|
|
1173
1189
|
const section = {
|
|
1174
1190
|
id,
|
|
@@ -1434,7 +1450,7 @@ async function processPage(pagePath, pageName, siteRoot, { isIndex = false, pare
|
|
|
1434
1450
|
page: {
|
|
1435
1451
|
route,
|
|
1436
1452
|
sourcePath: parentRoute === '/' ? `/${pageName}` : `${parentRoute}/${pageName}`,
|
|
1437
|
-
id: pageConfig.id
|
|
1453
|
+
id: authoredId(pageConfig.id),
|
|
1438
1454
|
isIndex,
|
|
1439
1455
|
title: resolveDisplayTitle(pageConfig.title, pageName, null),
|
|
1440
1456
|
description: pageConfig.description || '',
|
|
@@ -1460,7 +1476,7 @@ async function processPage(pagePath, pageName, siteRoot, { isIndex = false, pare
|
|
|
1460
1476
|
page: {
|
|
1461
1477
|
route,
|
|
1462
1478
|
sourcePath: parentRoute === '/' ? `/${pageName}` : `${parentRoute}/${pageName}`,
|
|
1463
|
-
id: pageConfig.id
|
|
1479
|
+
id: authoredId(pageConfig.id),
|
|
1464
1480
|
isIndex,
|
|
1465
1481
|
title: resolveDisplayTitle(pageConfig.title, pageName, null),
|
|
1466
1482
|
description: pageConfig.description || '',
|
|
@@ -1731,7 +1747,7 @@ async function processPage(pagePath, pageName, siteRoot, { isIndex = false, pare
|
|
|
1731
1747
|
page: {
|
|
1732
1748
|
route,
|
|
1733
1749
|
sourcePath, // Original folder-based path (for ancestor checking in navigation)
|
|
1734
|
-
id: pageConfig.id
|
|
1750
|
+
id: authoredId(pageConfig.id), // Stable page ID for page: links (survives reorganization)
|
|
1735
1751
|
isIndex, // Marks this page as the index for its parent route
|
|
1736
1752
|
title: resolveDisplayTitle(pageConfig.title, pageName, hierarchicalSections),
|
|
1737
1753
|
description: pageConfig.description || '',
|
package/src/uwx/site.js
CHANGED
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
rootOrderConfig,
|
|
72
72
|
composeLocalizedRoute,
|
|
73
73
|
isValidSlugSegment,
|
|
74
|
+
authoredId,
|
|
74
75
|
} from '../site/content-collector.js'
|
|
75
76
|
import { refuseBinding, refuseUnder, refuseOutsideLanguage, warnDuplicateBindings } from '../site/data-fetcher.js'
|
|
76
77
|
import { readLayoutFolder, layoutAreaRoute } from '../site/layout-folder.js'
|
|
@@ -312,7 +313,8 @@ function buildPageData(config, ctx) {
|
|
|
312
313
|
// `localizeRouteSlugs`, once the whole tree is known. `mode` is the plain
|
|
313
314
|
// delivery mode.
|
|
314
315
|
const data = { slug: { [sourceLocale]: slug }, mode } // both required by the entity type
|
|
315
|
-
|
|
316
|
+
// An authored id is a string (`authoredId`): `id: 404` reads as a number, which a store refuses.
|
|
317
|
+
setIf(data, 'stable_id', authoredId(config.id) ?? undefined)
|
|
316
318
|
setIf(data, 'title', localizeScalar(config.title, sourceLocale, translations))
|
|
317
319
|
setIf(data, 'description', localizeScalar(config.description, sourceLocale, translations))
|
|
318
320
|
setIf(data, 'label', localizeScalar(config.label, sourceLocale, translations))
|