@scalemule/news-publication 0.0.29 → 0.0.31

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.
@@ -4,6 +4,231 @@
4
4
  var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
+ var stable = (value) => Array.isArray(value) ? value.map(stable) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map((k) => [k, stable(value[k])])) : value;
8
+ var same = (a, b) => JSON.stringify(stable(a)) === JSON.stringify(stable(b));
9
+ var hasDraft = (data) => !!data && Object.keys(data).length > 0;
10
+ function useReviewDrafts(session, revision, initial, request) {
11
+ const [, render] = react.useState(0);
12
+ const entries = react.useRef({});
13
+ const active = react.useRef("");
14
+ const requester = react.useRef(request);
15
+ requester.current = request;
16
+ const timers = react.useRef({});
17
+ const flights = react.useRef({});
18
+ const key = (id, panel) => `story-response:${id}:${panel}`;
19
+ function backup(id, panel, entry) {
20
+ try {
21
+ if (entry.dirty) localStorage.setItem(key(id, panel), JSON.stringify({ ...entry, expires: Date.now() + 7 * 864e5 }));
22
+ else localStorage.removeItem(key(id, panel));
23
+ entry.local = true;
24
+ } catch {
25
+ entry.local = false;
26
+ }
27
+ }
28
+ function redraw() {
29
+ render((n) => n + 1);
30
+ }
31
+ async function flush(panel) {
32
+ clearTimeout(timers.current[panel]);
33
+ if (flights.current[panel]) {
34
+ await flights.current[panel];
35
+ if (entries.current[panel]?.dirty) return flush(panel);
36
+ return;
37
+ }
38
+ const id = active.current, revisionId = revision;
39
+ if (!revisionId) return;
40
+ const run = async () => {
41
+ let entry = entries.current[panel];
42
+ while (entry?.dirty && active.current === id) {
43
+ if (entry.status === "conflict") throw Error("Choose which response to keep before sending.");
44
+ const sent = entry.data;
45
+ entry.status = "saving";
46
+ redraw();
47
+ try {
48
+ const saved = await requester.current("", { action: "SAVE_DRAFT", revision_id: revisionId, panel, data: sent, expected_version: entry.version });
49
+ if (active.current !== id) return;
50
+ if (!Number.isSafeInteger(saved.version)) throw Error("Your response could not be saved yet. Please try again.");
51
+ entry = entries.current[panel];
52
+ entry.version = saved.version;
53
+ entry.dirty = !same(entry.data, sent);
54
+ entry.status = entry.dirty ? "saving" : "saved";
55
+ backup(id, panel, entry);
56
+ redraw();
57
+ } catch (error) {
58
+ if (active.current === id) {
59
+ entry.status = error.status === 409 ? "conflict" : "offline";
60
+ backup(id, panel, entry);
61
+ redraw();
62
+ }
63
+ throw error;
64
+ }
65
+ }
66
+ };
67
+ const promise = run();
68
+ flights.current[panel] = promise;
69
+ try {
70
+ await promise;
71
+ } finally {
72
+ if (flights.current[panel] === promise) delete flights.current[panel];
73
+ }
74
+ }
75
+ const flushRef = react.useRef(flush);
76
+ flushRef.current = flush;
77
+ react.useEffect(() => {
78
+ if (!revision) {
79
+ entries.current = {};
80
+ redraw();
81
+ return;
82
+ }
83
+ const id = `${session}:${revision}`;
84
+ active.current = id;
85
+ const next = {};
86
+ for (const panel of ["questions", "changes", "comment", "edits", "approve", "decline", "media", "metadata"]) {
87
+ const server = initial?.[panel] || { panel, version: 0, data: {} };
88
+ let entry = { ...server, status: "saved" };
89
+ try {
90
+ const raw = localStorage.getItem(key(id, panel));
91
+ const local = raw ? JSON.parse(raw) : null;
92
+ if (local?.dirty && local.expires > Date.now() && !same(local.data, server.data)) {
93
+ entry = { ...local, local: true, status: local.version === server.version ? "saving" : "conflict" };
94
+ } else localStorage.removeItem(key(id, panel));
95
+ } catch {
96
+ }
97
+ next[panel] = entry;
98
+ }
99
+ entries.current = next;
100
+ redraw();
101
+ for (const [panel, entry] of Object.entries(next)) if (entry.dirty && entry.status !== "conflict") void flushRef.current(panel).catch(() => {
102
+ });
103
+ const retry = () => {
104
+ for (const [panel, entry] of Object.entries(entries.current)) if (entry.dirty && entry.status !== "conflict") void flushRef.current(panel).catch(() => {
105
+ });
106
+ };
107
+ window.addEventListener("online", retry);
108
+ const interval = setInterval(retry, 15e3);
109
+ return () => {
110
+ active.current = "";
111
+ Object.values(timers.current).forEach(clearTimeout);
112
+ flights.current = {};
113
+ window.removeEventListener("online", retry);
114
+ clearInterval(interval);
115
+ };
116
+ }, [session, revision]);
117
+ function set(panel, data) {
118
+ if (!active.current) return;
119
+ const old = entries.current[panel] || { panel, version: 0, data: {} };
120
+ const entry = { ...old, data, dirty: true, status: old.status === "conflict" ? "conflict" : "saving" };
121
+ entries.current[panel] = entry;
122
+ backup(active.current, panel, entry);
123
+ redraw();
124
+ clearTimeout(timers.current[panel]);
125
+ if (entry.status !== "conflict") timers.current[panel] = setTimeout(() => void flushRef.current(panel).catch(() => {
126
+ }), 800);
127
+ }
128
+ async function resolve(panel, keepLocal) {
129
+ const latest = await requester.current();
130
+ if (latest.revision.id !== revision) throw Error("The newsroom shared a new preview. Reload before continuing; your previous writing is saved separately.");
131
+ const server = latest.response_drafts?.[panel] || { panel, version: 0, data: {} };
132
+ const data = keepLocal ? entries.current[panel].data : server.data;
133
+ entries.current[panel] = { ...server, data, dirty: keepLocal, status: keepLocal ? "saving" : "saved" };
134
+ backup(active.current, panel, entries.current[panel]);
135
+ redraw();
136
+ if (keepLocal) await flush(panel);
137
+ }
138
+ function consumed(saved) {
139
+ clearTimeout(timers.current[saved.panel]);
140
+ entries.current[saved.panel] = { ...saved, dirty: false, status: "saved" };
141
+ backup(active.current, saved.panel, entries.current[saved.panel]);
142
+ redraw();
143
+ }
144
+ return {
145
+ entries: entries.current,
146
+ set,
147
+ flush,
148
+ resolve,
149
+ consumed,
150
+ async discard(panel) {
151
+ set(panel, {});
152
+ await flush(panel);
153
+ },
154
+ async receipt(panel) {
155
+ await flush(panel);
156
+ const entry = entries.current[panel];
157
+ return entry?.version ? { panel, version: entry.version } : void 0;
158
+ }
159
+ };
160
+ }
161
+ async function stored(key, value) {
162
+ const db = await new Promise((resolve, reject) => {
163
+ const request = indexedDB.open("story-review-pending-media", 1);
164
+ request.onupgradeneeded = () => request.result.createObjectStore("photos");
165
+ request.onsuccess = () => resolve(request.result);
166
+ request.onerror = () => reject(request.error);
167
+ });
168
+ try {
169
+ return await new Promise((resolve, reject) => {
170
+ const tx = db.transaction("photos", value === void 0 ? "readonly" : "readwrite");
171
+ const store = tx.objectStore("photos");
172
+ const request = value === void 0 ? store.get(key) : value === null ? store.delete(key) : store.put(value, key);
173
+ tx.oncomplete = () => resolve(value === void 0 ? request.result : void 0);
174
+ tx.onerror = () => reject(tx.error);
175
+ tx.onabort = () => reject(tx.error);
176
+ });
177
+ } finally {
178
+ db.close();
179
+ }
180
+ }
181
+ function usePendingPhoto(key) {
182
+ const [photo, setPhoto] = react.useState(null);
183
+ const [saved, setSaved] = react.useState(false);
184
+ const [preview, setPreview] = react.useState("");
185
+ const generation = react.useRef(0);
186
+ const chain = react.useRef(Promise.resolve());
187
+ react.useEffect(() => {
188
+ const at = ++generation.current;
189
+ setPhoto(null);
190
+ setSaved(false);
191
+ if (key) void stored(key).then((value) => {
192
+ if (generation.current !== at) return;
193
+ if (value && value.expires > Date.now()) {
194
+ setPhoto(value);
195
+ setSaved(true);
196
+ } else if (value) void stored(key, null).catch(() => {
197
+ });
198
+ }).catch(() => {
199
+ });
200
+ return () => {
201
+ generation.current++;
202
+ };
203
+ }, [key]);
204
+ react.useEffect(() => {
205
+ if (!photo?.file.type.startsWith("image/")) {
206
+ setPreview("");
207
+ return;
208
+ }
209
+ const url = URL.createObjectURL(photo.file);
210
+ setPreview(url);
211
+ return () => URL.revokeObjectURL(url);
212
+ }, [photo?.file]);
213
+ async function select(file, replacement = null) {
214
+ if (!key) return;
215
+ const at = ++generation.current;
216
+ const value = file ? { file, replacement, expires: Date.now() + 7 * 864e5 } : null;
217
+ setPhoto(value);
218
+ setSaved(false);
219
+ const operation = chain.current.catch(() => {
220
+ }).then(() => stored(key, value));
221
+ chain.current = operation;
222
+ try {
223
+ await operation;
224
+ if (generation.current === at) setSaved(!!value);
225
+ } catch {
226
+ if (generation.current === at) setSaved(false);
227
+ }
228
+ }
229
+ return { photo, saved, preview, select };
230
+ }
231
+
7
232
  // src/story-diff.ts
