@scalemule/news-publication 0.0.92 → 0.0.93
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/bin/scalemule-article.mjs +4 -2
- package/dist/article-workflow-client.cjs +30 -1
- package/dist/article-workflow-client.d.cts +9 -1
- package/dist/article-workflow-client.d.ts +9 -1
- package/dist/article-workflow-client.js +9 -1
- package/dist/chunk-SNQKSUWB.js +42 -0
- package/dist/index.cjs +100 -50
- package/dist/index.d.cts +50 -6
- package/dist/index.d.ts +50 -6
- package/dist/index.js +9 -7
- package/dist/{snapshot-BBTow5Bn.d.cts → snapshot-C3kD6oFZ.d.cts} +1 -1
- package/dist/{snapshot-CHdsfUnU.d.ts → snapshot-CEtTFD_h.d.ts} +1 -1
- package/dist/snapshot-file.d.cts +2 -2
- package/dist/snapshot-file.d.ts +2 -2
- package/dist/{types-22lEf_UU.d.cts → types-aYDyFCGs.d.cts} +7 -0
- package/dist/{types-22lEf_UU.d.ts → types-aYDyFCGs.d.ts} +7 -0
- package/dist/verified-card.d.cts +1 -1
- package/dist/verified-card.d.ts +1 -1
- package/package.json +1 -1
|
@@ -18,7 +18,9 @@ Credentials: SCALEMULE_API_KEY and SCALEMULE_ACCESS_TOKEN (normal authorized mem
|
|
|
18
18
|
Platform admins may use their normal access token with --application-id ID.
|
|
19
19
|
Optional --api-url URL defaults to https://api.scalemule.com.
|
|
20
20
|
|
|
21
|
-
edit saves prose, excerpt, cards, citations
|
|
21
|
+
edit saves prose, excerpt, cards, citations, tenant-owned tag_ids and the headline set (title,
|
|
22
|
+
headline <= 120 chars with 70 recommended, fly_title <= 40, subtitle as the rubric, seo_title) in
|
|
23
|
+
one immutable revision.
|
|
22
24
|
Unspecified fields and media are retained. Default: save for review.
|
|
23
25
|
--release explicitly requests release under the existing policy; it never issues
|
|
24
26
|
APPROVE or changes a policy. Legacy onboarding requires an explicit
|
|
@@ -157,7 +159,7 @@ try {
|
|
|
157
159
|
while(!verified){
|
|
158
160
|
const response=await fetch(values.url,{redirect:'error',cache:'no-store',signal:AbortSignal.timeout(Math.min(5000,remaining()))});
|
|
159
161
|
const html=await response.text();
|
|
160
|
-
verified=response.ok&&articleEditHtmlMatches(html,{title:article.title,content_html:article.content_html,card_titles:(article.metadata?.cards??[]).map(c=>c.title),tags:checkpoint.document.tag_ids?article.tags:undefined});
|
|
162
|
+
verified=response.ok&&articleEditHtmlMatches(html,{title:article.title,headline:article.headline,content_html:article.content_html,card_titles:(article.metadata?.cards??[]).map(c=>c.title),tags:checkpoint.document.tag_ids?article.tags:undefined});
|
|
161
163
|
if(!verified)await new Promise(r=>setTimeout(r,Math.min(300,remaining())));
|
|
162
164
|
}
|
|
163
165
|
}catch(error){reason=error.message;}
|
|
@@ -31,8 +31,9 @@ var text = (html) => decode(html.replace(/<[^>]*>/gu, " ")).replace(/\s+/gu, " "
|
|
|
31
31
|
var plain = (value) => value.replace(/\s+/gu, " ").trim();
|
|
32
32
|
function articleEditHtmlMatches(html, expected) {
|
|
33
33
|
const visible = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/giu, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/giu, " ");
|
|
34
|
+
const shown = plain(expected.headline?.trim() || expected.title);
|
|
34
35
|
const headline = [...visible.matchAll(/<h1\b[^>]*>([\s\S]*?)<\/h1>/giu)].some(
|
|
35
|
-
(m) => text(m[1]) ===
|
|
36
|
+
(m) => text(m[1]) === shown
|
|
36
37
|
);
|
|
37
38
|
if (!headline) return false;
|
|
38
39
|
const paragraphs = [
|
|
@@ -339,6 +340,29 @@ async function runArticlePhoto(t, input) {
|
|
|
339
340
|
}
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
// src/headline.ts
|
|
344
|
+
var HEADLINE_MAX_CHARS = 120;
|
|
345
|
+
var FLY_TITLE_MAX_CHARS = 40;
|
|
346
|
+
var RUBRIC_MAX_CHARS = 500;
|
|
347
|
+
var SEO_TITLE_MAX_CHARS = 200;
|
|
348
|
+
var headlineLength = (value) => Array.from(value).length;
|
|
349
|
+
function headlineProblems(fields) {
|
|
350
|
+
const problems = [];
|
|
351
|
+
const check = (name, value, max) => {
|
|
352
|
+
if (value === void 0 || value === null) return;
|
|
353
|
+
if (typeof value !== "string") {
|
|
354
|
+
problems.push(`${name} must be a string`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (headlineLength(value.trim()) > max) problems.push(`${name} exceeds ${max} characters`);
|
|
358
|
+
};
|
|
359
|
+
check("headline", fields.headline, HEADLINE_MAX_CHARS);
|
|
360
|
+
check("fly_title", fields.fly_title, FLY_TITLE_MAX_CHARS);
|
|
361
|
+
check("subtitle", fields.subtitle, RUBRIC_MAX_CHARS);
|
|
362
|
+
check("seo_title", fields.seo_title, SEO_TITLE_MAX_CHARS);
|
|
363
|
+
return problems;
|
|
364
|
+
}
|
|
365
|
+
|
|
342
366
|
// src/article-edit-operation.ts
|
|
343
367
|
var ArticleEditOperationError = class extends Error {
|
|
344
368
|
constructor(code, stage, message, status, revisionId) {
|
|
@@ -378,7 +402,10 @@ var digest2 = async (v) => [
|
|
|
378
402
|
].map((v2) => v2.toString(16).padStart(2, "0")).join("");
|
|
379
403
|
var patchKeys = /* @__PURE__ */ new Set([
|
|
380
404
|
"title",
|
|
405
|
+
"headline",
|
|
406
|
+
"fly_title",
|
|
381
407
|
"subtitle",
|
|
408
|
+
"seo_title",
|
|
382
409
|
"content_markdown",
|
|
383
410
|
"excerpt",
|
|
384
411
|
"section",
|
|
@@ -407,6 +434,8 @@ function validate(input) {
|
|
|
407
434
|
} else if (typeof value !== "string")
|
|
408
435
|
throw Error(key + " must be a string");
|
|
409
436
|
}
|
|
437
|
+
const problems = headlineProblems(input.patch);
|
|
438
|
+
if (problems.length) throw Error(problems.join("; "));
|
|
410
439
|
if (input.expectedGeneration !== void 0 && (!Number.isSafeInteger(input.expectedGeneration) || input.expectedGeneration < 1))
|
|
411
440
|
throw Error("Expected generation must be positive");
|
|
412
441
|
if (input.enableReviewPolicy && ![
|
|
@@ -6,6 +6,7 @@ declare function articlePhotoHtmlMatches(html: string, lead: string | string[],
|
|
|
6
6
|
/** Requires visible body and actual card asides; serialized page data is not delivery. */
|
|
7
7
|
declare function articleEditHtmlMatches(html: string, expected: {
|
|
8
8
|
title: string;
|
|
9
|
+
headline?: string | null;
|
|
9
10
|
content_html: string;
|
|
10
11
|
card_titles: string[];
|
|
11
12
|
tags?: {
|
|
@@ -52,7 +53,7 @@ declare class ArticlePhotoOperationError extends Error {
|
|
|
52
53
|
constructor(code: string, stage: string, message: string, fileId?: string | undefined, status?: number | undefined);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
type ArticleEditPatch = Partial<Pick<ArticleDocument, "title" | "subtitle" | "content_markdown" | "excerpt" | "section" | "community" | "format" | "layout" | "cards" | "citations" | "tag_ids">>;
|
|
56
|
+
type ArticleEditPatch = Partial<Pick<ArticleDocument, "title" | "headline" | "fly_title" | "subtitle" | "seo_title" | "content_markdown" | "excerpt" | "section" | "community" | "format" | "layout" | "cards" | "citations" | "tag_ids">>;
|
|
56
57
|
type ArticleEditCheckpoint = {
|
|
57
58
|
version: 1;
|
|
58
59
|
identity: string;
|
|
@@ -92,7 +93,14 @@ declare class ArticleEditOperationError extends Error {
|
|
|
92
93
|
|
|
93
94
|
type ArticleDocument = {
|
|
94
95
|
title: string;
|
|
96
|
+
/** Short display headline (<= 120 characters, 70 recommended); see `headlineSet`. */
|
|
97
|
+
headline?: string;
|
|
98
|
+
/** Kicker above the headline (<= 40 characters). */
|
|
99
|
+
fly_title?: string;
|
|
100
|
+
/** The rubric (deck). */
|
|
95
101
|
subtitle?: string;
|
|
102
|
+
/** Search title for <title> and previews. */
|
|
103
|
+
seo_title?: string;
|
|
96
104
|
content_markdown: string;
|
|
97
105
|
excerpt?: string;
|
|
98
106
|
cards?: unknown[];
|
|
@@ -6,6 +6,7 @@ declare function articlePhotoHtmlMatches(html: string, lead: string | string[],
|
|
|
6
6
|
/** Requires visible body and actual card asides; serialized page data is not delivery. */
|
|
7
7
|
declare function articleEditHtmlMatches(html: string, expected: {
|
|
8
8
|
title: string;
|
|
9
|
+
headline?: string | null;
|
|
9
10
|
content_html: string;
|
|
10
11
|
card_titles: string[];
|
|
11
12
|
tags?: {
|
|
@@ -52,7 +53,7 @@ declare class ArticlePhotoOperationError extends Error {
|
|
|
52
53
|
constructor(code: string, stage: string, message: string, fileId?: string | undefined, status?: number | undefined);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
type ArticleEditPatch = Partial<Pick<ArticleDocument, "title" | "subtitle" | "content_markdown" | "excerpt" | "section" | "community" | "format" | "layout" | "cards" | "citations" | "tag_ids">>;
|
|
56
|
+
type ArticleEditPatch = Partial<Pick<ArticleDocument, "title" | "headline" | "fly_title" | "subtitle" | "seo_title" | "content_markdown" | "excerpt" | "section" | "community" | "format" | "layout" | "cards" | "citations" | "tag_ids">>;
|
|
56
57
|
type ArticleEditCheckpoint = {
|
|
57
58
|
version: 1;
|
|
58
59
|
identity: string;
|
|
@@ -92,7 +93,14 @@ declare class ArticleEditOperationError extends Error {
|
|
|
92
93
|
|
|
93
94
|
type ArticleDocument = {
|
|
94
95
|
title: string;
|
|
96
|
+
/** Short display headline (<= 120 characters, 70 recommended); see `headlineSet`. */
|
|
97
|
+
headline?: string;
|
|
98
|
+
/** Kicker above the headline (<= 40 characters). */
|
|
99
|
+
fly_title?: string;
|
|
100
|
+
/** The rubric (deck). */
|
|
95
101
|
subtitle?: string;
|
|
102
|
+
/** Search title for <title> and previews. */
|
|
103
|
+
seo_title?: string;
|
|
96
104
|
content_markdown: string;
|
|
97
105
|
excerpt?: string;
|
|
98
106
|
cards?: unknown[];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { headlineProblems } from './chunk-SNQKSUWB.js';
|
|
2
|
+
|
|
1
3
|
// src/article-photo-verification.ts
|
|
2
4
|
function articlePhotoHtmlMatches(html, lead, social) {
|
|
3
5
|
const contains = (tag, url) => tag.includes(url) || tag.includes(encodeURIComponent(url));
|
|
@@ -29,8 +31,9 @@ var text = (html) => decode(html.replace(/<[^>]*>/gu, " ")).replace(/\s+/gu, " "
|
|
|
29
31
|
var plain = (value) => value.replace(/\s+/gu, " ").trim();
|
|
30
32
|
function articleEditHtmlMatches(html, expected) {
|
|
31
33
|
const visible = html.replace(/<script\b[^>]*>[\s\S]*?<\/script>/giu, " ").replace(/<style\b[^>]*>[\s\S]*?<\/style>/giu, " ");
|
|
34
|
+
const shown = plain(expected.headline?.trim() || expected.title);
|
|
32
35
|
const headline = [...visible.matchAll(/<h1\b[^>]*>([\s\S]*?)<\/h1>/giu)].some(
|
|
33
|
-
(m) => text(m[1]) ===
|
|
36
|
+
(m) => text(m[1]) === shown
|
|
34
37
|
);
|
|
35
38
|
if (!headline) return false;
|
|
36
39
|
const paragraphs = [
|
|
@@ -376,7 +379,10 @@ var digest2 = async (v) => [
|
|
|
376
379
|
].map((v2) => v2.toString(16).padStart(2, "0")).join("");
|
|
377
380
|
var patchKeys = /* @__PURE__ */ new Set([
|
|
378
381
|
"title",
|
|
382
|
+
"headline",
|
|
383
|
+
"fly_title",
|
|
379
384
|
"subtitle",
|
|
385
|
+
"seo_title",
|
|
380
386
|
"content_markdown",
|
|
381
387
|
"excerpt",
|
|
382
388
|
"section",
|
|
@@ -405,6 +411,8 @@ function validate(input) {
|
|
|
405
411
|
} else if (typeof value !== "string")
|
|
406
412
|
throw Error(key + " must be a string");
|
|
407
413
|
}
|
|
414
|
+
const problems = headlineProblems(input.patch);
|
|
415
|
+
if (problems.length) throw Error(problems.join("; "));
|
|
408
416
|
if (input.expectedGeneration !== void 0 && (!Number.isSafeInteger(input.expectedGeneration) || input.expectedGeneration < 1))
|
|
409
417
|
throw Error("Expected generation must be positive");
|
|
410
418
|
if (input.enableReviewPolicy && ![
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/headline.ts
|
|
2
|
+
var HEADLINE_RECOMMENDED_CHARS = 70;
|
|
3
|
+
var HEADLINE_MAX_CHARS = 120;
|
|
4
|
+
var FLY_TITLE_MAX_CHARS = 40;
|
|
5
|
+
var RUBRIC_MAX_CHARS = 500;
|
|
6
|
+
var SEO_TITLE_MAX_CHARS = 200;
|
|
7
|
+
var text = (value) => {
|
|
8
|
+
if (typeof value !== "string") return void 0;
|
|
9
|
+
const trimmed = value.replace(/\s+/gu, " ").trim();
|
|
10
|
+
return trimmed || void 0;
|
|
11
|
+
};
|
|
12
|
+
var headlineLength = (value) => Array.from(value).length;
|
|
13
|
+
function headlineSet(article, selection = {}) {
|
|
14
|
+
const display = selection.stories?.[article.slug];
|
|
15
|
+
const title = text(article.title) ?? "";
|
|
16
|
+
const headline = text(display?.display_title) ?? text(article.headline) ?? title;
|
|
17
|
+
return {
|
|
18
|
+
flyTitle: text(article.fly_title) ?? text(article.section),
|
|
19
|
+
headline,
|
|
20
|
+
rubric: text(display?.deck) ?? text(article.subtitle),
|
|
21
|
+
seoTitle: text(article.seo_title) ?? title,
|
|
22
|
+
title
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function headlineProblems(fields) {
|
|
26
|
+
const problems = [];
|
|
27
|
+
const check = (name, value, max) => {
|
|
28
|
+
if (value === void 0 || value === null) return;
|
|
29
|
+
if (typeof value !== "string") {
|
|
30
|
+
problems.push(`${name} must be a string`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (headlineLength(value.trim()) > max) problems.push(`${name} exceeds ${max} characters`);
|
|
34
|
+
};
|
|
35
|
+
check("headline", fields.headline, HEADLINE_MAX_CHARS);
|
|
36
|
+
check("fly_title", fields.fly_title, FLY_TITLE_MAX_CHARS);
|
|
37
|
+
check("subtitle", fields.subtitle, RUBRIC_MAX_CHARS);
|
|
38
|
+
check("seo_title", fields.seo_title, SEO_TITLE_MAX_CHARS);
|
|
39
|
+
return problems;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { FLY_TITLE_MAX_CHARS, HEADLINE_MAX_CHARS, HEADLINE_RECOMMENDED_CHARS, RUBRIC_MAX_CHARS, SEO_TITLE_MAX_CHARS, headlineLength, headlineProblems, headlineSet };
|
package/dist/index.cjs
CHANGED
|
@@ -122,14 +122,14 @@ function resolveSection(item) {
|
|
|
122
122
|
if (/\b(food|dining|restaurant|restaurants|wine|winery|brewery|bakery|cafe|coffee|cuisine|chef)\b/i.test(desc)) return "Food & places";
|
|
123
123
|
if (/\b(community|volunteer|volunteers|library|libraries|neighborhood|nonprofit|charity|youth|senior|seniors)\b/i.test(desc)) return "Community";
|
|
124
124
|
}
|
|
125
|
-
const
|
|
126
|
-
if (/\b(health|hospitals?|clinics?|medical|doctors?|pharmac(?:y|ies)|wellness|veterinar(?:y|ian)|pet care|animal welfare|vaccinat(?:ion|ions)|patients?)\b/iu.test(
|
|
127
|
-
if (/\b(real estate|homeownership|mortgage|housing|tenants?|apartments?|below-market rate|home sales?|condos?)\b/iu.test(
|
|
128
|
-
if (/\b(city council|planning commission|board of supervisors|ballot|measure [a-z0-9]|election|transit|traffic|police|firefighter|wildfire|evacuation|pedestrian safety|city manager|public works)\b/iu.test(
|
|
129
|
-
if (/\b(storefront|retail|downtown business|chamber of commerce|opens? doors?|boutique|merchants?|shoppers?|commercial space)\b/iu.test(
|
|
130
|
-
if (/\b(festival|parade|fair|concert|workshop|exhibition|showcase|celebration|weigh-off|halloween|trunk-or-treat)\b/iu.test(
|
|
131
|
-
if (/\b(schools?|students?|teachers?|campuses?|classrooms?|high school|elementary|school district)\b/iu.test(
|
|
132
|
-
if (/\b(restaurants?|cafes?|baker(?:y|ies)|winer(?:y|ies)|brewer(?:y|ies)|dining|chefs?|menus?)\b/iu.test(
|
|
125
|
+
const text2 = `${item.title} ${item.excerpt ?? ""}`;
|
|
126
|
+
if (/\b(health|hospitals?|clinics?|medical|doctors?|pharmac(?:y|ies)|wellness|veterinar(?:y|ian)|pet care|animal welfare|vaccinat(?:ion|ions)|patients?)\b/iu.test(text2)) return "Health";
|
|
127
|
+
if (/\b(real estate|homeownership|mortgage|housing|tenants?|apartments?|below-market rate|home sales?|condos?)\b/iu.test(text2)) return "Real Estate";
|
|
128
|
+
if (/\b(city council|planning commission|board of supervisors|ballot|measure [a-z0-9]|election|transit|traffic|police|firefighter|wildfire|evacuation|pedestrian safety|city manager|public works)\b/iu.test(text2)) return "Civic life";
|
|
129
|
+
if (/\b(storefront|retail|downtown business|chamber of commerce|opens? doors?|boutique|merchants?|shoppers?|commercial space)\b/iu.test(text2)) return "Local businesses";
|
|
130
|
+
if (/\b(festival|parade|fair|concert|workshop|exhibition|showcase|celebration|weigh-off|halloween|trunk-or-treat)\b/iu.test(text2)) return "Events";
|
|
131
|
+
if (/\b(schools?|students?|teachers?|campuses?|classrooms?|high school|elementary|school district)\b/iu.test(text2)) return "Schools";
|
|
132
|
+
if (/\b(restaurants?|cafes?|baker(?:y|ies)|winer(?:y|ies)|brewer(?:y|ies)|dining|chefs?|menus?)\b/iu.test(text2)) return "Food & places";
|
|
133
133
|
return "Community";
|
|
134
134
|
}
|
|
135
135
|
function orderedSections(sections2, order = EDITORIAL_SECTION_ORDER) {
|
|
@@ -234,7 +234,7 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
234
234
|
return { lead, secondary, briefs, compact, sections: sections2, pendingImages, navigation: editorialSections(inventory) };
|
|
235
235
|
}
|
|
236
236
|
function displayHeadline(article, selection = {}) {
|
|
237
|
-
return selection.stories?.[article.slug]?.display_title?.trim() || article.title;
|
|
237
|
+
return selection.stories?.[article.slug]?.display_title?.trim() || article.headline?.trim() || article.title;
|
|
238
238
|
}
|
|
239
239
|
function briefSummary(article, selection = {}) {
|
|
240
240
|
const chosen = selection.stories?.[article.slug]?.brief?.trim();
|
|
@@ -284,11 +284,11 @@ function nearbyUpdates(items, selection = {}, now = Date.now()) {
|
|
|
284
284
|
return activeNearby(selection, now).filter((update) => published.has(update.slug)).map((update) => ({ ...update, article: published.get(update.slug) }));
|
|
285
285
|
}
|
|
286
286
|
function shortDeck(value, limit = 160) {
|
|
287
|
-
const
|
|
288
|
-
const characters = Array.from(
|
|
289
|
-
if (characters.length <= limit) return
|
|
287
|
+
const text2 = value?.trim().replace(/\s+/gu, " ") ?? "";
|
|
288
|
+
const characters = Array.from(text2);
|
|
289
|
+
if (characters.length <= limit) return text2;
|
|
290
290
|
let sentences = "";
|
|
291
|
-
for (const { segment } of new Intl.Segmenter("en", { granularity: "sentence" }).segment(
|
|
291
|
+
for (const { segment } of new Intl.Segmenter("en", { granularity: "sentence" }).segment(text2)) {
|
|
292
292
|
if (Array.from(sentences + segment.trimEnd()).length > limit) break;
|
|
293
293
|
sentences += segment;
|
|
294
294
|
}
|
|
@@ -322,8 +322,9 @@ function newsArticleJsonLd(publication, article, canonical, authorName) {
|
|
|
322
322
|
return {
|
|
323
323
|
"@context": "https://schema.org",
|
|
324
324
|
"@type": "NewsArticle",
|
|
325
|
-
headline: article.title,
|
|
326
|
-
|
|
325
|
+
headline: article.headline?.trim() || article.title,
|
|
326
|
+
...article.headline?.trim() && article.headline.trim() !== article.title ? { alternativeHeadline: article.title } : {},
|
|
327
|
+
description: article.subtitle ?? article.excerpt ?? void 0,
|
|
327
328
|
datePublished: article.published_at,
|
|
328
329
|
...article.metadata?.editorial?.modified_at ? { dateModified: article.metadata.editorial.modified_at } : {},
|
|
329
330
|
// updated_at also changes for media/operational maintenance. A trusted
|
|
@@ -370,15 +371,15 @@ function articleMeta(publication, article, canonical, indexable) {
|
|
|
370
371
|
const canonicalUrl = publicHttps(article.canonical_url) ?? url;
|
|
371
372
|
const image = publicHttps(article.og_image_url) ?? publicHttps(article.cover_image_url);
|
|
372
373
|
return {
|
|
373
|
-
title: article.title,
|
|
374
|
-
description: article.seo_description ?? article.excerpt ?? publication.description,
|
|
374
|
+
title: article.seo_title?.trim() || article.title,
|
|
375
|
+
description: article.seo_description ?? article.subtitle ?? article.excerpt ?? publication.description,
|
|
375
376
|
canonical: canonicalUrl,
|
|
376
377
|
indexable: indexable && !article.no_index,
|
|
377
378
|
openGraph: {
|
|
378
379
|
type: "article",
|
|
379
380
|
siteName: publication.name,
|
|
380
|
-
title: article.title,
|
|
381
|
-
description: article.excerpt ?? void 0,
|
|
381
|
+
title: article.headline?.trim() || article.title,
|
|
382
|
+
description: article.subtitle ?? article.excerpt ?? void 0,
|
|
382
383
|
url,
|
|
383
384
|
publishedTime: article.published_at,
|
|
384
385
|
images: image ? [image] : []
|
|
@@ -807,6 +808,47 @@ function resolveNavigation(context) {
|
|
|
807
808
|
return { primary, groups, featured: activeFeatured(context.config, now) };
|
|
808
809
|
}
|
|
809
810
|
|
|
811
|
+
// src/headline.ts
|
|
812
|
+
var HEADLINE_RECOMMENDED_CHARS = 70;
|
|
813
|
+
var HEADLINE_MAX_CHARS = 120;
|
|
814
|
+
var FLY_TITLE_MAX_CHARS = 40;
|
|
815
|
+
var RUBRIC_MAX_CHARS = 500;
|
|
816
|
+
var SEO_TITLE_MAX_CHARS = 200;
|
|
817
|
+
var text = (value) => {
|
|
818
|
+
if (typeof value !== "string") return void 0;
|
|
819
|
+
const trimmed = value.replace(/\s+/gu, " ").trim();
|
|
820
|
+
return trimmed || void 0;
|
|
821
|
+
};
|
|
822
|
+
var headlineLength = (value) => Array.from(value).length;
|
|
823
|
+
function headlineSet(article, selection = {}) {
|
|
824
|
+
const display = selection.stories?.[article.slug];
|
|
825
|
+
const title = text(article.title) ?? "";
|
|
826
|
+
const headline = text(display?.display_title) ?? text(article.headline) ?? title;
|
|
827
|
+
return {
|
|
828
|
+
flyTitle: text(article.fly_title) ?? text(article.section),
|
|
829
|
+
headline,
|
|
830
|
+
rubric: text(display?.deck) ?? text(article.subtitle),
|
|
831
|
+
seoTitle: text(article.seo_title) ?? title,
|
|
832
|
+
title
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function headlineProblems(fields) {
|
|
836
|
+
const problems = [];
|
|
837
|
+
const check = (name, value, max) => {
|
|
838
|
+
if (value === void 0 || value === null) return;
|
|
839
|
+
if (typeof value !== "string") {
|
|
840
|
+
problems.push(`${name} must be a string`);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (headlineLength(value.trim()) > max) problems.push(`${name} exceeds ${max} characters`);
|
|
844
|
+
};
|
|
845
|
+
check("headline", fields.headline, HEADLINE_MAX_CHARS);
|
|
846
|
+
check("fly_title", fields.fly_title, FLY_TITLE_MAX_CHARS);
|
|
847
|
+
check("subtitle", fields.subtitle, RUBRIC_MAX_CHARS);
|
|
848
|
+
check("seo_title", fields.seo_title, SEO_TITLE_MAX_CHARS);
|
|
849
|
+
return problems;
|
|
850
|
+
}
|
|
851
|
+
|
|
810
852
|
// src/analytics.ts
|
|
811
853
|
var ANALYTICS_VERSION = 1;
|
|
812
854
|
var ANALYTICS_COOKIE = "napsite_analytics_v1";
|
|
@@ -869,9 +911,9 @@ var PUBLIC_PATHS = /* @__PURE__ */ new Set([
|
|
|
869
911
|
]);
|
|
870
912
|
function shortText(value, max = 100) {
|
|
871
913
|
if (typeof value !== "string") return void 0;
|
|
872
|
-
const
|
|
873
|
-
if (!
|
|
874
|
-
return Array.from(
|
|
914
|
+
const text2 = value.trim();
|
|
915
|
+
if (!text2 || /[@\r\n\u0000-\u001f]/.test(text2)) return void 0;
|
|
916
|
+
return Array.from(text2).slice(0, max).join("");
|
|
875
917
|
}
|
|
876
918
|
function publicUrl(value, originOnly = false) {
|
|
877
919
|
if (typeof value !== "string") return void 0;
|
|
@@ -979,13 +1021,13 @@ function canHostMeetup(accountCreatedAt, now = /* @__PURE__ */ new Date()) {
|
|
|
979
1021
|
return Number.isFinite(created) && now.getTime() - created >= HOST_WAIT_MS;
|
|
980
1022
|
}
|
|
981
1023
|
function screenMessage(value) {
|
|
982
|
-
const
|
|
983
|
-
if (Array.from(
|
|
984
|
-
if (/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i.test(
|
|
985
|
-
if (/(https?:\/\/|www\.)\S+/i.test(
|
|
986
|
-
const digits =
|
|
1024
|
+
const text2 = Array.from(value.trim()).slice(0, 1e3).join("");
|
|
1025
|
+
if (Array.from(text2).length < 1) return { ok: false, reason: "Write a short message." };
|
|
1026
|
+
if (/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i.test(text2)) return { ok: false, reason: "Share an email in person, not in the chat." };
|
|
1027
|
+
if (/(https?:\/\/|www\.)\S+/i.test(text2)) return { ok: false, reason: "Links are not allowed in the chat." };
|
|
1028
|
+
const digits = text2.replace(/\D/g, "");
|
|
987
1029
|
if (digits.length >= 10) return { ok: false, reason: "Share a phone number in person, not in the chat." };
|
|
988
|
-
return { ok: true, text };
|
|
1030
|
+
return { ok: true, text: text2 };
|
|
989
1031
|
}
|
|
990
1032
|
function activityLabel(value, otherLabel = "") {
|
|
991
1033
|
if (value === "other") return Array.from(otherLabel.trim()).slice(0, 40).join("") || "Something else";
|
|
@@ -1088,9 +1130,9 @@ function classifiedPath(id) {
|
|
|
1088
1130
|
return `/v1/listings/${encodeURIComponent(id)}`;
|
|
1089
1131
|
}
|
|
1090
1132
|
function requireText(value, label, max, min = 1) {
|
|
1091
|
-
const
|
|
1092
|
-
if (Array.from(
|
|
1093
|
-
return
|
|
1133
|
+
const text2 = clipChars(typeof value === "string" ? value : "", max);
|
|
1134
|
+
if (Array.from(text2).length < min) throw new Error(`Please complete the ${label}.`);
|
|
1135
|
+
return text2;
|
|
1094
1136
|
}
|
|
1095
1137
|
function prepareJobPost(input, towns) {
|
|
1096
1138
|
const title = requireText(input.title, "job title", 120);
|
|
@@ -1623,7 +1665,7 @@ var COOKIE_CATEGORIES = [
|
|
|
1623
1665
|
];
|
|
1624
1666
|
function CookieConsentBanner({
|
|
1625
1667
|
title = "Your privacy choices",
|
|
1626
|
-
text,
|
|
1668
|
+
text: text2,
|
|
1627
1669
|
settingsUrl = "/cookies",
|
|
1628
1670
|
className = "",
|
|
1629
1671
|
publicationSlug,
|
|
@@ -1692,8 +1734,8 @@ function CookieConsentBanner({
|
|
|
1692
1734
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-banner-card", children: [
|
|
1693
1735
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-banner-content", children: [
|
|
1694
1736
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sm-cookie-banner-title", children: title }),
|
|
1695
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-cookie-banner-text", children:
|
|
1696
|
-
|
|
1737
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-cookie-banner-text", children: text2 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1738
|
+
text2,
|
|
1697
1739
|
" ",
|
|
1698
1740
|
/* @__PURE__ */ jsxRuntime.jsx("a", { href: settingsUrl, className: "sm-cookie-banner-link", children: "Cookie Settings" })
|
|
1699
1741
|
] }) : defaultText })
|
|
@@ -2398,12 +2440,12 @@ function readerSources(input) {
|
|
|
2398
2440
|
const url = new URL(value.url);
|
|
2399
2441
|
if (url.protocol !== "https:" || url.username || url.password || seen.has(url.href)) continue;
|
|
2400
2442
|
seen.add(url.href);
|
|
2401
|
-
const
|
|
2443
|
+
const text2 = (raw, limit) => {
|
|
2402
2444
|
if (typeof raw !== "string") return void 0;
|
|
2403
2445
|
const trimmed = raw.trim();
|
|
2404
2446
|
return trimmed ? Array.from(trimmed).slice(0, limit).join("") : void 0;
|
|
2405
2447
|
};
|
|
2406
|
-
output.push({ url: url.href, title:
|
|
2448
|
+
output.push({ url: url.href, title: text2(value.title, 500), publisher: text2(value.publisher, 255) });
|
|
2407
2449
|
if (output.length === 5) break;
|
|
2408
2450
|
} catch {
|
|
2409
2451
|
continue;
|
|
@@ -3254,23 +3296,23 @@ function normalizeUuid(id) {
|
|
|
3254
3296
|
}
|
|
3255
3297
|
return id.trim().toLowerCase();
|
|
3256
3298
|
}
|
|
3257
|
-
function escapeHtml(
|
|
3258
|
-
return
|
|
3299
|
+
function escapeHtml(text2) {
|
|
3300
|
+
return text2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3259
3301
|
}
|
|
3260
3302
|
function sanitizeInlineMarkdown(raw) {
|
|
3261
|
-
let
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3303
|
+
let text2 = raw.trim();
|
|
3304
|
+
text2 = text2.replace(/on\w+\s*=\s*(?:["'][^"']*["']|[^\s>]+)/gi, "");
|
|
3305
|
+
text2 = text2.replace(/javascript\s*:/gi, "");
|
|
3306
|
+
text2 = escapeHtml(text2);
|
|
3307
|
+
text2 = text2.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
3308
|
+
text2 = text2.replace(/__(.+?)__/g, "<strong>$1</strong>");
|
|
3309
|
+
text2 = text2.replace(/\*([^*]+?)\*/g, "<em>$1</em>");
|
|
3310
|
+
text2 = text2.replace(/_([^_]+?)_/g, "<em>$1</em>");
|
|
3311
|
+
text2 = text2.replace(/`([^`]+?)`/g, "<code>$1</code>");
|
|
3312
|
+
text2 = text2.replace(/\[([^\]]+)\]\(((?:https?:\/\/|mailto:)[^)]+)\)/g, (_m, label, url) => {
|
|
3271
3313
|
return `<a href="${escapeHtml(url)}" rel="noopener noreferrer nofollow">${label}</a>`;
|
|
3272
3314
|
});
|
|
3273
|
-
return
|
|
3315
|
+
return text2;
|
|
3274
3316
|
}
|
|
3275
3317
|
function parseDocumentBlocks(doc) {
|
|
3276
3318
|
const mediaList = doc.media || [];
|
|
@@ -3563,7 +3605,7 @@ function measurementConsent(headers, sponsorship = false, now = Date.now()) {
|
|
|
3563
3605
|
}
|
|
3564
3606
|
|
|
3565
3607
|
// src/sponsorship-report.ts
|
|
3566
|
-
var escape = (
|
|
3608
|
+
var escape = (text2) => String(text2 ?? "").replace(
|
|
3567
3609
|
/[&<>"']/g,
|
|
3568
3610
|
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]
|
|
3569
3611
|
);
|
|
@@ -3934,6 +3976,9 @@ exports.EDITORIAL_PRINCIPLES = EDITORIAL_PRINCIPLES;
|
|
|
3934
3976
|
exports.EDITORIAL_SECTION_ORDER = EDITORIAL_SECTION_ORDER;
|
|
3935
3977
|
exports.EVENTS = EVENTS;
|
|
3936
3978
|
exports.FEATURE_NAMES = FEATURE_NAMES;
|
|
3979
|
+
exports.FLY_TITLE_MAX_CHARS = FLY_TITLE_MAX_CHARS;
|
|
3980
|
+
exports.HEADLINE_MAX_CHARS = HEADLINE_MAX_CHARS;
|
|
3981
|
+
exports.HEADLINE_RECOMMENDED_CHARS = HEADLINE_RECOMMENDED_CHARS;
|
|
3937
3982
|
exports.JOB_EMPLOYMENT_TYPES = JOB_EMPLOYMENT_TYPES;
|
|
3938
3983
|
exports.LAYOUT_COOKIE = LAYOUT_COOKIE;
|
|
3939
3984
|
exports.LOCAL_BRIEFING_POLICY_VERSION = LOCAL_BRIEFING_POLICY_VERSION;
|
|
@@ -3947,6 +3992,8 @@ exports.NetworkCopyrightNotice = NetworkCopyrightNotice;
|
|
|
3947
3992
|
exports.NewsError = NewsError;
|
|
3948
3993
|
exports.READER_SSO_EVENTS = READER_SSO_EVENTS;
|
|
3949
3994
|
exports.READER_SSO_PROPERTY_KEYS = READER_SSO_PROPERTY_KEYS;
|
|
3995
|
+
exports.RUBRIC_MAX_CHARS = RUBRIC_MAX_CHARS;
|
|
3996
|
+
exports.SEO_TITLE_MAX_CHARS = SEO_TITLE_MAX_CHARS;
|
|
3950
3997
|
exports.SESSION_IDLE_MS = SESSION_IDLE_MS;
|
|
3951
3998
|
exports.SPONSORSHIP_OFFERINGS = SPONSORSHIP_OFFERINGS;
|
|
3952
3999
|
exports.SPONSORSHIP_POLICY = SPONSORSHIP_POLICY;
|
|
@@ -4008,6 +4055,9 @@ exports.getNetworkPublicationUrl = getNetworkPublicationUrl;
|
|
|
4008
4055
|
exports.getNetworkReaderHandoffUrl = getNetworkReaderHandoffUrl;
|
|
4009
4056
|
exports.getStoredTheme = getStoredTheme;
|
|
4010
4057
|
exports.getSystemTheme = getSystemTheme;
|
|
4058
|
+
exports.headlineLength = headlineLength;
|
|
4059
|
+
exports.headlineProblems = headlineProblems;
|
|
4060
|
+
exports.headlineSet = headlineSet;
|
|
4011
4061
|
exports.inventoryCacheOptions = inventoryCacheOptions;
|
|
4012
4062
|
exports.isAdult = isAdult;
|
|
4013
4063
|
exports.isClassifiedCategory = isClassifiedCategory;
|
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-aYDyFCGs.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-aYDyFCGs.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-C3kD6oFZ.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';
|
|
@@ -249,6 +249,7 @@ declare function composeEdition(items: NewsItem[], selection?: HomepageSelection
|
|
|
249
249
|
pendingImages: NewsItem[];
|
|
250
250
|
navigation: EditorialSection[];
|
|
251
251
|
};
|
|
252
|
+
/** The paper's override, else the article's short headline, else its long title (see `headlineSet`). */
|
|
252
253
|
declare function displayHeadline(article: NewsItem, selection?: HomepageSelection): string;
|
|
253
254
|
/** A complete existing sentence, not a cut-off or generated summary. */
|
|
254
255
|
declare function briefSummary(article: NewsItem, selection?: HomepageSelection): string;
|
|
@@ -404,11 +405,12 @@ declare function newsArticleJsonLd(publication: NewsPublication, article: NewsAr
|
|
|
404
405
|
mainEntityOfPage: string;
|
|
405
406
|
url: string;
|
|
406
407
|
dateModified?: string | undefined;
|
|
408
|
+
description: string | undefined;
|
|
409
|
+
datePublished: string;
|
|
410
|
+
alternativeHeadline?: string | undefined;
|
|
407
411
|
"@context": string;
|
|
408
412
|
"@type": string;
|
|
409
413
|
headline: string;
|
|
410
|
-
description: string | undefined;
|
|
411
|
-
datePublished: string;
|
|
412
414
|
};
|
|
413
415
|
declare function publicationJsonLd(publication: NewsPublication, canonical: string): {
|
|
414
416
|
"@context": string;
|
|
@@ -453,6 +455,48 @@ declare function articleMeta(publication: NewsPublication, article: NewsArticle,
|
|
|
453
455
|
/** One line a paper can serve at /llms.txt. Towns and the article path stay explicit. */
|
|
454
456
|
declare function llmsText(publication: NewsPublication, canonical: string): string;
|
|
455
457
|
|
|
458
|
+
/**
|
|
459
|
+
* The newspaper headline set. Every article may carry four pieces of title text, each with a job:
|
|
460
|
+
*
|
|
461
|
+
* - `fly_title` the kicker above the headline: a beat, column or section label (<= 40 characters);
|
|
462
|
+
* - `headline` the short display headline, written as a line, not a summary (recommended <= 70,
|
|
463
|
+
* hard limit 120); front pages and the article h1 show it;
|
|
464
|
+
* - `subtitle` the rubric (deck): one sentence that carries the facts;
|
|
465
|
+
* - `seo_title` the search title for <title> and link previews.
|
|
466
|
+
*
|
|
467
|
+
* `title` is the long descriptive title that every article already has; it is the fallback for the
|
|
468
|
+
* headline and the search title, so nothing changes for an article that carries only a title.
|
|
469
|
+
* A per-paper homepage override (`StoryDisplay.display_title`/`deck`) still wins where an editor set one.
|
|
470
|
+
*/
|
|
471
|
+
declare const HEADLINE_RECOMMENDED_CHARS = 70;
|
|
472
|
+
declare const HEADLINE_MAX_CHARS = 120;
|
|
473
|
+
declare const FLY_TITLE_MAX_CHARS = 40;
|
|
474
|
+
declare const RUBRIC_MAX_CHARS = 500;
|
|
475
|
+
declare const SEO_TITLE_MAX_CHARS = 200;
|
|
476
|
+
type Headlined = Pick<NewsItem, "slug" | "title" | "section"> & Partial<Pick<NewsItem, "headline" | "fly_title" | "excerpt">> & Partial<Pick<NewsArticle, "subtitle" | "seo_title">>;
|
|
477
|
+
/** Characters, never bytes: Cyrillic, CJK and emoji count once each. */
|
|
478
|
+
declare const headlineLength: (value: string) => number;
|
|
479
|
+
type HeadlineSet = {
|
|
480
|
+
/** Kicker: the editor's fly title, else the section. */
|
|
481
|
+
flyTitle?: string;
|
|
482
|
+
/** Display headline: the paper's override, else the article's headline, else its title. */
|
|
483
|
+
headline: string;
|
|
484
|
+
/** Rubric: the paper's deck override, else the subtitle. Never a cut sentence. */
|
|
485
|
+
rubric?: string;
|
|
486
|
+
/** Search title for <title> and previews. */
|
|
487
|
+
seoTitle: string;
|
|
488
|
+
/** The long descriptive title, for alternativeHeadline when it differs from the headline. */
|
|
489
|
+
title: string;
|
|
490
|
+
};
|
|
491
|
+
declare function headlineSet(article: Headlined, selection?: HomepageSelection): HeadlineSet;
|
|
492
|
+
/** Validation shared by the edit command and the editors: hard limits match the service. */
|
|
493
|
+
declare function headlineProblems(fields: {
|
|
494
|
+
headline?: unknown;
|
|
495
|
+
fly_title?: unknown;
|
|
496
|
+
subtitle?: unknown;
|
|
497
|
+
seo_title?: unknown;
|
|
498
|
+
}): string[];
|
|
499
|
+
|
|
456
500
|
/** Bounded event contract from Walnut Creek Times. Never accept raw form or chat text. */
|
|
457
501
|
declare const ANALYTICS_VERSION = 1;
|
|
458
502
|
declare const ANALYTICS_COOKIE = "napsite_analytics_v1";
|
|
@@ -675,4 +719,4 @@ declare function isSponsoredStoryItem(item: NewsItem): boolean;
|
|
|
675
719
|
*/
|
|
676
720
|
declare function selectRelevantLocalStories(currentStory: NewsItem, candidates: NewsItem[], publication: Pick<NewsPublication, "regionLabel" | "towns">, limit?: number): NewsItem[];
|
|
677
721
|
|
|
678
|
-
export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ARTICLE_CONVERSION_EXPERIMENT, type ArticleConversionVariant, ArticleProvenance, type Attribution, BROADSHEET_THEME, type BriefingCandidate, type BriefingPreferences, type BriefingReason, type BriefingSection, type BriefingSource, type BriefingVerification, COOKIE_CATEGORIES, CURATED_COMMUNITY_CARDS, type CommunityInvitationCategory, type ContextualCommunityInvitation, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, type FeaturedDestination, HomepageSelection, LAYOUT_COOKIE, LOCAL_BRIEFING_POLICY_VERSION, type ListeningArticle, type LocalBriefing, type LocalBriefingItem, type LocalBriefingOptions, Measurement, NAVIGATION_GROUPS, NETWORK_NAVIGATION, NEWS_CACHE, type NavigationConfig, type NavigationContext, type NavigationDestination, type NavigationGroup, type NavigationGroupLinks, type NavigationKind, type NavigationLink, type NavigationOverride, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, type ResolvedNavigation, SESSION_IDLE_MS, type SponsorshipReport, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyNavigationConfig, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, classifyStoryInvitationCategory, clearTheme, composeEdition, composeLocalBriefing, contextualCommunityInvitation, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, isSponsoredStoryItem, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveArticleConversionVariant, resolveCoupon, resolveEffectiveTheme, resolveLayout, resolveNavigation, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, selectRelevantLocalStories, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
|
|
722
|
+
export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ARTICLE_CONVERSION_EXPERIMENT, type ArticleConversionVariant, ArticleProvenance, type Attribution, BROADSHEET_THEME, type BriefingCandidate, type BriefingPreferences, type BriefingReason, type BriefingSection, type BriefingSource, type BriefingVerification, COOKIE_CATEGORIES, CURATED_COMMUNITY_CARDS, type CommunityInvitationCategory, type ContextualCommunityInvitation, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FLY_TITLE_MAX_CHARS, FeatureName, type FeaturedDestination, HEADLINE_MAX_CHARS, HEADLINE_RECOMMENDED_CHARS, type HeadlineSet, HomepageSelection, LAYOUT_COOKIE, LOCAL_BRIEFING_POLICY_VERSION, type ListeningArticle, type LocalBriefing, type LocalBriefingItem, type LocalBriefingOptions, Measurement, NAVIGATION_GROUPS, NETWORK_NAVIGATION, NEWS_CACHE, type NavigationConfig, type NavigationContext, type NavigationDestination, type NavigationGroup, type NavigationGroupLinks, type NavigationKind, type NavigationLink, type NavigationOverride, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, RUBRIC_MAX_CHARS, type ResolvedNavigation, SEO_TITLE_MAX_CHARS, SESSION_IDLE_MS, type SponsorshipReport, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyNavigationConfig, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, classifyStoryInvitationCategory, clearTheme, composeEdition, composeLocalBriefing, contextualCommunityInvitation, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, headlineLength, headlineProblems, headlineSet, inventoryCacheOptions, isSponsoredStoryItem, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveArticleConversionVariant, resolveCoupon, resolveEffectiveTheme, resolveLayout, resolveNavigation, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, selectRelevantLocalStories, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
|
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-aYDyFCGs.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-aYDyFCGs.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-CEtTFD_h.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';
|
|
@@ -249,6 +249,7 @@ declare function composeEdition(items: NewsItem[], selection?: HomepageSelection
|
|
|
249
249
|
pendingImages: NewsItem[];
|
|
250
250
|
navigation: EditorialSection[];
|
|
251
251
|
};
|
|
252
|
+
/** The paper's override, else the article's short headline, else its long title (see `headlineSet`). */
|
|
252
253
|
declare function displayHeadline(article: NewsItem, selection?: HomepageSelection): string;
|
|
253
254
|
/** A complete existing sentence, not a cut-off or generated summary. */
|
|
254
255
|
declare function briefSummary(article: NewsItem, selection?: HomepageSelection): string;
|
|
@@ -404,11 +405,12 @@ declare function newsArticleJsonLd(publication: NewsPublication, article: NewsAr
|
|
|
404
405
|
mainEntityOfPage: string;
|
|
405
406
|
url: string;
|
|
406
407
|
dateModified?: string | undefined;
|
|
408
|
+
description: string | undefined;
|
|
409
|
+
datePublished: string;
|
|
410
|
+
alternativeHeadline?: string | undefined;
|
|
407
411
|
"@context": string;
|
|
408
412
|
"@type": string;
|
|
409
413
|
headline: string;
|
|
410
|
-
description: string | undefined;
|
|
411
|
-
datePublished: string;
|
|
412
414
|
};
|
|
413
415
|
declare function publicationJsonLd(publication: NewsPublication, canonical: string): {
|
|
414
416
|
"@context": string;
|
|
@@ -453,6 +455,48 @@ declare function articleMeta(publication: NewsPublication, article: NewsArticle,
|
|
|
453
455
|
/** One line a paper can serve at /llms.txt. Towns and the article path stay explicit. */
|
|
454
456
|
declare function llmsText(publication: NewsPublication, canonical: string): string;
|
|
455
457
|
|
|
458
|
+
/**
|
|
459
|
+
* The newspaper headline set. Every article may carry four pieces of title text, each with a job:
|
|
460
|
+
*
|
|
461
|
+
* - `fly_title` the kicker above the headline: a beat, column or section label (<= 40 characters);
|
|
462
|
+
* - `headline` the short display headline, written as a line, not a summary (recommended <= 70,
|
|
463
|
+
* hard limit 120); front pages and the article h1 show it;
|
|
464
|
+
* - `subtitle` the rubric (deck): one sentence that carries the facts;
|
|
465
|
+
* - `seo_title` the search title for <title> and link previews.
|
|
466
|
+
*
|
|
467
|
+
* `title` is the long descriptive title that every article already has; it is the fallback for the
|
|
468
|
+
* headline and the search title, so nothing changes for an article that carries only a title.
|
|
469
|
+
* A per-paper homepage override (`StoryDisplay.display_title`/`deck`) still wins where an editor set one.
|
|
470
|
+
*/
|
|
471
|
+
declare const HEADLINE_RECOMMENDED_CHARS = 70;
|
|
472
|
+
declare const HEADLINE_MAX_CHARS = 120;
|
|
473
|
+
declare const FLY_TITLE_MAX_CHARS = 40;
|
|
474
|
+
declare const RUBRIC_MAX_CHARS = 500;
|
|
475
|
+
declare const SEO_TITLE_MAX_CHARS = 200;
|
|
476
|
+
type Headlined = Pick<NewsItem, "slug" | "title" | "section"> & Partial<Pick<NewsItem, "headline" | "fly_title" | "excerpt">> & Partial<Pick<NewsArticle, "subtitle" | "seo_title">>;
|
|
477
|
+
/** Characters, never bytes: Cyrillic, CJK and emoji count once each. */
|
|
478
|
+
declare const headlineLength: (value: string) => number;
|
|
479
|
+
type HeadlineSet = {
|
|
480
|
+
/** Kicker: the editor's fly title, else the section. */
|
|
481
|
+
flyTitle?: string;
|
|
482
|
+
/** Display headline: the paper's override, else the article's headline, else its title. */
|
|
483
|
+
headline: string;
|
|
484
|
+
/** Rubric: the paper's deck override, else the subtitle. Never a cut sentence. */
|
|
485
|
+
rubric?: string;
|
|
486
|
+
/** Search title for <title> and previews. */
|
|
487
|
+
seoTitle: string;
|
|
488
|
+
/** The long descriptive title, for alternativeHeadline when it differs from the headline. */
|
|
489
|
+
title: string;
|
|
490
|
+
};
|
|
491
|
+
declare function headlineSet(article: Headlined, selection?: HomepageSelection): HeadlineSet;
|
|
492
|
+
/** Validation shared by the edit command and the editors: hard limits match the service. */
|
|
493
|
+
declare function headlineProblems(fields: {
|
|
494
|
+
headline?: unknown;
|
|
495
|
+
fly_title?: unknown;
|
|
496
|
+
subtitle?: unknown;
|
|
497
|
+
seo_title?: unknown;
|
|
498
|
+
}): string[];
|
|
499
|
+
|
|
456
500
|
/** Bounded event contract from Walnut Creek Times. Never accept raw form or chat text. */
|
|
457
501
|
declare const ANALYTICS_VERSION = 1;
|
|
458
502
|
declare const ANALYTICS_COOKIE = "napsite_analytics_v1";
|
|
@@ -675,4 +719,4 @@ declare function isSponsoredStoryItem(item: NewsItem): boolean;
|
|
|
675
719
|
*/
|
|
676
720
|
declare function selectRelevantLocalStories(currentStory: NewsItem, candidates: NewsItem[], publication: Pick<NewsPublication, "regionLabel" | "towns">, limit?: number): NewsItem[];
|
|
677
721
|
|
|
678
|
-
export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ARTICLE_CONVERSION_EXPERIMENT, type ArticleConversionVariant, ArticleProvenance, type Attribution, BROADSHEET_THEME, type BriefingCandidate, type BriefingPreferences, type BriefingReason, type BriefingSection, type BriefingSource, type BriefingVerification, COOKIE_CATEGORIES, CURATED_COMMUNITY_CARDS, type CommunityInvitationCategory, type ContextualCommunityInvitation, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, type FeaturedDestination, HomepageSelection, LAYOUT_COOKIE, LOCAL_BRIEFING_POLICY_VERSION, type ListeningArticle, type LocalBriefing, type LocalBriefingItem, type LocalBriefingOptions, Measurement, NAVIGATION_GROUPS, NETWORK_NAVIGATION, NEWS_CACHE, type NavigationConfig, type NavigationContext, type NavigationDestination, type NavigationGroup, type NavigationGroupLinks, type NavigationKind, type NavigationLink, type NavigationOverride, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, type ResolvedNavigation, SESSION_IDLE_MS, type SponsorshipReport, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyNavigationConfig, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, classifyStoryInvitationCategory, clearTheme, composeEdition, composeLocalBriefing, contextualCommunityInvitation, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, isSponsoredStoryItem, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveArticleConversionVariant, resolveCoupon, resolveEffectiveTheme, resolveLayout, resolveNavigation, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, selectRelevantLocalStories, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
|
|
722
|
+
export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ARTICLE_CONVERSION_EXPERIMENT, type ArticleConversionVariant, ArticleProvenance, type Attribution, BROADSHEET_THEME, type BriefingCandidate, type BriefingPreferences, type BriefingReason, type BriefingSection, type BriefingSource, type BriefingVerification, COOKIE_CATEGORIES, CURATED_COMMUNITY_CARDS, type CommunityInvitationCategory, type ContextualCommunityInvitation, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FLY_TITLE_MAX_CHARS, FeatureName, type FeaturedDestination, HEADLINE_MAX_CHARS, HEADLINE_RECOMMENDED_CHARS, type HeadlineSet, HomepageSelection, LAYOUT_COOKIE, LOCAL_BRIEFING_POLICY_VERSION, type ListeningArticle, type LocalBriefing, type LocalBriefingItem, type LocalBriefingOptions, Measurement, NAVIGATION_GROUPS, NETWORK_NAVIGATION, NEWS_CACHE, type NavigationConfig, type NavigationContext, type NavigationDestination, type NavigationGroup, type NavigationGroupLinks, type NavigationKind, type NavigationLink, type NavigationOverride, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, RUBRIC_MAX_CHARS, type ResolvedNavigation, SEO_TITLE_MAX_CHARS, SESSION_IDLE_MS, type SponsorshipReport, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyNavigationConfig, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, classifyStoryInvitationCategory, clearTheme, composeEdition, composeLocalBriefing, contextualCommunityInvitation, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, headlineLength, headlineProblems, headlineSet, inventoryCacheOptions, isSponsoredStoryItem, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveArticleConversionVariant, resolveCoupon, resolveEffectiveTheme, resolveLayout, resolveNavigation, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, selectRelevantLocalStories, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { CookieSettingsView } from './chunk-PBRDIXQI.js';
|
|
|
8
8
|
import { localPublications } from './chunk-NNEY6VBX.js';
|
|
9
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, getNetworkReaderHandoffUrl, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub, topicalPublications } from './chunk-NNEY6VBX.js';
|
|
10
10
|
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';
|
|
11
|
+
export { FLY_TITLE_MAX_CHARS, HEADLINE_MAX_CHARS, HEADLINE_RECOMMENDED_CHARS, RUBRIC_MAX_CHARS, SEO_TITLE_MAX_CHARS, headlineLength, headlineProblems, headlineSet } from './chunk-SNQKSUWB.js';
|
|
11
12
|
export { communityMapEmbeds } from './chunk-IMYBSG7J.js';
|
|
12
13
|
export { articleFormatLabel, articleHeroRatio, articlePresentation, formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown } from './chunk-DN3TBZLK.js';
|
|
13
14
|
import { definePublication } from './chunk-57YVL6BD.js';
|
|
@@ -178,7 +179,7 @@ function composeEdition(items, selection = {}, now = Date.now()) {
|
|
|
178
179
|
return { lead, secondary, briefs, compact, sections: sections2, pendingImages, navigation: editorialSections(inventory) };
|
|
179
180
|
}
|
|
180
181
|
function displayHeadline(article, selection = {}) {
|
|
181
|
-
return selection.stories?.[article.slug]?.display_title?.trim() || article.title;
|
|
182
|
+
return selection.stories?.[article.slug]?.display_title?.trim() || article.headline?.trim() || article.title;
|
|
182
183
|
}
|
|
183
184
|
function briefSummary(article, selection = {}) {
|
|
184
185
|
const chosen = selection.stories?.[article.slug]?.brief?.trim();
|
|
@@ -266,8 +267,9 @@ function newsArticleJsonLd(publication, article, canonical, authorName) {
|
|
|
266
267
|
return {
|
|
267
268
|
"@context": "https://schema.org",
|
|
268
269
|
"@type": "NewsArticle",
|
|
269
|
-
headline: article.title,
|
|
270
|
-
|
|
270
|
+
headline: article.headline?.trim() || article.title,
|
|
271
|
+
...article.headline?.trim() && article.headline.trim() !== article.title ? { alternativeHeadline: article.title } : {},
|
|
272
|
+
description: article.subtitle ?? article.excerpt ?? void 0,
|
|
271
273
|
datePublished: article.published_at,
|
|
272
274
|
...article.metadata?.editorial?.modified_at ? { dateModified: article.metadata.editorial.modified_at } : {},
|
|
273
275
|
// updated_at also changes for media/operational maintenance. A trusted
|
|
@@ -314,15 +316,15 @@ function articleMeta(publication, article, canonical, indexable) {
|
|
|
314
316
|
const canonicalUrl = publicHttps(article.canonical_url) ?? url;
|
|
315
317
|
const image = publicHttps(article.og_image_url) ?? publicHttps(article.cover_image_url);
|
|
316
318
|
return {
|
|
317
|
-
title: article.title,
|
|
318
|
-
description: article.seo_description ?? article.excerpt ?? publication.description,
|
|
319
|
+
title: article.seo_title?.trim() || article.title,
|
|
320
|
+
description: article.seo_description ?? article.subtitle ?? article.excerpt ?? publication.description,
|
|
319
321
|
canonical: canonicalUrl,
|
|
320
322
|
indexable: indexable && !article.no_index,
|
|
321
323
|
openGraph: {
|
|
322
324
|
type: "article",
|
|
323
325
|
siteName: publication.name,
|
|
324
|
-
title: article.title,
|
|
325
|
-
description: article.excerpt ?? void 0,
|
|
326
|
+
title: article.headline?.trim() || article.title,
|
|
327
|
+
description: article.subtitle ?? article.excerpt ?? void 0,
|
|
326
328
|
url,
|
|
327
329
|
publishedTime: article.published_at,
|
|
328
330
|
images: image ? [image] : []
|
package/dist/snapshot-file.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-
|
|
2
|
-
import './types-
|
|
1
|
+
import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-C3kD6oFZ.cjs';
|
|
2
|
+
import './types-aYDyFCGs.cjs';
|
|
3
3
|
|
|
4
4
|
declare function readSnapshotFile(file: string): Promise<PublicationSnapshot | null>;
|
|
5
5
|
/**
|
package/dist/snapshot-file.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-
|
|
2
|
-
import './types-
|
|
1
|
+
import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-CEtTFD_h.js';
|
|
2
|
+
import './types-aYDyFCGs.js';
|
|
3
3
|
|
|
4
4
|
declare function readSnapshotFile(file: string): Promise<PublicationSnapshot | null>;
|
|
5
5
|
/**
|
|
@@ -97,7 +97,12 @@ type CategoryRef = {
|
|
|
97
97
|
type NewsItem = {
|
|
98
98
|
id: string;
|
|
99
99
|
slug: string;
|
|
100
|
+
/** The long descriptive title; the fallback for `headline` and `seo_title`. */
|
|
100
101
|
title: string;
|
|
102
|
+
/** Short display headline for front pages and the article h1 (see `headlineSet`). */
|
|
103
|
+
headline?: string | null;
|
|
104
|
+
/** Kicker above the headline; the section is the fallback. */
|
|
105
|
+
fly_title?: string | null;
|
|
101
106
|
excerpt: string | null;
|
|
102
107
|
cover_image_url: string | null;
|
|
103
108
|
/** Versioned canonical pixel identity supplied by the publication owner. */
|
|
@@ -222,11 +227,13 @@ type NewsArticle = NewsItem & {
|
|
|
222
227
|
cards?: VerifiedCard[] | unknown[];
|
|
223
228
|
};
|
|
224
229
|
};
|
|
230
|
+
/** A paper's own homepage overrides for one story; `fly_title` here overrides the article's kicker on that paper only. */
|
|
225
231
|
type StoryDisplay = {
|
|
226
232
|
display_title?: string;
|
|
227
233
|
deck?: string;
|
|
228
234
|
brief?: string;
|
|
229
235
|
towns?: string[];
|
|
236
|
+
fly_title?: string;
|
|
230
237
|
};
|
|
231
238
|
type NearbyUpdate = {
|
|
232
239
|
slug: string;
|
|
@@ -97,7 +97,12 @@ type CategoryRef = {
|
|
|
97
97
|
type NewsItem = {
|
|
98
98
|
id: string;
|
|
99
99
|
slug: string;
|
|
100
|
+
/** The long descriptive title; the fallback for `headline` and `seo_title`. */
|
|
100
101
|
title: string;
|
|
102
|
+
/** Short display headline for front pages and the article h1 (see `headlineSet`). */
|
|
103
|
+
headline?: string | null;
|
|
104
|
+
/** Kicker above the headline; the section is the fallback. */
|
|
105
|
+
fly_title?: string | null;
|
|
101
106
|
excerpt: string | null;
|
|
102
107
|
cover_image_url: string | null;
|
|
103
108
|
/** Versioned canonical pixel identity supplied by the publication owner. */
|
|
@@ -222,11 +227,13 @@ type NewsArticle = NewsItem & {
|
|
|
222
227
|
cards?: VerifiedCard[] | unknown[];
|
|
223
228
|
};
|
|
224
229
|
};
|
|
230
|
+
/** A paper's own homepage overrides for one story; `fly_title` here overrides the article's kicker on that paper only. */
|
|
225
231
|
type StoryDisplay = {
|
|
226
232
|
display_title?: string;
|
|
227
233
|
deck?: string;
|
|
228
234
|
brief?: string;
|
|
229
235
|
towns?: string[];
|
|
236
|
+
fly_title?: string;
|
|
230
237
|
};
|
|
231
238
|
type NearbyUpdate = {
|
|
232
239
|
slug: string;
|
package/dist/verified-card.d.cts
CHANGED
package/dist/verified-card.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/news-publication",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.93",
|
|
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": {
|