@uniweb/core 0.4.7 → 0.5.0

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/page.js +41 -90
  3. package/src/website.js +69 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/core",
3
- "version": "0.4.7",
3
+ "version": "0.5.0",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
package/src/page.js CHANGED
@@ -26,12 +26,11 @@ export default class Page {
26
26
  this.hideInHeader = pageData.hideInHeader || false
27
27
  this.hideInFooter = pageData.hideInFooter || false
28
28
 
29
- // Layout options (per-page overrides for header/footer/panels)
29
+ // Layout options (named layout + per-page overrides)
30
30
  this.layout = {
31
- header: pageData.layout?.header !== false,
32
- footer: pageData.layout?.footer !== false,
33
- leftPanel: pageData.layout?.leftPanel !== false,
34
- rightPanel: pageData.layout?.rightPanel !== false,
31
+ name: pageData.layout?.name || null,
32
+ hide: pageData.layout?.hide || [],
33
+ params: pageData.layout?.params || {},
35
34
  }
36
35
 
37
36
  // SEO configuration
@@ -143,17 +142,23 @@ export default class Page {
143
142
  return this.dynamicContext?.paramValue || null
144
143
  }
145
144
 
145
+ /**
146
+ * Get the resolved layout name for this page.
147
+ * Cascade: page.layout.name > foundation defaultLayout > null
148
+ * @returns {string|null}
149
+ */
150
+ getLayoutName() {
151
+ return this.layout.name || this.website?.getDefaultLayoutName() || null
152
+ }
153
+
146
154
  /**
147
155
  * Get all block groups (for Layout component)
148
- * @returns {Object} { header, body, footer, left, right }
156
+ * @returns {Object} { body, ...areas }
149
157
  */
150
158
  getBlockGroups() {
151
159
  return {
152
- header: this.getHeaderBlocks(),
153
160
  body: this.bodyBlocks,
154
- footer: this.getFooterBlocks(),
155
- left: this.getLeftBlocks(),
156
- right: this.getRightBlocks(),
161
+ ...this.getLayoutAreas(),
157
162
  }
158
163
  }
159
164
 
@@ -198,13 +203,14 @@ export default class Page {
198
203
 
199
204
  /**
200
205
  * Get all blocks (header, body, footer) as flat array
201
- * Respects page layout preferences (hasHeader, hasFooter, etc.)
206
+ * Respects page layout preferences (hide list)
202
207
  * @returns {Block[]}
203
208
  */
204
209
  getPageBlocks() {
205
210
  const blocks = []
206
- const headerBlocks = this.getHeaderBlocks()
207
- const footerBlocks = this.getFooterBlocks()
211
+ const areas = this.getLayoutAreas()
212
+ const headerBlocks = areas.header
213
+ const footerBlocks = areas.footer
208
214
 
209
215
  if (headerBlocks) blocks.push(...headerBlocks)
210
216
  blocks.push(...this.bodyBlocks)
@@ -222,69 +228,46 @@ export default class Page {
222
228
  }
223
229
 
224
230
  /**
225
- * Get header blocks (respects layout preference, delegates to website)
231
+ * Get blocks for a specific area, respecting hide list
232
+ * @param {string} areaName - Area name (e.g., 'header', 'footer', 'left')
226
233
  * @returns {Block[]|null}
227
234
  */
228
- getHeaderBlocks() {
229
- if (!this.hasHeader()) return null
230
- return this.website.getHeaderBlocks()
235
+ getAreaBlocks(areaName) {
236
+ if (this.layout.hide.includes(areaName)) return null
237
+ return this.website.getAreaBlocks(areaName, this.getLayoutName())
231
238
  }
232
239
 
233
240
  /**
234
- * Get footer blocks (respects layout preference, delegates to website)
235
- * @returns {Block[]|null}
241
+ * Get all areas for this page's layout, excluding hidden areas
242
+ * @returns {Object} Map of areaName -> Block[]
236
243
  */
237
- getFooterBlocks() {
238
- if (!this.hasFooter()) return null
239
- return this.website.getFooterBlocks()
240
- }
241
-
242
- /**
243
- * Get left panel blocks (respects layout preference, delegates to website)
244
- * @returns {Block[]|null}
245
- */
246
- getLeftBlocks() {
247
- if (!this.hasLeftPanel()) return null
248
- return this.website.getLeftBlocks()
249
- }
250
-
251
- /**
252
- * Get right panel blocks (respects layout preference, delegates to website)
253
- * @returns {Block[]|null}
254
- */
255
- getRightBlocks() {
256
- if (!this.hasRightPanel()) return null
257
- return this.website.getRightBlocks()
258
- }
259
-
260
- /**
261
- * Get header block (legacy - returns first block)
262
- * @returns {Block|null}
263
- * @deprecated Use getHeaderBlocks() instead
264
- */
265
- getHeader() {
266
- return this.getHeaderBlocks()?.[0] || null
244
+ getLayoutAreas() {
245
+ const allAreas = this.website.getLayoutAreas(this.getLayoutName())
246
+ const result = {}
247
+ for (const [name, blocks] of Object.entries(allAreas)) {
248
+ if (!this.layout.hide.includes(name)) {
249
+ result[name] = blocks
250
+ }
251
+ }
252
+ return result
267
253
  }
268
254
 
269
255
  /**
270
- * Get footer block (legacy - returns first block)
271
- * @returns {Block|null}
272
- * @deprecated Use getFooterBlocks() instead
256
+ * Get layout params for this page (merged with defaults at render time)
257
+ * @returns {Object}
273
258
  */
274
- getFooter() {
275
- return this.getFooterBlocks()?.[0] || null
259
+ getLayoutParams() {
260
+ return this.layout.params
276
261
  }
277
262
 
278
263
  /**
279
264
  * Reset block states (for scroll restoration)
280
265
  */
281
266
  resetBlockStates() {
267
+ const areas = this.getLayoutAreas()
282
268
  const allBlocks = [
283
- ...(this.getHeaderBlocks() || []),
284
269
  ...this.bodyBlocks,
285
- ...(this.getFooterBlocks() || []),
286
- ...(this.getLeftBlocks() || []),
287
- ...(this.getRightBlocks() || []),
270
+ ...Object.values(areas).flat(),
288
271
  ]
289
272
 
290
273
  for (const block of allBlocks) {
@@ -340,38 +323,6 @@ export default class Page {
340
323
  return !this.hidden && !this.hideInFooter
341
324
  }
342
325
 
343
- /**
344
- * Check if header should be rendered on this page
345
- * @returns {boolean}
346
- */
347
- hasHeader() {
348
- return this.layout.header
349
- }
350
-
351
- /**
352
- * Check if footer should be rendered on this page
353
- * @returns {boolean}
354
- */
355
- hasFooter() {
356
- return this.layout.footer
357
- }
358
-
359
- /**
360
- * Check if left panel should be rendered on this page
361
- * @returns {boolean}
362
- */
363
- hasLeftPanel() {
364
- return this.layout.leftPanel
365
- }
366
-
367
- /**
368
- * Check if right panel should be rendered on this page
369
- * @returns {boolean}
370
- */
371
- hasRightPanel() {
372
- return this.layout.rightPanel
373
- }
374
-
375
326
  /**
376
327
  * Check if page has child pages
377
328
  * @returns {boolean}
package/src/website.js CHANGED
@@ -11,19 +11,25 @@ import singularize from './singularize.js'
11
11
 
12
12
  export default class Website {
13
13
  constructor(websiteData) {
14
- const { pages = [], theme = {}, config = {}, header, footer, left, right, notFound, versionedScopes = {} } = websiteData
14
+ const { pages = [], theme = {}, config = {}, layouts, notFound, versionedScopes = {} } = websiteData
15
15
 
16
16
  // Site metadata
17
17
  this.name = config.name || ''
18
18
  this.description = config.description || ''
19
19
  this.url = config.url || ''
20
20
 
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
21
+ // General area storage: { layoutName: { areaName: Page } }
22
+ this._layoutSets = {}
23
+ if (layouts && typeof layouts === 'object') {
24
+ for (const [name, areaData] of Object.entries(layouts)) {
25
+ this._layoutSets[name] = {}
26
+ for (const [areaName, pageData] of Object.entries(areaData)) {
27
+ if (pageData) {
28
+ this._layoutSets[name][areaName] = new Page(pageData, `layout-${name}-${areaName}`, this)
29
+ }
30
+ }
31
+ }
32
+ }
27
33
 
28
34
  // Store 404 page (for SPA routing)
29
35
  // Convention: pages/404/ directory
@@ -458,9 +464,23 @@ export default class Website {
458
464
 
459
465
  /**
460
466
  * Get remote layout component from foundation config
467
+ * @param {string|null} layoutName - Named layout to look up (null = default)
461
468
  */
462
- getRemoteLayout() {
463
- return globalThis.uniweb?.foundationConfig?.Layout || null
469
+ getRemoteLayout(layoutName) {
470
+ const config = globalThis.uniweb?.foundationConfig
471
+ if (!config?.layouts) return null
472
+ if (layoutName && config.layouts[layoutName]) {
473
+ return config.layouts[layoutName]
474
+ }
475
+ return null
476
+ }
477
+
478
+ /**
479
+ * Get default layout name from foundation config
480
+ * @returns {string|null}
481
+ */
482
+ getDefaultLayoutName() {
483
+ return globalThis.uniweb?.foundationConfig?.defaultLayout || null
464
484
  }
465
485
 
466
486
  /**
@@ -471,23 +491,52 @@ export default class Website {
471
491
  }
472
492
 
473
493
  // ─────────────────────────────────────────────────────────────────
474
- // Layout Blocks (from layout panel pages)
494
+ // Layout Areas (general named areas)
475
495
  // ─────────────────────────────────────────────────────────────────
476
496
 
477
- getHeaderBlocks() {
478
- return this.headerPage?.bodyBlocks || null
479
- }
480
-
481
- getFooterBlocks() {
482
- return this.footerPage?.bodyBlocks || null
497
+ /**
498
+ * Get blocks for a specific area, with layout name resolution
499
+ * @param {string} areaName - Area name (e.g., 'header', 'footer', 'left', 'sidebar')
500
+ * @param {string} [layoutName] - Named layout to look in (falls back to 'default')
501
+ * @returns {Block[]|null}
502
+ */
503
+ getAreaBlocks(areaName, layoutName) {
504
+ if (layoutName && this._layoutSets[layoutName]) {
505
+ return this._layoutSets[layoutName][areaName]?.bodyBlocks || null
506
+ }
507
+ // Fallback to 'default' layout
508
+ if (this._layoutSets.default) {
509
+ return this._layoutSets.default[areaName]?.bodyBlocks || null
510
+ }
511
+ return null
483
512
  }
484
513
 
485
- getLeftBlocks() {
486
- return this.leftPage?.bodyBlocks || null
514
+ /**
515
+ * Get all areas for a layout as { areaName: Block[] }
516
+ * @param {string} [layoutName] - Named layout (falls back to 'default')
517
+ * @returns {Object} Map of areaName -> Block[]
518
+ */
519
+ getLayoutAreas(layoutName) {
520
+ const setName = layoutName || 'default'
521
+ const layoutSet = this._layoutSets[setName] || this._layoutSets.default
522
+ if (!layoutSet) return {}
523
+
524
+ const areas = {}
525
+ for (const [areaName, page] of Object.entries(layoutSet)) {
526
+ if (page?.bodyBlocks) {
527
+ areas[areaName] = page.bodyBlocks
528
+ }
529
+ }
530
+ return areas
487
531
  }
488
532
 
489
- getRightBlocks() {
490
- return this.rightPage?.bodyBlocks || null
533
+ /**
534
+ * Get layout metadata from foundation config
535
+ * @param {string} layoutName - Layout name
536
+ * @returns {Object|null} Layout meta { areas, transitions, defaults }
537
+ */
538
+ getLayoutMeta(layoutName) {
539
+ return globalThis.uniweb?.foundationConfig?.layoutMeta?.[layoutName] || null
491
540
  }
492
541
 
493
542
  /**