@scalemule/news-publication 0.0.19 → 0.0.21
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/dist/index.cjs +78 -2
- package/dist/index.d.cts +54 -1
- package/dist/index.d.ts +54 -1
- package/dist/index.js +76 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -82,7 +82,7 @@ var walnutCreekTimes = definePublication({
|
|
|
82
82
|
regionLabel: "Walnut Creek",
|
|
83
83
|
towns: ["Walnut Creek", "Rossmoor", "Saranap", "Pleasant Hill"],
|
|
84
84
|
nearbyTowns: ["Martinez", "Concord", "Contra Costa"],
|
|
85
|
-
sections: ["Civic life", "Community", "Local businesses", "
|
|
85
|
+
sections: ["Civic life", "Community", "Local businesses", "Real Estate", "Food & places", "Events", "Health"],
|
|
86
86
|
description: "Local news, independent businesses and community voices from Walnut Creek, Rossmoor, Saranap and Pleasant Hill.",
|
|
87
87
|
layout: "broadsheet",
|
|
88
88
|
layouts: ["broadsheet"],
|
|
@@ -174,9 +174,30 @@ function defaultRevalidateTargets() {
|
|
|
174
174
|
|
|
175
175
|
// src/editorial.ts
|
|
176
176
|
var labels = {
|
|
177
|
-
"Local businesses": "
|
|
177
|
+
"Local businesses": "Business",
|
|
178
|
+
"Events": "Things to do",
|
|
178
179
|
"Food & places": "Food & drink"
|
|
179
180
|
};
|
|
181
|
+
var EDITORIAL_SECTION_ORDER = [
|
|
182
|
+
"Civic life",
|
|
183
|
+
"Community",
|
|
184
|
+
"Local businesses",
|
|
185
|
+
"Real Estate",
|
|
186
|
+
"Food & places",
|
|
187
|
+
"Events",
|
|
188
|
+
"Health",
|
|
189
|
+
"Schools",
|
|
190
|
+
"Opinion"
|
|
191
|
+
];
|
|
192
|
+
function orderedSections(sections, order = EDITORIAL_SECTION_ORDER) {
|
|
193
|
+
return sections.filter((section) => section.name !== "Local news").slice().sort((left, right) => {
|
|
194
|
+
const leftRank = order.indexOf(left.name);
|
|
195
|
+
const rightRank = order.indexOf(right.name);
|
|
196
|
+
const leftOrder = leftRank === -1 ? order.length : leftRank;
|
|
197
|
+
const rightOrder = rightRank === -1 ? order.length : rightRank;
|
|
198
|
+
return leftOrder - rightOrder || left.name.localeCompare(right.name);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
180
201
|
function sectionId(name) {
|
|
181
202
|
return "section-" + name.normalize("NFKD").replace(/\p{Mark}/gu, "").toLowerCase().replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-|-$/g, "");
|
|
182
203
|
}
|
|
@@ -1815,6 +1836,57 @@ function readerSources(input) {
|
|
|
1815
1836
|
return output;
|
|
1816
1837
|
}
|
|
1817
1838
|
|
|
1839
|
+
// src/listening.ts
|
|
1840
|
+
function publicationListeningTrack(article, publicationId, publicationName, origin) {
|
|
1841
|
+
const articleUrl2 = new URL(`/news/${encodeURIComponent(article.slug)}`, origin).href;
|
|
1842
|
+
let storyId = articleUrl2;
|
|
1843
|
+
if (article.canonical_url) {
|
|
1844
|
+
try {
|
|
1845
|
+
const canonical = new URL(article.canonical_url);
|
|
1846
|
+
if (canonical.protocol === "https:" && !canonical.username && !canonical.password && /^\/news\/[^/]+\/?$/.test(canonical.pathname) && localPublications.some((item) => new URL(item.url).hostname === canonical.hostname.replace(/^www\./, ""))) {
|
|
1847
|
+
canonical.search = "";
|
|
1848
|
+
canonical.hash = "";
|
|
1849
|
+
canonical.hostname = canonical.hostname.replace(/^www\./, "");
|
|
1850
|
+
canonical.pathname = canonical.pathname.replace(/\/$/, "");
|
|
1851
|
+
storyId = canonical.href;
|
|
1852
|
+
}
|
|
1853
|
+
} catch {
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const revision = article.editorial?.modified_at;
|
|
1857
|
+
return {
|
|
1858
|
+
id: article.id,
|
|
1859
|
+
publicationId,
|
|
1860
|
+
publicationName,
|
|
1861
|
+
title: article.title,
|
|
1862
|
+
articleUrl: articleUrl2,
|
|
1863
|
+
storyId,
|
|
1864
|
+
...revision && Number.isFinite(Date.parse(revision)) ? { revision } : {},
|
|
1865
|
+
...article.audio?.duration_ms && article.audio.duration_ms > 0 ? { durationSeconds: article.audio.duration_ms / 1e3 } : {},
|
|
1866
|
+
...article.section ? { section: article.section } : {},
|
|
1867
|
+
...article.published_at ? { publishedAt: article.published_at } : {}
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
async function publicationListeningCandidates(items, read, publicationId, publicationName, origin) {
|
|
1871
|
+
const output = [];
|
|
1872
|
+
const recent = items.filter((item) => !item.editorial?.duplicate_url).slice(0, 30);
|
|
1873
|
+
let failures = 0;
|
|
1874
|
+
for (let i = 0; i < recent.length; i += 4) {
|
|
1875
|
+
const batch = await Promise.allSettled(recent.slice(i, i + 4).map((item) => read(item.slug)));
|
|
1876
|
+
for (const result of batch) {
|
|
1877
|
+
if (result.status === "rejected") {
|
|
1878
|
+
failures++;
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
const article = result.value;
|
|
1882
|
+
if (!article.editorial?.duplicate_url && article.audio?.url && article.audio.duration_ms && article.audio.duration_ms > 0)
|
|
1883
|
+
output.push(publicationListeningTrack(article, publicationId, publicationName, origin));
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
if (!output.length && failures) throw new Error("Audio inventory could not be loaded");
|
|
1887
|
+
return output.sort((a, b) => (b.publishedAt ?? "").localeCompare(a.publishedAt ?? ""));
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1818
1890
|
Object.defineProperty(exports, "COOKIE_CONSENT_BACKUP_KEY", {
|
|
1819
1891
|
enumerable: true,
|
|
1820
1892
|
get: function () { return compliance.COOKIE_CONSENT_BACKUP_KEY; }
|
|
@@ -1907,6 +1979,7 @@ exports.CLASSIFIED_CATEGORIES = CLASSIFIED_CATEGORIES;
|
|
|
1907
1979
|
exports.COOKIE_CATEGORIES = COOKIE_CATEGORIES;
|
|
1908
1980
|
exports.CookieConsentBanner = CookieConsentBanner;
|
|
1909
1981
|
exports.CookieSettingsView = CookieSettingsView;
|
|
1982
|
+
exports.EDITORIAL_SECTION_ORDER = EDITORIAL_SECTION_ORDER;
|
|
1910
1983
|
exports.EVENTS = EVENTS;
|
|
1911
1984
|
exports.FEATURE_NAMES = FEATURE_NAMES;
|
|
1912
1985
|
exports.JOB_EMPLOYMENT_TYPES = JOB_EMPLOYMENT_TYPES;
|
|
@@ -1974,6 +2047,7 @@ exports.matchesStoryTag = matchesStoryTag;
|
|
|
1974
2047
|
exports.nearbyUpdates = nearbyUpdates;
|
|
1975
2048
|
exports.newsArticleJsonLd = newsArticleJsonLd;
|
|
1976
2049
|
exports.normalizedText = normalizedText;
|
|
2050
|
+
exports.orderedSections = orderedSections;
|
|
1977
2051
|
exports.parseSnapshot = parseSnapshot;
|
|
1978
2052
|
exports.prepareClassified = prepareClassified;
|
|
1979
2053
|
exports.prepareJobPost = prepareJobPost;
|
|
@@ -1984,6 +2058,8 @@ exports.publicJobsPath = publicJobsPath;
|
|
|
1984
2058
|
exports.publicUrl = publicUrl;
|
|
1985
2059
|
exports.publicationFromSnapshot = publicationFromSnapshot;
|
|
1986
2060
|
exports.publicationJsonLd = publicationJsonLd;
|
|
2061
|
+
exports.publicationListeningCandidates = publicationListeningCandidates;
|
|
2062
|
+
exports.publicationListeningTrack = publicationListeningTrack;
|
|
1987
2063
|
exports.publicationMeta = publicationMeta;
|
|
1988
2064
|
exports.publicationRecord = publicationRecord;
|
|
1989
2065
|
exports.readerSources = readerSources;
|
package/dist/index.d.cts
CHANGED
|
@@ -102,6 +102,16 @@ declare function defaultRevalidateTargets(): {
|
|
|
102
102
|
paths: string[];
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Canonical reader-facing order for editorial sections across the network.
|
|
107
|
+
* Names not listed here sort after these, alphabetically. "Schools" and
|
|
108
|
+
* "Opinion" are reserved so future sections slot in without a release.
|
|
109
|
+
*/
|
|
110
|
+
declare const EDITORIAL_SECTION_ORDER: readonly ["Civic life", "Community", "Local businesses", "Real Estate", "Food & places", "Events", "Health", "Schools", "Opinion"];
|
|
111
|
+
/** Sorts sections into the canonical order, dropping the implicit "Local news" bucket. */
|
|
112
|
+
declare function orderedSections<T extends {
|
|
113
|
+
name: string;
|
|
114
|
+
}>(sections: T[], order?: readonly string[]): T[];
|
|
105
115
|
declare function sectionId(name: string): string;
|
|
106
116
|
declare function sectionPath(name: string): string;
|
|
107
117
|
declare function sectionDefinition(name: string): EditorialSection;
|
|
@@ -301,4 +311,47 @@ declare function readerSources(input: unknown): {
|
|
|
301
311
|
publisher?: string;
|
|
302
312
|
}[];
|
|
303
313
|
|
|
304
|
-
|
|
314
|
+
interface ListeningArticle {
|
|
315
|
+
id: string;
|
|
316
|
+
slug: string;
|
|
317
|
+
title: string;
|
|
318
|
+
canonical_url?: string | null;
|
|
319
|
+
section?: string | null;
|
|
320
|
+
published_at?: string;
|
|
321
|
+
editorial?: {
|
|
322
|
+
duplicate_url?: string;
|
|
323
|
+
modified_at?: string;
|
|
324
|
+
} | null;
|
|
325
|
+
audio?: {
|
|
326
|
+
url?: string | null;
|
|
327
|
+
duration_ms?: number | null;
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** Public metadata only. Never cache signed recording URLs in reader history. */
|
|
331
|
+
declare function publicationListeningTrack(article: ListeningArticle, publicationId: string, publicationName: string, origin: string): {
|
|
332
|
+
publishedAt?: string | undefined;
|
|
333
|
+
section?: string | undefined;
|
|
334
|
+
durationSeconds?: number | undefined;
|
|
335
|
+
revision?: string | undefined;
|
|
336
|
+
id: string;
|
|
337
|
+
publicationId: string;
|
|
338
|
+
publicationName: string;
|
|
339
|
+
title: string;
|
|
340
|
+
articleUrl: string;
|
|
341
|
+
storyId: string;
|
|
342
|
+
};
|
|
343
|
+
/** Bounded public read fan-out, initiated only when a reader requests a catch-up queue. */
|
|
344
|
+
declare function publicationListeningCandidates<T extends ListeningArticle>(items: T[], read: (slug: string) => Promise<ListeningArticle>, publicationId: string, publicationName: string, origin: string): Promise<{
|
|
345
|
+
publishedAt?: string | undefined;
|
|
346
|
+
section?: string | undefined;
|
|
347
|
+
durationSeconds?: number | undefined;
|
|
348
|
+
revision?: string | undefined;
|
|
349
|
+
id: string;
|
|
350
|
+
publicationId: string;
|
|
351
|
+
publicationName: string;
|
|
352
|
+
title: string;
|
|
353
|
+
articleUrl: string;
|
|
354
|
+
storyId: string;
|
|
355
|
+
}[]>;
|
|
356
|
+
|
|
357
|
+
export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, type ListeningArticle, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
|
package/dist/index.d.ts
CHANGED
|
@@ -102,6 +102,16 @@ declare function defaultRevalidateTargets(): {
|
|
|
102
102
|
paths: string[];
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Canonical reader-facing order for editorial sections across the network.
|
|
107
|
+
* Names not listed here sort after these, alphabetically. "Schools" and
|
|
108
|
+
* "Opinion" are reserved so future sections slot in without a release.
|
|
109
|
+
*/
|
|
110
|
+
declare const EDITORIAL_SECTION_ORDER: readonly ["Civic life", "Community", "Local businesses", "Real Estate", "Food & places", "Events", "Health", "Schools", "Opinion"];
|
|
111
|
+
/** Sorts sections into the canonical order, dropping the implicit "Local news" bucket. */
|
|
112
|
+
declare function orderedSections<T extends {
|
|
113
|
+
name: string;
|
|
114
|
+
}>(sections: T[], order?: readonly string[]): T[];
|
|
105
115
|
declare function sectionId(name: string): string;
|
|
106
116
|
declare function sectionPath(name: string): string;
|
|
107
117
|
declare function sectionDefinition(name: string): EditorialSection;
|
|
@@ -301,4 +311,47 @@ declare function readerSources(input: unknown): {
|
|
|
301
311
|
publisher?: string;
|
|
302
312
|
}[];
|
|
303
313
|
|
|
304
|
-
|
|
314
|
+
interface ListeningArticle {
|
|
315
|
+
id: string;
|
|
316
|
+
slug: string;
|
|
317
|
+
title: string;
|
|
318
|
+
canonical_url?: string | null;
|
|
319
|
+
section?: string | null;
|
|
320
|
+
published_at?: string;
|
|
321
|
+
editorial?: {
|
|
322
|
+
duplicate_url?: string;
|
|
323
|
+
modified_at?: string;
|
|
324
|
+
} | null;
|
|
325
|
+
audio?: {
|
|
326
|
+
url?: string | null;
|
|
327
|
+
duration_ms?: number | null;
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** Public metadata only. Never cache signed recording URLs in reader history. */
|
|
331
|
+
declare function publicationListeningTrack(article: ListeningArticle, publicationId: string, publicationName: string, origin: string): {
|
|
332
|
+
publishedAt?: string | undefined;
|
|
333
|
+
section?: string | undefined;
|
|
334
|
+
durationSeconds?: number | undefined;
|
|
335
|
+
revision?: string | undefined;
|
|
336
|
+
id: string;
|
|
337
|
+
publicationId: string;
|
|
338
|
+
publicationName: string;
|
|
339
|
+
title: string;
|
|
340
|
+
articleUrl: string;
|
|
341
|
+
storyId: string;
|
|
342
|
+
};
|
|
343
|
+
/** Bounded public read fan-out, initiated only when a reader requests a catch-up queue. */
|
|
344
|
+
declare function publicationListeningCandidates<T extends ListeningArticle>(items: T[], read: (slug: string) => Promise<ListeningArticle>, publicationId: string, publicationName: string, origin: string): Promise<{
|
|
345
|
+
publishedAt?: string | undefined;
|
|
346
|
+
section?: string | undefined;
|
|
347
|
+
durationSeconds?: number | undefined;
|
|
348
|
+
revision?: string | undefined;
|
|
349
|
+
id: string;
|
|
350
|
+
publicationId: string;
|
|
351
|
+
publicationName: string;
|
|
352
|
+
title: string;
|
|
353
|
+
articleUrl: string;
|
|
354
|
+
storyId: string;
|
|
355
|
+
}[]>;
|
|
356
|
+
|
|
357
|
+
export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, type ListeningArticle, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedP
|
|
|
6
6
|
export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-GB36YM56.js';
|
|
7
7
|
export { CookieConsentBanner } from './chunk-HTMIFWNA.js';
|
|
8
8
|
export { CookieSettingsView } from './chunk-3MFKPHO3.js';
|
|
9
|
+
import { localPublications } from './chunk-2IYYEFLD.js';
|
|
9
10
|
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, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, decorateNetworkUrl, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, hasConsented, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from './chunk-2IYYEFLD.js';
|
|
10
11
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
11
12
|
|
|
@@ -18,7 +19,7 @@ var walnutCreekTimes = definePublication({
|
|
|
18
19
|
regionLabel: "Walnut Creek",
|
|
19
20
|
towns: ["Walnut Creek", "Rossmoor", "Saranap", "Pleasant Hill"],
|
|
20
21
|
nearbyTowns: ["Martinez", "Concord", "Contra Costa"],
|
|
21
|
-
sections: ["Civic life", "Community", "Local businesses", "
|
|
22
|
+
sections: ["Civic life", "Community", "Local businesses", "Real Estate", "Food & places", "Events", "Health"],
|
|
22
23
|
description: "Local news, independent businesses and community voices from Walnut Creek, Rossmoor, Saranap and Pleasant Hill.",
|
|
23
24
|
layout: "broadsheet",
|
|
24
25
|
layouts: ["broadsheet"],
|
|
@@ -110,9 +111,30 @@ function defaultRevalidateTargets() {
|
|
|
110
111
|
|
|
111
112
|
// src/editorial.ts
|
|
112
113
|
var labels = {
|
|
113
|
-
"Local businesses": "
|
|
114
|
+
"Local businesses": "Business",
|
|
115
|
+
"Events": "Things to do",
|
|
114
116
|
"Food & places": "Food & drink"
|
|
115
117
|
};
|
|
118
|
+
var EDITORIAL_SECTION_ORDER = [
|
|
119
|
+
"Civic life",
|
|
120
|
+
"Community",
|
|
121
|
+
"Local businesses",
|
|
122
|
+
"Real Estate",
|
|
123
|
+
"Food & places",
|
|
124
|
+
"Events",
|
|
125
|
+
"Health",
|
|
126
|
+
"Schools",
|
|
127
|
+
"Opinion"
|
|
128
|
+
];
|
|
129
|
+
function orderedSections(sections, order = EDITORIAL_SECTION_ORDER) {
|
|
130
|
+
return sections.filter((section) => section.name !== "Local news").slice().sort((left, right) => {
|
|
131
|
+
const leftRank = order.indexOf(left.name);
|
|
132
|
+
const rightRank = order.indexOf(right.name);
|
|
133
|
+
const leftOrder = leftRank === -1 ? order.length : leftRank;
|
|
134
|
+
const rightOrder = rightRank === -1 ? order.length : rightRank;
|
|
135
|
+
return leftOrder - rightOrder || left.name.localeCompare(right.name);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
116
138
|
function sectionId(name) {
|
|
117
139
|
return "section-" + name.normalize("NFKD").replace(/\p{Mark}/gu, "").toLowerCase().replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-|-$/g, "");
|
|
118
140
|
}
|
|
@@ -496,4 +518,55 @@ function readerSources(input) {
|
|
|
496
518
|
return output;
|
|
497
519
|
}
|
|
498
520
|
|
|
499
|
-
|
|
521
|
+
// src/listening.ts
|
|
522
|
+
function publicationListeningTrack(article, publicationId, publicationName, origin) {
|
|
523
|
+
const articleUrl2 = new URL(`/news/${encodeURIComponent(article.slug)}`, origin).href;
|
|
524
|
+
let storyId = articleUrl2;
|
|
525
|
+
if (article.canonical_url) {
|
|
526
|
+
try {
|
|
527
|
+
const canonical = new URL(article.canonical_url);
|
|
528
|
+
if (canonical.protocol === "https:" && !canonical.username && !canonical.password && /^\/news\/[^/]+\/?$/.test(canonical.pathname) && localPublications.some((item) => new URL(item.url).hostname === canonical.hostname.replace(/^www\./, ""))) {
|
|
529
|
+
canonical.search = "";
|
|
530
|
+
canonical.hash = "";
|
|
531
|
+
canonical.hostname = canonical.hostname.replace(/^www\./, "");
|
|
532
|
+
canonical.pathname = canonical.pathname.replace(/\/$/, "");
|
|
533
|
+
storyId = canonical.href;
|
|
534
|
+
}
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const revision = article.editorial?.modified_at;
|
|
539
|
+
return {
|
|
540
|
+
id: article.id,
|
|
541
|
+
publicationId,
|
|
542
|
+
publicationName,
|
|
543
|
+
title: article.title,
|
|
544
|
+
articleUrl: articleUrl2,
|
|
545
|
+
storyId,
|
|
546
|
+
...revision && Number.isFinite(Date.parse(revision)) ? { revision } : {},
|
|
547
|
+
...article.audio?.duration_ms && article.audio.duration_ms > 0 ? { durationSeconds: article.audio.duration_ms / 1e3 } : {},
|
|
548
|
+
...article.section ? { section: article.section } : {},
|
|
549
|
+
...article.published_at ? { publishedAt: article.published_at } : {}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
async function publicationListeningCandidates(items, read, publicationId, publicationName, origin) {
|
|
553
|
+
const output = [];
|
|
554
|
+
const recent = items.filter((item) => !item.editorial?.duplicate_url).slice(0, 30);
|
|
555
|
+
let failures = 0;
|
|
556
|
+
for (let i = 0; i < recent.length; i += 4) {
|
|
557
|
+
const batch = await Promise.allSettled(recent.slice(i, i + 4).map((item) => read(item.slug)));
|
|
558
|
+
for (const result of batch) {
|
|
559
|
+
if (result.status === "rejected") {
|
|
560
|
+
failures++;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
const article = result.value;
|
|
564
|
+
if (!article.editorial?.duplicate_url && article.audio?.url && article.audio.duration_ms && article.audio.duration_ms > 0)
|
|
565
|
+
output.push(publicationListeningTrack(article, publicationId, publicationName, origin));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (!output.length && failures) throw new Error("Audio inventory could not be loaded");
|
|
569
|
+
return output.sort((a, b) => (b.publishedAt ?? "").localeCompare(a.publishedAt ?? ""));
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, EDITORIAL_SECTION_ORDER, EVENTS, NEWS_CACHE, NewsError, SESSION_IDLE_MS, UTM_FIELDS, UUID, activeNearby, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, composeEdition, defaultRevalidateTargets, displayHeadline, edgeCacheControl, editorialSections, feedCacheKey, feedCacheOptions, filterNews, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, referralCode, resolveCoupon, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/news-publication",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
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",
|