@scalemule/news-publication 0.0.67 → 0.0.69

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.
@@ -0,0 +1,262 @@
1
+ // src/article-workflow-client.ts
2
+ var ArticleWorkflowError = class extends Error {
3
+ constructor(message, status, articleId) {
4
+ super(message);
5
+ this.status = status;
6
+ this.articleId = articleId;
7
+ this.name = "ArticleWorkflowError";
8
+ }
9
+ status;
10
+ articleId;
11
+ };
12
+ var PublicationArticleClient = class {
13
+ constructor(config) {
14
+ this.config = config;
15
+ const url = new URL(config.apiUrl);
16
+ if (url.protocol !== "https:" && !["localhost", "127.0.0.1"].includes(url.hostname))
17
+ throw new Error("Use an HTTPS API endpoint");
18
+ this.base = config.apiUrl.replace(/\/$/, "") + `/v1/blog/publications/${encodeURIComponent(config.publicationId)}`;
19
+ }
20
+ config;
21
+ base;
22
+ async request(path, method = "GET", body, extra) {
23
+ const headers = new Headers(extra);
24
+ headers.set("accept", "application/json");
25
+ if (this.config.apiKey) headers.set("x-api-key", this.config.apiKey);
26
+ if (this.config.accessToken)
27
+ headers.set("authorization", `Bearer ${this.config.accessToken}`);
28
+ const form = body instanceof FormData;
29
+ if (body !== void 0 && !form)
30
+ headers.set("content-type", "application/json");
31
+ const url = new URL(
32
+ path.startsWith("/v1/") ? this.config.apiUrl.replace(/\/$/, "") + path : this.base + path
33
+ );
34
+ if (this.config.applicationId)
35
+ url.searchParams.set("application_id", this.config.applicationId);
36
+ const response = await (this.config.fetch ?? fetch)(url, {
37
+ method,
38
+ headers,
39
+ body: body === void 0 ? void 0 : form ? body : JSON.stringify(body),
40
+ cache: "no-store",
41
+ redirect: "error"
42
+ });
43
+ const value = await response.json();
44
+ if (!response.ok || value.success !== true || value.data == null)
45
+ throw new ArticleWorkflowError(
46
+ value.error?.message || `Article API returned ${response.status}`,
47
+ response.status
48
+ );
49
+ return value.data;
50
+ }
51
+ options(articleId) {
52
+ return this.request(
53
+ `/article-workflow/options${articleId ? `?article_id=${encodeURIComponent(articleId)}` : ""}`
54
+ );
55
+ }
56
+ get(articleId) {
57
+ return this.request(
58
+ `/articles/${encodeURIComponent(articleId)}/collaboration`
59
+ );
60
+ }
61
+ processing(articleId) {
62
+ return this.request(`/articles/${encodeURIComponent(articleId)}/processing`);
63
+ }
64
+ retryProcessing(articleId, expectedVersion, assetSourceFileId) {
65
+ return this.request(
66
+ `/articles/${encodeURIComponent(articleId)}/processing`,
67
+ "POST",
68
+ {
69
+ expected_version: expectedVersion,
70
+ asset_source_file_id: assetSourceFileId
71
+ }
72
+ );
73
+ }
74
+ create(key, authorProfileId, document, reviewPolicy = "SUBJECT_APPROVAL_PLUS_EDITOR") {
75
+ return this.request(
76
+ "/article-workflow",
77
+ "POST",
78
+ {
79
+ operation: "create",
80
+ author_profile_id: authorProfileId,
81
+ document,
82
+ review_policy: reviewPolicy
83
+ },
84
+ { "Idempotency-Key": key }
85
+ );
86
+ }
87
+ save(key, articleId, generation, document, reason = "EDITOR_EDIT") {
88
+ return this.request(
89
+ "/article-workflow",
90
+ "POST",
91
+ {
92
+ operation: "save",
93
+ article_id: articleId,
94
+ expected_generation: generation,
95
+ document,
96
+ reason
97
+ },
98
+ { "Idempotency-Key": key }
99
+ );
100
+ }
101
+ command(articleId, generation, action) {
102
+ return this.request(
103
+ `/articles/${encodeURIComponent(articleId)}/collaboration`,
104
+ "POST",
105
+ { ...action, expected_generation: generation }
106
+ );
107
+ }
108
+ registerPhoto(fileId) {
109
+ return this.request(
110
+ `/v1/photos/${this.config.applicationId ? "admin/register" : "register"}`,
111
+ "POST",
112
+ {
113
+ file_id: fileId,
114
+ content_kind: "article",
115
+ publication_id: this.config.publicationId,
116
+ ...this.config.applicationId ? { application_id: this.config.applicationId } : {}
117
+ }
118
+ );
119
+ }
120
+ upload(articleId, generation, file, filename, metadata) {
121
+ const body = new FormData();
122
+ body.set("metadata", JSON.stringify(metadata));
123
+ body.set("file", file, filename);
124
+ return this.request(`/articles/${encodeURIComponent(articleId)}/assets`, "POST", body, {
125
+ "If-Match": String(generation)
126
+ });
127
+ }
128
+ /** One agent operation over existing owners. Stops at conflicts/unknown upload acknowledgement;
129
+ * replay the same operation key to inspect/reuse staged contributions before another transfer.
130
+ * Editorial approval and release are always separate explicit operations. */
131
+ async submitArticle(input) {
132
+ if (input.document.media?.length)
133
+ throw new Error(
134
+ "Pass file assets separately when creating an article package"
135
+ );
136
+ const capabilities = await this.options();
137
+ if (capabilities.workflow_version !== 1)
138
+ throw new Error("Unsupported article workflow version");
139
+ if (input.assets?.length && !capabilities.capabilities.managed_private_asset_upload)
140
+ throw new Error("Managed article upload is not deployed");
141
+ const created = await this.create(
142
+ input.operationKey,
143
+ input.authorProfileId,
144
+ input.document,
145
+ input.reviewPolicy
146
+ );
147
+ try {
148
+ const selected = [];
149
+ for (const asset of input.assets ?? []) {
150
+ let current = await this.get(created.article_id);
151
+ const digest = Array.from(
152
+ new Uint8Array(
153
+ await crypto.subtle.digest(
154
+ "SHA-256",
155
+ await asset.file.arrayBuffer()
156
+ )
157
+ )
158
+ ).map((v) => v.toString(16).padStart(2, "0")).join("");
159
+ const existing = current.contributions.find(
160
+ (c) => c.kind === "MEDIA" && c.state !== "REJECTED" && c.state !== "WITHDRAWN" && c.payload.sha256 === digest && Object.entries(asset.metadata).every(
161
+ ([k, v]) => c.payload[k] === v
162
+ )
163
+ );
164
+ const staged = existing?.payload.file_id ? { contribution_id: existing.id, file_id: existing.payload.file_id } : await this.upload(
165
+ created.article_id,
166
+ current.generation,
167
+ asset.file,
168
+ asset.filename,
169
+ asset.metadata
170
+ );
171
+ await this.registerPhoto(staged.file_id);
172
+ if (input.approveMedia) {
173
+ if (!asset.metadata.rights_confirmed || asset.metadata.private_preview_only)
174
+ throw new Error("Publication requires confirmed image rights");
175
+ if (existing?.state !== "APPROVED") {
176
+ current = await this.get(created.article_id);
177
+ await this.command(created.article_id, current.generation, {
178
+ action: "RESOLVE",
179
+ contribution_id: staged.contribution_id,
180
+ state: "APPROVED"
181
+ });
182
+ }
183
+ selected.push({
184
+ kind: "image",
185
+ contribution_id: staged.contribution_id,
186
+ file_id: staged.file_id,
187
+ caption: asset.metadata.caption,
188
+ credit: asset.metadata.credit,
189
+ alt: asset.metadata.alt,
190
+ position: asset.position
191
+ });
192
+ }
193
+ }
194
+ if (selected.length) {
195
+ const current = await this.get(created.article_id);
196
+ const document = { ...input.document, media: selected };
197
+ const saved = current.revisions.find(
198
+ (r) => r.id === current.current_revision_id
199
+ )?.document;
200
+ if (!saved) throw new Error("Current immutable revision is missing");
201
+ if (sameDocument(saved, document))
202
+ return {
203
+ ...created,
204
+ revision_id: current.current_revision_id,
205
+ generation: current.generation,
206
+ editorial_status: current.status,
207
+ replayed: true
208
+ };
209
+ if (!sameDocument(
210
+ { ...saved, media: [] },
211
+ { ...input.document, media: [] }
212
+ ) || saved.media?.length)
213
+ throw new ArticleWorkflowError(
214
+ "The article was edited after this package began; load its current revision before changing it",
215
+ 409,
216
+ created.article_id
217
+ );
218
+ return await this.save(
219
+ `${input.operationKey}:assets`,
220
+ created.article_id,
221
+ current.generation,
222
+ document,
223
+ "MEDIA_UPDATE"
224
+ );
225
+ }
226
+ return created;
227
+ } catch (error) {
228
+ if (error instanceof ArticleWorkflowError) {
229
+ error.articleId = created.article_id;
230
+ throw error;
231
+ }
232
+ throw new ArticleWorkflowError(
233
+ error instanceof Error ? error.message : "Article package interrupted",
234
+ 0,
235
+ created.article_id
236
+ );
237
+ }
238
+ }
239
+ };
240
+ function sameDocument(a, b) {
241
+ const canonical = (d) => [
242
+ d.title,
243
+ d.subtitle || "",
244
+ d.content_markdown,
245
+ d.section,
246
+ d.community || "",
247
+ d.format || null,
248
+ d.layout ? [d.layout.id, d.layout.version] : null,
249
+ (d.media || []).map((m) => [
250
+ m.kind || "image",
251
+ m.contribution_id,
252
+ m.file_id,
253
+ m.caption,
254
+ m.credit,
255
+ m.alt,
256
+ m.position
257
+ ])
258
+ ];
259
+ return JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
260
+ }
261
+
262
+ export { ArticleWorkflowError, PublicationArticleClient };
@@ -232,4 +232,40 @@ function renderArticleBlocksToHtml(blocks, mediaMap, options) {
232
232
  return parts.join("\n");
233
233
  }
