@rathnasgala/theme 0.0.11 → 0.0.13

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.11",
3
+ "version": "0.0.13",
4
4
  "description": "Managed Gala static-site theme payload",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "themePackage": {
4
4
  "name": "@rathnasgala/theme",
5
- "version": "0.0.11",
5
+ "version": "0.0.13",
6
6
  "availableDesignThemes": [
7
7
  "editorial"
8
8
  ],
@@ -25,16 +25,16 @@
25
25
  "lib/provider-fixtures/embeds.v1.json": "5f26909fb433135321ddf7fdd14e45aeb702805c88f5676941166c5735143ee2",
26
26
  "lib/provider-fixtures/share-intents.v1.json": "66a8eedfcff711253ee45ceb94b53f7d5a6be3ddbbb3d1f0d4e31a114326cc0a",
27
27
  "src/404.njk": "be0d44370ac5402156aaf3d32ea581057bc61ec94c1a5f92bb809733af7a8a20",
28
- "src/_includes/layouts/base.njk": "b62dea9dcd5eeefc020e1823ce8afc7d5b0105df56d57fdcacbf414f4edb4dd2",
28
+ "src/_includes/layouts/base.njk": "d132b0a920e3dd9b60543394da4498b4d25b3821bdaaea0b3a5da55e9200b663",
29
29
  "src/_includes/layouts/post.njk": "1accd2832064ad19ee7bf18daf5fa1e60569f32c9b7a547934a6361f747d7eb7",
30
- "src/_includes/components/ui.njk": "de062ca818759edb53dc5e94ab839ac9453fda1fe458c61c04d9d89ef880c6af",
30
+ "src/_includes/components/ui.njk": "b95eca21eb78182989c114f8d03342769553470672e7402e1330e9070c293ff5",
31
31
  "src/_data/buildManifest.js": "628163305984e0d3537e27b0d4050f5ba5ead12a834878266d2ce8004ffe1b1e",
32
32
  "src/_data/engagementSnapshot.js": "9217dacebfaf008f7a0ad67ff4e8d1fa8fba609a9dc7e3430c78c5b32024da0e",
33
33
  "src/_data/feedLinks.js": "06c93352ebd888acb431a1c9a8be6ecff757895468e9f2d336761038751c2d5e",
34
34
  "src/_data/languages.js": "1358748f7fa2f74406d3295a030827d6229a53e94da72cf712e719d7a356aeac",
35
35
  "src/_data/site.js": "d9c001f4cc12a34027dd92303530b11b5402d47613459bb997e22c4bcbea326d",
36
36
  "src/assets/theme-mode.js": "3797c84dbaa090b5b21427a31928b5dd97f13d04d99833f5e1e74c6b50e01dcc",
37
- "src/assets/interactions.js": "c8ea01683941da3f81536a9c5d33d2e8d09a1f31f6090d33b53b70fa7aad621d",
37
+ "src/assets/interactions.js": "fafa38854663f840fb4eb206d3ca06a836abf36bedb035a4e8ac211805709f09",
38
38
  "src/assets/embed-codepen.svg": "e9def697f38c466fb2b6652535c4c814e8cbbbefc6e052a69f335aeb27e8356b",
39
39
  "src/assets/embed-gist.svg": "fcd9bc2bf29a5c118492b853c0a01390b7539c38ddb985da3c2a6e2f246a39f7",
40
40
  "src/assets/embed-x.svg": "0ae74edc6790ec7e933067fe5392bce756e93fa7ad71caacf1c1ca99fa4ec9b6",
@@ -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,7 +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
- {% if attributionTier != 'PAID' %}<p><a href="https://gala67.com/?utm_source=gala-site&amp;utm_medium=referral" rel="external">Built with Gala</a></p>{% endif %}
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. #}
62
+ {% set galaLinkPlacement = 'article-footer' if post else 'site-footer' %}
63
+ {% set galaLinkLanguage = post.language or site.site.defaultLanguage %}
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 %}
60
67
  </footer>
61
68
  <dialog id="gala-settings-dialog" class="gala-dialog" aria-labelledby="gala-settings-title">
62
69
  <form method="dialog" class="gala-dialog__close"><button type="submit" aria-label="Close settings">Close</button></form>
@@ -91,7 +98,6 @@
91
98
  <dialog id="gala-account-dialog" class="gala-dialog gala-account-dialog" aria-labelledby="gala-account-title">
92
99
  <form method="dialog" class="gala-dialog__close"><button type="submit" aria-label="Close account">Close</button></form>
