@scalemule/news-publication 0.0.83 → 0.0.84
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 +1 -1
- package/dist/index.cjs +6 -14
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -324,7 +324,7 @@ The publisher-supplied `PawMark` preserves the approved SVG paths. Hosts may set
|
|
|
324
324
|
|
|
325
325
|
### Homepage image identity
|
|
326
326
|
|
|
327
|
-
`NewsItem.visual_identity` is a versioned public identity from the Blog feed. `composeEdition` allocates covers across the entire bounded inventory,
|
|
327
|
+
`NewsItem.visual_identity` is a versioned public identity from the Blog feed. `composeEdition` allocates covers across the entire bounded inventory and holds repeated covers as `null` without mutating inputs. Lead, secondary, compact and section image placements require a usable distinct cover, including pinned stories. A held lead yields to the first ready story; an entirely unready inventory has no visual lead. Intentional briefs retain their editorial selection rules. `pendingImages` and the complete `navigation` inventory retain unresolved stories for repair and discovery: excluding them from image placements does not mean their photos were repaired. Consumer planners must not refill image placements from raw inventory or reintroduce duplicate covers. Legacy Photo rendition URLs are normalized by tenant/asset ID; server-side Photo identity detects copies under different IDs and ordinary resize/re-encoding/crop variants. Do not fetch or decode images in page components. Preserve existing caption/credit policy. See platform `docs/HOMEPAGE-PHOTO-IDENTITY.md` for storage, rights, concurrency and rollout tests.
|
|
328
328
|
|
|
329
329
|
### Standard article submission
|
|
330
330
|
|
package/dist/index.cjs
CHANGED
|
@@ -188,7 +188,7 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
188
188
|
return true;
|
|
189
189
|
});
|
|
190
190
|
const pins = !selection.pinned_until || Date.parse(selection.pinned_until) > now ? selection : {};
|
|
191
|
-
const selectedLead = inventory.find((item) => item.slug === pins.lead_slug) ?? inventory
|
|
191
|
+
const selectedLead = inventory.find((item) => item.slug === pins.lead_slug && coverIdentity(item)) ?? inventory.find((item) => coverIdentity(item));
|
|
192
192
|
const rest = inventory.filter((item) => item !== selectedLead);
|
|
193
193
|
const requested = new Set(pins.secondary_slugs ?? []);
|
|
194
194
|
const ordered = [
|
|
@@ -197,13 +197,11 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
197
197
|
];
|
|
198
198
|
const originalOrder = Array.from(new Map(ordered.map((item) => [item.id, item])).values());
|
|
199
199
|
const seenImages = /* @__PURE__ */ new Set();
|
|
200
|
-
const duplicateImages = /* @__PURE__ */ new Set();
|
|
201
200
|
const display = /* @__PURE__ */ new Map();
|
|
202
201
|
for (const item of [selectedLead, ...originalOrder]) {
|
|
203
202
|
if (!item) continue;
|
|
204
203
|
const key = coverIdentity(item);
|
|
205
204
|
if (key && seenImages.has(key)) {
|
|
206
|
-
duplicateImages.add(item.id);
|
|
207
205
|
display.set(item.id, { ...item, cover_image_url: null });
|
|
208
206
|
} else {
|
|
209
207
|
display.set(item.id, item);
|
|
@@ -214,14 +212,7 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
214
212
|
const lead = selectedLead ? display.get(selectedLead.id) : void 0;
|
|
215
213
|
let remaining = rest.map((item) => display.get(item.id));
|
|
216
214
|
const uniqueOrdered = originalOrder.map((item) => display.get(item.id));
|
|
217
|
-
const secondary =
|
|
218
|
-
const deferred = [];
|
|
219
|
-
for (const item of uniqueOrdered) {
|
|
220
|
-
if (secondary.length >= 3) break;
|
|
221
|
-
if (duplicateImages.has(item.id)) deferred.push(item);
|
|
222
|
-
else secondary.push(item);
|
|
223
|
-
}
|
|
224
|
-
while (secondary.length < 3 && deferred.length > 0) secondary.push(deferred.shift());
|
|
215
|
+
const secondary = uniqueOrdered.filter((item) => coverIdentity(item)).slice(0, 3);
|
|
225
216
|
const secondaryIds = new Set(secondary.map((item) => item.id));
|
|
226
217
|
remaining = remaining.filter((item) => !secondaryIds.has(item.id));
|
|
227
218
|
const eligible2 = remaining.filter((item) => {
|
|
@@ -236,10 +227,11 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
236
227
|
briefIds.add(item.id);
|
|
237
228
|
return true;
|
|
238
229
|
});
|
|
239
|
-
const compact = [...briefs, ...remaining.filter((item) => !briefIds.has(item.id))].slice(0, 4);
|
|
230
|
+
const compact = [...briefs, ...remaining.filter((item) => !briefIds.has(item.id))].filter((item) => coverIdentity(item)).slice(0, 4);
|
|
240
231
|
const compactIds = new Set(compact.map((item) => item.id));
|
|
241
|
-
const sections2 = editorialSections(remaining.filter((item) => !compactIds.has(item.id)));
|
|
242
|
-
|
|
232
|
+
const sections2 = editorialSections(remaining.filter((item) => !compactIds.has(item.id) && coverIdentity(item)));
|
|
233
|
+
const pendingImages = inventory.filter((item) => !coverIdentity(item));
|
|
234
|
+
return { lead, secondary, briefs, compact, sections: sections2, pendingImages, navigation: editorialSections(inventory) };
|
|
243
235
|
}
|
|
244
236
|
function displayHeadline(article, selection = {}) {
|
|
245
237
|
return selection.stories?.[article.slug]?.display_title?.trim() || article.title;
|
package/dist/index.d.cts
CHANGED
|
@@ -246,6 +246,7 @@ declare function composeEdition(items: NewsItem[], selection?: HomepageSelection
|
|
|
246
246
|
briefs: NewsItem[];
|
|
247
247
|
compact: NewsItem[];
|
|
248
248
|
sections: EditorialSection[];
|
|
249
|
+
pendingImages: NewsItem[];
|
|
249
250
|
navigation: EditorialSection[];
|
|
250
251
|
};
|
|
251
252
|
declare function displayHeadline(article: NewsItem, selection?: HomepageSelection): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -246,6 +246,7 @@ declare function composeEdition(items: NewsItem[], selection?: HomepageSelection
|
|
|
246
246
|
briefs: NewsItem[];
|
|
247
247
|
compact: NewsItem[];
|
|
248
248
|
sections: EditorialSection[];
|
|
249
|
+
pendingImages: NewsItem[];
|
|
249
250
|
navigation: EditorialSection[];
|
|
250
251
|
};
|
|
251
252
|
declare function displayHeadline(article: NewsItem, selection?: HomepageSelection): string;
|
package/dist/index.js
CHANGED
|
@@ -132,7 +132,7 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
132
132
|
return true;
|
|
133
133
|
});
|
|
134
134
|
const pins = !selection.pinned_until || Date.parse(selection.pinned_until) > now ? selection : {};
|
|
135
|
-
const selectedLead = inventory.find((item) => item.slug === pins.lead_slug) ?? inventory
|
|
135
|
+
const selectedLead = inventory.find((item) => item.slug === pins.lead_slug && coverIdentity(item)) ?? inventory.find((item) => coverIdentity(item));
|
|
136
136
|
const rest = inventory.filter((item) => item !== selectedLead);
|
|
137
137
|
const requested = new Set(pins.secondary_slugs ?? []);
|
|
138
138
|
const ordered = [
|
|
@@ -141,13 +141,11 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
141
141
|
];
|
|
142
142
|
const originalOrder = Array.from(new Map(ordered.map((item) => [item.id, item])).values());
|
|
143
143
|
const seenImages = /* @__PURE__ */ new Set();
|
|
144
|
-
const duplicateImages = /* @__PURE__ */ new Set();
|
|
145
144
|
const display = /* @__PURE__ */ new Map();
|
|
146
145
|
for (const item of [selectedLead, ...originalOrder]) {
|
|
147
146
|
if (!item) continue;
|
|
148
147
|
const key = coverIdentity(item);
|
|
149
148
|
if (key && seenImages.has(key)) {
|
|
150
|
-
duplicateImages.add(item.id);
|
|
151
149
|
display.set(item.id, { ...item, cover_image_url: null });
|
|
152
150
|
} else {
|
|
153
151
|
display.set(item.id, item);
|
|
@@ -158,14 +156,7 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
158
156
|
const lead = selectedLead ? display.get(selectedLead.id) : void 0;
|
|
159
157
|
let remaining = rest.map((item) => display.get(item.id));
|
|
160
158
|
const uniqueOrdered = originalOrder.map((item) => display.get(item.id));
|
|
161
|
-
const secondary =
|
|
162
|
-
const deferred = [];
|
|
163
|
-
for (const item of uniqueOrdered) {
|
|
164
|
-
if (secondary.length >= 3) break;
|
|
165
|
-
if (duplicateImages.has(item.id)) deferred.push(item);
|
|
166
|
-
else secondary.push(item);
|
|
167
|
-
}
|
|
168
|
-
while (secondary.length < 3 && deferred.length > 0) secondary.push(deferred.shift());
|
|
159
|
+
const secondary = uniqueOrdered.filter((item) => coverIdentity(item)).slice(0, 3);
|
|
169
160
|
const secondaryIds = new Set(secondary.map((item) => item.id));
|
|
170
161
|
remaining = remaining.filter((item) => !secondaryIds.has(item.id));
|
|
171
162
|
const eligible2 = remaining.filter((item) => {
|
|
@@ -180,10 +171,11 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
180
171
|
briefIds.add(item.id);
|
|
181
172
|
return true;
|
|
182
173
|
});
|
|
183
|
-
const compact = [...briefs, ...remaining.filter((item) => !briefIds.has(item.id))].slice(0, 4);
|
|
174
|
+
const compact = [...briefs, ...remaining.filter((item) => !briefIds.has(item.id))].filter((item) => coverIdentity(item)).slice(0, 4);
|
|
184
175
|
const compactIds = new Set(compact.map((item) => item.id));
|
|
185
|
-
const sections2 = editorialSections(remaining.filter((item) => !compactIds.has(item.id)));
|
|
186
|
-
|
|
176
|
+
const sections2 = editorialSections(remaining.filter((item) => !compactIds.has(item.id) && coverIdentity(item)));
|
|
177
|
+
const pendingImages = inventory.filter((item) => !coverIdentity(item));
|
|
178
|
+
return { lead, secondary, briefs, compact, sections: sections2, pendingImages, navigation: editorialSections(inventory) };
|
|
187
179
|
}
|
|
188
180
|
function displayHeadline(article, selection = {}) {
|
|
189
181
|
return selection.stories?.[article.slug]?.display_title?.trim() || article.title;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/news-publication",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.84",
|
|
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
|
"bin": {
|