@rathnasgala/theme 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rathnasgala/theme",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Managed Gala static-site theme payload",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -2,49 +2,50 @@
2
2
  "schemaVersion": 1,
3
3
  "themePackage": {
4
4
  "name": "@rathnasgala/theme",
5
- "version": "0.0.12",
5
+ "version": "0.0.14",
6
6
  "availableDesignThemes": [
7
7
  "editorial"
8
8
  ],
9
9
  "securityAdvisories": []
10
10
  },
11
11
  "files": {
12
- ".gitignore": "b5fa24e10155097a0d372926fd2e59e6072e7cc2cb6399bbfb4beb8b33ea315b",
13
12
  ".gala/publish.yml.template": "73080241d89bd09964b797a8bcc8e18471412e11bc873efad76305b60f497d08",
13
+ ".gitignore": "b5fa24e10155097a0d372926fd2e59e6072e7cc2cb6399bbfb4beb8b33ea315b",
14
14
  "eleventy.config.js": "efe587447e3dc374a220c7fa037fdf2fffc02d09ba43a4d2b96d6c6aae0e3325",
15
- "package.json": "cccea6fc4edf4b4b653bd5cfc0901ab20dd92ac8975408fb01ae3670005da641",
16
- "package-lock.json": "c97a6a2f794aef083a39a8d2628eccc4a3c484e1c6225899b77ec1fb7c466d9e",
17
15
  "lib/build-manifest.js": "88949f0112d683ca809bb67ca969e15634792423559a67bc79a534cd807e5535",
18
- "lib/site-config.js": "ab7cc170dc30c74ea5ef08b8ff6a82f9820998ae4190a06d64375dfd7d515864",
19
- "lib/performance-budget.js": "124c43494c092a956accec46d1463ea063ed73363d0db01f24aaea987459d683",
20
16
  "lib/engagement-snapshot.js": "298c11b1ab135a3a98a4e31b6320ab60dfd7fda6a9d9053df37d86282a5a6687",
17
+ "lib/performance-budget.js": "124c43494c092a956accec46d1463ea063ed73363d0db01f24aaea987459d683",
18
+ "lib/provider-fixtures/embeds.v1.json": "5f26909fb433135321ddf7fdd14e45aeb702805c88f5676941166c5735143ee2",
19
+ "lib/provider-fixtures/share-intents.v1.json": "66a8eedfcff711253ee45ceb94b53f7d5a6be3ddbbb3d1f0d4e31a114326cc0a",
21
20
  "lib/publication-state.js": "1b59c29a18bbb1193bddea898dac3a4fb832c7bc4751263be086f3d64392dea0",
22
21
  "lib/render-markdown.js": "40e6d6743b76de6c16d138475f188a4263967530c370d8b26a363f4df58c2caf",
23
22
  "lib/seo.js": "107de8804931945b8e73894877fc394d6b535a9d0d969a4ba5b68f2e136e0a17",
24
23
  "lib/share-targets.js": "458aff86fab2b62a364442cd8c612565c6e0b53eef2f20cb6237cc562b1e0731",
25
- "lib/provider-fixtures/embeds.v1.json": "5f26909fb433135321ddf7fdd14e45aeb702805c88f5676941166c5735143ee2",
26
- "lib/provider-fixtures/share-intents.v1.json": "66a8eedfcff711253ee45ceb94b53f7d5a6be3ddbbb3d1f0d4e31a114326cc0a",
24
+ "lib/site-config.js": "ab7cc170dc30c74ea5ef08b8ff6a82f9820998ae4190a06d64375dfd7d515864",
25
+ "package-lock.json": "c97a6a2f794aef083a39a8d2628eccc4a3c484e1c6225899b77ec1fb7c466d9e",
26
+ "package.json": "4c60bea06f39aec755ac68caf8d8841c1d59d0f6121fad7f4396231ce14712dc",
27
27
  "src/404.njk": "be0d44370ac5402156aaf3d32ea581057bc61ec94c1a5f92bb809733af7a8a20",
28
- "src/_includes/layouts/base.njk": "1bd300b68db1786b593ae5ef4325d68f21a5c79a78ac1cb3aba09fdba57bec9d",
29
- "src/_includes/layouts/post.njk": "1accd2832064ad19ee7bf18daf5fa1e60569f32c9b7a547934a6361f747d7eb7",
30
- "src/_includes/components/ui.njk": "de062ca818759edb53dc5e94ab839ac9453fda1fe458c61c04d9d89ef880c6af",
31
28
  "src/_data/buildManifest.js": "628163305984e0d3537e27b0d4050f5ba5ead12a834878266d2ce8004ffe1b1e",
32
29
  "src/_data/engagementSnapshot.js": "9217dacebfaf008f7a0ad67ff4e8d1fa8fba609a9dc7e3430c78c5b32024da0e",
33
30
  "src/_data/feedLinks.js": "06c93352ebd888acb431a1c9a8be6ecff757895468e9f2d336761038751c2d5e",
34
31
  "src/_data/languages.js": "1358748f7fa2f74406d3295a030827d6229a53e94da72cf712e719d7a356aeac",
35
32
  "src/_data/site.js": "d9c001f4cc12a34027dd92303530b11b5402d47613459bb997e22c4bcbea326d",
36
- "src/assets/theme-mode.js": "3797c84dbaa090b5b21427a31928b5dd97f13d04d99833f5e1e74c6b50e01dcc",
37
- "src/assets/interactions.js": "3cd88a919bde8b894fe8509be8db1b07ddd9459df8b31dac83bf9b0215cd73df",
33
+ "src/_includes/components/ui.njk": "b95eca21eb78182989c114f8d03342769553470672e7402e1330e9070c293ff5",
34
+ "src/_includes/layouts/base.njk": "d132b0a920e3dd9b60543394da4498b4d25b3821bdaaea0b3a5da55e9200b663",
35
+ "src/_includes/layouts/post.njk": "1accd2832064ad19ee7bf18daf5fa1e60569f32c9b7a547934a6361f747d7eb7",
38
36
  "src/assets/embed-codepen.svg": "e9def697f38c466fb2b6652535c4c814e8cbbbefc6e052a69f335aeb27e8356b",
39
37
  "src/assets/embed-gist.svg": "fcd9bc2bf29a5c118492b853c0a01390b7539c38ddb985da3c2a6e2f246a39f7",
40
38
  "src/assets/embed-x.svg": "0ae74edc6790ec7e933067fe5392bce756e93fa7ad71caacf1c1ca99fa4ec9b6",
41
39
  "src/assets/embed-youtube.svg": "10d192f2b14b67664fea88c8cf246321d20190def51d59fad5762e552219b067",
40
+ "src/assets/interactions.js": "fafa38854663f840fb4eb206d3ca06a836abf36bedb035a4e8ac211805709f09",
42
41
  "src/assets/preferences.js": "789127bee013ed6a1bbf8cefd7b9109291c16220e7a6e09314e3c93c54731d1d",
43
42
  "src/assets/search.js": "df425f0239277792ad39f2138bc89cb66cc4ecda5ed59042d46abc2f05372088",
43
+ "src/assets/theme-mode.js": "3797c84dbaa090b5b21427a31928b5dd97f13d04d99833f5e1e74c6b50e01dcc",
44
44
  "src/assets/theme.css": "2c835fb79531d7b4a71bc2d41a6deb6e6b3689bf8603d903a3ba93add66c0d60",
45
+ "src/cname.11ty.js": "206fc224df91c569e18312158e7f69803061f2952513f501ada54bd06c4f8006",
45
46
  "src/contact.njk": "f4d74d038781e15af0acef571901c91e7753a2e21b6bef617429585698839a34",
46
- "src/index.njk": "5c313f0fcdad3ddbfb79376bc9671b3f37539ef70c14bdbbd26d6e2e6de250e8",
47
47
  "src/feed.11ty.js": "009bd2384e08e02a9405683bab1653bab3aeaec16accb0fd01ba69b24c777da9",
48
+ "src/index.njk": "5c313f0fcdad3ddbfb79376bc9671b3f37539ef70c14bdbbd26d6e2e6de250e8",
48
49
  "src/languages.11ty.js": "399559199c65570d7d5efd88e20254838e316f81c06d7b28ec34990e963db915",
49
50
  "src/posts.11ty.js": "89c0e4742a67cd03f9fd13c8c8d4ca56375d97628b3cb439af7f9c92c240104a",
50
51
  "src/redirects.11ty.js": "3c163246a59f5364861db6a6f05ae88afc938a5379d86cf42776d8d49b0031f3",
@@ -6,7 +6,7 @@
6
6
  "scripts": {
7
7
  "build": "eleventy",
8
8
  "test": "node --test",
9
- "lint": "node --check eleventy.config.js && node --check lib/build-manifest.js && node --check lib/engagement-snapshot.js && node --check lib/performance-budget.js && node --check lib/publication-state.js && node --check lib/render-markdown.js && node --check lib/seo.js && node --check lib/share-targets.js && node --check lib/site-config.js && node --check scripts/push.js && node --check src/_data/buildManifest.js && node --check src/_data/engagementSnapshot.js && node --check src/_data/feedLinks.js && node --check src/_data/languages.js && node --check src/_data/site.js && node --check src/assets/interactions.js && node --check src/assets/preferences.js && node --check src/assets/search.js && node --check src/assets/theme-mode.js && node --check src/feed.11ty.js && node --check src/languages.11ty.js && node --check src/posts.11ty.js && node --check src/redirects.11ty.js && node --check src/search-index.11ty.js && node --check src/sitemap.11ty.js && node --check test/build-manifest.test.js && node --check test/component-contract.test.js && node --check test/config.test.js && node --check test/eleventy-manifest-build.test.js && node --check test/engagement-client.test.js && node --check test/engagement-snapshot.test.js && node --check test/feed.test.js && node --check test/fixtures/xss-payloads.js && node --check test/managed-files.test.js && node --check test/performance-budget.test.js && node --check test/preferences.test.js && node --check test/publication-state.test.js && node --check test/render-markdown.test.js && node --check test/search.test.js && node --check test/seo.test.js && node --check test/share-targets.test.js && node --check test/supporting-surfaces.test.js && node --check test/theme-contract.test.js && node --check test/theme-contrast.test.js && node --check test/theme-mode.test.js && node --check test/theme-release-workflow.test.js",
9
+ "lint": "node --check eleventy.config.js && node --check lib/build-manifest.js && node --check lib/engagement-snapshot.js && node --check lib/performance-budget.js && node --check lib/publication-state.js && node --check lib/render-markdown.js && node --check lib/seo.js && node --check lib/share-targets.js && node --check lib/site-config.js && node --check scripts/push.js && node --check src/_data/buildManifest.js && node --check src/_data/engagementSnapshot.js && node --check src/_data/feedLinks.js && node --check src/_data/languages.js && node --check src/_data/site.js && node --check src/assets/interactions.js && node --check src/assets/preferences.js && node --check src/assets/search.js && node --check src/assets/theme-mode.js && node --check src/cname.11ty.js && node --check src/feed.11ty.js && node --check src/languages.11ty.js && node --check src/posts.11ty.js && node --check src/redirects.11ty.js && node --check src/search-index.11ty.js && node --check src/sitemap.11ty.js && node --check test/build-manifest.test.js && node --check test/cname.test.js && node --check test/component-contract.test.js && node --check test/config.test.js && node --check test/eleventy-manifest-build.test.js && node --check test/engagement-client.test.js && node --check test/engagement-snapshot.test.js && node --check test/feed.test.js && node --check test/fixtures/xss-payloads.js && node --check test/managed-files.test.js && node --check test/performance-budget.test.js && node --check test/preferences.test.js && node --check test/publication-state.test.js && node --check test/render-markdown.test.js && node --check test/search.test.js && node --check test/seo.test.js && node --check test/share-targets.test.js && node --check test/supporting-surfaces.test.js && node --check test/theme-contract.test.js && node --check test/theme-contrast.test.js && node --check test/theme-mode.test.js && node --check test/theme-release-workflow.test.js",
10
10
  "push": "node scripts/push.js"
11
11
  },
12
12
  "engines": {
@@ -21,7 +21,16 @@
21
21
  at the moment of intent, and the intent is replayed once they are back. #}
22
22
  <section class="gala-engagement-actions" aria-label="Reader actions" data-engagement-actions>
23
23
  <div class="gala-reaction-actions" role="group" aria-label="React to this article">
24
- {% for reaction in ['like', 'love', 'insightful', 'curious', 'celebrate', 'support'] %}<button type="button" data-reaction="{{ reaction }}" aria-pressed="false">{{ reaction | capitalize }}</button>{% endfor %}
24
+ {# The six the platform actually stores (io.gala.api.engagement.ReactionType). Offering
25
+ any other name renders a button that can only ever fail on the way to the API. #}
26
+ {% for reaction in [
27
+ { value: 'like', label: 'Like' },
28
+ { value: 'insightful', label: 'Insightful' },
29
+ { value: 'celebrate', label: 'Celebrate' },
30
+ { value: 'funny', label: 'Funny' },
31
+ { value: 'mind-blown', label: 'Mind blown' },
32
+ { value: 'thank-you', label: 'Thank you' }
33
+ ] %}<button type="button" data-reaction="{{ reaction.value }}" aria-pressed="false">{{ reaction.label }}</button>{% endfor %}
25
34
  </div>
26
35
  <button type="button" data-follow-article aria-pressed="false">Follow article</button>
27
36
  <form data-comment-create>
@@ -56,11 +56,14 @@
56
56
  <main id="main-content">{{ content | safe }}</main>
57
57
  <footer class="gala-page-footer">
58
58
  <p>&copy; {{ site.site.name }}</p>
59
- {# Which footer the click came from, which language it was read in, and whose site it was.
60
- site.id is the identifier available to a built page and resolves to its owner. #}
59
+ {# Which repository sent the traffic, which footer the click came from, which language it
60
+ was read in, and which author owns it. Sites registered before these managed values
61
+ existed fall back rather than emitting the literal placeholder. #}
61
62
  {% set galaLinkPlacement = 'article-footer' if post else 'site-footer' %}
62
63
  {% set galaLinkLanguage = post.language or site.site.defaultLanguage %}
63
- {% if attributionTier != 'PAID' %}<p><a href="https://gala67.com/?utm_source=gala-site&amp;utm_medium=referral&amp;utm_campaign=built-with-gala&amp;utm_content={{ galaLinkPlacement | urlencode }}&amp;utm_term={{ galaLinkLanguage | urlencode }}&amp;utm_id={{ site.site.id | urlencode }}" rel="external">Built with Gala</a></p>{% endif %}
64
+ {% set galaLinkSource = site.site.repository if site.site.repository and site.site.repository != 'unavailable' else 'gala-site' %}
65
+ {% set galaLinkOwner = site.site.ownerId if site.site.ownerId and site.site.ownerId != 'unavailable' else site.site.id %}
66
+ {% if attributionTier != 'PAID' %}<p><a href="https://gala67.com/?utm_source={{ galaLinkSource | urlencode }}&amp;utm_medium=referral&amp;utm_campaign=built-with-gala&amp;utm_content={{ galaLinkPlacement | urlencode }}&amp;utm_term={{ galaLinkLanguage | urlencode }}&amp;utm_id={{ galaLinkOwner | urlencode }}" rel="external">Built with Gala</a></p>{% endif %}
64
67
  </footer>
65
68
  <dialog id="gala-settings-dialog" class="gala-dialog" aria-labelledby="gala-settings-title">
66
69
  <form method="dialog" class="gala-dialog__close"><button type="submit" aria-label="Close settings">Close</button></form>
@@ -39,7 +39,10 @@ document.addEventListener('click', async (event) => {
39
39
  if (reaction) {
40
40
  const region = reaction.closest('[data-engagement-url]');
41
41
  if (!region) return;
42
- if (!sessionUser) return requestSession({ kind: 'reaction', element: reaction });
42
+ if (!sessionUser) {
43
+ return requestSession({ kind: 'reaction', region,
44
+ selector: `[data-reaction="${reaction.dataset.reaction}"]` });
45
+ }
43
46
  const active = reaction.getAttribute('aria-pressed') !== 'true';
44
47
  const saved = await mutateEngagement(region, active ? 'reaction.add' : 'reaction.remove', {
45
48
  articleId: region.dataset.articleId,
@@ -54,7 +57,9 @@ document.addEventListener('click', async (event) => {
54
57
  if (follow) {
55
58
  const region = follow.closest('[data-engagement-url]');
56
59
  if (!region) return;
57
- if (!sessionUser) return requestSession({ kind: 'follow', element: follow });
60
+ if (!sessionUser) {
61
+ return requestSession({ kind: 'follow', region, selector: '[data-follow-article]' });
62
+ }
58
63
  const active = follow.getAttribute('aria-pressed') !== 'true';
59
64
  const saved = await mutateEngagement(region, active ? 'follow.add' : 'follow.remove', {
60
65
  targetType: 'articles', targetId: region.dataset.articleId
@@ -358,7 +363,13 @@ function recordView(region) {
358
363
  document.addEventListener('focusin', (event) => {
359
364
  if (sessionUser || pendingIntent) return;
360
365
  const field = event.target.closest('[data-comment-create] textarea, [data-comment-editor] textarea');
361
- if (field) requestSession({ kind: 'comment', element: field });
366
+ if (!field) return;
367
+ const region = field.closest('[data-engagement-url]');
368
+ const form = field.closest('[data-comment-create], [data-comment-editor]');
369
+ if (!region || !form) return;
370
+ requestSession({ kind: 'comment', region,
371
+ selector: `${form.matches('[data-comment-create]') ? '[data-comment-create]' : '[data-comment-editor]'} textarea`,
372
+ field });
362
373
  });
363
374
 
364
375
  document.addEventListener('submit', async (event) => {
@@ -371,7 +382,9 @@ document.addEventListener('submit', async (event) => {
371
382
  const body = form.elements.body?.value;
372
383
  if (!region || typeof body !== 'string' || body.trim() === '') return;
373
384
  if (!sessionUser) {
374
- return requestSession({ kind: 'comment', element: form.querySelector('textarea') });
385
+ return requestSession({ kind: 'comment', region,
386
+ selector: `${create ? '[data-comment-create]' : '[data-comment-editor]'} textarea`,
387
+ field: form.querySelector('textarea') });
375
388
  }
376
389
  if (create) {
377
390
  const saved = await mutateEngagement(region, 'comment.create', {
@@ -407,13 +420,14 @@ const pendingEngagementWrites = new Map();
407
420
  let pendingIntent = null;
408
421
 
409
422
  function requestSession(intent) {
410
- const field = intent.element;
423
+ const field = intent.field;
411
424
  pendingIntent = {
412
425
  kind: intent.kind,
413
- element: field,
426
+ region: intent.region,
427
+ selector: intent.selector,
414
428
  // Held here rather than left in the DOM, because signing in re-renders the region.
415
- draft: field && 'value' in field ? field.value : null,
416
- caret: field && 'selectionStart' in field ? field.selectionStart : null
429
+ draft: field ? field.value : null,
430
+ caret: field ? field.selectionStart : null
417
431
  };
418
432
  // One window, not three. Asking used to open a modal, containing a frame, containing a
419
433
  // button, that opened a popup. The popup is the only part that has to exist, because it is
@@ -424,7 +438,7 @@ function requestSession(intent) {
424
438
  const source = new URL(frame.getAttribute('src'), window.location.href);
425
439
  const signIn = new URL('/v1/widget/session/sign-in', source.origin);
426
440
  signIn.searchParams.set('siteId', source.searchParams.get('siteId') ?? '');
427
- if (field && typeof field.blur === 'function') field.blur();
441
+ if (field) field.blur();
428
442
  if (!window.open(signIn, 'gala-sign-in', 'popup,width=520,height=680')) {
429
443
  pendingIntent = null;
430
444
  if (status) status.textContent = 'Allow pop-ups for this site to sign in, then try again.';
@@ -434,14 +448,19 @@ function requestSession(intent) {
434
448
  /** Puts the reader back exactly where the sign-in interrupted them. */
435
449
  function resumeIntent() {
436
450
  const intent = pendingIntent;
437
- pendingIntent = null;
438
451
  if (!intent) return;
439
- const element = intent.element;
440
- if (!element || !element.isConnected) return;
452
+ // The sign-in window is still closing at the moment the session arrives, so focus set now
453
+ // would be handed straight back to it. Wait until this page actually holds focus.
454
+ if (!document.hasFocus()) return;
455
+ // Re-found rather than remembered: signing in re-renders the engagement region, so a node
456
+ // captured before the interruption may no longer be the one on the page.
457
+ const element = intent.region?.querySelector(intent.selector);
458
+ if (!element) return;
459
+ pendingIntent = null;
441
460
  if (intent.kind === 'comment') {
442
- if ('value' in element && intent.draft != null && element.value === '') element.value = intent.draft;
461
+ if (intent.draft != null && element.value === '') element.value = intent.draft;
443
462
  element.focus();
444
- if (intent.caret != null && 'setSelectionRange' in element) {
463
+ if (intent.caret != null && typeof element.setSelectionRange === 'function') {
445
464
  element.setSelectionRange(intent.caret, intent.caret);
446
465
  }
447
466
  return;
@@ -451,6 +470,12 @@ function resumeIntent() {
451
470
  element.click();
452
471
  }
453
472
 
473
+ // Signing in happens in another window, so the moment this one is focused again is the moment
474
+ // the reader is back and the interrupted action can be finished.
475
+ window.addEventListener('focus', () => {
476
+ if (sessionUser && pendingIntent) resumeIntent();
477
+ });
478
+
454
479
  document.querySelectorAll('[data-engagement-url]').forEach((region) => {
455
480
  refreshEngagement(region);
456
481
  recordView(region);
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Emits the CNAME file GitHub Pages needs to serve a custom domain.
3
+ *
4
+ * The published branch is force-pushed on every run, so anything not produced by the build is
5
+ * gone the next time someone writes a post. Deriving it from the site configuration means the
6
+ * custom domain is restated on every publish and cannot be lost, and there is exactly one place
7
+ * that decides what the domain is.
8
+ */
9
+ const PROVIDER_HOST = /\.github\.io$/;
10
+
11
+ /**
12
+ * The host this repository may claim as its own, or null.
13
+ *
14
+ * A site served under a path is served that way because GitHub gives it the domain held by the
15
+ * owner's main site — https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/about-custom-domains-and-github-pages
16
+ * puts a project site at `www.octocat.com/octo-project`. GitHub reads this file from the
17
+ * published branch and treats it as *this* repository's own domain, which overrides that
18
+ * inheritance: the blog would move to the domain root and take the address away from the site
19
+ * that owns it. So a path-prefixed site deliberately claims nothing.
20
+ */
21
+ export function customDomainHost(canonicalBaseUrl, pathPrefix = '/') {
22
+ if (pathPrefix !== '/') return null;
23
+ if (typeof canonicalBaseUrl !== 'string' || canonicalBaseUrl === '') return null;
24
+ let host;
25
+ try {
26
+ host = new URL(canonicalBaseUrl).host;
27
+ } catch {
28
+ return null;
29
+ }
30
+ // A provider-default site is served from the owner's github.io origin and must not claim a
31
+ // custom domain, or GitHub would reject a domain it cannot verify.
32
+ return PROVIDER_HOST.test(host) ? null : host;
33
+ }
34
+
35
+ function claimed(site) {
36
+ return customDomainHost(site?.hosting?.canonicalBaseUrl, site?.hosting?.pathPrefix ?? '/');
37
+ }
38
+
39
+ export default class CustomDomain {
40
+ data() {
41
+ return {
42
+ permalink: ({ site }) => claimed(site) ? '/CNAME' : false,
43
+ // GitHub Pages requires this exact extensionless name, so Eleventy's guard against
44
+ // extensionless output does not apply here.
45
+ eleventyAllowMissingExtension: true,
46
+ eleventyExcludeFromCollections: true
47
+ };
48
+ }
49
+
50
+ render({ site }) {
51
+ return `${claimed(site)}\n`;
52
+ }
53
+ }