93
100
  <h2 id="gala-account-title">Gala account</h2>
94
- <p class="gala-account-dialog__invite" data-account-invite hidden>Takes a couple of clicks to authenticate yourself. No passwords, no spam.</p>
95
101
  <iframe data-gala-session-frame title="Gala account and sign in" allow="identity-credentials-get" src="{{ site.platform.apiBaseUrl or 'https://api.gala67.com' }}/v1/widget/session?siteId={{ site.site.id | escape }}"></iframe>
96
102
  </dialog>
97
103
  {% endif %}
@@ -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,33 +420,47 @@ const pendingEngagementWrites = new Map();
407
420
  let pendingIntent = null;
408
421
 
409
422
  function requestSession(intent) {
410
- const dialog = document.getElementById('gala-account-dialog');
411
- const field = intent.element;
423
+ const field = intent.field;
412
424
  pendingIntent = {
413
425
  kind: intent.kind,
414
- element: field,
426
+ region: intent.region,
427
+ selector: intent.selector,
415
428
  // Held here rather than left in the DOM, because signing in re-renders the region.
416
- draft: field && 'value' in field ? field.value : null,
417
- caret: field && 'selectionStart' in field ? field.selectionStart : null
429
+ draft: field ? field.value : null,
430
+ caret: field ? field.selectionStart : null
418
431
  };
419
- if (!(dialog instanceof HTMLDialogElement)) return;
420
- const invite = dialog.querySelector('[data-account-invite]');
421
- if (invite) invite.hidden = false;
422
- if (field && typeof field.blur === 'function') field.blur();
423
- if (!dialog.open) dialog.showModal();
432
+ // One window, not three. Asking used to open a modal, containing a frame, containing a
433
+ // button, that opened a popup. The popup is the only part that has to exist, because it is
434
+ // the only one running at the top level on the origin the identity providers accept.
435
+ const frame = document.querySelector('[data-gala-session-frame]');
436
+ const status = document.querySelector('[data-engagement-status]');
437
+ if (!frame) return;
438
+ const source = new URL(frame.getAttribute('src'), window.location.href);
439
+ const signIn = new URL('/v1/widget/session/sign-in', source.origin);
440
+ signIn.searchParams.set('siteId', source.searchParams.get('siteId') ?? '');
441
+ if (field) field.blur();
442
+ if (!window.open(signIn, 'gala-sign-in', 'popup,width=520,height=680')) {
443
+ pendingIntent = null;
444
+ if (status) status.textContent = 'Allow pop-ups for this site to sign in, then try again.';
445
+ }
424
446
  }
425
447
 
426
448
  /** Puts the reader back exactly where the sign-in interrupted them. */
427
449
  function resumeIntent() {
428
450
  const intent = pendingIntent;
429
- pendingIntent = null;
430
451
  if (!intent) return;
431
- const element = intent.element;
432
- 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;
433
460
  if (intent.kind === 'comment') {
434
- if ('value' in element && intent.draft != null && element.value === '') element.value = intent.draft;
461
+ if (intent.draft != null && element.value === '') element.value = intent.draft;
435
462
  element.focus();
436
- if (intent.caret != null && 'setSelectionRange' in element) {
463
+ if (intent.caret != null && typeof element.setSelectionRange === 'function') {
437
464
  element.setSelectionRange(intent.caret, intent.caret);
438
465
  }
439
466
  return;
@@ -443,6 +470,12 @@ function resumeIntent() {
443
470
  element.click();
444
471
  }
445
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
+
446
479
  document.querySelectorAll('[data-engagement-url]').forEach((region) => {
447
480
  refreshEngagement(region);
448
481
  recordView(region);
@@ -543,13 +576,7 @@ if (sessionFrame) {
543
576
  document.querySelectorAll('[data-engagement-url]').forEach((region) => {
544
577
  if (changed) refreshEngagement(region);
545
578
  });
546
- // Signed in on the back of an interrupted action: close the interruption and finish it.
547
- if (sessionUser && pendingIntent) {
548
- const dialog = document.getElementById('gala-account-dialog');
549
- const invite = dialog?.querySelector('[data-account-invite]');
550
- if (invite) invite.hidden = true;
551
- if (dialog instanceof HTMLDialogElement && dialog.open) dialog.close();
552
- resumeIntent();
553
- }
579
+ // Signed in on the back of an interrupted action: finish what they were doing.
580
+ if (sessionUser && pendingIntent) resumeIntent();
554
581
  });
555
582
  }