8
233
  function proseDiff(before, after) {
9
234
  const tokens = (s) => s.match(/\s+|[\p{L}\p{N}\p{M}]+|[^\s]/gu) ?? [];
@@ -71,16 +296,47 @@ function StoryReview({
71
296
  publicationFooter
72
297
  }) {
73
298
  const base = `/api/news/review/${encodeURIComponent(sessionId)}`;
74
- 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("");
75
- 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");
299
+ const [review, setReview] = react.useState(null), [error, setError] = react.useState(""), [notice, setNotice] = react.useState(""), [busy, setBusy] = react.useState(false), [panel, setPanelState] = react.useState("article"), [verification, setVerification] = react.useState(false), [code, setCode] = react.useState("");
300
+ const [shareUrl, setShareUrl] = react.useState(""), [canShare, setCanShare] = react.useState(false);
301
+ const [contributorName, setContributorName] = react.useState(""), [contributorEmail, setContributorEmail] = react.useState("");
302
+ const [identityStep, setIdentityStep] = react.useState("name"), [requestedPanel, setRequestedPanel] = react.useState("comment");
303
+ function setPanel(next) {
304
+ if (review?.identity_required && !["article", "history", "identity", "share"].includes(next)) {
305
+ setRequestedPanel(next);
306
+ setPanelState("identity");
307
+ } else setPanelState(next);
308
+ }
309
+ const identity = review?.session_id || sessionId;
310
+ const [rights, setRights] = react.useState(false), [compare, setCompare] = react.useState(""), [compareTo, setCompareTo] = react.useState(""), [editingMedia, setEditingMedia] = react.useState(""), [commentTarget, setCommentTarget] = react.useState("STORY");
311
+ const pendingPhoto = usePendingPhoto(review && !review.identity_required ? `${identity}:${review.revision.id}` : void 0);
312
+ const file = pendingPhoto.photo?.file || null;
313
+ const galleryRef = react.useRef(null);
314
+ const cameraRef = react.useRef(null);
315
+ const errorRef = react.useRef(null);
316
+ react.useEffect(() => {
317
+ if (error) {
318
+ errorRef.current?.scrollIntoView({ block: "center" });
319
+ errorRef.current?.focus({ preventScroll: true });
320
+ }
321
+ }, [error]);
322
+ const noticeRef = react.useRef(null);
323
+ react.useEffect(() => {
324
+ if (notice) {
325
+ noticeRef.current?.scrollIntoView({ block: "center" });
326
+ noticeRef.current?.focus({ preventScroll: true });
327
+ }
328
+ }, [notice]);
76
329
  const responseRef = react.useRef(null);
77
330
  const thanksRef = react.useRef(null);
78
331
  const [moreQuestions, setMoreQuestions] = react.useState(false);
79
- const [correctionKind, setCorrectionKind] = react.useState("Correct a detail");
80
332
  const [replacementPhoto, setReplacementPhoto] = react.useState(null);
333
+ const photoToReplace = pendingPhoto.photo?.replacement || replacementPhoto;
81
334
  function openMedia(photo = null) {
82
- setReplacementPhoto(photo);
83
- setFile(null);
335
+ setReplacementPhoto(file ? pendingPhoto.photo?.replacement || null : photo);
336
+ if (file) {
337
+ setPanel("media");
338
+ return;
339
+ }
84
340
  setRights(false);
85
341
  setPanel("media");
86
342
  }
@@ -93,15 +349,32 @@ function StoryReview({
93
349
  responseRef.current?.focus({ preventScroll: true });
94
350
  }
95
351
  }, [panel]);
96
- const [mediaInfo, setMediaInfo] = react.useState({
97
- caption: "",
98
- who_is_pictured: "",
99
- creator: "",
100
- when_taken: "",
101
- where_taken: "",
102
- credit: "",
103
- alt: ""
104
- });
352
+ const emptyMediaInfo = { caption: "", who_is_pictured: "", creator: "", when_taken: "", where_taken: "", credit: "", alt: "" };
353
+ const drafts = useReviewDrafts(identity, review?.identity_required ? void 0 : review?.revision.id, review?.response_drafts, request);
354
+ const currentDraft = drafts.entries[panel];
355
+ const message = currentDraft?.data.message || "";
356
+ const answers = drafts.entries.questions?.data.answers || {};
357
+ const document = drafts.entries.edits?.data.document || review?.revision.document || null;
358
+ const correctionKind = drafts.entries.changes?.data.correctionKind || "Correct a detail";
359
+ const mediaPanel = panel === "metadata" ? "metadata" : "media";
360
+ const mediaInfo = { ...emptyMediaInfo, ...drafts.entries[mediaPanel]?.data.mediaInfo };
361
+ function setMessage(value) {
362
+ drafts.set(panel, value.trim() ? { ...currentDraft?.data, message: value, ...panel === "changes" ? { correctionKind } : {}, ...panel === "comment" ? { commentTarget: currentDraft?.data.commentTarget || commentTarget } : {} } : {});
363
+ }
364
+ function setAnswers(value) {
365
+ drafts.set("questions", Object.values(value).some((a) => a.trim()) ? { answers: value } : {});
366
+ }
367
+ function setDocument(value) {
368
+ drafts.set("edits", JSON.stringify(value) === JSON.stringify(review?.revision.document) ? {} : { document: value });
369
+ }
370
+ function setCorrectionKind(value) {
371
+ drafts.set("changes", { ...drafts.entries.changes?.data, correctionKind: value });
372
+ }
373
+ function setMediaInfo(value) {
374
+ drafts.set(mediaPanel, Object.values(value).some((v) => v.trim()) ? { mediaInfo: value, editingMedia: drafts.entries.metadata?.data.editingMedia || editingMedia } : {});
375
+ }
376
+ const pending = Object.entries(drafts.entries).filter(([, entry]) => hasDraft(entry.data));
377
+ const draftLabel = (name) => ({ questions: "your answers", changes: "your correction", media: "your photograph details", metadata: "your caption changes", edits: "your wording changes", comment: "your comment", decline: "your response", approve: "your approval message" })[name] || "your response";
105
378
  async function request(path = "", body) {
106
379
  const form = body instanceof FormData;
107
380
  const r = await fetch(base + path, {
@@ -113,9 +386,7 @@ function StoryReview({
113
386
  });
114
387
  const result = await r.json();
115
388
  if (!r.ok || result.success === false)
116
- throw new Error(
117
- result.error?.message || "This private review is unavailable. Please contact the newsroom."
118
- );
389
+ throw Object.assign(new Error(result.error?.message || "This private review is unavailable. Please contact the newsroom."), { status: r.status });
119
390
  return result.data ?? result;
120
391
  }
121
392
  async function load() {
@@ -126,17 +397,29 @@ function StoryReview({
126
397
  } else {
127
398
  setVerification(false);
128
399
  setReview(data);
129
- setDocument(data.revision.document);
130
400
  }
131
401
  }
132
402
  react.useEffect(() => {
133
403
  let active = true;
134
404
  void (async () => {
135
405
  try {
136
- const token = window.location.hash.slice(1);
137
- if (token) {
138
- await request("/session", { token });
139
- window.history.replaceState(null, "", window.location.pathname);
406
+ const token = new URL(window.location.href).searchParams.get("key") || (/^[a-f0-9]{64}$/.test(window.location.hash.slice(1)) ? window.location.hash.slice(1) : void 0);
407
+ let exchange = {};
408
+ try {
409
+ exchange = await request("/session", token ? { token } : {});
410
+ } catch (e) {
411
+ if (token) throw e;
412
+ }
413
+ const address = exchange.share_url || (token ? `/review/${encodeURIComponent(sessionId)}?key=${encodeURIComponent(token)}` : void 0);
414
+ if (address) {
415
+ const canonical = new URL(address, window.location.origin);
416
+ if (canonical.origin === window.location.origin) {
417
+ window.history.replaceState(null, "", canonical.pathname + canonical.search);
418
+ if (active) {
419
+ setShareUrl(canonical.href);
420
+ setCanShare(exchange.can_share === true);
421
+ }
422
+ }
140
423
  }
141
424
  const data = await request();
142
425
  if (active) {
@@ -146,7 +429,6 @@ function StoryReview({
146
429
  } else {
147
430
  setVerification(false);
148
431
  setReview(data);
149
- setDocument(data.revision.document);
150
432
  }
151
433
  }
152
434
  } catch (e) {
@@ -158,17 +440,40 @@ function StoryReview({
158
440
  active = false;
159
441
  };
160
442
  }, [sessionId]);
443
+ async function sharePrivateLink() {
444
+ if (!shareUrl) return;
445
+ if (navigator.share) {
446
+ try {
447
+ await navigator.share({ title: "Private story preview", url: shareUrl });
448
+ return;
449
+ } catch (e) {
450
+ if (e.name === "AbortError") return;
451
+ }
452
+ }
453
+ setPanel("share");
454
+ }
161
455
  async function send(action, success) {
162
- if (!review) return;
456
+ if (!review || busy) return;
457
+ if (action.action === "APPROVE" && file) {
458
+ setNotice("Your photo is not uploaded yet. Upload it or remove the selection before approving.");
459
+ setPanel("media");
460
+ return;
461
+ }
163
462
  setBusy(true);
164
463
  setError("");
165
464
  setNotice("");
166
465
  try {
167
- await request("", { revision_id: review.revision.id, ...action });
168
- await load();
466
+ const actionPanel = { ANSWER_QUESTIONS: "questions", REQUEST_CHANGES: "changes", COMMENT: "comment", PROPOSE_EDITS: "edits", EDIT_MEDIA_METADATA: "metadata", APPROVE: "approve", DECLINE: "decline" }[String(action.action)];
467
+ const receipt = actionPanel ? await drafts.receipt(actionPanel) : void 0;
468
+ const sent = await request("", { revision_id: review.revision.id, ...action, ...receipt ? { draft: receipt } : {} });
469
+ if (sent.cleared_draft) drafts.consumed(sent.cleared_draft);
470
+ try {
471
+ await load();
472
+ } catch {
473
+ success += " Refresh to see the latest preview.";
474
+ }
169
475
  setNotice(success);
170
476
  setPanel("article");
171
- setMessage("");
172
477
  } catch (e) {
173
478
  setError(
174
479
  e instanceof Error ? e.message : "Could not save your response."
@@ -189,12 +494,30 @@ function StoryReview({
189
494
  }
190
495
  }, [approved, finalEditor]);
191
496
  const approve = () => {
497
+ if (pending.length || file) {
498
+ setNotice("You have a response in progress. Send it or discard it, then return to approve the story.");
499
+ setPanel(file ? "media" : pending[0][0]);
500
+ return;
501
+ }
192
502
  if (celebration && finalEditor && !review?.questions.some((q) => q.required))
193
503
  void send({ action: "APPROVE", message: null }, approvalThanks);
194
504
  else setPanel("approve");
195
505
  };
506
+ function choosePhoto(selected2) {
507
+ if (!selected2) return;
508
+ const photo = ["image/jpeg", "image/png", "image/webp"].includes(selected2.type);
509
+ const video = ["video/mp4", "video/webm"].includes(selected2.type);
510
+ if (!photo && !video || selected2.size > (photo ? 12 : 25) * 1024 * 1024) {
511
+ setError("Please choose a JPEG, PNG or WebP photo up to 12 MB, or an MP4/WebM video up to 25 MB. For an HEIC photo, export or share it as JPEG first.");
512
+ return;
513
+ }
514
+ setError("");
515
+ setRights(false);
516
+ void pendingPhoto.select(selected2, photoToReplace);
517
+ }
196
518
  async function upload() {
197
- if (!review || !file || !rights) return;
519
+ if (!review || !file || !rights || busy) return;
520
+ const replacementPhoto2 = pendingPhoto.photo?.replacement;
198
521
  setBusy(true);
199
522
  setError("");
200
523
  try {
@@ -206,7 +529,7 @@ function StoryReview({
206
529
  );
207
530
  const received = await request("/media", form);
208
531
  let uploadNotice = "Media received privately. The newsroom will review it before publication.";
209
- if (replacementPhoto) {
532
+ if (replacementPhoto2) {
210
533
  uploadNotice = "Your replacement photo has been sent to the newsroom. The current photo will stay in this preview until we prepare an updated draft.";
211
534
  if (can("COMMENT")) {
212
535
  try {
@@ -216,7 +539,7 @@ function StoryReview({
216
539
  message: "Please use my newly uploaded photograph in place of this photo.",
217
540
  target: {
218
541
  kind: "PHOTO",
219
- reference: replacementPhoto.contribution_id,
542
+ reference: replacementPhoto2.contribution_id,
220
543
  replacement_contribution_id: received.id
221
544
  }
222
545
  });
@@ -227,8 +550,17 @@ function StoryReview({
227
550
  uploadNotice = "Your photo was uploaded privately for newsroom review. The newsroom will select the photo for the next preview.";
228
551
  }
229
552
  }
230
- await load();
231
- setFile(null);
553
+ await pendingPhoto.select(null);
554
+ try {
555
+ await drafts.discard("media");
556
+ } catch {
557
+ uploadNotice += " Your saved caption details still need to be cleared; you do not need to upload the photo again.";
558
+ }
559
+ try {
560
+ await load();
561
+ } catch {
562
+ uploadNotice += " Refresh to see the received photo below.";
563
+ }
232
564
  setRights(false);
233
565
  setNotice(uploadNotice);
234
566
  setReplacementPhoto(null);
@@ -256,8 +588,8 @@ function StoryReview({
256
588
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: review?.publication.name || publicationName })
257
589
  ] }) }),
258
590
  /* @__PURE__ */ jsxRuntime.jsxs("main", { className: `sm-story-review${celebration ? " sm-review-celebration" : ""}`, children: [
259
- error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: "sm-review-alert", children: error }),
260
- notice && !(approved && finalEditor && notice === approvalThanks) && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: "sm-review-notice", children: notice }),
591
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { ref: errorRef, tabIndex: -1, role: "alert", className: "sm-review-alert", children: error }),
592
+ notice && !(approved && finalEditor && notice === approvalThanks) && /* @__PURE__ */ jsxRuntime.jsx("p", { ref: noticeRef, tabIndex: -1, role: "status", className: "sm-review-notice", children: notice }),
261
593
  verification && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "sm-review-form", children: [
262
594
  /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Verify your email" }),
263
595
  /* @__PURE__ */ jsxRuntime.jsx("p", { children: "The editor requires verification using the email address on your invitation. Your story remains private until verified." }),
@@ -324,8 +656,36 @@ function StoryReview({
324
656
  }
325
657
  )
326
658
  ] }),
659
+ !review && error && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
660
+ "Ask the person who invited you to use ",
661
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Share private preview" }),
662
+ " or copy the complete address from their open preview. You do not need an account to read it."
663
+ ] }),
327
664
  !review && !error && !verification && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Opening your private preview\u2026" }),
328
665
  review && document && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
666
+ canShare && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-sharing", children: [
667
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Private family preview. Share only with people you want to invite." }),
668
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => void sharePrivateLink(), children: "Share private preview" })
669
+ ] }),
670
+ review.participant && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-identity-status", children: [
671
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
672
+ "Contributing as ",
673
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: review.reviewer })
674
+ ] }),
675
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy || pending.length > 0 || !!file, onClick: async () => {
676
+ setBusy(true);
677
+ try {
678
+ await request("/leave", {});
679
+ setPanelState("article");
680
+ setIdentityStep("name");
681
+ await load();
682
+ } catch (e) {
683
+ setError(e.message);
684
+ } finally {
685
+ setBusy(false);
686
+ }
687
+ }, children: "Not you?" })
688
+ ] }),
329
689
  celebration ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-intro", children: [
