@rathnasgala/theme 0.0.16 → 0.0.18
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 +15 -8
- package/payload/lib/performance-budget.js +10 -1
- package/payload/lib/site-config.js +23 -3
- package/payload/src/_includes/components/ui.njk +5 -6
- package/payload/src/_includes/layouts/base.njk +8 -7
- package/payload/src/assets/engagement-comments.js +264 -0
- package/payload/src/assets/engagement-transport.js +73 -0
- package/payload/src/assets/interactions.js +12 -196
- package/payload/src/assets/theme-mode.js +14 -2
- package/payload/src/assets/theme.css +322 -47
- package/payload/src/assets/vendor/hooks.js +2 -0
- package/payload/src/assets/vendor/htm.js +2 -0
- package/payload/src/assets/vendor/preact.js +2 -0
- package/payload/src/assets/vendor/signals-core.js +2 -0
- package/payload/src/assets/vendor/signals.js +2 -0
|
@@ -70,61 +70,6 @@ document.addEventListener('click', async (event) => {
|
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const reply = event.target.closest('[data-reply-comment]');
|
|
74
|
-
if (reply) {
|
|
75
|
-
const region = reply.closest('[data-engagement-url]');
|
|
76
|
-
const form = region?.querySelector('[data-comment-create]');
|
|
77
|
-
if (!form) return;
|
|
78
|
-
form.dataset.parentCommentId = reply.dataset.replyComment;
|
|
79
|
-
const context = form.querySelector('[data-comment-reply-context]');
|
|
80
|
-
if (context) {
|
|
81
|
-
context.hidden = false;
|
|
82
|
-
context.textContent = `Replying to ${reply.dataset.replyAuthor || 'comment'}.`;
|
|
83
|
-
}
|
|
84
|
-
const cancel = form.querySelector('[data-cancel-reply]');
|
|
85
|
-
if (cancel) cancel.hidden = false;
|
|
86
|
-
form.querySelector('textarea')?.focus();
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const cancelReply = event.target.closest('[data-cancel-reply]');
|
|
91
|
-
if (cancelReply) {
|
|
92
|
-
clearReply(cancelReply.closest('[data-comment-create]'));
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const edit = event.target.closest('[data-edit-comment]');
|
|
97
|
-
if (edit) {
|
|
98
|
-
showCommentEditor(edit);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const cancelEdit = event.target.closest('[data-cancel-comment-edit]');
|
|
103
|
-
if (cancelEdit) {
|
|
104
|
-
cancelEdit.closest('[data-comment-editor]')?.remove();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const remove = event.target.closest('[data-delete-comment]');
|
|
109
|
-
if (remove) {
|
|
110
|
-
const region = remove.closest('[data-engagement-url]');
|
|
111
|
-
if (!region) return;
|
|
112
|
-
await mutateEngagement(region, 'comment.delete', {
|
|
113
|
-
articleId: region.dataset.articleId,
|
|
114
|
-
commentId: remove.dataset.deleteComment
|
|
115
|
-
});
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const moreComments = event.target.closest('[data-comments-cursor]');
|
|
120
|
-
if (moreComments) {
|
|
121
|
-
const region = moreComments.closest('[data-engagement-url]');
|
|
122
|
-
if (!region) return;
|
|
123
|
-
moreComments.disabled = true;
|
|
124
|
-
await refreshEngagement(region, moreComments.dataset.commentsCursor, true);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
73
|
const share = event.target.closest('[data-copy-url]');
|
|
129
74
|
if (share) {
|
|
130
75
|
const value = share.dataset.copyUrl;
|
|
@@ -187,57 +132,6 @@ function engagementCounts(data) {
|
|
|
187
132
|
];
|
|
188
133
|
}
|
|
189
134
|
|
|
190
|
-
function commentList(items, currentUser) {
|
|
191
|
-
const roots = document.createElement('ol');
|
|
192
|
-
roots.className = 'gala-comments';
|
|
193
|
-
const nodes = new Map();
|
|
194
|
-
for (const comment of items) {
|
|
195
|
-
const item = document.createElement('li');
|
|
196
|
-
item.dataset.commentId = comment.commentId;
|
|
197
|
-
const author = comment.author?.displayName ?? '[deleted]';
|
|
198
|
-
item.append(textElement('strong', author));
|
|
199
|
-
const body = textElement('p', comment.deleted ? '[deleted]' : comment.body);
|
|
200
|
-
body.dataset.commentBody = '';
|
|
201
|
-
item.append(body);
|
|
202
|
-
if (!comment.deleted && currentUser) {
|
|
203
|
-
const actions = document.createElement('div');
|
|
204
|
-
actions.className = 'gala-comment-actions';
|
|
205
|
-
const reply = textElement('button', 'Reply');
|
|
206
|
-
reply.type = 'button';
|
|
207
|
-
reply.dataset.replyComment = comment.commentId;
|
|
208
|
-
reply.dataset.replyAuthor = author;
|
|
209
|
-
actions.append(reply);
|
|
210
|
-
if (comment.author?.userId === currentUser.id) {
|
|
211
|
-
const edit = textElement('button', 'Edit');
|
|
212
|
-
edit.type = 'button';
|
|
213
|
-
edit.dataset.editComment = comment.commentId;
|
|
214
|
-
const remove = textElement('button', 'Delete');
|
|
215
|
-
remove.type = 'button';
|
|
216
|
-
remove.dataset.deleteComment = comment.commentId;
|
|
217
|
-
actions.append(edit, remove);
|
|
218
|
-
}
|
|
219
|
-
item.append(actions);
|
|
220
|
-
}
|
|
221
|
-
nodes.set(comment.commentId, item);
|
|
222
|
-
}
|
|
223
|
-
for (const comment of items) {
|
|
224
|
-
const item = nodes.get(comment.commentId);
|
|
225
|
-
const parent = comment.parentCommentId ? nodes.get(comment.parentCommentId) : null;
|
|
226
|
-
if (!parent) {
|
|
227
|
-
roots.append(item);
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
let replies = parent.querySelector(':scope > ol');
|
|
231
|
-
if (!replies) {
|
|
232
|
-
replies = document.createElement('ol');
|
|
233
|
-
replies.className = 'gala-comment-replies';
|
|
234
|
-
parent.append(replies);
|
|
235
|
-
}
|
|
236
|
-
replies.append(item);
|
|
237
|
-
}
|
|
238
|
-
return roots;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
135
|
function renderEngagement(region, payload, appendComments = false) {
|
|
242
136
|
const data = payload?.data;
|
|
243
137
|
if (!data || typeof data !== 'object') throw new TypeError('Engagement data is invalid');
|
|
@@ -267,60 +161,19 @@ function renderEngagement(region, payload, appendComments = false) {
|
|
|
267
161
|
live.append(summary);
|
|
268
162
|
}
|
|
269
163
|
|
|
270
|
-
if (Array.isArray(data.comments?.items) && data.comments.items.length > 0) {
|
|
271
|
-
live.append(commentList(data.comments.items, sessionUser));
|
|
272
|
-
}
|
|
273
|
-
live.querySelector('[data-comments-cursor]')?.remove();
|
|
274
|
-
if (data.comments?.nextCursor) {
|
|
275
|
-
const more = textElement('button', 'Load more comments');
|
|
276
|
-
more.type = 'button';
|
|
277
|
-
more.dataset.commentsCursor = data.comments.nextCursor;
|
|
278
|
-
live.append(more);
|
|
279
|
-
}
|
|
280
164
|
region.querySelector('[data-engagement-snapshot]')?.remove();
|
|
281
165
|
region.querySelector('.gala-engagement__placeholder')?.remove();
|
|
282
166
|
}
|
|
283
167
|
|
|
284
|
-
function
|
|
285
|
-
if (!form) return;
|
|
286
|
-
delete form.dataset.parentCommentId;
|
|
287
|
-
const context = form.querySelector('[data-comment-reply-context]');
|
|
288
|
-
if (context) {
|
|
289
|
-
context.hidden = true;
|
|
290
|
-
context.textContent = '';
|
|
291
|
-
}
|
|
292
|
-
const cancel = form.querySelector('[data-cancel-reply]');
|
|
293
|
-
if (cancel) cancel.hidden = true;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function showCommentEditor(control) {
|
|
297
|
-
const item = control.closest('[data-comment-id]');
|
|
298
|
-
if (!item || item.querySelector('[data-comment-editor]')) return;
|
|
299
|
-
const form = document.createElement('form');
|
|
300
|
-
form.dataset.commentEditor = control.dataset.editComment;
|
|
301
|
-
const textarea = document.createElement('textarea');
|
|
302
|
-
textarea.name = 'body';
|
|
303
|
-
textarea.maxLength = 5000;
|
|
304
|
-
textarea.required = true;
|
|
305
|
-
textarea.value = item.querySelector('[data-comment-body]')?.textContent ?? '';
|
|
306
|
-
const save = textElement('button', 'Save');
|
|
307
|
-
save.type = 'submit';
|
|
308
|
-
const cancel = textElement('button', 'Cancel');
|
|
309
|
-
cancel.type = 'button';
|
|
310
|
-
cancel.dataset.cancelCommentEdit = '';
|
|
311
|
-
form.append(textarea, save, cancel);
|
|
312
|
-
item.append(form);
|
|
313
|
-
textarea.focus();
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async function refreshEngagement(region, commentsCursor = '', appendComments = false) {
|
|
168
|
+
async function refreshEngagement(region, commentsCursor = '', appendComments = false, fresh = false) {
|
|
317
169
|
const status = region.querySelector('[data-engagement-status]');
|
|
318
170
|
try {
|
|
319
171
|
const requestUrl = new URL(region.dataset.engagementUrl);
|
|
320
172
|
if (commentsCursor) requestUrl.searchParams.set('commentsCursor', commentsCursor);
|
|
321
173
|
const response = await fetch(requestUrl, {
|
|
322
174
|
headers: { Accept: 'application/json' },
|
|
323
|
-
credentials: 'omit'
|
|
175
|
+
credentials: 'omit',
|
|
176
|
+
cache: fresh ? 'no-store' : 'default'
|
|
324
177
|
});
|
|
325
178
|
if (!response.ok) throw new Error(`Engagement returned HTTP ${response.status}`);
|
|
326
179
|
const payload = await response.json();
|
|
@@ -358,52 +211,14 @@ function recordView(region) {
|
|
|
358
211
|
}).catch(() => {});
|
|
359
212
|
}
|
|
360
213
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
214
|
+
/*
|
|
215
|
+
* The comments island asks for a sign-in when the reader reaches for something that needs one.
|
|
216
|
+
* Opening the window and replaying the interrupted intent stays here, because that is a property
|
|
217
|
+
* of the page, not of any one island.
|
|
218
|
+
*/
|
|
219
|
+
document.addEventListener('gala-request-sign-in', (event) => {
|
|
364
220
|
if (sessionUser || pendingIntent) return;
|
|
365
|
-
|
|
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 });
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
document.addEventListener('submit', async (event) => {
|
|
376
|
-
const create = event.target.closest('[data-comment-create]');
|
|
377
|
-
const editor = event.target.closest('[data-comment-editor]');
|
|
378
|
-
if (!create && !editor) return;
|
|
379
|
-
event.preventDefault();
|
|
380
|
-
const form = create ?? editor;
|
|
381
|
-
const region = form.closest('[data-engagement-url]');
|
|
382
|
-
const body = form.elements.body?.value;
|
|
383
|
-
if (!region || typeof body !== 'string' || body.trim() === '') return;
|
|
384
|
-
if (!sessionUser) {
|
|
385
|
-
return requestSession({ kind: 'comment', region,
|
|
386
|
-
selector: `${create ? '[data-comment-create]' : '[data-comment-editor]'} textarea`,
|
|
387
|
-
field: form.querySelector('textarea') });
|
|
388
|
-
}
|
|
389
|
-
if (create) {
|
|
390
|
-
const saved = await mutateEngagement(region, 'comment.create', {
|
|
391
|
-
articleId: region.dataset.articleId,
|
|
392
|
-
parentCommentId: create.dataset.parentCommentId || null,
|
|
393
|
-
body
|
|
394
|
-
});
|
|
395
|
-
if (!saved) return;
|
|
396
|
-
create.reset();
|
|
397
|
-
clearReply(create);
|
|
398
|
-
} else {
|
|
399
|
-
const saved = await mutateEngagement(region, 'comment.edit', {
|
|
400
|
-
articleId: region.dataset.articleId,
|
|
401
|
-
commentId: editor.dataset.commentEditor,
|
|
402
|
-
body
|
|
403
|
-
});
|
|
404
|
-
if (!saved) return;
|
|
405
|
-
editor.remove();
|
|
406
|
-
}
|
|
221
|
+
requestSession({ kind: event.detail?.kind ?? 'comment' });
|
|
407
222
|
});
|
|
408
223
|
|
|
409
224
|
let sessionUser = null;
|
|
@@ -556,7 +371,8 @@ async function mutateEngagement(region, operation, payload) {
|
|
|
556
371
|
if (status) status.textContent = 'Saving…';
|
|
557
372
|
await sendEngagementWrite(operation, payload);
|
|
558
373
|
if (status) status.textContent = 'Saved.';
|
|
559
|
-
|
|
374
|
+
// The reader's own write must be visible to them at once, so this one bypasses the cache.
|
|
375
|
+
await refreshEngagement(region, '', false, true);
|
|
560
376
|
return true;
|
|
561
377
|
} catch (error) {
|
|
562
378
|
if (status) status.textContent = engagementErrorMessage(error.message);
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
const MODES = ['system', 'light', 'dark'];
|
|
2
2
|
const STORAGE_KEY = 'gala-color-mode';
|
|
3
3
|
|
|
4
|
+
/*
|
|
5
|
+
* The reader's own choice always wins. Absent one, the publication's `design.colorMode` decides
|
|
6
|
+
* what they see first — it is already on the element, server-rendered. This used to fall back to
|
|
7
|
+
* `system` unconditionally, which meant a writer could set a colour mode and no reader ever saw
|
|
8
|
+
* it: the setting existed everywhere except in the page.
|
|
9
|
+
*/
|
|
10
|
+
function publicationDefault() {
|
|
11
|
+
const declared = document.documentElement.dataset.mode;
|
|
12
|
+
return MODES.includes(declared) ? declared : 'system';
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
function storedMode() {
|
|
5
16
|
try {
|
|
6
17
|
const mode = localStorage.getItem(STORAGE_KEY);
|
|
7
|
-
|
|
18
|
+
if (MODES.includes(mode)) return mode;
|
|
8
19
|
} catch {
|
|
9
|
-
return
|
|
20
|
+
return publicationDefault();
|
|
10
21
|
}
|
|
22
|
+
return publicationDefault();
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
function applyMode(mode) {
|