@scalemule/news-publication 0.0.92 → 0.0.94
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 +42 -1
- package/dist/article-workflow-client.d.cts +11 -1
- package/dist/article-workflow-client.d.ts +11 -1
- package/dist/article-workflow-client.js +16 -1
- package/dist/chunk-7SAFGLPH.js +47 -0
- package/dist/index.cjs +105 -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,34 @@ 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 (!value.trim()) {
|
|
358
|
+
problems.push(`${name} is blank; omit it to fall back to the title`);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (headlineLength(value.trim()) > max) problems.push(`${name} exceeds ${max} characters`);
|
|
362
|
+
if (/[\p{Cc}]/u.test(value)) problems.push(`${name} contains control characters`);
|
|
363
|
+
};
|
|
364
|
+
check("headline", fields.headline, HEADLINE_MAX_CHARS);
|
|
365
|
+
check("fly_title", fields.fly_title, FLY_TITLE_MAX_CHARS);
|
|
366
|
+
check("subtitle", fields.subtitle, RUBRIC_MAX_CHARS);
|
|
367
|
+
check("seo_title", fields.seo_title, SEO_TITLE_MAX_CHARS);
|
|
368
|
+
return problems;
|
|
369
|
+
}
|
|
370
|
+
|
|
342
371
|
// src/article-edit-operation.ts
|
|
343
372
|
var ArticleEditOperationError = class extends Error {
|
|
344
373
|
constructor(code, stage, message, status, revisionId) {
|
|
@@ -378,7 +407,10 @@ var digest2 = async (v) => [
|
|
|
378
407
|
].map((v2) => v2.toString(16).padStart(2, "0")).join("");
|
|
379
408
|
var patchKeys = /* @__PURE__ */ new Set([
|
|
380
409
|
"title",
|
|
410
|
+
"headline",
|
|
411
|
+
"fly_title",
|
|
381
412
|
"subtitle",
|
|
413
|
+
"seo_title",
|
|
382
414
|
"content_markdown",
|
|
383
415
|
"excerpt",
|
|
384
416
|
"section",
|
|
@@ -407,6 +439,8 @@ function validate(input) {
|
|
|
407
439
|
} else if (typeof value !== "string")
|
|
408
440
|
throw Error(key + " must be a string");
|
|
409
441
|
}
|
|
442
|
+
const problems = headlineProblems(input.patch);
|
|
443
|
+
if (problems.length) throw Error(problems.join("; "));
|
|
410
444
|
if (input.expectedGeneration !== void 0 && (!Number.isSafeInteger(input.expectedGeneration) || input.expectedGeneration < 1))
|
|
411
445
|
throw Error("Expected generation must be positive");
|
|
412
446
|
if (input.enableReviewPolicy && ![
|
|
@@ -477,6 +511,13 @@ async function runArticleEdit(t, input) {
|
|
|
477
511
|
throw error;
|
|
478
512
|
})
|
|
479
513
|
]);
|
|
514
|
+
const headlineKeys = ["headline", "fly_title", "seo_title"].filter((key) => key in input.patch);
|
|
515
|
+
if (headlineKeys.length && options.capabilities?.headline_set !== true)
|
|
516
|
+
throw new ArticleEditOperationError(
|
|
517
|
+
"CAPABILITY_UNAVAILABLE",
|
|
518
|
+
"preflight",
|
|
519
|
+
`The server must expose headline_set before editing ${headlineKeys.join(", ")}; no article was changed`
|
|
520
|
+
);
|
|
480
521
|
if (options.workflow_version !== 1 || options.capabilities?.versioned_editorial_fields !== true)
|
|
481
522
|
throw new ArticleEditOperationError(
|
|
482
523
|
"CAPABILITY_UNAVAILABLE",
|
|
@@ -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[];
|
|
@@ -200,6 +208,8 @@ type ArticleWorkflowOptions = {
|
|
|
200
208
|
prioritized_narration_processing?: boolean;
|
|
201
209
|
content_first_editor_document?: boolean;
|
|
202
210
|
versioned_editorial_fields?: boolean;
|
|
211
|
+
/** The server stores headline, fly_title and seo_title on the versioned document. */
|
|
212
|
+
headline_set?: boolean;
|
|
203
213
|
versioned_editorial_tags?: boolean;
|
|
204
214
|
managed_submission_version?: number | null;
|
|
205
215
|
multipart_submission?: boolean;
|
|
@@ -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[];
|
|
@@ -200,6 +208,8 @@ type ArticleWorkflowOptions = {
|
|
|
200
208
|
prioritized_narration_processing?: boolean;
|
|
201
209
|
content_first_editor_document?: boolean;
|
|
202
210
|
versioned_editorial_fields?: boolean;
|
|
211
|
+
/** The server stores headline, fly_title and seo_title on the versioned document. */
|
|
212
|
+
headline_set?: boolean;
|
|
203
213
|
versioned_editorial_tags?: boolean;
|
|
204
214
|
managed_submission_version?: number | null;
|
|
205
215
|
multipart_submission?: boolean;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { headlineProblems } from './chunk-7SAFGLPH.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 && ![
|
|
@@ -475,6 +483,13 @@ async function runArticleEdit(t, input) {
|
|
|
475
483
|
throw error;
|
|
476
484
|
})
|
|
477
485
|
]);
|
|
486
|
+
const headlineKeys = ["headline", "fly_title", "seo_title"].filter((key) => key in input.patch);
|
|
487
|
+
if (headlineKeys.length && options.capabilities?.headline_set !== true)
|
|
488
|
+
throw new ArticleEditOperationError(
|
|
489
|
+
"CAPABILITY_UNAVAILABLE",
|
|
490
|
+
"preflight",
|
|
491
|
+
`The server must expose headline_set before editing ${headlineKeys.join(", ")}; no article was changed`
|
|
492
|
+
);
|
|
478
493
|
if (options.workflow_version !== 1 || options.capabilities?.versioned_editorial_fields !== true)
|
|
479
494
|
throw new ArticleEditOperationError(
|
|
480
495
|
"CAPABILITY_UNAVAILABLE",
|
|
@@ -0,0 +1,47 @@
|
|
|
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(display?.fly_title) ?? 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 (!value.trim()) {
|
|
34
|
+
problems.push(`${name} is blank; omit it to fall back to the title`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (headlineLength(value.trim()) > max) problems.push(`${name} exceeds ${max} characters`);
|
|
38
|
+
if (/[\p{Cc}]/u.test(value)) problems.push(`${name} contains control characters`);
|
|
39
|
+
};
|
|
40
|
+
check("headline", fields.headline, HEADLINE_MAX_CHARS);
|
|
41
|
+
check("fly_title", fields.fly_title, FLY_TITLE_MAX_CHARS);
|
|
42
|
+
check("subtitle", fields.subtitle, RUBRIC_MAX_CHARS);
|
|
43
|
+
check("seo_title", fields.seo_title, SEO_TITLE_MAX_CHARS);
|
|
44
|
+
return problems;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
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,52 @@ 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(display?.fly_title) ?? 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 (!value.trim()) {
|
|
844
|
+
problems.push(`${name} is blank; omit it to fall back to the title`);
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (headlineLength(value.trim()) > max) problems.push(`${name} exceeds ${max} characters`);
|
|
848
|
+
if (/[\p{Cc}]/u.test(value)) problems.push(`${name} contains control characters`);
|
|
849
|
+
};
|
|
850
|
+
check("headline", fields.headline, HEADLINE_MAX_CHARS);
|
|
851
|
+
check("fly_title", fields.fly_title, FLY_TITLE_MAX_CHARS);
|
|
852
|
+
check("subtitle", fields.subtitle, RUBRIC_MAX_CHARS);
|
|
853
|
+
check("seo_title", fields.seo_title, SEO_TITLE_MAX_CHARS);
|
|
854
|
+
return problems;
|
|
855
|
+
}
|
|
856
|
+
|
|
810
857
|
// src/analytics.ts
|
|
811
858
|
var ANALYTICS_VERSION = 1;
|
|
812
859
|
var ANALYTICS_COOKIE = "napsite_analytics_v1";
|
|
@@ -869,9 +916,9 @@ var PUBLIC_PATHS = /* @__PURE__ */ new Set([
|
|
|
869
916
|
]);
|
|
870
917
|
function shortText(value, max = 100) {
|
|
871
918
|
if (typeof value !== "string") return void 0;
|
|
872
|
-
const
|
|
873
|
-
if (!
|
|
874
|
-
return Array.from(
|
|
919
|
+
const text2 = value.trim();
|
|
920
|
+
if (!text2 || /[@\r\n\u0000-\u001f]/.test(text2)) return void 0;
|
|
921
|
+
return Array.from(text2).slice(0, max).join("");
|
|
875
922
|
}
|
|
876
923
|
function publicUrl(value, originOnly = false) {
|
|
877
924
|
if (typeof value !== "string") return void 0;
|
|
@@ -979,13 +1026,13 @@ function canHostMeetup(accountCreatedAt, now = /* @__PURE__ */ new Date()) {
|
|
|
979
1026
|
return Number.isFinite(created) && now.getTime() - created >= HOST_WAIT_MS;
|
|
980
1027
|
}
|
|
981
1028
|
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 =
|
|
1029
|
+
const text2 = Array.from(value.trim()).slice(0, 1e3).join("");
|
|
1030
|
+
if (Array.from(text2).length < 1) return { ok: false, reason: "Write a short message." };
|
|
1031
|
+
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." };
|
|
1032
|
+
if (/(https?:\/\/|www\.)\S+/i.test(text2)) return { ok: false, reason: "Links are not allowed in the chat." };
|
|
1033
|
+
const digits = text2.replace(/\D/g, "");
|
|
987
1034
|
if (digits.length >= 10) return { ok: false, reason: "Share a phone number in person, not in the chat." };
|
|
988
|
-
return { ok: true, text };
|
|
1035
|
+
return { ok: true, text: text2 };
|
|
989
1036
|
}
|
|
990
1037
|
function activityLabel(value, otherLabel = "") {
|
|
991
1038
|
if (value === "other") return Array.from(otherLabel.trim()).slice(0, 40).join("") || "Something else";
|
|
@@ -1088,9 +1135,9 @@ function classifiedPath(id) {
|
|
|
1088
1135
|
return `/v1/listings/${encodeURIComponent(id)}`;
|
|
1089
1136
|
}
|
|
1090
1137
|
function requireText(value, label, max, min = 1) {
|
|
1091
|
-
const
|
|
1092
|
-
if (Array.from(
|
|
1093
|
-
return
|
|
1138
|
+
const text2 = clipChars(typeof value === "string" ? value : "", max);
|
|
1139
|
+
if (Array.from(text2).length < min) throw new Error(`Please complete the ${label}.`);
|
|
1140
|
+
return text2;
|
|
1094
1141
|
}
|
|
1095
1142
|
function prepareJobPost(input, towns) {
|
|
1096
1143
|
const title = requireText(input.title, "job title", 120);
|
|
@@ -1623,7 +1670,7 @@ var COOKIE_CATEGORIES = [
|
|
|
1623
1670
|
];
|
|
1624
1671
|
function CookieConsentBanner({
|
|
1625
1672
|
title = "Your privacy choices",
|
|
1626
|
-
text,
|
|
1673
|
+
text: text2,
|
|
1627
1674
|
settingsUrl = "/cookies",
|
|
1628
1675
|
className = "",
|
|
1629
1676
|
publicationSlug,
|
|
@@ -1692,8 +1739,8 @@ function CookieConsentBanner({
|
|
|
1692
1739
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-banner-card", children: [
|
|
1693
1740
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm-cookie-banner-content", children: [
|
|
1694
1741
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "sm-cookie-banner-title", children: title }),
|
|
1695
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-cookie-banner-text", children:
|
|
1696
|
-
|
|
1742
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "sm-cookie-banner-text", children: text2 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1743
|
+
text2,
|
|
1697
1744
|
" ",
|
|
1698
1745
|
/* @__PURE__ */ jsxRuntime.jsx("a", { href: settingsUrl, className: "sm-cookie-banner-link", children: "Cookie Settings" })
|
|
1699
1746
|
] }) : defaultText })
|
|
@@ -2398,12 +2445,12 @@ function readerSources(input) {
|
|
|
2398
2445
|
const url = new URL(value.url);
|
|
2399
2446
|
if (url.protocol !== "https:" || url.username || url.password || seen.has(url.href)) continue;
|
|
2400
2447
|
seen.add(url.href);
|
|
2401
|
-
const
|
|
2448
|
+
const text2 = (raw, limit) => {
|
|
2402
2449
|
if (typeof raw !== "string") return void 0;
|
|
2403
2450
|
const trimmed = raw.trim();
|
|
2404
2451
|
return trimmed ? Array.from(trimmed).slice(0, limit).join("") : void 0;
|
|
2405
2452
|
};
|
|
2406
|
-
output.push({ url: url.href, title:
|
|
2453
|
+
output.push({ url: url.href, title: text2(value.title, 500), publisher: text2(value.publisher, 255) });
|
|
2407
2454
|
if (output.length === 5) break;
|
|
2408
2455
|
} catch {
|
|
2409
2456
|
continue;
|
|
@@ -3254,23 +3301,23 @@ function normalizeUuid(id) {
|
|
|
3254
3301
|
}
|
|
3255
3302
|
return id.trim().toLowerCase();
|
|
3256
3303
|
}
|
|
3257
|
-
function escapeHtml(
|
|
3258
|
-
return
|
|
3304
|
+
function escapeHtml(text2) {
|
|
3305
|
+
return text2.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3259
3306
|
}
|
|
3260
3307
|
function sanitizeInlineMarkdown(raw) {
|
|
3261
|
-
let
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3308
|
+
let text2 = raw.trim();
|
|
3309
|
+
text2 = text2.replace(/on\w+\s*=\s*(?:["'][^"']*["']|[^\s>]+)/gi, "");
|
|
3310
|
+
text2 = text2.replace(/javascript\s*:/gi, "");
|
|
3311
|
+
text2 = escapeHtml(text2);
|
|
3312
|
+
text2 = text2.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
3313
|
+
text2 = text2.replace(/__(.+?)__/g, "<strong>$1</strong>");
|
|
3314
|
+
text2 = text2.replace(/\*([^*]+?)\*/g, "<em>$1</em>");
|
|
3315
|
+
text2 = text2.replace(/_([^_]+?)_/g, "<em>$1</em>");
|
|
3316
|
+
text2 = text2.replace(/`([^`]+?)`/g, "<code>$1</code>");
|
|
3317
|
+
text2 = text2.replace(/\[([^\]]+)\]\(((?:https?:\/\/|mailto:)[^)]+)\)/g, (_m, label, url) => {
|
|
3271
3318
|
return `<a href="${escapeHtml(url)}" rel="noopener noreferrer nofollow">${label}</a>`;
|
|
3272
3319
|
});
|
|
3273
|
-
return
|
|
3320
|
+
return text2;
|
|
3274
3321
|
}
|
|
3275
3322
|
function parseDocumentBlocks(doc) {
|
|
3276
3323
|
const mediaList = doc.media || [];
|
|
@@ -3563,7 +3610,7 @@ function measurementConsent(headers, sponsorship = false, now = Date.now()) {
|
|
|
3563
3610
|
}
|
|
3564
3611
|
|
|
3565
3612
|
// src/sponsorship-report.ts
|
|
3566
|
-
var escape = (
|
|
3613
|
+
var escape = (text2) => String(text2 ?? "").replace(
|
|
3567
3614
|
/[&<>"']/g,
|
|
3568
3615
|
(c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]
|
|
3569
3616
|
);
|
|
@@ -3934,6 +3981,9 @@ exports.EDITORIAL_PRINCIPLES = EDITORIAL_PRINCIPLES;
|
|
|
3934
3981
|
exports.EDITORIAL_SECTION_ORDER = EDITORIAL_SECTION_ORDER;
|
|
3935
3982
|
exports.EVENTS = EVENTS;
|
|
3936
3983
|
exports.FEATURE_NAMES = FEATURE_NAMES;
|
|
3984
|
+
exports.FLY_TITLE_MAX_CHARS = FLY_TITLE_MAX_CHARS;
|
|
3985
|
+
exports.HEADLINE_MAX_CHARS = HEADLINE_MAX_CHARS;
|
|
3986
|
+
exports.HEADLINE_RECOMMENDED_CHARS = HEADLINE_RECOMMENDED_CHARS;
|
|
3937
3987
|
exports.JOB_EMPLOYMENT_TYPES = JOB_EMPLOYMENT_TYPES;
|
|
3938
3988
|
exports.LAYOUT_COOKIE = LAYOUT_COOKIE;
|
|
3939
3989
|
exports.LOCAL_BRIEFING_POLICY_VERSION = LOCAL_BRIEFING_POLICY_VERSION;
|
|
@@ -3947,6 +3997,8 @@ exports.NetworkCopyrightNotice = NetworkCopyrightNotice;
|
|
|
3947
3997
|
exports.NewsError = NewsError;
|
|
3948
3998
|
exports.READER_SSO_EVENTS = READER_SSO_EVENTS;
|
|
3949
3999
|
exports.READER_SSO_PROPERTY_KEYS = READER_SSO_PROPERTY_KEYS;
|
|
4000
|
+
exports.RUBRIC_MAX_CHARS = RUBRIC_MAX_CHARS;
|
|
4001
|
+
exports.SEO_TITLE_MAX_CHARS = SEO_TITLE_MAX_CHARS;
|
|
3950
4002
|
exports.SESSION_IDLE_MS = SESSION_IDLE_MS;
|
|
3951
4003
|
exports.SPONSORSHIP_OFFERINGS = SPONSORSHIP_OFFERINGS;
|
|
3952
4004
|
exports.SPONSORSHIP_POLICY = SPONSORSHIP_POLICY;
|
|
@@ -4008,6 +4060,9 @@ exports.getNetworkPublicationUrl = getNetworkPublicationUrl;
|
|
|
4008
4060
|
exports.getNetworkReaderHandoffUrl = getNetworkReaderHandoffUrl;
|
|
4009
4061
|
exports.getStoredTheme = getStoredTheme;
|
|
4010
4062
|
exports.getSystemTheme = getSystemTheme;
|
|
4063
|
+
exports.headlineLength = headlineLength;
|
|
4064
|
+
exports.headlineProblems = headlineProblems;
|
|
4065
|
+
exports.headlineSet = headlineSet;
|
|
4011
4066
|
exports.inventoryCacheOptions = inventoryCacheOptions;
|
|
4012
4067
|
exports.isAdult = isAdult;
|
|
4013
4068
|
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-7SAFGLPH.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.94",
|
|
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": {
|