330
690
  review.revision.number > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
331
691
  /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Updated preview" }),
@@ -408,7 +768,10 @@ function StoryReview({
408
768
  ] }),
409
769
  celebration && /* @__PURE__ */ jsxRuntime.jsxs("section", { id: "review-options", className: "sm-review-welcome", children: [
410
770
  /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Help us make this just right" }),
411
- /* @__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." }),
771
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
772
+ "We prepared this announcement as a starting point so you don't have to write anything yourself. ",
773
+ can("APPROVE") ? "If everything looks right, you can simply approve it." : "Read it at your own pace, and let us know if anything needs a change."
774
+ ] }),
412
775
  /* @__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." }),
413
776
  can("ANSWER_QUESTIONS") && review.questions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
414
777
  "button",
@@ -436,6 +799,7 @@ function StoryReview({
436
799
  className: `sm-review-actions${celebration ? " sm-review-concierge-actions" : ""}${panel !== "article" ? " sm-review-actions-editing" : ""}`,
437
800
  "aria-label": "Review actions",
438
801
  children: [
802
+ !can("APPROVE") && can("COMMENT") && (review.identity_required || review.participant) && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("feedback"), children: "Looks good to me" }),
439
803
  can("APPROVE") && !review.published && /* @__PURE__ */ jsxRuntime.jsx(
440
804
  "button",
441
805
  {
@@ -460,7 +824,103 @@ function StoryReview({
460
824
  can("VIEW_SELECTED_HISTORY") && review.history.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("history"), children: "See previous previews" }),
461
825
  can("DOWNLOAD_PREVIEW") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => window.print(), children: "Print / save preview" })
462
826
  ] }),
