@rathnasgala2/template 3.0.0 → 3.0.1

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/README.md CHANGED
@@ -486,14 +486,14 @@ brief sits on top of the page kinds above:
486
486
  collide).
487
487
  - **`robots.txt`, `llms.txt`, `llms-full.txt`** (`internal/robots.js`,
488
488
  `internal/llms.js`). `robots.txt` allows all crawlers and names the absolute
489
- sitemap; `publication.crawlers.ai: "block"` (read defensively, absent means
490
- allow) adds `Disallow: /` groups for the 15 known AI crawlers. It is always
491
- written at the output root: **only a robots.txt at the host root is honoured
492
- by crawlers**, so a site served from a path (for example a GitHub project
493
- site) must have the same file at its host root. `llms.txt` follows llmstxt.org
494
- (`# title`, `> description`, `## Articles`, `## Series`, `## Optional`);
495
- `llms-full.txt` is every published article as plain text. Unlisted content is
496
- excluded from both.
489
+ sitemap; `publication.crawlers.ai: "block"` (validated by the build-input
490
+ schema, absent means allow) adds `Disallow: /` groups for the 15 known AI
491
+ crawlers. It is always written at the output root: **only a robots.txt at the
492
+ host root is honoured by crawlers**, so a site served from a path (for example
493
+ a GitHub project site) must have the same file at its host root. `llms.txt`
494
+ follows llmstxt.org (`# title`, `> description`, `## Articles`, `## Series`,
495
+ `## Optional`); `llms-full.txt` is every published article as plain text.
496
+ Unlisted content is excluded from both.
497
497
  - **`404.html`**: the `error` page kind's pure `renderErrorPageBody`, now
498
498
  actually wired into `renderPublication` at the fixed `errorDocumentKey`
499
499
  (`internal/route.js#errorDocumentPath`) — `404.html` at the root, or
@@ -584,8 +584,8 @@ compatibility promise in this MVP.
584
584
 
585
585
  ## Consuming `@rathnasgala2/schemas`
586
586
 
587
- This repository consumes `@rathnasgala2/schemas@2.11.0` from the public npm
588
- registry, declared as `"@rathnasgala2/schemas": "2.11.0"` (exact pin, no range)
587
+ This repository consumes `@rathnasgala2/schemas@3.0.0` from the public npm
588
+ registry, declared as `"@rathnasgala2/schemas": "3.0.0"` (exact pin, no range)
589
589
  so `package-lock.json` records the resolved version and integrity hash. The
590
590
  LOCAL-1/LOCAL-39 local-tarball convention
591
591
  (`file:../../local-packages/rathnasgala2-schemas-*.tgz`) is retired for this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rathnasgala2/template",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Deterministic Eleventy-backed static publication renderer for Galascribe portable repositories.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -71,7 +71,7 @@
