@rathnasgala/theme 2.0.5 → 2.0.7

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,12 +1,15 @@
1
1
  {
2
2
  "name": "@rathnasgala/theme",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
4
4
  "description": "Managed Gala static-site theme payload",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/rathnasgala/site-template.git"
9
9
  },
10
+ "gala": {
11
+ "sourceCommit": "1df5e1f4ca4d80d190e05d022577a141c8330038"
12
+ },
10
13
  "files": [
11
14
  "payload"
12
15
  ],
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "themePackage": {
4
4
  "name": "@rathnasgala/theme",
5
- "version": "2.0.5",
5
+ "version": "2.0.7",
6
6
  "availableDesignThemes": [
7
7
  "editorial",
8
8
  "modern",
@@ -15,13 +15,15 @@
15
15
  "managedCssBytes": 32768
16
16
  },
17
17
  "files": {
18
- ".gala/publish.yml.template": "73080241d89bd09964b797a8bcc8e18471412e11bc873efad76305b60f497d08",
18
+ ".gala/publish.yml.template": "d93da94c10378693cdd927cd6623a003ff57c15d8b8aeba3644c914f2753d824",
19
19
  ".gitignore": "b5fa24e10155097a0d372926fd2e59e6072e7cc2cb6399bbfb4beb8b33ea315b",
20
- "eleventy.config.js": "0b72076f2f884a3a4a5b2ea4a124e0c1308c735526275bb847a3d6ebd082a186",
20
+ "eleventy.config.js": "858198dcbdb96db836dd6926f39d94e4e4b628d1cec626e90c164963f59995b8",
21
21
  "lib/accent.js": "e6c921e0a3afe094b18709e3e40baa06692017fa6576d84b1ec6b7d788da8dd0",
22
- "lib/build-manifest.js": "88949f0112d683ca809bb67ca969e15634792423559a67bc79a534cd807e5535",
22
+ "lib/build-manifest.js": "240dd6900e1277986fa935449ad1757641c47a325599b8b847a4e8dfcb50181c",
23
23
  "lib/engagement-snapshot.js": "298c11b1ab135a3a98a4e31b6320ab60dfd7fda6a9d9053df37d86282a5a6687",
24
24
  "lib/performance-budget.js": "c279622dfde237cd6e3c64775c91d770a0e1f54f5f94257ccc95d9a44de2c4c9",
25
+ "lib/prism-compiled-output.js": "0c1f93d025c70dbec10c78f17f59ae10a9a6de773b58adb2f26f42de62f7be2c",
26
+ "lib/prism-session.js": "16a55d9d3d6b5bd2f95bfdb15a76a795b0406ddc51fae45014e0b0f5795194ca",
25
27
  "lib/provider-fixtures/embeds.v1.json": "5f26909fb433135321ddf7fdd14e45aeb702805c88f5676941166c5735143ee2",
26
28
  "lib/provider-fixtures/share-intents.v1.json": "66a8eedfcff711253ee45ceb94b53f7d5a6be3ddbbb3d1f0d4e31a114326cc0a",
27
29
  "lib/publication-state.js": "1b59c29a18bbb1193bddea898dac3a4fb832c7bc4751263be086f3d64392dea0",
@@ -30,17 +32,18 @@
30
32
  "lib/share-targets.js": "458aff86fab2b62a364442cd8c612565c6e0b53eef2f20cb6237cc562b1e0731",
31
33
  "lib/site-config.js": "55eccfc4eab65a58fdcb06aa53f578b7b423cc186b93cad18c30705aec219d34",
32
34
  "package-lock.json": "dc11470b1ad897c58d91745f23ff25711c593e62972ec9fb83f4e10fd77e1ecd",
33
- "package.json": "1b3b70f6e94391d309625cb8cef503ee92ecd282bec587643b535ae7a14c24c0",
35
+ "package.json": "7a3c2b559054b1613d84cbda3a1fd370bc725818f9f418f0fe90cf3f8f0a9caa",
34
36
  "scripts/build-reader.js": "743194c8dc9cfb9b6631db1732b5f36a92e3a4f905a2965c71d56e1ef50c2e45",
35
37
  "scripts/lint.js": "c4bb3c961686043fa8c79bed546758ef09d44441421d8209fec9d69f74171025",
36
- "src/_data/buildManifest.js": "628163305984e0d3537e27b0d4050f5ba5ead12a834878266d2ce8004ffe1b1e",
38
+ "src/_data/buildManifest.js": "a889cb4642c2f0beaa7f31f662fd8da7ab2d4dce819faf69a3aa984e55270336",
37
39
  "src/_data/engagementSnapshot.js": "9217dacebfaf008f7a0ad67ff4e8d1fa8fba609a9dc7e3430c78c5b32024da0e",
38
40
  "src/_data/feedLinks.js": "06c93352ebd888acb431a1c9a8be6ecff757895468e9f2d336761038751c2d5e",
39
41
  "src/_data/languages.js": "1358748f7fa2f74406d3295a030827d6229a53e94da72cf712e719d7a356aeac",
40
42
  "src/_data/site.js": "432be73b49c20f088c304a2f7d7cd85906a747e0f3b381a4b395c5c44d68bb57",
41
43
  "src/_includes/components/ui.njk": "0634c206c2db0507f77686635e3b406e1d8878011fd7eac64563df8499fe8f60",
42
- "src/_includes/layouts/base.njk": "28043f394af8e2e3b09b6218ed3de2a506535fd5d59e67225c0fbd4431f967ff",
43
- "src/_includes/layouts/post.njk": "be1069e47be0da7cc32be9db6163c8de6180ab0bdd1d4a2320485bc720017cea",
44
+ "src/_includes/layouts/base.njk": "f7620e9c3b7f5793ac2b421841e8c3840d07533477c81d4fc25aff0bb22b933d",
45
+ "src/_includes/layouts/post.njk": "46fd004846aed6c7d0668bad4ed0239f001038c33feb66a71638ee811c44f0ba",
46
+ "src/_includes/layouts/prism-configuration.njk": "c984441ac50465eb6ee0e050c68c054e3a0bca98960fb92fb2f5795443301594",
44
47
  "src/404.njk": "be0d44370ac5402156aaf3d32ea581057bc61ec94c1a5f92bb809733af7a8a20",
45
48
  "src/accent.11ty.js": "52e7d3eb731c5f4649c14c4745f65da026c25b58cf462d075b8ef363f0e487dc",
46
49
  "src/assets/embed-codepen.svg": "e9def697f38c466fb2b6652535c4c814e8cbbbefc6e052a69f335aeb27e8356b",
@@ -52,17 +55,19 @@
52
55
  "src/assets/favicon.svg": "8615e3d96fe37ae9ed14afb1886e7c0e690eef5b10be66ca91cccfd085a74bb6",
53
56
  "src/assets/interactions.js": "d7ac42be1c3453fb5a7bc1b7de52bca2bc7322e60ef235ce4953f5382a3550c5",
54
57
  "src/assets/preferences.js": "789127bee013ed6a1bbf8cefd7b9109291c16220e7a6e09314e3c93c54731d1d",
55
- "src/assets/reader.js": "96018c305177c5cbea77b4958ccba7800be82ace513f47fc811bdd41fb8eee63",
58
+ "src/assets/prism-session.js": "1fab23725b843006c3f589af7669ef3ad6ad01f3264a3b00564e64b56738d2cd",
59
+ "src/assets/reader.js": "b0b8d8244774a7e55f15053da02161a1a884f0827ccba748f7d424d4a7830a3b",
56
60
  "src/assets/search.js": "df425f0239277792ad39f2138bc89cb66cc4ecda5ed59042d46abc2f05372088",
57
61
  "src/assets/theme-mode.js": "944b29db11a577961a60ddcfa52f07ef2411f3f3631bea85bd79be16428c08be",
58
62
  "src/assets/theme.css": "c8491d9f4340b32efd841bc6d7cc17a8d5e568032bec53acfcb9f7637288b76f",
59
- "src/client/reader.js": "5a61d629517c5315b3cb14dbae6cee226ba828e95132100f2ad36e614c57470a",
63
+ "src/client/reader.js": "643aec955c5beb6f902c737313dbc1a8232150fcb5d800350aa7fb1fb69f1760",
60
64
  "src/cname.11ty.js": "206fc224df91c569e18312158e7f69803061f2952513f501ada54bd06c4f8006",
61
65
  "src/contact.njk": "f4d74d038781e15af0acef571901c91e7753a2e21b6bef617429585698839a34",
62
66
  "src/feed.11ty.js": "009bd2384e08e02a9405683bab1653bab3aeaec16accb0fd01ba69b24c777da9",
63
67
  "src/index.njk": "5c313f0fcdad3ddbfb79376bc9671b3f37539ef70c14bdbbd26d6e2e6de250e8",
64
68
  "src/languages.11ty.js": "399559199c65570d7d5efd88e20254838e316f81c06d7b28ec34990e963db915",
65
- "src/posts.11ty.js": "c0bdc81183f5849670d100ceb97ecad0d1eb05f0de3eea34a0ce7427fd2e7e2a",
69
+ "src/posts.11ty.js": "abdfd572fb93fc79c92e1ba2aa9601e768e2ee32ae108038a429407c0b1cb053",
70
+ "src/prism-configurations.11ty.js": "ecfda10ef9c1092549e7eeb75b971c14481bea0f48919764b7df79bf026ec6aa",
66
71
  "src/redirects.11ty.js": "3c163246a59f5364861db6a6f05ae88afc938a5379d86cf42776d8d49b0031f3",
67
72
  "src/search-index.11ty.js": "700dc2a2be0ef20de3a828a4612fcf7a082691eade29c6e1d75f09c24b6c6024",
68
73
  "src/search.njk": "63f2e8aeebb63c90da2e375eece639ce096cad1eff7f28628cafc46faf7ceee6",
@@ -33,3 +33,4 @@ jobs:
33
33
  floor-guard-override-commit-sha: ${{ contains(github.event.head_commit.message || '', 'Gala-Floor-Override:') && github.sha || '' }}
34
34
  secrets:
35
35
  site-secret: ${{ secrets.GALA_SITE_SECRET }}
36
+ previous-site-secret: ${{ secrets.GALA_PREVIOUS_SITE_SECRET }}
@@ -2,6 +2,7 @@ import { markdownLibrary } from './lib/render-markdown.js';
2
2
  import { readBuildManifest } from './lib/build-manifest.js';
3
3
  import { loadSiteConfiguration } from './lib/site-config.js';
4
4
  import { enforcePerformanceBudgets } from './lib/performance-budget.js';
5
+ import { verifyPrismCompiledOutput } from './lib/prism-compiled-output.js';
5
6
  import { lstat, realpath } from 'node:fs/promises';
6
7
  import path from 'node:path';
7
8
  import { createHash } from 'node:crypto';
@@ -57,6 +58,7 @@ export default async function (eleventyConfig) {
57
58
  outputDirectory: path.resolve('_site'),
58
59
  budgets: site.performance.budgets
59
60
  });
61
+ await verifyPrismCompiledOutput({ outputDirectory: path.resolve('_site'), manifest });
60
62
  });