463
- panel !== "article" && /* @__PURE__ */ jsxRuntime.jsxs(
827
+ panel === "article" && (pending.length > 0 || file) && /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "sm-review-resume", "aria-label": "Unfinished responses", children: [
828
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "You have a response in progress" }),
829
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Send your response when you're ready, then return here to review and approve the story." }),
830
+ pending.map(([name]) => /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => setPanel(name), children: [
831
+ "Continue ",
832
+ draftLabel(name)
833
+ ] }, name)),
834
+ file && !pending.some(([name]) => name === "media") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("media"), children: "Continue your photo upload" })
835
+ ] }),
836
+ panel === "share" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Share private preview", children: [
837
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Share with your family" }),
838
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Anyone with this link can read the private preview. To send feedback or photos, they will enter their own name and verify their email. No account is needed." }),
839
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "review-share-url", children: "Private preview link" }),
840
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: "review-share-url", readOnly: true, value: shareUrl, onFocus: (e) => e.currentTarget.select() }),
841
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: async () => {
842
+ try {
843
+ await navigator.clipboard.writeText(shareUrl);
844
+ setNotice("Private link copied. Paste it into your message to family.");
845
+ } catch {
846
+ setNotice("Select the link above, then choose Copy on your device.");
847
+ }
848
+ }, children: "Copy private link" }),
849
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("article"), children: "Back to the story" })
850
+ ] }),
851
+ panel === "identity" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Introduce yourself", children: [
852
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: identityStep === "code" ? "Check your email" : "Who is helping with the story?" }),
853
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your name will accompany your feedback for the newsroom. Your email stays private. No password or account is needed." }),
854
+ identityStep === "name" ? /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: async (e) => {
855
+ e.preventDefault();
856
+ setBusy(true);
857
+ setError("");
858
+ try {
859
+ await request("/join", { name: contributorName, email: contributorEmail });
860
+ await request("/verify", {});
861
+ setIdentityStep("code");
862
+ setCode("");
863
+ } catch (e2) {
864
+ setError(e2.message);
865
+ } finally {
866
+ setBusy(false);
867
+ }
868
+ }, children: [
869
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-name", children: "Your name" }),
870
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: "contributor-name", autoComplete: "name", value: contributorName, maxLength: 150, required: true, onChange: (e) => setContributorName(e.target.value) }),
871
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-email", children: "Your email" }),
872
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: "contributor-email", type: "email", autoComplete: "email", value: contributorEmail, maxLength: 320, required: true, onChange: (e) => setContributorEmail(e.target.value) }),
873
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "We'll email you a code to confirm it's you." }),
874
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: busy ? "Sending your code\u2026" : "Email me a code" })
875
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: async (e) => {
876
+ e.preventDefault();
877
+ setBusy(true);
878
+ setError("");
879
+ try {
880
+ await request("/verify", { code });
881
+ await load();
882
+ setCode("");
883
+ setPanelState(requestedPanel);
884
+ } catch (e2) {
885
+ setError(e2.message);
886
+ } finally {
887
+ setBusy(false);
888
+ }
889
+ }, children: [
890
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
891
+ "Enter the eight-digit code sent to ",
892
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: contributorEmail }),
893
+ ". It expires in ten minutes."
894
+ ] }),
895
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-code", children: "Email verification code" }),
896
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: "contributor-code", inputMode: "numeric", autoComplete: "one-time-code", pattern: "[0-9]{8}", maxLength: 8, value: code, required: true, onChange: (e) => setCode(e.target.value.replace(/\s/g, "")) }),
897
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: busy ? "Verifying\u2026" : "Verify and continue" }),
898
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: busy, onClick: async () => {
899
+ setBusy(true);
900
+ try {
901
+ await request("/verify", {});
902
+ setNotice("A new code was sent. Check your inbox and spam folder.");
903
+ } catch (e) {
904
+ setError(e.message);
905
+ } finally {
906
+ setBusy(false);
907
+ }
908
+ }, children: "Send a new code" }),
909
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: busy, onClick: () => setIdentityStep("name"), children: "Use a different email" })
910
+ ] }),
911
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Just read the story" })
912
+ ] }),
913
+ panel === "feedback" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Family feedback", children: [
914
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Looks good to you?" }),
915
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
916
+ "We'll send your feedback as ",
917
+ review.reviewer,
918
+ ". The newsroom will request approval from the designated family reviewer separately. This will not publish the story."
919
+ ] }),
920
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void send({ action: "COMMENT", message: "This draft looks good to me.", target: { kind: "STORY" } }, "Thank you. Your feedback has been sent to the newsroom. The story is still private."), children: "Send my feedback" }),
921
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Back to the story" })
922
+ ] }),
923
+ !["article", "identity", "share", "feedback"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(
464
924
  "section",
465
925
  {
466
926
  ref: responseRef,
@@ -469,356 +929,394 @@ function StoryReview({
469
929
  "aria-label": "Your response",
470
930
  children: [
471
931
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-form-heading", children: [
472
- /* @__PURE__ */ jsxRuntime.jsx("h2", { children: panel === "approve" ? "Approve this exact draft" : panel === "edits" ? "Propose changes" : panel === "media" ? replacementPhoto ? "Change photo" : "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" }),
932
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: panel === "approve" ? "Approve this exact draft" : panel === "edits" ? "Propose changes" : panel === "media" ? photoToReplace ? "Change photo" : "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" }),
473
933
  /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Close" })
474
934
  ] }),
475
- panel === "edits" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
476
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your suggestions will be reviewed by an editor. They do not replace the newsroom\u2019s draft." }),
477
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
478
- "Headline",
479
- /* @__PURE__ */ jsxRuntime.jsx(
480
- "input",
481
- {
482
- value: document.title,
483
- onChange: (e) => setDocument({ ...document, title: e.target.value })
484
- }
485
- )
486
- ] }),
487
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
488
- "Short introduction",
489
- /* @__PURE__ */ jsxRuntime.jsx(
490
- "input",
491
- {
492
- value: document.subtitle,
493
- onChange: (e) => setDocument({ ...document, subtitle: e.target.value })
494
- }
495
- )
496
- ] }),
497
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
498
- "Suggested article text",
935
+ panel !== "history" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-save-state", role: "status", "aria-live": "polite", children: [
936
+ currentDraft?.status === "conflict" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
937
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "A response was saved in another tab or device. Your writing here is still available. Choose which version to keep." }),
938
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void drafts.resolve(panel, true).catch((e) => setError(e.message)), children: "Keep what I wrote here" }),
939
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void drafts.resolve(panel, false).catch((e) => setError(e.message)), children: "Use the saved response" })
940
+ ] }) : currentDraft?.status === "offline" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
941
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: currentDraft.local ? "Saved on this device. Waiting for a connection to save online." : "Not saved yet. Keep this page open and try again." }),
942
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void drafts.flush(panel).catch((e) => setError(e.message)), children: "Try saving again" })
943
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: currentDraft?.status === "saving" ? "Saving your writing\u2026" : hasDraft(currentDraft?.data) ? "Saved \u2014 not sent yet. You can safely come back later." : "Your writing saves automatically. Use the Send button below when you're ready to share it with the newsroom." }),
944
+ hasDraft(currentDraft?.data) && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-quiet", disabled: busy, onClick: () => {
945
+ if (window.confirm("Discard this unsent response? Responses already sent will be kept.")) void drafts.discard(panel).catch((e) => setError(e.message));
946
+ }, children: "Discard this unsent response" })
947
+ ] }),
948
+ /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { disabled: busy, className: "sm-review-response-fields", children: [
949
+ panel === "edits" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
950
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your suggestions will be reviewed by an editor. They do not replace the newsroom\u2019s draft." }),
951
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
952
+ "Headline",
953
+ /* @__PURE__ */ jsxRuntime.jsx(
954
+ "input",
955
+ {
956
+ value: document.title,
957
+ onChange: (e) => setDocument({ ...document, title: e.target.value })
958
+ }
959
+ )
960
+ ] }),
961
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
962
+ "Short introduction",
963
+ /* @__PURE__ */ jsxRuntime.jsx(
964
+ "input",
965
+ {
966
+ value: document.subtitle,
967
+ onChange: (e) => setDocument({ ...document, subtitle: e.target.value })
968
+ }
969
+ )
970
+ ] }),
971
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
972
+ "Suggested article text",
973
+ /* @__PURE__ */ jsxRuntime.jsx(
974
+ "textarea",
975
+ {
976
+ rows: 16,
977
+ value: document.content_markdown,
978
+ onChange: (e) => setDocument({
979
+ ...document,
980
+ content_markdown: e.target.value
981
+ })
982
+ }
983
+ )
984
+ ] }),
499
985
  /* @__PURE__ */ jsxRuntime.jsx(
500
- "textarea",
986
+ "button",
501
987
  {
502
- rows: 16,
503
- value: document.content_markdown,
504
- onChange: (e) => setDocument({
505
- ...document,
506
- content_markdown: e.target.value
507
- })
988
+ disabled: busy,
989
+ onClick: () => void send(
990
+ {
991
+ action: "PROPOSE_EDITS",
992
+ title: document.title,
993
+ subtitle: document.subtitle,
994
+ content_markdown: document.content_markdown,
995
+ message
996
+ },
997
+ "Your proposed changes have been sent to the newsroom."
998
+ ),
999
+ children: "Send proposed changes"
508
1000
  }
509
1001
  )
