@uniweb/core 0.5.22 → 0.6.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.
- package/package.json +1 -1
- package/src/block.js +15 -0
- package/src/page.js +64 -2
- package/src/website.js +11 -0
package/package.json
CHANGED
package/src/block.js
CHANGED
|
@@ -215,6 +215,21 @@ export default class Block {
|
|
|
215
215
|
return content.groups
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// Foundation content handler hook
|
|
219
|
+
// Runs BEFORE semantic parsing. Foundations can declare a handler
|
|
220
|
+
// in foundation.js `handlers.content` to transform raw ProseMirror
|
|
221
|
+
// content — typically for template engine instantiation of
|
|
222
|
+
// {placeholder} expressions against profile/report data.
|
|
223
|
+
const contentHandler = globalThis.uniweb?.foundationConfig?.handlers?.content
|
|
224
|
+
if (contentHandler && typeof contentHandler === 'function') {
|
|
225
|
+
try {
|
|
226
|
+
const transformed = contentHandler(content, this)
|
|
227
|
+
if (transformed !== undefined) content = transformed
|
|
228
|
+
} catch (err) {
|
|
229
|
+
console.error('Foundation content handler failed:', err)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
218
233
|
// ProseMirror document - use semantic-parser
|
|
219
234
|
if (content?.type === 'doc') {
|
|
220
235
|
return this.extractFromProseMirror(content)
|
package/src/page.js
CHANGED
|
@@ -73,13 +73,25 @@ export default class Page {
|
|
|
73
73
|
this.versionMeta = pageData.versionMeta || null // { versions, latestId }
|
|
74
74
|
this.versionScope = pageData.versionScope || null // The route where versioning starts
|
|
75
75
|
|
|
76
|
+
// Build-time flag: does this page have renderable content?
|
|
77
|
+
// Distinct from "are sections loaded?" — content-less containers
|
|
78
|
+
// (folders with page.yml but no markdown) are always false.
|
|
79
|
+
// Falls back to checking sections for backward compat (non-split mode).
|
|
80
|
+
this._hasContent = pageData.hasContent ?? (pageData.sections?.length > 0)
|
|
81
|
+
|
|
76
82
|
// Store raw section data for lazy block building
|
|
77
83
|
// Blocks are created on first access (when page is rendered), not during Website init
|
|
78
84
|
// This ensures foundationConfig is available for getDefaultBlockType()
|
|
79
85
|
// Layout panels (header, footer, left, right) are shared at Website level
|
|
86
|
+
// undefined = not yet loaded (split mode, non-current page)
|
|
87
|
+
// [] = loaded but empty (content-less container)
|
|
88
|
+
// [...] = loaded with content
|
|
80
89
|
this._bodySections = pageData.sections
|
|
81
90
|
this._bodyBlocks = null
|
|
82
91
|
|
|
92
|
+
// Guard against concurrent loadContent() calls
|
|
93
|
+
this._loadingContent = null
|
|
94
|
+
|
|
83
95
|
Object.seal(this)
|
|
84
96
|
}
|
|
85
97
|
|
|
@@ -89,6 +101,9 @@ export default class Page {
|
|
|
89
101
|
*/
|
|
90
102
|
get bodyBlocks() {
|
|
91
103
|
if (!this._bodyBlocks) {
|
|
104
|
+
// If sections haven't been loaded yet (split mode), return empty array.
|
|
105
|
+
// PageRenderer will call loadContent() before rendering.
|
|
106
|
+
if (this._bodySections === undefined) return []
|
|
92
107
|
this._bodyBlocks = (this._bodySections || []).map(
|
|
93
108
|
(section, index) => new Block(section, index, this)
|
|
94
109
|
)
|
|
@@ -360,11 +375,58 @@ export default class Page {
|
|
|
360
375
|
}
|
|
361
376
|
|
|
362
377
|
/**
|
|
363
|
-
* Check if page has body content (sections)
|
|
378
|
+
* Check if page has body content (sections).
|
|
379
|
+
* Uses a build-time flag — always reflects whether the page has markdown,
|
|
380
|
+
* regardless of whether section content has been loaded yet (split mode).
|
|
364
381
|
* @returns {boolean}
|
|
365
382
|
*/
|
|
366
383
|
hasContent() {
|
|
367
|
-
return this.
|
|
384
|
+
return this._hasContent
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Check if section content has been loaded.
|
|
389
|
+
* In non-split mode, always true (sections are always present).
|
|
390
|
+
* In split mode, true for the pre-embedded current page and any page
|
|
391
|
+
* whose content has been fetched via loadContent().
|
|
392
|
+
* @returns {boolean}
|
|
393
|
+
*/
|
|
394
|
+
isContentLoaded() {
|
|
395
|
+
return this._bodySections !== undefined
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Fetch and store section content from the server.
|
|
400
|
+
* Deduplicates concurrent calls (e.g., rapid navigation).
|
|
401
|
+
* No-op if content is already loaded or embedded.
|
|
402
|
+
* @returns {Promise<void>}
|
|
403
|
+
*/
|
|
404
|
+
async loadContent() {
|
|
405
|
+
if (this._bodySections !== undefined) return // already loaded or embedded
|
|
406
|
+
if (this._loadingContent) return this._loadingContent // deduplicate
|
|
407
|
+
|
|
408
|
+
this._loadingContent = (async () => {
|
|
409
|
+
try {
|
|
410
|
+
const base = this.website.basePath || ''
|
|
411
|
+
// Locale-aware URL: non-default locale pages live under /{locale}/_pages/
|
|
412
|
+
const localePrefix = this.website.activeLocale !== this.website.defaultLocale
|
|
413
|
+
? `/${this.website.activeLocale}` : ''
|
|
414
|
+
const routePath = this.route === '/' ? '/index' : this.route
|
|
415
|
+
const res = await fetch(`${base}${localePrefix}/_pages${routePath}.json`)
|
|
416
|
+
if (!res.ok) {
|
|
417
|
+
console.warn(`[Page] Failed to load content for ${this.route}: ${res.status}`)
|
|
418
|
+
this._bodySections = [] // Mark as loaded (empty) to prevent retries
|
|
419
|
+
return
|
|
420
|
+
}
|
|
421
|
+
const data = await res.json()
|
|
422
|
+
this._bodySections = data.sections || []
|
|
423
|
+
this._bodyBlocks = null // Reset lazy cache so getter rebuilds
|
|
424
|
+
} finally {
|
|
425
|
+
this._loadingContent = null
|
|
426
|
+
}
|
|
427
|
+
})()
|
|
428
|
+
|
|
429
|
+
return this._loadingContent
|
|
368
430
|
}
|
|
369
431
|
|
|
370
432
|
// ─────────────────────────────────────────────────────────────────
|
package/src/website.js
CHANGED
|
@@ -605,6 +605,17 @@ export default class Website {
|
|
|
605
605
|
return globalThis.uniweb?.foundationConfig?.layoutMeta?.[layoutName] || null
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
+
/**
|
|
609
|
+
* Whether view transitions are enabled for SPA navigation.
|
|
610
|
+
* Defaults to true — the browser's default crossfade is progressive
|
|
611
|
+
* enhancement with no downside. Foundations can set viewTransitions: false
|
|
612
|
+
* in foundation.js to disable.
|
|
613
|
+
* @type {boolean}
|
|
614
|
+
*/
|
|
615
|
+
get viewTransitions() {
|
|
616
|
+
return globalThis.uniweb.foundationConfig?.viewTransitions !== false
|
|
617
|
+
}
|
|
618
|
+
|
|
608
619
|
/**
|
|
609
620
|
* Get remote props from foundation config
|
|
610
621
|
*/
|