@rathnasgala/theme 0.0.20 → 2.0.2
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/payload/.gala/managed-files.json +28 -23
- package/payload/eleventy.config.js +15 -1
- package/payload/lib/accent.js +105 -0
- package/payload/lib/render-markdown.js +1 -1
- package/payload/lib/seo.js +17 -3
- package/payload/lib/site-config.js +75 -5
- package/payload/package-lock.json +487 -2
- package/payload/package.json +7 -6
- package/payload/scripts/build-reader.js +24 -0
- package/payload/scripts/lint.js +21 -0
- package/payload/src/_data/site.js +16 -0
- package/payload/src/_includes/components/ui.njk +38 -17
- package/payload/src/_includes/layouts/base.njk +33 -26
- package/payload/src/_includes/layouts/post.njk +11 -7
- package/payload/src/accent.11ty.js +10 -0
- package/payload/src/assets/engagement-comments.js +125 -234
- package/payload/src/assets/engagement-transport.js +10 -4
- package/payload/src/assets/interactions.js +161 -39
- package/payload/src/assets/reader.js +2 -0
- package/payload/src/assets/theme.css +1 -676
- package/payload/src/client/reader.js +5 -0
- package/payload/src/posts.11ty.js +6 -0
- package/payload/src/styles/theme.css +1058 -0
- package/payload/static/favicon.ico +0 -0
- package/payload/src/assets/vendor/hooks.js +0 -2
- package/payload/src/assets/vendor/htm.js +0 -2
- package/payload/src/assets/vendor/preact.js +0 -2
- package/payload/src/assets/vendor/signals-core.js +0 -2
- package/payload/src/assets/vendor/signals.js +0 -2
|
@@ -1,13 +1,106 @@
|
|
|
1
1
|
function selectableFallback(control) {
|
|
2
2
|
const region = control.closest('.gala-share');
|
|
3
3
|
const fallback = region?.querySelector('.gala-share__fallback');
|
|
4
|
+
fallback?.classList.add('gala-share__fallback--visible');
|
|
4
5
|
fallback?.focus();
|
|
5
6
|
fallback?.select();
|
|
6
7
|
const status = region?.querySelector('.gala-share__status');
|
|
7
8
|
if (status) status.textContent = 'Select and copy the URL shown.';
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
const readingProgress = document.querySelector('[data-reading-progress]');
|
|
12
|
+
const readingContent = document.querySelector('.gala-markdown');
|
|
13
|
+
if (readingProgress && readingContent) {
|
|
14
|
+
let progressFrame = null;
|
|
15
|
+
const updateReadingProgress = () => {
|
|
16
|
+
progressFrame = null;
|
|
17
|
+
const start = readingContent.offsetTop;
|
|
18
|
+
const distance = Math.max(1, readingContent.offsetHeight - window.innerHeight);
|
|
19
|
+
const progress = Math.min(1, Math.max(0, (window.scrollY - start) / distance));
|
|
20
|
+
readingProgress.value = progress;
|
|
21
|
+
};
|
|
22
|
+
const scheduleReadingProgress = () => {
|
|
23
|
+
if (progressFrame == null) progressFrame = requestAnimationFrame(updateReadingProgress);
|
|
24
|
+
};
|
|
25
|
+
addEventListener('scroll', scheduleReadingProgress, { passive: true });
|
|
26
|
+
addEventListener('resize', scheduleReadingProgress, { passive: true });
|
|
27
|
+
scheduleReadingProgress();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const articleToc = document.querySelector('.gala-toc');
|
|
31
|
+
if (articleToc?.tagName === 'DETAILS') {
|
|
32
|
+
const tocNavigation = articleToc.querySelector('nav');
|
|
33
|
+
const tocLinks = [...articleToc.querySelectorAll('a[href^="#"]')];
|
|
34
|
+
const headings = tocLinks.map((link) => document.getElementById(link.hash.slice(1))).filter(Boolean);
|
|
35
|
+
let tocFrame = null;
|
|
36
|
+
const synchronizeToc = () => {
|
|
37
|
+
tocFrame = null;
|
|
38
|
+
articleToc.classList.toggle('gala-toc--floating', articleToc.getBoundingClientRect().top <= 96);
|
|
39
|
+
if (!headings.length) return;
|
|
40
|
+
const readingLine = window.innerHeight * 0.42;
|
|
41
|
+
let active = headings[0];
|
|
42
|
+
for (const heading of headings) {
|
|
43
|
+
if (heading.getBoundingClientRect().top <= readingLine) active = heading;
|
|
44
|
+
else break;
|
|
45
|
+
}
|
|
46
|
+
const activeLink = tocLinks.find((link) => link.hash === `#${active.id}`);
|
|
47
|
+
for (const link of tocLinks) {
|
|
48
|
+
if (link === activeLink) link.setAttribute('aria-current', 'location');
|
|
49
|
+
else link.removeAttribute('aria-current');
|
|
50
|
+
}
|
|
51
|
+
if (tocNavigation && tocLinks.length) {
|
|
52
|
+
const firstRect = tocLinks[0].getBoundingClientRect();
|
|
53
|
+
const lastRect = tocLinks[tocLinks.length - 1].getBoundingClientRect();
|
|
54
|
+
articleToc.classList.toggle('gala-toc--overflowing',
|
|
55
|
+
lastRect.bottom - firstRect.top > tocNavigation.clientHeight);
|
|
56
|
+
}
|
|
57
|
+
if (activeLink && tocNavigation && tocNavigation.scrollHeight > tocNavigation.clientHeight) {
|
|
58
|
+
const navigationRect = tocNavigation.getBoundingClientRect();
|
|
59
|
+
const activeRect = activeLink.getBoundingClientRect();
|
|
60
|
+
const target = tocNavigation.scrollTop + activeRect.top - navigationRect.top
|
|
61
|
+
- (tocNavigation.clientHeight - activeRect.height) / 2;
|
|
62
|
+
tocNavigation.scrollTo({ top: Math.max(0, target), behavior: 'auto' });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const scheduleToc = () => {
|
|
66
|
+
if (tocFrame == null) tocFrame = requestAnimationFrame(synchronizeToc);
|
|
67
|
+
};
|
|
68
|
+
addEventListener('scroll', scheduleToc, { passive: true });
|
|
69
|
+
addEventListener('resize', scheduleToc, { passive: true });
|
|
70
|
+
scheduleToc();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const actionRail = document.querySelector('.gala-action-rail');
|
|
74
|
+
const actionDock = document.querySelector('[data-action-dock]');
|
|
75
|
+
if (actionRail && actionDock) {
|
|
76
|
+
const synchronizeActionRail = (entries) => {
|
|
77
|
+
actionRail.classList.toggle('gala-action-rail--integrated', entries[0].isIntersecting);
|
|
78
|
+
};
|
|
79
|
+
new IntersectionObserver(synchronizeActionRail, {
|
|
80
|
+
rootMargin: '0px 0px 18% 0px', threshold: 0
|
|
81
|
+
}).observe(actionDock);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function presentFollowState(control, following) {
|
|
85
|
+
const label = following ? 'Unfollow article' : 'Follow article';
|
|
86
|
+
control.setAttribute('aria-pressed', String(following));
|
|
87
|
+
control.setAttribute('aria-label', label);
|
|
88
|
+
control.title = label;
|
|
89
|
+
const visibleLabel = control.querySelector('[data-follow-label]');
|
|
90
|
+
if (visibleLabel) visibleLabel.textContent = label;
|
|
91
|
+
}
|
|
92
|
+
|
|
10
93
|
document.addEventListener('click', async (event) => {
|
|
94
|
+
if (event.target instanceof HTMLDialogElement && event.target.open) {
|
|
95
|
+
const bounds = event.target.getBoundingClientRect();
|
|
96
|
+
const outside = event.clientX < bounds.left || event.clientX > bounds.right
|
|
97
|
+
|| event.clientY < bounds.top || event.clientY > bounds.bottom;
|
|
98
|
+
if (outside) {
|
|
99
|
+
event.target.close();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
11
104
|
const embed = event.target.closest('[data-gala-embed-load]');
|
|
12
105
|
if (embed) {
|
|
13
106
|
const container = embed.closest('[data-gala-embed]');
|
|
@@ -65,8 +158,7 @@ document.addEventListener('click', async (event) => {
|
|
|
65
158
|
targetType: 'articles', targetId: region.dataset.articleId
|
|
66
159
|
});
|
|
67
160
|
if (!saved) return;
|
|
68
|
-
follow
|
|
69
|
-
follow.textContent = active ? 'Unfollow article' : 'Follow article';
|
|
161
|
+
presentFollowState(follow, active);
|
|
70
162
|
return;
|
|
71
163
|
}
|
|
72
164
|
|
|
@@ -87,6 +179,20 @@ document.addEventListener('click', async (event) => {
|
|
|
87
179
|
return;
|
|
88
180
|
}
|
|
89
181
|
|
|
182
|
+
const nativeShare = event.target.closest('[data-native-share]');
|
|
183
|
+
if (nativeShare) {
|
|
184
|
+
if (navigator.share) {
|
|
185
|
+
try {
|
|
186
|
+
await navigator.share({ title: document.title, url: nativeShare.dataset.nativeShare });
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error.name !== 'AbortError') selectableFallback(nativeShare);
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
selectableFallback(nativeShare);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
90
196
|
const copy = event.target.closest('[data-copy-code]');
|
|
91
197
|
if (!copy) return;
|
|
92
198
|
const code = copy.closest('.gala-code-block')?.querySelector('code');
|
|
@@ -115,13 +221,6 @@ codeBlocks.forEach((pre) => {
|
|
|
115
221
|
wrapper.prepend(control);
|
|
116
222
|
});
|
|
117
223
|
|
|
118
|
-
function textElement(name, text, className) {
|
|
119
|
-
const element = document.createElement(name);
|
|
120
|
-
element.textContent = text;
|
|
121
|
-
if (className) element.className = className;
|
|
122
|
-
return element;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
224
|
function engagementCounts(data) {
|
|
126
225
|
const reactionTotal = Object.values(data.reactions ?? {})
|
|
127
226
|
.reduce((total, count) => total + (Number.isSafeInteger(count) ? count : 0), 0);
|
|
@@ -132,33 +231,22 @@ function engagementCounts(data) {
|
|
|
132
231
|
];
|
|
133
232
|
}
|
|
134
233
|
|
|
135
|
-
function
|
|
234
|
+
function publicCount(value) {
|
|
235
|
+
if (value < 1000) return '<1K';
|
|
236
|
+
return new Intl.NumberFormat('en', {
|
|
237
|
+
notation: 'compact', maximumFractionDigits: 1
|
|
238
|
+
}).format(value);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function renderEngagement(region, payload) {
|
|
136
242
|
const data = payload?.data;
|
|
137
243
|
if (!data || typeof data !== 'object') throw new TypeError('Engagement data is invalid');
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
profile.className = 'gala-engagement-profile';
|
|
145
|
-
profile.setAttribute('aria-label', 'Author profile');
|
|
146
|
-
profile.append(textElement('h2', data.profile.displayName));
|
|
147
|
-
if (data.profile.username) profile.append(textElement('p', `@${data.profile.username}`));
|
|
148
|
-
if (Number.isSafeInteger(data.profile.followerCount)) {
|
|
149
|
-
profile.append(textElement('p', `${data.profile.followerCount} followers`));
|
|
150
|
-
}
|
|
151
|
-
live.append(profile);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!appendComments) {
|
|
155
|
-
const summary = document.createElement('dl');
|
|
156
|
-
for (const [label, value] of engagementCounts(data)) {
|
|
157
|
-
const item = document.createElement('div');
|
|
158
|
-
item.append(textElement('dt', label), textElement('dd', String(value)));
|
|
159
|
-
summary.append(item);
|
|
160
|
-
}
|
|
161
|
-
live.append(summary);
|
|
244
|
+
for (const [label, value] of engagementCounts(data)) {
|
|
245
|
+
region.querySelectorAll(`[data-engagement-stat="${label.toLowerCase()}"]`)
|
|
246
|
+
.forEach((count) => {
|
|
247
|
+
count.textContent = publicCount(value);
|
|
248
|
+
count.closest('div')?.setAttribute('title', `${value} ${label.toLowerCase()}`);
|
|
249
|
+
});
|
|
162
250
|
}
|
|
163
251
|
|
|
164
252
|
region.querySelector('[data-engagement-snapshot]')?.remove();
|
|
@@ -167,6 +255,7 @@ function renderEngagement(region, payload, appendComments = false) {
|
|
|
167
255
|
|
|
168
256
|
async function refreshEngagement(region, commentsCursor = '', appendComments = false, fresh = false) {
|
|
169
257
|
const status = region.querySelector('[data-engagement-status]');
|
|
258
|
+
const commentsOwnStatus = Boolean(region.querySelector('[data-gala-comments]'));
|
|
170
259
|
try {
|
|
171
260
|
const requestUrl = new URL(region.dataset.engagementUrl);
|
|
172
261
|
if (commentsCursor) requestUrl.searchParams.set('commentsCursor', commentsCursor);
|
|
@@ -177,11 +266,11 @@ async function refreshEngagement(region, commentsCursor = '', appendComments = f
|
|
|
177
266
|
});
|
|
178
267
|
if (!response.ok) throw new Error(`Engagement returned HTTP ${response.status}`);
|
|
179
268
|
const payload = await response.json();
|
|
180
|
-
renderEngagement(region, payload
|
|
181
|
-
if (status) status.textContent = payload.errors?.length
|
|
269
|
+
renderEngagement(region, payload);
|
|
270
|
+
if (status && !commentsOwnStatus) status.textContent = payload.errors?.length
|
|
182
271
|
? 'Some engagement data is temporarily unavailable.' : '';
|
|
183
272
|
} catch {
|
|
184
|
-
if (status) status.textContent = '
|
|
273
|
+
if (status && !commentsOwnStatus) status.textContent = 'Engagement is temporarily unavailable.';
|
|
185
274
|
}
|
|
186
275
|
}
|
|
187
276
|
|
|
@@ -225,6 +314,7 @@ let sessionUser = null;
|
|
|
225
314
|
// The reader the current engagement render was built for: null until the frame reports, and
|
|
226
315
|
// null again for a signed-out reader, so an anonymous load never re-requests.
|
|
227
316
|
let renderedSessionUser = null;
|
|
317
|
+
let readerStateRequestId = null;
|
|
228
318
|
const pendingEngagementWrites = new Map();
|
|
229
319
|
|
|
230
320
|
/**
|
|
@@ -331,7 +421,14 @@ function sendEngagementWrite(operation, payload) {
|
|
|
331
421
|
if (!sessionFrame || !sessionUser) return Promise.reject(new Error('AUTHENTICATION_REQUIRED'));
|
|
332
422
|
const requestId = crypto.randomUUID();
|
|
333
423
|
return new Promise((resolve, reject) => {
|
|
334
|
-
|
|
424
|
+
const timeout = setTimeout(() => {
|
|
425
|
+
pendingEngagementWrites.delete(requestId);
|
|
426
|
+
reject(new Error('REQUEST_TIMEOUT'));
|
|
427
|
+
}, 10_000);
|
|
428
|
+
pendingEngagementWrites.set(requestId, {
|
|
429
|
+
resolve: (value) => { clearTimeout(timeout); resolve(value); },
|
|
430
|
+
reject: (error) => { clearTimeout(timeout); reject(error); }
|
|
431
|
+
});
|
|
335
432
|
sessionFrame.contentWindow.postMessage({
|
|
336
433
|
type: 'gala-engagement-write', requestId, operation, payload
|
|
337
434
|
}, new URL(sessionFrame.src).origin);
|
|
@@ -392,13 +489,26 @@ if (sessionFrame) {
|
|
|
392
489
|
else pending.reject(new Error(event.data.error?.code || 'ENGAGEMENT_WRITE_FAILED'));
|
|
393
490
|
return;
|
|
394
491
|
}
|
|
492
|
+
if (event.data?.type === 'gala-reader-state-result') {
|
|
493
|
+
if (event.data.requestId !== readerStateRequestId || event.data.ok !== true) return;
|
|
494
|
+
readerStateRequestId = null;
|
|
495
|
+
const active = new Set(Array.isArray(event.data.state?.reactions) ? event.data.state.reactions : []);
|
|
496
|
+
document.querySelectorAll('[data-reaction]').forEach((control) => {
|
|
497
|
+
control.setAttribute('aria-pressed', String(active.has(control.dataset.reaction)));
|
|
498
|
+
});
|
|
499
|
+
document.querySelectorAll('[data-follow-article]').forEach((control) => {
|
|
500
|
+
const following = event.data.state?.following === true;
|
|
501
|
+
presentFollowState(control, following);
|
|
502
|
+
});
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
395
505
|
/* The frame is cross-origin, so its content height is not readable from here — it reports
|
|
396
506
|
its own, and the box is sized to it. Without this the account panel scrolled inside a fixed
|
|
397
507
|
box, clipping the first line of its own text. */
|
|
398
508
|
if (event.data?.type === 'gala-session-height') {
|
|
399
509
|
const height = Number(event.data.height);
|
|
400
510
|
if (Number.isFinite(height) && height > 0 && height < 2000) {
|
|
401
|
-
sessionFrame.
|
|
511
|
+
sessionFrame.height = String(Math.ceil(height));
|
|
402
512
|
}
|
|
403
513
|
return;
|
|
404
514
|
}
|
|
@@ -410,12 +520,24 @@ if (sessionFrame) {
|
|
|
410
520
|
control.setAttribute('aria-label', displayName
|
|
411
521
|
? `Account: ${displayName}` : 'Sign in or view account');
|
|
412
522
|
control.title = displayName ? `Account: ${displayName}` : 'Account';
|
|
523
|
+
const label = control.querySelector('[data-user-label]');
|
|
524
|
+
if (label) label.textContent = displayName || 'Sign in';
|
|
413
525
|
}
|
|
414
526
|
// The session frame reports on every page load, signed in or not. Re-reading engagement
|
|
415
527
|
// then duplicated the request made on load and returned an identical payload for anyone
|
|
416
528
|
// who was not signed in. Only a change of reader can change what the API answers.
|
|
417
529
|
const changed = renderedSessionUser !== (sessionUser?.id ?? null);
|
|
418
530
|
renderedSessionUser = sessionUser?.id ?? null;
|
|
531
|
+
if (sessionUser) {
|
|
532
|
+
const region = document.querySelector('[data-article-id]');
|
|
533
|
+
if (region) {
|
|
534
|
+
readerStateRequestId = crypto.randomUUID();
|
|
535
|
+
sessionFrame.contentWindow.postMessage({
|
|
536
|
+
type: 'gala-reader-state-request', requestId: readerStateRequestId,
|
|
537
|
+
articleId: region.dataset.articleId
|
|
538
|
+
}, sessionOrigin);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
419
541
|
document.querySelectorAll('[data-engagement-url]').forEach((region) => {
|
|
420
542
|
if (changed) refreshEngagement(region);
|
|
421
543
|
});
|
|
@@ -0,0 +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)});})();
|