@uniweb/core 0.4.5 → 0.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/core",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
package/src/block.js CHANGED
@@ -8,17 +8,14 @@
8
8
  import { parseContent as parseSemanticContent } from '@uniweb/semantic-parser'
9
9
 
10
10
  export default class Block {
11
- constructor(blockData, id) {
11
+ constructor(blockData, id, page) {
12
12
  this.id = id
13
13
  this.stableId = blockData.stableId || null // Stable section ID for scroll targeting (from filename or frontmatter)
14
- // 'type' matches frontmatter convention; 'component' supported for backwards compatibility
15
- this.type = blockData.type || blockData.component || 'Section'
14
+ this.page = page
15
+ this.website = page.website
16
+ this.type = blockData.type || this.website.getDefaultBlockType()
16
17
  this.Component = null
17
18
 
18
- // Back-references (set by Page when creating blocks)
19
- this.page = null
20
- this.website = null
21
-
22
19
  // Content structure
23
20
  // The content can be:
24
21
  // 1. Raw ProseMirror content (from content collection)
@@ -77,7 +74,7 @@ export default class Block {
77
74
 
78
75
  // Child blocks (subsections)
79
76
  this.childBlocks = blockData.subsections
80
- ? blockData.subsections.map((block, i) => new Block(block, `${id}_${i}`))
77
+ ? blockData.subsections.map((block, i) => new Block(block, `${id}_${i}`, this.page))
81
78
  : []
82
79
 
83
80
  // Fetch configuration (from section frontmatter)
package/src/page.js CHANGED
@@ -8,15 +8,7 @@
8
8
  import Block from './block.js'
9
9
 
10
10
  export default class Page {
11
- constructor(
12
- pageData,
13
- id,
14
- website,
15
- pageHeader,
16
- pageFooter,
17
- pageLeft,
18
- pageRight,
19
- ) {
11
+ constructor(pageData, id, website) {
20
12
  this.id = id
21
13
  this.stableId = pageData.id || null // Stable page ID for page: links (from page.yml)
22
14
  this.route = pageData.route
@@ -77,14 +69,25 @@ export default class Page {
77
69
  this.versionMeta = pageData.versionMeta || null // { versions, latestId }
78
70
  this.versionScope = pageData.versionScope || null // The route where versioning starts
79
71
 
80
- // Build block groups for all layout areas
81
- this.pageBlocks = this.buildPageBlocks(
82
- pageData.sections,
83
- pageHeader?.sections,
84
- pageFooter?.sections,
85
- pageLeft?.sections,
86
- pageRight?.sections,
87
- )
72
+ // Store raw section data for lazy block building
73
+ // Blocks are created on first access (when page is rendered), not during Website init
74
+ // This ensures foundationConfig is available for getDefaultBlockType()
75
+ // Layout panels (header, footer, left, right) are shared at Website level
76
+ this._bodySections = pageData.sections
77
+ this._bodyBlocks = null
78
+ }
79
+
80
+ /**
81
+ * Lazy getter for body blocks
82
+ * Blocks are built on first access, ensuring foundation is loaded
83
+ */
84
+ get bodyBlocks() {
85
+ if (!this._bodyBlocks) {
86
+ this._bodyBlocks = (this._bodySections || []).map(
87
+ (section, index) => new Block(section, index, this)
88
+ )
89
+ }
90
+ return this._bodyBlocks
88
91
  }
89
92
 
90
93
  /**
@@ -140,66 +143,18 @@ export default class Page {
140
143
  return this.dynamicContext?.paramValue || null
141
144
  }
142
145
 
143
- /**
144
- * Build the page block structure for all layout areas.
145
- * Each area can have multiple sections/blocks.
146
- *
147
- * @param {Array} body - Body sections from page content
148
- * @param {Array} header - Header sections from @header page
149
- * @param {Array} footer - Footer sections from @footer page
150
- * @param {Array} left - Left panel sections from @left page
151
- * @param {Array} right - Right panel sections from @right page
152
- * @returns {Object} Block groups for each layout area
153
- */
154
- buildPageBlocks(body, header, footer, left, right) {
155
- const buildBlocks = (sections, prefix) => {
156
- if (!sections || sections.length === 0) return null
157
- return sections.map((section, index) => {
158
- const block = new Block(section, `${prefix}-${index}`)
159
- this.initBlockReferences(block)
160
- return block
161
- })
162
- }
163
-
164
- const bodyBlocks = (body || []).map((section, index) => {
165
- const block = new Block(section, index)
166
- this.initBlockReferences(block)
167
- return block
168
- })
169
-
170
- return {
171
- header: buildBlocks(header, 'header'),
172
- body: bodyBlocks,
173
- footer: buildBlocks(footer, 'footer'),
174
- left: buildBlocks(left, 'left'),
175
- right: buildBlocks(right, 'right'),
176
- }
177
- }
178
-
179
- /**
180
- * Initialize block back-references to page and website.
181
- * Also recursively sets references for child blocks.
182
- *
183
- * @param {Block} block - The block to initialize
184
- */
185
- initBlockReferences(block) {
186
- block.page = this
187
- block.website = this.website
188
-
189
- // Recursively set references for child blocks
190
- if (block.childBlocks?.length) {
191
- for (const childBlock of block.childBlocks) {
192
- this.initBlockReferences(childBlock)
193
- }
194
- }
195
- }
196
-
197
146
  /**
198
147
  * Get all block groups (for Layout component)
199
148
  * @returns {Object} { header, body, footer, left, right }
200
149
  */
201
150
  getBlockGroups() {
202
- return this.pageBlocks
151
+ return {
152
+ header: this.getHeaderBlocks(),
153
+ body: this.bodyBlocks,
154
+ footer: this.getFooterBlocks(),
155
+ left: this.getLeftBlocks(),
156
+ right: this.getRightBlocks(),
157
+ }
203
158
  }
204
159
 
205
160
  // ─────────────────────────────────────────────────────────────────
@@ -238,8 +193,7 @@ export default class Page {
238
193
  * @returns {Object|null} First body block's info or null
239
194
  */
240
195
  getFirstBodyBlockInfo() {
241
- const bodyBlocks = this.pageBlocks.body
242
- return bodyBlocks?.[0]?.getBlockInfo() || null
196
+ return this.bodyBlocks?.[0]?.getBlockInfo() || null
243
197
  }
244
198
 
245
199
  /**
@@ -249,62 +203,58 @@ export default class Page {
249
203
  */
250
204
  getPageBlocks() {
251
205
  const blocks = []
206
+ const headerBlocks = this.getHeaderBlocks()
207
+ const footerBlocks = this.getFooterBlocks()
252
208
 
253
- if (this.hasHeader() && this.pageBlocks.header) {
254
- blocks.push(...this.pageBlocks.header)
255
- }
256
-
257
- blocks.push(...this.pageBlocks.body)
258
-
259
- if (this.hasFooter() && this.pageBlocks.footer) {
260
- blocks.push(...this.pageBlocks.footer)
261
- }
209
+ if (headerBlocks) blocks.push(...headerBlocks)
210
+ blocks.push(...this.bodyBlocks)
211
+ if (footerBlocks) blocks.push(...footerBlocks)
262
212
 
263
213
  return blocks
264
214
  }
265
215
 
266
216
  /**
267
- * Get just body blocks
217
+ * Get body blocks
268
218
  * @returns {Block[]}
269
219
  */
270
220
  getBodyBlocks() {
271
- return this.pageBlocks.body
221
+ return this.bodyBlocks
272
222
  }
273
223
 
274
224
  /**
275
- * Get header blocks (respects layout preference)
225
+ * Get header blocks (respects layout preference, delegates to website)
276
226
  * @returns {Block[]|null}
277
227
  */
278
228
  getHeaderBlocks() {
279
229
  if (!this.hasHeader()) return null
280
- return this.pageBlocks.header
230
+ return this.website.getHeaderBlocks()
281
231
  }
282
232
 
283
233
  /**
284
- * Get footer blocks (respects layout preference)
234
+ * Get footer blocks (respects layout preference, delegates to website)
285
235
  * @returns {Block[]|null}
286
236
  */
287
237
  getFooterBlocks() {
288
238
  if (!this.hasFooter()) return null
289
- return this.pageBlocks.footer
239
+ return this.website.getFooterBlocks()
290
240
  }
291
241
 
292
242
  /**
293
- * Get left panel blocks (respects layout preference)
243
+ * Get left panel blocks (respects layout preference, delegates to website)
294
244
  * @returns {Block[]|null}
295
245
  */
296
246
  getLeftBlocks() {
297
247
  if (!this.hasLeftPanel()) return null
298
- return this.pageBlocks.left
248
+ return this.website.getLeftBlocks()
299
249
  }
300
250
 
301
251
  /**
302
- * Get right panel blocks (respects layout preference)
252
+ * Get right panel blocks (respects layout preference, delegates to website)
303
253
  * @returns {Block[]|null}
304
254
  */
305
255
  getRightBlocks() {
306
256
  if (!this.hasRightPanel()) return null
307
- return this.pageBlocks.right
257
+ return this.website.getRightBlocks()
308
258
  }
309
259
 
310
260
  /**
@@ -313,7 +263,7 @@ export default class Page {
313
263
  * @deprecated Use getHeaderBlocks() instead
314
264
  */
315
265
  getHeader() {
316
- return this.pageBlocks.header?.[0] || null
266
+ return this.getHeaderBlocks()?.[0] || null
317
267
  }
318
268
 
319
269
  /**
@@ -322,7 +272,7 @@ export default class Page {
322
272
  * @deprecated Use getFooterBlocks() instead
323
273
  */
324
274
  getFooter() {
325
- return this.pageBlocks.footer?.[0] || null
275
+ return this.getFooterBlocks()?.[0] || null
326
276
  }
327
277
 
328
278
  /**
@@ -330,11 +280,11 @@ export default class Page {
330
280
  */
331
281
  resetBlockStates() {
332
282
  const allBlocks = [
333
- ...(this.pageBlocks.header || []),
334
- ...this.pageBlocks.body,
335
- ...(this.pageBlocks.footer || []),
336
- ...(this.pageBlocks.left || []),
337
- ...(this.pageBlocks.right || []),
283
+ ...(this.getHeaderBlocks() || []),
284
+ ...this.bodyBlocks,
285
+ ...(this.getFooterBlocks() || []),
286
+ ...(this.getLeftBlocks() || []),
287
+ ...(this.getRightBlocks() || []),
338
288
  ]
339
289
 
340
290
  for (const block of allBlocks) {
@@ -435,7 +385,7 @@ export default class Page {
435
385
  * @returns {boolean}
436
386
  */
437
387
  hasContent() {
438
- return this.pageBlocks.body.length > 0
388
+ return this.bodyBlocks.length > 0
439
389
  }
440
390
 
441
391
  // ─────────────────────────────────────────────────────────────────
package/src/website.js CHANGED
@@ -18,12 +18,12 @@ export default class Website {
18
18
  this.description = config.description || ''
19
19
  this.url = config.url || ''
20
20
 
21
- // Store layout panels (header, footer, left, right)
22
- // These come from top-level properties set by content-collector
23
- this.headerPage = header || null
24
- this.footerPage = footer || null
25
- this.leftPage = left || null
26
- this.rightPage = right || null
21
+ // Layout panels as Page objects (header, footer, left, right)
22
+ // They build their own blocks lazily, just like regular pages
23
+ this.headerPage = header ? new Page(header, 'header', this) : null
24
+ this.footerPage = footer ? new Page(footer, 'footer', this) : null
25
+ this.leftPage = left ? new Page(left, 'left', this) : null
26
+ this.rightPage = right ? new Page(right, 'right', this) : null
27
27
 
28
28
  // Store 404 page (for SPA routing)
29
29
  // Convention: pages/404/ directory
@@ -44,8 +44,7 @@ export default class Website {
44
44
  this._dynamicPageCache = new Map()
45
45
 
46
46
  this.pages = regularPages.map(
47
- (page, index) =>
48
- new Page(page, index, this, this.headerPage, this.footerPage, this.leftPage, this.rightPage)
47
+ (page, index) => new Page(page, index, this)
49
48
  )
50
49
 
51
50
  // Build parent-child relationships based on route structure
@@ -416,15 +415,7 @@ export default class Website {
416
415
  }
417
416
 
418
417
  // Create the page instance
419
- const dynamicPage = new Page(
420
- pageData,
421
- `dynamic-${concreteRoute}`,
422
- this,
423
- this.headerPage,
424
- this.footerPage,
425
- this.leftPage,
426
- this.rightPage
427
- )
418
+ const dynamicPage = new Page(pageData, `dynamic-${concreteRoute}`, this)
428
419
 
429
420
  // Copy parent reference from template
430
421
  dynamicPage.parent = templatePage.parent
@@ -472,6 +463,33 @@ export default class Website {
472
463
  return globalThis.uniweb?.foundationConfig?.Layout || null
473
464
  }
474
465
 
466
+ /**
467
+ * Get default block type from foundation config
468
+ */
469
+ getDefaultBlockType() {
470
+ return globalThis.uniweb?.foundationConfig?.defaultType || 'Section'
471
+ }
472
+
473
+ // ─────────────────────────────────────────────────────────────────
474
+ // Layout Blocks (from layout panel pages)
475
+ // ─────────────────────────────────────────────────────────────────
476
+
477
+ getHeaderBlocks() {
478
+ return this.headerPage?.bodyBlocks || null
479
+ }
480
+
481
+ getFooterBlocks() {
482
+ return this.footerPage?.bodyBlocks || null
483
+ }
484
+
485
+ getLeftBlocks() {
486
+ return this.leftPage?.bodyBlocks || null
487
+ }
488
+
489
+ getRightBlocks() {
490
+ return this.rightPage?.bodyBlocks || null
491
+ }
492
+
475
493
  /**
476
494
  * Get remote props from foundation config
477
495
  */