@uniweb/runtime 0.1.1 → 0.2.1
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/README.md +48 -171
- package/package.json +9 -21
- package/src/index.jsx +27 -44
- package/src/components/Link.jsx +0 -28
- package/src/components/SafeHtml.jsx +0 -22
- package/src/core/block.js +0 -311
- package/src/core/input.js +0 -15
- package/src/core/page.js +0 -75
- package/src/core/uniweb.js +0 -103
- package/src/core/website.js +0 -157
- package/src/vite/content-collector.js +0 -269
- package/src/vite/foundation-plugin.js +0 -194
- package/src/vite/index.js +0 -7
- package/src/vite/site-content-plugin.js +0 -135
package/src/core/block.js
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Block
|
|
3
|
-
*
|
|
4
|
-
* Represents a section/block on a page. Contains content, properties,
|
|
5
|
-
* child blocks, and state management. Connects to foundation components.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { parseContent as parseSemanticContent } from '@uniweb/semantic-parser'
|
|
9
|
-
|
|
10
|
-
export default class Block {
|
|
11
|
-
constructor(blockData, id) {
|
|
12
|
-
this.id = id
|
|
13
|
-
this.component = blockData.component || 'Section'
|
|
14
|
-
this.Component = null
|
|
15
|
-
|
|
16
|
-
// Content structure
|
|
17
|
-
// The content can be:
|
|
18
|
-
// 1. Raw ProseMirror content (from content collection)
|
|
19
|
-
// 2. Pre-parsed content with main/items structure
|
|
20
|
-
// For now, store raw and parse on demand
|
|
21
|
-
this.rawContent = blockData.content || {}
|
|
22
|
-
this.parsedContent = this.parseContent(blockData.content)
|
|
23
|
-
|
|
24
|
-
const { main, items } = this.parsedContent
|
|
25
|
-
this.main = main
|
|
26
|
-
this.items = items
|
|
27
|
-
|
|
28
|
-
// Block configuration
|
|
29
|
-
const blockConfig = blockData.params || blockData.config || {}
|
|
30
|
-
this.preset = blockData.preset
|
|
31
|
-
this.themeName = `context__${blockConfig.theme || 'light'}`
|
|
32
|
-
this.standardOptions = blockConfig.standardOptions || {}
|
|
33
|
-
this.properties = blockConfig.properties || blockConfig
|
|
34
|
-
|
|
35
|
-
// Child blocks (subsections)
|
|
36
|
-
this.childBlocks = blockData.subsections
|
|
37
|
-
? blockData.subsections.map((block, i) => new Block(block, `${id}_${i}`))
|
|
38
|
-
: []
|
|
39
|
-
|
|
40
|
-
// Input data
|
|
41
|
-
this.input = blockData.input || null
|
|
42
|
-
|
|
43
|
-
// State management
|
|
44
|
-
this.startState = null
|
|
45
|
-
this.state = null
|
|
46
|
-
this.resetStateHook = null
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Parse content into structured format using semantic-parser
|
|
51
|
-
* Supports multiple content formats:
|
|
52
|
-
* 1. Pre-parsed groups structure (from editor)
|
|
53
|
-
* 2. ProseMirror document (from markdown collection)
|
|
54
|
-
* 3. Simple key-value content (PoC style)
|
|
55
|
-
*
|
|
56
|
-
* Uses @uniweb/semantic-parser for rich content extraction including:
|
|
57
|
-
* - Pretitle detection (H3 before H1)
|
|
58
|
-
* - Banner/background image detection
|
|
59
|
-
* - Semantic grouping (main + items)
|
|
60
|
-
* - Lists, links, buttons, etc.
|
|
61
|
-
*/
|
|
62
|
-
parseContent(content) {
|
|
63
|
-
// If content is already parsed with groups structure
|
|
64
|
-
if (content?.groups) {
|
|
65
|
-
return content.groups
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ProseMirror document - use semantic-parser
|
|
69
|
-
if (content?.type === 'doc') {
|
|
70
|
-
return this.extractFromProseMirror(content)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Simple key-value content (PoC style) - pass through directly
|
|
74
|
-
// This allows components to receive content like { title, subtitle, items }
|
|
75
|
-
if (content && typeof content === 'object' && !Array.isArray(content)) {
|
|
76
|
-
return {
|
|
77
|
-
main: { header: {}, body: {} },
|
|
78
|
-
items: [],
|
|
79
|
-
// Store raw content for direct access
|
|
80
|
-
raw: content
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Fallback
|
|
85
|
-
return {
|
|
86
|
-
main: { header: {}, body: {} },
|
|
87
|
-
items: []
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Extract structured content from ProseMirror document
|
|
93
|
-
* Uses @uniweb/semantic-parser for intelligent content extraction
|
|
94
|
-
*/
|
|
95
|
-
extractFromProseMirror(doc) {
|
|
96
|
-
try {
|
|
97
|
-
// Parse with semantic-parser
|
|
98
|
-
const { groups, sequence, byType } = parseSemanticContent(doc)
|
|
99
|
-
|
|
100
|
-
// Transform groups structure to match expected format
|
|
101
|
-
const main = groups.main || { header: {}, body: {} }
|
|
102
|
-
const items = groups.items || []
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
main,
|
|
106
|
-
items,
|
|
107
|
-
// Include additional data for advanced use cases
|
|
108
|
-
sequence,
|
|
109
|
-
byType,
|
|
110
|
-
metadata: groups.metadata
|
|
111
|
-
}
|
|
112
|
-
} catch (err) {
|
|
113
|
-
console.warn('[Block] Semantic parser error, using fallback:', err.message)
|
|
114
|
-
return this.extractFromProseMirrorFallback(doc)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Fallback extraction when semantic-parser fails
|
|
120
|
-
*/
|
|
121
|
-
extractFromProseMirrorFallback(doc) {
|
|
122
|
-
const main = { header: {}, body: {} }
|
|
123
|
-
const items = []
|
|
124
|
-
|
|
125
|
-
if (!doc.content) return { main, items }
|
|
126
|
-
|
|
127
|
-
for (const node of doc.content) {
|
|
128
|
-
if (node.type === 'heading') {
|
|
129
|
-
const text = this.extractText(node)
|
|
130
|
-
if (node.attrs?.level === 1) {
|
|
131
|
-
main.header.title = text
|
|
132
|
-
} else if (node.attrs?.level === 2) {
|
|
133
|
-
main.header.subtitle = text
|
|
134
|
-
}
|
|
135
|
-
} else if (node.type === 'paragraph') {
|
|
136
|
-
const text = this.extractText(node)
|
|
137
|
-
if (!main.body.paragraphs) main.body.paragraphs = []
|
|
138
|
-
main.body.paragraphs.push(text)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return { main, items }
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Extract text from a node
|
|
147
|
-
*/
|
|
148
|
-
extractText(node) {
|
|
149
|
-
if (!node.content) return ''
|
|
150
|
-
return node.content
|
|
151
|
-
.filter((n) => n.type === 'text')
|
|
152
|
-
.map((n) => n.text)
|
|
153
|
-
.join('')
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Initialize the component from the foundation
|
|
158
|
-
* @returns {React.ComponentType|null}
|
|
159
|
-
*/
|
|
160
|
-
initComponent() {
|
|
161
|
-
if (this.Component) return this.Component
|
|
162
|
-
|
|
163
|
-
this.Component = globalThis.uniweb?.getComponent(this.component)
|
|
164
|
-
|
|
165
|
-
if (!this.Component) {
|
|
166
|
-
console.warn(`[Block] Component not found: ${this.component}`)
|
|
167
|
-
return null
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Initialize state from component defaults
|
|
171
|
-
const defaults = this.Component.blockDefaults || { state: this.Component.blockState }
|
|
172
|
-
this.startState = defaults.state ? { ...defaults.state } : null
|
|
173
|
-
this.initState()
|
|
174
|
-
|
|
175
|
-
return this.Component
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Get structured block content for components
|
|
180
|
-
*/
|
|
181
|
-
getBlockContent() {
|
|
182
|
-
const mainHeader = this.main?.header || {}
|
|
183
|
-
const mainBody = this.main?.body || {}
|
|
184
|
-
const banner = this.main?.banner || null
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
banner,
|
|
188
|
-
pretitle: mainHeader.pretitle || '',
|
|
189
|
-
title: mainHeader.title || '',
|
|
190
|
-
subtitle: mainHeader.subtitle || '',
|
|
191
|
-
description: mainHeader.description || '',
|
|
192
|
-
paragraphs: mainBody.paragraphs || [],
|
|
193
|
-
images: mainBody.imgs || mainBody.images || [],
|
|
194
|
-
links: mainBody.links || [],
|
|
195
|
-
icons: mainBody.icons || [],
|
|
196
|
-
properties: mainBody.propertyBlocks?.[0] || {},
|
|
197
|
-
videos: mainBody.videos || [],
|
|
198
|
-
lists: mainBody.lists || [],
|
|
199
|
-
buttons: mainBody.buttons || []
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Get block properties
|
|
205
|
-
*/
|
|
206
|
-
getBlockProperties() {
|
|
207
|
-
return this.properties
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Get child block renderer from runtime
|
|
212
|
-
*/
|
|
213
|
-
getChildBlockRenderer() {
|
|
214
|
-
return globalThis.uniweb?.childBlockRenderer
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Get links from block content
|
|
219
|
-
* @param {Object} options
|
|
220
|
-
* @returns {Array}
|
|
221
|
-
*/
|
|
222
|
-
getBlockLinks(options = {}) {
|
|
223
|
-
const website = globalThis.uniweb?.activeWebsite
|
|
224
|
-
|
|
225
|
-
if (options.nested) {
|
|
226
|
-
const lists = this.main?.body?.lists || []
|
|
227
|
-
const links = lists[0]
|
|
228
|
-
return Block.parseNestedLinks(links, website)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const links = this.main?.body?.links || []
|
|
232
|
-
return links.map((link) => ({
|
|
233
|
-
route: website?.makeHref(link.href) || link.href,
|
|
234
|
-
label: link.label
|
|
235
|
-
}))
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Initialize block state
|
|
240
|
-
*/
|
|
241
|
-
initState() {
|
|
242
|
-
this.state = this.startState
|
|
243
|
-
if (this.resetStateHook) this.resetStateHook()
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* React hook for block state management
|
|
248
|
-
* @param {Function} useState - React useState hook
|
|
249
|
-
* @param {any} initState - Initial state
|
|
250
|
-
* @returns {[any, Function]}
|
|
251
|
-
*/
|
|
252
|
-
useBlockState(useState, initState) {
|
|
253
|
-
if (initState !== undefined && this.startState === null) {
|
|
254
|
-
this.startState = initState
|
|
255
|
-
this.state = initState
|
|
256
|
-
} else {
|
|
257
|
-
initState = this.startState
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const [state, setState] = useState(initState)
|
|
261
|
-
|
|
262
|
-
this.resetStateHook = () => setState(initState)
|
|
263
|
-
|
|
264
|
-
return [state, (newState) => setState((this.state = newState))]
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Parse nested links structure
|
|
269
|
-
*/
|
|
270
|
-
static parseNestedLinks(list, website) {
|
|
271
|
-
const parsed = []
|
|
272
|
-
|
|
273
|
-
if (!list?.length) return parsed
|
|
274
|
-
|
|
275
|
-
for (const listItem of list) {
|
|
276
|
-
const { links = [], lists = [], paragraphs = [] } = listItem
|
|
277
|
-
|
|
278
|
-
const link = links[0]
|
|
279
|
-
const nestedList = lists[0]
|
|
280
|
-
const text = paragraphs[0]
|
|
281
|
-
|
|
282
|
-
let label = ''
|
|
283
|
-
let href = ''
|
|
284
|
-
let subLinks = []
|
|
285
|
-
let hasData = true
|
|
286
|
-
|
|
287
|
-
if (link) {
|
|
288
|
-
label = link.label
|
|
289
|
-
href = link.href
|
|
290
|
-
if (nestedList) {
|
|
291
|
-
subLinks = Block.parseNestedLinks(nestedList, website)
|
|
292
|
-
}
|
|
293
|
-
} else {
|
|
294
|
-
label = text
|
|
295
|
-
hasData = false
|
|
296
|
-
if (nestedList) {
|
|
297
|
-
subLinks = Block.parseNestedLinks(nestedList, website)
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
parsed.push({
|
|
302
|
-
label,
|
|
303
|
-
route: website?.makeHref(href) || href,
|
|
304
|
-
child_items: subLinks,
|
|
305
|
-
hasData
|
|
306
|
-
})
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return parsed
|
|
310
|
-
}
|
|
311
|
-
}
|
package/src/core/input.js
DELETED
package/src/core/page.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Page
|
|
3
|
-
*
|
|
4
|
-
* Represents a single page with header, body sections, and footer.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import Block from './block.js'
|
|
8
|
-
|
|
9
|
-
export default class Page {
|
|
10
|
-
constructor(pageData, id, pageHeader, pageFooter) {
|
|
11
|
-
this.id = id
|
|
12
|
-
this.route = pageData.route
|
|
13
|
-
this.title = pageData.title || ''
|
|
14
|
-
this.description = pageData.description || ''
|
|
15
|
-
|
|
16
|
-
this.pageBlocks = this.buildPageBlocks(
|
|
17
|
-
pageData.sections,
|
|
18
|
-
pageHeader?.sections,
|
|
19
|
-
pageFooter?.sections
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Build the page block structure
|
|
25
|
-
*/
|
|
26
|
-
buildPageBlocks(body, header, footer) {
|
|
27
|
-
const headerSection = header?.[0]
|
|
28
|
-
const footerSection = footer?.[0]
|
|
29
|
-
const bodySections = body || []
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
header: headerSection ? new Block(headerSection, 'header') : null,
|
|
33
|
-
body: bodySections.map((section, index) => new Block(section, index)),
|
|
34
|
-
footer: footerSection ? new Block(footerSection, 'footer') : null,
|
|
35
|
-
leftPanel: null,
|
|
36
|
-
rightPanel: null
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get all blocks (header, body, footer) as flat array
|
|
42
|
-
* @returns {Block[]}
|
|
43
|
-
*/
|
|
44
|
-
getPageBlocks() {
|
|
45
|
-
return [
|
|
46
|
-
this.pageBlocks.header,
|
|
47
|
-
...this.pageBlocks.body,
|
|
48
|
-
this.pageBlocks.footer
|
|
49
|
-
].filter(Boolean)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get just body blocks
|
|
54
|
-
* @returns {Block[]}
|
|
55
|
-
*/
|
|
56
|
-
getBodyBlocks() {
|
|
57
|
-
return this.pageBlocks.body
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get header block
|
|
62
|
-
* @returns {Block|null}
|
|
63
|
-
*/
|
|
64
|
-
getHeader() {
|
|
65
|
-
return this.pageBlocks.header
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Get footer block
|
|
70
|
-
* @returns {Block|null}
|
|
71
|
-
*/
|
|
72
|
-
getFooter() {
|
|
73
|
-
return this.pageBlocks.footer
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/core/uniweb.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Uniweb Core Runtime
|
|
3
|
-
*
|
|
4
|
-
* The main runtime instance that manages the website, foundation components,
|
|
5
|
-
* and provides utilities to components.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import Website from './website.js'
|
|
9
|
-
|
|
10
|
-
export default class Uniweb {
|
|
11
|
-
constructor(configData) {
|
|
12
|
-
this.activeWebsite = new Website(configData)
|
|
13
|
-
this.childBlockRenderer = null // Function to render child blocks
|
|
14
|
-
this.routingComponents = {} // Link, SafeHtml, useNavigate, etc.
|
|
15
|
-
this.foundation = null // The loaded foundation module
|
|
16
|
-
this.foundationConfig = {} // Configuration from foundation
|
|
17
|
-
this.language = 'en'
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Set the foundation module after loading
|
|
22
|
-
* @param {Object} foundation - The loaded ESM foundation module
|
|
23
|
-
*/
|
|
24
|
-
setFoundation(foundation) {
|
|
25
|
-
this.foundation = foundation
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Get a component from the foundation by name
|
|
30
|
-
* @param {string} name - Component name
|
|
31
|
-
* @returns {React.ComponentType|undefined}
|
|
32
|
-
*/
|
|
33
|
-
getComponent(name) {
|
|
34
|
-
if (!this.foundation) {
|
|
35
|
-
console.warn('[Runtime] No foundation loaded')
|
|
36
|
-
return undefined
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Use foundation's getComponent interface
|
|
40
|
-
if (typeof this.foundation.getComponent === 'function') {
|
|
41
|
-
return this.foundation.getComponent(name)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Fallback: direct component access
|
|
45
|
-
return this.foundation[name]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* List available components from the foundation
|
|
50
|
-
* @returns {string[]}
|
|
51
|
-
*/
|
|
52
|
-
listComponents() {
|
|
53
|
-
if (!this.foundation) return []
|
|
54
|
-
|
|
55
|
-
if (typeof this.foundation.listComponents === 'function') {
|
|
56
|
-
return this.foundation.listComponents()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return []
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Get component schema
|
|
64
|
-
* @param {string} name - Component name
|
|
65
|
-
* @returns {Object|undefined}
|
|
66
|
-
*/
|
|
67
|
-
getSchema(name) {
|
|
68
|
-
if (!this.foundation) return undefined
|
|
69
|
-
|
|
70
|
-
if (typeof this.foundation.getSchema === 'function') {
|
|
71
|
-
return this.foundation.getSchema(name)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return undefined
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Set foundation configuration
|
|
79
|
-
* @param {Object} config
|
|
80
|
-
*/
|
|
81
|
-
setFoundationConfig(config) {
|
|
82
|
-
this.foundationConfig = config
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Legacy compatibility - maps to new method names
|
|
86
|
-
getRemoteComponent(name) {
|
|
87
|
-
return this.getComponent(name)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
setRemoteComponents(components) {
|
|
91
|
-
// Legacy: components was an object map
|
|
92
|
-
// Convert to foundation-like interface
|
|
93
|
-
this.foundation = {
|
|
94
|
-
getComponent: (name) => components[name],
|
|
95
|
-
listComponents: () => Object.keys(components),
|
|
96
|
-
components
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
setRemoteConfig(config) {
|
|
101
|
-
this.setFoundationConfig(config)
|
|
102
|
-
}
|
|
103
|
-
}
|
package/src/core/website.js
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Website
|
|
3
|
-
*
|
|
4
|
-
* Manages pages, themes, and localization for a website instance.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import Page from './page.js'
|
|
8
|
-
|
|
9
|
-
export default class Website {
|
|
10
|
-
constructor(websiteData) {
|
|
11
|
-
const { pages = [], theme = {}, config = {} } = websiteData
|
|
12
|
-
|
|
13
|
-
// Extract special pages (header, footer) and regular pages
|
|
14
|
-
this.headerPage = pages.find((p) => p.route === '/@header')
|
|
15
|
-
this.footerPage = pages.find((p) => p.route === '/@footer')
|
|
16
|
-
|
|
17
|
-
this.pages = pages
|
|
18
|
-
.filter((page) => page.route !== '/@header' && page.route !== '/@footer')
|
|
19
|
-
.map(
|
|
20
|
-
(page, index) =>
|
|
21
|
-
new Page(page, index, this.headerPage, this.footerPage)
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
this.activePage =
|
|
25
|
-
this.pages.find((page) => page.route === '/' || page.route === '/index') ||
|
|
26
|
-
this.pages[0]
|
|
27
|
-
|
|
28
|
-
this.pageRoutes = this.pages.map((page) => page.route)
|
|
29
|
-
this.themeData = theme
|
|
30
|
-
this.config = config
|
|
31
|
-
this.activeLang = config.defaultLanguage || 'en'
|
|
32
|
-
this.langs = config.languages || [
|
|
33
|
-
{ label: 'English', value: 'en' },
|
|
34
|
-
{ label: 'français', value: 'fr' }
|
|
35
|
-
]
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get page by route
|
|
40
|
-
* @param {string} route
|
|
41
|
-
* @returns {Page|undefined}
|
|
42
|
-
*/
|
|
43
|
-
getPage(route) {
|
|
44
|
-
return this.pages.find((page) => page.route === route)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Set active page by route
|
|
49
|
-
* @param {string} route
|
|
50
|
-
*/
|
|
51
|
-
setActivePage(route) {
|
|
52
|
-
const page = this.getPage(route)
|
|
53
|
-
if (page) {
|
|
54
|
-
this.activePage = page
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Get remote layout component from foundation config
|
|
60
|
-
*/
|
|
61
|
-
getRemoteLayout() {
|
|
62
|
-
return globalThis.uniweb?.foundationConfig?.Layout || null
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get remote props from foundation config
|
|
67
|
-
*/
|
|
68
|
-
getRemoteProps() {
|
|
69
|
-
return globalThis.uniweb?.foundationConfig?.props || null
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get routing components (Link, useNavigate, etc.)
|
|
74
|
-
*/
|
|
75
|
-
getRoutingComponents() {
|
|
76
|
-
return globalThis.uniweb?.routingComponents || {}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Make href (for link transformation)
|
|
81
|
-
* @param {string} href
|
|
82
|
-
* @returns {string}
|
|
83
|
-
*/
|
|
84
|
-
makeHref(href) {
|
|
85
|
-
// Could add basename handling here
|
|
86
|
-
return href
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Get available languages
|
|
91
|
-
*/
|
|
92
|
-
getLanguages() {
|
|
93
|
-
return this.langs
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Get current language
|
|
98
|
-
*/
|
|
99
|
-
getLanguage() {
|
|
100
|
-
return this.activeLang
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Localize a value
|
|
105
|
-
* @param {any} val - Value to localize (object with lang keys, or string)
|
|
106
|
-
* @param {string} defaultVal - Default value if not found
|
|
107
|
-
* @param {string} givenLang - Override language
|
|
108
|
-
* @param {boolean} fallbackDefaultLangVal - Fall back to default language
|
|
109
|
-
* @returns {string}
|
|
110
|
-
*/
|
|
111
|
-
localize(val, defaultVal = '', givenLang = '', fallbackDefaultLangVal = false) {
|
|
112
|
-
const lang = givenLang || this.activeLang
|
|
113
|
-
const defaultLang = this.langs[0]?.value || 'en'
|
|
114
|
-
|
|
115
|
-
if (typeof val === 'object' && !Array.isArray(val)) {
|
|
116
|
-
return fallbackDefaultLangVal
|
|
117
|
-
? val?.[lang] || val?.[defaultLang] || defaultVal
|
|
118
|
-
: val?.[lang] || defaultVal
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (typeof val === 'string') {
|
|
122
|
-
if (!val.startsWith('{') && !val.startsWith('"')) return val
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
const obj = JSON.parse(val)
|
|
126
|
-
if (typeof obj === 'object') {
|
|
127
|
-
return fallbackDefaultLangVal
|
|
128
|
-
? obj?.[lang] || obj?.[defaultLang] || defaultVal
|
|
129
|
-
: obj?.[lang] || defaultVal
|
|
130
|
-
}
|
|
131
|
-
return obj
|
|
132
|
-
} catch {
|
|
133
|
-
return val
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return defaultVal
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Get search data for all pages
|
|
142
|
-
*/
|
|
143
|
-
getSearchData() {
|
|
144
|
-
return this.pages.map((page) => ({
|
|
145
|
-
id: page.id,
|
|
146
|
-
title: page.title,
|
|
147
|
-
href: page.route,
|
|
148
|
-
route: page.route,
|
|
149
|
-
description: page.description,
|
|
150
|
-
content: page
|
|
151
|
-
.getPageBlocks()
|
|
152
|
-
.map((b) => b.title)
|
|
153
|
-
.filter(Boolean)
|
|
154
|
-
.join('\n')
|
|
155
|
-
}))
|
|
156
|
-
}
|
|
157
|
-
}
|