71
71
  "dependencies": {
72
72
  "@11ty/eleventy": "3.1.6",
73
73
  "@11ty/eleventy-plugin-syntaxhighlight": "5.0.2",
74
- "@rathnasgala2/schemas": "2.11.0",
74
+ "@rathnasgala2/schemas": "3.0.0",
75
75
  "jpeg-js": "0.4.4",
76
76
  "markdown-it": "14.3.1",
77
77
  "sanitize-html": "2.17.7"
@@ -49,7 +49,7 @@ export const GALA_BASE_STYLESHEET_MEDIA_TYPE = 'text/css; charset=utf-8';
49
49
  *
50
50
  * @type {Readonly<Record<string, string | string[]>>}
51
51
  */
52
- const DEFAULT_TOKENS = Object.freeze({
52
+ export const GALA_BASE_DEFAULT_TOKENS = Object.freeze({
53
53
  'border-button': 'none',
54
54
  'border-card': ['1px solid #e2e6ec', '1px solid #232a34'],
55
55
  'border-chip': 'none',
@@ -195,7 +195,7 @@ const DEFAULT_TOKENS = Object.freeze({
195
195
 
196
196
  /** @type {readonly string[]} every token key this layer defaults, sorted. */
197
197
  export const GALA_BASE_DEFAULT_TOKEN_KEYS = Object.freeze(
198
- Object.keys(DEFAULT_TOKENS),
198
+ Object.keys(GALA_BASE_DEFAULT_TOKENS),
199
199
  );
200
200
 
201
201
  /**
@@ -203,7 +203,7 @@ export const GALA_BASE_DEFAULT_TOKEN_KEYS = Object.freeze(
203
203
  * @returns {string} the declaration list for one palette
204
204
  */
205
205
  function defaultDeclarations(index) {
206
- return Object.entries(DEFAULT_TOKENS)
206
+ return Object.entries(GALA_BASE_DEFAULT_TOKENS)
207
207
  .map(([key, value]) => {
208
208
  const resolved = typeof value === 'string' ? value : (value[index] ?? '');
209
209
  return `--gala-${key}:${resolved};`;
@@ -49,7 +49,7 @@
49
49
  * or a `background-image`.
50
50
  *
51
51
  * Contract 2.1.0 (TPL-H2/TPL-H3/TPL-M7 fix): S2.0's empty `pseudoClasses`
52
- * catalog made every interaction state unstylable while the closed 35-token
52
+ * catalog made every interaction state unstylable while the closed 116-token
53
53
  * catalog already required `color-focus` and `color-link-visited`, tokens
54
54
  * only a pseudo-class can ever apply. {@link PSEUDO_CLASSES} publishes a
55
55
  * small closed catalog instead of widening indefinitely; the module
@@ -628,7 +628,7 @@ export const TEMPLATE_STYLING_CONTRACT_INVALID =
628
628
  * Structural self-validation of a `templateStylingContract` object against
629
629
  * its closed shape (the checks this repository can run
630
630
  * without a published `urn:gala:schema:template-styling-contract` schema
631
- * entry — no such schema ID is registered in `@rathnasgala2/schemas@2.11.0`;
631
+ * entry — no such schema ID is registered in `@rathnasgala2/schemas@3.0.0`;
632
632
  * only `urn:gala:schema:theme-contract:2.0.0` is, which this module's sibling
633
633
  * theme fixtures are validated against with the schema package's own
634
634
  * exported `validateGalaDocument`, see `test/theme-styling-contract.test.js`).
@@ -81,28 +81,20 @@ export function monogram(name) {
81
81
 
82
82
  /**
83
83
  * Render the optional newsletter panel: a call-to-action link to the
84
- * sign-up page the author configured in publication settings, never a form.
85
- * Reads `publication.newsletter` defensively: absent, or without an
86
- * `https:` `url`, renders nothing.
84
+ * sign-up page configured in `publication.newsletter` (validated by the
85
+ * build-input schema: an `https:` URL, a title and a short text), never a
86
+ * form. Renders nothing when the publication has no newsletter.
87
87
  *
88
88
  * @param {object} options rendering options
89
- * @param {unknown} options.newsletter `publication.newsletter` (may be
90
- * undefined on a build input that predates the field)
89
+ * @param {{url: string, title: string, text: string} | undefined} options.newsletter
90
+ * `publication.newsletter`
91
91
  * @param {Readonly<Record<string, string | ((...args: string[]) => string)>>} options.messages
92
92
  * the resolved message catalog
93
93
  * @returns {string} the panel markup, or an empty string
94
94
  */
95
95
  export function renderNewsletterPanel({ newsletter, messages }) {
96
- const value = /** @type {Record<string, unknown> | undefined} */ (
97
- newsletter && typeof newsletter === 'object' ? newsletter : undefined
98
- );
99
- if (!value || typeof value.url !== 'string' || !/^https:\/\//.test(value.url))
100
- return '';
101
- const title =
102
- typeof value.title === 'string' && value.title !== ''
103
- ? value.title
104
- : /** @type {string} */ (messages.newsletterLabel);
105
- const text = typeof value.text === 'string' ? value.text : '';
96
+ if (!newsletter) return '';
97
+ const { url, title, text } = newsletter;
106
98
  return (
107
99
  `<section class="g-wrap g-newsletter" aria-labelledby="newsletter-title">` +
108
100
  `<div class="g-panel">` +
@@ -111,7 +103,7 @@ export function renderNewsletterPanel({ newsletter, messages }) {
111
103
  `<h2 id="newsletter-title">${escapeHtml(title)}</h2>` +
112
104
  (text ? `<p>${escapeHtml(text)}</p>` : '') +
113
105
  `</div>` +
114
- `<a class="g-btn" href="${escapeHtml(value.url)}" rel="noopener">${escapeHtml(/** @type {string} */ (messages.newsletterActionLabel))}${icon('arrow')}</a>` +
106
+ `<a class="g-btn" href="${escapeHtml(url)}" rel="noopener">${escapeHtml(/** @type {string} */ (messages.newsletterActionLabel))}${icon('arrow')}</a>` +
115
107
  `</div></section>`
116
108
  );
117
109
  }
@@ -611,7 +611,7 @@ export function buildGeneratedPages(validatedInput, options = {}) {
611
611
  });
612
612
 
613
613
  const newsletterHtml = renderNewsletterPanel({
614
- newsletter: /** @type {{newsletter?: unknown}} */ (publication).newsletter,
614
+ newsletter: publication.newsletter,
615
615
  messages,
616
616
  });
617
617
 
@@ -3,10 +3,8 @@
3
3
  *
4
4
  * Every site allows all crawlers and names its sitemap by absolute URL. The
5
5
  * publication may additionally opt out of AI training and AI-assistant
6
- * crawlers through `publication.crawlers.ai` (`"allow"` | `"block"`, absent
7
- * means allow). The field is read defensively, exactly like
8
- * `publication.newsletter`: a build input that predates it, or carries any
9
- * other value, is treated as `"allow"`.
6
+ * crawlers through the validated `publication.crawlers.ai` (`"allow"` |
7
+ * `"block"`; absent means allow).
10
8
  *
11
9
  * Unlisted (`noindex`) pages are deliberately not disallowed here: a
12
10
  * crawler can only honour a `noindex` directive on a page it is allowed to
@@ -45,18 +43,12 @@ export const AI_CRAWLER_USER_AGENTS = Object.freeze([
45
43
  ]);
46
44
 
47
45
  /**
48
- * @param {unknown} publication `publication` (may predate `crawlers`)
46
+ * @param {{crawlers?: {ai: 'allow' | 'block'}}} publication the validated
47
+ * `publication` (`crawlers` is optional; absent means allow)
49
48
  * @returns {'allow' | 'block'} the AI crawler policy
50
49
  */
51
50
  export function readAiCrawlerPolicy(publication) {
52
- const crawlers = /** @type {{crawlers?: unknown} | undefined} */ (
53
- publication && typeof publication === 'object' ? publication : undefined
54
- )?.crawlers;
55
- const ai =
56
- crawlers && typeof crawlers === 'object'
57
- ? /** @type {{ai?: unknown}} */ (crawlers).ai
58
- : undefined;
59
- return ai === 'block' ? 'block' : 'allow';
51
+ return publication.crawlers?.ai ?? 'allow';
60
52
  }
61
53
 
62
54
  /**
package/types/index.d.ts CHANGED
@@ -130,6 +130,8 @@ export interface PublicationNormalized {
130
130
  contactAuthorId?: string;
131
131
  socialLinks: unknown[];
132
132
  defaultImage?: ResolvedFileRef;
133
+ newsletter?: { url: string; title: string; text: string };
134
+ crawlers?: { ai: 'allow' | 'block' };
133
135
  profile?: { route: string; body: RenderableBody };
134
136
  footerCard?: {
135
137
  enabled: boolean;