@scalemule/news-publication 0.0.81 → 0.0.83
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 +48 -9
- package/bin/scalemule-article.mjs +22 -5
- package/dist/article-workflow-client.cjs +27 -135
- package/dist/article-workflow-client.d.cts +73 -13
- package/dist/article-workflow-client.d.ts +73 -13
- package/dist/article-workflow-client.js +27 -135
- package/dist/index.cjs +201 -0
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +196 -1
- package/package.json +1 -1
- package/tokens/broadsheet.css +4 -1
package/dist/index.js
CHANGED
|
@@ -773,8 +773,11 @@ var EVENTS = {
|
|
|
773
773
|
article_shared: browser("engagement"),
|
|
774
774
|
promotion_viewed: browser("content"),
|
|
775
775
|
promotion_clicked: browser("engagement"),
|
|
776
|
+
newsletter_invitation_viewed: browser("content"),
|
|
777
|
+
related_stories_viewed: browser("content"),
|
|
776
778
|
newsletter_signup_started: browser("engagement"),
|
|
777
779
|
newsletter_subscription_confirmed: server(),
|
|
780
|
+
experiment_exposed: browser("system"),
|
|
778
781
|
web_vital_recorded: browser("system"),
|
|
779
782
|
scroll_depth_reached: browser("content"),
|
|
780
783
|
navigation_clicked: browser("navigation"),
|
|
@@ -1020,4 +1023,196 @@ function campaignReportHtml(report) {
|
|
|
1020
1023
|
<footer><p>${escape(report.reporting_note)}</p><p>${escape(report.editorial_independence)}</p><p>Campaign reference: ${escape(report.campaign_id)}</p></footer></body></html>`;
|
|
1021
1024
|
}
|
|
1022
1025
|
|
|
1023
|
-
|
|
1026
|
+
// src/community-conversion.ts
|
|
1027
|
+
var ARTICLE_CONVERSION_EXPERIMENT = "article_conversion_module";
|
|
1028
|
+
var CELEBRATION_PATTERN = /\b(celebrat(?:e|ion|ions|ing)|wedding|weddings|baby|babies|newborn|quarter[- ]?finals?|milestone|milestones|anniversar(?:y|ies)|graduat(?:e|es|ion|ions)|honor(?:s|ed|ing)?|award(?:s|ed)?|champion|champions|hero(?:es)?|little warrior|birthday)\b/iu;
|
|
1029
|
+
var SPONSORED_MARKER_PATTERN = /\b(sponsored|paid post|partner content|advertorial|promoted|brand studio)\b/iu;
|
|
1030
|
+
function resolveArticleConversionVariant(assignedVariant, queryOverride) {
|
|
1031
|
+
const candidate = (queryOverride ?? assignedVariant ?? "").trim().toLowerCase();
|
|
1032
|
+
if (candidate === "treatment" || candidate === "inline" || candidate === "b") {
|
|
1033
|
+
return "treatment";
|
|
1034
|
+
}
|
|
1035
|
+
return "control";
|
|
1036
|
+
}
|
|
1037
|
+
function classifyStoryInvitationCategory(story) {
|
|
1038
|
+
const combinedText = [
|
|
1039
|
+
story.title,
|
|
1040
|
+
story.excerpt ?? "",
|
|
1041
|
+
story.section ?? "",
|
|
1042
|
+
...(story.tags ?? []).map((t) => t.name),
|
|
1043
|
+
...(story.categories ?? []).map((c) => c.name)
|
|
1044
|
+
].join(" ");
|
|
1045
|
+
if (CELEBRATION_PATTERN.test(combinedText)) {
|
|
1046
|
+
return "celebrations";
|
|
1047
|
+
}
|
|
1048
|
+
const section = resolveSection(story);
|
|
1049
|
+
switch (section) {
|
|
1050
|
+
case "Local businesses":
|
|
1051
|
+
return "local_businesses";
|
|
1052
|
+
case "Schools":
|
|
1053
|
+
return "education";
|
|
1054
|
+
case "Events":
|
|
1055
|
+
return "community_events";
|
|
1056
|
+
case "Civic life":
|
|
1057
|
+
case "Real Estate":
|
|
1058
|
+
case "Health":
|
|
1059
|
+
return "neighborhood_news";
|
|
1060
|
+
default:
|
|
1061
|
+
return "community";
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function contextualCommunityInvitation(story, publication) {
|
|
1065
|
+
const category = classifyStoryInvitationCategory(story);
|
|
1066
|
+
const region = publication.regionLabel.trim() || publication.name.trim();
|
|
1067
|
+
const coverage = publication.coverageText?.trim() || (publication.towns && publication.towns.length > 0 ? publication.towns.slice(0, 3).join(", ") : region);
|
|
1068
|
+
switch (category) {
|
|
1069
|
+
case "celebrations":
|
|
1070
|
+
return {
|
|
1071
|
+
category,
|
|
1072
|
+
eyebrow: `${region} Community`,
|
|
1073
|
+
heading: `Stay connected to ${region}`,
|
|
1074
|
+
subprompt: "Discover more people and milestones worth celebrating.",
|
|
1075
|
+
description: `${region} is full of stories worth celebrating. Discover the people, places, events, and milestones shaping ${coverage}.`,
|
|
1076
|
+
ctaLabel: "Subscribe",
|
|
1077
|
+
relatedHeading: `More stories from ${region}`
|
|
1078
|
+
};
|
|
1079
|
+
case "local_businesses":
|
|
1080
|
+
return {
|
|
1081
|
+
category,
|
|
1082
|
+
eyebrow: `${region} Local Businesses`,
|
|
1083
|
+
heading: `Stay connected to ${region}`,
|
|
1084
|
+
subprompt: "Meet the entrepreneurs building your community.",
|
|
1085
|
+
description: `Discover the founders, independent shops, places, and events shaping ${coverage}.`,
|
|
1086
|
+
ctaLabel: "Subscribe",
|
|
1087
|
+
relatedHeading: `More local stories in ${region}`
|
|
1088
|
+
};
|
|
1089
|
+
case "education":
|
|
1090
|
+
return {
|
|
1091
|
+
category,
|
|
1092
|
+
eyebrow: `${region} Schools & Youth`,
|
|
1093
|
+
heading: `Stay connected to ${region}`,
|
|
1094
|
+
subprompt: "Follow achievements from local students and schools.",
|
|
1095
|
+
description: `Discover the students, educators, neighborhood milestones, and events shaping ${coverage}.`,
|
|
1096
|
+
ctaLabel: "Subscribe",
|
|
1097
|
+
relatedHeading: `More stories from ${region}`
|
|
1098
|
+
};
|
|
1099
|
+
case "community_events":
|
|
1100
|
+
return {
|
|
1101
|
+
category,
|
|
1102
|
+
eyebrow: `${region} Events & Gatherings`,
|
|
1103
|
+
heading: `Stay connected to ${region}`,
|
|
1104
|
+
subprompt: "Find something happening near you this weekend.",
|
|
1105
|
+
description: `Discover the people, places, weekend gatherings, and local stories shaping ${coverage}.`,
|
|
1106
|
+
ctaLabel: "Subscribe",
|
|
1107
|
+
relatedHeading: `Happening around ${region}`
|
|
1108
|
+
};
|
|
1109
|
+
case "neighborhood_news":
|
|
1110
|
+
return {
|
|
1111
|
+
category,
|
|
1112
|
+
eyebrow: `${region} Neighborhood Briefing`,
|
|
1113
|
+
heading: `Stay connected to ${region}`,
|
|
1114
|
+
subprompt: "Stay informed about changes affecting your neighborhood.",
|
|
1115
|
+
description: `Follow the civic decisions, neighborhood projects, people, and events shaping ${coverage}.`,
|
|
1116
|
+
ctaLabel: "Subscribe",
|
|
1117
|
+
relatedHeading: `More from ${region}`
|
|
1118
|
+
};
|
|
1119
|
+
case "community":
|
|
1120
|
+
default:
|
|
1121
|
+
return {
|
|
1122
|
+
category: "community",
|
|
1123
|
+
eyebrow: `${region} Community`,
|
|
1124
|
+
heading: `Stay connected to ${region}`,
|
|
1125
|
+
subprompt: "Discover the people, places, and events shaping your community.",
|
|
1126
|
+
description: `Receive local reporting on the people, places, events, and stories shaping ${coverage}.`,
|
|
1127
|
+
ctaLabel: "Subscribe",
|
|
1128
|
+
relatedHeading: `More stories from ${region}`
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
function isSponsoredStoryItem(item) {
|
|
1133
|
+
const markers = [
|
|
1134
|
+
item.section ?? "",
|
|
1135
|
+
...(item.tags ?? []).map((t) => `${t.name} ${t.slug}`),
|
|
1136
|
+
...(item.categories ?? []).map((c) => `${c.name} ${c.slug}`)
|
|
1137
|
+
].join(" ");
|
|
1138
|
+
return SPONSORED_MARKER_PATTERN.test(markers);
|
|
1139
|
+
}
|
|
1140
|
+
function normalizedCoverIdentity(item) {
|
|
1141
|
+
const raw = item.cover_image_url?.trim();
|
|
1142
|
+
if (!raw) return null;
|
|
1143
|
+
if (item.visual_identity?.version === 1 && /^[a-f0-9]{64}$/u.test(item.visual_identity.key)) {
|
|
1144
|
+
return `pixels:${item.visual_identity.key}`;
|
|
1145
|
+
}
|
|
1146
|
+
try {
|
|
1147
|
+
const url = new URL(raw);
|
|
1148
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
|
|
1149
|
+
url.hash = "";
|
|
1150
|
+
url.searchParams.sort();
|
|
1151
|
+
return `url:${url.toString()}`;
|
|
1152
|
+
} catch {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
function selectRelevantLocalStories(currentStory, candidates, publication, limit = 3) {
|
|
1157
|
+
if (limit <= 0) return [];
|
|
1158
|
+
const currentSection = resolveSection(currentStory);
|
|
1159
|
+
const pubTowns = Array.isArray(publication.towns) ? publication.towns : [];
|
|
1160
|
+
const currentTowns = new Set(storyTowns(currentStory, pubTowns).map((t) => t.toLowerCase()));
|
|
1161
|
+
const currentTags = /* @__PURE__ */ new Set([
|
|
1162
|
+
...(currentStory.tags ?? []).map((t) => t.name.toLowerCase()),
|
|
1163
|
+
...(currentStory.categories ?? []).map((c) => c.name.toLowerCase())
|
|
1164
|
+
]);
|
|
1165
|
+
const primaryRegion = publication.regionLabel.trim().toLowerCase();
|
|
1166
|
+
const seenIds = /* @__PURE__ */ new Set([currentStory.id]);
|
|
1167
|
+
const seenSlugs = /* @__PURE__ */ new Set([currentStory.slug]);
|
|
1168
|
+
const usedCovers = /* @__PURE__ */ new Set();
|
|
1169
|
+
const currentCover = normalizedCoverIdentity(currentStory);
|
|
1170
|
+
if (currentCover) usedCovers.add(currentCover);
|
|
1171
|
+
const scored = candidates.filter((item) => {
|
|
1172
|
+
if (!item?.id || !item?.slug) return false;
|
|
1173
|
+
if (seenIds.has(item.id) || seenSlugs.has(item.slug)) return false;
|
|
1174
|
+
if (isSponsoredStoryItem(item)) return false;
|
|
1175
|
+
return true;
|
|
1176
|
+
}).map((item) => {
|
|
1177
|
+
let score = 0;
|
|
1178
|
+
const cover = normalizedCoverIdentity(item);
|
|
1179
|
+
if (cover) score += 40;
|
|
1180
|
+
const itemTowns = storyTowns(item, pubTowns).map((t) => t.toLowerCase());
|
|
1181
|
+
if (itemTowns.some((t) => currentTowns.has(t))) {
|
|
1182
|
+
score += 25;
|
|
1183
|
+
} else if (primaryRegion && `${item.title} ${item.excerpt ?? ""}`.toLowerCase().includes(primaryRegion)) {
|
|
1184
|
+
score += 18;
|
|
1185
|
+
}
|
|
1186
|
+
const section = resolveSection(item);
|
|
1187
|
+
if (section === currentSection) {
|
|
1188
|
+
score += 20;
|
|
1189
|
+
} else if (["Community", "Events", "Schools", "Local businesses", "Food & places"].includes(section)) {
|
|
1190
|
+
score += 12;
|
|
1191
|
+
}
|
|
1192
|
+
const itemTags = [
|
|
1193
|
+
...(item.tags ?? []).map((t) => t.name.toLowerCase()),
|
|
1194
|
+
...(item.categories ?? []).map((c) => c.name.toLowerCase())
|
|
1195
|
+
];
|
|
1196
|
+
const sharedTags = itemTags.filter((t) => currentTags.has(t)).length;
|
|
1197
|
+
score += Math.min(sharedTags * 8, 16);
|
|
1198
|
+
if (CELEBRATION_PATTERN.test(`${item.title} ${item.excerpt ?? ""}`)) {
|
|
1199
|
+
score += 6;
|
|
1200
|
+
}
|
|
1201
|
+
const parsedTime = Date.parse(item.published_at);
|
|
1202
|
+
const publishedMs = Number.isFinite(parsedTime) ? parsedTime : 0;
|
|
1203
|
+
return { item: { ...item, section }, cover, score, publishedMs };
|
|
1204
|
+
}).sort((a, b) => b.score - a.score || b.publishedMs - a.publishedMs);
|
|
1205
|
+
const selected = [];
|
|
1206
|
+
for (const entry of scored) {
|
|
1207
|
+
if (selected.length >= limit) break;
|
|
1208
|
+
if (seenIds.has(entry.item.id) || seenSlugs.has(entry.item.slug)) continue;
|
|
1209
|
+
if (entry.cover && usedCovers.has(entry.cover)) continue;
|
|
1210
|
+
seenIds.add(entry.item.id);
|
|
1211
|
+
seenSlugs.add(entry.item.slug);
|
|
1212
|
+
if (entry.cover) usedCovers.add(entry.cover);
|
|
1213
|
+
selected.push(entry.item);
|
|
1214
|
+
}
|
|
1215
|
+
return selected;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ARTICLE_CONVERSION_EXPERIMENT, ArticleProvenance, EDITORIAL_SECTION_ORDER, EVENTS, LOCAL_BRIEFING_POLICY_VERSION, NAVIGATION_GROUPS, NETWORK_NAVIGATION, NEWS_CACHE, NewsError, SESSION_IDLE_MS, UTM_FIELDS, UUID, activeNearby, applyNavigationConfig, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, classifyStoryInvitationCategory, composeEdition, composeLocalBriefing, contextualCommunityInvitation, defaultRevalidateTargets, displayHeadline, edgeCacheControl, editorialSections, feedCacheKey, feedCacheOptions, filterNews, inventoryCacheOptions, isSponsoredStoryItem, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, referralCode, resolveArticleConversionVariant, resolveCoupon, resolveNavigation, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, selectRelevantLocalStories, 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.83",
|
|
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": {
|
package/tokens/broadsheet.css
CHANGED
|
@@ -508,12 +508,15 @@
|
|
|
508
508
|
.publication [class*="sectionCollection"] > [class*="section"]:only-child [class*="sectionFeature"],
|
|
509
509
|
.publication [class*="sectionCollection"] > [class*="section"]:last-child:nth-child(odd) [class*="sectionFeature"] {
|
|
510
510
|
display: block;
|
|
511
|
+
grid-column: auto;
|
|
512
|
+
grid-row: auto;
|
|
511
513
|
}
|
|
512
514
|
|
|
513
515
|
.publication [class*="sectionCollection"] > [class*="section"]:only-child [class*="sectionSupporting"],
|
|
514
516
|
.publication [class*="sectionCollection"] > [class*="section"]:last-child:nth-child(odd) [class*="sectionSupporting"] {
|
|
515
517
|
display: grid;
|
|
516
|
-
grid-
|
|
518
|
+
grid-column: auto;
|
|
519
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
517
520
|
gap: 20px;
|
|
518
521
|
align-content: start;
|
|
519
522
|
}
|