@scalemule/news-publication 0.0.34 → 0.0.35
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/README.md +17 -0
- package/dist/story-review.cjs +145 -13
- package/dist/story-review.d.cts +5 -1
- package/dist/story-review.d.ts +5 -1
- package/dist/story-review.js +145 -13
- package/package.json +1 -1
- package/story-review.css +5 -0
package/README.md
CHANGED
|
@@ -111,3 +111,20 @@ The review endpoint returns the decision for the exact invited revision. Editors
|
|
|
111
111
|
### Publication framing for private previews (0.0.28)
|
|
112
112
|
|
|
113
113
|
Pass `publicationHeader` and `publicationFooter` React nodes to `StoryReview` to reuse a publication's real navigation, masthead, and footer. The private banner remains above the publication header and the review controls remain separate from the story. The standalone masthead remains the default. The host must suppress analytics on private review routes; public article indexing and sharing features are not mounted by these slots. This patch changes presentation only; review permissions and publication gates are unchanged.
|
|
114
|
+
|
|
115
|
+
### Private review sign-in and email options
|
|
116
|
+
|
|
117
|
+
`StoryReview` accepts `readerAccountUrl` and a client `renderSignIn(returnTo)`
|
|
118
|
+
function so the host publication can reuse its existing account and sign-in
|
|
119
|
+
components. Hosts must implement the `/api/news/review/:session/account`
|
|
120
|
+
server adapter to connect the authenticated, verified reader through Blog’s
|
|
121
|
+
`join-account` endpoint. Never trust a browser-supplied email or verification flag.
|
|
122
|
+
The private return path belongs in the host’s signed, HttpOnly OAuth flow cookie,
|
|
123
|
+
not the provider’s authorization URL. Contributor access does not imply subject
|
|
124
|
+
approval authority.
|
|
125
|
+
|
|
126
|
+
Optional questions can be browsed and copied before identity verification.
|
|
127
|
+
`feedbackEmail` enables mail-app links containing the story title, exact revision
|
|
128
|
+
reference and optional answers, never the invitation credential. Configure a
|
|
129
|
+
monitored mailbox; received email must be recorded by the newsroom. Online
|
|
130
|
+
answers retain their existing autosave and explicit-send behavior.
|
package/dist/story-review.cjs
CHANGED
|
@@ -291,6 +291,23 @@ function proseDiff(before, after) {
|
|
|
291
291
|
emit("same", a.slice(endA).join(""));
|
|
292
292
|
return out;
|
|
293
293
|
}
|
|
294
|
+
|
|
295
|
+
// src/story-review-email.ts
|
|
296
|
+
function reviewEmailText(review, answers) {
|
|
297
|
+
const header = `Private story feedback \u2014 not published
|
|
298
|
+
${review.revision.document.title}
|
|
299
|
+
Preview v${review.revision.number} \xB7 Reference ${review.revision.id}
|
|
300
|
+
|
|
301
|
+
Your name:
|
|
302
|
+
|
|
303
|
+
`;
|
|
304
|
+
return header + (answers === void 0 ? "My feedback:\n" : "These questions are optional. Answer only what you would like to share.\n\n" + review.questions.map((q, i) => `${i + 1}. ${q.prompt}
|
|
305
|
+
${answers[q.id] || ""}`).join("\n\n"));
|
|
306
|
+
}
|
|
307
|
+
function reviewEmailLink(email, title, text) {
|
|
308
|
+
if (!email || !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test(email)) return void 0;
|
|
309
|
+
return `mailto:${encodeURIComponent(email)}?subject=${encodeURIComponent(`Private story feedback: ${title}`)}&body=${encodeURIComponent(text)}`;
|
|
310
|
+
}
|
|
294
311
|
function EditorialNotices({ editorial }) {
|
|
295
312
|
return editorial ? /* @__PURE__ */ jsxRuntime.jsxs("aside", { "aria-label": "Editorial notices", children: [
|
|
296
313
|
editorial.content_kind && editorial.content_kind !== "EDITORIAL" && /* @__PURE__ */ jsxRuntime.jsx("p", { children: human(editorial.content_kind) }),
|
|
@@ -310,15 +327,40 @@ function StoryReview({
|
|
|
310
327
|
publicationName,
|
|
311
328
|
nameplateUrl,
|
|
312
329
|
publicationHeader,
|
|
313
|
-
publicationFooter
|
|
330
|
+
publicationFooter,
|
|
331
|
+
readerAccountUrl,
|
|
332
|
+
renderSignIn,
|
|
333
|
+
feedbackEmail
|
|
314
334
|
}) {
|
|
315
335
|
const base = `/api/news/review/${encodeURIComponent(sessionId)}`;
|
|
316
336
|
const [review, setReview] = react.useState(null), [error, setError] = react.useState(""), [notice, setNotice] = react.useState(""), [busy, setBusy] = react.useState(false), [panel, setPanelState] = react.useState("article"), [verification, setVerification] = react.useState(false), [code, setCode] = react.useState("");
|
|
317
337
|
const [shareUrl, setShareUrl] = react.useState(""), [canShare, setCanShare] = react.useState(false);
|
|
318
338
|
const [contributorName, setContributorName] = react.useState(""), [contributorEmail, setContributorEmail] = react.useState("");
|
|
319
339
|
const [identityStep, setIdentityStep] = react.useState("name"), [requestedPanel, setRequestedPanel] = react.useState("comment");
|
|
340
|
+
const [reader, setReader] = react.useState(null);
|
|
341
|
+
const [readerLoading, setReaderLoading] = react.useState(!!readerAccountUrl);
|
|
342
|
+
const [useEmailCode, setUseEmailCode] = react.useState(!renderSignIn);
|
|
343
|
+
const [returnPanel, setReturnPanel] = react.useState("");
|
|
344
|
+
const resumed = react.useRef(false);
|
|
345
|
+
const [copiedQuestions, setCopiedQuestions] = react.useState(false);
|
|
346
|
+
react.useEffect(() => {
|
|
347
|
+
if (!readerAccountUrl) return;
|
|
348
|
+
let active = true;
|
|
349
|
+
void fetch(readerAccountUrl, { credentials: "same-origin", cache: "no-store", signal: AbortSignal.timeout(15e3) }).then(async (response) => {
|
|
350
|
+
if (response.ok) {
|
|
351
|
+
const data = await response.json();
|
|
352
|
+
if (active) setReader(data.user || null);
|
|
353
|
+
}
|
|
354
|
+
}).catch(() => {
|
|
355
|
+
}).finally(() => {
|
|
356
|
+
if (active) setReaderLoading(false);
|
|
357
|
+
});
|
|
358
|
+
return () => {
|
|
359
|
+
active = false;
|
|
360
|
+
};
|
|
361
|
+
}, [readerAccountUrl]);
|
|
320
362
|
function setPanel(next) {
|
|
321
|
-
if (review?.identity_required && !["article", "history", "identity", "share"].includes(next)) {
|
|
363
|
+
if (review?.identity_required && !["article", "history", "identity", "share", "question-list"].includes(next)) {
|
|
322
364
|
setRequestedPanel(next);
|
|
323
365
|
setPanelState("identity");
|
|
324
366
|
} else setPanelState(next);
|
|
@@ -416,10 +458,41 @@ function StoryReview({
|
|
|
416
458
|
setReview(data);
|
|
417
459
|
}
|
|
418
460
|
}
|
|
461
|
+
async function connectReader(target = requestedPanel) {
|
|
462
|
+
setBusy(true);
|
|
463
|
+
setError("");
|
|
464
|
+
try {
|
|
465
|
+
await request("/account", {});
|
|
466
|
+
await load();
|
|
467
|
+
setPanelState(target);
|
|
468
|
+
setReturnPanel("");
|
|
469
|
+
const url = new URL(window.location.href);
|
|
470
|
+
url.searchParams.delete("respond");
|
|
471
|
+
window.history.replaceState(null, "", url.pathname + url.search);
|
|
472
|
+
} catch (e) {
|
|
473
|
+
setError(e.message);
|
|
474
|
+
setPanelState("identity");
|
|
475
|
+
} finally {
|
|
476
|
+
setBusy(false);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
react.useEffect(() => {
|
|
480
|
+
if (!returnPanel || !review || !reader?.email_verified || resumed.current) return;
|
|
481
|
+
resumed.current = true;
|
|
482
|
+
void connectReader(returnPanel);
|
|
483
|
+
}, [returnPanel, review, reader]);
|
|
419
484
|
react.useEffect(() => {
|
|
420
485
|
let active = true;
|
|
421
486
|
void (async () => {
|
|
422
487
|
try {
|
|
488
|
+
const returnUrl = new URL(window.location.href);
|
|
489
|
+
if (returnUrl.searchParams.get("signin") === "retry") setError("Sign-in wasn\u2019t completed. Try again or use an email code.");
|
|
490
|
+
const respond = returnUrl.searchParams.get("respond") || "";
|
|
491
|
+
if (["feedback", "changes", "questions", "media", "comment", "edits"].includes(respond)) {
|
|
492
|
+
setRequestedPanel(respond);
|
|
493
|
+
setReturnPanel(respond);
|
|
494
|
+
setPanelState("identity");
|
|
495
|
+
}
|
|
423
496
|
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);
|
|
424
497
|
let exchange = {};
|
|
425
498
|
try {
|
|
@@ -431,7 +504,9 @@ function StoryReview({
|
|
|
431
504
|
if (address) {
|
|
432
505
|
const canonical = new URL(address, window.location.origin);
|
|
433
506
|
if (canonical.origin === window.location.origin) {
|
|
434
|
-
|
|
507
|
+
const address2 = new URL(canonical);
|
|
508
|
+
if (respond) address2.searchParams.set("respond", respond);
|
|
509
|
+
window.history.replaceState(null, "", address2.pathname + address2.search);
|
|
435
510
|
if (active) {
|
|
436
511
|
setShareUrl(canonical.href);
|
|
437
512
|
setCanShare(exchange.can_share === true);
|
|
@@ -596,6 +671,15 @@ function StoryReview({
|
|
|
596
671
|
(r) => r.id === (compareTo || review?.revision.id)
|
|
597
672
|
);
|
|
598
673
|
const errorMessage = error ? /* @__PURE__ */ jsxRuntime.jsx("p", { ref: errorRef, tabIndex: -1, role: "alert", className: "sm-review-alert", children: error }) : null;
|
|
674
|
+
const emailLink = review ? reviewEmailLink(feedbackEmail, review.revision.document.title, reviewEmailText(review)) : void 0;
|
|
675
|
+
const questionText = review ? reviewEmailText(review, answers) : "";
|
|
676
|
+
const questionsEmail = review ? reviewEmailLink(feedbackEmail, review.revision.document.title, questionText) : void 0;
|
|
677
|
+
const signInReturn = shareUrl ? new URL(shareUrl).pathname + new URL(shareUrl).search + `&respond=${encodeURIComponent(requestedPanel)}` : "";
|
|
678
|
+
const emailAlternative = emailLink && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "sm-review-email-alternative", children: [
|
|
679
|
+
"Prefer email? ",
|
|
680
|
+
/* @__PURE__ */ jsxRuntime.jsx("a", { href: emailLink, children: "Email the newsroom" }),
|
|
681
|
+
". Your email app will open; send your message there."
|
|
682
|
+
] });
|
|
599
683
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `sm-review-frame${publicationHeader ? " sm-review-publication-frame" : ""}`, children: [
|
|
600
684
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-banner-wrap", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-private", children: [
|
|
601
685
|
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: review?.published ? "Private review" : "Private preview \xB7 Not published" }),
|
|
@@ -796,7 +880,7 @@ function StoryReview({
|
|
|
796
880
|
{
|
|
797
881
|
onClick: () => {
|
|
798
882
|
setMoreQuestions(false);
|
|
799
|
-
setPanel("questions");
|
|
883
|
+
setPanel(review.identity_required ? "question-list" : "questions");
|
|
800
884
|
},
|
|
801
885
|
children: "Tell Us More"
|
|
802
886
|
}
|
|
@@ -809,7 +893,8 @@ function StoryReview({
|
|
|
809
893
|
children: "Prefer Not to Publish"
|
|
810
894
|
}
|
|
811
895
|
),
|
|
812
|
-
can("COMMENT") && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-quiet", onClick: () => setPanel("comment"), children: "Leave a Comment" })
|
|
896
|
+
can("COMMENT") && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "sm-review-quiet", onClick: () => setPanel("comment"), children: "Leave a Comment" }),
|
|
897
|
+
emailAlternative
|
|
813
898
|
] }),
|
|
814
899
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
815
900
|
"nav",
|
|
@@ -832,7 +917,7 @@ function StoryReview({
|
|
|
832
917
|
can("UPLOAD_MEDIA") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => openMedia(), children: celebration ? "Add Photos" : "Add photos" }),
|
|
833
918
|
!celebration && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
834
919
|
can("PROPOSE_EDITS") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("edits"), children: "Suggest edits" }),
|
|
835
|
-
can("ANSWER_QUESTIONS") && review.questions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("questions"), children: "Answer questions" }),
|
|
920
|
+
can("ANSWER_QUESTIONS") && review.questions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel(review.identity_required ? "question-list" : "questions"), children: "Answer questions" }),
|
|
836
921
|
can("COMMENT") && /* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("comment"), children: "Leave a comment" })
|
|
837
922
|
] })
|
|
838
923
|
]
|
|
@@ -854,7 +939,10 @@ function StoryReview({
|
|
|
854
939
|
panel === "share" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Share private preview", children: [
|
|
855
940
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Share with your family" }),
|
|
856
941
|
errorMessage,
|
|
857
|
-
/* @__PURE__ */ jsxRuntime.
|
|
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
|
+
] }),
|
|
858
946
|
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "review-share-url", children: "Private preview link" }),
|
|
859
947
|
/* @__PURE__ */ jsxRuntime.jsx("input", { id: "review-share-url", readOnly: true, value: shareUrl, onFocus: (e) => e.currentTarget.select() }),
|
|
860
948
|
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: async () => {
|
|
@@ -867,11 +955,48 @@ function StoryReview({
|
|
|
867
955
|
}, children: "Copy private link" }),
|
|
868
956
|
/* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => setPanel("article"), children: "Back to the story" })
|
|
869
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
|
+
] }),
|
|
870
985
|
panel === "identity" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Introduce yourself", children: [
|
|
871
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: identityStep === "code" ? "Check your email" : "
|
|
986
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: identityStep === "code" ? "Check your email" : "Continue to help with the story" }),
|
|
872
987
|
errorMessage,
|
|
873
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { children: "Your name
|
|
874
|
-
identityStep === "name"
|
|
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) => {
|
|
875
1000
|
e.preventDefault();
|
|
876
1001
|
setBusy(true);
|
|
877
1002
|
setError("");
|
|
@@ -891,8 +1016,10 @@ function StoryReview({
|
|
|
891
1016
|
/* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "contributor-email", children: "Your email" }),
|
|
892
1017
|
/* @__PURE__ */ jsxRuntime.jsx("input", { id: "contributor-email", type: "email", autoComplete: "email", value: contributorEmail, maxLength: 320, required: true, onChange: (e) => setContributorEmail(e.target.value) }),
|
|
893
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." }),
|
|
894
1020
|
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, children: busy ? "Sending your code\u2026" : "Email me a code" })
|
|
895
|
-
] })
|
|
1021
|
+
] }),
|
|
1022
|
+
identityStep === "code" && /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: async (e) => {
|
|
896
1023
|
e.preventDefault();
|
|
897
1024
|
setBusy(true);
|
|
898
1025
|
setError("");
|
|
@@ -928,6 +1055,7 @@ function StoryReview({
|
|
|
928
1055
|
}, children: "Send a new code" }),
|
|
929
1056
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: busy, onClick: () => setIdentityStep("name"), children: "Use a different email" })
|
|
930
1057
|
] }),
|
|
1058
|
+
emailAlternative,
|
|
931
1059
|
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Just read the story" })
|
|
932
1060
|
] }),
|
|
933
1061
|
panel === "feedback" && /* @__PURE__ */ jsxRuntime.jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Family feedback", children: [
|
|
@@ -941,7 +1069,7 @@ function StoryReview({
|
|
|
941
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" }),
|
|
942
1070
|
/* @__PURE__ */ jsxRuntime.jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Back to the story" })
|
|
943
1071
|
] }),
|
|
944
|
-
!["article", "identity", "share", "feedback"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1072
|
+
!["article", "identity", "share", "feedback", "question-list"].includes(panel) && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
945
1073
|
"section",
|
|
946
1074
|
{
|
|
947
1075
|
ref: responseRef,
|
|
@@ -1062,7 +1190,11 @@ function StoryReview({
|
|
|
1062
1190
|
onClick: () => setMoreQuestions(true),
|
|
1063
1191
|
children: "Answer More Questions"
|
|
1064
1192
|
}
|
|
1065
|
-
)
|
|
1193
|
+
),
|
|
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
|
+
] })
|
|
1066
1198
|
]
|
|
1067
1199
|
}
|
|
1068
1200
|
),
|
package/dist/story-review.d.cts
CHANGED
|
@@ -32,13 +32,17 @@ type Revision = {
|
|
|
32
32
|
document: Document;
|
|
33
33
|
content_html: string;
|
|
34
34
|
};
|
|
35
|
-
declare function StoryReview({ sessionId, publicationName, nameplateUrl, publicationHeader, publicationFooter, }: {
|
|
35
|
+
declare function StoryReview({ sessionId, publicationName, nameplateUrl, publicationHeader, publicationFooter, readerAccountUrl, renderSignIn, feedbackEmail, }: {
|
|
36
36
|
sessionId: string;
|
|
37
37
|
publicationName: string;
|
|
38
38
|
nameplateUrl?: string;
|
|
39
39
|
/** The publication supplies its real shell; private content still loads only through the review capability. */
|
|
40
40
|
publicationHeader?: react__default.ReactNode;
|
|
41
41
|
publicationFooter?: react__default.ReactNode;
|
|
42
|
+
/** Reuse the publication's existing account and sign-in components. */
|
|
43
|
+
readerAccountUrl?: string;
|
|
44
|
+
renderSignIn?: (returnTo: string) => react__default.ReactNode;
|
|
45
|
+
feedbackEmail?: string;
|
|
42
46
|
}): react__default.JSX.Element;
|
|
43
47
|
/** Paragraph comparisons preserve readable prose and identify title/dek and media changes. */
|
|
44
48
|
declare function ReviewDiff({ before, after, }: {
|
package/dist/story-review.d.ts
CHANGED
|
@@ -32,13 +32,17 @@ type Revision = {
|
|
|
32
32
|
document: Document;
|
|
33
33
|
content_html: string;
|
|
34
34
|
};
|
|
35
|
-
declare function StoryReview({ sessionId, publicationName, nameplateUrl, publicationHeader, publicationFooter, }: {
|
|
35
|
+
declare function StoryReview({ sessionId, publicationName, nameplateUrl, publicationHeader, publicationFooter, readerAccountUrl, renderSignIn, feedbackEmail, }: {
|
|
36
36
|
sessionId: string;
|
|
37
37
|
publicationName: string;
|
|
38
38
|
nameplateUrl?: string;
|
|
39
39
|
/** The publication supplies its real shell; private content still loads only through the review capability. */
|
|
40
40
|
publicationHeader?: react__default.ReactNode;
|
|
41
41
|
publicationFooter?: react__default.ReactNode;
|
|
42
|
+
/** Reuse the publication's existing account and sign-in components. */
|
|
43
|
+
readerAccountUrl?: string;
|
|
44
|
+
renderSignIn?: (returnTo: string) => react__default.ReactNode;
|
|
45
|
+
feedbackEmail?: string;
|
|
42
46
|
}): react__default.JSX.Element;
|
|
43
47
|
/** Paragraph comparisons preserve readable prose and identify title/dek and media changes. */
|
|
44
48
|
declare function ReviewDiff({ before, after, }: {
|
package/dist/story-review.js
CHANGED
|
@@ -289,6 +289,23 @@ function proseDiff(before, after) {
|
|
|
289
289
|
emit("same", a.slice(endA).join(""));
|
|
290
290
|
return out;
|
|
291
291
|
}
|
|
292
|
+
|
|
293
|
+
// src/story-review-email.ts
|
|
294
|
+
function reviewEmailText(review, answers) {
|
|
295
|
+
const header = `Private story feedback \u2014 not published
|
|
296
|
+
${review.revision.document.title}
|
|
297
|
+
Preview v${review.revision.number} \xB7 Reference ${review.revision.id}
|
|
298
|
+
|
|
299
|
+
Your name:
|
|
300
|
+
|
|
301
|
+
`;
|
|
302
|
+
return header + (answers === void 0 ? "My feedback:\n" : "These questions are optional. Answer only what you would like to share.\n\n" + review.questions.map((q, i) => `${i + 1}. ${q.prompt}
|
|
303
|
+
${answers[q.id] || ""}`).join("\n\n"));
|
|
304
|
+
}
|
|
305
|
+
function reviewEmailLink(email, title, text) {
|
|
306
|
+
if (!email || !/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test(email)) return void 0;
|
|
307
|
+
return `mailto:${encodeURIComponent(email)}?subject=${encodeURIComponent(`Private story feedback: ${title}`)}&body=${encodeURIComponent(text)}`;
|
|
308
|
+
}
|
|
292
309
|
function EditorialNotices({ editorial }) {
|
|
293
310
|
return editorial ? /* @__PURE__ */ jsxs("aside", { "aria-label": "Editorial notices", children: [
|
|
294
311
|
editorial.content_kind && editorial.content_kind !== "EDITORIAL" && /* @__PURE__ */ jsx("p", { children: human(editorial.content_kind) }),
|
|
@@ -308,15 +325,40 @@ function StoryReview({
|
|
|
308
325
|
publicationName,
|
|
309
326
|
nameplateUrl,
|
|
310
327
|
publicationHeader,
|
|
311
|
-
publicationFooter
|
|
328
|
+
publicationFooter,
|
|
329
|
+
readerAccountUrl,
|
|
330
|
+
renderSignIn,
|
|
331
|
+
feedbackEmail
|
|
312
332
|
}) {
|
|
313
333
|
const base = `/api/news/review/${encodeURIComponent(sessionId)}`;
|
|
314
334
|
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("");
|
|
315
335
|
const [shareUrl, setShareUrl] = useState(""), [canShare, setCanShare] = useState(false);
|
|
316
336
|
const [contributorName, setContributorName] = useState(""), [contributorEmail, setContributorEmail] = useState("");
|
|
317
337
|
const [identityStep, setIdentityStep] = useState("name"), [requestedPanel, setRequestedPanel] = useState("comment");
|
|
338
|
+
const [reader, setReader] = useState(null);
|
|
339
|
+
const [readerLoading, setReaderLoading] = useState(!!readerAccountUrl);
|
|
340
|
+
const [useEmailCode, setUseEmailCode] = useState(!renderSignIn);
|
|
341
|
+
const [returnPanel, setReturnPanel] = useState("");
|
|
342
|
+
const resumed = useRef(false);
|
|
343
|
+
const [copiedQuestions, setCopiedQuestions] = useState(false);
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
if (!readerAccountUrl) return;
|
|
346
|
+
let active = true;
|
|
347
|
+
void fetch(readerAccountUrl, { credentials: "same-origin", cache: "no-store", signal: AbortSignal.timeout(15e3) }).then(async (response) => {
|
|
348
|
+
if (response.ok) {
|
|
349
|
+
const data = await response.json();
|
|
350
|
+
if (active) setReader(data.user || null);
|
|
351
|
+
}
|
|
352
|
+
}).catch(() => {
|
|
353
|
+
}).finally(() => {
|
|
354
|
+
if (active) setReaderLoading(false);
|
|
355
|
+
});
|
|
356
|
+
return () => {
|
|
357
|
+
active = false;
|
|
358
|
+
};
|
|
359
|
+
}, [readerAccountUrl]);
|
|
318
360
|
function setPanel(next) {
|
|
319
|
-
if (review?.identity_required && !["article", "history", "identity", "share"].includes(next)) {
|
|
361
|
+
if (review?.identity_required && !["article", "history", "identity", "share", "question-list"].includes(next)) {
|
|
320
362
|
setRequestedPanel(next);
|
|
321
363
|
setPanelState("identity");
|
|
322
364
|
} else setPanelState(next);
|
|
@@ -414,10 +456,41 @@ function StoryReview({
|
|
|
414
456
|
setReview(data);
|
|
415
457
|
}
|
|
416
458
|
}
|
|
459
|
+
async function connectReader(target = requestedPanel) {
|
|
460
|
+
setBusy(true);
|
|
461
|
+
setError("");
|
|
462
|
+
try {
|
|
463
|
+
await request("/account", {});
|
|
464
|
+
await load();
|
|
465
|
+
setPanelState(target);
|
|
466
|
+
setReturnPanel("");
|
|
467
|
+
const url = new URL(window.location.href);
|
|
468
|
+
url.searchParams.delete("respond");
|
|
469
|
+
window.history.replaceState(null, "", url.pathname + url.search);
|
|
470
|
+
} catch (e) {
|
|
471
|
+
setError(e.message);
|
|
472
|
+
setPanelState("identity");
|
|
473
|
+
} finally {
|
|
474
|
+
setBusy(false);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
useEffect(() => {
|
|
478
|
+
if (!returnPanel || !review || !reader?.email_verified || resumed.current) return;
|
|
479
|
+
resumed.current = true;
|
|
480
|
+
void connectReader(returnPanel);
|
|
481
|
+
}, [returnPanel, review, reader]);
|
|
417
482
|
useEffect(() => {
|
|
418
483
|
let active = true;
|
|
419
484
|
void (async () => {
|
|
420
485
|
try {
|
|
486
|
+
const returnUrl = new URL(window.location.href);
|
|
487
|
+
if (returnUrl.searchParams.get("signin") === "retry") setError("Sign-in wasn\u2019t completed. Try again or use an email code.");
|
|
488
|
+
const respond = returnUrl.searchParams.get("respond") || "";
|
|
489
|
+
if (["feedback", "changes", "questions", "media", "comment", "edits"].includes(respond)) {
|
|
490
|
+
setRequestedPanel(respond);
|
|
491
|
+
setReturnPanel(respond);
|
|
492
|
+
setPanelState("identity");
|
|
493
|
+
}
|
|
421
494
|
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);
|
|
422
495
|
let exchange = {};
|
|
423
496
|
try {
|
|
@@ -429,7 +502,9 @@ function StoryReview({
|
|
|
429
502
|
if (address) {
|
|
430
503
|
const canonical = new URL(address, window.location.origin);
|
|
431
504
|
if (canonical.origin === window.location.origin) {
|
|
432
|
-
|
|
505
|
+
const address2 = new URL(canonical);
|
|
506
|
+
if (respond) address2.searchParams.set("respond", respond);
|
|
507
|
+
window.history.replaceState(null, "", address2.pathname + address2.search);
|
|
433
508
|
if (active) {
|
|
434
509
|
setShareUrl(canonical.href);
|
|
435
510
|
setCanShare(exchange.can_share === true);
|
|
@@ -594,6 +669,15 @@ function StoryReview({
|
|
|
594
669
|
(r) => r.id === (compareTo || review?.revision.id)
|
|
595
670
|
);
|
|
596
671
|
const errorMessage = error ? /* @__PURE__ */ jsx("p", { ref: errorRef, tabIndex: -1, role: "alert", className: "sm-review-alert", children: error }) : null;
|
|
672
|
+
const emailLink = review ? reviewEmailLink(feedbackEmail, review.revision.document.title, reviewEmailText(review)) : void 0;
|
|
673
|
+
const questionText = review ? reviewEmailText(review, answers) : "";
|
|
674
|
+
const questionsEmail = review ? reviewEmailLink(feedbackEmail, review.revision.document.title, questionText) : void 0;
|
|
675
|
+
const signInReturn = shareUrl ? new URL(shareUrl).pathname + new URL(shareUrl).search + `&respond=${encodeURIComponent(requestedPanel)}` : "";
|
|
676
|
+
const emailAlternative = emailLink && /* @__PURE__ */ jsxs("p", { className: "sm-review-email-alternative", children: [
|
|
677
|
+
"Prefer email? ",
|
|
678
|
+
/* @__PURE__ */ jsx("a", { href: emailLink, children: "Email the newsroom" }),
|
|
679
|
+
". Your email app will open; send your message there."
|
|
680
|
+
] });
|
|
597
681
|
return /* @__PURE__ */ jsxs("div", { className: `sm-review-frame${publicationHeader ? " sm-review-publication-frame" : ""}`, children: [
|
|
598
682
|
/* @__PURE__ */ jsx("div", { className: "sm-review-banner-wrap", children: /* @__PURE__ */ jsxs("div", { className: "sm-review-private", children: [
|
|
599
683
|
/* @__PURE__ */ jsx("strong", { children: review?.published ? "Private review" : "Private preview \xB7 Not published" }),
|
|
@@ -794,7 +878,7 @@ function StoryReview({
|
|
|
794
878
|
{
|
|
795
879
|
onClick: () => {
|
|
796
880
|
setMoreQuestions(false);
|
|
797
|
-
setPanel("questions");
|
|
881
|
+
setPanel(review.identity_required ? "question-list" : "questions");
|
|
798
882
|
},
|
|
799
883
|
children: "Tell Us More"
|
|
800
884
|
}
|
|
@@ -807,7 +891,8 @@ function StoryReview({
|
|
|
807
891
|
children: "Prefer Not to Publish"
|
|
808
892
|
}
|
|
809
893
|
),
|
|
810
|
-
can("COMMENT") && /* @__PURE__ */ jsx("button", { className: "sm-review-quiet", onClick: () => setPanel("comment"), children: "Leave a Comment" })
|
|
894
|
+
can("COMMENT") && /* @__PURE__ */ jsx("button", { className: "sm-review-quiet", onClick: () => setPanel("comment"), children: "Leave a Comment" }),
|
|
895
|
+
emailAlternative
|
|
811
896
|
] }),
|
|
812
897
|
/* @__PURE__ */ jsxs(
|
|
813
898
|
"nav",
|
|
@@ -830,7 +915,7 @@ function StoryReview({
|
|
|
830
915
|
can("UPLOAD_MEDIA") && /* @__PURE__ */ jsx("button", { disabled: busy, onClick: () => openMedia(), children: celebration ? "Add Photos" : "Add photos" }),
|
|
831
916
|
!celebration && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
832
917
|
can("PROPOSE_EDITS") && /* @__PURE__ */ jsx("button", { disabled: busy, onClick: () => setPanel("edits"), children: "Suggest edits" }),
|
|
833
|
-
can("ANSWER_QUESTIONS") && review.questions.length > 0 && /* @__PURE__ */ jsx("button", { disabled: busy, onClick: () => setPanel("questions"), children: "Answer questions" }),
|
|
918
|
+
can("ANSWER_QUESTIONS") && review.questions.length > 0 && /* @__PURE__ */ jsx("button", { disabled: busy, onClick: () => setPanel(review.identity_required ? "question-list" : "questions"), children: "Answer questions" }),
|
|
834
919
|
can("COMMENT") && /* @__PURE__ */ jsx("button", { disabled: busy, onClick: () => setPanel("comment"), children: "Leave a comment" })
|
|
835
920
|
] })
|
|
836
921
|
]
|
|
@@ -852,7 +937,10 @@ function StoryReview({
|
|
|
852
937
|
panel === "share" && /* @__PURE__ */ jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Share private preview", children: [
|
|
853
938
|
/* @__PURE__ */ jsx("h2", { children: "Share with your family" }),
|
|
854
939
|
errorMessage,
|
|
855
|
-
/* @__PURE__ */
|
|
940
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
941
|
+
"Anyone with this link can read the private preview. ",
|
|
942
|
+
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."
|
|
943
|
+
] }),
|
|
856
944
|
/* @__PURE__ */ jsx("label", { htmlFor: "review-share-url", children: "Private preview link" }),
|
|
857
945
|
/* @__PURE__ */ jsx("input", { id: "review-share-url", readOnly: true, value: shareUrl, onFocus: (e) => e.currentTarget.select() }),
|
|
858
946
|
/* @__PURE__ */ jsx("button", { onClick: async () => {
|
|
@@ -865,11 +953,48 @@ function StoryReview({
|
|
|
865
953
|
}, children: "Copy private link" }),
|
|
866
954
|
/* @__PURE__ */ jsx("button", { onClick: () => setPanel("article"), children: "Back to the story" })
|
|
867
955
|
] }),
|
|
956
|
+
panel === "question-list" && /* @__PURE__ */ jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Optional story questions", children: [
|
|
957
|
+
/* @__PURE__ */ jsxs("div", { className: "sm-review-form-heading", children: [
|
|
958
|
+
/* @__PURE__ */ jsx("h2", { children: "Tell us a little more" }),
|
|
959
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setPanel("article"), children: "Close" })
|
|
960
|
+
] }),
|
|
961
|
+
/* @__PURE__ */ jsx("p", { children: "Completely optional. Read the questions first, then answer online or copy them into an email. Even one answer helps." }),
|
|
962
|
+
/* @__PURE__ */ jsx("ol", { className: "sm-review-question-list", children: review.questions.filter((q, i) => moreQuestions || i < 5 || q.required).map((q) => /* @__PURE__ */ jsx("li", { children: q.prompt }, q.id)) }),
|
|
963
|
+
!moreQuestions && review.questions.length > 5 && /* @__PURE__ */ jsx("button", { onClick: () => setMoreQuestions(true), children: "See all questions" }),
|
|
964
|
+
/* @__PURE__ */ jsx("button", { className: "sm-review-primary", onClick: () => setPanel("questions"), children: "Answer online" }),
|
|
965
|
+
/* @__PURE__ */ jsx("p", { children: "Sign in to save your answers as you go. You can return whenever it suits you." }),
|
|
966
|
+
/* @__PURE__ */ jsx("button", { onClick: async () => {
|
|
967
|
+
try {
|
|
968
|
+
await navigator.clipboard.writeText(questionText);
|
|
969
|
+
setNotice("Questions copied. Paste them into an email and answer as many as you like.");
|
|
970
|
+
} catch {
|
|
971
|
+
setCopiedQuestions(true);
|
|
972
|
+
}
|
|
973
|
+
}, children: "Copy all questions" }),
|
|
974
|
+
copiedQuestions && /* @__PURE__ */ jsxs("label", { children: [
|
|
975
|
+
"Copy these questions",
|
|
976
|
+
/* @__PURE__ */ jsx("textarea", { readOnly: true, rows: 10, value: questionText, onFocus: (e) => e.currentTarget.select() })
|
|
977
|
+
] }),
|
|
978
|
+
questionsEmail && /* @__PURE__ */ jsxs("p", { children: [
|
|
979
|
+
/* @__PURE__ */ jsx("a", { href: questionsEmail, children: "Answer by email" }),
|
|
980
|
+
" \u2014 opens your email app with the questions ready."
|
|
981
|
+
] })
|
|
982
|
+
] }),
|
|
868
983
|
panel === "identity" && /* @__PURE__ */ jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Introduce yourself", children: [
|
|
869
|
-
/* @__PURE__ */ jsx("h2", { children: identityStep === "code" ? "Check your email" : "
|
|
984
|
+
/* @__PURE__ */ jsx("h2", { children: identityStep === "code" ? "Check your email" : "Continue to help with the story" }),
|
|
870
985
|
errorMessage,
|
|
871
|
-
/* @__PURE__ */ jsx("p", { children: "Your name
|
|
872
|
-
identityStep === "name"
|
|
986
|
+
/* @__PURE__ */ jsx("p", { children: "Your name accompanies your feedback. Your email stays private." }),
|
|
987
|
+
identityStep === "name" && renderSignIn && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
988
|
+
readerLoading ? /* @__PURE__ */ jsx("p", { role: "status", children: "Checking your sign-in\u2026" }) : reader?.email_verified ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
989
|
+
/* @__PURE__ */ jsxs("button", { className: "sm-review-primary", disabled: busy, onClick: () => void connectReader(), children: [
|
|
990
|
+
"Continue as ",
|
|
991
|
+
reader.full_name || reader.email
|
|
992
|
+
] }),
|
|
993
|
+
/* @__PURE__ */ jsx("p", { children: "Your reader account is ready. No email code is needed." })
|
|
994
|
+
] }) : renderSignIn(signInReturn),
|
|
995
|
+
!useEmailCode && /* @__PURE__ */ jsx("button", { className: "sm-review-quiet", onClick: () => setUseEmailCode(true), children: "Use an email code instead" })
|
|
996
|
+
] }),
|
|
997
|
+
identityStep === "name" && useEmailCode && /* @__PURE__ */ jsxs("form", { onSubmit: async (e) => {
|
|
873
998
|
e.preventDefault();
|
|
874
999
|
setBusy(true);
|
|
875
1000
|
setError("");
|
|
@@ -889,8 +1014,10 @@ function StoryReview({
|
|
|
889
1014
|
/* @__PURE__ */ jsx("label", { htmlFor: "contributor-email", children: "Your email" }),
|
|
890
1015
|
/* @__PURE__ */ jsx("input", { id: "contributor-email", type: "email", autoComplete: "email", value: contributorEmail, maxLength: 320, required: true, onChange: (e) => setContributorEmail(e.target.value) }),
|
|
891
1016
|
/* @__PURE__ */ jsx("p", { children: "We'll email you a code to confirm it's you." }),
|
|
1017
|
+
/* @__PURE__ */ jsx("p", { children: "No password or account is needed for this option." }),
|
|
892
1018
|
/* @__PURE__ */ jsx("button", { disabled: busy, children: busy ? "Sending your code\u2026" : "Email me a code" })
|
|
893
|
-
] })
|
|
1019
|
+
] }),
|
|
1020
|
+
identityStep === "code" && /* @__PURE__ */ jsxs("form", { onSubmit: async (e) => {
|
|
894
1021
|
e.preventDefault();
|
|
895
1022
|
setBusy(true);
|
|
896
1023
|
setError("");
|
|
@@ -926,6 +1053,7 @@ function StoryReview({
|
|
|
926
1053
|
}, children: "Send a new code" }),
|
|
927
1054
|
/* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: () => setIdentityStep("name"), children: "Use a different email" })
|
|
928
1055
|
] }),
|
|
1056
|
+
emailAlternative,
|
|
929
1057
|
/* @__PURE__ */ jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Just read the story" })
|
|
930
1058
|
] }),
|
|
931
1059
|
panel === "feedback" && /* @__PURE__ */ jsxs("section", { ref: responseRef, tabIndex: -1, className: "sm-review-form", "aria-label": "Family feedback", children: [
|
|
@@ -939,7 +1067,7 @@ function StoryReview({
|
|
|
939
1067
|
/* @__PURE__ */ 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" }),
|
|
940
1068
|
/* @__PURE__ */ jsx("button", { disabled: busy, onClick: () => setPanel("article"), children: "Back to the story" })
|
|
941
1069
|
] }),
|
|
942
|
-
!["article", "identity", "share", "feedback"].includes(panel) && /* @__PURE__ */ jsxs(
|
|
1070
|
+
!["article", "identity", "share", "feedback", "question-list"].includes(panel) && /* @__PURE__ */ jsxs(
|
|
943
1071
|
"section",
|
|
944
1072
|
{
|
|
945
1073
|
ref: responseRef,
|
|
@@ -1060,7 +1188,11 @@ function StoryReview({
|
|
|
1060
1188
|
onClick: () => setMoreQuestions(true),
|
|
1061
1189
|
children: "Answer More Questions"
|
|
1062
1190
|
}
|
|
1063
|
-
)
|
|
1191
|
+
),
|
|
1192
|
+
questionsEmail && /* @__PURE__ */ jsxs("p", { children: [
|
|
1193
|
+
/* @__PURE__ */ jsx("a", { href: questionsEmail, children: "Email these answers instead" }),
|
|
1194
|
+
". Send them from your email app; your online draft stays saved."
|
|
1195
|
+
] })
|
|
1064
1196
|
]
|
|
1065
1197
|
}
|
|
1066
1198
|
),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/news-publication",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"description": "Shared server framework for ScaleMule local news publications. Walnut Creek Times is the broadsheet default; each paper supplies its own identity and feature flags.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
package/story-review.css
CHANGED
|
@@ -630,3 +630,8 @@
|
|
|
630
630
|
.sm-review-sharing button, .sm-review-identity-status button { min-height:48px; font:inherit; padding:10px 16px; cursor:pointer; }
|
|
631
631
|
.sm-review-form input#review-share-url { width:100%; font-size:16px; min-width:0; }
|
|
632
632
|
.sm-review-form label[for^="contributor-"] { display:block; margin-top:16px; }
|
|
633
|
+
|
|
634
|
+
.sm-review-question-list { padding-left: 1.5rem; margin: 1.5rem 0; }
|
|
635
|
+
.sm-review-question-list li { padding-left: .4rem; margin: 0 0 1.2rem; line-height: 1.6; }
|
|
636
|
+
.sm-review-email-alternative { border-top: 1px solid var(--sm-review-line, #d7d7d7); padding-top: 1rem; margin-top: 1.5rem; line-height: 1.6; }
|
|
637
|
+
.sm-review-email-alternative a { font-weight: 700; text-decoration: underline; text-underline-offset: .2em; }
|