@scalemule/news-publication 0.0.68 → 0.0.70

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.
@@ -18,4 +18,4 @@ declare function articlePresentation(value?: ArticlePresentation | null): Articl
18
18
  declare function articleHeroRatio(layout: ArticleLayout): number;
19
19
  declare function articleFormatLabel(format?: ArticleFormat | null): string | undefined;
20
20
 
21
- export { type ArticleFormat as A, type ArticlePresentation as a, type ArticleLayout as b, articleFormatLabel as c, articleHeroRatio as d, articlePresentation as e };
21
+ export { type ArticleFormat as A, type ArticleLayout as a, type ArticlePresentation as b, articleFormatLabel as c, articleHeroRatio as d, articlePresentation as e };
@@ -18,4 +18,4 @@ declare function articlePresentation(value?: ArticlePresentation | null): Articl
18
18
  declare function articleHeroRatio(layout: ArticleLayout): number;
19
19
  declare function articleFormatLabel(format?: ArticleFormat | null): string | undefined;
20
20
 
21
- export { type ArticleFormat as A, type ArticlePresentation as a, type ArticleLayout as b, articleFormatLabel as c, articleHeroRatio as d, articlePresentation as e };
21
+ export { type ArticleFormat as A, type ArticleLayout as a, type ArticlePresentation as b, articleFormatLabel as c, articleHeroRatio as d, articlePresentation as e };
@@ -39,6 +39,7 @@ var PublicationArticleClient = class {
39
39
  method,
40
40
  headers,
41
41
  body: body === void 0 ? void 0 : form ? body : JSON.stringify(body),
42
+ signal: AbortSignal.timeout(form ? 9e4 : 3e4),
42
43
  cache: "no-store",
43
44
  redirect: "error"
44
45
  });
@@ -61,7 +62,17 @@ var PublicationArticleClient = class {
61
62
  );
62
63
  }
63
64
  processing(articleId) {
64
- return this.request(`/articles/${encodeURIComponent(articleId)}/processing`);
65
+ return this.request(
66
+ `/articles/${encodeURIComponent(articleId)}/processing`
67
+ );
68
+ }
69
+ /** Existing private narration path for a saved revision and a before-publication audio policy. */
70
+ narrateRevision(articleId, expectedGeneration) {
71
+ return this.request(
72
+ `/articles/${encodeURIComponent(articleId)}/collaboration/narrate`,
73
+ "POST",
74
+ { expected_generation: expectedGeneration }
75
+ );
65
76
  }
