@scalemule/news-publication 0.0.25 → 0.0.26

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.
@@ -0,0 +1,800 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/story-diff.ts
8
+ function proseDiff(before, after) {
9
+ const tokens = (s) => s.match(/\s+|[\p{L}\p{N}\p{M}]+|[^\s]/gu) ?? [];
10
+ const a = tokens(before), b = tokens(after), out = [];
11
+ const emit = (kind, text) => {
12
+ if (!text) return;
13
+ const last = out.at(-1);
14
+ if (last?.kind === kind) last.text += text;
15
+ else out.push({ kind, text });
16
+ };
17
+ let start = 0, endA = a.length, endB = b.length;
18
+ while (start < endA && start < endB && a[start] === b[start]) {
19
+ emit("same", a[start]);
20
+ start++;
21
+ }
22
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
23
+ endA--;
24
+ endB--;
25
+ }
26
+ if ((endA - start) * (endB - start) > 25e4) {
27
+ emit("removed", a.slice(start, endA).join(""));
28
+ emit("added", b.slice(start, endB).join(""));
29
+ } else {
30
+ const n = endA - start, m = endB - start, grid = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
31
+ for (let i2 = n - 1; i2 >= 0; i2--)
32
+ for (let j2 = m - 1; j2 >= 0; j2--)
33
+ grid[i2][j2] = a[start + i2] === b[start + j2] ? grid[i2 + 1][j2 + 1] + 1 : Math.max(grid[i2 + 1][j2], grid[i2][j2 + 1]);
34
+ let i = 0, j = 0;
35
+ while (i < n || j < m) {
36
+ if (i < n && j < m && a[start + i] === b[start + j]) {
37
+ emit("same", a[start + i]);
38
+ i++;
39
+ j++;
40
+ } else if (i < n && (j === m || grid[i + 1][j] >= grid[i][j + 1])) {
41
+ emit("removed", a[start + i]);
42
+ i++;
43
+ } else {
44
+ emit("added", b[start + j]);
45
+ j++;
46
+ }
47
+ }
48
+ }
49
+ emit("same", a.slice(endA).join(""));
50
+ return out;
51
+ }
52
+ function EditorialNotices({ editorial }) {
53
+ return editorial ? /* @__PURE__ */ jsxRuntime.jsxs("aside", { "aria-label": "Editorial notices", children: [
54
+ editorial.content_kind && editorial.content_kind !== "EDITORIAL" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: human(editorial.content_kind) }),
55
+ editorial.disclosure && /* @__PURE__ */ jsxRuntime.jsx("p", { children: editorial.disclosure }),
56
+ editorial.notices?.map((n, i) => /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
57
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
58
+ n.kind,
59
+ ": "
60
+ ] }),
61
+ n.text
62
+ ] }, i))
63
+ ] }) : null;
64
+ }
65
+ var human = (s) => s.toLowerCase().replaceAll("_", " ");
66
+ function StoryReview({
67
+ sessionId,
68
+ publicationName
69
+ }) {
70
+ const base = `/api/news/review/${encodeURIComponent(sessionId)}`;
71
+ const [review, setReview] = react.useState(null), [error, setError] = react.useState(""), [notice, setNotice] = react.useState(""), [busy, setBusy] = react.useState(false), [panel, setPanel] = react.useState("article"), [verification, setVerification] = react.useState(false), [code, setCode] = react.useState("");
72
+ const [message, setMessage] = react.useState(""), [document, setDocument] = react.useState(null), [answers, setAnswers] = react.useState({}), [file, setFile] = react.useState(null), [rights, setRights] = react.useState(false), [compare, setCompare] = react.useState(""), [compareTo, setCompareTo] = react.useState(""), [editingMedia, setEditingMedia] = react.useState(""), [commentTarget, setCommentTarget] = react.useState("STORY");
73
+ const [mediaInfo, setMediaInfo] = react.useState({
74
+ caption: "",
75
+ who_is_pictured: "",
76
+ creator: "",
77
+ when_taken: "",
78
+ where_taken: "",
79
+ credit: "",
80
+ alt: ""
81
+ });
82
+ async function request(path = "", body) {
83
+ const form = body instanceof FormData;
84
+ const r = await fetch(base + path, {
85
+ method: body === void 0 ? "GET" : "POST",
86
+ credentials: "same-origin",
87
+ cache: "no-store",
88
+ headers: body === void 0 || form ? {} : { "content-type": "application/json" },
89
+ body: body === void 0 ? void 0 : form ? body : JSON.stringify(body)
90
+ });
91
+ const result = await r.json();
92
+ if (!r.ok || result.success === false)
93
+ throw new Error(
94
+ result.error?.message || "This private review is unavailable. Please contact the newsroom."
95
+ );
96
+ return result.data ?? result;
97
+ }
98
+ async function load() {
99
+ const data = await request();
100
+ if (data.verification_required) {
101
+ setVerification(true);
102
+ setReview(null);
103
+ } else {
104
+ setVerification(false);
105
+ setReview(data);
106
+ setDocument(data.revision.document);
107
+ }
108
+ }
109
+ react.useEffect(() => {
110
+ let active = true;
111
+ void (async () => {
112
+ try {
113
+ const token = window.location.hash.slice(1);
114
+ if (token) {
115
+ await request("/session", { token });
116
+ window.history.replaceState(null, "", window.location.pathname);
117
+ }
118
+ const data = await request();
119
+ if (active) {
120
+ if (data.verification_required) {
121
+ setVerification(true);
122
+ setReview(null);
123
+ } else {
124
+ setVerification(false);
125
+ setReview(data);
126
+ setDocument(data.revision.document);
127
+ }
128
+ }
129
+ } catch (e) {
130
+ if (active)
131
+ setError(e instanceof Error ? e.message : "Unable to open review.");
132
+ }
133
+ })();
134
+ return () => {
135
+ active = false;
136
+ };
137
+ }, [sessionId]);
138
+ async function send(action, success) {
139
+ if (!review) return;
140
+ setBusy(true);
141
+ setError("");
142
+ setNotice("");
143
+ try {
144
+ await request("", { revision_id: review.revision.id, ...action });
145
+ await load();
146
+ setNotice(success);
147
+ setPanel("article");
148
+ setMessage("");
149
+ } catch (e) {
150
+ setError(
151
+ e instanceof Error ? e.message : "Could not save your response."
152
+ );
153
+ } finally {
154
+ setBusy(false);
155
+ }
156
+ }
157
+ const can = (p) => review?.permissions.includes(p);
158
+ async function upload() {
159
+ if (!review || !file || !rights) return;
160
+ setBusy(true);
161
+ setError("");
162
+ try {
163
+ const form = new FormData();
164
+ form.set("file", file);
165
+ form.set(
166
+ "metadata",
167
+ JSON.stringify({ ...mediaInfo, rights_confirmed: true })
168
+ );
169
+ await request("/media", form);
170
+ await load();
171
+ setFile(null);
172
+ setRights(false);
173
+ setNotice(
174
+ "Media received privately. The newsroom will review it before publication."
175
+ );
176
+ setPanel("article");
177
+ } catch (e) {
178
+ setError(e instanceof Error ? e.message : "Upload failed.");
179
+ } finally {
180
+ setBusy(false);
181
+ }
182
+ }
183
+ const revisions = review ? [
184
+ review.revision,
185
+ ...review.history.filter((r) => r.id !== review.revision.id)
186
+ ] : [];
187
+ const selected = revisions.find((r) => r.id === compare), selectedAfter = revisions.find(
188
+ (r) => r.id === (compareTo || review?.revision.id)
189
+ );
190
+ return /* @__PURE__ */ jsxRuntime.jsxs("main", { className: "sm-story-review", children: [
191
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-private", children: [
192
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Private story preview" }),
193
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: review?.published ? "This revision has been published. This review link remains private." : "This article has not been published." })
194
+ ] }),
195
+ /* @__PURE__ */ jsxRuntime.jsx("header", { className: "sm-review-masthead", children: review?.publication.name || publicationName }),
196
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "sm-review-alert", children: error }),
197
+ notice && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: "sm-review-notice", children: notice }),
198
+ verification && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "sm-review-form", children: [
199
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Verify your email" }),
200
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "The editor requires verification using the email address on your invitation. Your story remains private until verified." }),
201
+ /* @__PURE__ */ jsxRuntime.jsx(
202
+ "button",
203
+ {
204
+ disabled: busy,
205
+ onClick: async () => {
206
+ setBusy(true);
207
+ setError("");
208
+ try {
209
+ await request("/verify", {});
210
+ setNotice(
211
+ "A code was sent to the email address chosen by the editor. It expires in ten minutes."
212
+ );
213
+ } catch (e) {
214
+ setError(
215
+ e instanceof Error ? e.message : "Could not send code."
216
+ );
217
+ } finally {
218
+ setBusy(false);
219
+ }
220
+ },
221
+ children: "Email me a verification code"
222
+ }
223
+ ),
224
+ /* @__PURE__ */ jsxRuntime.jsxs(
225
+ "form",
226
+ {
227
+ onSubmit: async (e) => {
228
+ e.preventDefault();
229
+ setBusy(true);
230
+ setError("");
231
+ try {
232
+ await request("/verify", { code });
233
+ await load();
234
+ setCode("");
235
+ setNotice("Email verified.");
236
+ } catch (e2) {
237
+ setError(
238
+ e2 instanceof Error ? e2.message : "Verification failed."
239
+ );
240
+ } finally {
241
+ setBusy(false);
242
+ }
243
+ },
244
+ children: [
245
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
246
+ "Eight-digit code",
247
+ /* @__PURE__ */ jsxRuntime.jsx(
248
+ "input",
249
+ {
250
+ inputMode: "numeric",
251
+ autoComplete: "one-time-code",
252
+ pattern: "[0-9]{8}",
253
+ required: true,
254
+ value: code,
255
+ onChange: (e) => setCode(e.target.value)
256
+ }
257
+ )
258
+ ] }),
259
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: "Verify and open preview" })
260
+ ]
261
+ }
262
+ )
263
+ ] }),
264
+ !review && !error && !verification && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Opening your private preview\u2026" }),
265
+ review && document && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
266
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-intro", children: [
267
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
268
+ "Prepared for ",
269
+ review.reviewer,
270
+ " \xB7 Version ",
271
+ review.revision.number
272
+ ] }),
273
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: ["FACT_CHECK", "CONTRIBUTOR_REVIEW"].includes(
274
+ review.review_policy
275
+ ) ? "Please help us check the facts and represent your contribution accurately. The newsroom retains the final editorial decision." : "This voluntary announcement requires subject approval before publication. You may request changes or decline participation." })
276
+ ] }),
277
+ /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "sm-review-article", children: [
278
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
279
+ review.revision.document.section,
280
+ review.revision.document.community && ` \xB7 ${review.revision.document.community}`
281
+ ] }),
282
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { children: review.revision.document.title }),
283
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-review-dek", children: review.revision.document.subtitle }),
284
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: review.revision.editorial }),
285
+ review.revision.document.media.filter((m) => m.position === "hero").map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id)),
286
+ /* @__PURE__ */ jsxRuntime.jsx(
287
+ "div",
288
+ {
289
+ className: "sm-review-prose",
290
+ dangerouslySetInnerHTML: { __html: review.revision.content_html }
291
+ }
292
+ ),
293
+ review.revision.document.media.filter((m) => m.position !== "hero").map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id))
294
+ ] }),
295
+ /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "sm-review-actions", "aria-label": "Review actions", children: [
296
+ can("APPROVE") && !review.published && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("approve"), children: review.review_policy === "FACT_CHECK" ? "Confirm fact check" : "Approve draft" }),
297
+ can("REQUEST_CHANGES") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("changes"), children: "Request changes" }),
298
+ can("PROPOSE_EDITS") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("edits"), children: "Suggest edits" }),
299
+ can("ANSWER_QUESTIONS") && review.questions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("questions"), children: "Answer questions" }),
300
+ can("UPLOAD_MEDIA") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("media"), children: "Add photos" }),
301
+ can("COMMENT") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("comment"), children: "Leave a comment" }),
302
+ can("VIEW_SELECTED_HISTORY") && review.history.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("history"), children: "Shared history" }),
303
+ can("DOWNLOAD_PREVIEW") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => window.print(), children: "Print / save preview" })
304
+ ] }),
305
+ panel !== "article" && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "sm-review-form", "aria-label": "Your response", children: [
306
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-form-heading", children: [
307
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: panel === "approve" ? "Approve this exact draft" : panel === "edits" ? "Propose changes" : panel === "media" ? "Contribute a photograph" : panel === "questions" ? "Your story, in your words" : panel === "history" ? "Selected revision history" : panel === "decline" ? "Decline participation" : panel === "changes" ? "Request changes" : "Message the newsroom" }),
308
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Close" })
309
+ ] }),
310
+ panel === "edits" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
311
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your suggestions will be reviewed by an editor. They do not replace the newsroom\u2019s draft." }),
312
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
313
+ "Headline",
314
+ /* @__PURE__ */ jsxRuntime.jsx(
315
+ "input",
316
+ {
317
+ value: document.title,
318
+ onChange: (e) => setDocument({ ...document, title: e.target.value })
319
+ }
320
+ )
321
+ ] }),
322
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
323
+ "Dek",
324
+ /* @__PURE__ */ jsxRuntime.jsx(
325
+ "input",
326
+ {
327
+ value: document.subtitle,
328
+ onChange: (e) => setDocument({ ...document, subtitle: e.target.value })
329
+ }
330
+ )
331
+ ] }),
332
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
333
+ "Suggested article text",
334
+ /* @__PURE__ */ jsxRuntime.jsx(
335
+ "textarea",
336
+ {
337
+ rows: 16,
338
+ value: document.content_markdown,
339
+ onChange: (e) => setDocument({
340
+ ...document,
341
+ content_markdown: e.target.value
342
+ })
343
+ }
344
+ )
345
+ ] }),
346
+ /* @__PURE__ */ jsxRuntime.jsx(
347
+ "button",
348
+ {
349
+ disabled: busy,
350
+ onClick: () => void send(
351
+ {
352
+ action: "PROPOSE_EDITS",
353
+ title: document.title,
354
+ subtitle: document.subtitle,
355
+ content_markdown: document.content_markdown,
356
+ message
357
+ },
358
+ "Your proposed changes have been sent to the newsroom."
359
+ ),
360
+ children: "Send proposed changes"
361
+ }
362
+ )
363
+ ] }),
364
+ panel === "questions" && /* @__PURE__ */ jsxRuntime.jsxs(
365
+ "form",
366
+ {
367
+ onSubmit: (e) => {
368
+ e.preventDefault();
369
+ void send(
370
+ { action: "ANSWER_QUESTIONS", answers },
371
+ "Your answers have been saved for editorial review."
372
+ );
373
+ },
374
+ children: [
375
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "All questions are optional unless marked required. Share only what you would like considered for publication." }),
376
+ review.questions.map((q) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
377
+ q.prompt,
378
+ q.required ? " (required)" : " (optional)",
379
+ /* @__PURE__ */ jsxRuntime.jsx(
380
+ "textarea",
381
+ {
382
+ required: q.required,
383
+ rows: 3,
384
+ value: answers[q.id] || "",
385
+ onChange: (e) => setAnswers({ ...answers, [q.id]: e.target.value })
386
+ }
387
+ )
388
+ ] }, q.id)),
389
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: "Send answers" })
390
+ ]
391
+ }
392
+ ),
393
+ panel === "metadata" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
394
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Caption and credit changes are proposals for the newsroom; historical captions remain intact." }),
395
+ ["caption", "credit", "alt"].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
396
+ key,
397
+ /* @__PURE__ */ jsxRuntime.jsx(
398
+ "input",
399
+ {
400
+ value: mediaInfo[key],
401
+ onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
402
+ }
403
+ )
404
+ ] }, key)),
405
+ /* @__PURE__ */ jsxRuntime.jsx(
406
+ "button",
407
+ {
408
+ disabled: busy,
409
+ onClick: () => void send(
410
+ {
411
+ action: "EDIT_MEDIA_METADATA",
412
+ contribution_id: editingMedia,
413
+ caption: mediaInfo.caption,
414
+ credit: mediaInfo.credit,
415
+ alt: mediaInfo.alt
416
+ },
417
+ "Your proposed media descriptions have been sent to the newsroom."
418
+ ),
419
+ children: "Propose caption / credit changes"
420
+ }
421
+ )
422
+ ] }),
423
+ panel === "media" && /* @__PURE__ */ jsxRuntime.jsxs(
424
+ "form",
425
+ {
426
+ onSubmit: (e) => {
427
+ e.preventDefault();
428
+ void upload();
429
+ },
430
+ children: [
431
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "JPEG, PNG or WebP photos up to 12 MB; MP4 or WebM videos up to 25 MB. Media remain private until selected and approved by the newsroom." }),
432
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
433
+ "Photograph or video",
434
+ /* @__PURE__ */ jsxRuntime.jsx(
435
+ "input",
436
+ {
437
+ type: "file",
438
+ accept: "image/jpeg,image/png,image/webp,video/mp4,video/webm",
439
+ required: true,
440
+ onChange: (e) => setFile(e.target.files?.[0] || null)
441
+ }
442
+ )
443
+ ] }),
444
+ Object.entries({
445
+ caption: "Caption",
446
+ who_is_pictured: "Who is pictured?",
447
+ creator: "Photographer / creator",
448
+ when_taken: "When was it taken?",
449
+ where_taken: "Where was it taken?",
450
+ credit: "Credit line",
451
+ alt: "Alt-text suggestion"
452
+ }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
453
+ label,
454
+ /* @__PURE__ */ jsxRuntime.jsx(
455
+ "input",
456
+ {
457
+ value: mediaInfo[key],
458
+ onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
459
+ }
460
+ )
461
+ ] }, key)),
462
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-check", children: [
463
+ /* @__PURE__ */ jsxRuntime.jsx(
464
+ "input",
465
+ {
466
+ type: "checkbox",
467
+ required: true,
468
+ checked: rights,
469
+ onChange: (e) => setRights(e.target.checked)
470
+ }
471
+ ),
472
+ "I confirm that I have the right or permission to provide this media to ",
473
+ review.publication.name,
474
+ " for editorial publication."
475
+ ] }),
476
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy || !rights || !file, children: "Upload privately" })
477
+ ]
478
+ }
479
+ ),
480
+ ["approve", "changes", "comment", "decline"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
481
+ panel === "approve" && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
482
+ "You are approving version ",
483
+ review.revision.number,
484
+ ". Changes to the article require a new approval.",
485
+ review.review_policy === "SUBJECT_APPROVAL_AUTO_PUBLISH" ? " The editor has configured this version to publish or schedule after the required approvals are received." : " The newsroom will complete its editorial workflow before publication."
486
+ ] }),
487
+ panel === "decline" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: ["FACT_CHECK", "CONTRIBUTOR_REVIEW"].includes(
488
+ review.review_policy
489
+ ) ? "You can decline this review. The newsroom may still publish its reporting." : "Declining participation prevents publication of this voluntary announcement." }),
490
+ panel === "comment" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
491
+ "Comment on",
492
+ /* @__PURE__ */ jsxRuntime.jsxs(
493
+ "select",
494
+ {
495
+ value: commentTarget,
496
+ onChange: (e) => setCommentTarget(e.target.value),
497
+ children: [
498
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "STORY", children: "Entire story" }),
499
+ review.revision.document.content_markdown.split(/\n\s*\n/).slice(0, 200).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: `PARAGRAPH:${i + 1}`, children: [
500
+ "Paragraph ",
501
+ i + 1
502
+ ] }, i)),
503
+ review.revision.document.media.map((m, i) => /* @__PURE__ */ jsxRuntime.jsxs(
504
+ "option",
505
+ {
506
+ value: `MEDIA:${m.contribution_id}`,
507
+ children: [
508
+ "Photo / caption ",
509
+ i + 1
510
+ ]
511
+ },
512
+ m.contribution_id
513
+ )),
514
+ review.questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: `QUESTION:${q.id}`, children: q.prompt }, q.id))
515
+ ]
516
+ }
517
+ )
518
+ ] }),
519
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
520
+ panel === "changes" ? "What should change?" : "Message (optional)",
521
+ /* @__PURE__ */ jsxRuntime.jsx(
522
+ "textarea",
523
+ {
524
+ rows: 5,
525
+ value: message,
526
+ onChange: (e) => setMessage(e.target.value)
527
+ }
528
+ )
529
+ ] }),
530
+ /* @__PURE__ */ jsxRuntime.jsx(
531
+ "button",
532
+ {
533
+ disabled: busy || panel === "changes" && !message.trim() || panel === "comment" && !message.trim(),
534
+ onClick: () => void send(
535
+ {
536
+ action: panel === "approve" ? "APPROVE" : panel === "changes" ? "REQUEST_CHANGES" : panel === "decline" ? "DECLINE" : "COMMENT",
537
+ message: message || null,
538
+ ...panel === "comment" ? {
539
+ target: {
540
+ kind: commentTarget.split(":")[0],
541
+ reference: commentTarget.split(":")[1] || null
542
+ }
543
+ } : {}
544
+ },
545
+ panel === "approve" ? "Your approval has been recorded for this version." : panel === "decline" ? "Your decision has been recorded." : "Your message has been sent to the newsroom."
546
+ ),
547
+ children: panel === "approve" ? `Approve version ${review.revision.number}` : panel === "changes" ? "Send change request" : panel === "decline" ? "Decline participation" : "Send comment"
548
+ }
549
+ )
550
+ ] }),
551
+ panel === "history" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
552
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Only revisions selected by the editor are visible here." }),
553
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
554
+ "Compare from",
555
+ /* @__PURE__ */ jsxRuntime.jsxs(
556
+ "select",
557
+ {
558
+ value: compare,
559
+ onChange: (e) => setCompare(e.target.value),
560
+ children: [
561
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Choose a shared revision" }),
562
+ revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
563
+ "Version ",
564
+ r.number
565
+ ] }, r.id))
566
+ ]
567
+ }
568
+ )
569
+ ] }),
570
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
571
+ "Compare to",
572
+ /* @__PURE__ */ jsxRuntime.jsx(
573
+ "select",
574
+ {
575
+ value: compareTo || review.revision.id,
576
+ onChange: (e) => setCompareTo(e.target.value),
577
+ children: revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
578
+ "Version ",
579
+ r.number
580
+ ] }, r.id))
581
+ }
582
+ )
583
+ ] }),
584
+ selected && selectedAfter && /* @__PURE__ */ jsxRuntime.jsx(ReviewDiff, { before: selected, after: selectedAfter }),
585
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
586
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Read selected revision" }),
587
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: selected?.document.title }),
588
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: selected?.document.subtitle }),
589
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: selected?.editorial }),
590
+ /* @__PURE__ */ jsxRuntime.jsx(
591
+ "div",
592
+ {
593
+ className: "sm-review-prose",
594
+ dangerouslySetInnerHTML: {
595
+ __html: selected?.content_html || ""
596
+ }
597
+ }
598
+ ),
599
+ selected?.document.media.map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id))
600
+ ] })
601
+ ] })
602
+ ] }),
603
+ review.contributions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "sm-review-contributions", children: [
604
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Your contributions and newsroom messages" }),
605
+ review.contributions.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
606
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
607
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: human(c.kind) }),
608
+ " \xB7 ",
609
+ human(c.state)
610
+ ] }),
611
+ c.payload.target && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
612
+ "Regarding ",
613
+ human(c.payload.target.kind),
614
+ " ",
615
+ c.payload.target.reference
616
+ ] }),
617
+ c.payload.message && /* @__PURE__ */ jsxRuntime.jsx("p", { children: c.payload.message }),
618
+ c.payload.answers && Object.entries(c.payload.answers).map(([q, a]) => /* @__PURE__ */ jsxRuntime.jsx("p", { children: a }, q)),
619
+ c.kind === "MEDIA" && c.payload.file_id && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
620
+ /* @__PURE__ */ jsxRuntime.jsx(
621
+ PrivatePhoto,
622
+ {
623
+ base,
624
+ media: {
625
+ file_id: c.payload.file_id,
626
+ caption: c.payload.caption || "",
627
+ credit: c.payload.credit || "",
628
+ alt: c.payload.caption || "",
629
+ position: "gallery",
630
+ contribution_id: c.id
631
+ }
632
+ }
633
+ ),
634
+ c.state !== "WITHDRAWN" && can("EDIT_MEDIA_METADATA") && /* @__PURE__ */ jsxRuntime.jsx(
635
+ "button",
636
+ {
637
+ onClick: () => {
638
+ setEditingMedia(c.id);
639
+ setMediaInfo({
640
+ ...mediaInfo,
641
+ caption: c.payload.caption || "",
642
+ credit: c.payload.credit || "",
643
+ alt: c.payload.caption || ""
644
+ });
645
+ setPanel("metadata");
646
+ },
647
+ children: "Suggest caption / credit changes"
648
+ }
649
+ ),
650
+ c.state !== "WITHDRAWN" && can("UPLOAD_MEDIA") && /* @__PURE__ */ jsxRuntime.jsx(
651
+ "button",
652
+ {
653
+ disabled: busy,
654
+ onClick: () => void send(
655
+ {
656
+ action: "WITHDRAW_MEDIA",
657
+ contribution_id: c.id
658
+ },
659
+ "Your media has been withdrawn from consideration."
660
+ ),
661
+ children: "Withdraw this media"
662
+ }
663
+ )
664
+ ] })
665
+ ] }, c.id))
666
+ ] }),
667
+ can("DECLINE") && !review.published && /* @__PURE__ */ jsxRuntime.jsx("footer", { className: "sm-review-decline", children: /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("decline"), children: "Decline participation / please don\u2019t publish this announcement" }) })
668
+ ] })
669
+ ] });
670
+ }
671
+ function PrivatePhoto({ base, media }) {
672
+ const [url, setUrl] = react.useState("");
673
+ const [video, setVideo] = react.useState(false);
674
+ react.useEffect(() => {
675
+ let active = true, object = "";
676
+ void fetch(`${base}/media/${encodeURIComponent(media.file_id)}`, {
677
+ cache: "no-store",
678
+ credentials: "same-origin"
679
+ }).then((r) => {
680
+ if (!r.ok) throw new Error("unavailable");
681
+ return r.blob();
682
+ }).then((blob) => {
683
+ object = URL.createObjectURL(blob);
684
+ if (active) {
685
+ setUrl(object);
686
+ setVideo(blob.type.startsWith("video/"));
687
+ } else URL.revokeObjectURL(object);
688
+ }).catch(() => setUrl(""));
689
+ return () => {
690
+ active = false;
691
+ if (object) URL.revokeObjectURL(object);
692
+ };
693
+ }, [base, media.file_id]);
694
+ return /* @__PURE__ */ jsxRuntime.jsxs("figure", { children: [
695
+ url ? video ? /* @__PURE__ */ jsxRuntime.jsx(
696
+ "video",
697
+ {
698
+ src: url,
699
+ controls: true,
700
+ playsInline: true,
701
+ preload: "metadata",
702
+ "aria-label": media.alt
703
+ }
704
+ ) : /* @__PURE__ */ jsxRuntime.jsx("img", { src: url, alt: media.alt, referrerPolicy: "no-referrer" }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Media unavailable." }),
705
+ /* @__PURE__ */ jsxRuntime.jsxs("figcaption", { children: [
706
+ media.caption,
707
+ media.credit && ` Photo: ${media.credit}`
708
+ ] })
709
+ ] });
710
+ }
711
+ function ReviewDiff({
712
+ before,
713
+ after
714
+ }) {
715
+ const [side, setSide] = react.useState(false);
716
+ const fields = [
717
+ ["title", "Headline"],
718
+ ["subtitle", "Dek"],
719
+ ["content_markdown", "Article"],
720
+ ["section", "Section"],
721
+ ["community", "Community"]
722
+ ];
723
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-diff", children: [
724
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
725
+ "Version ",
726
+ before.number,
727
+ " compared with version ",
728
+ after.number
729
+ ] }),
730
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-check", children: [
731
+ /* @__PURE__ */ jsxRuntime.jsx(
732
+ "input",
733
+ {
734
+ type: "checkbox",
735
+ checked: side,
736
+ onChange: (e) => setSide(e.target.checked)
737
+ }
738
+ ),
739
+ "Side-by-side"
740
+ ] }),
741
+ fields.filter(([k]) => before.document[k] !== after.document[k]).map(([k, label]) => /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
742
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: label }),
743
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: side ? "sm-review-diff-columns" : "", children: side ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
744
+ /* @__PURE__ */ jsxRuntime.jsx("del", { children: String(before.document[k]) }),
745
+ /* @__PURE__ */ jsxRuntime.jsx("ins", { children: String(after.document[k]) })
746
+ ] }) : proseDiff(
747
+ String(before.document[k]),
748
+ String(after.document[k])
749
+ ).map(
750
+ (run, i) => run.kind === "added" ? /* @__PURE__ */ jsxRuntime.jsx("ins", { children: run.text }, i) : run.kind === "removed" ? /* @__PURE__ */ jsxRuntime.jsx("del", { children: run.text }, i) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: run.text }, i)
751
+ ) })
752
+ ] }, k)),
753
+ JSON.stringify(before.editorial) !== JSON.stringify(after.editorial) && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
754
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: "Editorial notices and disclosures" }),
755
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-diff-columns", children: [
756
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
757
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
758
+ "Version ",
759
+ before.number
760
+ ] }),
761
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: before.editorial })
762
+ ] }),
763
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
764
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
765
+ "Version ",
766
+ after.number
767
+ ] }),
768
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: after.editorial })
769
+ ] })
770
+ ] })
771
+ ] }),
772
+ JSON.stringify(before.document.media) !== JSON.stringify(after.document.media) && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
773
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: "Photographs and captions" }),
774
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-diff-columns", children: [before, after].map((r) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
775
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
776
+ "Version ",
777
+ r.number
778
+ ] }),
779
+ r.document.media.map((m, i) => /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
780
+ "Photo ",
781
+ i + 1,
782
+ " (",
783
+ m.position,
784
+ "): ",
785
+ m.caption,
786
+ " \u2014 ",
787
+ m.credit,
788
+ " \xB7 Alt: ",
789
+ m.alt,
790
+ !before.document.media.some(
791
+ (previous) => previous.file_id === m.file_id
792
+ ) ? " \xB7 Added photograph" : ""
793
+ ] }, m.contribution_id))
794
+ ] }, r.id)) })
795
+ ] })
796
+ ] });
797
+ }
798
+
799
+ exports.ReviewDiff = ReviewDiff;
800
+ exports.StoryReview = StoryReview;