@uniweb/core 0.1.5 → 0.1.6
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 +76 -8
- package/src/page.js +74 -6
- package/src/website.js +53 -7
package/package.json
CHANGED
package/src/block.js
CHANGED
|
@@ -10,9 +10,14 @@ import { parseContent as parseSemanticContent } from '@uniweb/semantic-parser'
|
|
|
10
10
|
export default class Block {
|
|
11
11
|
constructor(blockData, id) {
|
|
12
12
|
this.id = id
|
|
13
|
-
|
|
13
|
+
// 'type' matches frontmatter convention; 'component' supported for backwards compatibility
|
|
14
|
+
this.type = blockData.type || blockData.component || 'Section'
|
|
14
15
|
this.Component = null
|
|
15
16
|
|
|
17
|
+
// Back-references (set by Page when creating blocks)
|
|
18
|
+
this.page = null
|
|
19
|
+
this.website = null
|
|
20
|
+
|
|
16
21
|
// Content structure
|
|
17
22
|
// The content can be:
|
|
18
23
|
// 1. Raw ProseMirror content (from content collection)
|
|
@@ -28,7 +33,7 @@ export default class Block {
|
|
|
28
33
|
// Block configuration
|
|
29
34
|
const blockConfig = blockData.params || blockData.config || {}
|
|
30
35
|
this.preset = blockData.preset
|
|
31
|
-
this.themeName =
|
|
36
|
+
this.themeName = blockConfig.theme || 'light'
|
|
32
37
|
this.standardOptions = blockConfig.standardOptions || {}
|
|
33
38
|
this.properties = blockConfig.properties || blockConfig
|
|
34
39
|
|
|
@@ -40,10 +45,13 @@ export default class Block {
|
|
|
40
45
|
// Input data
|
|
41
46
|
this.input = blockData.input || null
|
|
42
47
|
|
|
43
|
-
// State management
|
|
48
|
+
// State management (dynamic, can change at runtime)
|
|
44
49
|
this.startState = null
|
|
45
50
|
this.state = null
|
|
46
51
|
this.resetStateHook = null
|
|
52
|
+
|
|
53
|
+
// Context (static, defined per component type)
|
|
54
|
+
this.context = null
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
/**
|
|
@@ -160,18 +168,25 @@ export default class Block {
|
|
|
160
168
|
initComponent() {
|
|
161
169
|
if (this.Component) return this.Component
|
|
162
170
|
|
|
163
|
-
this.Component = globalThis.uniweb?.getComponent(this.
|
|
171
|
+
this.Component = globalThis.uniweb?.getComponent(this.type)
|
|
164
172
|
|
|
165
173
|
if (!this.Component) {
|
|
166
|
-
console.warn(`[Block] Component not found: ${this.
|
|
174
|
+
console.warn(`[Block] Component not found: ${this.type}`)
|
|
167
175
|
return null
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
this.
|
|
178
|
+
// Get component-level block configuration
|
|
179
|
+
// Supports: Component.block (preferred), Component.blockDefaults (legacy)
|
|
180
|
+
const blockConfig = this.Component.block || this.Component.blockDefaults || {}
|
|
181
|
+
|
|
182
|
+
// Initialize state (dynamic, can change at runtime)
|
|
183
|
+
const stateDefaults = blockConfig.state || this.Component.blockState
|
|
184
|
+
this.startState = stateDefaults ? { ...stateDefaults } : null
|
|
173
185
|
this.initState()
|
|
174
186
|
|
|
187
|
+
// Initialize context (static, per component type)
|
|
188
|
+
this.context = blockConfig.context ? { ...blockConfig.context } : null
|
|
189
|
+
|
|
175
190
|
return this.Component
|
|
176
191
|
}
|
|
177
192
|
|
|
@@ -243,6 +258,59 @@ export default class Block {
|
|
|
243
258
|
if (this.resetStateHook) this.resetStateHook()
|
|
244
259
|
}
|
|
245
260
|
|
|
261
|
+
// ─────────────────────────────────────────────────────────────────
|
|
262
|
+
// Cross-Block Communication
|
|
263
|
+
// ─────────────────────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get this block's index within its page.
|
|
267
|
+
* Useful for finding neighboring blocks.
|
|
268
|
+
*
|
|
269
|
+
* @returns {number} The index, or -1 if not found
|
|
270
|
+
*/
|
|
271
|
+
getIndex() {
|
|
272
|
+
if (!this.page) return -1
|
|
273
|
+
return this.page.getBlockIndex(this)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get information about this block for cross-component communication.
|
|
278
|
+
* Other components (like NavBar) can use this to adapt their behavior.
|
|
279
|
+
*
|
|
280
|
+
* @returns {Object} Block info: { type, theme, state, context }
|
|
281
|
+
*/
|
|
282
|
+
getBlockInfo() {
|
|
283
|
+
return {
|
|
284
|
+
type: this.type,
|
|
285
|
+
theme: this.themeName,
|
|
286
|
+
state: this.state,
|
|
287
|
+
context: this.context
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get information about the next block in the page.
|
|
293
|
+
* Commonly used by headers/navbars to adapt to the first content section.
|
|
294
|
+
*
|
|
295
|
+
* @returns {Object|null} Next block's info or null
|
|
296
|
+
*/
|
|
297
|
+
getNextBlockInfo() {
|
|
298
|
+
const index = this.getIndex()
|
|
299
|
+
if (index < 0 || !this.page) return null
|
|
300
|
+
return this.page.getBlockInfo(index + 1)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Get information about the previous block in the page.
|
|
305
|
+
*
|
|
306
|
+
* @returns {Object|null} Previous block's info or null
|
|
307
|
+
*/
|
|
308
|
+
getPrevBlockInfo() {
|
|
309
|
+
const index = this.getIndex()
|
|
310
|
+
if (index <= 0 || !this.page) return null
|
|
311
|
+
return this.page.getBlockInfo(index - 1)
|
|
312
|
+
}
|
|
313
|
+
|
|
246
314
|
/**
|
|
247
315
|
* React hook for block state management
|
|
248
316
|
* @param {Function} useState - React useState hook
|
package/src/page.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import Block from './block.js'
|
|
9
9
|
|
|
10
10
|
export default class Page {
|
|
11
|
-
constructor(pageData, id, pageHeader, pageFooter, pageLeft, pageRight) {
|
|
11
|
+
constructor(pageData, id, website, pageHeader, pageFooter, pageLeft, pageRight) {
|
|
12
12
|
this.id = id
|
|
13
13
|
this.route = pageData.route
|
|
14
14
|
this.title = pageData.title || ''
|
|
@@ -48,9 +48,9 @@ export default class Page {
|
|
|
48
48
|
// Child pages (for nested hierarchy) - populated by Website
|
|
49
49
|
this.children = []
|
|
50
50
|
|
|
51
|
-
// Back-reference to website
|
|
52
|
-
this.website =
|
|
53
|
-
this.site =
|
|
51
|
+
// Back-reference to website
|
|
52
|
+
this.website = website
|
|
53
|
+
this.site = website // Alias
|
|
54
54
|
|
|
55
55
|
// Scroll position memory (for navigation restoration)
|
|
56
56
|
this.scrollY = 0
|
|
@@ -99,18 +99,46 @@ export default class Page {
|
|
|
99
99
|
buildPageBlocks(body, header, footer, left, right) {
|
|
100
100
|
const buildBlocks = (sections, prefix) => {
|
|
101
101
|
if (!sections || sections.length === 0) return null
|
|
102
|
-
return sections.map((section, index) =>
|
|
102
|
+
return sections.map((section, index) => {
|
|
103
|
+
const block = new Block(section, `${prefix}-${index}`)
|
|
104
|
+
this.initBlockReferences(block)
|
|
105
|
+
return block
|
|
106
|
+
})
|
|
103
107
|
}
|
|
104
108
|
|
|
109
|
+
const bodyBlocks = (body || []).map((section, index) => {
|
|
110
|
+
const block = new Block(section, index)
|
|
111
|
+
this.initBlockReferences(block)
|
|
112
|
+
return block
|
|
113
|
+
})
|
|
114
|
+
|
|
105
115
|
return {
|
|
106
116
|
header: buildBlocks(header, 'header'),
|
|
107
|
-
body:
|
|
117
|
+
body: bodyBlocks,
|
|
108
118
|
footer: buildBlocks(footer, 'footer'),
|
|
109
119
|
left: buildBlocks(left, 'left'),
|
|
110
120
|
right: buildBlocks(right, 'right')
|
|
111
121
|
}
|
|
112
122
|
}
|
|
113
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Initialize block back-references to page and website.
|
|
126
|
+
* Also recursively sets references for child blocks.
|
|
127
|
+
*
|
|
128
|
+
* @param {Block} block - The block to initialize
|
|
129
|
+
*/
|
|
130
|
+
initBlockReferences(block) {
|
|
131
|
+
block.page = this
|
|
132
|
+
block.website = this.website
|
|
133
|
+
|
|
134
|
+
// Recursively set references for child blocks
|
|
135
|
+
if (block.childBlocks?.length) {
|
|
136
|
+
for (const childBlock of block.childBlocks) {
|
|
137
|
+
this.initBlockReferences(childBlock)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
114
142
|
/**
|
|
115
143
|
* Get all block groups (for Layout component)
|
|
116
144
|
* @returns {Object} { header, body, footer, left, right }
|
|
@@ -119,6 +147,46 @@ export default class Page {
|
|
|
119
147
|
return this.pageBlocks
|
|
120
148
|
}
|
|
121
149
|
|
|
150
|
+
// ─────────────────────────────────────────────────────────────────
|
|
151
|
+
// Cross-Block Communication
|
|
152
|
+
// ─────────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Find a block's index within the page's block list.
|
|
156
|
+
* Searches across all layout areas (header, body, footer, left, right).
|
|
157
|
+
*
|
|
158
|
+
* @param {Block} block - The block to find
|
|
159
|
+
* @returns {number} The index in the flat list, or -1 if not found
|
|
160
|
+
*/
|
|
161
|
+
getBlockIndex(block) {
|
|
162
|
+
const allBlocks = this.getPageBlocks()
|
|
163
|
+
return allBlocks.indexOf(block)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get information about a block at a specific index.
|
|
168
|
+
* Used for cross-component communication (e.g., NavBar checking Hero's theme).
|
|
169
|
+
*
|
|
170
|
+
* @param {number} index - The block index
|
|
171
|
+
* @returns {Object|null} Block info { theme, component, state } or null
|
|
172
|
+
*/
|
|
173
|
+
getBlockInfo(index) {
|
|
174
|
+
const allBlocks = this.getPageBlocks()
|
|
175
|
+
const block = allBlocks[index]
|
|
176
|
+
return block?.getBlockInfo() || null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get the first body block's info.
|
|
181
|
+
* Common use case: NavBar checking if first section supports overlay.
|
|
182
|
+
*
|
|
183
|
+
* @returns {Object|null} First body block's info or null
|
|
184
|
+
*/
|
|
185
|
+
getFirstBodyBlockInfo() {
|
|
186
|
+
const bodyBlocks = this.pageBlocks.body
|
|
187
|
+
return bodyBlocks?.[0]?.getBlockInfo() || null
|
|
188
|
+
}
|
|
189
|
+
|
|
122
190
|
/**
|
|
123
191
|
* Get all blocks (header, body, footer) as flat array
|
|
124
192
|
* Respects page layout preferences (hasHeader, hasFooter, etc.)
|
package/src/website.js
CHANGED
|
@@ -46,15 +46,9 @@ export default class Website {
|
|
|
46
46
|
.filter((page) => !specialRoutes.includes(page.route))
|
|
47
47
|
.map(
|
|
48
48
|
(page, index) =>
|
|
49
|
-
new Page(page, index, this.headerPage, this.footerPage, this.leftPage, this.rightPage)
|
|
49
|
+
new Page(page, index, this, this.headerPage, this.footerPage, this.leftPage, this.rightPage)
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
-
// Set reference from pages back to website
|
|
53
|
-
for (const page of this.pages) {
|
|
54
|
-
page.website = this
|
|
55
|
-
page.site = this // Alias
|
|
56
|
-
}
|
|
57
|
-
|
|
58
52
|
this.activePage =
|
|
59
53
|
this.pages.find((page) => page.route === '/' || page.route === '/index') ||
|
|
60
54
|
this.pages[0]
|
|
@@ -287,8 +281,60 @@ export default class Website {
|
|
|
287
281
|
return defaultVal
|
|
288
282
|
}
|
|
289
283
|
|
|
284
|
+
// ─────────────────────────────────────────────────────────────────
|
|
285
|
+
// Search API
|
|
286
|
+
// ─────────────────────────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Check if search is enabled for this site
|
|
290
|
+
* @returns {boolean}
|
|
291
|
+
*/
|
|
292
|
+
isSearchEnabled() {
|
|
293
|
+
// Search is enabled by default unless explicitly disabled
|
|
294
|
+
return this.config?.search?.enabled !== false
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get search configuration
|
|
299
|
+
* @returns {Object} Search configuration
|
|
300
|
+
*/
|
|
301
|
+
getSearchConfig() {
|
|
302
|
+
const config = this.config?.search || {}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
enabled: this.isSearchEnabled(),
|
|
306
|
+
indexUrl: this.getSearchIndexUrl(),
|
|
307
|
+
locale: this.getActiveLocale(),
|
|
308
|
+
include: {
|
|
309
|
+
pages: config.include?.pages !== false,
|
|
310
|
+
sections: config.include?.sections !== false,
|
|
311
|
+
headings: config.include?.headings !== false,
|
|
312
|
+
paragraphs: config.include?.paragraphs !== false,
|
|
313
|
+
links: config.include?.links !== false,
|
|
314
|
+
lists: config.include?.lists !== false
|
|
315
|
+
},
|
|
316
|
+
exclude: {
|
|
317
|
+
routes: config.exclude?.routes || [],
|
|
318
|
+
components: config.exclude?.components || []
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get the URL for the search index file
|
|
325
|
+
* @returns {string} URL to fetch the search index
|
|
326
|
+
*/
|
|
327
|
+
getSearchIndexUrl() {
|
|
328
|
+
const locale = this.getActiveLocale()
|
|
329
|
+
const isDefault = locale === this.getDefaultLocale()
|
|
330
|
+
|
|
331
|
+
// Default locale uses root path, others use locale prefix
|
|
332
|
+
return isDefault ? '/search-index.json' : `/${locale}/search-index.json`
|
|
333
|
+
}
|
|
334
|
+
|
|
290
335
|
/**
|
|
291
336
|
* Get search data for all pages
|
|
337
|
+
* @deprecated Use getSearchConfig() and fetch the search index instead
|
|
292
338
|
*/
|
|
293
339
|
getSearchData() {
|
|
294
340
|
return this.pages.map((page) => ({
|