66
77
  retryProcessing(articleId, expectedVersion, assetSourceFileId) {
67
78
  return this.request(
@@ -1,4 +1,4 @@
1
- import { A as ArticleFormat } from './article-presentation-0d1FiDgA.cjs';
1
+ import { A as ArticleFormat, a as ArticleLayout } from './article-presentation-BrNCcF-5.cjs';
2
2
 
3
3
  type ArticleDocument = {
4
4
  title: string;
@@ -33,6 +33,55 @@ type AssetMetadata = {
33
33
  rights_confirmed: boolean;
34
34
  private_preview_only?: boolean;
35
35
  };
36
+ type ArticleProcessing = {
37
+ article_id: string;
38
+ version: number;
39
+ publication_status: string;
40
+ narration: {
41
+ status: string;
42
+ attempts?: number | null;
43
+ error_code?: string | null;
44
+ last_progress_at?: string | null;
45
+ };
46
+ visual: {
47
+ status: string;
48
+ provider: string;
49
+ policy_governed: boolean;
50
+ };
51
+ assets: {
52
+ source_file_id: string;
53
+ public_file_id: string;
54
+ status: string;
55
+ attempts: number;
56
+ error_code?: string | null;
57
+ last_progress_at: string;
58
+ }[];
59
+ };
60
+ type ArticleWorkflowOptions = {
61
+ workflow_version: number;
62
+ formats: ArticleFormat[];
63
+ layouts: {
64
+ definition: ArticleLayout;
65
+ visual_requirements: {
66
+ hero_aspect: string;
67
+ mobile_aspect: string;
68
+ share_aspect: string;
69
+ };
70
+ }[];
71
+ sections: string[];
72
+ language: string;
73
+ narration?: {
74
+ provider: string;
75
+ model: string;
76
+ voice: string;
77
+ required_before_publish: boolean;
78
+ } | null;
79
+ capabilities: {
80
+ managed_private_asset_upload: boolean;
81
+ inline_media_body?: boolean;
82
+ automatic_public_narration?: boolean;
83
+ };
84
+ };
36
85
  type ArticleOperation = {
37
86
  article_id: string;
38
87
  revision_id: string;
@@ -78,23 +127,14 @@ declare class PublicationArticleClient {
78
127
  fetch?: typeof fetch;
79
128
  });
80
129
  private request;
81
- options(articleId?: string): Promise<{
82
- workflow_version: number;
83
- formats: string[];
84
- layouts: unknown[];
85
- capabilities: {
86
- managed_private_asset_upload: boolean;
87
- };
88
- }>;
130
+ options(articleId?: string): Promise<ArticleWorkflowOptions>;
89
131
  get(articleId: string): Promise<Record>;
90
- processing(articleId: string): Promise<{
91
- narration: {
92
- status: string;
93
- error_code?: string;
94
- };
95
- visual: {
96
- status: string;
97
- };
132
+ processing(articleId: string): Promise<ArticleProcessing>;
133
+ /** Existing private narration path for a saved revision and a before-publication audio policy. */
134
+ narrateRevision(articleId: string, expectedGeneration: number): Promise<{
135
+ status: string;
136
+ audio_id?: string | null;
137
+ job_id?: string | null;
98
138
  }>;
99
139
  retryProcessing(articleId: string, expectedVersion: number, assetSourceFileId?: string): Promise<unknown>;
100
140
  create(key: string, authorProfileId: string, document: ArticleDocument, reviewPolicy?: string): Promise<ArticleOperation>;
@@ -128,4 +168,4 @@ declare class PublicationArticleClient {
128
168
  }): Promise<ArticleOperation>;
129
169
  }
130
170
 
131
- export { type ArticleDocument, type ArticleMedia, type ArticleOperation, ArticleWorkflowError, type AssetMetadata, PublicationArticleClient };
171
+ export { type ArticleDocument, type ArticleMedia, type ArticleOperation, type ArticleProcessing, ArticleWorkflowError, type ArticleWorkflowOptions, type AssetMetadata, PublicationArticleClient };
@@ -1,4 +1,4 @@
1
- import { A as ArticleFormat } from './article-presentation-0d1FiDgA.js';
1
+ import { A as ArticleFormat, a as ArticleLayout } from './article-presentation-BrNCcF-5.js';
2
2
 
3
3
  type ArticleDocument = {
4
4
  title: string;
@@ -33,6 +33,55 @@ type AssetMetadata = {
33
33
  rights_confirmed: boolean;
34
34
  private_preview_only?: boolean;
35
35
  };
36
+ type ArticleProcessing = {
37
+ article_id: string;
38
+ version: number;
39
+ publication_status: string;
40
+ narration: {
41
+ status: string;
42
+ attempts?: number | null;
43
+ error_code?: string | null;
44
+ last_progress_at?: string | null;
45
+ };
46
+ visual: {
47
+ status: string;
48
+ provider: string;
49
+ policy_governed: boolean;
50
+ };
51
+ assets: {
52
+ source_file_id: string;
53
+ public_file_id: string;
54
+ status: string;
55
+ attempts: number;
56
+ error_code?: string | null;
57
+ last_progress_at: string;
58
+ }[];
59
+ };
60
+ type ArticleWorkflowOptions = {
61
+ workflow_version: number;
62
+ formats: ArticleFormat[];
63
+ layouts: {
64
+ definition: ArticleLayout;
65
+ visual_requirements: {
66
+ hero_aspect: string;
67
+ mobile_aspect: string;
68
+ share_aspect: string;
69
+ };
70
+ }[];
71
+ sections: string[];
72
+ language: string;
73
+ narration?: {
74
+ provider: string;
75
+ model: string;
76
+ voice: string;
77
+ required_before_publish: boolean;
78
+ } | null;
79
+ capabilities: {
80
+ managed_private_asset_upload: boolean;
81
+ inline_media_body?: boolean;
82
+ automatic_public_narration?: boolean;
83
+ };
84
+ };
36
85
  type ArticleOperation = {
37
86
  article_id: string;
38
87
  revision_id: string;
@@ -78,23 +127,14 @@ declare class PublicationArticleClient {
78
127
  fetch?: typeof fetch;
79
128
  });
80
129
  private request;
81
- options(articleId?: string): Promise<{
82
- workflow_version: number;
83
- formats: string[];
84
- layouts: unknown[];
85
- capabilities: {
86
- managed_private_asset_upload: boolean;
87
- };
88
- }>;
130
+ options(articleId?: string): Promise<ArticleWorkflowOptions>;
89
131
  get(articleId: string): Promise<Record>;
90
- processing(articleId: string): Promise<{
91
- narration: {
92
- status: string;
93
- error_code?: string;
94
- };
95
- visual: {
96
- status: string;
97
- };
132
+ processing(articleId: string): Promise<ArticleProcessing>;
133
+ /** Existing private narration path for a saved revision and a before-publication audio policy. */
134
+ narrateRevision(articleId: string, expectedGeneration: number): Promise<{
135
+ status: string;
136
+ audio_id?: string | null;
137
+ job_id?: string | null;
98
138
  }>;
99
139
  retryProcessing(articleId: string, expectedVersion: number, assetSourceFileId?: string): Promise<unknown>;
100
140
  create(key: string, authorProfileId: string, document: ArticleDocument, reviewPolicy?: string): Promise<ArticleOperation>;
@@ -128,4 +168,4 @@ declare class PublicationArticleClient {
128
168
  }): Promise<ArticleOperation>;
129
169
  }
130
170
 
131
- export { type ArticleDocument, type ArticleMedia, type ArticleOperation, ArticleWorkflowError, type AssetMetadata, PublicationArticleClient };
171
+ export { type ArticleDocument, type ArticleMedia, type ArticleOperation, type ArticleProcessing, ArticleWorkflowError, type ArticleWorkflowOptions, type AssetMetadata, PublicationArticleClient };
@@ -37,6 +37,7 @@ var PublicationArticleClient = class {
37
37
  method,
38
38
  headers,
39
39
  body: body === void 0 ? void 0 : form ? body : JSON.stringify(body),
40
+ signal: AbortSignal.timeout(form ? 9e4 : 3e4),
40
41
  cache: "no-store",
41
42
  redirect: "error"
42
43
  });
@@ -59,7 +60,17 @@ var PublicationArticleClient = class {
59
60
  );
60
61
  }