510
1002
  ] }),
511
- /* @__PURE__ */ jsxRuntime.jsx(
512
- "button",
1003
+ panel === "questions" && /* @__PURE__ */ jsxRuntime.jsxs(
1004
+ "form",
513
1005
  {
514
- disabled: busy,
515
- onClick: () => void send(
516
- {
517
- action: "PROPOSE_EDITS",
518
- title: document.title,
519
- subtitle: document.subtitle,
520
- content_markdown: document.content_markdown,
521
- message
522
- },
523
- "Your proposed changes have been sent to the newsroom."
524
- ),
525
- children: "Send proposed changes"
526
- }
527
- )
528
- ] }),
529
- panel === "questions" && /* @__PURE__ */ jsxRuntime.jsxs(
530
- "form",
531
- {
532
- onSubmit: (e) => {
533
- e.preventDefault();
534
- void send(
535
- { action: "ANSWER_QUESTIONS", answers },
536
- "Thank you for sharing. We'll take care of turning your answers into an updated draft."
537
- );
538
- },
539
- children: [
540
- /* @__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." }),
541
- review.questions.filter((q, i) => moreQuestions || i < 5 || q.required).map((q) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
542
- q.prompt,
543
- q.required ? " (required)" : " (optional)",
1006
+ onSubmit: (e) => {
1007
+ e.preventDefault();
1008
+ void send(
1009
+ { action: "ANSWER_QUESTIONS", answers },
1010
+ "Thank you for sharing. We'll take care of turning your answers into an updated draft."
1011
+ );
1012
+ },
1013
+ children: [
1014
+ /* @__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." }),
1015
+ review.questions.filter((q, i) => moreQuestions || i < 5 || q.required).map((q) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1016
+ q.prompt,
1017
+ q.required ? " (required)" : " (optional)",
1018
+ /* @__PURE__ */ jsxRuntime.jsx(
1019
+ "textarea",
1020
+ {
1021
+ required: q.required,
1022
+ rows: 3,
1023
+ value: answers[q.id] || "",
1024
+ onChange: (e) => setAnswers({ ...answers, [q.id]: e.target.value })
1025
+ }
1026
+ )
1027
+ ] }, q.id)),
544
1028
  /* @__PURE__ */ jsxRuntime.jsx(
545
- "textarea",
1029
+ "button",
1030
+ {
1031
+ className: "sm-review-primary",
1032
+ disabled: busy || !Object.values(answers).some((a) => a.trim()),
1033
+ children: "Send These Answers"
1034
+ }
1035
+ ),
1036
+ !moreQuestions && review.questions.length > 5 && /* @__PURE__ */ jsxRuntime.jsx(
1037
+ "button",
546
1038
  {
547
- required: q.required,
548
- rows: 3,
549
- value: answers[q.id] || "",
550
- onChange: (e) => setAnswers({ ...answers, [q.id]: e.target.value })
1039
+ type: "button",
1040
+ onClick: () => setMoreQuestions(true),
1041
+ children: "Answer More Questions"
551
1042
  }
552
1043
  )
