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