61
62
  processing(articleId) {
62
- return this.request(`/articles/${encodeURIComponent(articleId)}/processing`);
63
+ return this.request(
64
+ `/articles/${encodeURIComponent(articleId)}/processing`
65
+ );
66
+ }
67
+ /** Existing private narration path for a saved revision and a before-publication audio policy. */
68
+ narrateRevision(articleId, expectedGeneration) {
69
+ return this.request(
70
+ `/articles/${encodeURIComponent(articleId)}/collaboration/narrate`,
71
+ "POST",
72
+ { expected_generation: expectedGeneration }
73
+ );
63
74
  }
64
75
  retryProcessing(articleId, expectedVersion, assetSourceFileId) {
65
76
  return this.request(
package/dist/index.d.cts CHANGED
@@ -17,7 +17,7 @@ export { C as CommunityMapArticleEmbed, c as communityMapEmbeds } from './commun
17
17
  export { C as ContentBlock, S as StructuredDocument, a as StructuredMedia, f as formatArtworkCaption, p as parseDocumentBlocks, r as renderArticleBlocksToHtml, s as sanitizeInlineMarkdown } from './article-blocks-DsabIDmH.cjs';
18
18
  import { M as Measurement } from './sponsorships-CtnBe3S8.cjs';
19
19
  export { a as Campaign, b as CampaignStatus, c as CampaignTarget, C as Creative, d as Placement, e as PlacementType, P as PublicAcknowledgment, S as SPONSORSHIP_OFFERINGS, f as SPONSORSHIP_POLICY, g as SponsorParty, p as placementAvailable, s as safeSponsorUrl } from './sponsorships-CtnBe3S8.cjs';
20
- export { A as ArticleFormat, b as ArticleLayout, a as ArticlePresentation, c as articleFormatLabel, d as articleHeroRatio, e as articlePresentation } from './article-presentation-0d1FiDgA.cjs';
20
+ export { A as ArticleFormat, a as ArticleLayout, b as ArticlePresentation, c as articleFormatLabel, d as articleHeroRatio, e as articlePresentation } from './article-presentation-BrNCcF-5.cjs';
21
21
  import './pets.cjs';
22
22
 
23
23
  /** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export { C as CommunityMapArticleEmbed, c as communityMapEmbeds } from './commun
17
17
  export { C as ContentBlock, S as StructuredDocument, a as StructuredMedia, f as formatArtworkCaption, p as parseDocumentBlocks, r as renderArticleBlocksToHtml, s as sanitizeInlineMarkdown } from './article-blocks-DsabIDmH.js';
18
18
  import { M as Measurement } from './sponsorships-CtnBe3S8.js';
19
19
  export { a as Campaign, b as CampaignStatus, c as CampaignTarget, C as Creative, d as Placement, e as PlacementType, P as PublicAcknowledgment, S as SPONSORSHIP_OFFERINGS, f as SPONSORSHIP_POLICY, g as SponsorParty, p as placementAvailable, s as safeSponsorUrl } from './sponsorships-CtnBe3S8.js';
20
- export { A as ArticleFormat, b as ArticleLayout, a as ArticlePresentation, c as articleFormatLabel, d as articleHeroRatio, e as articlePresentation } from './article-presentation-0d1FiDgA.js';
20
+ export { A as ArticleFormat, a as ArticleLayout, b as ArticlePresentation, c as articleFormatLabel, d as articleHeroRatio, e as articlePresentation } from './article-presentation-BrNCcF-5.js';
21
21
  import './pets.js';
22
22
 
23
23
  /** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
@@ -541,6 +541,7 @@ function EditorialNotices({ editorial }) {
541
541
  ] }, i))
542
542
  ] }) : null;
543
543
  }
544
+ var ReviewMediaSession = react.createContext(false);
544
545
  var human = (s) => s.toLowerCase().replaceAll("_", " ");
545
546
  function StoryReview({
546
547
  sessionId,
@@ -552,9 +553,11 @@ function StoryReview({
552
553
  renderSignIn,
553
554
  feedbackEmail,
554
555
  initialReview,
555
- onReviewLoaded
556
+ onReviewLoaded,
557
+ formatImageCaption = (value) => value || ""
556
558
  }) {
557
559
  const base = `/api/news/review/${encodeURIComponent(sessionId)}`;
560
+ const [mediaSessionReady, setMediaSessionReady] = react.useState(false);
558
561
  const [review, setReview] = react.useState(initialReview ?? 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("");
559
562
  const [shareUrl, setShareUrl] = react.useState(""), [canShare, setCanShare] = react.useState(false);
560
563
  const [contributorName, setContributorName] = react.useState(""), [contributorEmail, setContributorEmail] = react.useState("");
@@ -692,6 +695,7 @@ function StoryReview({
692
695
  setReview(null);
693
696
  } else {
694
697
  setVerification(false);
698
+ setMediaSessionReady(true);
695
699
  setReview(data);
696
700
  }
697
701
  }
@@ -757,6 +761,7 @@ function StoryReview({
757
761
  setReview(null);
758
762
  } else {
759
763
  setVerification(false);
764
+ setMediaSessionReady(true);
760
765
  setReview(data);
761
766
  }
762
767
  }
@@ -932,15 +937,11 @@ function StoryReview({
932
937
  ));
933
938
  const generated = review?.generated_visual;
934
939
  const hero = sourceHero?.length ? sourceHero : generated && /^https:\/\//.test(generated.url) ? /* @__PURE__ */ jsxRuntime.jsxs("figure", { children: [
935
- /* @__PURE__ */ jsxRuntime.jsx("img", { src: generated.url, alt: generated.alt || review?.revision.document.title || "Article illustration" }),
936
- /* @__PURE__ */ jsxRuntime.jsxs("figcaption", { children: [
937
- generated.caption,
938
- " ",
939
- generated.label
940
- ] })
940
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: generated.url, alt: formatImageCaption(generated.alt) || review?.revision.document.title || "Article illustration" }),
941
+ formatImageCaption(generated.caption) && /* @__PURE__ */ jsxRuntime.jsx("figcaption", { children: formatImageCaption(generated.caption) })
941
942
  ] }) : null;