553
- ] }, q.id)),
1044
+ ]
1045
+ }
1046
+ ),
1047
+ panel === "metadata" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1048
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Caption and credit changes are proposals for the newsroom; historical captions remain intact." }),
1049
+ ["caption", "credit", "alt"].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1050
+ key,
554
1051
  /* @__PURE__ */ jsxRuntime.jsx(
555
- "button",
556
- {
557
- className: "sm-review-primary",
558
- disabled: busy || !Object.values(answers).some((a) => a.trim()),
559
- children: "Send These Answers"
560
- }
561
- ),
562
- !moreQuestions && review.questions.length > 5 && /* @__PURE__ */ jsxRuntime.jsx(
563
- "button",
1052
+ "input",
564
1053
  {
565
- type: "button",
566
- onClick: () => setMoreQuestions(true),
567
- children: "Answer More Questions"
1054
+ value: mediaInfo[key],
1055
+ onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
568
1056
  }
569
1057
  )
570
- ]
571
- }
572
- ),
573
- panel === "metadata" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
574
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Caption and credit changes are proposals for the newsroom; historical captions remain intact." }),
575
- ["caption", "credit", "alt"].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
576
- key,
1058
+ ] }, key)),
577
1059
  /* @__PURE__ */ jsxRuntime.jsx(
578
- "input",
1060
+ "button",
579
1061
  {
580
- value: mediaInfo[key],
581
- onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
1062
+ disabled: busy,
1063
+ onClick: () => void send(
1064
+ {
1065
+ action: "EDIT_MEDIA_METADATA",
1066
+ contribution_id: drafts.entries.metadata?.data.editingMedia || editingMedia,
1067
+ caption: mediaInfo.caption,
1068
+ credit: mediaInfo.credit,
1069
+ alt: mediaInfo.alt
1070
+ },
1071
+ "Your proposed media descriptions have been sent to the newsroom."
1072
+ ),
1073
+ children: "Propose caption / credit changes"
582
1074
  }
583
1075
  )
584
- ] }, key)),
585
- /* @__PURE__ */ jsxRuntime.jsx(
586
- "button",
1076
+ ] }),
1077
+ panel === "media" && /* @__PURE__ */ jsxRuntime.jsxs(
1078
+ "form",
587
1079
  {
588
- disabled: busy,
589
- onClick: () => void send(
590
- {
591
- action: "EDIT_MEDIA_METADATA",
592
- contribution_id: editingMedia,
593
- caption: mediaInfo.caption,
594
- credit: mediaInfo.credit,
595
- alt: mediaInfo.alt
596
- },
597
- "Your proposed media descriptions have been sent to the newsroom."
598
- ),
599
- children: "Propose caption / credit changes"
600
- }
601
- )
602
- ] }),
603
- panel === "media" && /* @__PURE__ */ jsxRuntime.jsxs(
604
- "form",
605
- {
606
- onSubmit: (e) => {
607
- e.preventDefault();
608
- void upload();
609
- },
610
- children: [
611
- replacementPhoto && /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Choose the photograph you'd like us to use instead. We'll review it and prepare an updated preview; the current photo stays in place until then." }),
612
- /* @__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." }),
613
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
614
- "Photograph or video",
615
- /* @__PURE__ */ jsxRuntime.jsx(
616
- "input",
617
- {
618
- type: "file",
619
- accept: "image/jpeg,image/png,image/webp,video/mp4,video/webm",
620
- required: true,
621
- onChange: (e) => setFile(e.target.files?.[0] || null)
622
- }
623
- )
624
- ] }),
625
- /* @__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." }),
626
- Object.entries({
627
- caption: "Caption (optional)",
628
- credit: "Photographer / credit (optional)"
629
- }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
630
- label,
631
- /* @__PURE__ */ jsxRuntime.jsx(
632
- "input",
633
- {
634
- value: mediaInfo[key],
635
- onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
636
- }
637
- )
638
- ] }, key)),
639
- /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "sm-review-media-details", children: [
640
- /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Add more photograph details (optional)" }),
1080
+ onSubmit: (e) => {
1081
+ e.preventDefault();
1082
+ void upload();
1083
+ },
1084
+ children: [
1085
+ photoToReplace && /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Choose the photograph you'd like us to use instead. We'll review it and prepare an updated preview; the current photo stays in place until then." }),
1086
+ /* @__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." }),
1087
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-photo-picker", children: [
1088
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "1. Choose a photo" }) }),
1089
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => galleryRef.current?.click(), children: "Choose from my photos" }),
1090
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => cameraRef.current?.click(), children: "Take a photo" }),
1091
+ /* @__PURE__ */ jsxRuntime.jsx("input", { ref: cameraRef, "aria-label": "Take a photo", type: "file", accept: "image/jpeg,image/png,image/webp", capture: "environment", hidden: true, onChange: (e) => choosePhoto(e.target.files?.[0] || null) }),
1092
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-native-file", children: [
1093
+ "Photograph or video",
1094
+ /* @__PURE__ */ jsxRuntime.jsx(
1095
+ "input",
1096
+ {
1097
+ ref: galleryRef,
1098
+ type: "file",
1099
+ accept: "image/jpeg,image/png,image/webp,video/mp4,video/webm",
1100
+ onChange: (e) => choosePhoto(e.target.files?.[0] || null)
1101
+ }
1102
+ )
1103
+ ] })
1104
+ ] }),
1105
+ file && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-selected-photo", children: [
1106
+ pendingPhoto.preview && /* @__PURE__ */ jsxRuntime.jsx("img", { src: pendingPhoto.preview, alt: "Your selected photograph" }),
1107
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: file.name }) }),
1108
+ /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: pendingPhoto.saved ? "Photo kept on this device \u2014 not uploaded yet." : "Photo selected \u2014 not uploaded yet. Keep this page open." }),
1109
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => {
1110
+ void pendingPhoto.select(null);
1111
+ setRights(false);
1112
+ }, children: "Remove selected photo" })
1113
+ ] }),
1114
+ /* @__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." }),
641
1115
  Object.entries({
642
- who_is_pictured: "Who is pictured?",
643
- creator: "Photographer / creator",
644
- when_taken: "When was it taken?",
645
- where_taken: "Where was it taken?",
646
- alt: "Describe the photograph for readers who cannot see it"
1116
+ caption: "Caption (optional)",
1117
+ credit: "Photographer / credit (optional)"
647
1118
  }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
648
1119
  label,
649
1120
  /* @__PURE__ */ jsxRuntime.jsx(
650
1121
  "input",
651
1122
  {
652
1123
  value: mediaInfo[key],
653
- onChange: (e) => setMediaInfo({
654
- ...mediaInfo,
655
- [key]: e.target.value
656
- })
1124
+ onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
657
1125
  }
658
1126
  )
