@uniweb/core 0.7.13 → 0.7.15
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 +26 -15
- package/src/seo.js +28 -0
- package/src/website.js +9 -0
package/package.json
CHANGED
package/src/block.js
CHANGED
|
@@ -437,6 +437,15 @@ export default class Block {
|
|
|
437
437
|
* @returns {Object|null} Next block's info or null
|
|
438
438
|
*/
|
|
439
439
|
getNextBlockInfo() {
|
|
440
|
+
// Layout-area blocks (header/footer/panels) live on a shared, contentless
|
|
441
|
+
// area page — not the content page being rendered — so walking their own
|
|
442
|
+
// page's sequence never reaches the content. Their "next block" is the
|
|
443
|
+
// first content section of the active page (a header adapting to the
|
|
444
|
+
// section it floats over). Resolve against the active page instead.
|
|
445
|
+
const active = this.website?.activePage
|
|
446
|
+
if (active && active !== this.page) {
|
|
447
|
+
return active.getFirstBodyBlockInfo()
|
|
448
|
+
}
|
|
440
449
|
const index = this.getIndex()
|
|
441
450
|
if (index < 0 || !this.page) return null
|
|
442
451
|
return this.page.getBlockInfo(index + 1)
|
|
@@ -448,6 +457,12 @@ export default class Block {
|
|
|
448
457
|
* @returns {Object|null} Previous block's info or null
|
|
449
458
|
*/
|
|
450
459
|
getPrevBlockInfo() {
|
|
460
|
+
// See getNextBlockInfo: from a shared layout-area block, "previous" is the
|
|
461
|
+
// last content section of the active page (e.g. a footer adapting to it).
|
|
462
|
+
const active = this.website?.activePage
|
|
463
|
+
if (active && active !== this.page) {
|
|
464
|
+
return active.getLastBodyBlockInfo()
|
|
465
|
+
}
|
|
451
466
|
const index = this.getIndex()
|
|
452
467
|
if (index <= 0 || !this.page) return null
|
|
453
468
|
return this.page.getBlockInfo(index - 1)
|
package/src/page.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import Block from './block.js'
|
|
9
9
|
import ObservableState from './observable-state.js'
|
|
10
|
+
import { normalizeSeo } from './seo.js'
|
|
10
11
|
|
|
11
12
|
// Normalize a page's nav-visibility into a deduped hideIn array (the layout-area
|
|
12
13
|
// names the page is suppressed from). Reads the canonical hideIn (array, or a single
|
|
@@ -63,16 +64,8 @@ export default class Page {
|
|
|
63
64
|
params: pageData.layout?.params || {},
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
// SEO configuration
|
|
67
|
-
this.seo =
|
|
68
|
-
noindex: pageData.seo?.noindex || false,
|
|
69
|
-
image: pageData.seo?.image || null,
|
|
70
|
-
ogTitle: pageData.seo?.ogTitle || null,
|
|
71
|
-
ogDescription: pageData.seo?.ogDescription || null,
|
|
72
|
-
canonical: pageData.seo?.canonical || null,
|
|
73
|
-
changefreq: pageData.seo?.changefreq || null,
|
|
74
|
-
priority: pageData.seo?.priority || null,
|
|
75
|
-
}
|
|
67
|
+
// SEO configuration (canonical shape — shared with the site level via normalizeSeo)
|
|
68
|
+
this.seo = normalizeSeo(pageData.seo)
|
|
76
69
|
|
|
77
70
|
// Parent page (set by Website.buildPageHierarchy())
|
|
78
71
|
this.parent = null
|
|
@@ -159,16 +152,22 @@ export default class Page {
|
|
|
159
152
|
*/
|
|
160
153
|
getHeadMeta() {
|
|
161
154
|
const resolvedTitle = this.getTitle()
|
|
155
|
+
// Site-level seo provides the defaults; a page overrides any field it sets.
|
|
156
|
+
// (Website.seo / Website.keywords are the normalized site-root values.)
|
|
157
|
+
const site = this.website?.seo || {}
|
|
158
|
+
const siteKeywords = this.website?.keywords || null
|
|
162
159
|
return {
|
|
163
160
|
title: resolvedTitle,
|
|
164
161
|
description: this.description,
|
|
165
|
-
keywords: this.keywords,
|
|
162
|
+
keywords: this.keywords || siteKeywords,
|
|
166
163
|
canonical: this.seo.canonical,
|
|
167
|
-
robots: this.seo.noindex ? 'noindex, nofollow' : null,
|
|
164
|
+
robots: this.seo.noindex || site.noindex ? 'noindex, nofollow' : null,
|
|
168
165
|
og: {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
166
|
+
// Page content wins; site-level seo only fills gaps. Image is the
|
|
167
|
+
// exception authors most want cascaded (the shared social card).
|
|
168
|
+
title: this.seo.ogTitle || resolvedTitle || site.ogTitle,
|
|
169
|
+
description: this.seo.ogDescription || this.description || site.ogDescription,
|
|
170
|
+
image: this.seo.image || site.image,
|
|
172
171
|
url: this.route,
|
|
173
172
|
},
|
|
174
173
|
}
|
|
@@ -266,6 +265,18 @@ export default class Page {
|
|
|
266
265
|
return this.bodyBlocks?.[0]?.getBlockInfo() || null
|
|
267
266
|
}
|
|
268
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Get the last body block's info.
|
|
270
|
+
* Symmetric to getFirstBodyBlockInfo — e.g. a footer adapting to the
|
|
271
|
+
* content section directly above it.
|
|
272
|
+
*
|
|
273
|
+
* @returns {Object|null} Last body block's info or null
|
|
274
|
+
*/
|
|
275
|
+
getLastBodyBlockInfo() {
|
|
276
|
+
const blocks = this.bodyBlocks
|
|
277
|
+
return blocks?.[blocks.length - 1]?.getBlockInfo() || null
|
|
278
|
+
}
|
|
279
|
+
|
|
269
280
|
/**
|
|
270
281
|
* Get all blocks (header, body, footer) as flat array
|
|
271
282
|
* Respects page layout preferences (hide list)
|
package/src/seo.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical SEO / social-card shape.
|
|
3
|
+
*
|
|
4
|
+
* Shared by Website (site-level defaults) and Page (per-page), so the same
|
|
5
|
+
* concept has the same shape at every level. Site `seo` cascades as the
|
|
6
|
+
* default; a page overrides any field it sets (see Page.getHeadMeta).
|
|
7
|
+
*
|
|
8
|
+
* Field names are camelCase to match the framework's authored convention
|
|
9
|
+
* (`defaultLanguage`, etc.). `changefreq`/`priority` are sitemap hints — only
|
|
10
|
+
* meaningful per-page, but carried uniformly so the shape never drifts.
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} [raw] - raw `seo:` block from site.yml / page.yml
|
|
13
|
+
* @returns {{ noindex: boolean, image: string|null, ogTitle: string|null,
|
|
14
|
+
* ogDescription: string|null, canonical: string|null, changefreq: string|null,
|
|
15
|
+
* priority: (string|number)|null }}
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeSeo(raw) {
|
|
18
|
+
const seo = raw || {}
|
|
19
|
+
return {
|
|
20
|
+
noindex: seo.noindex || false,
|
|
21
|
+
image: seo.image || null,
|
|
22
|
+
ogTitle: seo.ogTitle || null,
|
|
23
|
+
ogDescription: seo.ogDescription || null,
|
|
24
|
+
canonical: seo.canonical || null,
|
|
25
|
+
changefreq: seo.changefreq || null,
|
|
26
|
+
priority: seo.priority || null,
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/website.js
CHANGED
|
@@ -9,6 +9,7 @@ import DataStore from './datastore.js'
|
|
|
9
9
|
import EntityStore from './entity-store.js'
|
|
10
10
|
import FetcherDispatcher from './fetcher-dispatcher.js'
|
|
11
11
|
import ObservableState from './observable-state.js'
|
|
12
|
+
import { normalizeSeo } from './seo.js'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Website — orchestration root for a single site instance.
|
|
@@ -73,6 +74,8 @@ export default class Website {
|
|
|
73
74
|
this.pageRoutes = []
|
|
74
75
|
this.themeData = {}
|
|
75
76
|
this.config = {}
|
|
77
|
+
this.seo = {} // site-level SEO/social defaults (normalized; cascade to pages)
|
|
78
|
+
this.keywords = null // site-level default keywords (localized list or null)
|
|
76
79
|
this.siteDefaultLocale = 'en'
|
|
77
80
|
this.defaultLocale = 'en'
|
|
78
81
|
this.activeLocale = 'en'
|
|
@@ -153,6 +156,12 @@ export default class Website {
|
|
|
153
156
|
this.themeData = theme
|
|
154
157
|
this.config = config
|
|
155
158
|
|
|
159
|
+
// Site-level SEO/social metadata — defaults that cascade to every page's
|
|
160
|
+
// effective head meta (Page.getHeadMeta). `config.seo` / `config.keywords`
|
|
161
|
+
// ride the raw site.yml spread; normalize seo to the canonical shape here.
|
|
162
|
+
this.seo = normalizeSeo(config.seo)
|
|
163
|
+
this.keywords = config.keywords || null
|
|
164
|
+
|
|
156
165
|
this.siteDefaultLocale = config.defaultLanguage || 'en'
|
|
157
166
|
this.defaultLocale = config.domainLocale || this.siteDefaultLocale
|
|
158
167
|
this.activeLocale = config.activeLocale || this.defaultLocale
|