234
234
 
235
- export { formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown };
235
+ // src/article-presentation.ts
236
+ var standard = {
237
+ id: "standard",
238
+ version: 1,
239
+ name: "Standard article",
240
+ renderer: "standard",
241
+ hero_aspect: "landscape",
242
+ headline_position: "before_hero",
243
+ wide_body: false
244
+ };
245
+ var formats = [
246
+ "news",
247
+ "brief",
248
+ "explainer",
249
+ "interview",
250
+ "opinion",
251
+ "photo_essay",
252
+ "video_story"
253
+ ];
254
+ function articlePresentation(value) {
255
+ const l = value?.layout;
256
+ if (!l || !/^[a-z0-9_-]{1,80}$/.test(l.id) || !Number.isInteger(l.version) || l.version < 1 || !["standard", "feature", "portrait"].includes(l.renderer) || !["landscape", "portrait", "square"].includes(l.hero_aspect) || !["before_hero", "after_hero"].includes(l.headline_position) || typeof l.wide_body !== "boolean") {
257
+ return { layout: standard };
258
+ }
259
+ return {
260
+ format: value?.format && formats.includes(value.format) ? value.format : void 0,
261
+ layout: l
262
+ };
263
+ }
264
+ function articleHeroRatio(layout) {
265
+ return layout.hero_aspect === "portrait" ? 4 / 5 : layout.hero_aspect === "square" ? 1 : 16 / 9;
266
+ }
267
+ function articleFormatLabel(format) {
268
+ return format === "opinion" ? "Opinion" : format === "explainer" ? "Explainer" : format === "interview" ? "Interview" : format === "photo_essay" ? "Photo essay" : format === "video_story" ? "Video story" : void 0;
269
+ }
270
+
271
+ export { articleFormatLabel, articleHeroRatio, articlePresentation, formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown };
package/dist/index.cjs CHANGED
@@ -3586,6 +3586,42 @@ function campaignReportHtml(report) {
3586
3586
  <footer><p>${escape(report.reporting_note)}</p><p>${escape(report.editorial_independence)}</p><p>Campaign reference: ${escape(report.campaign_id)}</p></footer></body></html>`;
3587
3587
  }
3588
3588
 
3589
+ // src/article-presentation.ts
3590
+ var standard = {
3591
+ id: "standard",
3592
+ version: 1,
3593
+ name: "Standard article",
3594
+ renderer: "standard",
3595
+ hero_aspect: "landscape",
3596
+ headline_position: "before_hero",
3597
+ wide_body: false
3598
+ };
3599
+ var formats = [
3600
+ "news",
3601
+ "brief",
3602
+ "explainer",
3603
+ "interview",
3604
+ "opinion",
3605
+ "photo_essay",
3606
+ "video_story"
3607
+ ];
3608
+ function articlePresentation(value) {
3609
+ const l = value?.layout;
3610
+ if (!l || !/^[a-z0-9_-]{1,80}$/.test(l.id) || !Number.isInteger(l.version) || l.version < 1 || !["standard", "feature", "portrait"].includes(l.renderer) || !["landscape", "portrait", "square"].includes(l.hero_aspect) || !["before_hero", "after_hero"].includes(l.headline_position) || typeof l.wide_body !== "boolean") {
3611
+ return { layout: standard };
3612
+ }
3613
+ return {
3614
+ format: value?.format && formats.includes(value.format) ? value.format : void 0,
3615
+ layout: l
3616
+ };
3617
+ }
3618
+ function articleHeroRatio(layout) {
3619
+ return layout.hero_aspect === "portrait" ? 4 / 5 : layout.hero_aspect === "square" ? 1 : 16 / 9;
3620
+ }
3621
+ function articleFormatLabel(format) {
3622
+ return format === "opinion" ? "Opinion" : format === "explainer" ? "Explainer" : format === "interview" ? "Interview" : format === "photo_essay" ? "Photo essay" : format === "video_story" ? "Video story" : void 0;
3623
+ }
3624
+
3589
3625
  Object.defineProperty(exports, "COOKIE_CONSENT_BACKUP_KEY", {
3590
3626
  enumerable: true,
3591
3627
  get: function () { return compliance.COOKIE_CONSENT_BACKUP_KEY; }
@@ -3726,7 +3762,10 @@ exports.applyNavigationConfig = applyNavigationConfig;
3726
3762
  exports.applyTheme = applyTheme;
3727
3763
  exports.articleCacheKey = articleCacheKey;
3728
3764
  exports.articleCacheOptions = articleCacheOptions;
3765
+ exports.articleFormatLabel = articleFormatLabel;
3766
+ exports.articleHeroRatio = articleHeroRatio;
3729
3767
  exports.articleMeta = articleMeta;
3768
+ exports.articlePresentation = articlePresentation;
3730
3769
  exports.articleUrl = articleUrl;
3731
3770
  exports.attribution = attribution;
3732
3771
  exports.briefSummary = briefSummary;
package/dist/index.d.cts CHANGED
@@ -17,6 +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
21
  import './pets.cjs';
21
22
 
22
23
  /** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
package/dist/index.d.ts CHANGED
@@ -17,6 +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
21
  import './pets.js';
21
22
 
22
23
  /** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
package/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
+ export { NetworkCopyrightNotice } from './chunk-FWEYKWKP.js';
1
2
  export { CURATED_COMMUNITY_CARDS, VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-KYUVXEJY.js';
3
+ export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-AMLVCZDP.js';
2
4
  export { CookieConsentBanner } from './chunk-XSAVJJVW.js';
3
5
  export { CookieSettingsView } from './chunk-X7AU7DV6.js';
4
6
  export { THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, ThemeToggle, applyTheme, clearTheme, getStoredTheme, getSystemTheme, resolveEffectiveTheme, setTheme, subscribeTheme, themeInitScript } from './chunk-NQD3M7ZY.js';
5
7
  export { LocalNetworkBar } from './chunk-Q77T2S7B.js';
6
- export { NetworkCopyrightNotice } from './chunk-FWEYKWKP.js';
7
8
  import { localPublications } from './chunk-XWXPERLK.js';
8
9
  export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EDITORIAL_PRINCIPLES, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, NETWORK_PARENT, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, consentModel, decorateNetworkUrl, effectivePreferences, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub, topicalPublications } from './chunk-XWXPERLK.js';
9
10
  export { communityMapEmbeds } from './chunk-IMYBSG7J.js';
10
- export { formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown } from './chunk-OISLCYER.js';
11
+ export { articleFormatLabel, articleHeroRatio, articlePresentation, formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown } from './chunk-DN3TBZLK.js';
11
12
  import { definePublication } from './chunk-57YVL6BD.js';
12
13
  export { BROADSHEET_THEME, FEATURE_NAMES, LAYOUT_COOKIE, acceptRemoteRecord, acceptSnapshot, definePublication, featureEnabled, parseSnapshot, publicationFromSnapshot, publicationRecord, resolveLayout, snapshotFromRecord, syncPublication } from './chunk-57YVL6BD.js';
13
14
  export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedPath, classifiedSearchPath, clipChars, dealDollars, dealGoPath, dealsFeedPath, employmentLabel, isClassifiedCategory, isEmploymentType, jobEmployerBody, jobOpeningBody, jobPostingBody, prepareClassified, prepareJobPost, publicJobPath, publicJobsPath } from './chunk-C7ZPLH2K.js';
14
- export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-AMLVCZDP.js';
15
15
  export { SPONSORSHIP_OFFERINGS, SPONSORSHIP_POLICY, placementAvailable, safeSponsorUrl } from './chunk-66GI2IX6.js';
16
16
  import './chunk-AFJ6S5WJ.js';
17
17
  import { jsxs, jsx } from 'react/jsx-runtime';