659
- ] }, key))
660
- ] }),
661
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-check", children: [
1127
+ ] }, key)),
1128
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "sm-review-media-details", children: [
1129
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Add more photograph details (optional)" }),
1130
+ Object.entries({
1131
+ who_is_pictured: "Who is pictured?",
1132
+ creator: "Photographer / creator",
1133
+ when_taken: "When was it taken?",
1134
+ where_taken: "Where was it taken?",
1135
+ alt: "Describe the photograph for readers who cannot see it"
1136
+ }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1137
+ label,
1138
+ /* @__PURE__ */ jsxRuntime.jsx(
1139
+ "input",
1140
+ {
1141
+ value: mediaInfo[key],
1142
+ onChange: (e) => setMediaInfo({
1143
+ ...mediaInfo,
1144
+ [key]: e.target.value
1145
+ })
1146
+ }
1147
+ )
1148
+ ] }, key))
1149
+ ] }),
1150
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "2. Confirm permission" }) }),
1151
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-check", children: [
1152
+ /* @__PURE__ */ jsxRuntime.jsx(
1153
+ "input",
1154
+ {
1155
+ type: "checkbox",
1156
+ required: true,
1157
+ checked: rights,
1158
+ onChange: (e) => setRights(e.target.checked)
1159
+ }
1160
+ ),
1161
+ "I confirm that I have the right or permission to provide this media to ",
1162
+ review.publication.name,
1163
+ " for editorial publication."
1164
+ ] }),
1165
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `sm-review-send-bar${rights && file ? " is-ready" : ""}`, children: [
1166
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: busy ? "Uploading your photo. Please keep this page open\u2026" : !file ? "Choose a photo above to continue." : !rights ? "Tick the permission box above, then upload your photo." : "Ready. Tap Upload privately to send this photo to the newsroom." }),
1167
+ busy && /* @__PURE__ */ jsxRuntime.jsx("progress", { "aria-label": "Uploading photo" }),
1168
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-primary", disabled: busy || !rights || !file, children: busy ? "Uploading\u2026" : "Upload privately" })
1169
+ ] })
1170
+ ]
1171
+ }
1172
+ ),
1173
+ ["approve", "changes", "comment", "decline"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1174
+ panel === "approve" && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1175
+ "You are approving version ",
1176
+ review.revision.number,
1177
+ ". Changes to the article require a new approval.",
1178
+ 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."
1179
+ ] }),
1180
+ panel === "decline" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: ["FACT_CHECK", "CONTRIBUTOR_REVIEW"].includes(
1181
+ review.review_policy
1182
+ ) ? "You can decline this review. The newsroom may still publish its reporting." : "Declining participation prevents publication of this voluntary announcement." }),
1183
+ panel === "comment" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1184
+ "Comment on",
1185
+ /* @__PURE__ */ jsxRuntime.jsxs(
1186
+ "select",
1187
+ {
1188
+ value: currentDraft?.data.commentTarget || commentTarget,
1189
+ onChange: (e) => {
1190
+ setCommentTarget(e.target.value);
1191
+ if (message) drafts.set("comment", { ...currentDraft?.data, commentTarget: e.target.value });
1192
+ },
1193
+ children: [
1194
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "STORY", children: "Entire story" }),
1195
+ review.revision.document.content_markdown.split(/\n\s*\n/).slice(0, 200).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: `PARAGRAPH:${i + 1}`, children: [
1196
+ "Paragraph ",
1197
+ i + 1
1198
+ ] }, i)),
1199
+ review.revision.document.media.map((m, i) => /* @__PURE__ */ jsxRuntime.jsxs(
1200
+ "option",
1201
+ {
1202
+ value: `MEDIA:${m.contribution_id}`,
1203
+ children: [
1204
+ "Photo / caption ",
1205
+ i + 1
1206
+ ]
1207
+ },
1208
+ m.contribution_id
1209
+ )),
1210
+ review.questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: `QUESTION:${q.id}`, children: q.prompt }, q.id))
1211
+ ]
1212
+ }
1213
+ )
1214
+ ] }),
1215
+ panel === "changes" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1216
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1217
+ "What would you like to change?",
662
1218
  /* @__PURE__ */ jsxRuntime.jsx(
663
- "input",
1219
+ "select",
664
1220
  {
665
- type: "checkbox",
666
- required: true,
667
- checked: rights,
668
- onChange: (e) => setRights(e.target.checked)
1221
+ value: correctionKind,
1222
+ onChange: (e) => setCorrectionKind(e.target.value),
1223
+ children: [
1224
+ "Correct a detail",
1225
+ "Correct a name",
1226
+ "Correct a professional / education detail",
1227
+ "Remove something",
1228
+ "Rewrite something",
1229
+ "Add something",
1230
+ "General comment"
1231
+ ].map((kind) => /* @__PURE__ */ jsxRuntime.jsx("option", { children: kind }, kind))
669
1232
  }
670
- ),
671
- "I confirm that I have the right or permission to provide this media to ",
672
- review.publication.name,
673
- " for editorial publication."
1233
+ )
674
1234
  ] }),
675
- /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy || !rights || !file, children: "Upload privately" })
676
- ]
677
- }
678
- ),
679
- ["approve", "changes", "comment", "decline"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
680
- panel === "approve" && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
681
- "You are approving version ",
682
- review.revision.number,
683
- ". Changes to the article require a new approval.",
684
- 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."
685
- ] }),
686
- panel === "decline" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: ["FACT_CHECK", "CONTRIBUTOR_REVIEW"].includes(
687
- review.review_policy
688
- ) ? "You can decline this review. The newsroom may still publish its reporting." : "Declining participation prevents publication of this voluntary announcement." }),
689
- panel === "comment" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
690
- "Comment on",
691
- /* @__PURE__ */ jsxRuntime.jsxs(
692
- "select",
693
- {
694
- value: commentTarget,
695
- onChange: (e) => setCommentTarget(e.target.value),
696
- children: [
697
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "STORY", children: "Entire story" }),
698
- review.revision.document.content_markdown.split(/\n\s*\n/).slice(0, 200).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: `PARAGRAPH:${i + 1}`, children: [
699
- "Paragraph ",
700
- i + 1
701
- ] }, i)),
702
- review.revision.document.media.map((m, i) => /* @__PURE__ */ jsxRuntime.jsxs(
703
- "option",
704
- {
705
- value: `MEDIA:${m.contribution_id}`,
706
- children: [
707
- "Photo / caption ",
708
- i + 1
709
- ]
710
- },
711
- m.contribution_id
712
- )),
713
- review.questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: `QUESTION:${q.id}`, children: q.prompt }, q.id))
714
- ]
715
- }
716
- )
717
- ] }),
718
- panel === "changes" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1235
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "A sentence or two is enough. We'll take care of the wording." }),
1236
+ can("PROPOSE_EDITS") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("edits"), children: "Or edit the wording directly" })
1237
+ ] }),
719
1238
  /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