942
943
  const heroBeforeHeadline = presentation.layout.headline_position === "after_hero";
943
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `sm-review-frame${publicationHeader ? " sm-review-publication-frame" : ""}`, children: [
944
+ return /* @__PURE__ */ jsxRuntime.jsx(ReviewMediaSession.Provider, { value: mediaSessionReady, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `sm-review-frame${publicationHeader ? " sm-review-publication-frame" : ""}`, children: [
944
945
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-banner-wrap", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-private", children: [
945
946
  /* @__PURE__ */ jsxRuntime.jsx("strong", { children: review?.article_published || review?.published ? "Private review" : "Private preview \xB7 Not published" }),
946
947
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: review?.published ? "This revision has been published. This review link remains private." : review?.article_published ? "The article is published. This private link preserves the version you were invited to review." : `Prepared by ${publicationName} for ${celebration ? "family " : ""}review.` })
@@ -1902,7 +1903,7 @@ function StoryReview({
1902
1903
  ] })
1903
1904
  ] }),
1904
1905
  publicationFooter
1905
- ] });
1906
+ ] }) });
1906
1907
  }
1907
1908
  function PrivatePhoto({
1908
1909
  base,
@@ -1912,9 +1913,11 @@ function PrivatePhoto({
1912
1913
  disabled,
1913
1914
  isInline
1914
1915
  }) {
1916
+ const sessionReady = react.useContext(ReviewMediaSession);
1915
1917
  const [url, setUrl] = react.useState("");
1916
1918
  const [video, setVideo] = react.useState(false);
1917
1919
  react.useEffect(() => {
1920
+ if (!sessionReady) return;
1918
1921
  let active = true, object = "";
1919
1922
  void fetch(`${base}/media/${encodeURIComponent(media.file_id)}`, {
1920
1923
  cache: "no-store",
@@ -1933,7 +1936,7 @@ function PrivatePhoto({
1933
1936
  active = false;
1934
1937
  if (object) URL.revokeObjectURL(object);
1935
1938
  };
1936
- }, [base, media.file_id]);
1939
+ }, [base, media.file_id, sessionReady]);
1937
1940
  const captionInfo = formatArtworkCaption(media);
1938
1941
  return /* @__PURE__ */ jsxRuntime.jsxs(
1939
1942
  "figure",
@@ -1957,7 +1960,7 @@ function PrivatePhoto({
1957
1960
  alt: media.alt || captionInfo.title || "Story photograph",
1958
1961
  referrerPolicy: "no-referrer"
1959
1962
  }
1960
- ) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Media unavailable." }),
1963
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { children: sessionReady ? "Media unavailable." : "Loading photograph\u2026" }),
1961
1964
  /* @__PURE__ */ jsxRuntime.jsxs("figcaption", { className: "sm-review-photo-details", children: [
1962
1965
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-review-caption-text", children: [
1963
1966
  captionInfo.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sm-review-artwork-title", children: /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
@@ -1,4 +1,4 @@
1
- import { A as ArticleFormat, a as ArticlePresentation } from './article-presentation-0d1FiDgA.cjs';
1
+ import { A as ArticleFormat, b as ArticlePresentation } from './article-presentation-BrNCcF-5.cjs';
2
2
  import React__default from 'react';
3
3
  import { C as ContentBlock } from './article-blocks-DsabIDmH.cjs';
4
4
 
@@ -107,7 +107,7 @@ type StoryReviewData = {
107
107
  celebration_type?: string | null;
108
108
  decision?: string | null;
109
109
  };
110
- declare function StoryReview({ sessionId, publicationName, nameplateUrl, publicationHeader, publicationFooter, readerAccountUrl, renderSignIn, feedbackEmail, initialReview, onReviewLoaded, }: {
110
+ declare function StoryReview({ sessionId, publicationName, nameplateUrl, publicationHeader, publicationFooter, readerAccountUrl, renderSignIn, feedbackEmail, initialReview, onReviewLoaded, formatImageCaption, }: {
111
111
  sessionId: string;
112
112
  publicationName: string;
113
113
  nameplateUrl?: string;
@@ -121,6 +121,7 @@ declare function StoryReview({ sessionId, publicationName, nameplateUrl, publica
121
121
  /** Only a server-validated private capability may supply this snapshot. Never cache publicly. */
122
122
  initialReview?: StoryReviewData;
123
123
  onReviewLoaded?: (review: StoryReviewData) => void;
124
+ formatImageCaption?: (value?: string | null) => string;
124
125
  }): React__default.JSX.Element;
125
126
  /** Paragraph comparisons preserve readable prose and identify title/dek and media changes. */
126
127
  declare function ReviewDiff({ before, after, }: {
@@ -1,4 +1,4 @@
1
- import { A as ArticleFormat, a as ArticlePresentation } from './article-presentation-0d1FiDgA.js';
1
+ import { A as ArticleFormat, b as ArticlePresentation } from './article-presentation-BrNCcF-5.js';
2
2
  import React__default from 'react';
3
3
  import { C as ContentBlock } from './article-blocks-DsabIDmH.js';
4
4
 
@@ -107,7 +107,7 @@ type StoryReviewData = {
107
107
  celebration_type?: string | null;
108
108
  decision?: string | null;
109
109
  };
110
- declare function StoryReview({ sessionId, publicationName, nameplateUrl, publicationHeader, publicationFooter, readerAccountUrl, renderSignIn, feedbackEmail, initialReview, onReviewLoaded, }: {
110
+ declare function StoryReview({ sessionId, publicationName, nameplateUrl, publicationHeader, publicationFooter, readerAccountUrl, renderSignIn, feedbackEmail, initialReview, onReviewLoaded, formatImageCaption, }: {
111
111
  sessionId: string;
112
112
  publicationName: string;
113
113
  nameplateUrl?: string;
@@ -121,6 +121,7 @@ declare function StoryReview({ sessionId, publicationName, nameplateUrl, publica
121
121
  /** Only a server-validated private capability may supply this snapshot. Never cache publicly. */
122
122
  initialReview?: StoryReviewData;
123
123
  onReviewLoaded?: (review: StoryReviewData) => void;
124
+ formatImageCaption?: (value?: string | null) => string;
124
125
  }): React__default.JSX.Element;
125
126
  /** Paragraph comparisons preserve readable prose and identify title/dek and media changes. */
126
127
  declare function ReviewDiff({ before, after, }: {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { articlePresentation, articleFormatLabel, parseDocumentBlocks, formatArtworkCaption } from './chunk-DN3TBZLK.js';
3
- import { useState, useRef, useEffect } from 'react';
3
+ import { createContext, useState, useRef, useEffect, useContext } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
6
6
  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;
@@ -320,6 +320,7 @@ function EditorialNotices({ editorial }) {
320
320
  ] }, i))
321
321
  ] }) : null;
322
322
  }
323
+ var ReviewMediaSession = createContext(false);
323
324
  var human = (s) => s.toLowerCase().replaceAll("_", " ");
324
325
  function StoryReview({
325
326
  sessionId,
@@ -331,9 +332,11 @@ function StoryReview({
331
332
  renderSignIn,
332
333
  feedbackEmail,
333
334
  initialReview,
334
- onReviewLoaded
335
+ onReviewLoaded,
336
+ formatImageCaption = (value) => value || ""
335
337
  }) {
336
338
  const base = `/api/news/review/${encodeURIComponent(sessionId)}`;
339
+ const [mediaSessionReady, setMediaSessionReady] = useState(false);
337
340
  const [review, setReview] = useState(initialReview ?? null), [error, setError] = useState(""), [notice, setNotice] = useState(""), [busy, setBusy] = useState(false), [panel, setPanelState] = useState("article"), [verification, setVerification] = useState(false), [code, setCode] = useState("");
338
341
  const [shareUrl, setShareUrl] = useState(""), [canShare, setCanShare] = useState(false);
339
342
  const [contributorName, setContributorName] = useState(""), [contributorEmail, setContributorEmail] = useState("");
@@ -471,6 +474,7 @@ function StoryReview({
471
474
  setReview(null);
472
475
  } else {
473
476
  setVerification(false);
477
+ setMediaSessionReady(true);
474
478
  setReview(data);
475
479
  }
476
480
  }
@@ -536,6 +540,7 @@ function StoryReview({
536
540
  setReview(null);
537
541
  } else {
538
542
  setVerification(false);
543
+ setMediaSessionReady(true);
539
544
  setReview(data);
540
545
  }
541
546
  }
@@ -711,15 +716,11 @@ function StoryReview({
711
716
  ));
712
717
  const generated = review?.generated_visual;
713
718
  const hero = sourceHero?.length ? sourceHero : generated && /^https:\/\//.test(generated.url) ? /* @__PURE__ */ jsxs("figure", { children: [
714
- /* @__PURE__ */ jsx("img", { src: generated.url, alt: generated.alt || review?.revision.document.title || "Article illustration" }),
715
- /* @__PURE__ */ jsxs("figcaption", { children: [
716
- generated.caption,
717
- " ",
718
- generated.label
719
- ] })
719
+ /* @__PURE__ */ jsx("img", { src: generated.url, alt: formatImageCaption(generated.alt) || review?.revision.document.title || "Article illustration" }),
720
+ formatImageCaption(generated.caption) && /* @__PURE__ */ jsx("figcaption", { children: formatImageCaption(generated.caption) })
720
721
  ] }) : null;
721
722
  const heroBeforeHeadline = presentation.layout.headline_position === "after_hero";
722
- return /* @__PURE__ */ jsxs("div", { className: `sm-review-frame${publicationHeader ? " sm-review-publication-frame" : ""}`, children: [
723
+ return /* @__PURE__ */ jsx(ReviewMediaSession.Provider, { value: mediaSessionReady, children: /* @__PURE__ */ jsxs("div", { className: `sm-review-frame${publicationHeader ? " sm-review-publication-frame" : ""}`, children: [
723
724
  /* @__PURE__ */ jsx("div", { className: "sm-review-banner-wrap", children: /* @__PURE__ */ jsxs("div", { className: "sm-review-private", children: [
724
725
  /* @__PURE__ */ jsx("strong", { children: review?.article_published || review?.published ? "Private review" : "Private preview \xB7 Not published" }),
725
726
  /* @__PURE__ */ jsx("span", { children: review?.published ? "This revision has been published. This review link remains private." : review?.article_published ? "The article is published. This private link preserves the version you were invited to review." : `Prepared by ${publicationName} for ${celebration ? "family " : ""}review.` })
@@ -1681,7 +1682,7 @@ function StoryReview({
1681
1682
  ] })
1682
1683
  ] }),
1683
1684
  publicationFooter
1684
- ] });
1685
+ ] }) });
1685
1686
  }
1686
1687
  function PrivatePhoto({
1687
1688
  base,
@@ -1691,9 +1692,11 @@ function PrivatePhoto({
1691
1692
  disabled,
1692
1693
  isInline
1693
1694
  }) {
1695
+ const sessionReady = useContext(ReviewMediaSession);
1694
1696
  const [url, setUrl] = useState("");
1695
1697
  const [video, setVideo] = useState(false);
1696
1698
  useEffect(() => {
1699
+ if (!sessionReady) return;
1697
1700
  let active = true, object = "";
1698
1701
  void fetch(`${base}/media/${encodeURIComponent(media.file_id)}`, {
1699
1702
  cache: "no-store",
@@ -1712,7 +1715,7 @@ function PrivatePhoto({
1712
1715
  active = false;
1713
1716
  if (object) URL.revokeObjectURL(object);
1714
1717
  };
1715
- }, [base, media.file_id]);
1718
+ }, [base, media.file_id, sessionReady]);
1716
1719
  const captionInfo = formatArtworkCaption(media);
1717
1720
  return /* @__PURE__ */ jsxs(
1718
1721
  "figure",
@@ -1736,7 +1739,7 @@ function PrivatePhoto({
1736
1739
  alt: media.alt || captionInfo.title || "Story photograph",
1737
1740
  referrerPolicy: "no-referrer"
1738
1741
  }
1739
- ) : /* @__PURE__ */ jsx("p", { children: "Media unavailable." }),
1742
+ ) : /* @__PURE__ */ jsx("p", { children: sessionReady ? "Media unavailable." : "Loading photograph\u2026" }),
1740
1743
  /* @__PURE__ */ jsxs("figcaption", { className: "sm-review-photo-details", children: [
1741
1744
  /* @__PURE__ */ jsxs("div", { className: "sm-review-caption-text", children: [
1742
1745
  captionInfo.title && /* @__PURE__ */ jsx("div", { className: "sm-review-artwork-title", children: /* @__PURE__ */ jsxs("strong", { children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
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",