61
63
 
62
64
  return {
@@ -7,6 +7,8 @@ const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
7
  const ULID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
8
8
  const STATES = new Set(['published', 'tombstoned']);
9
9
  const ASSIGNED_SOURCE = /^content\/posts\/[a-z0-9]+(?:-[a-z0-9]+)*\/index\.[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*\.md$/;
10
+ const PRISM_STATES = new Set(['PUBLISHED', 'STALE', 'DISABLED', 'REVOKED']);
11
+ const PRISM_HASH = /^[0-9a-f]{64}$/;
10
12
 
11
13
  function absoluteHttps(value, field) {
12
14
  let parsed;
@@ -129,7 +131,7 @@ function validatedRedirect(redirect, index, seen) {
129
131
 
130
132
  export function validateBuildManifest(value) {
131
133
  if (value == null || Array.isArray(value) || typeof value !== 'object'
132
- || value.schemaVersion !== 1 || !DATE.test(value.evaluationDate)
134
+ || ![1, 2].includes(value.schemaVersion) || !DATE.test(value.evaluationDate)
133
135
  || !Array.isArray(value.posts) || !Array.isArray(value.redirects)) {
134
136
  throw new TypeError('Unsupported build manifest schema');
135
137
  }
@@ -159,14 +161,54 @@ export function validateBuildManifest(value) {
159
161
  return Object.freeze({ ...assigned });
160
162
  });
161
163
  }
164
+ let configurations;
165
+ if (value.schemaVersion === 2) {
166
+ if (!Array.isArray(value.configurations) || value.prism?.schemaVersion !== 1) {
167
+ throw new TypeError('build manifest V2 requires Prism settings and configurations');
168
+ }
169
+ const configurationIds = new Set();
170
+ configurations = value.configurations.map((configuration, index) => {
171
+ if (configuration == null || typeof configuration !== 'object' || Array.isArray(configuration)
172
+ || !ULID.test(configuration.configurationId) || !ULID.test(configuration.revisionId)
173
+ || !ULID.test(configuration.approvalId) || !ULID.test(configuration.articleId)
174
+ || !LANGUAGE.test(configuration.language) || !PRISM_STATES.has(configuration.state)
175
+ || !PRISM_HASH.test(configuration.sourceRevisionHash)
176
+ || !PRISM_HASH.test(configuration.configurationContentHash)
177
+ || configuration.hashContract !== 'GALA_PRISM_HASH_V1'
178
+ || !['NOFOLLOW', 'FOLLOW'].includes(configuration.configurationLinkPolicy)
179
+ || configurationIds.has(configuration.configurationId)) {
180
+ throw new TypeError(`build manifest configurations[${index}] is invalid`);
181
+ }
182
+ if (typeof configuration.relativeUrl !== 'string' || !configuration.relativeUrl.startsWith('/')
183
+ || !configuration.relativeUrl.endsWith('/') || configuration.relativeUrl.split('/').includes('..')) {
184
+ throw new TypeError(`build manifest configurations[${index}].relativeUrl is invalid`);
185
+ }
186
+ absoluteHttps(configuration.pageUrl, `build manifest configurations[${index}].pageUrl`);
187
+ absoluteHttps(configuration.canonicalUrl, `build manifest configurations[${index}].canonicalUrl`);
188
+ if (configuration.state === 'PUBLISHED' && typeof configuration.body !== 'string') {
189
+ throw new TypeError(`build manifest configurations[${index}].body is required when published`);
190
+ }
191
+ if (configuration.state !== 'PUBLISHED' && Object.hasOwn(configuration, 'body')) {
192
+ throw new TypeError(`build manifest configurations[${index}] fallback must not contain prose`);
193
+ }
194
+ configurationIds.add(configuration.configurationId);
195
+ return Object.freeze({ ...configuration });
196
+ });
197
+ } else if (value.configurations != null || value.prism != null) {
198
+ throw new TypeError('build manifest V1 cannot contain Prism delivery fields');
199
+ }
162
200
  return Object.freeze({
163
- schemaVersion: 1,
201
+ schemaVersion: value.schemaVersion,
164
202
  evaluationDate: value.evaluationDate,
165
203
  ...(assignedContentIds == null
166
204
  ? {}
167
205
  : { assignedContentIds: Object.freeze(assignedContentIds) }),
168
206
  posts: Object.freeze(posts),
169
- redirects: Object.freeze(redirects)
207
+ redirects: Object.freeze(redirects),
208
+ ...(configurations == null ? {} : {
209
+ prism: Object.freeze({ ...value.prism }),
210
+ configurations: Object.freeze(configurations)
211
+ })
170
212
  });
171
213
  }
172
214
 
@@ -0,0 +1,77 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ export class PrismCompiledOutputError extends Error {
5
+ constructor(code, message) {
6
+ super(`${code}: ${message}`);
7
+ this.name = 'PrismCompiledOutputError';
8
+ this.code = code;
9
+ }
10
+ }
11
+
12
+ /** Verifies the emitted files, not template source, so CI catches generator and layout regressions. */
13
+ export async function verifyPrismCompiledOutput({ outputDirectory, manifest }) {
14
+ const configurations = manifest?.configurations ?? [];
15
+ if (configurations.length === 0) return;
16
+ const discovery = await Promise.all([
17
+ optionalText(path.join(outputDirectory, 'sitemap.xml')),
18
+ ...[...new Set((manifest.posts ?? []).map((post) => post.language))]
19
+ .map((language) => optionalText(path.join(outputDirectory, 'feed', `${language}.xml`))),
20
+ ]);
21
+ for (const configuration of configurations) {
22
+ if (discovery.some((document) => document.includes(configuration.pageUrl))) {
23
+ throw new PrismCompiledOutputError('PRISM_CONFIGURATION_DISCOVERY_LEAK',
24
+ `${configuration.pageUrl} appears in sitemap or feed output`);
25
+ }
26
+ const parent = (manifest.posts ?? []).find((post) => post.id === configuration.articleId
27
+ && post.language === configuration.language);
28
+ if (!parent) throw new PrismCompiledOutputError('PRISM_CONFIGURATION_PARENT_MISSING',
29
+ configuration.configurationId);
30
+ const file = path.join(outputDirectory, ...configuration.relativeUrl.split('/').filter(Boolean),
31
+ 'index.html');
32
+ const html = await optionalText(file);
33
+ const canonical = [...html.matchAll(/<link\s+[^>]*rel=["']canonical["'][^>]*>/gi)];
34
+ if (canonical.length !== 1 || !canonical[0][0].includes(`href="${parent.canonicalUrl}"`)) {
35
+ throw new PrismCompiledOutputError('PRISM_CANONICAL_INVALID', configuration.configurationId);
36
+ }
37
+ const restore = anchorFor(html, parent.pageUrl);
38
+ if (!restore || !restore.includes('data-prism-restore') || /rel=["'][^"']*nofollow/i.test(restore)) {
39
+ throw new PrismCompiledOutputError('PRISM_CANONICAL_RESTORE_INVALID',
40
+ configuration.configurationId);
41
+ }
42
+ if (configuration.state !== 'PUBLISHED' && configuration.body
43
+ && html.includes(configuration.body.replace(/^#+\s*/m, '').trim())) {
44
+ throw new PrismCompiledOutputError('PRISM_STALE_BODY_LEAK', configuration.configurationId);
45
+ }
46
+ }
47
+ for (const post of manifest.posts ?? []) {
48
+ const html = await optionalText(path.join(outputDirectory,
49
+ ...post.relativeUrl.split('/').filter(Boolean), 'index.html'));
50
+ for (const configuration of configurations.filter((item) => item.articleId === post.id
51
+ && item.language === post.language && item.state === 'PUBLISHED')) {
52
+ assertLinkPolicy(html, configuration.pageUrl,
53
+ post.prismConfigurationLinkPolicy ?? configuration.configurationLinkPolicy);
54
+ }
55
+ }
56
+ }
57
+
58
+ function assertLinkPolicy(html, url, policy) {
59
+ const anchor = anchorFor(html, url);
60
+ if (!anchor) throw new PrismCompiledOutputError('PRISM_CONFIGURATION_LINK_MISSING', url);
61
+ const rel = /rel=["']([^"']*)["']/i.exec(anchor)?.[1]?.split(/\s+/) ?? [];
62
+ if ((policy === 'NOFOLLOW') !== rel.includes('nofollow')) {
63
+ throw new PrismCompiledOutputError('PRISM_CONFIGURATION_LINK_POLICY_MISMATCH', url);
64
+ }
65
+ }
66
+
67
+ function anchorFor(html, href) {
68
+ return [...html.matchAll(/<a\s+[^>]*>/gi)].map((match) => match[0])
69
+ .find((anchor) => anchor.includes(`href="${href}"`));
70
+ }
71
+
72
+ async function optionalText(file) {
73
+ try { return await readFile(file, 'utf8'); } catch (error) {
74
+ if (error.code === 'ENOENT') return '';
75
+ throw error;
76
+ }
77
+ }
@@ -0,0 +1,20 @@
1
+ const KEY = 'gala.prism.session.v1';
2
+
3
+ /** Builds the synchronous, page-specific canonical-arrival redirect. */
4
+ export function prismSessionBootstrap(post, configurations) {
5
+ if (!post?.id || !post?.language || !post?.prismSourceHash || configurations.length === 0) return '';
6
+ const available = configurations.map((configuration) => ({
7
+ configurationId: configuration.configurationId,
8
+ sourceRevisionHash: configuration.sourceRevisionHash,
9
+ pageUrl: configuration.pageUrl,
10
+ }));
11
+ const facts = JSON.stringify({
12
+ articleId: post.id,
13
+ language: post.language,
14
+ sourceRevisionHash: post.prismSourceHash,
15
+ available,
16
+ }).replaceAll('<', '\\u003c');
17
+ return `(function(){try{var k='${KEY}',p=${facts},r=sessionStorage.getItem(k);if(!r)return;var s=JSON.parse(r),c=p.available.find(function(x){return x.configurationId===s.configurationId&&x.sourceRevisionHash===s.sourceRevisionHash});if(s.articleId!==p.articleId||s.language!==p.language||s.sourceRevisionHash!==p.sourceRevisionHash||!c){sessionStorage.removeItem(k);return}var u=new URL(c.pageUrl,location.href);if(u.origin===location.origin)location.replace(u.href)}catch(e){try{sessionStorage.removeItem('${KEY}')}catch(x){}}})();`;
18
+ }
19
+
20
+ export { KEY as PRISM_SESSION_KEY };
@@ -8,6 +8,7 @@
8
8
  "build": "npm run build:reader && eleventy",
9
9
  "pretest": "npm run build:reader",
10
10
  "test": "node --test",
11
+ "test:prism-compiled-output": "node --test test/prism-seo-compiled-output.test.js",
11
12
  "lint": "node scripts/lint.js",
12
13
  "push": "node scripts/push.js"
13
14
  },
@@ -1,3 +1,8 @@
1
1
  import { readBuildManifest } from '../../lib/build-manifest.js';
2
2
 
3
- export default await readBuildManifest();
3
+ const manifest = await readBuildManifest();
4
+
5
+ export default Object.freeze({
6
+ ...manifest,
7
+ configurations: manifest.configurations ?? Object.freeze([])
8
+ });
@@ -3,8 +3,9 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; base-uri 'self'; object-src 'none'; script-src 'self' 'sha256-{{ themeBootstrap | sha256Csp }}'{% if seo %} 'sha256-{{ seo.structuredDataJson | sha256Csp }}'{% endif %}; style-src 'self'; img-src 'self' data: https:; connect-src 'self' {{ site.platform.apiBaseUrl or 'https://api.gala67.com' }}; frame-src {{ site.platform.apiBaseUrl or 'https://api.gala67.com' }} https://www.youtube-nocookie.com https://codepen.io; form-action 'self'; upgrade-insecure-requests">
6
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; base-uri 'self'; object-src 'none'; script-src 'self' 'sha256-{{ themeBootstrap | sha256Csp }}'{% if prismBootstrap %} 'sha256-{{ prismBootstrap | sha256Csp }}'{% endif %}{% if seo %} 'sha256-{{ seo.structuredDataJson | sha256Csp }}'{% endif %}; style-src 'self'; img-src 'self' data: https:; connect-src 'self' {{ site.platform.apiBaseUrl or 'https://api.gala67.com' }}; frame-src {{ site.platform.apiBaseUrl or 'https://api.gala67.com' }} https://www.youtube-nocookie.com https://codepen.io; form-action 'self'; upgrade-insecure-requests">
7
7
  <script>{{ themeBootstrap | safe }}</script>
8
+ {% if prismBootstrap %}<script>{{ prismBootstrap | safe }}</script>{% endif %}
8
9
  <title>{{ post.frontmatter.title or title }} · {{ site.site.name }}</title>
9
10
  {% if post %}<link rel="canonical" href="{{ post.canonicalUrl }}">{% endif %}
10
11
  {% for alternate in hreflangLinks %}<link rel="alternate" hreflang="{{ alternate.hreflang }}" href="{{ alternate.href }}">{% endfor %}
@@ -5,6 +5,10 @@ layout: layouts/base.njk
5
5
  <article id="article" class="gala-article">
6
6
  {% if post.publicationState == "published" %}<progress class="gala-reading-progress" data-reading-progress max="1" value="0" aria-label="Reading progress"></progress><header class="gala-article-header"><p class="gala-eyebrow">Published {{ post.frontmatter.publishAfterDate }} · {{ post.language }} · {{ readingMinutes }} min read</p><h1>{{ post.frontmatter.title }}</h1>{% set byline = post.frontmatter.author or site.site.author %}{% if byline and byline != "unavailable" and byline != "New Gala Site" and byline != site.site.name %}<p class="gala-byline">By {{ byline }}</p>{% endif %}</header>{% endif %}
7
7
  {% if post.publicationState == "published" %}{{ languageSwitcher(hreflangLinks, post.language) }}{% endif %}
8
+ {% if prismConfigurations.length %}<nav class="gala-prism-selector" aria-label="Approved editions">
9
+ <span aria-current="page">Complete</span>
10
+ {% for configuration in prismConfigurations %}<a href="{{ configuration.pageUrl }}" data-prism-select data-prism-article-id="{{ post.id }}" data-prism-language="{{ post.language }}" data-prism-configuration-id="{{ configuration.configurationId }}" data-prism-source-hash="{{ configuration.sourceRevisionHash }}"{% if post.prismConfigurationLinkPolicy == "NOFOLLOW" %} rel="nofollow"{% endif %}>{{ configuration.depth }} · {{ configuration.intent }}</a>{% endfor %}
11
+ </nav>{% endif %}
8
12
  {% if post.publicationState == "published" %}<section class="gala-engagement" data-article-id="{{ post.id | escape }}" data-engagement-url="{{ site.platform.apiBaseUrl or 'https://api.gala67.com' }}/v1/articles/{{ post.id | escape }}/engagement">{% endif %}
9
13
  <div class="gala-reading-layout">
10
14
  {% if postTableOfContents.length %}{{ tableOfContents(postTableOfContents) }}{% else %}<span></span>{% endif %}
@@ -0,0 +1,20 @@
1
+ ---
2
+ layout: layouts/base.njk
3
+ ---
4
+ <article id="article" class="gala-article gala-prism-article" data-prism-configuration-id="{{ prismConfiguration.configurationId }}" data-prism-revision-id="{{ prismConfiguration.revisionId }}" data-prism-approval-id="{{ prismConfiguration.approvalId }}" data-prism-source-hash="{{ prismConfiguration.sourceRevisionHash }}">
5
+ <header class="gala-article-header">
6
+ <p class="gala-eyebrow">{{ prismConfiguration.depth }} · {{ prismConfiguration.intent }} · {{ prismConfiguration.modality }}</p>
7
+ <h1>{{ post.frontmatter.title }}</h1>
8
+ <p><a href="{{ post.pageUrl }}" data-prism-canonical data-prism-restore>Read the canonical work</a></p>
9
+ </header>
10
+ {% if prismConfiguration.state == "PUBLISHED" %}
11
+ <div class="gala-markdown">{{ content | safe }}</div>
12
+ {% if prismAlternates.length > 1 %}<nav class="gala-prism-selector" aria-label="Other approved editions">
13
+ {% for alternate in prismAlternates %}{% if alternate.configurationId != prismConfiguration.configurationId %}
14
+ <a href="{{ alternate.pageUrl }}" data-prism-select data-prism-article-id="{{ post.id }}" data-prism-language="{{ post.language }}" data-prism-configuration-id="{{ alternate.configurationId }}" data-prism-source-hash="{{ alternate.sourceRevisionHash }}"{% if prismConfiguration.configurationLinkPolicy == "NOFOLLOW" %} rel="nofollow"{% endif %}>{{ alternate.depth }} · {{ alternate.intent }}</a>
15
+ {% endif %}{% endfor %}
16
+ </nav>{% endif %}
17
+ {% else %}
18
+ <p>This edition is no longer current. <a href="{{ post.pageUrl }}" data-prism-canonical data-prism-restore>Continue with the canonical work</a>.</p>
19
+ {% endif %}
20
+ </article>
@@ -0,0 +1,20 @@
1
+ const KEY = 'gala.prism.session.v1';
2
+
3
+ document.addEventListener('click', (event) => {
4
+ const restore = event.target.closest('[data-prism-restore]');
5
+ if (restore) {
6
+ try { sessionStorage.removeItem(KEY); } catch {}
7
+ return;
8
+ }
9
+ const selection = event.target.closest('[data-prism-select]');
10
+ if (!selection) return;
11
+ try {
12
+ sessionStorage.setItem(KEY, JSON.stringify({
13
+ articleId: selection.dataset.prismArticleId,
14
+ language: selection.dataset.prismLanguage,
15
+ configurationId: selection.dataset.prismConfigurationId,
16
+ sourceRevisionHash: selection.dataset.prismSourceHash,
17
+ selectedAt: new Date().toISOString(),
18
+ }));
19
+ } catch {}
20
+ });
@@ -1,2 +1,2 @@
1
- (()=>{var b=["system","light","dark"],O="gala-color-mode";function D(){let e=document.documentElement.dataset.mode;return b.includes(e)?e:"system"}function J(){try{let e=localStorage.getItem(O);if(b.includes(e))return e}catch{return D()}return D()}function M(e){document.documentElement.dataset.mode=e;let t=b[(b.indexOf(e)+1)%b.length];document.querySelectorAll("[data-theme-mode-toggle]").forEach(n=>{let a=n.querySelector?.("[data-theme-mode-label]");a?a.textContent=`Theme: ${e}`:n.textContent=`Theme: ${e}`,n.setAttribute("aria-label",`Color mode: ${e}. Activate for ${t}.`)})}document.addEventListener("DOMContentLoaded",()=>{M(J()),document.querySelectorAll("[data-theme-mode-toggle]").forEach(e=>{e.addEventListener("click",()=>{let t=b[(b.indexOf(document.documentElement.dataset.mode)+1)%b.length];try{localStorage.setItem(O,t)}catch{}M(t)})})});var F="gala-language-preference";function Z(){try{return localStorage.getItem(F)}catch{return null}}function P(e,t){return[...e.options].find(n=>n.value===t)}document.addEventListener("DOMContentLoaded",()=>{let e=Z();document.querySelectorAll("[data-language-preference]").forEach(t=>{e!=null&&P(t,e)!=null&&(t.value=e),t.addEventListener("change",()=>{try{localStorage.setItem(F,t.value)}catch{}if(!t.hasAttribute("data-navigate-on-selection"))return;let n=P(t,t.value);n?.dataset.url&&window.location.assign(n.dataset.url)})})});function v(e){let t=e.closest(".gala-share"),n=t?.querySelector(".gala-share__fallback");n?.classList.add("gala-share__fallback--visible"),n?.focus(),n?.select();let a=t?.querySelector(".gala-share__status");a&&(a.textContent="Select and copy the URL shown.")}var G=document.querySelector("[data-reading-progress]"),q=document.querySelector(".gala-markdown");if(G&&q){let e=null,t=()=>{e=null;let a=q.offsetTop,s=Math.max(1,q.offsetHeight-window.innerHeight),o=Math.min(1,Math.max(0,(window.scrollY-a)/s));G.value=o},n=()=>{e==null&&(e=requestAnimationFrame(t))};addEventListener("scroll",n,{passive:!0}),addEventListener("resize",n,{passive:!0}),n()}var C=document.querySelector(".gala-toc");if(C?.tagName==="DETAILS"){let e=C.querySelector("nav"),t=[...C.querySelectorAll('a[href^="#"]')],n=t.map(d=>document.getElementById(d.hash.slice(1))).filter(Boolean),a=null,s=()=>{if(a=null,C.classList.toggle("gala-toc--floating",C.getBoundingClientRect().top<=96),!n.length)return;let d=window.innerHeight*.42,f=n[0];for(let i of n)if(i.getBoundingClientRect().top<=d)f=i;else break;let E=t.find(i=>i.hash===`#${f.id}`);for(let i of t)i===E?i.setAttribute("aria-current","location"):i.removeAttribute("aria-current");if(e&&t.length){let i=t[0].getBoundingClientRect(),r=t[t.length-1].getBoundingClientRect();C.classList.toggle("gala-toc--overflowing",r.bottom-i.top>e.clientHeight)}if(E&&e&&e.scrollHeight>e.clientHeight){let i=e.getBoundingClientRect(),r=E.getBoundingClientRect(),c=e.scrollTop+r.top-i.top-(e.clientHeight-r.height)/2;e.scrollTo({top:Math.max(0,c),behavior:"auto"})}},o=()=>{a==null&&(a=requestAnimationFrame(s))};addEventListener("scroll",o,{passive:!0}),addEventListener("resize",o,{passive:!0}),o()}var H=document.querySelector(".gala-action-rail"),$=document.querySelector("[data-action-dock]");if(H&&$){let e=t=>{H.classList.toggle("gala-action-rail--integrated",t[0].isIntersecting)};new IntersectionObserver(e,{rootMargin:"0px 0px 18% 0px",threshold:0}).observe($)}function W(e,t){let n=t?"Unfollow article":"Follow article";e.setAttribute("aria-pressed",String(t)),e.setAttribute("aria-label",n),e.title=n;let a=e.querySelector("[data-follow-label]");a&&(a.textContent=n)}document.addEventListener("click",async e=>{if(e.target instanceof HTMLDialogElement&&e.target.open){let i=e.target.getBoundingClientRect();if(e.clientX<i.left||e.clientX>i.right||e.clientY<i.top||e.clientY>i.bottom){e.target.close();return}}let t=e.target.closest("[data-gala-embed-load]");if(t){let i=t.closest("[data-gala-embed]"),r=t.dataset.galaEmbedSrc,c=t.dataset.galaEmbedLoad;if(!i||!r||!["youtube","codepen"].includes(c))return;let u=document.createElement("iframe");u.src=r,u.title=c==="youtube"?"YouTube video":"CodePen example",u.loading="eager",u.referrerPolicy="strict-origin-when-cross-origin",u.setAttribute("sandbox","allow-forms allow-popups allow-presentation allow-same-origin allow-scripts"),u.setAttribute("allow",c==="youtube"?"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share":"fullscreen"),u.setAttribute("allowfullscreen",""),i.replaceChildren(u);return}let n=e.target.closest("[data-open-dialog]");if(n){let i=document.getElementById(n.dataset.openDialog);i instanceof HTMLDialogElement&&i.showModal();return}let a=e.target.closest("[data-reaction]");if(a){let i=a.closest("[data-engagement-url]");if(!i)return;if(!y)return R({kind:"reaction",region:i,selector:`[data-reaction="${a.dataset.reaction}"]`});let r=a.getAttribute("aria-pressed")!=="true";if(!await B(i,r?"reaction.add":"reaction.remove",{articleId:i.dataset.articleId,reaction:a.dataset.reaction}))return;a.setAttribute("aria-pressed",String(r));return}let s=e.target.closest("[data-follow-article]");if(s){let i=s.closest("[data-engagement-url]");if(!i)return;if(!y)return R({kind:"follow",region:i,selector:"[data-follow-article]"});let r=s.getAttribute("aria-pressed")!=="true";if(!await B(i,r?"follow.add":"follow.remove",{targetType:"articles",targetId:i.dataset.articleId}))return;W(s,r);return}let o=e.target.closest("[data-copy-url]");if(o){let i=o.dataset.copyUrl;if(!window.isSecureContext||!navigator.clipboard?.writeText){v(o);return}try{await navigator.clipboard.writeText(i);let r=o.closest(".gala-share")?.querySelector(".gala-share__status");r&&(r.textContent="Link copied.")}catch{v(o)}return}let d=e.target.closest("[data-native-share]");if(d){if(navigator.share)try{await navigator.share({title:document.title,url:d.dataset.nativeShare})}catch(i){i.name!=="AbortError"&&v(d)}else v(d);return}let f=e.target.closest("[data-copy-code]");if(!f)return;let E=f.closest(".gala-code-block")?.querySelector("code");if(!(!E||!navigator.clipboard?.writeText||!window.isSecureContext))try{await navigator.clipboard.writeText(E.textContent),f.textContent="Copied"}catch{f.textContent="Select code to copy"}});var ee=new Set([...document.querySelectorAll("pre code")].map(e=>e.closest("pre")).filter(Boolean));ee.forEach(e=>{let t=document.createElement("div");t.className="gala-code-block",e.before(t),t.append(e);let n=document.createElement("button");n.type="button",n.dataset.copyCode="",n.textContent="Copy code",n.setAttribute("aria-label","Copy code block"),t.prepend(n)});function te(e){return[["Reactions",Object.values(e.reactions??{}).reduce((n,a)=>n+(Number.isSafeInteger(a)?a:0),0)],["Comments",Number.isSafeInteger(e.comments?.totalCount)?e.comments.totalCount:0],["Views",Number.isSafeInteger(e.views?.count)?e.views.count:0]]}function ne(e){return e<1e3?"<1K":new Intl.NumberFormat("en",{notation:"compact",maximumFractionDigits:1}).format(e)}function ae(e,t){let n=t?.data;if(!n||typeof n!="object")throw new TypeError("Engagement data is invalid");for(let[a,s]of te(n))e.querySelectorAll(`[data-engagement-stat="${a.toLowerCase()}"]`).forEach(o=>{o.textContent=ne(s),o.closest("div")?.setAttribute("title",`${s} ${a.toLowerCase()}`)});e.querySelector("[data-engagement-snapshot]")?.remove(),e.querySelector(".gala-engagement__placeholder")?.remove()}async function x(e,t="",n=!1,a=!1){let s=e.querySelector("[data-engagement-status]"),o=!!e.querySelector("[data-gala-comments]");try{let d=new URL(e.dataset.engagementUrl);t&&d.searchParams.set("commentsCursor",t);let f=await fetch(d,{headers:{Accept:"application/json"},credentials:"omit",cache:a?"no-store":"default"});if(!f.ok)throw new Error(`Engagement returned HTTP ${f.status}`);let E=await f.json();ae(e,E),s&&!o&&(s.textContent=E.errors?.length?"Some engagement data is temporarily unavailable.":"")}catch{s&&!o&&(s.textContent="Engagement is temporarily unavailable.")}}function re(){let e=new URL(window.location.href).searchParams,t={};for(let n of["utm_source","utm_medium","utm_campaign","utm_content","utm_term"]){let a=e.get(n);a&&[...a].length<=128&&(t[n]=a)}return t}function oe(e){let t=new URL(e.dataset.engagementUrl);t.pathname=t.pathname.replace(/\/engagement$/,"/views"),fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({language:document.documentElement.lang||void 0,referrer:document.referrer||void 0,campaign:re()}),credentials:"omit",keepalive:!0}).catch(()=>{})}document.addEventListener("gala-request-sign-in",e=>{y||T||R({kind:e.detail?.kind??"comment"})});var y=null,j=null,_=null,L=new Map,T=null;function R(e){let t=e.field;T={kind:e.kind,region:e.region,selector:e.selector,draft:t?t.value:null,caret:t?t.selectionStart:null};let n=document.querySelector("[data-gala-session-frame]"),a=document.querySelector("[data-engagement-status]");if(!n)return;let s=new URL(n.getAttribute("src"),window.location.href),o=new URL("/v1/widget/session/sign-in",s.origin);o.searchParams.set("siteId",s.searchParams.get("siteId")??""),t&&t.blur(),window.open(o,"gala-sign-in","popup,width=520,height=680")||(T=null,a&&(a.textContent="Allow pop-ups for this site to sign in, then try again."))}function Y(){let e=T;if(!e||!document.hasFocus())return;let t=e.region?.querySelector(e.selector);if(t){if(T=null,e.kind==="comment"){e.draft!=null&&t.value===""&&(t.value=e.draft),t.focus(),e.caret!=null&&typeof t.setSelectionRange=="function"&&t.setSelectionRange(e.caret,e.caret);return}t.click()}}window.addEventListener("focus",()=>{y&&T&&Y()});window.addEventListener("message",e=>{let t=document.querySelector("[data-gala-session-frame]");if(!t||e.data?.type!=="gala-session-established")return;let n=new URL(t.src).origin;e.origin===n&&t.contentWindow?.postMessage({type:"gala-session-recheck"},n)});document.querySelectorAll("[data-engagement-url]").forEach(e=>{x(e),oe(e)});var I=document.querySelector("[data-gala-session-frame]");function V(e){return e==="AUTHENTICATION_REQUIRED"||e==="INVALID_BEARER_TOKEN"||e==="REAUTHENTICATION_REQUIRED"?"Sign in again to continue.":e==="ENGAGEMENT_RATE_LIMITED"?"You are posting too quickly; try again shortly.":e==="CONTACT_RATE_LIMITED"?"You are sending too quickly; try again later.":e==="RESOURCE_NOT_FOUND"?"That item is no longer available.":e==="INVALID_ENGAGEMENT_WRITE"||e==="INVALID_CONTACT_SUBMISSION"||e==="INVALID_REQUEST"?"Check your entry and try again.":e==="ACCESS_DENIED"?"That action is not available for this account.":e==="IDEMPOTENCY_CONFLICT"||e==="ENGAGEMENT_STATE_CONFLICT"?"The item changed; reload and try again.":"The action could not be completed. Try again."}function Q(e,t){if(!I||!y)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let n=crypto.randomUUID();return new Promise((a,s)=>{let o=setTimeout(()=>{L.delete(n),s(new Error("REQUEST_TIMEOUT"))},1e4);L.set(n,{resolve:d=>{clearTimeout(o),a(d)},reject:d=>{clearTimeout(o),s(d)}}),I.contentWindow.postMessage({type:"gala-engagement-write",requestId:n,operation:e,payload:t},new URL(I.src).origin)})}document.addEventListener("submit",async e=>{let t=e.target.closest("[data-contact-form] form");if(!t)return;e.preventDefault();let n=t.closest("[data-contact-form]"),a=n.querySelector("[data-contact-status]");if(!y){a.textContent="Sign in with the account button before sending.";return}let s=new FormData(t);try{a.textContent="Sending\u2026",await Q("contact.submit",{siteId:n.dataset.siteId,subject:s.get("subject"),message:s.get("message"),website:s.get("website")||null,phone:s.get("phone")||null}),t.reset(),a.textContent="Message sent."}catch(o){a.textContent=V(o.message)}});async function B(e,t,n){let a=e.querySelector("[data-engagement-status]");try{return a&&(a.textContent="Saving\u2026"),await Q(t,n),a&&(a.textContent="Saved."),await x(e,"",!1,!0),!0}catch(s){return a&&(a.textContent=V(s.message)),!1}}if(I){let e=new URL(I.src).origin;window.addEventListener("message",t=>{if(t.origin!==e||t.source!==I.contentWindow)return;if(t.data?.type==="gala-engagement-result"){let o=L.get(t.data.requestId);if(!o)return;L.delete(t.data.requestId),t.data.ok===!0?o.resolve(t.data.result):o.reject(new Error(t.data.error?.code||"ENGAGEMENT_WRITE_FAILED"));return}if(t.data?.type==="gala-reader-state-result"){if(t.data.requestId!==_||t.data.ok!==!0)return;_=null;let o=new Set(Array.isArray(t.data.state?.reactions)?t.data.state.reactions:[]);document.querySelectorAll("[data-reaction]").forEach(d=>{d.setAttribute("aria-pressed",String(o.has(d.dataset.reaction)))}),document.querySelectorAll("[data-follow-article]").forEach(d=>{let f=t.data.state?.following===!0;W(d,f)});return}if(t.data?.type==="gala-session-height"){let o=Number(t.data.height);Number.isFinite(o)&&o>0&&o<2e3&&(I.height=String(Math.ceil(o)));return}if(t.data?.type!=="gala-session")return;y=t.data.user&&typeof t.data.user.id=="string"?t.data.user:null;let n=document.querySelector("[data-user-control]"),a=y?.displayName;if(n){n.setAttribute("aria-label",a?`Account: ${a}`:"Sign in or view account"),n.title=a?`Account: ${a}`:"Account";let o=n.querySelector("[data-user-label]");o&&(o.textContent=a||"Sign in")}let s=j!==(y?.id??null);if(j=y?.id??null,y){let o=document.querySelector("[data-article-id]");o&&(_=crypto.randomUUID(),I.contentWindow.postMessage({type:"gala-reader-state-request",requestId:_,articleId:o.dataset.articleId},e))}document.querySelectorAll("[data-engagement-url]").forEach(o=>{s&&x(o)}),y&&T&&Y()})}document.querySelectorAll("[data-gala-search]").forEach(e=>{let t=e.querySelector("form"),n=t?.elements.namedItem("q"),a=e.querySelector("[data-search-status]"),s=e.querySelector("[data-search-results]"),o=e.dataset.indexUrl,d,f=c=>String(c??"").normalize("NFKC").toLocaleLowerCase();function E(c,u){s.replaceChildren();for(let m of c){let p=document.createElement("li"),l=document.createElement("article"),h=document.createElement("h2"),w=document.createElement("a");if(w.href=m.url,w.lang=m.language,w.textContent=m.title,h.append(w),l.append(h),m.description){let k=document.createElement("p");k.textContent=m.description,l.append(k)}p.append(l),s.append(p)}a.textContent=`${c.length} result${c.length===1?"":"s"} for \u201C${u}\u201D.`}async function i(c){let u=c.trim();if(u===""){s.replaceChildren(),a.textContent="Enter a search term.";return}a.textContent="Searching\u2026";try{d??=fetch(o,{headers:{Accept:"application/json"}}).then(async l=>{if(!l.ok)throw new Error(`Search index returned HTTP ${l.status}`);let h=await l.json();if(h?.schemaVersion!==1||!Array.isArray(h.entries))throw new TypeError("Search index schema is unsupported");return h.entries});let m=f(u),p=(await d).filter(l=>f([l.title,l.description,l.language,...Array.isArray(l.tags)?l.tags:[],l.body].join(`
2
- `)).includes(m));E(p,u)}catch{s.replaceChildren(),a.textContent="Search is temporarily unavailable."}}t?.addEventListener("submit",c=>{c.preventDefault();let u=n.value,m=new URL(window.location.href);u.trim()===""?m.searchParams.delete("q"):m.searchParams.set("q",u),window.history.replaceState(null,"",m),i(u)});let r=new URLSearchParams(window.location.search).get("q")??"";n&&(n.value=r),i(r)});var S={value:null},A=document.querySelector("[data-gala-session-frame]"),z=A?new URL(A.src).origin:null,N=new Map;function K(e){return e==="AUTHENTICATION_REQUIRED"||e==="INVALID_BEARER_TOKEN"||e==="REAUTHENTICATION_REQUIRED"?"Sign in again to continue.":e==="ENGAGEMENT_RATE_LIMITED"?"You are posting too quickly; try again shortly.":e==="CONTACT_RATE_LIMITED"?"You are sending too quickly; try again later.":e==="RESOURCE_NOT_FOUND"?"That item is no longer available.":e==="INVALID_ENGAGEMENT_WRITE"||e==="INVALID_CONTACT_SUBMISSION"||e==="INVALID_REQUEST"?"Check your entry and try again.":e==="ACCESS_DENIED"?"That action is not available for this account.":e==="IDEMPOTENCY_CONFLICT"||e==="ENGAGEMENT_STATE_CONFLICT"?"The item changed; reload and try again.":"The action could not be completed. Try again."}function X(e,t){if(!A||!S.value)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let n=crypto.randomUUID();return new Promise((a,s)=>{let o=setTimeout(()=>{N.delete(n),s(new Error("REQUEST_TIMEOUT"))},1e4);N.set(n,{resolve:d=>{clearTimeout(o),a(d)},reject:d=>{clearTimeout(o),s(d)}}),A.contentWindow.postMessage({type:"gala-engagement-write",requestId:n,operation:e,payload:t},z)})}function U(e){document.dispatchEvent(new CustomEvent("gala-request-sign-in",{detail:e}))}A&&window.addEventListener("message",e=>{if(e.origin!==z||e.source!==A.contentWindow)return;if(e.data?.type==="gala-engagement-result"){let n=N.get(e.data.requestId);if(!n)return;N.delete(e.data.requestId),e.data.ok===!0?n.resolve(e.data.result):n.reject(new Error(e.data.error?.code||"ENGAGEMENT_WRITE_FAILED"));return}if(e.data?.type!=="gala-session")return;let t=e.data.user;S.value=t&&typeof t.id=="string"?t:null,window.dispatchEvent(new CustomEvent("gala-session-change"))});var se=5,g=(e,t,n)=>{let a=document.createElement(e);return t&&(a.className=t),n!==void 0&&(a.textContent=n),a};function ie(e){let t=new Map;for(let a of e){let s=a.parentCommentId??"";t.has(s)||t.set(s,[]),t.get(s).push(a)}let n=(a,s)=>(t.get(a)??[]).map(o=>({...o,depth:s,replies:n(o.commentId,s+1)}));return n("",0)}function ce(e,t){let n={items:[],cursor:null,total:0,busy:!1,status:"",phase:"loading"},a=/\/v1\/articles\/([^/]+)\/engagement/.exec(t)?.[1]??"",s=e.closest(".gala-conversation")?.querySelector("[data-engagement-status]");async function o(r="",{append:c=!1,fresh:u=!1}={}){let m=new URL(t);r&&m.searchParams.set("commentsCursor",r);let p=await fetch(m,{headers:{Accept:"application/json"},credentials:"omit",cache:u?"no-store":"default"});if(!p.ok)throw new Error(`Comments returned HTTP ${p.status}`);let l=(await p.json())?.data?.comments;if(!l||!Array.isArray(l.items))throw new TypeError("Comment page is invalid");n.items=c?[...n.items,...l.items]:l.items,n.cursor=l.nextCursor??null,Number.isSafeInteger(l.totalCount)&&(n.total=l.totalCount)}async function d(r,c){if(!S.value)return U({kind:"comment"}),!1;n.busy=!0,n.status="",i();try{return await X(r,c),await o("",{fresh:!0}),!0}catch(u){return n.status=K(u.message),!1}finally{n.busy=!1,i()}}function f(r,c,u){let m=g("form","gala-comment__form"),p=g("textarea");p.rows=3,p.value=c,p.placeholder=r,p.setAttribute("aria-label",r);let l=g("button","","Post");return l.type="submit",l.disabled=n.busy,m.append(p,l),m.addEventListener("submit",async h=>{h.preventDefault();let w=p.value.trim();w&&await u(w)&&(p.value="")}),m}function E(r){let c=g("li","gala-comment");c.dataset.commentId=r.commentId,c.dataset.depth=r.depth;let u=g("p","gala-comment__meta");if(u.append(g("strong","",r.author?.displayName??"[deleted]")),r.createdAt){let m=g("time","",new Date(r.createdAt).toLocaleDateString());m.dateTime=r.createdAt,u.append(m)}if(c.append(u,g("p","gala-comment__body",r.deleted?"[deleted]":r.body)),!r.deleted){let m=g("div","gala-comment-actions");if(r.depth<se){let l=g("button","","Reply");l.type="button",l.dataset.replyComment=r.commentId,l.addEventListener("click",()=>{let h=c.querySelector(":scope > .gala-comment__form");if(h){h.remove();return}m.after(f(`Reply to ${r.author?.displayName??"this comment"}`,"",w=>d("comment.create",{articleId:a,parentCommentId:r.commentId,body:w})))}),m.append(l)}if(S.value&&r.author?.userId===S.value.id){let l=g("button","","Edit");l.type="button",l.dataset.editComment=r.commentId,l.addEventListener("click",()=>m.after(f("Edit your comment",r.body??"",w=>d("comment.edit",{articleId:a,commentId:r.commentId,body:w}))));let h=g("button","","Delete");h.type="button",h.dataset.deleteComment=r.commentId,h.addEventListener("click",()=>d("comment.delete",{articleId:a,commentId:r.commentId})),m.append(l,h)}else{let l=g("button","","Report");l.type="button",l.dataset.reportComment=r.commentId,l.addEventListener("click",()=>d("comment.report",{articleId:a,commentId:r.commentId,reason:"OTHER"})),m.append(l)}c.append(m)}if(r.replies.length){let m=g("ol","gala-comment-replies");m.append(...r.replies.map(E)),c.append(m)}return c}function i(){if(s&&(s.textContent=n.phase==="loading"?"Loading comments\u2026":n.phase==="unavailable"?"Comments are temporarily unavailable.":n.status),n.phase!=="ready"){e.replaceChildren();return}let r=g("section","gala-comments-island");if(r.setAttribute("aria-label","Comments"),r.append(g("p","gala-comments__heading",n.total===1?"1 comment":`${n.total} comments`)),S.value)r.append(f("Add a comment","",c=>d("comment.create",{articleId:a,body:c})));else{let c=g("p","gala-comments__prompt"),u=g("button","","Sign in to join the conversation");u.type="button",u.addEventListener("click",()=>U({kind:"comment"})),c.append(u),r.append(c)}if(!n.items.length)r.append(g("p","gala-comments__empty","No comments yet."));else{let c=g("ol","gala-comments");c.append(...ie(n.items).map(E)),r.append(c)}if(n.cursor){let c=g("button","gala-comments__more","Show more comments");c.type="button",c.disabled=n.busy,c.addEventListener("click",async()=>{n.busy=!0,n.status="Loading more comments\u2026",i();try{await o(n.cursor,{append:!0}),n.status=""}catch{n.status="More comments couldn\u2019t be loaded. Try again."}n.busy=!1,i()}),r.append(c)}e.replaceChildren(r)}window.addEventListener("gala-session-change",i),i(),o().then(()=>{n.phase="ready"}).catch(()=>{n.phase="unavailable"}).finally(i)}document.querySelectorAll("[data-gala-comments]").forEach(e=>{let t=e.closest("[data-engagement-url]")?.dataset.engagementUrl;t&&ce(e,t)});})();
1
+ (()=>{var b=["system","light","dark"],O="gala-color-mode";function D(){let e=document.documentElement.dataset.mode;return b.includes(e)?e:"system"}function Z(){try{let e=localStorage.getItem(O);if(b.includes(e))return e}catch{return D()}return D()}function M(e){document.documentElement.dataset.mode=e;let t=b[(b.indexOf(e)+1)%b.length];document.querySelectorAll("[data-theme-mode-toggle]").forEach(a=>{let n=a.querySelector?.("[data-theme-mode-label]");n?n.textContent=`Theme: ${e}`:a.textContent=`Theme: ${e}`,a.setAttribute("aria-label",`Color mode: ${e}. Activate for ${t}.`)})}document.addEventListener("DOMContentLoaded",()=>{M(Z()),document.querySelectorAll("[data-theme-mode-toggle]").forEach(e=>{e.addEventListener("click",()=>{let t=b[(b.indexOf(document.documentElement.dataset.mode)+1)%b.length];try{localStorage.setItem(O,t)}catch{}M(t)})})});var F="gala-language-preference";function ee(){try{return localStorage.getItem(F)}catch{return null}}function P(e,t){return[...e.options].find(a=>a.value===t)}document.addEventListener("DOMContentLoaded",()=>{let e=ee();document.querySelectorAll("[data-language-preference]").forEach(t=>{e!=null&&P(t,e)!=null&&(t.value=e),t.addEventListener("change",()=>{try{localStorage.setItem(F,t.value)}catch{}if(!t.hasAttribute("data-navigate-on-selection"))return;let a=P(t,t.value);a?.dataset.url&&window.location.assign(a.dataset.url)})})});var H="gala.prism.session.v1";document.addEventListener("click",e=>{if(e.target.closest("[data-prism-restore]")){try{sessionStorage.removeItem(H)}catch{}return}let a=e.target.closest("[data-prism-select]");if(a)try{sessionStorage.setItem(H,JSON.stringify({articleId:a.dataset.prismArticleId,language:a.dataset.prismLanguage,configurationId:a.dataset.prismConfigurationId,sourceRevisionHash:a.dataset.prismSourceHash,selectedAt:new Date().toISOString()}))}catch{}});function v(e){let t=e.closest(".gala-share"),a=t?.querySelector(".gala-share__fallback");a?.classList.add("gala-share__fallback--visible"),a?.focus(),a?.select();let n=t?.querySelector(".gala-share__status");n&&(n.textContent="Select and copy the URL shown.")}var G=document.querySelector("[data-reading-progress]"),q=document.querySelector(".gala-markdown");if(G&&q){let e=null,t=()=>{e=null;let n=q.offsetTop,s=Math.max(1,q.offsetHeight-window.innerHeight),o=Math.min(1,Math.max(0,(window.scrollY-n)/s));G.value=o},a=()=>{e==null&&(e=requestAnimationFrame(t))};addEventListener("scroll",a,{passive:!0}),addEventListener("resize",a,{passive:!0}),a()}var C=document.querySelector(".gala-toc");if(C?.tagName==="DETAILS"){let e=C.querySelector("nav"),t=[...C.querySelectorAll('a[href^="#"]')],a=t.map(d=>document.getElementById(d.hash.slice(1))).filter(Boolean),n=null,s=()=>{if(n=null,C.classList.toggle("gala-toc--floating",C.getBoundingClientRect().top<=96),!a.length)return;let d=window.innerHeight*.42,f=a[0];for(let i of a)if(i.getBoundingClientRect().top<=d)f=i;else break;let y=t.find(i=>i.hash===`#${f.id}`);for(let i of t)i===y?i.setAttribute("aria-current","location"):i.removeAttribute("aria-current");if(e&&t.length){let i=t[0].getBoundingClientRect(),r=t[t.length-1].getBoundingClientRect();C.classList.toggle("gala-toc--overflowing",r.bottom-i.top>e.clientHeight)}if(y&&e&&e.scrollHeight>e.clientHeight){let i=e.getBoundingClientRect(),r=y.getBoundingClientRect(),c=e.scrollTop+r.top-i.top-(e.clientHeight-r.height)/2;e.scrollTo({top:Math.max(0,c),behavior:"auto"})}},o=()=>{n==null&&(n=requestAnimationFrame(s))};addEventListener("scroll",o,{passive:!0}),addEventListener("resize",o,{passive:!0}),o()}var $=document.querySelector(".gala-action-rail"),j=document.querySelector("[data-action-dock]");if($&&j){let e=t=>{$.classList.toggle("gala-action-rail--integrated",t[0].isIntersecting)};new IntersectionObserver(e,{rootMargin:"0px 0px 18% 0px",threshold:0}).observe(j)}function Y(e,t){let a=t?"Unfollow article":"Follow article";e.setAttribute("aria-pressed",String(t)),e.setAttribute("aria-label",a),e.title=a;let n=e.querySelector("[data-follow-label]");n&&(n.textContent=a)}document.addEventListener("click",async e=>{if(e.target instanceof HTMLDialogElement&&e.target.open){let i=e.target.getBoundingClientRect();if(e.clientX<i.left||e.clientX>i.right||e.clientY<i.top||e.clientY>i.bottom){e.target.close();return}}let t=e.target.closest("[data-gala-embed-load]");if(t){let i=t.closest("[data-gala-embed]"),r=t.dataset.galaEmbedSrc,c=t.dataset.galaEmbedLoad;if(!i||!r||!["youtube","codepen"].includes(c))return;let u=document.createElement("iframe");u.src=r,u.title=c==="youtube"?"YouTube video":"CodePen example",u.loading="eager",u.referrerPolicy="strict-origin-when-cross-origin",u.setAttribute("sandbox","allow-forms allow-popups allow-presentation allow-same-origin allow-scripts"),u.setAttribute("allow",c==="youtube"?"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share":"fullscreen"),u.setAttribute("allowfullscreen",""),i.replaceChildren(u);return}let a=e.target.closest("[data-open-dialog]");if(a){let i=document.getElementById(a.dataset.openDialog);i instanceof HTMLDialogElement&&i.showModal();return}let n=e.target.closest("[data-reaction]");if(n){let i=n.closest("[data-engagement-url]");if(!i)return;if(!E)return R({kind:"reaction",region:i,selector:`[data-reaction="${n.dataset.reaction}"]`});let r=n.getAttribute("aria-pressed")!=="true";if(!await W(i,r?"reaction.add":"reaction.remove",{articleId:i.dataset.articleId,reaction:n.dataset.reaction}))return;n.setAttribute("aria-pressed",String(r));return}let s=e.target.closest("[data-follow-article]");if(s){let i=s.closest("[data-engagement-url]");if(!i)return;if(!E)return R({kind:"follow",region:i,selector:"[data-follow-article]"});let r=s.getAttribute("aria-pressed")!=="true";if(!await W(i,r?"follow.add":"follow.remove",{targetType:"articles",targetId:i.dataset.articleId}))return;Y(s,r);return}let o=e.target.closest("[data-copy-url]");if(o){let i=o.dataset.copyUrl;if(!window.isSecureContext||!navigator.clipboard?.writeText){v(o);return}try{await navigator.clipboard.writeText(i);let r=o.closest(".gala-share")?.querySelector(".gala-share__status");r&&(r.textContent="Link copied.")}catch{v(o)}return}let d=e.target.closest("[data-native-share]");if(d){if(navigator.share)try{await navigator.share({title:document.title,url:d.dataset.nativeShare})}catch(i){i.name!=="AbortError"&&v(d)}else v(d);return}let f=e.target.closest("[data-copy-code]");if(!f)return;let y=f.closest(".gala-code-block")?.querySelector("code");if(!(!y||!navigator.clipboard?.writeText||!window.isSecureContext))try{await navigator.clipboard.writeText(y.textContent),f.textContent="Copied"}catch{f.textContent="Select code to copy"}});var te=new Set([...document.querySelectorAll("pre code")].map(e=>e.closest("pre")).filter(Boolean));te.forEach(e=>{let t=document.createElement("div");t.className="gala-code-block",e.before(t),t.append(e);let a=document.createElement("button");a.type="button",a.dataset.copyCode="",a.textContent="Copy code",a.setAttribute("aria-label","Copy code block"),t.prepend(a)});function ae(e){return[["Reactions",Object.values(e.reactions??{}).reduce((a,n)=>a+(Number.isSafeInteger(n)?n:0),0)],["Comments",Number.isSafeInteger(e.comments?.totalCount)?e.comments.totalCount:0],["Views",Number.isSafeInteger(e.views?.count)?e.views.count:0]]}function ne(e){return e<1e3?"<1K":new Intl.NumberFormat("en",{notation:"compact",maximumFractionDigits:1}).format(e)}function re(e,t){let a=t?.data;if(!a||typeof a!="object")throw new TypeError("Engagement data is invalid");for(let[n,s]of ae(a))e.querySelectorAll(`[data-engagement-stat="${n.toLowerCase()}"]`).forEach(o=>{o.textContent=ne(s),o.closest("div")?.setAttribute("title",`${s} ${n.toLowerCase()}`)});e.querySelector("[data-engagement-snapshot]")?.remove(),e.querySelector(".gala-engagement__placeholder")?.remove()}async function x(e,t="",a=!1,n=!1){let s=e.querySelector("[data-engagement-status]"),o=!!e.querySelector("[data-gala-comments]");try{let d=new URL(e.dataset.engagementUrl);t&&d.searchParams.set("commentsCursor",t);let f=await fetch(d,{headers:{Accept:"application/json"},credentials:"omit",cache:n?"no-store":"default"});if(!f.ok)throw new Error(`Engagement returned HTTP ${f.status}`);let y=await f.json();re(e,y),s&&!o&&(s.textContent=y.errors?.length?"Some engagement data is temporarily unavailable.":"")}catch{s&&!o&&(s.textContent="Engagement is temporarily unavailable.")}}function oe(){let e=new URL(window.location.href).searchParams,t={};for(let a of["utm_source","utm_medium","utm_campaign","utm_content","utm_term"]){let n=e.get(a);n&&[...n].length<=128&&(t[a]=n)}return t}function se(e){let t=new URL(e.dataset.engagementUrl);t.pathname=t.pathname.replace(/\/engagement$/,"/views"),fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({language:document.documentElement.lang||void 0,referrer:document.referrer||void 0,campaign:oe()}),credentials:"omit",keepalive:!0}).catch(()=>{})}document.addEventListener("gala-request-sign-in",e=>{E||S||R({kind:e.detail?.kind??"comment"})});var E=null,B=null,_=null,L=new Map,S=null;function R(e){let t=e.field;S={kind:e.kind,region:e.region,selector:e.selector,draft:t?t.value:null,caret:t?t.selectionStart:null};let a=document.querySelector("[data-gala-session-frame]"),n=document.querySelector("[data-engagement-status]");if(!a)return;let s=new URL(a.getAttribute("src"),window.location.href),o=new URL("/v1/widget/session/sign-in",s.origin);o.searchParams.set("siteId",s.searchParams.get("siteId")??""),t&&t.blur(),window.open(o,"gala-sign-in","popup,width=520,height=680")||(S=null,n&&(n.textContent="Allow pop-ups for this site to sign in, then try again."))}function V(){let e=S;if(!e||!document.hasFocus())return;let t=e.region?.querySelector(e.selector);if(t){if(S=null,e.kind==="comment"){e.draft!=null&&t.value===""&&(t.value=e.draft),t.focus(),e.caret!=null&&typeof t.setSelectionRange=="function"&&t.setSelectionRange(e.caret,e.caret);return}t.click()}}window.addEventListener("focus",()=>{E&&S&&V()});window.addEventListener("message",e=>{let t=document.querySelector("[data-gala-session-frame]");if(!t||e.data?.type!=="gala-session-established")return;let a=new URL(t.src).origin;e.origin===a&&t.contentWindow?.postMessage({type:"gala-session-recheck"},a)});document.querySelectorAll("[data-engagement-url]").forEach(e=>{x(e),se(e)});var I=document.querySelector("[data-gala-session-frame]");function Q(e){return e==="AUTHENTICATION_REQUIRED"||e==="INVALID_BEARER_TOKEN"||e==="REAUTHENTICATION_REQUIRED"?"Sign in again to continue.":e==="ENGAGEMENT_RATE_LIMITED"?"You are posting too quickly; try again shortly.":e==="CONTACT_RATE_LIMITED"?"You are sending too quickly; try again later.":e==="RESOURCE_NOT_FOUND"?"That item is no longer available.":e==="INVALID_ENGAGEMENT_WRITE"||e==="INVALID_CONTACT_SUBMISSION"||e==="INVALID_REQUEST"?"Check your entry and try again.":e==="ACCESS_DENIED"?"That action is not available for this account.":e==="IDEMPOTENCY_CONFLICT"||e==="ENGAGEMENT_STATE_CONFLICT"?"The item changed; reload and try again.":"The action could not be completed. Try again."}function K(e,t){if(!I||!E)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let a=crypto.randomUUID();return new Promise((n,s)=>{let o=setTimeout(()=>{L.delete(a),s(new Error("REQUEST_TIMEOUT"))},1e4);L.set(a,{resolve:d=>{clearTimeout(o),n(d)},reject:d=>{clearTimeout(o),s(d)}}),I.contentWindow.postMessage({type:"gala-engagement-write",requestId:a,operation:e,payload:t},new URL(I.src).origin)})}document.addEventListener("submit",async e=>{let t=e.target.closest("[data-contact-form] form");if(!t)return;e.preventDefault();let a=t.closest("[data-contact-form]"),n=a.querySelector("[data-contact-status]");if(!E){n.textContent="Sign in with the account button before sending.";return}let s=new FormData(t);try{n.textContent="Sending\u2026",await K("contact.submit",{siteId:a.dataset.siteId,subject:s.get("subject"),message:s.get("message"),website:s.get("website")||null,phone:s.get("phone")||null}),t.reset(),n.textContent="Message sent."}catch(o){n.textContent=Q(o.message)}});async function W(e,t,a){let n=e.querySelector("[data-engagement-status]");try{return n&&(n.textContent="Saving\u2026"),await K(t,a),n&&(n.textContent="Saved."),await x(e,"",!1,!0),!0}catch(s){return n&&(n.textContent=Q(s.message)),!1}}if(I){let e=new URL(I.src).origin;window.addEventListener("message",t=>{if(t.origin!==e||t.source!==I.contentWindow)return;if(t.data?.type==="gala-engagement-result"){let o=L.get(t.data.requestId);if(!o)return;L.delete(t.data.requestId),t.data.ok===!0?o.resolve(t.data.result):o.reject(new Error(t.data.error?.code||"ENGAGEMENT_WRITE_FAILED"));return}if(t.data?.type==="gala-reader-state-result"){if(t.data.requestId!==_||t.data.ok!==!0)return;_=null;let o=new Set(Array.isArray(t.data.state?.reactions)?t.data.state.reactions:[]);document.querySelectorAll("[data-reaction]").forEach(d=>{d.setAttribute("aria-pressed",String(o.has(d.dataset.reaction)))}),document.querySelectorAll("[data-follow-article]").forEach(d=>{let f=t.data.state?.following===!0;Y(d,f)});return}if(t.data?.type==="gala-session-height"){let o=Number(t.data.height);Number.isFinite(o)&&o>0&&o<2e3&&(I.height=String(Math.ceil(o)));return}if(t.data?.type!=="gala-session")return;E=t.data.user&&typeof t.data.user.id=="string"?t.data.user:null;let a=document.querySelector("[data-user-control]"),n=E?.displayName;if(a){a.setAttribute("aria-label",n?`Account: ${n}`:"Sign in or view account"),a.title=n?`Account: ${n}`:"Account";let o=a.querySelector("[data-user-label]");o&&(o.textContent=n||"Sign in")}let s=B!==(E?.id??null);if(B=E?.id??null,E){let o=document.querySelector("[data-article-id]");o&&(_=crypto.randomUUID(),I.contentWindow.postMessage({type:"gala-reader-state-request",requestId:_,articleId:o.dataset.articleId},e))}document.querySelectorAll("[data-engagement-url]").forEach(o=>{s&&x(o)}),E&&S&&V()})}document.querySelectorAll("[data-gala-search]").forEach(e=>{let t=e.querySelector("form"),a=t?.elements.namedItem("q"),n=e.querySelector("[data-search-status]"),s=e.querySelector("[data-search-results]"),o=e.dataset.indexUrl,d,f=c=>String(c??"").normalize("NFKC").toLocaleLowerCase();function y(c,u){s.replaceChildren();for(let m of c){let p=document.createElement("li"),l=document.createElement("article"),h=document.createElement("h2"),w=document.createElement("a");if(w.href=m.url,w.lang=m.language,w.textContent=m.title,h.append(w),l.append(h),m.description){let k=document.createElement("p");k.textContent=m.description,l.append(k)}p.append(l),s.append(p)}n.textContent=`${c.length} result${c.length===1?"":"s"} for \u201C${u}\u201D.`}async function i(c){let u=c.trim();if(u===""){s.replaceChildren(),n.textContent="Enter a search term.";return}n.textContent="Searching\u2026";try{d??=fetch(o,{headers:{Accept:"application/json"}}).then(async l=>{if(!l.ok)throw new Error(`Search index returned HTTP ${l.status}`);let h=await l.json();if(h?.schemaVersion!==1||!Array.isArray(h.entries))throw new TypeError("Search index schema is unsupported");return h.entries});let m=f(u),p=(await d).filter(l=>f([l.title,l.description,l.language,...Array.isArray(l.tags)?l.tags:[],l.body].join(`
2
+ `)).includes(m));y(p,u)}catch{s.replaceChildren(),n.textContent="Search is temporarily unavailable."}}t?.addEventListener("submit",c=>{c.preventDefault();let u=a.value,m=new URL(window.location.href);u.trim()===""?m.searchParams.delete("q"):m.searchParams.set("q",u),window.history.replaceState(null,"",m),i(u)});let r=new URLSearchParams(window.location.search).get("q")??"";a&&(a.value=r),i(r)});var T={value:null},A=document.querySelector("[data-gala-session-frame]"),z=A?new URL(A.src).origin:null,N=new Map;function X(e){return e==="AUTHENTICATION_REQUIRED"||e==="INVALID_BEARER_TOKEN"||e==="REAUTHENTICATION_REQUIRED"?"Sign in again to continue.":e==="ENGAGEMENT_RATE_LIMITED"?"You are posting too quickly; try again shortly.":e==="CONTACT_RATE_LIMITED"?"You are sending too quickly; try again later.":e==="RESOURCE_NOT_FOUND"?"That item is no longer available.":e==="INVALID_ENGAGEMENT_WRITE"||e==="INVALID_CONTACT_SUBMISSION"||e==="INVALID_REQUEST"?"Check your entry and try again.":e==="ACCESS_DENIED"?"That action is not available for this account.":e==="IDEMPOTENCY_CONFLICT"||e==="ENGAGEMENT_STATE_CONFLICT"?"The item changed; reload and try again.":"The action could not be completed. Try again."}function J(e,t){if(!A||!T.value)return Promise.reject(new Error("AUTHENTICATION_REQUIRED"));let a=crypto.randomUUID();return new Promise((n,s)=>{let o=setTimeout(()=>{N.delete(a),s(new Error("REQUEST_TIMEOUT"))},1e4);N.set(a,{resolve:d=>{clearTimeout(o),n(d)},reject:d=>{clearTimeout(o),s(d)}}),A.contentWindow.postMessage({type:"gala-engagement-write",requestId:a,operation:e,payload:t},z)})}function U(e){document.dispatchEvent(new CustomEvent("gala-request-sign-in",{detail:e}))}A&&window.addEventListener("message",e=>{if(e.origin!==z||e.source!==A.contentWindow)return;if(e.data?.type==="gala-engagement-result"){let a=N.get(e.data.requestId);if(!a)return;N.delete(e.data.requestId),e.data.ok===!0?a.resolve(e.data.result):a.reject(new Error(e.data.error?.code||"ENGAGEMENT_WRITE_FAILED"));return}if(e.data?.type!=="gala-session")return;let t=e.data.user;T.value=t&&typeof t.id=="string"?t:null,window.dispatchEvent(new CustomEvent("gala-session-change"))});var ie=5,g=(e,t,a)=>{let n=document.createElement(e);return t&&(n.className=t),a!==void 0&&(n.textContent=a),n};function ce(e){let t=new Map;for(let n of e){let s=n.parentCommentId??"";t.has(s)||t.set(s,[]),t.get(s).push(n)}let a=(n,s)=>(t.get(n)??[]).map(o=>({...o,depth:s,replies:a(o.commentId,s+1)}));return a("",0)}function le(e,t){let a={items:[],cursor:null,total:0,busy:!1,status:"",phase:"loading"},n=/\/v1\/articles\/([^/]+)\/engagement/.exec(t)?.[1]??"",s=e.closest(".gala-conversation")?.querySelector("[data-engagement-status]");async function o(r="",{append:c=!1,fresh:u=!1}={}){let m=new URL(t);r&&m.searchParams.set("commentsCursor",r);let p=await fetch(m,{headers:{Accept:"application/json"},credentials:"omit",cache:u?"no-store":"default"});if(!p.ok)throw new Error(`Comments returned HTTP ${p.status}`);let l=(await p.json())?.data?.comments;if(!l||!Array.isArray(l.items))throw new TypeError("Comment page is invalid");a.items=c?[...a.items,...l.items]:l.items,a.cursor=l.nextCursor??null,Number.isSafeInteger(l.totalCount)&&(a.total=l.totalCount)}async function d(r,c){if(!T.value)return U({kind:"comment"}),!1;a.busy=!0,a.status="",i();try{return await J(r,c),await o("",{fresh:!0}),!0}catch(u){return a.status=X(u.message),!1}finally{a.busy=!1,i()}}function f(r,c,u){let m=g("form","gala-comment__form"),p=g("textarea");p.rows=3,p.value=c,p.placeholder=r,p.setAttribute("aria-label",r);let l=g("button","","Post");return l.type="submit",l.disabled=a.busy,m.append(p,l),m.addEventListener("submit",async h=>{h.preventDefault();let w=p.value.trim();w&&await u(w)&&(p.value="")}),m}function y(r){let c=g("li","gala-comment");c.dataset.commentId=r.commentId,c.dataset.depth=r.depth;let u=g("p","gala-comment__meta");if(u.append(g("strong","",r.author?.displayName??"[deleted]")),r.createdAt){let m=g("time","",new Date(r.createdAt).toLocaleDateString());m.dateTime=r.createdAt,u.append(m)}if(c.append(u,g("p","gala-comment__body",r.deleted?"[deleted]":r.body)),!r.deleted){let m=g("div","gala-comment-actions");if(r.depth<ie){let l=g("button","","Reply");l.type="button",l.dataset.replyComment=r.commentId,l.addEventListener("click",()=>{let h=c.querySelector(":scope > .gala-comment__form");if(h){h.remove();return}m.after(f(`Reply to ${r.author?.displayName??"this comment"}`,"",w=>d("comment.create",{articleId:n,parentCommentId:r.commentId,body:w})))}),m.append(l)}if(T.value&&r.author?.userId===T.value.id){let l=g("button","","Edit");l.type="button",l.dataset.editComment=r.commentId,l.addEventListener("click",()=>m.after(f("Edit your comment",r.body??"",w=>d("comment.edit",{articleId:n,commentId:r.commentId,body:w}))));let h=g("button","","Delete");h.type="button",h.dataset.deleteComment=r.commentId,h.addEventListener("click",()=>d("comment.delete",{articleId:n,commentId:r.commentId})),m.append(l,h)}else{let l=g("button","","Report");l.type="button",l.dataset.reportComment=r.commentId,l.addEventListener("click",()=>d("comment.report",{articleId:n,commentId:r.commentId,reason:"OTHER"})),m.append(l)}c.append(m)}if(r.replies.length){let m=g("ol","gala-comment-replies");m.append(...r.replies.map(y)),c.append(m)}return c}function i(){if(s&&(s.textContent=a.phase==="loading"?"Loading comments\u2026":a.phase==="unavailable"?"Comments are temporarily unavailable.":a.status),a.phase!=="ready"){e.replaceChildren();return}let r=g("section","gala-comments-island");if(r.setAttribute("aria-label","Comments"),r.append(g("p","gala-comments__heading",a.total===1?"1 comment":`${a.total} comments`)),T.value)r.append(f("Add a comment","",c=>d("comment.create",{articleId:n,body:c})));else{let c=g("p","gala-comments__prompt"),u=g("button","","Sign in to join the conversation");u.type="button",u.addEventListener("click",()=>U({kind:"comment"})),c.append(u),r.append(c)}if(!a.items.length)r.append(g("p","gala-comments__empty","No comments yet."));else{let c=g("ol","gala-comments");c.append(...ce(a.items).map(y)),r.append(c)}if(a.cursor){let c=g("button","gala-comments__more","Show more comments");c.type="button",c.disabled=a.busy,c.addEventListener("click",async()=>{a.busy=!0,a.status="Loading more comments\u2026",i();try{await o(a.cursor,{append:!0}),a.status=""}catch{a.status="More comments couldn\u2019t be loaded. Try again."}a.busy=!1,i()}),r.append(c)}e.replaceChildren(r)}window.addEventListener("gala-session-change",i),i(),o().then(()=>{a.phase="ready"}).catch(()=>{a.phase="unavailable"}).finally(i)}document.querySelectorAll("[data-gala-comments]").forEach(e=>{let t=e.closest("[data-engagement-url]")?.dataset.engagementUrl;t&&le(e,t)});})();
@@ -1,5 +1,6 @@
1
1
  import '../assets/theme-mode.js';
2
2
  import '../assets/preferences.js';
3
+ import '../assets/prism-session.js';
3
4
  import '../assets/interactions.js';
4
5
  import '../assets/search.js';
5
6
  import '../assets/engagement-comments.js';
@@ -2,6 +2,7 @@ import { renderMarkdownDocument } from '../lib/render-markdown.js';
2
2
  import { articleHreflang, postSeo } from '../lib/seo.js';
3
3
  import { engagementFor } from '../lib/engagement-snapshot.js';
4
4
  import { resolveShareTargets } from '../lib/share-targets.js';
5
+ import { prismSessionBootstrap } from '../lib/prism-session.js';
5
6
 
6
7
  export default class ValidatedPostPages {
7
8
  data() {
@@ -32,6 +33,13 @@ export default class ValidatedPostPages {
32
33
  ? renderedDocument(post).tableOfContents
33
34
  : [],
34
35
  readingMinutes: ({ post }) => readingMinutes(post?.body),
36
+ prismConfigurations: ({ buildManifest, post }) => (buildManifest.configurations ?? []).filter(
37
+ (configuration) => configuration.articleId === post.id
38
+ && configuration.language === post.language && configuration.state === 'PUBLISHED'),
39
+ prismBootstrap: ({ buildManifest, post }) => prismSessionBootstrap(post,
40
+ (buildManifest.configurations ?? []).filter((configuration) =>
41
+ configuration.articleId === post.id && configuration.language === post.language
42
+ && configuration.state === 'PUBLISHED')),
35
43
  seo: ({ post, site }) => post?.publicationState === 'published'
36
44
  ? postSeo({ post, site, renderedHtml: renderedDocument(post).html })
37
45
  : null
@@ -0,0 +1,31 @@
1
+ import { renderMarkdownDocument } from '../lib/render-markdown.js';
2
+ import { postSeo } from '../lib/seo.js';
3
+
4
+ export default class PrismConfigurationPages {
5
+ data() {
6
+ return {
7
+ pagination: { data: 'buildManifest.configurations', size: 1, alias: 'prismConfiguration' },
8
+ layout: 'layouts/prism-configuration.njk',
9
+ permalink: ({ prismConfiguration }) => `${prismConfiguration.relativeUrl}index.html`,
10
+ eleventyComputed: {
11
+ post: ({ buildManifest, prismConfiguration }) => buildManifest.posts.find((candidate) =>
12
+ candidate.id === prismConfiguration.articleId && candidate.language === prismConfiguration.language),
13
+ prismAlternates: ({ buildManifest, prismConfiguration }) => buildManifest.configurations.filter(
14
+ (candidate) => candidate.articleId === prismConfiguration.articleId
15
+ && candidate.language === prismConfiguration.language && candidate.state === 'PUBLISHED'),
16
+ seo: ({ post, prismConfiguration, site }) =>
17
+ prismConfiguration?.state === 'PUBLISHED' && post?.publicationState === 'published'
18
+ ? postSeo({
19
+ post: { ...post, canonicalUrl: prismConfiguration.canonicalUrl },
20
+ site,
21
+ renderedHtml: renderMarkdownDocument(prismConfiguration.body).html
22
+ }) : null
23
+ }
24
+ };
25
+ }
26
+
27
+ render({ prismConfiguration }) {
28
+ if (prismConfiguration.state !== 'PUBLISHED') return '';
29
+ return renderMarkdownDocument(prismConfiguration.body).html;
30
+ }
31
+ }