720
- "What would you like to change?",
1239
+ panel === "changes" ? "What should change?" : "Message (optional)",
721
1240
  /* @__PURE__ */ jsxRuntime.jsx(
722
- "select",
1241
+ "textarea",
723
1242
  {
724
- value: correctionKind,
725
- onChange: (e) => setCorrectionKind(e.target.value),
726
- children: [
727
- "Correct a detail",
728
- "Correct a name",
729
- "Correct a professional / education detail",
730
- "Remove something",
731
- "Rewrite something",
732
- "Add something",
733
- "General comment"
734
- ].map((kind) => /* @__PURE__ */ jsxRuntime.jsx("option", { children: kind }, kind))
1243
+ rows: 5,
1244
+ value: message,
1245
+ onChange: (e) => setMessage(e.target.value)
735
1246
  }
736
1247
  )
737
1248
  ] }),
738
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "A sentence or two is enough. We'll take care of the wording." }),
739
- can("PROPOSE_EDITS") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("edits"), children: "Or edit the wording directly" })
740
- ] }),
741
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
742
- panel === "changes" ? "What should change?" : "Message (optional)",
743
1249
  /* @__PURE__ */ jsxRuntime.jsx(
744
- "textarea",
1250
+ "button",
745
1251
  {
746
- rows: 5,
747
- value: message,
748
- onChange: (e) => setMessage(e.target.value)
1252
+ disabled: busy || panel === "changes" && !message.trim() || panel === "comment" && !message.trim(),
1253
+ onClick: () => void send(
1254
+ {
1255
+ action: panel === "approve" ? "APPROVE" : panel === "changes" ? "REQUEST_CHANGES" : panel === "decline" ? "DECLINE" : "COMMENT",
1256
+ message: panel === "changes" ? `${correctionKind}: ${message}` : message || null,
1257
+ ...panel === "comment" ? {
1258
+ target: {
1259
+ kind: (currentDraft?.data.commentTarget || commentTarget).split(":")[0],
1260
+ reference: (currentDraft?.data.commentTarget || commentTarget).split(":")[1] || null
1261
+ }
1262
+ } : {}
1263
+ },
1264
+ 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."
1265
+ ),
1266
+ children: panel === "approve" ? `Approve version ${review.revision.number}` : panel === "changes" ? "Send change request" : panel === "decline" ? "Prefer not to publish" : "Send comment"
749
1267
  }
750
1268
  )
751
1269
  ] }),
752
- /* @__PURE__ */ jsxRuntime.jsx(
753
- "button",
754
- {
755
- disabled: busy || panel === "changes" && !message.trim() || panel === "comment" && !message.trim(),
756
- onClick: () => void send(
1270
+ panel === "history" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1271
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Only revisions selected by the editor are visible here." }),
1272
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1273
+ "Compare from",
1274
+ /* @__PURE__ */ jsxRuntime.jsxs(
1275
+ "select",
757
1276
  {
758
- action: panel === "approve" ? "APPROVE" : panel === "changes" ? "REQUEST_CHANGES" : panel === "decline" ? "DECLINE" : "COMMENT",
759
- message: panel === "changes" ? `${correctionKind}: ${message}` : message || null,
760
- ...panel === "comment" ? {
761
- target: {
762
- kind: commentTarget.split(":")[0],
763
- reference: commentTarget.split(":")[1] || null
764
- }
765
- } : {}
766
- },
767
- 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."
768
- ),
769
- children: panel === "approve" ? `Approve version ${review.revision.number}` : panel === "changes" ? "Send change request" : panel === "decline" ? "Prefer not to publish" : "Send comment"
770
- }
771
- )
772
- ] }),
773
- panel === "history" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
774
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Only revisions selected by the editor are visible here." }),
775
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
776
- "Compare from",
777
- /* @__PURE__ */ jsxRuntime.jsxs(
778
- "select",
779
- {
780
- value: compare,
781
- onChange: (e) => setCompare(e.target.value),
782
- children: [
783
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Choose a shared revision" }),
784
- revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
1277
+ value: compare,
1278
+ onChange: (e) => setCompare(e.target.value),
1279
+ children: [
1280
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Choose a shared revision" }),
1281
+ revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
1282
+ "Version ",
1283
+ r.number
1284
+ ] }, r.id))
1285
+ ]
1286
+ }
1287
+ )
1288
+ ] }),
1289
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1290
+ "Compare to",
1291
+ /* @__PURE__ */ jsxRuntime.jsx(
1292
+ "select",
1293
+ {
1294
+ value: compareTo || review.revision.id,
1295
+ onChange: (e) => setCompareTo(e.target.value),
1296
+ children: revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
785
1297
  "Version ",
786
1298
  r.number
787
1299
  ] }, r.id))
788
- ]
789
- }
790
- )
791
- ] }),
792
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
793
- "Compare to",
794
- /* @__PURE__ */ jsxRuntime.jsx(
795
- "select",
796
- {
797
- value: compareTo || review.revision.id,
798
- onChange: (e) => setCompareTo(e.target.value),
799
- children: revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
800
- "Version ",
801
- r.number
802
- ] }, r.id))
803
- }
804
- )
805
- ] }),
806
- selected && selectedAfter && /* @__PURE__ */ jsxRuntime.jsx(ReviewDiff, { before: selected, after: selectedAfter }),
807
- /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
808
- /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Read selected revision" }),
809
- /* @__PURE__ */ jsxRuntime.jsx("h3", { children: selected?.document.title }),
810
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: selected?.document.subtitle }),
811
- /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: selected?.editorial }),
812
- /* @__PURE__ */ jsxRuntime.jsx(
813
- "div",
814
- {
815
- className: "sm-review-prose",
816
- dangerouslySetInnerHTML: {
817
- __html: selected?.content_html || ""
818
1300
  }
819
- }
820
- ),
821
- selected?.document.media.map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id))
1301
+ )
1302
+ ] }),
1303
+ selected && selectedAfter && /* @__PURE__ */ jsxRuntime.jsx(ReviewDiff, { before: selected, after: selectedAfter }),
1304
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
1305
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Read selected revision" }),
1306
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: selected?.document.title }),
1307
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: selected?.document.subtitle }),
1308
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: selected?.editorial }),
1309
+ /* @__PURE__ */ jsxRuntime.jsx(
1310
+ "div",
1311
+ {
1312
+ className: "sm-review-prose",
1313
+ dangerouslySetInnerHTML: {
1314
+ __html: selected?.content_html || ""
1315
+ }
1316
+ }
1317
+ ),
1318
+ selected?.document.media.map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id))
1319
+ ] })
822
1320
  ] })
823
1321
  ] })
824
1322
  ]
@@ -860,12 +1358,7 @@ function StoryReview({
860
1358
  {
861
1359
  onClick: () => {
862
1360
  setEditingMedia(c.id);
863
- setMediaInfo({
864
- ...mediaInfo,
865
- caption: c.payload.caption || "",
866
- credit: c.payload.credit || "",
867
- alt: c.payload.caption || ""
868
- });
1361
+ if (!hasDraft(drafts.entries.metadata?.data)) drafts.set("metadata", { editingMedia: c.id, mediaInfo: { ...emptyMediaInfo, caption: c.payload.caption || "", credit: c.payload.credit || "", alt: c.payload.alt || "" } });
869
1362
  setPanel("metadata");
870
1363
  },
871
1364
  children: "Suggest caption / credit changes"