@uniweb/core 0.7.12 → 0.7.14

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.7.12",
3
+ "version": "0.7.14",
4
4
  "description": "Core classes for the Uniweb platform - Uniweb, Website, Page, Block",
5
5
  "type": "module",
6
6
  "exports": {
@@ -30,8 +30,8 @@
30
30
  "vitest": "^4.1.7"
31
31
  },
32
32
  "dependencies": {
33
- "@uniweb/semantic-parser": "1.1.17",
34
- "@uniweb/theming": "0.1.3"
33
+ "@uniweb/theming": "0.1.4",
34
+ "@uniweb/semantic-parser": "1.1.17"
35
35
  },
36
36
  "scripts": {
37
37
  "test": "vitest run"
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
- title: this.seo.ogTitle || resolvedTitle,
170
- description: this.seo.ogDescription || this.description,
171
- image: this.seo.image,
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
  }
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