@scalemule/news-publication 0.0.25 → 0.0.27

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,980 @@
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
+ nameplateUrl
70
+ }) {
71
+ const base = `/api/news/review/${encodeURIComponent(sessionId)}`;
72
+ 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("");
73
+ 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");
74
+ const responseRef = react.useRef(null);
75
+ const thanksRef = react.useRef(null);
76
+ const [moreQuestions, setMoreQuestions] = react.useState(false);
77
+ const [correctionKind, setCorrectionKind] = react.useState("Correct a detail");
78
+ react.useEffect(() => {
79
+ if (panel !== "article") {
80
+ responseRef.current?.scrollIntoView({
81
+ block: "start",
82
+ behavior: window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth"
83
+ });
84
+ responseRef.current?.focus({ preventScroll: true });
85
+ }
86
+ }, [panel]);
87
+ const [mediaInfo, setMediaInfo] = react.useState({
88
+ caption: "",
89
+ who_is_pictured: "",
90
+ creator: "",
91
+ when_taken: "",
92
+ where_taken: "",
93
+ credit: "",
94
+ alt: ""
95
+ });
96
+ async function request(path = "", body) {
97
+ const form = body instanceof FormData;
98
+ const r = await fetch(base + path, {
99
+ method: body === void 0 ? "GET" : "POST",
100
+ credentials: "same-origin",
101
+ cache: "no-store",
102
+ headers: body === void 0 || form ? {} : { "content-type": "application/json" },
103
+ body: body === void 0 ? void 0 : form ? body : JSON.stringify(body)
104
+ });
105
+ const result = await r.json();
106
+ if (!r.ok || result.success === false)
107
+ throw new Error(
108
+ result.error?.message || "This private review is unavailable. Please contact the newsroom."
109
+ );
110
+ return result.data ?? result;
111
+ }
112
+ async function load() {
113
+ const data = await request();
114
+ if (data.verification_required) {
115
+ setVerification(true);
116
+ setReview(null);
117
+ } else {
118
+ setVerification(false);
119
+ setReview(data);
120
+ setDocument(data.revision.document);
121
+ }
122
+ }
123
+ react.useEffect(() => {
124
+ let active = true;
125
+ void (async () => {
126
+ try {
127
+ const token = window.location.hash.slice(1);
128
+ if (token) {
129
+ await request("/session", { token });
130
+ window.history.replaceState(null, "", window.location.pathname);
131
+ }
132
+ const data = await request();
133
+ if (active) {
134
+ if (data.verification_required) {
135
+ setVerification(true);
136
+ setReview(null);
137
+ } else {
138
+ setVerification(false);
139
+ setReview(data);
140
+ setDocument(data.revision.document);
141
+ }
142
+ }
143
+ } catch (e) {
144
+ if (active)
145
+ setError(e instanceof Error ? e.message : "Unable to open review.");
146
+ }
147
+ })();
148
+ return () => {
149
+ active = false;
150
+ };
151
+ }, [sessionId]);
152
+ async function send(action, success) {
153
+ if (!review) return;
154
+ setBusy(true);
155
+ setError("");
156
+ setNotice("");
157
+ try {
158
+ await request("", { revision_id: review.revision.id, ...action });
159
+ await load();
160
+ setNotice(success);
161
+ setPanel("article");
162
+ setMessage("");
163
+ } catch (e) {
164
+ setError(
165
+ e instanceof Error ? e.message : "Could not save your response."
166
+ );
167
+ } finally {
168
+ setBusy(false);
169
+ }
170
+ }
171
+ const can = (p) => review?.permissions.includes(p);
172
+ const celebration = !!review?.celebration_type;
173
+ const approved = review?.decision === "APPROVED";
174
+ const finalEditor = review?.review_policy === "SUBJECT_APPROVAL_PLUS_EDITOR";
175
+ const approvalThanks = `Thank you. We're glad to help commemorate this special occasion. This version has been sent to the ${review?.publication.name || publicationName} newsroom for final editorial review. It has not been published yet.`;
176
+ react.useEffect(() => {
177
+ if (approved && finalEditor && thanksRef.current) {
178
+ thanksRef.current.scrollIntoView({ block: "start" });
179
+ thanksRef.current.focus({ preventScroll: true });
180
+ }
181
+ }, [approved, finalEditor]);
182
+ const approve = () => {
183
+ if (celebration && finalEditor && !review?.questions.some((q) => q.required))
184
+ void send({ action: "APPROVE", message: null }, approvalThanks);
185
+ else setPanel("approve");
186
+ };
187
+ async function upload() {
188
+ if (!review || !file || !rights) return;
189
+ setBusy(true);
190
+ setError("");
191
+ try {
192
+ const form = new FormData();
193
+ form.set("file", file);
194
+ form.set(
195
+ "metadata",
196
+ JSON.stringify({ ...mediaInfo, rights_confirmed: true })
197
+ );
198
+ await request("/media", form);
199
+ await load();
200
+ setFile(null);
201
+ setRights(false);
202
+ setNotice(
203
+ "Media received privately. The newsroom will review it before publication."
204
+ );
205
+ setPanel("article");
206
+ } catch (e) {
207
+ setError(e instanceof Error ? e.message : "Upload failed.");
208
+ } finally {
209
+ setBusy(false);
210
+ }
211
+ }
212
+ const revisions = review ? [
213
+ review.revision,
214
+ ...review.history.filter((r) => r.id !== review.revision.id)
215
+ ] : [];
216
+ const selected = revisions.find((r) => r.id === compare), selectedAfter = revisions.find(
217
+ (r) => r.id === (compareTo || review?.revision.id)
218
+ );
219
+ return /* @__PURE__ */ jsxRuntime.jsxs(
220
+ "main",
221
+ {
222
+ className: `sm-story-review${celebration ? " sm-review-celebration" : ""}`,
223
+ children: [
224
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-private", children: [
225
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: review?.published ? "Private review" : "Private preview \xB7 Not published" }),
226
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: review?.published ? "This revision has been published. This review link remains private." : `Prepared by ${publicationName} for ${celebration ? "family " : ""}review.` })
227
+ ] }),
228
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sm-review-masthead", children: [
229
+ nameplateUrl && /* @__PURE__ */ jsxRuntime.jsx("img", { src: nameplateUrl, alt: "", width: 80, height: 80 }),
230
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: review?.publication.name || publicationName })
231
+ ] }),
232
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "sm-review-alert", children: error }),
233
+ notice && !(approved && finalEditor && notice === approvalThanks) && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: "sm-review-notice", children: notice }),
234
+ verification && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "sm-review-form", children: [
235
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Verify your email" }),
236
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "The editor requires verification using the email address on your invitation. Your story remains private until verified." }),
237
+ /* @__PURE__ */ jsxRuntime.jsx(
238
+ "button",
239
+ {
240
+ disabled: busy,
241
+ onClick: async () => {
242
+ setBusy(true);
243
+ setError("");
244
+ try {
245
+ await request("/verify", {});
246
+ setNotice(
247
+ "A code was sent to the email address chosen by the editor. It expires in ten minutes."
248
+ );
249
+ } catch (e) {
250
+ setError(
251
+ e instanceof Error ? e.message : "Could not send code."
252
+ );
253
+ } finally {
254
+ setBusy(false);
255
+ }
256
+ },
257
+ children: "Email me a verification code"
258
+ }
259
+ ),
260
+ /* @__PURE__ */ jsxRuntime.jsxs(
261
+ "form",
262
+ {
263
+ onSubmit: async (e) => {
264
+ e.preventDefault();
265
+ setBusy(true);
266
+ setError("");
267
+ try {
268
+ await request("/verify", { code });
269
+ await load();
270
+ setCode("");
271
+ setNotice("Email verified.");
272
+ } catch (e2) {
273
+ setError(
274
+ e2 instanceof Error ? e2.message : "Verification failed."
275
+ );
276
+ } finally {
277
+ setBusy(false);
278
+ }
279
+ },
280
+ children: [
281
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
282
+ "Eight-digit code",
283
+ /* @__PURE__ */ jsxRuntime.jsx(
284
+ "input",
285
+ {
286
+ inputMode: "numeric",
287
+ autoComplete: "one-time-code",
288
+ pattern: "[0-9]{8}",
289
+ required: true,
290
+ value: code,
291
+ onChange: (e) => setCode(e.target.value)
292
+ }
293
+ )
294
+ ] }),
295
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: "Verify and open preview" })
296
+ ]
297
+ }
298
+ )
299
+ ] }),
300
+ !review && !error && !verification && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Opening your private preview\u2026" }),
301
+ review && document && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
302
+ celebration ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-intro", children: [
303
+ review.revision.number > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
304
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Updated preview" }),
305
+ " \u2014 We incorporated the information you sent us."
306
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: "A special occasion, thoughtfully told. Read your announcement below." }),
307
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "#review-options", children: "Review options" })
308
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-intro", children: [
309
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
310
+ "Prepared for ",
311
+ review.reviewer,
312
+ " \xB7 Version",
313
+ " ",
314
+ review.revision.number
315
+ ] }),
316
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: ["FACT_CHECK", "CONTRIBUTOR_REVIEW"].includes(
317
+ review.review_policy
318
+ ) ? "Please help us check the facts and represent your contribution accurately. The newsroom retains the final editorial decision." : "Please review this announcement. You may suggest changes or decline participation." })
319
+ ] }),
320
+ approved && finalEditor && !review.published && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: thanksRef, tabIndex: -1, className: "sm-review-thanks", role: "status", children: [
321
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Thank you." }),
322
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "We're glad to help commemorate this special occasion." }),
323
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
324
+ "This version has been sent to the ",
325
+ review.publication.name,
326
+ " ",
327
+ "newsroom for final editorial review. It has not been published yet."
328
+ ] })
329
+ ] }),
330
+ /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "sm-review-article", children: [
331
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "sm-review-category", children: [
332
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: review.revision.document.section }),
333
+ celebration && /* @__PURE__ */ jsxRuntime.jsx("span", { children: human(review.celebration_type || "") })
334
+ ] }),
335
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { children: review.revision.document.title }),
336
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-review-dek", children: review.revision.document.subtitle }),
337
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: review.revision.editorial }),
338
+ review.revision.document.media.filter((m) => m.position === "hero").map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id)),
339
+ celebration && !review.revision.document.media.some(
340
+ (m) => m.position === "hero"
341
+ ) && can("UPLOAD_MEDIA") && /* @__PURE__ */ jsxRuntime.jsxs(
342
+ "aside",
343
+ {
344
+ className: "sm-review-photo-invitation",
345
+ "aria-label": "Family photograph",
346
+ children: [
347
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
348
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "A photograph makes it yours." }),
349
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Family photograph may be added before publication." })
350
+ ] }),
351
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("media"), children: "+ Add a photograph" })
352
+ ]
353
+ }
354
+ ),
355
+ /* @__PURE__ */ jsxRuntime.jsx(
356
+ "div",
357
+ {
358
+ className: "sm-review-prose",
359
+ dangerouslySetInnerHTML: { __html: review.revision.content_html }
360
+ }
361
+ ),
362
+ review.revision.document.media.filter((m) => m.position !== "hero").map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id))
363
+ ] }),
364
+ celebration && /* @__PURE__ */ jsxRuntime.jsxs("section", { id: "review-options", className: "sm-review-welcome", children: [
365
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Help us make this just right" }),
366
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "We prepared this announcement as a starting point so you don't have to write anything yourself. If everything looks right, you can simply approve it." }),
367
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "If you'd like, you can also correct a detail, add photographs, or tell us a little more about the couple. We'll take care of turning any additional information into the next draft." }),
368
+ can("ANSWER_QUESTIONS") && review.questions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
369
+ "button",
370
+ {
371
+ onClick: () => {
372
+ setMoreQuestions(false);
373
+ setPanel("questions");
374
+ },
375
+ children: "Tell Us More"
376
+ }
377
+ ),
378
+ can("DECLINE") && !review.published && /* @__PURE__ */ jsxRuntime.jsx(
379
+ "button",
380
+ {
381
+ className: "sm-review-quiet",
382
+ onClick: () => setPanel("decline"),
383
+ children: "Prefer Not to Publish"
384
+ }
385
+ ),
386
+ can("COMMENT") && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-quiet", onClick: () => setPanel("comment"), children: "Leave a Comment" })
387
+ ] }),
388
+ /* @__PURE__ */ jsxRuntime.jsxs(
389
+ "nav",
390
+ {
391
+ className: `sm-review-actions${celebration ? " sm-review-concierge-actions" : ""}${panel !== "article" ? " sm-review-actions-editing" : ""}`,
392
+ "aria-label": "Review actions",
393
+ children: [
394
+ can("APPROVE") && !review.published && /* @__PURE__ */ jsxRuntime.jsx(
395
+ "button",
396
+ {
397
+ className: "sm-review-primary",
398
+ disabled: busy || approved || review.decision === "DECLINED",
399
+ onClick: approve,
400
+ children: approved ? "Thank you \u2014 Approved" : review.review_policy === "FACT_CHECK" ? "Confirm fact check" : celebration ? "Looks Good \u2014 Approve" : "Approve draft"
401
+ }
402
+ ),
403
+ can("REQUEST_CHANGES") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("changes"), children: celebration ? "Suggest a Change" : "Request changes" }),
404
+ celebration && can("PROPOSE_EDITS") && !can("REQUEST_CHANGES") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("edits"), children: "Suggest a Change" }),
405
+ can("UPLOAD_MEDIA") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("media"), children: celebration ? "Add Photos" : "Add photos" }),
406
+ !celebration && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
407
+ can("PROPOSE_EDITS") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("edits"), children: "Suggest edits" }),
408
+ can("ANSWER_QUESTIONS") && review.questions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("questions"), children: "Answer questions" }),
409
+ can("COMMENT") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("comment"), children: "Leave a comment" })
410
+ ] })
411
+ ]
412
+ }
413
+ ),
414
+ (can("VIEW_SELECTED_HISTORY") && review.history.length > 0 || can("DOWNLOAD_PREVIEW")) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-utilities", children: [
415
+ can("VIEW_SELECTED_HISTORY") && review.history.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("history"), children: "See previous previews" }),
416
+ can("DOWNLOAD_PREVIEW") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => window.print(), children: "Print / save preview" })
417
+ ] }),
418
+ panel !== "article" && /* @__PURE__ */ jsxRuntime.jsxs(
419
+ "section",
420
+ {
421
+ ref: responseRef,
422
+ tabIndex: -1,
423
+ className: "sm-review-form",
424
+ "aria-label": "Your response",
425
+ children: [
426
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-form-heading", children: [
427
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: panel === "approve" ? "Approve this exact draft" : panel === "edits" ? "Propose changes" : panel === "media" ? "Contribute a photograph" : panel === "questions" ? "Tell us a little more" : panel === "history" ? "Selected revision history" : panel === "decline" ? "Prefer not to publish" : panel === "changes" ? "Suggest a change" : "Message the newsroom" }),
428
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Close" })
429
+ ] }),
430
+ panel === "edits" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
431
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your suggestions will be reviewed by an editor. They do not replace the newsroom\u2019s draft." }),
432
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
433
+ "Headline",
434
+ /* @__PURE__ */ jsxRuntime.jsx(
435
+ "input",
436
+ {
437
+ value: document.title,
438
+ onChange: (e) => setDocument({ ...document, title: e.target.value })
439
+ }
440
+ )
441
+ ] }),
442
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
443
+ "Short introduction",
444
+ /* @__PURE__ */ jsxRuntime.jsx(
445
+ "input",
446
+ {
447
+ value: document.subtitle,
448
+ onChange: (e) => setDocument({ ...document, subtitle: e.target.value })
449
+ }
450
+ )
451
+ ] }),
452
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
453
+ "Suggested article text",
454
+ /* @__PURE__ */ jsxRuntime.jsx(
455
+ "textarea",
456
+ {
457
+ rows: 16,
458
+ value: document.content_markdown,
459
+ onChange: (e) => setDocument({
460
+ ...document,
461
+ content_markdown: e.target.value
462
+ })
463
+ }
464
+ )
465
+ ] }),
466
+ /* @__PURE__ */ jsxRuntime.jsx(
467
+ "button",
468
+ {
469
+ disabled: busy,
470
+ onClick: () => void send(
471
+ {
472
+ action: "PROPOSE_EDITS",
473
+ title: document.title,
474
+ subtitle: document.subtitle,
475
+ content_markdown: document.content_markdown,
476
+ message
477
+ },
478
+ "Your proposed changes have been sent to the newsroom."
479
+ ),
480
+ children: "Send proposed changes"
481
+ }
482
+ )
483
+ ] }),
484
+ panel === "questions" && /* @__PURE__ */ jsxRuntime.jsxs(
485
+ "form",
486
+ {
487
+ onSubmit: (e) => {
488
+ e.preventDefault();
489
+ void send(
490
+ { action: "ANSWER_QUESTIONS", answers },
491
+ "Thank you for sharing. We'll take care of turning your answers into an updated draft."
492
+ );
493
+ },
494
+ children: [
495
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: review.questions.some((q) => q.required) ? "Only questions marked required must be answered. Share what you would like considered for publication." : "Completely optional. Even one answer can help us make the announcement more personal." }),
496
+ review.questions.filter((q, i) => moreQuestions || i < 5 || q.required).map((q) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
497
+ q.prompt,
498
+ q.required ? " (required)" : " (optional)",
499
+ /* @__PURE__ */ jsxRuntime.jsx(
500
+ "textarea",
501
+ {
502
+ required: q.required,
503
+ rows: 3,
504
+ value: answers[q.id] || "",
505
+ onChange: (e) => setAnswers({ ...answers, [q.id]: e.target.value })
506
+ }
507
+ )
508
+ ] }, q.id)),
509
+ /* @__PURE__ */ jsxRuntime.jsx(
510
+ "button",
511
+ {
512
+ className: "sm-review-primary",
513
+ disabled: busy || !Object.values(answers).some((a) => a.trim()),
514
+ children: "Send These Answers"
515
+ }
516
+ ),
517
+ !moreQuestions && review.questions.length > 5 && /* @__PURE__ */ jsxRuntime.jsx(
518
+ "button",
519
+ {
520
+ type: "button",
521
+ onClick: () => setMoreQuestions(true),
522
+ children: "Answer More Questions"
523
+ }
524
+ )
525
+ ]
526
+ }
527
+ ),
528
+ panel === "metadata" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
529
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Caption and credit changes are proposals for the newsroom; historical captions remain intact." }),
530
+ ["caption", "credit", "alt"].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
531
+ key,
532
+ /* @__PURE__ */ jsxRuntime.jsx(
533
+ "input",
534
+ {
535
+ value: mediaInfo[key],
536
+ onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
537
+ }
538
+ )
539
+ ] }, key)),
540
+ /* @__PURE__ */ jsxRuntime.jsx(
541
+ "button",
542
+ {
543
+ disabled: busy,
544
+ onClick: () => void send(
545
+ {
546
+ action: "EDIT_MEDIA_METADATA",
547
+ contribution_id: editingMedia,
548
+ caption: mediaInfo.caption,
549
+ credit: mediaInfo.credit,
550
+ alt: mediaInfo.alt
551
+ },
552
+ "Your proposed media descriptions have been sent to the newsroom."
553
+ ),
554
+ children: "Propose caption / credit changes"
555
+ }
556
+ )
557
+ ] }),
558
+ panel === "media" && /* @__PURE__ */ jsxRuntime.jsxs(
559
+ "form",
560
+ {
561
+ onSubmit: (e) => {
562
+ e.preventDefault();
563
+ void upload();
564
+ },
565
+ children: [
566
+ /* @__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." }),
567
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
568
+ "Photograph or video",
569
+ /* @__PURE__ */ jsxRuntime.jsx(
570
+ "input",
571
+ {
572
+ type: "file",
573
+ accept: "image/jpeg,image/png,image/webp,video/mp4,video/webm",
574
+ required: true,
575
+ onChange: (e) => setFile(e.target.files?.[0] || null)
576
+ }
577
+ )
578
+ ] }),
579
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-review-help", children: "Choose a photograph and confirm permission below. All descriptions are optional; we can help with the caption." }),
580
+ Object.entries({
581
+ caption: "Caption (optional)",
582
+ credit: "Photographer / credit (optional)"
583
+ }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
584
+ label,
585
+ /* @__PURE__ */ jsxRuntime.jsx(
586
+ "input",
587
+ {
588
+ value: mediaInfo[key],
589
+ onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
590
+ }
591
+ )
592
+ ] }, key)),
593
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "sm-review-media-details", children: [
594
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Add more photograph details (optional)" }),
595
+ Object.entries({
596
+ who_is_pictured: "Who is pictured?",
597
+ creator: "Photographer / creator",
598
+ when_taken: "When was it taken?",
599
+ where_taken: "Where was it taken?",
600
+ alt: "Describe the photograph for readers who cannot see it"
601
+ }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
602
+ label,
603
+ /* @__PURE__ */ jsxRuntime.jsx(
604
+ "input",
605
+ {
606
+ value: mediaInfo[key],
607
+ onChange: (e) => setMediaInfo({
608
+ ...mediaInfo,
609
+ [key]: e.target.value
610
+ })
611
+ }
612
+ )
613
+ ] }, key))
614
+ ] }),
615
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-check", children: [
616
+ /* @__PURE__ */ jsxRuntime.jsx(
617
+ "input",
618
+ {
619
+ type: "checkbox",
620
+ required: true,
621
+ checked: rights,
622
+ onChange: (e) => setRights(e.target.checked)
623
+ }
624
+ ),
625
+ "I confirm that I have the right or permission to provide this media to ",
626
+ review.publication.name,
627
+ " for editorial publication."
628
+ ] }),
629
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy || !rights || !file, children: "Upload privately" })
630
+ ]
631
+ }
632
+ ),
633
+ ["approve", "changes", "comment", "decline"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
634
+ panel === "approve" && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
635
+ "You are approving version ",
636
+ review.revision.number,
637
+ ". Changes to the article require a new approval.",
638
+ 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."
639
+ ] }),
640
+ panel === "decline" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: ["FACT_CHECK", "CONTRIBUTOR_REVIEW"].includes(
641
+ review.review_policy
642
+ ) ? "You can decline this review. The newsroom may still publish its reporting." : "Declining participation prevents publication of this voluntary announcement." }),
643
+ panel === "comment" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
644
+ "Comment on",
645
+ /* @__PURE__ */ jsxRuntime.jsxs(
646
+ "select",
647
+ {
648
+ value: commentTarget,
649
+ onChange: (e) => setCommentTarget(e.target.value),
650
+ children: [
651
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "STORY", children: "Entire story" }),
652
+ review.revision.document.content_markdown.split(/\n\s*\n/).slice(0, 200).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: `PARAGRAPH:${i + 1}`, children: [
653
+ "Paragraph ",
654
+ i + 1
655
+ ] }, i)),
656
+ review.revision.document.media.map((m, i) => /* @__PURE__ */ jsxRuntime.jsxs(
657
+ "option",
658
+ {
659
+ value: `MEDIA:${m.contribution_id}`,
660
+ children: [
661
+ "Photo / caption ",
662
+ i + 1
663
+ ]
664
+ },
665
+ m.contribution_id
666
+ )),
667
+ review.questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: `QUESTION:${q.id}`, children: q.prompt }, q.id))
668
+ ]
669
+ }
670
+ )
671
+ ] }),
672
+ panel === "changes" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
673
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
674
+ "What would you like to change?",
675
+ /* @__PURE__ */ jsxRuntime.jsx(
676
+ "select",
677
+ {
678
+ value: correctionKind,
679
+ onChange: (e) => setCorrectionKind(e.target.value),
680
+ children: [
681
+ "Correct a detail",
682
+ "Correct a name",
683
+ "Correct a professional / education detail",
684
+ "Remove something",
685
+ "Rewrite something",
686
+ "Add something",
687
+ "General comment"
688
+ ].map((kind) => /* @__PURE__ */ jsxRuntime.jsx("option", { children: kind }, kind))
689
+ }
690
+ )
691
+ ] }),
692
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "A sentence or two is enough. We'll take care of the wording." }),
693
+ can("PROPOSE_EDITS") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("edits"), children: "Or edit the wording directly" })
694
+ ] }),
695
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
696
+ panel === "changes" ? "What should change?" : "Message (optional)",
697
+ /* @__PURE__ */ jsxRuntime.jsx(
698
+ "textarea",
699
+ {
700
+ rows: 5,
701
+ value: message,
702
+ onChange: (e) => setMessage(e.target.value)
703
+ }
704
+ )
705
+ ] }),
706
+ /* @__PURE__ */ jsxRuntime.jsx(
707
+ "button",
708
+ {
709
+ disabled: busy || panel === "changes" && !message.trim() || panel === "comment" && !message.trim(),
710
+ onClick: () => void send(
711
+ {
712
+ action: panel === "approve" ? "APPROVE" : panel === "changes" ? "REQUEST_CHANGES" : panel === "decline" ? "DECLINE" : "COMMENT",
713
+ message: panel === "changes" ? `${correctionKind}: ${message}` : message || null,
714
+ ...panel === "comment" ? {
715
+ target: {
716
+ kind: commentTarget.split(":")[0],
717
+ reference: commentTarget.split(":")[1] || null
718
+ }
719
+ } : {}
720
+ },
721
+ panel === "approve" ? celebration && finalEditor ? approvalThanks : "Your approval has been recorded for this version." : panel === "decline" ? "Thank you for letting us know. The newsroom has received your decision." : "Your message has been sent to the newsroom."
722
+ ),
723
+ children: panel === "approve" ? `Approve version ${review.revision.number}` : panel === "changes" ? "Send change request" : panel === "decline" ? "Prefer not to publish" : "Send comment"
724
+ }
725
+ )
726
+ ] }),
727
+ panel === "history" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
728
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Only revisions selected by the editor are visible here." }),
729
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
730
+ "Compare from",
731
+ /* @__PURE__ */ jsxRuntime.jsxs(
732
+ "select",
733
+ {
734
+ value: compare,
735
+ onChange: (e) => setCompare(e.target.value),
736
+ children: [
737
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Choose a shared revision" }),
738
+ revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
739
+ "Version ",
740
+ r.number
741
+ ] }, r.id))
742
+ ]
743
+ }
744
+ )
745
+ ] }),
746
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
747
+ "Compare to",
748
+ /* @__PURE__ */ jsxRuntime.jsx(
749
+ "select",
750
+ {
751
+ value: compareTo || review.revision.id,
752
+ onChange: (e) => setCompareTo(e.target.value),
753
+ children: revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
754
+ "Version ",
755
+ r.number
756
+ ] }, r.id))
757
+ }
758
+ )
759
+ ] }),
760
+ selected && selectedAfter && /* @__PURE__ */ jsxRuntime.jsx(ReviewDiff, { before: selected, after: selectedAfter }),
761
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
762
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Read selected revision" }),
763
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: selected?.document.title }),
764
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: selected?.document.subtitle }),
765
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: selected?.editorial }),
766
+ /* @__PURE__ */ jsxRuntime.jsx(
767
+ "div",
768
+ {
769
+ className: "sm-review-prose",
770
+ dangerouslySetInnerHTML: {
771
+ __html: selected?.content_html || ""
772
+ }
773
+ }
774
+ ),
775
+ selected?.document.media.map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id))
776
+ ] })
777
+ ] })
778
+ ]
779
+ }
780
+ ),
781
+ review.contributions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "sm-review-contributions", children: [
782
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Your contributions and newsroom messages" }),
783
+ review.contributions.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
784
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
785
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: human(c.kind) }),
786
+ " \xB7 ",
787
+ human(c.state)
788
+ ] }),
789
+ c.payload.target && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
790
+ "Regarding ",
791
+ human(c.payload.target.kind),
792
+ " ",
793
+ c.payload.target.reference
794
+ ] }),
795
+ c.payload.message && /* @__PURE__ */ jsxRuntime.jsx("p", { children: c.payload.message }),
796
+ c.payload.answers && Object.entries(c.payload.answers).map(([q, a]) => /* @__PURE__ */ jsxRuntime.jsx("p", { children: a }, q)),
797
+ c.kind === "MEDIA" && c.payload.file_id && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
798
+ /* @__PURE__ */ jsxRuntime.jsx(
799
+ PrivatePhoto,
800
+ {
801
+ base,
802
+ media: {
803
+ file_id: c.payload.file_id,
804
+ caption: c.payload.caption || "",
805
+ credit: c.payload.credit || "",
806
+ alt: c.payload.caption || "",
807
+ position: "gallery",
808
+ contribution_id: c.id
809
+ }
810
+ }
811
+ ),
812
+ c.state !== "WITHDRAWN" && can("EDIT_MEDIA_METADATA") && /* @__PURE__ */ jsxRuntime.jsx(
813
+ "button",
814
+ {
815
+ onClick: () => {
816
+ setEditingMedia(c.id);
817
+ setMediaInfo({
818
+ ...mediaInfo,
819
+ caption: c.payload.caption || "",
820
+ credit: c.payload.credit || "",
821
+ alt: c.payload.caption || ""
822
+ });
823
+ setPanel("metadata");
824
+ },
825
+ children: "Suggest caption / credit changes"
826
+ }
827
+ ),
828
+ c.state !== "WITHDRAWN" && can("UPLOAD_MEDIA") && /* @__PURE__ */ jsxRuntime.jsx(
829
+ "button",
830
+ {
831
+ disabled: busy,
832
+ onClick: () => void send(
833
+ {
834
+ action: "WITHDRAW_MEDIA",
835
+ contribution_id: c.id
836
+ },
837
+ "Your media has been withdrawn from consideration."
838
+ ),
839
+ children: "Withdraw this media"
840
+ }
841
+ )
842
+ ] })
843
+ ] }, c.id))
844
+ ] }),
845
+ !celebration && 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" }) })
846
+ ] })
847
+ ]
848
+ }
849
+ );
850
+ }
851
+ function PrivatePhoto({ base, media }) {
852
+ const [url, setUrl] = react.useState("");
853
+ const [video, setVideo] = react.useState(false);
854
+ react.useEffect(() => {
855
+ let active = true, object = "";
856
+ void fetch(`${base}/media/${encodeURIComponent(media.file_id)}`, {
857
+ cache: "no-store",
858
+ credentials: "same-origin"
859
+ }).then((r) => {
860
+ if (!r.ok) throw new Error("unavailable");
861
+ return r.blob();
862
+ }).then((blob) => {
863
+ object = URL.createObjectURL(blob);
864
+ if (active) {
865
+ setUrl(object);
866
+ setVideo(blob.type.startsWith("video/"));
867
+ } else URL.revokeObjectURL(object);
868
+ }).catch(() => setUrl(""));
869
+ return () => {
870
+ active = false;
871
+ if (object) URL.revokeObjectURL(object);
872
+ };
873
+ }, [base, media.file_id]);
874
+ return /* @__PURE__ */ jsxRuntime.jsxs("figure", { children: [
875
+ url ? video ? /* @__PURE__ */ jsxRuntime.jsx(
876
+ "video",
877
+ {
878
+ src: url,
879
+ controls: true,
880
+ playsInline: true,
881
+ preload: "metadata",
882
+ "aria-label": media.alt
883
+ }
884
+ ) : /* @__PURE__ */ jsxRuntime.jsx("img", { src: url, alt: media.alt, referrerPolicy: "no-referrer" }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Media unavailable." }),
885
+ /* @__PURE__ */ jsxRuntime.jsxs("figcaption", { children: [
886
+ media.caption,
887
+ media.credit && ` Photo: ${media.credit}`
888
+ ] })
889
+ ] });
890
+ }
891
+ function ReviewDiff({
892
+ before,
893
+ after
894
+ }) {
895
+ const [side, setSide] = react.useState(false);
896
+ const fields = [
897
+ ["title", "Headline"],
898
+ ["subtitle", "Dek"],
899
+ ["content_markdown", "Article"],
900
+ ["section", "Section"],
901
+ ["community", "Community"]
902
+ ];
903
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-diff", children: [
904
+ /* @__PURE__ */ jsxRuntime.jsxs("h3", { children: [
905
+ "Version ",
906
+ before.number,
907
+ " compared with version ",
908
+ after.number
909
+ ] }),
910
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-check", children: [
911
+ /* @__PURE__ */ jsxRuntime.jsx(
912
+ "input",
913
+ {
914
+ type: "checkbox",
915
+ checked: side,
916
+ onChange: (e) => setSide(e.target.checked)
917
+ }
918
+ ),
919
+ "Side-by-side"
920
+ ] }),
921
+ fields.filter(([k]) => before.document[k] !== after.document[k]).map(([k, label]) => /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
922
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: label }),
923
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: side ? "sm-review-diff-columns" : "", children: side ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
924
+ /* @__PURE__ */ jsxRuntime.jsx("del", { children: String(before.document[k]) }),
925
+ /* @__PURE__ */ jsxRuntime.jsx("ins", { children: String(after.document[k]) })
926
+ ] }) : proseDiff(
927
+ String(before.document[k]),
928
+ String(after.document[k])
929
+ ).map(
930
+ (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)
931
+ ) })
932
+ ] }, k)),
933
+ JSON.stringify(before.editorial) !== JSON.stringify(after.editorial) && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
934
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: "Editorial notices and disclosures" }),
935
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-diff-columns", children: [
936
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
937
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
938
+ "Version ",
939
+ before.number
940
+ ] }),
941
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: before.editorial })
942
+ ] }),
943
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
944
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
945
+ "Version ",
946
+ after.number
947
+ ] }),
948
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: after.editorial })
949
+ ] })
950
+ ] })
951
+ ] }),
952
+ JSON.stringify(before.document.media) !== JSON.stringify(after.document.media) && /* @__PURE__ */ jsxRuntime.jsxs("section", { children: [
953
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { children: "Photographs and captions" }),
954
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-diff-columns", children: [before, after].map((r) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
955
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
956
+ "Version ",
957
+ r.number
958
+ ] }),
959
+ r.document.media.map((m, i) => /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
960
+ "Photo ",
961
+ i + 1,
962
+ " (",
963
+ m.position,
964
+ "): ",
965
+ m.caption,
966
+ " \u2014 ",
967
+ m.credit,
968
+ " \xB7 Alt: ",
969
+ m.alt,
970
+ !before.document.media.some(
971
+ (previous) => previous.file_id === m.file_id
972
+ ) ? " \xB7 Added photograph" : ""
973
+ ] }, m.contribution_id))
974
+ ] }, r.id)) })
975
+ ] })
976
+ ] });
977
+ }
978
+
979
+ exports.ReviewDiff = ReviewDiff;
980
+ exports.StoryReview = StoryReview;