@uniweb/core 0.7.29 → 0.7.31
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 +3 -3
- package/src/block.js +99 -2
- package/src/website.js +27 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.31",
|
|
4
4
|
"description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"vitest": "^4.1.7"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@uniweb/
|
|
35
|
-
"@uniweb/
|
|
34
|
+
"@uniweb/theming": "0.1.14",
|
|
35
|
+
"@uniweb/semantic-parser": "1.1.20"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"test": "vitest run"
|
package/src/block.js
CHANGED
|
@@ -8,6 +8,70 @@
|
|
|
8
8
|
import { parseContent as parseSemanticContent } from '@uniweb/semantic-parser'
|
|
9
9
|
import { normalizeTokenValue } from '@uniweb/theming'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Lift container fences out of a content document.
|
|
13
|
+
*
|
|
14
|
+
* A ` ```@Component{params} ` fence parses to an `inset_block` node carrying a
|
|
15
|
+
* body of real block content. This rewrites each one to the `inset_placeholder`
|
|
16
|
+
* a leaf inset leaves behind, so a container resolves through exactly the same
|
|
17
|
+
* path: `getInset(refId)` → a Block → the foundation's component. Kit and the
|
|
18
|
+
* SSR renderer already handle placeholders, so neither needs to know containers
|
|
19
|
+
* exist.
|
|
20
|
+
*
|
|
21
|
+
* PURE with respect to the input. `blockData.content` is shared with the sync /
|
|
22
|
+
* pull machinery, which must keep seeing `inset_block` — that is the canonical
|
|
23
|
+
* stored shape. Nodes on the path to a container are cloned; everything else is
|
|
24
|
+
* passed through by reference, so a document with no containers costs one array
|
|
25
|
+
* walk and allocates nothing.
|
|
26
|
+
*
|
|
27
|
+
* Containers are NOT recursed into here. A container's body becomes its child
|
|
28
|
+
* Block's content, and that Block's constructor lifts its own containers — so
|
|
29
|
+
* nesting resolves one level at a time, each at the level that owns it.
|
|
30
|
+
*
|
|
31
|
+
* @param {Object} content - ProseMirror document (never mutated)
|
|
32
|
+
* @returns {{ content: Object, refs: Array<{refId, type, params, content}> }}
|
|
33
|
+
*/
|
|
34
|
+
function liftContainers(content) {
|
|
35
|
+
if (!content || !Array.isArray(content.content)) return { content, refs: [] }
|
|
36
|
+
|
|
37
|
+
const refs = []
|
|
38
|
+
|
|
39
|
+
const visit = (nodes) => {
|
|
40
|
+
let changed = false
|
|
41
|
+
const out = nodes.map((node) => {
|
|
42
|
+
if (!node) return node
|
|
43
|
+
|
|
44
|
+
if (node.type === 'inset_block') {
|
|
45
|
+
const { component, ...params } = node.attrs || {}
|
|
46
|
+
const refId = `container_${refs.length}`
|
|
47
|
+
refs.push({
|
|
48
|
+
refId,
|
|
49
|
+
type: component,
|
|
50
|
+
params,
|
|
51
|
+
content: { type: 'doc', content: node.content || [] },
|
|
52
|
+
})
|
|
53
|
+
changed = true
|
|
54
|
+
return { type: 'inset_placeholder', attrs: { refId, embedKind: 'block' } }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(node.content)) {
|
|
58
|
+
const inner = visit(node.content)
|
|
59
|
+
if (inner !== node.content) {
|
|
60
|
+
changed = true
|
|
61
|
+
return { ...node, content: inner }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return node
|
|
65
|
+
})
|
|
66
|
+
return changed ? out : nodes
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const next = visit(content.content)
|
|
70
|
+
return next === content.content
|
|
71
|
+
? { content, refs }
|
|
72
|
+
: { content: { ...content, content: next }, refs }
|
|
73
|
+
}
|
|
74
|
+
|
|
11
75
|
export default class Block {
|
|
12
76
|
constructor(blockData, id, page) {
|
|
13
77
|
this.id = id
|
|
@@ -22,8 +86,17 @@ export default class Block {
|
|
|
22
86
|
// 1. Raw ProseMirror content (from content collection)
|
|
23
87
|
// 2. Pre-parsed content with main/items structure
|
|
24
88
|
// For now, store raw and parse on demand
|
|
25
|
-
|
|
26
|
-
|
|
89
|
+
//
|
|
90
|
+
// Container fences (```@Component around a body) arrive as `inset_block`
|
|
91
|
+
// nodes and are lifted out HERE, into the same placeholder + refId shape
|
|
92
|
+
// the build gives leaf insets. Doing it at render-graph construction
|
|
93
|
+
// rather than at build time is deliberate: `inset_block` is the canonical
|
|
94
|
+
// STORED shape, so the content that syncs and round-trips must keep
|
|
95
|
+
// carrying it. `blockData.content` is left untouched — the lift produces a
|
|
96
|
+
// new tree and only this Block's view of it changes.
|
|
97
|
+
const lifted = liftContainers(blockData.content)
|
|
98
|
+
this.rawContent = lifted.content || {}
|
|
99
|
+
this.parsedContent = this.parseContent(lifted.content)
|
|
27
100
|
|
|
28
101
|
// Merge fetched data from prerender (if present)
|
|
29
102
|
// Prerender stores fetched data in blockData.parsedContent.data
|
|
@@ -114,6 +187,30 @@ export default class Block {
|
|
|
114
187
|
}
|
|
115
188
|
}
|
|
116
189
|
|
|
190
|
+
// Containers, appended AFTER the leaf insets so `block.insets[0]` keeps
|
|
191
|
+
// meaning what it meant to every foundation already using <Visual>.
|
|
192
|
+
// Unlike a leaf inset, a container's body becomes the child Block's
|
|
193
|
+
// content, so the foundation's component receives a fully parsed
|
|
194
|
+
// `content` — title, paragraphs, items, sequence — exactly as a section
|
|
195
|
+
// does. Nested containers resolve for free: the child Block runs this
|
|
196
|
+
// same constructor over its own body.
|
|
197
|
+
for (let i = 0; i < lifted.refs.length; i++) {
|
|
198
|
+
const ref = lifted.refs[i]
|
|
199
|
+
this.insets.push(
|
|
200
|
+
new Block(
|
|
201
|
+
{
|
|
202
|
+
type: ref.type,
|
|
203
|
+
params: ref.params || {},
|
|
204
|
+
content: ref.content,
|
|
205
|
+
stableId: ref.refId,
|
|
206
|
+
refId: ref.refId,
|
|
207
|
+
},
|
|
208
|
+
`${id}_container_${i}`,
|
|
209
|
+
this.page
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
117
214
|
// Fetch configuration (from section frontmatter)
|
|
118
215
|
// Supports local files (path) or remote URLs (url)
|
|
119
216
|
this.fetch = blockData.fetch || null
|
package/src/website.js
CHANGED
|
@@ -1011,6 +1011,21 @@ export default class Website {
|
|
|
1011
1011
|
|
|
1012
1012
|
return {
|
|
1013
1013
|
enabled: this.isSearchEnabled(),
|
|
1014
|
+
// Which provider serves results. `index` (the default) downloads a
|
|
1015
|
+
// prebuilt index and queries it in the browser — free, and works on any
|
|
1016
|
+
// host. `endpoint` queries a server-side search API, which is what makes
|
|
1017
|
+
// dynamic/API-backed content searchable. Any other value names a
|
|
1018
|
+
// foundation-supplied search transport.
|
|
1019
|
+
//
|
|
1020
|
+
// Kit resolves and loads the provider; core only passes the declaration
|
|
1021
|
+
// through, so a site that never searches pays nothing for the vocabulary
|
|
1022
|
+
// (see kit's search module for the resolution rules).
|
|
1023
|
+
provider: config.provider || 'index',
|
|
1024
|
+
// Base-RELATIVE path for the `endpoint` provider. Left raw here: kit
|
|
1025
|
+
// resolves it against `basePath`, so one spelling works whether the site
|
|
1026
|
+
// is served from the root, from a subdirectory, or from a backend
|
|
1027
|
+
// subpath. Undefined unless declared.
|
|
1028
|
+
endpoint: config.endpoint,
|
|
1014
1029
|
indexUrl: this.getSearchIndexUrl(),
|
|
1015
1030
|
locale: this.getActiveLocale(),
|
|
1016
1031
|
include: {
|
|
@@ -1029,7 +1044,14 @@ export default class Website {
|
|
|
1029
1044
|
}
|
|
1030
1045
|
|
|
1031
1046
|
/**
|
|
1032
|
-
* Get the URL for the search index file
|
|
1047
|
+
* Get the URL for the search index file.
|
|
1048
|
+
*
|
|
1049
|
+
* Includes `basePath`, so the URL is correct on a site deployed under a
|
|
1050
|
+
* subdirectory (`base: /docs/`) and on a backend-hosted site served from a
|
|
1051
|
+
* subpath. Omitting it was a real bug: the returned path was fetched
|
|
1052
|
+
* verbatim, so search 404'd on every non-root deployment while data fetching
|
|
1053
|
+
* — which resolves the same base — worked.
|
|
1054
|
+
*
|
|
1033
1055
|
* @returns {string} URL to fetch the search index
|
|
1034
1056
|
*/
|
|
1035
1057
|
getSearchIndexUrl() {
|
|
@@ -1037,7 +1059,10 @@ export default class Website {
|
|
|
1037
1059
|
const isDefault = locale === this.getDefaultLocale()
|
|
1038
1060
|
|
|
1039
1061
|
// Default locale uses root path, others use locale prefix
|
|
1040
|
-
|
|
1062
|
+
const path = isDefault ? '/search-index.json' : `/${locale}/search-index.json`
|
|
1063
|
+
|
|
1064
|
+
// `basePath` is already normalized without a trailing slash ('' at root).
|
|
1065
|
+
return `${this.basePath || ''}${path}`
|
|
1041
1066
|
}
|
|
1042
1067
|
|
|
1043
1068
|
/**
|