@scalemule/news-publication 0.0.66 → 0.0.68
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 +16 -0
- package/dist/article-presentation-0d1FiDgA.d.cts +21 -0
- package/dist/article-presentation-0d1FiDgA.d.ts +21 -0
- package/dist/article-workflow-client.cjs +265 -0
- package/dist/article-workflow-client.d.cts +131 -0
- package/dist/article-workflow-client.d.ts +131 -0
- package/dist/article-workflow-client.js +262 -0
- package/dist/{chunk-OISLCYER.js → chunk-DN3TBZLK.js} +37 -1
- package/dist/index.cjs +89 -20
- package/dist/index.d.cts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +53 -23
- package/dist/{snapshot-qj4BeNzB.d.cts → snapshot-BBTow5Bn.d.cts} +1 -1
- package/dist/{snapshot-CjP7bSXJ.d.ts → snapshot-CHdsfUnU.d.ts} +1 -1
- package/dist/snapshot-file.d.cts +2 -2
- package/dist/snapshot-file.d.ts +2 -2
- package/dist/story-review.cjs +176 -115
- package/dist/story-review.d.cts +71 -2
- package/dist/story-review.d.ts +71 -2
- package/dist/story-review.js +146 -117
- package/dist/{types-DBi49N8m.d.ts → types-22lEf_UU.d.cts} +5 -0
- package/dist/{types-DBi49N8m.d.cts → types-22lEf_UU.d.ts} +5 -0
- package/dist/verified-card.d.cts +1 -1
- package/dist/verified-card.d.ts +1 -1
- package/package.json +7 -2
- package/story-review.css +5 -0
|
@@ -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
|
-
|
|
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
|
@@ -158,42 +158,72 @@ function editorialSections(items) {
|
|
|
158
158
|
}
|
|
159
159
|
return Array.from(groups, ([name, articles]) => ({ ...sectionDefinition(name), items: articles }));
|
|
160
160
|
}
|
|
161
|
+
function coverIdentity(item) {
|
|
162
|
+
if (!item.cover_image_url?.trim()) return null;
|
|
163
|
+
const identity = item.visual_identity;
|
|
164
|
+
if (identity?.version === 1 && /^[a-f0-9]{64}$/u.test(identity.key)) return `pixels:${identity.key}`;
|
|
165
|
+
try {
|
|
166
|
+
const url = new URL(item.cover_image_url.trim());
|
|
167
|
+
if (url.protocol !== "https:" || url.username || url.password) return null;
|
|
168
|
+
if (url.hostname === "cdn-public.scalemule.com") {
|
|
169
|
+
const match = url.pathname.match(/^\/([a-f0-9-]{36})\/anonymous\/photos\/([a-f0-9-]{36})\//iu);
|
|
170
|
+
if (match) return `photo:${match[1].toLowerCase()}:${match[2].toLowerCase()}`;
|
|
171
|
+
}
|
|
172
|
+
if (["api.scalemule.com", "gw.scalemule.com"].includes(url.hostname)) {
|
|
173
|
+
const match = url.pathname.match(/^\/v1\/photos\/public\/([a-f0-9-]{36})(?:\/|$)/iu);
|
|
174
|
+
if (match) return `photo-public:${match[1].toLowerCase()}`;
|
|
175
|
+
}
|
|
176
|
+
url.hash = "";
|
|
177
|
+
url.searchParams.sort();
|
|
178
|
+
return `url:${url.toString()}`;
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
161
183
|
function composeEdition(items, selection = {}, now = Date.now()) {
|
|
162
184
|
const seen = /* @__PURE__ */ new Set();
|
|
163
|
-
|
|
185
|
+
let inventory = items.slice(0, 200).filter((item) => {
|
|
164
186
|
if (!item.id || !item.slug || seen.has(item.id)) return false;
|
|
165
187
|
seen.add(item.id);
|
|
166
188
|
return true;
|
|
167
189
|
});
|
|
168
190
|
const pins = !selection.pinned_until || Date.parse(selection.pinned_until) > now ? selection : {};
|
|
169
|
-
const
|
|
170
|
-
const
|
|
191
|
+
const selectedLead = inventory.find((item) => item.slug === pins.lead_slug) ?? inventory[0];
|
|
192
|
+
const rest = inventory.filter((item) => item !== selectedLead);
|
|
171
193
|
const requested = new Set(pins.secondary_slugs ?? []);
|
|
172
194
|
const ordered = [
|
|
173
|
-
...(pins.secondary_slugs ?? []).flatMap((slug) =>
|
|
174
|
-
...
|
|
195
|
+
...(pins.secondary_slugs ?? []).flatMap((slug) => rest.find((item) => item.slug === slug) ?? []),
|
|
196
|
+
...rest.filter((item) => !requested.has(item.slug))
|
|
175
197
|
];
|
|
176
|
-
const
|
|
198
|
+
const originalOrder = Array.from(new Map(ordered.map((item) => [item.id, item])).values());
|
|
177
199
|
const seenImages = /* @__PURE__ */ new Set();
|
|
178
|
-
|
|
179
|
-
|
|
200
|
+
const duplicateImages = /* @__PURE__ */ new Set();
|
|
201
|
+
const display = /* @__PURE__ */ new Map();
|
|
202
|
+
for (const item of [selectedLead, ...originalOrder]) {
|
|
203
|
+
if (!item) continue;
|
|
204
|
+
const key = coverIdentity(item);
|
|
205
|
+
if (key && seenImages.has(key)) {
|
|
206
|
+
duplicateImages.add(item.id);
|
|
207
|
+
display.set(item.id, { ...item, cover_image_url: null });
|
|
208
|
+
} else {
|
|
209
|
+
display.set(item.id, item);
|
|
210
|
+
if (key) seenImages.add(key);
|
|
211
|
+
}
|
|
180
212
|
}
|
|
213
|
+
inventory = inventory.map((item) => display.get(item.id));
|
|
214
|
+
const lead = selectedLead ? display.get(selectedLead.id) : void 0;
|
|
215
|
+
let remaining = rest.map((item) => display.get(item.id));
|
|
216
|
+
const uniqueOrdered = originalOrder.map((item) => display.get(item.id));
|
|
181
217
|
const secondary = [];
|
|
182
|
-
const
|
|
218
|
+
const deferred = [];
|
|
183
219
|
for (const item of uniqueOrdered) {
|
|
184
220
|
if (secondary.length >= 3) break;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
deferredWithDuplicateImage.push(item);
|
|
188
|
-
} else {
|
|
189
|
-
secondary.push(item);
|
|
190
|
-
if (img) seenImages.add(img);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
while (secondary.length < 3 && deferredWithDuplicateImage.length > 0) {
|
|
194
|
-
secondary.push(deferredWithDuplicateImage.shift());
|
|
221
|
+
if (duplicateImages.has(item.id)) deferred.push(item);
|
|
222
|
+
else secondary.push(item);
|
|
195
223
|
}
|
|
196
|
-
|
|
224
|
+
while (secondary.length < 3 && deferred.length > 0) secondary.push(deferred.shift());
|
|
225
|
+
const secondaryIds = new Set(secondary.map((item) => item.id));
|
|
226
|
+
remaining = remaining.filter((item) => !secondaryIds.has(item.id));
|
|
197
227
|
const eligible2 = remaining.filter((item) => {
|
|
198
228
|
const age = now - Date.parse(item.published_at);
|
|
199
229
|
return item.excerpt?.trim() && age >= 0 && age <= 7 * 864e5;
|
|
@@ -3556,6 +3586,42 @@ function campaignReportHtml(report) {
|
|
|
3556
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>`;
|
|
3557
3587
|
}
|
|
3558
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
|
+
|
|
3559
3625
|
Object.defineProperty(exports, "COOKIE_CONSENT_BACKUP_KEY", {
|
|
3560
3626
|
enumerable: true,
|
|
3561
3627
|
get: function () { return compliance.COOKIE_CONSENT_BACKUP_KEY; }
|
|
@@ -3696,7 +3762,10 @@ exports.applyNavigationConfig = applyNavigationConfig;
|
|
|
3696
3762
|
exports.applyTheme = applyTheme;
|
|
3697
3763
|
exports.articleCacheKey = articleCacheKey;
|
|
3698
3764
|
exports.articleCacheOptions = articleCacheOptions;
|
|
3765
|
+
exports.articleFormatLabel = articleFormatLabel;
|
|
3766
|
+
exports.articleHeroRatio = articleHeroRatio;
|
|
3699
3767
|
exports.articleMeta = articleMeta;
|
|
3768
|
+
exports.articlePresentation = articlePresentation;
|
|
3700
3769
|
exports.articleUrl = articleUrl;
|
|
3701
3770
|
exports.attribution = attribution;
|
|
3702
3771
|
exports.briefSummary = briefSummary;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, b as NewsItem, H as HomepageSelection, c as NearbyUpdate, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-
|
|
2
|
-
export { A as ArticleCitation, e as CardType, f as CategoryRef, g as CouponSettings, h as CoverImageMetadata, i as FEATURE_NAMES, j as FeatureFlags, k as NewsFeed, l as NewsLayoutId, m as PublicationModules, R as ReferralSettings, S as StoryDisplay, T as TagRef, V as VerifiedCard, n as VerifiedCardAction, o as VerifiedCardFact, p as VerifiedCardImage } from './types-
|
|
1
|
+
import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, b as NewsItem, H as HomepageSelection, c as NearbyUpdate, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-22lEf_UU.cjs';
|
|
2
|
+
export { A as ArticleCitation, e as CardType, f as CategoryRef, g as CouponSettings, h as CoverImageMetadata, i as FEATURE_NAMES, j as FeatureFlags, k as NewsFeed, l as NewsLayoutId, m as PublicationModules, R as ReferralSettings, S as StoryDisplay, T as TagRef, V as VerifiedCard, n as VerifiedCardAction, o as VerifiedCardFact, p as VerifiedCardImage } from './types-22lEf_UU.cjs';
|
|
3
3
|
export { M as MEET_ACTIVITIES, b as MeetActivity, c as MeetupInput, a as PreparedMeetup, P as PublicPlace, d as activityLabel, e as canHostMeetup, i as isAdult, p as prepareMeetup, s as screenMessage } from './meet-CO9Unz5M.cjs';
|
|
4
4
|
export { b as CLASSIFIED_CATEGORIES, c as ClassifiedCategory, C as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, d as PreparedClassified, e as PreparedJob, P as PublicDeal, f as PublicDealOffer, a as PublicJob, g as categoryLabel, h as classifiedPath, i as classifiedSearchPath, j as clipChars, k as dealDollars, l as dealGoPath, m as dealsFeedPath, n as employmentLabel, o as isClassifiedCategory, p as isEmploymentType, q as jobEmployerBody, r as jobOpeningBody, s as jobPostingBody, t as prepareClassified, u as prepareJobPost, v as publicJobPath, w as publicJobsPath } from './marketplace-29i5w4LS.cjs';
|
|
5
|
-
export { b as PublicationRecord, P as PublicationSnapshot, S as SnapshotHooks, c as SyncSource, a as SyncedPublication, d as acceptRemoteRecord, e as acceptSnapshot, p as parseSnapshot, f as publicationFromSnapshot, g as publicationRecord, s as snapshotFromRecord, h as syncPublication } from './snapshot-
|
|
5
|
+
export { b as PublicationRecord, P as PublicationSnapshot, S as SnapshotHooks, c as SyncSource, a as SyncedPublication, d as acceptRemoteRecord, e as acceptSnapshot, p as parseSnapshot, f as publicationFromSnapshot, g as publicationRecord, s as snapshotFromRecord, h as syncPublication } from './snapshot-BBTow5Bn.cjs';
|
|
6
6
|
import { CookieCategoryDefinition, CookieConsentRecord } from '@scalemule/compliance';
|
|
7
7
|
export { COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, CookieCategory, CookieCategoryDefinition, CookieConsentRecord, CookieDisclosure, CookiePreferences, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, consentModel, decorateNetworkUrl, effectivePreferences, getCookieConsent, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
|
|
8
8
|
export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.cjs';
|
|
@@ -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. */
|
|
@@ -237,9 +238,9 @@ declare function sectionId(name: string): string;
|
|
|
237
238
|
declare function sectionPath(name: string): string;
|
|
238
239
|
declare function sectionDefinition(name: string): EditorialSection;
|
|
239
240
|
declare function editorialSections(items: NewsItem[]): EditorialSection[];
|
|
240
|
-
/** Published inventory only.
|
|
241
|
+
/** Published inventory only. All image-bearing slots share one allocation. */
|
|
241
242
|
declare function composeEdition(items: NewsItem[], selection?: HomepageSelection, now?: number): {
|
|
242
|
-
lead: NewsItem;
|
|
243
|
+
lead: NewsItem | undefined;
|
|
243
244
|
secondary: NewsItem[];
|
|
244
245
|
briefs: NewsItem[];
|
|
245
246
|
compact: NewsItem[];
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, b as NewsItem, H as HomepageSelection, c as NearbyUpdate, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-
|
|
2
|
-
export { A as ArticleCitation, e as CardType, f as CategoryRef, g as CouponSettings, h as CoverImageMetadata, i as FEATURE_NAMES, j as FeatureFlags, k as NewsFeed, l as NewsLayoutId, m as PublicationModules, R as ReferralSettings, S as StoryDisplay, T as TagRef, V as VerifiedCard, n as VerifiedCardAction, o as VerifiedCardFact, p as VerifiedCardImage } from './types-
|
|
1
|
+
import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, b as NewsItem, H as HomepageSelection, c as NearbyUpdate, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-22lEf_UU.js';
|
|
2
|
+
export { A as ArticleCitation, e as CardType, f as CategoryRef, g as CouponSettings, h as CoverImageMetadata, i as FEATURE_NAMES, j as FeatureFlags, k as NewsFeed, l as NewsLayoutId, m as PublicationModules, R as ReferralSettings, S as StoryDisplay, T as TagRef, V as VerifiedCard, n as VerifiedCardAction, o as VerifiedCardFact, p as VerifiedCardImage } from './types-22lEf_UU.js';
|
|
3
3
|
export { M as MEET_ACTIVITIES, b as MeetActivity, c as MeetupInput, a as PreparedMeetup, P as PublicPlace, d as activityLabel, e as canHostMeetup, i as isAdult, p as prepareMeetup, s as screenMessage } from './meet-BWpxY20X.js';
|
|
4
4
|
export { b as CLASSIFIED_CATEGORIES, c as ClassifiedCategory, C as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, d as PreparedClassified, e as PreparedJob, P as PublicDeal, f as PublicDealOffer, a as PublicJob, g as categoryLabel, h as classifiedPath, i as classifiedSearchPath, j as clipChars, k as dealDollars, l as dealGoPath, m as dealsFeedPath, n as employmentLabel, o as isClassifiedCategory, p as isEmploymentType, q as jobEmployerBody, r as jobOpeningBody, s as jobPostingBody, t as prepareClassified, u as prepareJobPost, v as publicJobPath, w as publicJobsPath } from './marketplace-29i5w4LS.js';
|
|
5
|
-
export { b as PublicationRecord, P as PublicationSnapshot, S as SnapshotHooks, c as SyncSource, a as SyncedPublication, d as acceptRemoteRecord, e as acceptSnapshot, p as parseSnapshot, f as publicationFromSnapshot, g as publicationRecord, s as snapshotFromRecord, h as syncPublication } from './snapshot-
|
|
5
|
+
export { b as PublicationRecord, P as PublicationSnapshot, S as SnapshotHooks, c as SyncSource, a as SyncedPublication, d as acceptRemoteRecord, e as acceptSnapshot, p as parseSnapshot, f as publicationFromSnapshot, g as publicationRecord, s as snapshotFromRecord, h as syncPublication } from './snapshot-CHdsfUnU.js';
|
|
6
6
|
import { CookieCategoryDefinition, CookieConsentRecord } from '@scalemule/compliance';
|
|
7
7
|
export { COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, CookieCategory, CookieCategoryDefinition, CookieConsentRecord, CookieDisclosure, CookiePreferences, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, consentModel, decorateNetworkUrl, effectivePreferences, getCookieConsent, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
|
|
8
8
|
export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.js';
|
|
@@ -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. */
|
|
@@ -237,9 +238,9 @@ declare function sectionId(name: string): string;
|
|
|
237
238
|
declare function sectionPath(name: string): string;
|
|
238
239
|
declare function sectionDefinition(name: string): EditorialSection;
|
|
239
240
|
declare function editorialSections(items: NewsItem[]): EditorialSection[];
|
|
240
|
-
/** Published inventory only.
|
|
241
|
+
/** Published inventory only. All image-bearing slots share one allocation. */
|
|
241
242
|
declare function composeEdition(items: NewsItem[], selection?: HomepageSelection, now?: number): {
|
|
242
|
-
lead: NewsItem;
|
|
243
|
+
lead: NewsItem | undefined;
|
|
243
244
|
secondary: NewsItem[];
|
|
244
245
|
briefs: NewsItem[];
|
|
245
246
|
compact: NewsItem[];
|
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-
|
|
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';
|
|
@@ -101,42 +101,72 @@ function editorialSections(items) {
|
|
|
101
101
|
}
|
|
102
102
|
return Array.from(groups, ([name, articles]) => ({ ...sectionDefinition(name), items: articles }));
|
|
103
103
|
}
|
|
104
|
+
function coverIdentity(item) {
|
|
105
|
+
if (!item.cover_image_url?.trim()) return null;
|
|
106
|
+
const identity = item.visual_identity;
|
|
107
|
+
if (identity?.version === 1 && /^[a-f0-9]{64}$/u.test(identity.key)) return `pixels:${identity.key}`;
|
|
108
|
+
try {
|
|
109
|
+
const url = new URL(item.cover_image_url.trim());
|
|
110
|
+
if (url.protocol !== "https:" || url.username || url.password) return null;
|
|
111
|
+
if (url.hostname === "cdn-public.scalemule.com") {
|
|
112
|
+
const match = url.pathname.match(/^\/([a-f0-9-]{36})\/anonymous\/photos\/([a-f0-9-]{36})\//iu);
|
|
113
|
+
if (match) return `photo:${match[1].toLowerCase()}:${match[2].toLowerCase()}`;
|
|
114
|
+
}
|
|
115
|
+
if (["api.scalemule.com", "gw.scalemule.com"].includes(url.hostname)) {
|
|
116
|
+
const match = url.pathname.match(/^\/v1\/photos\/public\/([a-f0-9-]{36})(?:\/|$)/iu);
|
|
117
|
+
if (match) return `photo-public:${match[1].toLowerCase()}`;
|
|
118
|
+
}
|
|
119
|
+
url.hash = "";
|
|
120
|
+
url.searchParams.sort();
|
|
121
|
+
return `url:${url.toString()}`;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
104
126
|
function composeEdition(items, selection = {}, now = Date.now()) {
|
|
105
127
|
const seen = /* @__PURE__ */ new Set();
|
|
106
|
-
|
|
128
|
+
let inventory = items.slice(0, 200).filter((item) => {
|
|
107
129
|
if (!item.id || !item.slug || seen.has(item.id)) return false;
|
|
108
130
|
seen.add(item.id);
|
|
109
131
|
return true;
|
|
110
132
|
});
|
|
111
133
|
const pins = !selection.pinned_until || Date.parse(selection.pinned_until) > now ? selection : {};
|
|
112
|
-
const
|
|
113
|
-
const
|
|
134
|
+
const selectedLead = inventory.find((item) => item.slug === pins.lead_slug) ?? inventory[0];
|
|
135
|
+
const rest = inventory.filter((item) => item !== selectedLead);
|
|
114
136
|
const requested = new Set(pins.secondary_slugs ?? []);
|
|
115
137
|
const ordered = [
|
|
116
|
-
...(pins.secondary_slugs ?? []).flatMap((slug) =>
|
|
117
|
-
...
|
|
138
|
+
...(pins.secondary_slugs ?? []).flatMap((slug) => rest.find((item) => item.slug === slug) ?? []),
|
|
139
|
+
...rest.filter((item) => !requested.has(item.slug))
|
|
118
140
|
];
|
|
119
|
-
const
|
|
141
|
+
const originalOrder = Array.from(new Map(ordered.map((item) => [item.id, item])).values());
|
|
120
142
|
const seenImages = /* @__PURE__ */ new Set();
|
|
121
|
-
|
|
122
|
-
|
|
143
|
+
const duplicateImages = /* @__PURE__ */ new Set();
|
|
144
|
+
const display = /* @__PURE__ */ new Map();
|
|
145
|
+
for (const item of [selectedLead, ...originalOrder]) {
|
|
146
|
+
if (!item) continue;
|
|
147
|
+
const key = coverIdentity(item);
|
|
148
|
+
if (key && seenImages.has(key)) {
|
|
149
|
+
duplicateImages.add(item.id);
|
|
150
|
+
display.set(item.id, { ...item, cover_image_url: null });
|
|
151
|
+
} else {
|
|
152
|
+
display.set(item.id, item);
|
|
153
|
+
if (key) seenImages.add(key);
|
|
154
|
+
}
|
|
123
155
|
}
|
|
156
|
+
inventory = inventory.map((item) => display.get(item.id));
|
|
157
|
+
const lead = selectedLead ? display.get(selectedLead.id) : void 0;
|
|
158
|
+
let remaining = rest.map((item) => display.get(item.id));
|
|
159
|
+
const uniqueOrdered = originalOrder.map((item) => display.get(item.id));
|
|
124
160
|
const secondary = [];
|
|
125
|
-
const
|
|
161
|
+
const deferred = [];
|
|
126
162
|
for (const item of uniqueOrdered) {
|
|
127
163
|
if (secondary.length >= 3) break;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
deferredWithDuplicateImage.push(item);
|
|
131
|
-
} else {
|
|
132
|
-
secondary.push(item);
|
|
133
|
-
if (img) seenImages.add(img);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
while (secondary.length < 3 && deferredWithDuplicateImage.length > 0) {
|
|
137
|
-
secondary.push(deferredWithDuplicateImage.shift());
|
|
164
|
+
if (duplicateImages.has(item.id)) deferred.push(item);
|
|
165
|
+
else secondary.push(item);
|
|
138
166
|
}
|
|
139
|
-
|
|
167
|
+
while (secondary.length < 3 && deferred.length > 0) secondary.push(deferred.shift());
|
|
168
|
+
const secondaryIds = new Set(secondary.map((item) => item.id));
|
|
169
|
+
remaining = remaining.filter((item) => !secondaryIds.has(item.id));
|
|
140
170
|
const eligible2 = remaining.filter((item) => {
|
|
141
171
|
const age = now - Date.parse(item.published_at);
|
|
142
172
|
return item.excerpt?.trim() && age >= 0 && age <= 7 * 864e5;
|