@scalemule/news-publication 0.0.42 → 0.0.44

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.
@@ -308,6 +308,194 @@ function reviewEmailLink(email, title, text) {
308
308
  if (!email || !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test(email)) return void 0;
309
309
  return `mailto:${encodeURIComponent(email)}?subject=${encodeURIComponent(`Private story feedback: ${title}`)}&body=${encodeURIComponent(text)}`;
310
310
  }
311
+
312
+ // src/article-blocks.ts
313
+ function normalizeUuid(id) {
314
+ const clean = id.trim().toLowerCase().replace(/[^0-9a-f]/g, "");
315
+ if (clean.length === 32) {
316
+ return `${clean.slice(0, 8)}-${clean.slice(8, 12)}-${clean.slice(12, 16)}-${clean.slice(16, 20)}-${clean.slice(20)}`;
317
+ }
318
+ return id.trim().toLowerCase();
319
+ }
320
+ function escapeHtml(text) {
321
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
322
+ }
323
+ function sanitizeInlineMarkdown(raw) {
324
+ let text = raw.trim();
325
+ text = text.replace(/on\w+\s*=\s*(?:["'][^"']*["']|[^\s>]+)/gi, "");
326
+ text = text.replace(/javascript\s*:/gi, "");
327
+ text = escapeHtml(text);
328
+ text = text.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
329
+ text = text.replace(/__(.+?)__/g, "<strong>$1</strong>");
330
+ text = text.replace(/\*([^*]+?)\*/g, "<em>$1</em>");
331
+ text = text.replace(/_([^_]+?)_/g, "<em>$1</em>");
332
+ text = text.replace(/`([^`]+?)`/g, "<code>$1</code>");
333
+ text = text.replace(/\[([^\]]+)\]\(((?:https?:\/\/|mailto:)[^)]+)\)/g, (_m, label, url) => {
334
+ return `<a href="${escapeHtml(url)}" rel="noopener noreferrer nofollow">${label}</a>`;
335
+ });
336
+ return text;
337
+ }
338
+ function parseDocumentBlocks(doc) {
339
+ const mediaList = doc.media || [];
340
+ const mediaMap = /* @__PURE__ */ new Map();
341
+ for (const m of mediaList) {
342
+ mediaMap.set(normalizeUuid(m.file_id), m);
343
+ if (m.contribution_id) {
344
+ mediaMap.set(normalizeUuid(m.contribution_id), m);
345
+ }
346
+ }
347
+ if (Array.isArray(doc.blocks) && doc.blocks.length > 0) {
348
+ const validated = [];
349
+ for (const b of doc.blocks) {
350
+ if (b.type === "inline_media") {
351
+ const norm = normalizeUuid(b.media_id);
352
+ const resolved = mediaMap.get(norm);
353
+ if (resolved) {
354
+ validated.push({
355
+ type: "inline_media",
356
+ media_id: resolved.file_id,
357
+ placement: "inline"
358
+ });
359
+ }
360
+ } else {
361
+ validated.push(b);
362
+ }
363
+ }
364
+ return validated;
365
+ }
366
+ const md = (doc.content_markdown || "").trim();
367
+ if (!md) return [];
368
+ const rawParagraphs = md.split(/\n{2,}/);
369
+ const blocks = [];
370
+ for (let i = 0; i < rawParagraphs.length; i++) {
371
+ const p = rawParagraphs[i].trim();
372
+ if (!p) continue;
373
+ let matchedMediaId = null;
374
+ const directiveMatch = p.match(/^::inline_media\{id=["']?([0-9a-f-]{32,36})["']?\}/i) || p.match(/^<!--\s*inline_media:([0-9a-f-]{32,36})\s*-->/i);
375
+ if (directiveMatch) {
376
+ matchedMediaId = normalizeUuid(directiveMatch[1]);
377
+ if (!mediaMap.has(matchedMediaId)) {
378
+ continue;
379
+ }
380
+ } else {
381
+ const imgMatch = p.match(/^!\[([^\]]*)\]\(([^)]+)\)/);
382
+ if (imgMatch) {
383
+ const src = imgMatch[2];
384
+ const uuidMatch = src.match(/(?:media:|\/)([0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}|[0-9a-f]{32})(?:\/|$|\?)/i);
385
+ if (uuidMatch) {
386
+ const norm = normalizeUuid(uuidMatch[1]);
387
+ if (mediaMap.has(norm)) {
388
+ matchedMediaId = norm;
389
+ }
390
+ }
391
+ }
392
+ }
393
+ if (matchedMediaId && mediaMap.has(matchedMediaId)) {
394
+ const mediaItem = mediaMap.get(matchedMediaId);
395
+ blocks.push({
396
+ type: "inline_media",
397
+ media_id: mediaItem.file_id,
398
+ placement: "inline"
399
+ });
400
+ const lines2 = p.split("\n").filter((l) => l.trim().length > 0);
401
+ if (lines2.length > 1 && !lines2[0].startsWith("::") && !lines2[0].startsWith("<!--")) {
402
+ const remaining = lines2.slice(1).join("\n").trim();
403
+ const isEcho = mediaItem.artwork_title && remaining.toLowerCase().includes(mediaItem.artwork_title.toLowerCase());
404
+ if (!isEcho && remaining) {
405
+ blocks.push({
406
+ type: "paragraph",
407
+ html: sanitizeInlineMarkdown(remaining)
408
+ });
409
+ }
410
+ }
411
+ continue;
412
+ }
413
+ const headingMatch = p.match(/^(#{1,6})\s+(.+)$/);
414
+ if (headingMatch) {
415
+ blocks.push({
416
+ type: "heading",
417
+ level: headingMatch[1].length,
418
+ html: sanitizeInlineMarkdown(headingMatch[2])
419
+ });
420
+ continue;
421
+ }
422
+ if (/^(\*{3,}|-{3,})$/.test(p)) {
423
+ blocks.push({ type: "divider" });
424
+ continue;
425
+ }
426
+ if (p.startsWith(">")) {
427
+ const quoteText = p.split("\n").map((l) => l.replace(/^>\s?/, "")).join(" ");
428
+ blocks.push({
429
+ type: "quote",
430
+ html: sanitizeInlineMarkdown(quoteText)
431
+ });
432
+ continue;
433
+ }
434
+ const lines = p.split("\n");
435
+ const isUnordered = lines.every((l) => /^\s*[-*]\s+/.test(l));
436
+ const isOrdered = lines.every((l) => /^\s*\d+\.\s+/.test(l));
437
+ if ((isUnordered || isOrdered) && lines.length > 0) {
438
+ const items = lines.map(
439
+ (l) => sanitizeInlineMarkdown(l.replace(/^\s*(?:[-*]|\d+\.)\s+/, ""))
440
+ );
441
+ blocks.push({
442
+ type: "list",
443
+ ordered: isOrdered,
444
+ items
445
+ });
446
+ continue;
447
+ }
448
+ blocks.push({
449
+ type: "paragraph",
450
+ html: sanitizeInlineMarkdown(p)
451
+ });
452
+ }
453
+ return blocks;
454
+ }
455
+ function formatArtworkCaption(media) {
456
+ let title = media.artwork_title ? media.artwork_title.trim() : void 0;
457
+ let artist = media.artist_name ? media.artist_name.trim() : void 0;
458
+ let mediumAndDate;
459
+ if (media.medium && media.artwork_date) {
460
+ mediumAndDate = `${media.medium} (${media.artwork_date})`;
461
+ } else if (media.medium) {
462
+ mediumAndDate = media.medium;
463
+ } else if (media.artwork_date) {
464
+ mediumAndDate = media.artwork_date;
465
+ }
466
+ let credit;
467
+ if (media.photographer) {
468
+ credit = `Photo: ${media.photographer}`;
469
+ } else if (media.credit) {
470
+ credit = media.credit.startsWith("Photo:") ? media.credit : `Photo: ${media.credit}`;
471
+ }
472
+ let source = media.source ? `Source: ${media.source}` : void 0;
473
+ let captionText = !title && media.caption ? media.caption : void 0;
474
+ if (!title && media.caption) {
475
+ const lines = media.caption.split("\n").map((l) => l.trim()).filter(Boolean);
476
+ if (lines.length >= 2 && (lines[0].startsWith("\u201C") || lines[0].startsWith('"'))) {
477
+ title = lines[0].replace(/^[“"']|[”"']$/g, "").trim();
478
+ artist = lines[1];
479
+ if (lines[2] && !lines[2].startsWith("Source:")) {
480
+ mediumAndDate = lines[2];
481
+ }
482
+ for (const line of lines) {
483
+ if (line.startsWith("Source:")) {
484
+ source = line;
485
+ }
486
+ }
487
+ captionText = void 0;
488
+ }
489
+ }
490
+ return {
491
+ title,
492
+ artist,
493
+ mediumAndDate,
494
+ credit,
495
+ source,
496
+ captionText
497
+ };
498
+ }
311
499
  function EditorialNotices({ editorial }) {
312
500
  return editorial ? /* @__PURE__ */ jsxRuntime.jsxs("aside", { "aria-label": "Editorial notices", children: [
313
501
  editorial.content_kind && editorial.content_kind !== "EDITORIAL" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: human(editorial.content_kind) }),
@@ -399,15 +587,27 @@ function StoryReview({
399
587
  setRights(false);
400
588
  setPanel("media");
401
589
  }
590
+ function openCorrection(photo) {
591
+ const title = photo.artwork_title || photo.caption || "Photograph";
592
+ setPanel("changes");
593
+ drafts.set("changes", {
594
+ ...drafts.entries.changes?.data,
595
+ correctionKind: "Correct a detail",
596
+ message: drafts.entries.changes?.data.message || `Correction regarding \u201C${title}\u201D: `
597
+ });
598
+ }
402
599
  react.useEffect(() => {
403
600
  if (panel !== "article") {
404
- responseRef.current?.scrollIntoView({
405
- block: "start",
406
- behavior: window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth"
407
- });
408
- responseRef.current?.focus({ preventScroll: true });
601
+ responseRef.current?.focus();
602
+ const handleKeyDown = (e) => {
603
+ if (e.key === "Escape" && !busy) {
604
+ setPanel("article");
605
+ }
606
+ };
607
+ window.addEventListener("keydown", handleKeyDown);
608
+ return () => window.removeEventListener("keydown", handleKeyDown);
409
609
  }
410
- }, [panel]);
610
+ }, [panel, busy]);
411
611
  const emptyMediaInfo = { caption: "", who_is_pictured: "", creator: "", when_taken: "", where_taken: "", credit: "", alt: "" };
412
612
  const drafts = useReviewDrafts(identity, review?.identity_required ? void 0 : review?.revision.id, review?.response_drafts, request);
413
613
  const currentDraft = drafts.entries[panel];
@@ -776,11 +976,25 @@ function StoryReview({
776
976
  ] }),
777
977
  /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy || pending.length > 0 || !!file, onClick: async () => {
778
978
  setBusy(true);
979
+ setError("");
779
980
  try {
981
+ const prevReviewer = review.reviewer;
780
982
  await request("/leave", {});
983
+ setReader(null);
781
984
  setPanelState("article");
782
985
  setIdentityStep("name");
783
986
  await load();
987
+ if (readerAccountUrl) {
988
+ void fetch(readerAccountUrl, { credentials: "same-origin", cache: "no-store" }).then(async (res) => {
989
+ if (res.ok) {
990
+ const d = await res.json();
991
+ setReader(d.user || null);
992
+ } else {
993
+ setReader(null);
994
+ }
995
+ }).catch(() => setReader(null));
996
+ }
997
+ setNotice(`You are no longer contributing as ${prevReviewer}. You can review the draft or sign in with another account.`);
784
998
  } catch (e) {
785
999
  setError(e.message);
786
1000
  } finally {
@@ -830,6 +1044,7 @@ function StoryReview({
830
1044
  media: m,
831
1045
  base,
832
1046
  onChange: can("UPLOAD_MEDIA") ? () => openMedia(m) : void 0,
1047
+ onSuggestCorrection: can("REQUEST_CHANGES") ? () => openCorrection(m) : void 0,
833
1048
  disabled: busy
834
1049
  },
835
1050
  m.file_id
@@ -850,19 +1065,77 @@ function StoryReview({
850
1065
  ]
851
1066
  }
852
1067
  ),
853
- /* @__PURE__ */ jsxRuntime.jsx(
854
- "div",
855
- {
856
- className: "sm-review-prose",
857
- dangerouslySetInnerHTML: { __html: review.revision.content_html }
1068
+ (() => {
1069
+ const blocks = parseDocumentBlocks({
1070
+ content_markdown: review.revision.document.content_markdown,
1071
+ media: review.revision.document.media,
1072
+ blocks: review.revision.document.blocks
1073
+ });
1074
+ const mediaMap = /* @__PURE__ */ new Map();
1075
+ for (const m of review.revision.document.media) {
1076
+ mediaMap.set(m.file_id, m);
1077
+ if (m.contribution_id) mediaMap.set(m.contribution_id, m);
858
1078
  }
859
- ),
860
- review.revision.document.media.filter((m) => m.position !== "hero").map((m) => /* @__PURE__ */ jsxRuntime.jsx(
1079
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-prose", children: blocks.length === 0 && review.revision.content_html ? /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: review.revision.content_html } }) : blocks.map((block, idx) => {
1080
+ switch (block.type) {
1081
+ case "paragraph":
1082
+ return /* @__PURE__ */ jsxRuntime.jsx(
1083
+ "p",
1084
+ {
1085
+ dangerouslySetInnerHTML: { __html: block.html }
1086
+ },
1087
+ idx
1088
+ );
1089
+ case "heading": {
1090
+ if (block.level === 1)
1091
+ return /* @__PURE__ */ jsxRuntime.jsx("h1", { dangerouslySetInnerHTML: { __html: block.html } }, idx);
1092
+ if (block.level === 2)
1093
+ return /* @__PURE__ */ jsxRuntime.jsx("h2", { dangerouslySetInnerHTML: { __html: block.html } }, idx);
1094
+ if (block.level === 3)
1095
+ return /* @__PURE__ */ jsxRuntime.jsx("h3", { dangerouslySetInnerHTML: { __html: block.html } }, idx);
1096
+ if (block.level === 4)
1097
+ return /* @__PURE__ */ jsxRuntime.jsx("h4", { dangerouslySetInnerHTML: { __html: block.html } }, idx);
1098
+ if (block.level === 5)
1099
+ return /* @__PURE__ */ jsxRuntime.jsx("h5", { dangerouslySetInnerHTML: { __html: block.html } }, idx);
1100
+ return /* @__PURE__ */ jsxRuntime.jsx("h6", { dangerouslySetInnerHTML: { __html: block.html } }, idx);
1101
+ }
1102
+ case "quote":
1103
+ return /* @__PURE__ */ jsxRuntime.jsxs("blockquote", { children: [
1104
+ /* @__PURE__ */ jsxRuntime.jsx("p", { dangerouslySetInnerHTML: { __html: block.html } }),
1105
+ block.attribution && /* @__PURE__ */ jsxRuntime.jsx("cite", { children: block.attribution })
1106
+ ] }, idx);
1107
+ case "list":
1108
+ return block.ordered ? /* @__PURE__ */ jsxRuntime.jsx("ol", { children: block.items.map((it, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { dangerouslySetInnerHTML: { __html: it } }, i)) }, idx) : /* @__PURE__ */ jsxRuntime.jsx("ul", { children: block.items.map((it, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { dangerouslySetInnerHTML: { __html: it } }, i)) }, idx);
1109
+ case "divider":
1110
+ return /* @__PURE__ */ jsxRuntime.jsx("hr", {}, idx);
1111
+ case "inline_media": {
1112
+ const m = mediaMap.get(block.media_id);
1113
+ if (!m) return null;
1114
+ return /* @__PURE__ */ jsxRuntime.jsx(
1115
+ PrivatePhoto,
1116
+ {
1117
+ media: m,
1118
+ base,
1119
+ onChange: m.kind !== "video" && can("UPLOAD_MEDIA") ? () => openMedia(m) : void 0,
1120
+ onSuggestCorrection: can("REQUEST_CHANGES") ? () => openCorrection(m) : void 0,
1121
+ disabled: busy,
1122
+ isInline: true
1123
+ },
1124
+ m.file_id
1125
+ );
1126
+ }
1127
+ default:
1128
+ return null;
1129
+ }
1130
+ }) });
1131
+ })(),
1132
+ review.revision.document.media.filter((m) => m.position === "gallery").map((m) => /* @__PURE__ */ jsxRuntime.jsx(
861
1133
  PrivatePhoto,
862
1134
  {
863
1135
  media: m,
864
1136
  base,
865
1137
  onChange: m.kind !== "video" && can("UPLOAD_MEDIA") ? () => openMedia(m) : void 0,
1138
+ onSuggestCorrection: can("REQUEST_CHANGES") ? () => openCorrection(m) : void 0,
866
1139
  disabled: busy
867
1140
  },
868
1141
  m.file_id
@@ -936,544 +1209,573 @@ function StoryReview({
936
1209
  ] }, name)),
937
1210
  file && !pending.some(([name]) => name === "media") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("media"), children: "Continue your photo upload" })
938
1211
  ] }),
939
- panel === "share" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Share private preview", children: [
940
- /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Share with your family" }),
941
- errorMessage,
942
- /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
943
- "Anyone with this link can read the private preview. ",
944
- renderSignIn ? "To contribute, they can continue with Google or use an email code." : "To send feedback or photos, they will enter their own name and verify their email. No account is needed."
945
- ] }),
946
- /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "review-share-url", children: "Private preview link" }),
947
- /* @__PURE__ */ jsxRuntime.jsx("input", { id: "review-share-url", readOnly: true, value: shareUrl, onFocus: (e) => e.currentTarget.select() }),
948
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: async () => {
949
- try {
950
- await navigator.clipboard.writeText(shareUrl);
951
- setNotice("Private link copied. Paste it into your message to family.");
952
- } catch {
953
- setNotice("Select the link above, then choose Copy on your device.");
954
- }
955
- }, children: "Copy private link" }),
956
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("article"), children: "Back to the story" })
957
- ] }),
958
- panel === "question-list" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Optional story questions", children: [
959
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-form-heading", children: [
960
- /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Tell us a little more" }),
961
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("article"), children: "Close" })
962
- ] }),
963
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Completely optional. Read the questions first, then answer online or copy them into an email. Even one answer helps." }),
964
- /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "sm-review-question-list", children: review.questions.filter((q, i) => moreQuestions || i < 5 || q.required).map((q) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: q.prompt }, q.id)) }),
965
- !moreQuestions && review.questions.length > 5 && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setMoreQuestions(true), children: "See all questions" }),
966
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-primary", onClick: () => setPanel("questions"), children: "Answer online" }),
967
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Sign in to save your answers as you go. You can return whenever it suits you." }),
968
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: async () => {
969
- try {
970
- await navigator.clipboard.writeText(questionText);
971
- setNotice("Questions copied. Paste them into an email and answer as many as you like.");
972
- } catch {
973
- setCopiedQuestions(true);
974
- }
975
- }, children: "Copy all questions" }),
976
- copiedQuestions && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
977
- "Copy these questions",
978
- /* @__PURE__ */ jsxRuntime.jsx("textarea", { readOnly: true, rows: 10, value: questionText, onFocus: (e) => e.currentTarget.select() })
979
- ] }),
980
- questionsEmail && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
981
- /* @__PURE__ */ jsxRuntime.jsx("a", { href: questionsEmail, children: "Answer by email" }),
982
- " \u2014 opens your email app with the questions ready."
983
- ] })
984
- ] }),
985
- panel === "identity" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Introduce yourself", children: [
986
- /* @__PURE__ */ jsxRuntime.jsx("h2", { children: identityStep === "code" ? "Check your email" : "Continue to help with the story" }),
987
- errorMessage,
988
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your name accompanies your feedback. Your email stays private." }),
989
- identityStep === "name" && renderSignIn && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
990
- readerLoading ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Checking your sign-in\u2026" }) : reader?.email_verified ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
991
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "sm-review-primary", disabled: busy, onClick: () => void connectReader(), children: [
992
- "Continue as ",
993
- reader.full_name || reader.email
994
- ] }),
995
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your reader account is ready. No email code is needed." })
996
- ] }) : renderSignIn(signInReturn),
997
- !useEmailCode && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-quiet", onClick: () => setUseEmailCode(true), children: "Use an email code instead" })
998
- ] }),
999
- identityStep === "name" && useEmailCode && /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: async (e) => {
1000
- e.preventDefault();
1001
- setBusy(true);
1002
- setError("");
1003
- try {
1004
- await request("/join", { name: contributorName, email: contributorEmail });
1005
- await request("/verify", {});
1006
- setIdentityStep("code");
1007
- setCode("");
1008
- } catch (e2) {
1009
- setError(e2.message);
1010
- } finally {
1011
- setBusy(false);
1012
- }
1013
- }, children: [
1014
- /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-name", children: "Your name" }),
1015
- /* @__PURE__ */ jsxRuntime.jsx("input", { id: "contributor-name", autoComplete: "name", value: contributorName, maxLength: 150, required: true, onChange: (e) => setContributorName(e.target.value) }),
1016
- /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-email", children: "Your email" }),
1017
- /* @__PURE__ */ jsxRuntime.jsx("input", { id: "contributor-email", type: "email", autoComplete: "email", value: contributorEmail, maxLength: 320, required: true, onChange: (e) => setContributorEmail(e.target.value) }),
1018
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "We'll email you a code to confirm it's you." }),
1019
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No password or account is needed for this option." }),
1020
- /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: busy ? "Sending your code\u2026" : "Email me a code" })
1021
- ] }),
1022
- identityStep === "code" && /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: async (e) => {
1023
- e.preventDefault();
1024
- setBusy(true);
1025
- setError("");
1026
- try {
1027
- await request("/verify", { code });
1028
- await load();
1029
- setCode("");
1030
- setPanelState(requestedPanel);
1031
- } catch (e2) {
1032
- setError(e2.message);
1033
- } finally {
1034
- setBusy(false);
1035
- }
1036
- }, children: [
1037
- /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1038
- "Enter the eight-digit code sent to ",
1039
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: contributorEmail }),
1040
- ". It expires in ten minutes."
1041
- ] }),
1042
- /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-code", children: "Email verification code" }),
1043
- /* @__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, "")) }),
1044
- /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: busy ? "Verifying\u2026" : "Verify and continue" }),
1045
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: busy, onClick: async () => {
1046
- setBusy(true);
1047
- try {
1048
- await request("/verify", {});
1049
- setNotice("A new code was sent. Check your inbox and spam folder.");
1050
- } catch (e) {
1051
- setError(e.message);
1052
- } finally {
1053
- setBusy(false);
1054
- }
1055
- }, children: "Send a new code" }),
1056
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: busy, onClick: () => setIdentityStep("name"), children: "Use a different email" })
1057
- ] }),
1058
- emailAlternative,
1059
- /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Just read the story" })
1060
- ] }),
1061
- panel === "feedback" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Family feedback", children: [
1062
- /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Looks good to you?" }),
1063
- errorMessage,
1064
- /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1065
- "We'll send your feedback as ",
1066
- review.reviewer,
1067
- ". The newsroom will request approval from the designated family reviewer separately. This will not publish the story."
1068
- ] }),
1069
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-primary", 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.", false), children: "Send my feedback" }),
1070
- /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Back to the story" })
1071
- ] }),
1072
- !["article", "identity", "share", "feedback", "question-list"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(
1073
- "section",
1212
+ panel !== "article" && /* @__PURE__ */ jsxRuntime.jsx(
1213
+ "div",
1074
1214
  {
1075
- ref: responseRef,
1076
- tabIndex: -1,
1077
- className: "sm-review-form",
1078
- "aria-label": "Your response",
1079
- children: [
1080
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-form-heading", children: [
1081
- /* @__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" }),
1082
- /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Close" })
1215
+ className: "sm-review-modal-backdrop",
1216
+ role: "dialog",
1217
+ "aria-modal": "true",
1218
+ onClick: (e) => {
1219
+ if (e.target === e.currentTarget && !busy) setPanel("article");
1220
+ },
1221
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-modal-container", children: [
1222
+ panel === "share" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Share private preview", children: [
1223
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-form-heading", children: [
1224
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Share with your family" }),
1225
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, type: "button", onClick: () => setPanel("article"), "aria-label": "Close", children: "Close" })
1226
+ ] }),
1227
+ errorMessage,
1228
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1229
+ "Anyone with this link can read the private preview. ",
1230
+ renderSignIn ? "To contribute, they can continue with Google or use an email code." : "To send feedback or photos, they will enter their own name and verify their email. No account is needed."
1231
+ ] }),
1232
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "review-share-url", children: "Private preview link" }),
1233
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: "review-share-url", readOnly: true, value: shareUrl, onFocus: (e) => e.currentTarget.select() }),
1234
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: async () => {
1235
+ try {
1236
+ await navigator.clipboard.writeText(shareUrl);
1237
+ setNotice("Private link copied. Paste it into your message to family.");
1238
+ } catch {
1239
+ setNotice("Select the link above, then choose Copy on your device.");
1240
+ }
1241
+ }, children: "Copy private link" }),
1242
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("article"), children: "Back to the story" })
1083
1243
  ] }),
1084
- errorMessage,
1085
- panel !== "history" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-save-state", role: "status", "aria-live": "polite", children: [
1086
- currentDraft?.status === "conflict" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1087
- /* @__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." }),
1088
- /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void drafts.resolve(panel, true).catch((e) => setError(e.message)), children: "Keep what I wrote here" }),
1089
- /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void drafts.resolve(panel, false).catch((e) => setError(e.message)), children: "Use the saved response" })
1090
- ] }) : currentDraft?.status === "offline" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1091
- /* @__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." }),
1092
- /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void drafts.flush(panel).catch((e) => setError(e.message)), children: "Try saving again" })
1093
- ] }) : /* @__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." }),
1094
- hasDraft(currentDraft?.data) && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-quiet", disabled: busy, onClick: () => {
1095
- if (window.confirm("Discard this unsent response? Responses already sent will be kept.")) void drafts.discard(panel).catch((e) => setError(e.message));
1096
- }, children: "Discard this unsent response" })
1244
+ panel === "question-list" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Optional story questions", children: [
1245
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-form-heading", children: [
1246
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Tell us a little more" }),
1247
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("article"), children: "Close" })
1248
+ ] }),
1249
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Completely optional. Read the questions first, then answer online or copy them into an email. Even one answer helps." }),
1250
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "sm-review-question-list", children: review.questions.filter((q, i) => moreQuestions || i < 5 || q.required).map((q) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: q.prompt }, q.id)) }),
1251
+ !moreQuestions && review.questions.length > 5 && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setMoreQuestions(true), children: "See all questions" }),
1252
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-primary", onClick: () => setPanel("questions"), children: "Answer online" }),
1253
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Sign in to save your answers as you go. You can return whenever it suits you." }),
1254
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: async () => {
1255
+ try {
1256
+ await navigator.clipboard.writeText(questionText);
1257
+ setNotice("Questions copied. Paste them into an email and answer as many as you like.");
1258
+ } catch {
1259
+ setCopiedQuestions(true);
1260
+ }
1261
+ }, children: "Copy all questions" }),
1262
+ copiedQuestions && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1263
+ "Copy these questions",
1264
+ /* @__PURE__ */ jsxRuntime.jsx("textarea", { readOnly: true, rows: 10, value: questionText, onFocus: (e) => e.currentTarget.select() })
1265
+ ] }),
1266
+ questionsEmail && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1267
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: questionsEmail, children: "Answer by email" }),
1268
+ " \u2014 opens your email app with the questions ready."
1269
+ ] })
1097
1270
  ] }),
1098
- /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { disabled: busy, className: "sm-review-response-fields", children: [
1099
- panel === "edits" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1100
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your suggestions will be reviewed by an editor. They do not replace the newsroom\u2019s draft." }),
1101
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1102
- "Headline",
1103
- /* @__PURE__ */ jsxRuntime.jsx(
1104
- "input",
1105
- {
1106
- value: document.title,
1107
- onChange: (e) => setDocument({ ...document, title: e.target.value })
1108
- }
1109
- )
1110
- ] }),
1111
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1112
- "Short introduction",
1113
- /* @__PURE__ */ jsxRuntime.jsx(
1114
- "input",
1115
- {
1116
- value: document.subtitle,
1117
- onChange: (e) => setDocument({ ...document, subtitle: e.target.value })
1118
- }
1119
- )
1120
- ] }),
1121
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1122
- "Suggested article text",
1123
- /* @__PURE__ */ jsxRuntime.jsx(
1124
- "textarea",
1125
- {
1126
- rows: 16,
1127
- value: document.content_markdown,
1128
- onChange: (e) => setDocument({
1129
- ...document,
1130
- content_markdown: e.target.value
1131
- })
1132
- }
1133
- )
1271
+ panel === "identity" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Introduce yourself", children: [
1272
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-form-heading", children: [
1273
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: identityStep === "code" ? "Check your email" : requestedPanel === "media" ? photoToReplace ? "Change photo" : "Contribute a photograph" : requestedPanel === "changes" ? "Suggest a change" : requestedPanel === "edits" ? "Propose changes" : requestedPanel === "comment" ? "Leave a comment" : "Continue to help with the story" }),
1274
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, type: "button", onClick: () => setPanel("article"), "aria-label": "Close", children: "Close" })
1275
+ ] }),
1276
+ errorMessage,
1277
+ photoToReplace && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-target-preview", style: { padding: "10px 14px", background: "#f8fafc", borderRadius: "8px", border: "1px solid #e2e8f0", margin: "10px 0 16px" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: "13px", color: "#334155" }, children: [
1278
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Replacing:" }),
1279
+ " ",
1280
+ photoToReplace.artwork_title ? `\u201C${photoToReplace.artwork_title}\u201D` : photoToReplace.caption || "Header photograph"
1281
+ ] }) }),
1282
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your name accompanies your feedback. Your email stays private." }),
1283
+ identityStep === "name" && renderSignIn && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1284
+ readerLoading ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: "Checking your sign-in\u2026" }) : reader?.email_verified ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1285
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "sm-review-primary", disabled: busy, onClick: () => void connectReader(), children: [
1286
+ "Continue as ",
1287
+ reader.full_name || reader.email
1288
+ ] }),
1289
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your reader account is ready. No email code is needed." })
1290
+ ] }) : renderSignIn(signInReturn),
1291
+ !useEmailCode && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-quiet", onClick: () => setUseEmailCode(true), children: "Use an email code instead" })
1292
+ ] }),
1293
+ identityStep === "name" && useEmailCode && /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: async (e) => {
1294
+ e.preventDefault();
1295
+ setBusy(true);
1296
+ setError("");
1297
+ try {
1298
+ await request("/join", { name: contributorName, email: contributorEmail });
1299
+ await request("/verify", {});
1300
+ setIdentityStep("code");
1301
+ setCode("");
1302
+ } catch (e2) {
1303
+ setError(e2.message);
1304
+ } finally {
1305
+ setBusy(false);
1306
+ }
1307
+ }, children: [
1308
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-name", children: "Your name" }),
1309
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: "contributor-name", autoComplete: "name", value: contributorName, maxLength: 150, required: true, onChange: (e) => setContributorName(e.target.value) }),
1310
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-email", children: "Your email" }),
1311
+ /* @__PURE__ */ jsxRuntime.jsx("input", { id: "contributor-email", type: "email", autoComplete: "email", value: contributorEmail, maxLength: 320, required: true, onChange: (e) => setContributorEmail(e.target.value) }),
1312
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "We'll email you a code to confirm it's you." }),
1313
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No password or account is needed for this option." }),
1314
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: busy ? "Sending your code\u2026" : "Email me a code" })
1315
+ ] }),
1316
+ identityStep === "code" && /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: async (e) => {
1317
+ e.preventDefault();
1318
+ setBusy(true);
1319
+ setError("");
1320
+ try {
1321
+ await request("/verify", { code });
1322
+ await load();
1323
+ setCode("");
1324
+ setPanelState(requestedPanel);
1325
+ } catch (e2) {
1326
+ setError(e2.message);
1327
+ } finally {
1328
+ setBusy(false);
1329
+ }
1330
+ }, children: [
1331
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1332
+ "Enter the eight-digit code sent to ",
1333
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: contributorEmail }),
1334
+ ". It expires in ten minutes."
1134
1335
  ] }),
1135
- /* @__PURE__ */ jsxRuntime.jsx(
1136
- "button",
1137
- {
1138
- disabled: busy,
1139
- onClick: () => void send(
1140
- {
1141
- action: "PROPOSE_EDITS",
1142
- title: document.title,
1143
- subtitle: document.subtitle,
1144
- content_markdown: document.content_markdown,
1145
- message
1146
- },
1147
- "Your proposed changes have been sent to the newsroom."
1148
- ),
1149
- children: "Send proposed changes"
1336
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-code", children: "Email verification code" }),
1337
+ /* @__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, "")) }),
1338
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: busy ? "Verifying\u2026" : "Verify and continue" }),
1339
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: busy, onClick: async () => {
1340
+ setBusy(true);
1341
+ try {
1342
+ await request("/verify", {});
1343
+ setNotice("A new code was sent. Check your inbox and spam folder.");
1344
+ } catch (e) {
1345
+ setError(e.message);
1346
+ } finally {
1347
+ setBusy(false);
1150
1348
  }
1151
- )
1349
+ }, children: "Send a new code" }),
1350
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: busy, onClick: () => setIdentityStep("name"), children: "Use a different email" })
1152
1351
  ] }),
1153
- panel === "questions" && /* @__PURE__ */ jsxRuntime.jsxs(
1154
- "form",
1155
- {
1156
- onSubmit: (e) => {
1157
- e.preventDefault();
1158
- void send(
1159
- { action: "ANSWER_QUESTIONS", answers },
1160
- "Thank you for sharing. We'll take care of turning your answers into an updated draft."
1161
- );
1162
- },
1163
- children: [
1164
- /* @__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." }),
1165
- review.questions.filter((q, i) => moreQuestions || i < 5 || q.required).map((q) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1166
- q.prompt,
1167
- q.required ? " (required)" : " (optional)",
1352
+ emailAlternative,
1353
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Just read the story" })
1354
+ ] }),
1355
+ panel === "feedback" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Family feedback", children: [
1356
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Looks good to you?" }),
1357
+ errorMessage,
1358
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1359
+ "We'll send your feedback as ",
1360
+ review.reviewer,
1361
+ ". The newsroom will request approval from the designated family reviewer separately. This will not publish the story."
1362
+ ] }),
1363
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-primary", 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.", false), children: "Send my feedback" }),
1364
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Back to the story" })
1365
+ ] }),
1366
+ !["article", "identity", "share", "feedback", "question-list"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(
1367
+ "section",
1368
+ {
1369
+ ref: responseRef,
1370
+ tabIndex: -1,
1371
+ className: "sm-review-form",
1372
+ "aria-label": "Your response",
1373
+ children: [
1374
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-form-heading", children: [
1375
+ /* @__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" }),
1376
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Close" })
1377
+ ] }),
1378
+ errorMessage,
1379
+ panel !== "history" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-save-state", role: "status", "aria-live": "polite", children: [
1380
+ currentDraft?.status === "conflict" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1381
+ /* @__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." }),
1382
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void drafts.resolve(panel, true).catch((e) => setError(e.message)), children: "Keep what I wrote here" }),
1383
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void drafts.resolve(panel, false).catch((e) => setError(e.message)), children: "Use the saved response" })
1384
+ ] }) : currentDraft?.status === "offline" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1385
+ /* @__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." }),
1386
+ /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => void drafts.flush(panel).catch((e) => setError(e.message)), children: "Try saving again" })
1387
+ ] }) : /* @__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." }),
1388
+ hasDraft(currentDraft?.data) && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-quiet", disabled: busy, onClick: () => {
1389
+ if (window.confirm("Discard this unsent response? Responses already sent will be kept.")) void drafts.discard(panel).catch((e) => setError(e.message));
1390
+ }, children: "Discard this unsent response" })
1391
+ ] }),
1392
+ /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { disabled: busy, className: "sm-review-response-fields", children: [
1393
+ panel === "edits" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1394
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your suggestions will be reviewed by an editor. They do not replace the newsroom\u2019s draft." }),
1395
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1396
+ "Headline",
1397
+ /* @__PURE__ */ jsxRuntime.jsx(
1398
+ "input",
1399
+ {
1400
+ value: document.title,
1401
+ onChange: (e) => setDocument({ ...document, title: e.target.value })
1402
+ }
1403
+ )
1404
+ ] }),
1405
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1406
+ "Short introduction",
1407
+ /* @__PURE__ */ jsxRuntime.jsx(
1408
+ "input",
1409
+ {
1410
+ value: document.subtitle,
1411
+ onChange: (e) => setDocument({ ...document, subtitle: e.target.value })
1412
+ }
1413
+ )
1414
+ ] }),
1415
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1416
+ "Suggested article text",
1417
+ /* @__PURE__ */ jsxRuntime.jsx(
1418
+ "textarea",
1419
+ {
1420
+ rows: 16,
1421
+ value: document.content_markdown,
1422
+ onChange: (e) => setDocument({
1423
+ ...document,
1424
+ content_markdown: e.target.value
1425
+ })
1426
+ }
1427
+ )
1428
+ ] }),
1168
1429
  /* @__PURE__ */ jsxRuntime.jsx(
1169
- "textarea",
1430
+ "button",
1170
1431
  {
1171
- required: q.required,
1172
- rows: 3,
1173
- value: answers[q.id] || "",
1174
- onChange: (e) => setAnswers({ ...answers, [q.id]: e.target.value })
1432
+ disabled: busy,
1433
+ onClick: () => void send(
1434
+ {
1435
+ action: "PROPOSE_EDITS",
1436
+ title: document.title,
1437
+ subtitle: document.subtitle,
1438
+ content_markdown: document.content_markdown,
1439
+ message
1440
+ },
1441
+ "Your proposed changes have been sent to the newsroom."
1442
+ ),
1443
+ children: "Send proposed changes"
1175
1444
  }
1176
1445
  )
1177
- ] }, q.id)),
1178
- /* @__PURE__ */ jsxRuntime.jsx(
1179
- "button",
1180
- {
1181
- className: "sm-review-primary",
1182
- disabled: busy || !Object.values(answers).some((a) => a.trim()),
1183
- children: "Send These Answers"
1184
- }
1185
- ),
1186
- !moreQuestions && review.questions.length > 5 && /* @__PURE__ */ jsxRuntime.jsx(
1187
- "button",
1446
+ ] }),
1447
+ panel === "questions" && /* @__PURE__ */ jsxRuntime.jsxs(
1448
+ "form",
1188
1449
  {
1189
- type: "button",
1190
- onClick: () => setMoreQuestions(true),
1191
- children: "Answer More Questions"
1450
+ onSubmit: (e) => {
1451
+ e.preventDefault();
1452
+ void send(
1453
+ { action: "ANSWER_QUESTIONS", answers },
1454
+ "Thank you for sharing. We'll take care of turning your answers into an updated draft."
1455
+ );
1456
+ },
1457
+ children: [
1458
+ /* @__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." }),
1459
+ review.questions.filter((q, i) => moreQuestions || i < 5 || q.required).map((q) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1460
+ q.prompt,
1461
+ q.required ? " (required)" : " (optional)",
1462
+ /* @__PURE__ */ jsxRuntime.jsx(
1463
+ "textarea",
1464
+ {
1465
+ required: q.required,
1466
+ rows: 3,
1467
+ value: answers[q.id] || "",
1468
+ onChange: (e) => setAnswers({ ...answers, [q.id]: e.target.value })
1469
+ }
1470
+ )
1471
+ ] }, q.id)),
1472
+ /* @__PURE__ */ jsxRuntime.jsx(
1473
+ "button",
1474
+ {
1475
+ className: "sm-review-primary",
1476
+ disabled: busy || !Object.values(answers).some((a) => a.trim()),
1477
+ children: "Send These Answers"
1478
+ }
1479
+ ),
1480
+ !moreQuestions && review.questions.length > 5 && /* @__PURE__ */ jsxRuntime.jsx(
1481
+ "button",
1482
+ {
1483
+ type: "button",
1484
+ onClick: () => setMoreQuestions(true),
1485
+ children: "Answer More Questions"
1486
+ }
1487
+ ),
1488
+ questionsEmail && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1489
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: questionsEmail, children: "Email these answers instead" }),
1490
+ ". Send them from your email app; your online draft stays saved."
1491
+ ] })
1492
+ ]
1192
1493
  }
1193
1494
  ),
1194
- questionsEmail && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1195
- /* @__PURE__ */ jsxRuntime.jsx("a", { href: questionsEmail, children: "Email these answers instead" }),
1196
- ". Send them from your email app; your online draft stays saved."
1197
- ] })
1198
- ]
1199
- }
1200
- ),
1201
- panel === "metadata" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1202
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Caption and credit changes are proposals for the newsroom; historical captions remain intact." }),
1203
- ["caption", "credit", "alt"].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1204
- key,
1205
- /* @__PURE__ */ jsxRuntime.jsx(
1206
- "input",
1207
- {
1208
- value: mediaInfo[key],
1209
- onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
1210
- }
1211
- )
1212
- ] }, key)),
1213
- /* @__PURE__ */ jsxRuntime.jsx(
1214
- "button",
1215
- {
1216
- disabled: busy,
1217
- onClick: () => void send(
1218
- {
1219
- action: "EDIT_MEDIA_METADATA",
1220
- contribution_id: drafts.entries.metadata?.data.editingMedia || editingMedia,
1221
- caption: mediaInfo.caption,
1222
- credit: mediaInfo.credit,
1223
- alt: mediaInfo.alt
1224
- },
1225
- "Your proposed media descriptions have been sent to the newsroom."
1226
- ),
1227
- children: "Propose caption / credit changes"
1228
- }
1229
- )
1230
- ] }),
1231
- panel === "media" && /* @__PURE__ */ jsxRuntime.jsxs(
1232
- "form",
1233
- {
1234
- onSubmit: (e) => {
1235
- e.preventDefault();
1236
- void upload();
1237
- },
1238
- children: [
1239
- 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." }),
1240
- /* @__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." }),
1241
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-photo-picker", children: [
1242
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "1. Choose a photo" }) }),
1243
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => galleryRef.current?.click(), children: "Choose from my photos" }),
1244
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => cameraRef.current?.click(), children: "Take a photo" }),
1245
- /* @__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) }),
1246
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-native-file", children: [
1247
- "Photograph or video",
1495
+ panel === "metadata" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1496
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Caption and credit changes are proposals for the newsroom; historical captions remain intact." }),
1497
+ ["caption", "credit", "alt"].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1498
+ key,
1248
1499
  /* @__PURE__ */ jsxRuntime.jsx(
1249
1500
  "input",
1250
1501
  {
1251
- ref: galleryRef,
1252
- type: "file",
1253
- accept: "image/jpeg,image/png,image/webp,video/mp4,video/webm",
1254
- onChange: (e) => choosePhoto(e.target.files?.[0] || null)
1502
+ value: mediaInfo[key],
1503
+ onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
1255
1504
  }
1256
1505
  )
1257
- ] })
1258
- ] }),
1259
- file && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-selected-photo", children: [
1260
- pendingPhoto.preview && /* @__PURE__ */ jsxRuntime.jsx("img", { src: pendingPhoto.preview, alt: "Your selected photograph" }),
1261
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: file.name }) }),
1262
- /* @__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." }),
1263
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => {
1264
- void pendingPhoto.select(null);
1265
- setRights(false);
1266
- }, children: "Remove selected photo" })
1267
- ] }),
1268
- /* @__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." }),
1269
- Object.entries({
1270
- caption: "Caption (optional)",
1271
- credit: "Photographer / credit (optional)"
1272
- }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1273
- label,
1506
+ ] }, key)),
1274
1507
  /* @__PURE__ */ jsxRuntime.jsx(
1275
- "input",
1508
+ "button",
1276
1509
  {
1277
- value: mediaInfo[key],
1278
- onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
1510
+ disabled: busy,
1511
+ onClick: () => void send(
1512
+ {
1513
+ action: "EDIT_MEDIA_METADATA",
1514
+ contribution_id: drafts.entries.metadata?.data.editingMedia || editingMedia,
1515
+ caption: mediaInfo.caption,
1516
+ credit: mediaInfo.credit,
1517
+ alt: mediaInfo.alt
1518
+ },
1519
+ "Your proposed media descriptions have been sent to the newsroom."
1520
+ ),
1521
+ children: "Propose caption / credit changes"
1279
1522
  }
1280
1523
  )
1281
- ] }, key)),
1282
- /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "sm-review-media-details", children: [
1283
- /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Add more photograph details (optional)" }),
1284
- Object.entries({
1285
- who_is_pictured: "Who is pictured?",
1286
- creator: "Photographer / creator",
1287
- when_taken: "When was it taken?",
1288
- where_taken: "Where was it taken?",
1289
- alt: "Describe the photograph for readers who cannot see it"
1290
- }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1291
- label,
1524
+ ] }),
1525
+ panel === "media" && /* @__PURE__ */ jsxRuntime.jsxs(
1526
+ "form",
1527
+ {
1528
+ onSubmit: (e) => {
1529
+ e.preventDefault();
1530
+ void upload();
1531
+ },
1532
+ children: [
1533
+ photoToReplace && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-target-preview", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-target-preview-title", children: [
1534
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Replacing:" }),
1535
+ " ",
1536
+ photoToReplace.artwork_title ? `\u201C${photoToReplace.artwork_title}\u201D` : photoToReplace.caption || "Header photograph"
1537
+ ] }) }),
1538
+ 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." }),
1539
+ /* @__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." }),
1540
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-photo-picker", children: [
1541
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "1. Choose a photo" }) }),
1542
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => galleryRef.current?.click(), children: "Choose from my photos" }),
1543
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => cameraRef.current?.click(), children: "Take a photo" }),
1544
+ /* @__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) }),
1545
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-native-file", children: [
1546
+ "Photograph or video",
1547
+ /* @__PURE__ */ jsxRuntime.jsx(
1548
+ "input",
1549
+ {
1550
+ ref: galleryRef,
1551
+ type: "file",
1552
+ accept: "image/jpeg,image/png,image/webp,video/mp4,video/webm",
1553
+ onChange: (e) => choosePhoto(e.target.files?.[0] || null)
1554
+ }
1555
+ )
1556
+ ] })
1557
+ ] }),
1558
+ file && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-selected-photo", children: [
1559
+ pendingPhoto.preview && /* @__PURE__ */ jsxRuntime.jsx("img", { src: pendingPhoto.preview, alt: "Your selected photograph" }),
1560
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: file.name }) }),
1561
+ /* @__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." }),
1562
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => {
1563
+ void pendingPhoto.select(null);
1564
+ setRights(false);
1565
+ }, children: "Remove selected photo" })
1566
+ ] }),
1567
+ /* @__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." }),
1568
+ Object.entries({
1569
+ caption: "Caption (optional)",
1570
+ credit: "Photographer / credit (optional)"
1571
+ }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1572
+ label,
1573
+ /* @__PURE__ */ jsxRuntime.jsx(
1574
+ "input",
1575
+ {
1576
+ value: mediaInfo[key],
1577
+ onChange: (e) => setMediaInfo({ ...mediaInfo, [key]: e.target.value })
1578
+ }
1579
+ )
1580
+ ] }, key)),
1581
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "sm-review-media-details", children: [
1582
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Add more photograph details (optional)" }),
1583
+ Object.entries({
1584
+ who_is_pictured: "Who is pictured?",
1585
+ creator: "Photographer / creator",
1586
+ when_taken: "When was it taken?",
1587
+ where_taken: "Where was it taken?",
1588
+ alt: "Describe the photograph for readers who cannot see it"
1589
+ }).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1590
+ label,
1591
+ /* @__PURE__ */ jsxRuntime.jsx(
1592
+ "input",
1593
+ {
1594
+ value: mediaInfo[key],
1595
+ onChange: (e) => setMediaInfo({
1596
+ ...mediaInfo,
1597
+ [key]: e.target.value
1598
+ })
1599
+ }
1600
+ )
1601
+ ] }, key))
1602
+ ] }),
1603
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "2. Confirm permission" }) }),
1604
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-check", children: [
1605
+ /* @__PURE__ */ jsxRuntime.jsx(
1606
+ "input",
1607
+ {
1608
+ type: "checkbox",
1609
+ required: true,
1610
+ checked: rights,
1611
+ onChange: (e) => setRights(e.target.checked)
1612
+ }
1613
+ ),
1614
+ "I confirm that I have the right or permission to provide this media to ",
1615
+ review.publication.name,
1616
+ " for editorial publication."
1617
+ ] }),
1618
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `sm-review-send-bar${rights && file ? " is-ready" : ""}`, children: [
1619
+ /* @__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." }),
1620
+ busy && /* @__PURE__ */ jsxRuntime.jsx("progress", { "aria-label": "Uploading photo" }),
1621
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-primary", disabled: busy || !rights || !file, children: busy ? "Uploading\u2026" : "Upload privately" })
1622
+ ] })
1623
+ ]
1624
+ }
1625
+ ),
1626
+ ["approve", "changes", "comment", "decline"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1627
+ panel === "approve" && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1628
+ "You are approving version ",
1629
+ review.revision.number,
1630
+ ". Changes to the article require a new approval.",
1631
+ 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."
1632
+ ] }),
1633
+ panel === "decline" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: ["FACT_CHECK", "CONTRIBUTOR_REVIEW"].includes(
1634
+ review.review_policy
1635
+ ) ? "You can decline this review. The newsroom may still publish its reporting." : "Declining participation prevents publication of this voluntary announcement." }),
1636
+ panel === "comment" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1637
+ "Comment on",
1638
+ /* @__PURE__ */ jsxRuntime.jsxs(
1639
+ "select",
1640
+ {
1641
+ value: currentDraft?.data.commentTarget || commentTarget,
1642
+ onChange: (e) => {
1643
+ setCommentTarget(e.target.value);
1644
+ if (message) drafts.set("comment", { ...currentDraft?.data, commentTarget: e.target.value });
1645
+ },
1646
+ children: [
1647
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "STORY", children: "Entire story" }),
1648
+ review.revision.document.content_markdown.split(/\n\s*\n/).slice(0, 200).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: `PARAGRAPH:${i + 1}`, children: [
1649
+ "Paragraph ",
1650
+ i + 1
1651
+ ] }, i)),
1652
+ review.revision.document.media.map((m, i) => /* @__PURE__ */ jsxRuntime.jsxs(
1653
+ "option",
1654
+ {
1655
+ value: `MEDIA:${m.contribution_id}`,
1656
+ children: [
1657
+ "Photo / caption ",
1658
+ i + 1
1659
+ ]
1660
+ },
1661
+ m.contribution_id
1662
+ )),
1663
+ review.questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: `QUESTION:${q.id}`, children: q.prompt }, q.id))
1664
+ ]
1665
+ }
1666
+ )
1667
+ ] }),
1668
+ panel === "changes" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1669
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1670
+ "What would you like to change?",
1671
+ /* @__PURE__ */ jsxRuntime.jsx(
1672
+ "select",
1673
+ {
1674
+ value: correctionKind,
1675
+ onChange: (e) => setCorrectionKind(e.target.value),
1676
+ children: [
1677
+ "Correct a detail",
1678
+ "Correct a name",
1679
+ "Correct a professional / education detail",
1680
+ "Remove something",
1681
+ "Rewrite something",
1682
+ "Add something",
1683
+ "General comment"
1684
+ ].map((kind) => /* @__PURE__ */ jsxRuntime.jsx("option", { children: kind }, kind))
1685
+ }
1686
+ )
1687
+ ] }),
1688
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "A sentence or two is enough. We'll take care of the wording." }),
1689
+ can("PROPOSE_EDITS") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("edits"), children: "Or edit the wording directly" })
1690
+ ] }),
1691
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1692
+ panel === "changes" ? "What should change?" : "Message (optional)",
1292
1693
  /* @__PURE__ */ jsxRuntime.jsx(
1293
- "input",
1694
+ "textarea",
1294
1695
  {
1295
- value: mediaInfo[key],
1296
- onChange: (e) => setMediaInfo({
1297
- ...mediaInfo,
1298
- [key]: e.target.value
1299
- })
1696
+ rows: 5,
1697
+ value: message,
1698
+ onChange: (e) => setMessage(e.target.value)
1300
1699
  }
1301
1700
  )
1302
- ] }, key))
1303
- ] }),
1304
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "2. Confirm permission" }) }),
1305
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "sm-review-check", children: [
1701
+ ] }),
1306
1702
  /* @__PURE__ */ jsxRuntime.jsx(
1307
- "input",
1703
+ "button",
1308
1704
  {
1309
- type: "checkbox",
1310
- required: true,
1311
- checked: rights,
1312
- onChange: (e) => setRights(e.target.checked)
1705
+ disabled: busy || panel === "changes" && !message.trim() || panel === "comment" && !message.trim(),
1706
+ onClick: () => void send(
1707
+ {
1708
+ action: panel === "approve" ? "APPROVE" : panel === "changes" ? "REQUEST_CHANGES" : panel === "decline" ? "DECLINE" : "COMMENT",
1709
+ message: panel === "changes" ? `${correctionKind}: ${message}` : message || null,
1710
+ ...panel === "comment" ? {
1711
+ target: {
1712
+ kind: (currentDraft?.data.commentTarget || commentTarget).split(":")[0],
1713
+ reference: (currentDraft?.data.commentTarget || commentTarget).split(":")[1] || null
1714
+ }
1715
+ } : {}
1716
+ },
1717
+ 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."
1718
+ ),
1719
+ children: panel === "approve" ? `Approve version ${review.revision.number}` : panel === "changes" ? "Send change request" : panel === "decline" ? "Prefer not to publish" : "Send comment"
1313
1720
  }
1314
- ),
1315
- "I confirm that I have the right or permission to provide this media to ",
1316
- review.publication.name,
1317
- " for editorial publication."
1721
+ )
1318
1722
  ] }),
1319
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `sm-review-send-bar${rights && file ? " is-ready" : ""}`, children: [
1320
- /* @__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." }),
1321
- busy && /* @__PURE__ */ jsxRuntime.jsx("progress", { "aria-label": "Uploading photo" }),
1322
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-primary", disabled: busy || !rights || !file, children: busy ? "Uploading\u2026" : "Upload privately" })
1323
- ] })
1324
- ]
1325
- }
1326
- ),
1327
- ["approve", "changes", "comment", "decline"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1328
- panel === "approve" && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
1329
- "You are approving version ",
1330
- review.revision.number,
1331
- ". Changes to the article require a new approval.",
1332
- 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."
1333
- ] }),
1334
- panel === "decline" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: ["FACT_CHECK", "CONTRIBUTOR_REVIEW"].includes(
1335
- review.review_policy
1336
- ) ? "You can decline this review. The newsroom may still publish its reporting." : "Declining participation prevents publication of this voluntary announcement." }),
1337
- panel === "comment" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1338
- "Comment on",
1339
- /* @__PURE__ */ jsxRuntime.jsxs(
1340
- "select",
1341
- {
1342
- value: currentDraft?.data.commentTarget || commentTarget,
1343
- onChange: (e) => {
1344
- setCommentTarget(e.target.value);
1345
- if (message) drafts.set("comment", { ...currentDraft?.data, commentTarget: e.target.value });
1346
- },
1347
- children: [
1348
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "STORY", children: "Entire story" }),
1349
- review.revision.document.content_markdown.split(/\n\s*\n/).slice(0, 200).map((_, i) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: `PARAGRAPH:${i + 1}`, children: [
1350
- "Paragraph ",
1351
- i + 1
1352
- ] }, i)),
1353
- review.revision.document.media.map((m, i) => /* @__PURE__ */ jsxRuntime.jsxs(
1354
- "option",
1723
+ panel === "history" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1724
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Only revisions selected by the editor are visible here." }),
1725
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1726
+ "Compare from",
1727
+ /* @__PURE__ */ jsxRuntime.jsxs(
1728
+ "select",
1355
1729
  {
1356
- value: `MEDIA:${m.contribution_id}`,
1730
+ value: compare,
1731
+ onChange: (e) => setCompare(e.target.value),
1357
1732
  children: [
1358
- "Photo / caption ",
1359
- i + 1
1733
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Choose a shared revision" }),
1734
+ revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
1735
+ "Version ",
1736
+ r.number
1737
+ ] }, r.id))
1360
1738
  ]
1361
- },
1362
- m.contribution_id
1363
- )),
1364
- review.questions.map((q) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: `QUESTION:${q.id}`, children: q.prompt }, q.id))
1365
- ]
1366
- }
1367
- )
1368
- ] }),
1369
- panel === "changes" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1370
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1371
- "What would you like to change?",
1372
- /* @__PURE__ */ jsxRuntime.jsx(
1373
- "select",
1374
- {
1375
- value: correctionKind,
1376
- onChange: (e) => setCorrectionKind(e.target.value),
1377
- children: [
1378
- "Correct a detail",
1379
- "Correct a name",
1380
- "Correct a professional / education detail",
1381
- "Remove something",
1382
- "Rewrite something",
1383
- "Add something",
1384
- "General comment"
1385
- ].map((kind) => /* @__PURE__ */ jsxRuntime.jsx("option", { children: kind }, kind))
1386
- }
1387
- )
1388
- ] }),
1389
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "A sentence or two is enough. We'll take care of the wording." }),
1390
- can("PROPOSE_EDITS") && /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("edits"), children: "Or edit the wording directly" })
1391
- ] }),
1392
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1393
- panel === "changes" ? "What should change?" : "Message (optional)",
1394
- /* @__PURE__ */ jsxRuntime.jsx(
1395
- "textarea",
1396
- {
1397
- rows: 5,
1398
- value: message,
1399
- onChange: (e) => setMessage(e.target.value)
1400
- }
1401
- )
1402
- ] }),
1403
- /* @__PURE__ */ jsxRuntime.jsx(
1404
- "button",
1405
- {
1406
- disabled: busy || panel === "changes" && !message.trim() || panel === "comment" && !message.trim(),
1407
- onClick: () => void send(
1408
- {
1409
- action: panel === "approve" ? "APPROVE" : panel === "changes" ? "REQUEST_CHANGES" : panel === "decline" ? "DECLINE" : "COMMENT",
1410
- message: panel === "changes" ? `${correctionKind}: ${message}` : message || null,
1411
- ...panel === "comment" ? {
1412
- target: {
1413
- kind: (currentDraft?.data.commentTarget || commentTarget).split(":")[0],
1414
- reference: (currentDraft?.data.commentTarget || commentTarget).split(":")[1] || null
1415
1739
  }
1416
- } : {}
1417
- },
1418
- 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."
1419
- ),
1420
- children: panel === "approve" ? `Approve version ${review.revision.number}` : panel === "changes" ? "Send change request" : panel === "decline" ? "Prefer not to publish" : "Send comment"
1421
- }
1422
- )
1423
- ] }),
1424
- panel === "history" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1425
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Only revisions selected by the editor are visible here." }),
1426
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1427
- "Compare from",
1428
- /* @__PURE__ */ jsxRuntime.jsxs(
1429
- "select",
1430
- {
1431
- value: compare,
1432
- onChange: (e) => setCompare(e.target.value),
1433
- children: [
1434
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Choose a shared revision" }),
1435
- revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
1436
- "Version ",
1437
- r.number
1438
- ] }, r.id))
1439
- ]
1440
- }
1441
- )
1442
- ] }),
1443
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1444
- "Compare to",
1445
- /* @__PURE__ */ jsxRuntime.jsx(
1446
- "select",
1447
- {
1448
- value: compareTo || review.revision.id,
1449
- onChange: (e) => setCompareTo(e.target.value),
1450
- children: revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
1451
- "Version ",
1452
- r.number
1453
- ] }, r.id))
1454
- }
1455
- )
1456
- ] }),
1457
- selected && selectedAfter && /* @__PURE__ */ jsxRuntime.jsx(ReviewDiff, { before: selected, after: selectedAfter }),
1458
- /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
1459
- /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Read selected revision" }),
1460
- /* @__PURE__ */ jsxRuntime.jsx("h3", { children: selected?.document.title }),
1461
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: selected?.document.subtitle }),
1462
- /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: selected?.editorial }),
1463
- /* @__PURE__ */ jsxRuntime.jsx(
1464
- "div",
1465
- {
1466
- className: "sm-review-prose",
1467
- dangerouslySetInnerHTML: {
1468
- __html: selected?.content_html || ""
1469
- }
1470
- }
1471
- ),
1472
- selected?.document.media.map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id))
1473
- ] })
1474
- ] })
1475
- ] })
1476
- ]
1740
+ )
1741
+ ] }),
1742
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1743
+ "Compare to",
1744
+ /* @__PURE__ */ jsxRuntime.jsx(
1745
+ "select",
1746
+ {
1747
+ value: compareTo || review.revision.id,
1748
+ onChange: (e) => setCompareTo(e.target.value),
1749
+ children: revisions.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: r.id, children: [
1750
+ "Version ",
1751
+ r.number
1752
+ ] }, r.id))
1753
+ }
1754
+ )
1755
+ ] }),
1756
+ selected && selectedAfter && /* @__PURE__ */ jsxRuntime.jsx(ReviewDiff, { before: selected, after: selectedAfter }),
1757
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
1758
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Read selected revision" }),
1759
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: selected?.document.title }),
1760
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: selected?.document.subtitle }),
1761
+ /* @__PURE__ */ jsxRuntime.jsx(EditorialNotices, { editorial: selected?.editorial }),
1762
+ /* @__PURE__ */ jsxRuntime.jsx(
1763
+ "div",
1764
+ {
1765
+ className: "sm-review-prose",
1766
+ dangerouslySetInnerHTML: {
1767
+ __html: selected?.content_html || ""
1768
+ }
1769
+ }
1770
+ ),
1771
+ selected?.document.media.map((m) => /* @__PURE__ */ jsxRuntime.jsx(PrivatePhoto, { media: m, base }, m.file_id))
1772
+ ] })
1773
+ ] })
1774
+ ] })
1775
+ ]
1776
+ }
1777
+ )
1778
+ ] })
1477
1779
  }
1478
1780
  ),
1479
1781
  review.contributions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "sm-review-contributions", children: [
@@ -1541,7 +1843,14 @@ function StoryReview({
1541
1843
  publicationFooter
1542
1844
  ] });
1543
1845
  }
1544
- function PrivatePhoto({ base, media, onChange, disabled }) {
1846
+ function PrivatePhoto({
1847
+ base,
1848
+ media,
1849
+ onChange,
1850
+ onSuggestCorrection,
1851
+ disabled,
1852
+ isInline
1853
+ }) {
1545
1854
  const [url, setUrl] = react.useState("");
1546
1855
  const [video, setVideo] = react.useState(false);
1547
1856
  react.useEffect(() => {
@@ -1564,25 +1873,69 @@ function PrivatePhoto({ base, media, onChange, disabled }) {
1564
1873
  if (object) URL.revokeObjectURL(object);
1565
1874
  };
1566
1875
  }, [base, media.file_id]);
1567
- return /* @__PURE__ */ jsxRuntime.jsxs("figure", { children: [
1568
- url ? video ? /* @__PURE__ */ jsxRuntime.jsx(
1569
- "video",
1570
- {
1571
- src: url,
1572
- controls: true,
1573
- playsInline: true,
1574
- preload: "metadata",
1575
- "aria-label": media.alt
1576
- }
1577
- ) : /* @__PURE__ */ jsxRuntime.jsx("img", { src: url, alt: media.alt, referrerPolicy: "no-referrer" }) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Media unavailable." }),
1578
- /* @__PURE__ */ jsxRuntime.jsxs("figcaption", { className: "sm-review-photo-details", children: [
1579
- (media.caption || media.credit) && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1580
- media.caption,
1581
- media.credit && ` Photo: ${media.credit}`
1582
- ] }),
1583
- onChange && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-change-photo", type: "button", disabled, onClick: onChange, children: "Change photo" })
1584
- ] })
1585
- ] });
1876
+ const captionInfo = formatArtworkCaption(media);
1877
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1878
+ "figure",
1879
+ {
1880
+ className: `sm-review-photo-figure ${isInline ? "sm-review-inline-figure" : "sm-review-hero-figure"}`,
1881
+ "data-media-id": media.file_id,
1882
+ children: [
1883
+ url ? video ? /* @__PURE__ */ jsxRuntime.jsx(
1884
+ "video",
1885
+ {
1886
+ src: url,
1887
+ controls: true,
1888
+ playsInline: true,
1889
+ preload: "metadata",
1890
+ "aria-label": media.alt || captionInfo.title || "Story video"
1891
+ }
1892
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1893
+ "img",
1894
+ {
1895
+ src: url,
1896
+ alt: media.alt || captionInfo.title || "Story photograph",
1897
+ referrerPolicy: "no-referrer"
1898
+ }
1899
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Media unavailable." }),
1900
+ /* @__PURE__ */ jsxRuntime.jsxs("figcaption", { className: "sm-review-photo-details", children: [
1901
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-caption-text", children: [
1902
+ captionInfo.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-artwork-title", children: /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
1903
+ "\u201C",
1904
+ captionInfo.title,
1905
+ "\u201D"
1906
+ ] }) }),
1907
+ captionInfo.artist && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-artist-name", children: captionInfo.artist }),
1908
+ captionInfo.mediumAndDate && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-artwork-medium", children: captionInfo.mediumAndDate }),
1909
+ captionInfo.captionText && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-photo-caption", children: captionInfo.captionText }),
1910
+ captionInfo.credit && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-photo-credit", children: captionInfo.credit }),
1911
+ captionInfo.source && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-photo-source", children: captionInfo.source })
1912
+ ] }),
1913
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-photo-controls", children: [
1914
+ onChange && /* @__PURE__ */ jsxRuntime.jsx(
1915
+ "button",
1916
+ {
1917
+ className: "sm-review-change-photo",
1918
+ type: "button",
1919
+ disabled,
1920
+ onClick: onChange,
1921
+ children: "Change photo"
1922
+ }
1923
+ ),
1924
+ onSuggestCorrection && /* @__PURE__ */ jsxRuntime.jsx(
1925
+ "button",
1926
+ {
1927
+ className: "sm-review-suggest-correction",
1928
+ type: "button",
1929
+ disabled,
1930
+ onClick: onSuggestCorrection,
1931
+ children: "Suggest correction"
1932
+ }
1933
+ )
1934
+ ] })
1935
+ ] })
1936
+ ]
1937
+ }
1938
+ );
1586
1939
  }
1587
1940
  function ReviewDiff({
1588
1941
  before,