@scalemule/news-publication 0.0.21 → 0.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/chunk-I3ZTQCV3.js +227 -0
- package/dist/index.cjs +226 -0
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/dist/snapshot-C0IGpt-B.d.cts +48 -0
- package/dist/snapshot-CxuyMF39.d.ts +48 -0
- package/dist/snapshot-file.d.cts +2 -1
- package/dist/snapshot-file.d.ts +2 -1
- package/dist/{snapshot-OZFP3-Up.d.cts → types-DFDCZHbS.d.cts} +40 -46
- package/dist/{snapshot-OZFP3-Up.d.ts → types-DFDCZHbS.d.ts} +40 -46
- package/dist/verified-card.cjs +232 -0
- package/dist/verified-card.d.cts +22 -0
- package/dist/verified-card.d.ts +22 -0
- package/dist/verified-card.js +1 -0
- package/package.json +9 -2
- package/tokens/broadsheet.css +210 -0
- package/verified-card.css +208 -0
package/README.md
CHANGED
|
@@ -85,3 +85,18 @@ import { ArticleProvenance } from '@scalemule/news-publication'
|
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
The component inherits the publication's typography. Optional styling hooks are `article-provenance` and `article-provenance__label`. `readerSources()` is available when an existing publication component owns source-list rendering.
|
|
88
|
+
|
|
89
|
+
### Verified Community Cards
|
|
90
|
+
|
|
91
|
+
`VerifiedCardSection` and `VerifiedCardComponent` render broadsheet-styled fact sheets for verified entities (`PLACE`, `ACTIVITY`, `EVENT`, `ORGANIZATION`, `PRODUCT`). Verified cards display confirmed operating hours, venues, contact details, dates, and primary action links (tickets, registration, maps) validated by newsroom staff.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { VerifiedCardSection, getArticleCards } from '@scalemule/news-publication'
|
|
95
|
+
|
|
96
|
+
const cards = getArticleCards(article.metadata?.cards)
|
|
97
|
+
|
|
98
|
+
<VerifiedCardSection cards={cards} />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Styles adhere to the publication theme (`--pub-paper`, `--pub-ink`, `--pub-accent`, `--pub-rule`, `--pub-soft`) and are included in `@scalemule/news-publication/tokens.css` or available standalone via `@scalemule/news-publication/verified-card.css`.
|
|
102
|
+
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
// src/verified-card.tsx
|
|
4
|
+
var ALLOWED_CARD_TYPES = /* @__PURE__ */ new Set([
|
|
5
|
+
"PLACE",
|
|
6
|
+
"ACTIVITY",
|
|
7
|
+
"EVENT",
|
|
8
|
+
"ORGANIZATION",
|
|
9
|
+
"PRODUCT"
|
|
10
|
+
]);
|
|
11
|
+
function clip(val, maxChars) {
|
|
12
|
+
if (typeof val !== "string") return void 0;
|
|
13
|
+
const trimmed = val.trim();
|
|
14
|
+
if (!trimmed) return void 0;
|
|
15
|
+
return Array.from(trimmed).slice(0, maxChars).join("");
|
|
16
|
+
}
|
|
17
|
+
function isSafeActionUrl(raw) {
|
|
18
|
+
if (typeof raw !== "string") return false;
|
|
19
|
+
const trimmed = raw.trim();
|
|
20
|
+
if (!trimmed || trimmed.length > 2048) return false;
|
|
21
|
+
if (trimmed.startsWith("tel:") || trimmed.startsWith("mailto:")) {
|
|
22
|
+
return /^tel:\+?[\d\-().\s]+$/.test(trimmed) || /^mailto:[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(trimmed);
|
|
26
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return false;
|
|
27
|
+
if (url.username || url.password) return false;
|
|
28
|
+
const hostname = url.hostname.toLowerCase();
|
|
29
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".internal") || hostname.endsWith(".local") || hostname === "127.0.0.1" || hostname === "169.254.169.254") {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
for (const [key] of url.searchParams.entries()) {
|
|
33
|
+
const cleanKey = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
34
|
+
if (cleanKey === "token" || cleanKey === "apikey" || cleanKey === "accesstoken" || cleanKey === "secret" || cleanKey === "clientsecret" || cleanKey === "authorization" || cleanKey === "signature" || cleanKey === "password" || cleanKey === "xamzsignature" || cleanKey === "xamzsecuritytoken") {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function getArticleCards(rawCards) {
|
|
44
|
+
if (!Array.isArray(rawCards) || rawCards.length === 0) return [];
|
|
45
|
+
const cards = [];
|
|
46
|
+
for (const item of rawCards) {
|
|
47
|
+
if (!item || typeof item !== "object") continue;
|
|
48
|
+
const c = item;
|
|
49
|
+
const id = clip(c.id, 64);
|
|
50
|
+
const title = clip(c.title, 300);
|
|
51
|
+
if (!id || !title) continue;
|
|
52
|
+
const rawType = typeof c.card_type === "string" ? c.card_type : c.cardType;
|
|
53
|
+
const cardType = ALLOWED_CARD_TYPES.has(rawType) ? rawType : "PLACE";
|
|
54
|
+
const kicker = clip(c.kicker, 120) || (cardType === "PLACE" ? "LOCAL VENUE \xB7 VERIFIED DIRECTORY" : cardType === "EVENT" ? "COMMUNITY EVENT \xB7 VERIFIED FACTSHEET" : cardType === "ACTIVITY" ? "COMMUNITY ACTIVITY \xB7 VERIFIED FACTSHEET" : cardType === "ORGANIZATION" ? "COMMUNITY ORGANIZATION \xB7 DIRECTORY" : "VERIFIED FACTSHEET");
|
|
55
|
+
const subtitle = clip(c.subtitle, 300);
|
|
56
|
+
const summary = clip(c.summary, 1e3);
|
|
57
|
+
const badge = clip(c.badge, 64);
|
|
58
|
+
const facts = [];
|
|
59
|
+
if (Array.isArray(c.facts)) {
|
|
60
|
+
for (const f of c.facts) {
|
|
61
|
+
if (!f || typeof f !== "object") continue;
|
|
62
|
+
const label = clip(f.label, 80);
|
|
63
|
+
const value = clip(f.value, 500);
|
|
64
|
+
if (!label || !value) continue;
|
|
65
|
+
let href = void 0;
|
|
66
|
+
const rawHref = f.href;
|
|
67
|
+
if (typeof rawHref === "string" && isSafeActionUrl(rawHref)) {
|
|
68
|
+
href = rawHref;
|
|
69
|
+
}
|
|
70
|
+
facts.push({ label, value, href });
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
const timeframe = clip(c.timeframe, 200);
|
|
74
|
+
if (timeframe) facts.push({ label: "Timeframe", value: timeframe });
|
|
75
|
+
const address = clip(c.address, 300);
|
|
76
|
+
if (address) {
|
|
77
|
+
facts.push({
|
|
78
|
+
label: "Address",
|
|
79
|
+
value: address,
|
|
80
|
+
href: `https://maps.google.com/?q=${encodeURIComponent(address)}`
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const hours = clip(c.hours, 300);
|
|
84
|
+
if (hours) facts.push({ label: "Hours", value: hours });
|
|
85
|
+
const phone = clip(c.phone, 80);
|
|
86
|
+
if (phone) {
|
|
87
|
+
facts.push({
|
|
88
|
+
label: "Phone",
|
|
89
|
+
value: phone,
|
|
90
|
+
href: `tel:${phone.replace(/[^+\d]/g, "")}`
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const cost = clip(c.cost, 120);
|
|
94
|
+
if (cost) facts.push({ label: "Cost", value: cost });
|
|
95
|
+
}
|
|
96
|
+
const actions = [];
|
|
97
|
+
if (Array.isArray(c.actions)) {
|
|
98
|
+
for (const act of c.actions) {
|
|
99
|
+
if (!act || typeof act !== "object") continue;
|
|
100
|
+
const actObj = act;
|
|
101
|
+
const label = clip(actObj.label, 80);
|
|
102
|
+
const url = typeof actObj.url === "string" ? actObj.url.trim() : "";
|
|
103
|
+
if (!label || !url || !isSafeActionUrl(url)) continue;
|
|
104
|
+
actions.push({
|
|
105
|
+
label,
|
|
106
|
+
url,
|
|
107
|
+
primary: Boolean(actObj.primary)
|
|
108
|
+
});
|
|
109
|
+
if (actions.length >= 5) break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
let image = void 0;
|
|
113
|
+
if (c.image && typeof c.image === "object") {
|
|
114
|
+
const img = c.image;
|
|
115
|
+
const masterUrl = typeof img.master_url === "string" && isSafeActionUrl(img.master_url) ? img.master_url : void 0;
|
|
116
|
+
const url = typeof img.url === "string" && isSafeActionUrl(img.url) ? img.url : masterUrl;
|
|
117
|
+
if (url) {
|
|
118
|
+
image = {
|
|
119
|
+
file_id: clip(img.file_id, 64),
|
|
120
|
+
master_url: masterUrl,
|
|
121
|
+
url,
|
|
122
|
+
width: typeof img.width === "number" ? img.width : void 0,
|
|
123
|
+
height: typeof img.height === "number" ? img.height : void 0,
|
|
124
|
+
alt: clip(img.alt, 500),
|
|
125
|
+
caption: clip(img.caption, 1e3),
|
|
126
|
+
attribution: clip(img.attribution, 500),
|
|
127
|
+
label: clip(img.label, 100),
|
|
128
|
+
ai_generated: Boolean(img.ai_generated)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const verificationNote = clip(c.verification_note || c.verificationNote, 300) || "Verified by publication reporting staff";
|
|
133
|
+
const verifiedAt = clip(c.verified_at || c.verifiedAt, 100) || "October 2026";
|
|
134
|
+
let rating = void 0;
|
|
135
|
+
if (typeof c.rating === "number" || typeof c.rating === "string") {
|
|
136
|
+
rating = c.rating;
|
|
137
|
+
}
|
|
138
|
+
cards.push({
|
|
139
|
+
id,
|
|
140
|
+
cardType,
|
|
141
|
+
kicker,
|
|
142
|
+
title,
|
|
143
|
+
subtitle,
|
|
144
|
+
summary,
|
|
145
|
+
badge,
|
|
146
|
+
facts,
|
|
147
|
+
actions,
|
|
148
|
+
verificationNote,
|
|
149
|
+
verifiedAt,
|
|
150
|
+
image,
|
|
151
|
+
rating
|
|
152
|
+
});
|
|
153
|
+
if (cards.length >= 10) break;
|
|
154
|
+
}
|
|
155
|
+
return cards;
|
|
156
|
+
}
|
|
157
|
+
function VerifiedCardComponent({
|
|
158
|
+
card,
|
|
159
|
+
className
|
|
160
|
+
}) {
|
|
161
|
+
return /* @__PURE__ */ jsxs(
|
|
162
|
+
"aside",
|
|
163
|
+
{
|
|
164
|
+
className: `article-verified-card ${className || ""}`.trim(),
|
|
165
|
+
"aria-label": `Verified Factsheet: ${card.title}`,
|
|
166
|
+
children: [
|
|
167
|
+
/* @__PURE__ */ jsxs("div", { className: "article-verified-card__header", children: [
|
|
168
|
+
/* @__PURE__ */ jsx("span", { className: "article-verified-card__kicker", children: card.kicker }),
|
|
169
|
+
card.badge && /* @__PURE__ */ jsx("span", { className: "article-verified-card__badge", children: card.badge })
|
|
170
|
+
] }),
|
|
171
|
+
/* @__PURE__ */ jsx("h2", { className: "article-verified-card__title", children: card.title }),
|
|
172
|
+
card.subtitle && /* @__PURE__ */ jsx("p", { className: "article-verified-card__subtitle", children: card.subtitle }),
|
|
173
|
+
card.summary && /* @__PURE__ */ jsx("p", { className: "article-verified-card__summary", children: card.summary }),
|
|
174
|
+
card.facts.length > 0 && /* @__PURE__ */ jsx("dl", { className: "article-verified-card__fact-grid", children: card.facts.map((fact, i) => /* @__PURE__ */ jsxs("div", { className: "article-verified-card__fact-row", children: [
|
|
175
|
+
/* @__PURE__ */ jsx("dt", { className: "article-verified-card__fact-label", children: fact.label }),
|
|
176
|
+
/* @__PURE__ */ jsx("dd", { className: "article-verified-card__fact-value", children: fact.href ? /* @__PURE__ */ jsx(
|
|
177
|
+
"a",
|
|
178
|
+
{
|
|
179
|
+
className: "article-verified-card__fact-link",
|
|
180
|
+
href: fact.href,
|
|
181
|
+
rel: "noopener noreferrer",
|
|
182
|
+
target: fact.href.startsWith("http") ? "_blank" : void 0,
|
|
183
|
+
children: fact.value
|
|
184
|
+
}
|
|
185
|
+
) : fact.value })
|
|
186
|
+
] }, i)) }),
|
|
187
|
+
card.actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "article-verified-card__actions", children: card.actions.map((act, i) => /* @__PURE__ */ jsxs(
|
|
188
|
+
"a",
|
|
189
|
+
{
|
|
190
|
+
className: `article-verified-card__action ${act.primary ? "article-verified-card__action--primary" : "article-verified-card__action--secondary"}`,
|
|
191
|
+
href: act.url,
|
|
192
|
+
target: "_blank",
|
|
193
|
+
rel: "noopener noreferrer",
|
|
194
|
+
children: [
|
|
195
|
+
/* @__PURE__ */ jsx("span", { children: act.label }),
|
|
196
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "article-verified-card__action-icon", children: "\u2197" })
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
i
|
|
200
|
+
)) }),
|
|
201
|
+
/* @__PURE__ */ jsxs("footer", { className: "article-verified-card__footer", children: [
|
|
202
|
+
/* @__PURE__ */ jsxs("div", { className: "article-verified-card__seal", children: [
|
|
203
|
+
/* @__PURE__ */ jsx("span", { className: "article-verified-card__seal-check", "aria-hidden": "true", children: "\u2713" }),
|
|
204
|
+
/* @__PURE__ */ jsx("span", { children: card.verificationNote })
|
|
205
|
+
] }),
|
|
206
|
+
/* @__PURE__ */ jsx("span", { className: "article-verified-card__date", children: card.verifiedAt })
|
|
207
|
+
] })
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
function VerifiedCardSection({
|
|
213
|
+
cards,
|
|
214
|
+
className
|
|
215
|
+
}) {
|
|
216
|
+
if (!cards || cards.length === 0) return null;
|
|
217
|
+
return /* @__PURE__ */ jsx(
|
|
218
|
+
"section",
|
|
219
|
+
{
|
|
220
|
+
className: `article-verified-card-section ${className || ""}`.trim(),
|
|
221
|
+
"aria-label": "Verified Community Information",
|
|
222
|
+
children: cards.map((card) => /* @__PURE__ */ jsx(VerifiedCardComponent, { card }, card.id))
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl };
|
package/dist/index.cjs
CHANGED
|
@@ -1886,6 +1886,228 @@ async function publicationListeningCandidates(items, read, publicationId, public
|
|
|
1886
1886
|
if (!output.length && failures) throw new Error("Audio inventory could not be loaded");
|
|
1887
1887
|
return output.sort((a, b) => (b.publishedAt ?? "").localeCompare(a.publishedAt ?? ""));
|
|
1888
1888
|
}
|
|
1889
|
+
var ALLOWED_CARD_TYPES = /* @__PURE__ */ new Set([
|
|
1890
|
+
"PLACE",
|
|
1891
|
+
"ACTIVITY",
|
|
1892
|
+
"EVENT",
|
|
1893
|
+
"ORGANIZATION",
|
|
1894
|
+
"PRODUCT"
|
|
1895
|
+
]);
|
|
1896
|
+
function clip(val, maxChars) {
|
|
1897
|
+
if (typeof val !== "string") return void 0;
|
|
1898
|
+
const trimmed = val.trim();
|
|
1899
|
+
if (!trimmed) return void 0;
|
|
1900
|
+
return Array.from(trimmed).slice(0, maxChars).join("");
|
|
1901
|
+
}
|
|
1902
|
+
function isSafeActionUrl(raw) {
|
|
1903
|
+
if (typeof raw !== "string") return false;
|
|
1904
|
+
const trimmed = raw.trim();
|
|
1905
|
+
if (!trimmed || trimmed.length > 2048) return false;
|
|
1906
|
+
if (trimmed.startsWith("tel:") || trimmed.startsWith("mailto:")) {
|
|
1907
|
+
return /^tel:\+?[\d\-().\s]+$/.test(trimmed) || /^mailto:[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed);
|
|
1908
|
+
}
|
|
1909
|
+
try {
|
|
1910
|
+
const url = new URL(trimmed);
|
|
1911
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return false;
|
|
1912
|
+
if (url.username || url.password) return false;
|
|
1913
|
+
const hostname = url.hostname.toLowerCase();
|
|
1914
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".internal") || hostname.endsWith(".local") || hostname === "127.0.0.1" || hostname === "169.254.169.254") {
|
|
1915
|
+
return false;
|
|
1916
|
+
}
|
|
1917
|
+
for (const [key] of url.searchParams.entries()) {
|
|
1918
|
+
const cleanKey = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1919
|
+
if (cleanKey === "token" || cleanKey === "apikey" || cleanKey === "accesstoken" || cleanKey === "secret" || cleanKey === "clientsecret" || cleanKey === "authorization" || cleanKey === "signature" || cleanKey === "password" || cleanKey === "xamzsignature" || cleanKey === "xamzsecuritytoken") {
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
return true;
|
|
1924
|
+
} catch {
|
|
1925
|
+
return false;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
function getArticleCards(rawCards) {
|
|
1929
|
+
if (!Array.isArray(rawCards) || rawCards.length === 0) return [];
|
|
1930
|
+
const cards = [];
|
|
1931
|
+
for (const item of rawCards) {
|
|
1932
|
+
if (!item || typeof item !== "object") continue;
|
|
1933
|
+
const c = item;
|
|
1934
|
+
const id = clip(c.id, 64);
|
|
1935
|
+
const title = clip(c.title, 300);
|
|
1936
|
+
if (!id || !title) continue;
|
|
1937
|
+
const rawType = typeof c.card_type === "string" ? c.card_type : c.cardType;
|
|
1938
|
+
const cardType = ALLOWED_CARD_TYPES.has(rawType) ? rawType : "PLACE";
|
|
1939
|
+
const kicker = clip(c.kicker, 120) || (cardType === "PLACE" ? "LOCAL VENUE \xB7 VERIFIED DIRECTORY" : cardType === "EVENT" ? "COMMUNITY EVENT \xB7 VERIFIED FACTSHEET" : cardType === "ACTIVITY" ? "COMMUNITY ACTIVITY \xB7 VERIFIED FACTSHEET" : cardType === "ORGANIZATION" ? "COMMUNITY ORGANIZATION \xB7 DIRECTORY" : "VERIFIED FACTSHEET");
|
|
1940
|
+
const subtitle = clip(c.subtitle, 300);
|
|
1941
|
+
const summary = clip(c.summary, 1e3);
|
|
1942
|
+
const badge = clip(c.badge, 64);
|
|
1943
|
+
const facts = [];
|
|
1944
|
+
if (Array.isArray(c.facts)) {
|
|
1945
|
+
for (const f of c.facts) {
|
|
1946
|
+
if (!f || typeof f !== "object") continue;
|
|
1947
|
+
const label = clip(f.label, 80);
|
|
1948
|
+
const value = clip(f.value, 500);
|
|
1949
|
+
if (!label || !value) continue;
|
|
1950
|
+
let href = void 0;
|
|
1951
|
+
const rawHref = f.href;
|
|
1952
|
+
if (typeof rawHref === "string" && isSafeActionUrl(rawHref)) {
|
|
1953
|
+
href = rawHref;
|
|
1954
|
+
}
|
|
1955
|
+
facts.push({ label, value, href });
|
|
1956
|
+
}
|
|
1957
|
+
} else {
|
|
1958
|
+
const timeframe = clip(c.timeframe, 200);
|
|
1959
|
+
if (timeframe) facts.push({ label: "Timeframe", value: timeframe });
|
|
1960
|
+
const address = clip(c.address, 300);
|
|
1961
|
+
if (address) {
|
|
1962
|
+
facts.push({
|
|
1963
|
+
label: "Address",
|
|
1964
|
+
value: address,
|
|
1965
|
+
href: `https://maps.google.com/?q=${encodeURIComponent(address)}`
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
const hours = clip(c.hours, 300);
|
|
1969
|
+
if (hours) facts.push({ label: "Hours", value: hours });
|
|
1970
|
+
const phone = clip(c.phone, 80);
|
|
1971
|
+
if (phone) {
|
|
1972
|
+
facts.push({
|
|
1973
|
+
label: "Phone",
|
|
1974
|
+
value: phone,
|
|
1975
|
+
href: `tel:${phone.replace(/[^+\d]/g, "")}`
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
const cost = clip(c.cost, 120);
|
|
1979
|
+
if (cost) facts.push({ label: "Cost", value: cost });
|
|
1980
|
+
}
|
|
1981
|
+
const actions = [];
|
|
1982
|
+
if (Array.isArray(c.actions)) {
|
|
1983
|
+
for (const act of c.actions) {
|
|
1984
|
+
if (!act || typeof act !== "object") continue;
|
|
1985
|
+
const actObj = act;
|
|
1986
|
+
const label = clip(actObj.label, 80);
|
|
1987
|
+
const url = typeof actObj.url === "string" ? actObj.url.trim() : "";
|
|
1988
|
+
if (!label || !url || !isSafeActionUrl(url)) continue;
|
|
1989
|
+
actions.push({
|
|
1990
|
+
label,
|
|
1991
|
+
url,
|
|
1992
|
+
primary: Boolean(actObj.primary)
|
|
1993
|
+
});
|
|
1994
|
+
if (actions.length >= 5) break;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
let image = void 0;
|
|
1998
|
+
if (c.image && typeof c.image === "object") {
|
|
1999
|
+
const img = c.image;
|
|
2000
|
+
const masterUrl = typeof img.master_url === "string" && isSafeActionUrl(img.master_url) ? img.master_url : void 0;
|
|
2001
|
+
const url = typeof img.url === "string" && isSafeActionUrl(img.url) ? img.url : masterUrl;
|
|
2002
|
+
if (url) {
|
|
2003
|
+
image = {
|
|
2004
|
+
file_id: clip(img.file_id, 64),
|
|
2005
|
+
master_url: masterUrl,
|
|
2006
|
+
url,
|
|
2007
|
+
width: typeof img.width === "number" ? img.width : void 0,
|
|
2008
|
+
height: typeof img.height === "number" ? img.height : void 0,
|
|
2009
|
+
alt: clip(img.alt, 500),
|
|
2010
|
+
caption: clip(img.caption, 1e3),
|
|
2011
|
+
attribution: clip(img.attribution, 500),
|
|
2012
|
+
label: clip(img.label, 100),
|
|
2013
|
+
ai_generated: Boolean(img.ai_generated)
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
const verificationNote = clip(c.verification_note || c.verificationNote, 300) || "Verified by publication reporting staff";
|
|
2018
|
+
const verifiedAt = clip(c.verified_at || c.verifiedAt, 100) || "October 2026";
|
|
2019
|
+
let rating = void 0;
|
|
2020
|
+
if (typeof c.rating === "number" || typeof c.rating === "string") {
|
|
2021
|
+
rating = c.rating;
|
|
2022
|
+
}
|
|
2023
|
+
cards.push({
|
|
2024
|
+
id,
|
|
2025
|
+
cardType,
|
|
2026
|
+
kicker,
|
|
2027
|
+
title,
|
|
2028
|
+
subtitle,
|
|
2029
|
+
summary,
|
|
2030
|
+
badge,
|
|
2031
|
+
facts,
|
|
2032
|
+
actions,
|
|
2033
|
+
verificationNote,
|
|
2034
|
+
verifiedAt,
|
|
2035
|
+
image,
|
|
2036
|
+
rating
|
|
2037
|
+
});
|
|
2038
|
+
if (cards.length >= 10) break;
|
|
2039
|
+
}
|
|
2040
|
+
return cards;
|
|
2041
|
+
}
|
|
2042
|
+
function VerifiedCardComponent({
|
|
2043
|
+
card,
|
|
2044
|
+
className
|
|
2045
|
+
}) {
|
|
2046
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2047
|
+
"aside",
|
|
2048
|
+
{
|
|
2049
|
+
className: `article-verified-card ${className || ""}`.trim(),
|
|
2050
|
+
"aria-label": `Verified Factsheet: ${card.title}`,
|
|
2051
|
+
children: [
|
|
2052
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "article-verified-card__header", children: [
|
|
2053
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__kicker", children: card.kicker }),
|
|
2054
|
+
card.badge && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__badge", children: card.badge })
|
|
2055
|
+
] }),
|
|
2056
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "article-verified-card__title", children: card.title }),
|
|
2057
|
+
card.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "article-verified-card__subtitle", children: card.subtitle }),
|
|
2058
|
+
card.summary && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "article-verified-card__summary", children: card.summary }),
|
|
2059
|
+
card.facts.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "article-verified-card__fact-grid", children: card.facts.map((fact, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "article-verified-card__fact-row", children: [
|
|
2060
|
+
/* @__PURE__ */ jsxRuntime.jsx("dt", { className: "article-verified-card__fact-label", children: fact.label }),
|
|
2061
|
+
/* @__PURE__ */ jsxRuntime.jsx("dd", { className: "article-verified-card__fact-value", children: fact.href ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2062
|
+
"a",
|
|
2063
|
+
{
|
|
2064
|
+
className: "article-verified-card__fact-link",
|
|
2065
|
+
href: fact.href,
|
|
2066
|
+
rel: "noopener noreferrer",
|
|
2067
|
+
target: fact.href.startsWith("http") ? "_blank" : void 0,
|
|
2068
|
+
children: fact.value
|
|
2069
|
+
}
|
|
2070
|
+
) : fact.value })
|
|
2071
|
+
] }, i)) }),
|
|
2072
|
+
card.actions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "article-verified-card__actions", children: card.actions.map((act, i) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2073
|
+
"a",
|
|
2074
|
+
{
|
|
2075
|
+
className: `article-verified-card__action ${act.primary ? "article-verified-card__action--primary" : "article-verified-card__action--secondary"}`,
|
|
2076
|
+
href: act.url,
|
|
2077
|
+
target: "_blank",
|
|
2078
|
+
rel: "noopener noreferrer",
|
|
2079
|
+
children: [
|
|
2080
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: act.label }),
|
|
2081
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "article-verified-card__action-icon", children: "\u2197" })
|
|
2082
|
+
]
|
|
2083
|
+
},
|
|
2084
|
+
i
|
|
2085
|
+
)) }),
|
|
2086
|
+
/* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "article-verified-card__footer", children: [
|
|
2087
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "article-verified-card__seal", children: [
|
|
2088
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__seal-check", "aria-hidden": "true", children: "\u2713" }),
|
|
2089
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: card.verificationNote })
|
|
2090
|
+
] }),
|
|
2091
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__date", children: card.verifiedAt })
|
|
2092
|
+
] })
|
|
2093
|
+
]
|
|
2094
|
+
}
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
function VerifiedCardSection({
|
|
2098
|
+
cards,
|
|
2099
|
+
className
|
|
2100
|
+
}) {
|
|
2101
|
+
if (!cards || cards.length === 0) return null;
|
|
2102
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2103
|
+
"section",
|
|
2104
|
+
{
|
|
2105
|
+
className: `article-verified-card-section ${className || ""}`.trim(),
|
|
2106
|
+
"aria-label": "Verified Community Information",
|
|
2107
|
+
children: cards.map((card) => /* @__PURE__ */ jsxRuntime.jsx(VerifiedCardComponent, { card }, card.id))
|
|
2108
|
+
}
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
1889
2111
|
|
|
1890
2112
|
Object.defineProperty(exports, "COOKIE_CONSENT_BACKUP_KEY", {
|
|
1891
2113
|
enumerable: true,
|
|
@@ -1996,6 +2218,8 @@ exports.THEME_STORAGE_KEY = THEME_STORAGE_KEY;
|
|
|
1996
2218
|
exports.ThemeToggle = ThemeToggle;
|
|
1997
2219
|
exports.UTM_FIELDS = UTM_FIELDS;
|
|
1998
2220
|
exports.UUID = UUID;
|
|
2221
|
+
exports.VerifiedCardComponent = VerifiedCardComponent;
|
|
2222
|
+
exports.VerifiedCardSection = VerifiedCardSection;
|
|
1999
2223
|
exports.acceptRemoteRecord = acceptRemoteRecord;
|
|
2000
2224
|
exports.acceptSnapshot = acceptSnapshot;
|
|
2001
2225
|
exports.activeNearby = activeNearby;
|
|
@@ -2030,6 +2254,7 @@ exports.feedCacheOptions = feedCacheOptions;
|
|
|
2030
2254
|
exports.filterNews = filterNews;
|
|
2031
2255
|
exports.findPublicationByHost = findPublicationByHost;
|
|
2032
2256
|
exports.findPublicationBySlug = findPublicationBySlug;
|
|
2257
|
+
exports.getArticleCards = getArticleCards;
|
|
2033
2258
|
exports.getNetworkPublicationUrl = getNetworkPublicationUrl;
|
|
2034
2259
|
exports.getStoredTheme = getStoredTheme;
|
|
2035
2260
|
exports.getSystemTheme = getSystemTheme;
|
|
@@ -2037,6 +2262,7 @@ exports.inventoryCacheOptions = inventoryCacheOptions;
|
|
|
2037
2262
|
exports.isAdult = isAdult;
|
|
2038
2263
|
exports.isClassifiedCategory = isClassifiedCategory;
|
|
2039
2264
|
exports.isEmploymentType = isEmploymentType;
|
|
2265
|
+
exports.isSafeActionUrl = isSafeActionUrl;
|
|
2040
2266
|
exports.jobEmployerBody = jobEmployerBody;
|
|
2041
2267
|
exports.jobOpeningBody = jobOpeningBody;
|
|
2042
2268
|
exports.jobPostingBody = jobPostingBody;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { A as ArticleCitation,
|
|
1
|
+
import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, H as HomepageSelection, b as NearbyUpdate, c as NewsItem, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-DFDCZHbS.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-DFDCZHbS.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-RWmfj3I3.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-C0IGpt-B.cjs';
|
|
5
6
|
import { CookieCategoryDefinition, CookieConsentRecord } from '@scalemule/compliance';
|
|
6
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, decorateNetworkUrl, getCookieConsent, hasConsented, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
|
|
7
8
|
export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.cjs';
|
|
@@ -10,6 +11,7 @@ export { ThemeToggle, ThemeToggleProps } from './theme-toggle.cjs';
|
|
|
10
11
|
export { PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, listedPublications, localPublications } from './network.cjs';
|
|
11
12
|
export { LocalNetworkBar, LocalNetworkBarProps } from './network-bar.cjs';
|
|
12
13
|
import * as react from 'react';
|
|
14
|
+
export { VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './verified-card.cjs';
|
|
13
15
|
|
|
14
16
|
/** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
|
|
15
17
|
declare const BROADSHEET_THEME: PublicationTheme;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { A as ArticleCitation,
|
|
1
|
+
import { a as PublicationTheme, P as PublicationInput, N as NewsPublication, F as FeatureName, H as HomepageSelection, b as NearbyUpdate, c as NewsItem, E as EditorialSection, d as NewsArticle, C as CouponOffer } from './types-DFDCZHbS.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-DFDCZHbS.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-RWmfj3I3.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-CxuyMF39.js';
|
|
5
6
|
import { CookieCategoryDefinition, CookieConsentRecord } from '@scalemule/compliance';
|
|
6
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, decorateNetworkUrl, getCookieConsent, hasConsented, isCategoryAllowed, isGeoConsentRequired, queryNetworkConsentHub, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from '@scalemule/compliance';
|
|
7
8
|
export { CookieConsentBanner, CookieConsentBannerProps } from './cookie-consent-banner.js';
|
|
@@ -10,6 +11,7 @@ export { ThemeToggle, ThemeToggleProps } from './theme-toggle.js';
|
|
|
10
11
|
export { PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, listedPublications, localPublications } from './network.js';
|
|
11
12
|
export { LocalNetworkBar, LocalNetworkBarProps } from './network-bar.js';
|
|
12
13
|
import * as react from 'react';
|
|
14
|
+
export { VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './verified-card.js';
|
|
13
15
|
|
|
14
16
|
/** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
|
|
15
17
|
declare const BROADSHEET_THEME: PublicationTheme;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
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';
|
|
2
2
|
export { LocalNetworkBar } from './chunk-QBT66OVE.js';
|
|
3
|
+
export { VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-I3ZTQCV3.js';
|
|
3
4
|
import { definePublication } from './chunk-C7JTEHTH.js';
|
|
4
5
|
export { BROADSHEET_THEME, FEATURE_NAMES, LAYOUT_COOKIE, acceptRemoteRecord, acceptSnapshot, definePublication, featureEnabled, parseSnapshot, publicationFromSnapshot, publicationRecord, resolveLayout, snapshotFromRecord, syncPublication } from './chunk-C7JTEHTH.js';
|
|
5
6
|
export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedPath, classifiedSearchPath, clipChars, dealDollars, dealGoPath, dealsFeedPath, employmentLabel, isClassifiedCategory, isEmploymentType, jobEmployerBody, jobOpeningBody, jobPostingBody, prepareClassified, prepareJobPost, publicJobPath, publicJobsPath } from './chunk-C7ZPLH2K.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { P as PublicationInput, N as NewsPublication } from './types-DFDCZHbS.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Serializable application config. Code hooks (`matchTag`, `coupons.resolve`)
|
|
5
|
+
* stay in the site and are attached after the snapshot loads.
|
|
6
|
+
*/
|
|
7
|
+
type PublicationRecord = Omit<PublicationInput, "matchTag" | "coupons"> & {
|
|
8
|
+
coupons?: Omit<NonNullable<PublicationInput["coupons"]>, "resolve">;
|
|
9
|
+
};
|
|
10
|
+
/** Last config the application accepted. Age never invalidates it. */
|
|
11
|
+
type PublicationSnapshot = {
|
|
12
|
+
version: 1;
|
|
13
|
+
syncedAt: string;
|
|
14
|
+
record: PublicationRecord;
|
|
15
|
+
};
|
|
16
|
+
type SnapshotHooks = {
|
|
17
|
+
matchTag?: PublicationInput["matchTag"];
|
|
18
|
+
resolveCoupon?: NonNullable<PublicationInput["coupons"]>["resolve"];
|
|
19
|
+
};
|
|
20
|
+
type SyncSource = "fresh" | "retained";
|
|
21
|
+
type SyncedPublication = {
|
|
22
|
+
publication: NewsPublication;
|
|
23
|
+
snapshot: PublicationSnapshot;
|
|
24
|
+
source: SyncSource;
|
|
25
|
+
/** How long this snapshot has been the one on disk. Zero for a fresh sync. */
|
|
26
|
+
ageMs: number;
|
|
27
|
+
};
|
|
28
|
+
declare function publicationRecord(publication: NewsPublication): PublicationRecord;
|
|
29
|
+
declare function snapshotFromRecord(record: PublicationRecord, syncedAt?: string): PublicationSnapshot;
|
|
30
|
+
declare function parseSnapshot(raw: string): PublicationSnapshot | null;
|
|
31
|
+
declare function acceptSnapshot(value: unknown): PublicationSnapshot | null;
|
|
32
|
+
/** A remote payload replaces the snapshot only when it is a complete record for this paper. */
|
|
33
|
+
declare function acceptRemoteRecord(value: unknown, expectedSlug?: string): PublicationRecord | null;
|
|
34
|
+
declare function publicationFromSnapshot(snapshot: PublicationSnapshot, hooks?: SnapshotHooks): NewsPublication;
|
|
35
|
+
/**
|
|
36
|
+
* Fetch the application config. Any failure, timeout, or malformed body keeps
|
|
37
|
+
* the previous snapshot. Nothing here expires a snapshot because the API was
|
|
38
|
+
* unreachable for hours or days.
|
|
39
|
+
*/
|
|
40
|
+
declare function syncPublication(input: {
|
|
41
|
+
previous: PublicationSnapshot | null;
|
|
42
|
+
fetchRecord: () => Promise<unknown>;
|
|
43
|
+
expectedSlug?: string;
|
|
44
|
+
now?: number;
|
|
45
|
+
hooks?: SnapshotHooks;
|
|
46
|
+
}): Promise<SyncedPublication>;
|
|
47
|
+
|
|
48
|
+
export { type PublicationSnapshot as P, type SnapshotHooks as S, type SyncedPublication as a, type PublicationRecord as b, type SyncSource as c, acceptRemoteRecord as d, acceptSnapshot as e, publicationFromSnapshot as f, publicationRecord as g, syncPublication as h, parseSnapshot as p, snapshotFromRecord as s };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { P as PublicationInput, N as NewsPublication } from './types-DFDCZHbS.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Serializable application config. Code hooks (`matchTag`, `coupons.resolve`)
|
|
5
|
+
* stay in the site and are attached after the snapshot loads.
|
|
6
|
+
*/
|
|
7
|
+
type PublicationRecord = Omit<PublicationInput, "matchTag" | "coupons"> & {
|
|
8
|
+
coupons?: Omit<NonNullable<PublicationInput["coupons"]>, "resolve">;
|
|
9
|
+
};
|
|
10
|
+
/** Last config the application accepted. Age never invalidates it. */
|
|
11
|
+
type PublicationSnapshot = {
|
|
12
|
+
version: 1;
|
|
13
|
+
syncedAt: string;
|
|
14
|
+
record: PublicationRecord;
|
|
15
|
+
};
|
|
16
|
+
type SnapshotHooks = {
|
|
17
|
+
matchTag?: PublicationInput["matchTag"];
|
|
18
|
+
resolveCoupon?: NonNullable<PublicationInput["coupons"]>["resolve"];
|
|
19
|
+
};
|
|
20
|
+
type SyncSource = "fresh" | "retained";
|
|
21
|
+
type SyncedPublication = {
|
|
22
|
+
publication: NewsPublication;
|
|
23
|
+
snapshot: PublicationSnapshot;
|
|
24
|
+
source: SyncSource;
|
|
25
|
+
/** How long this snapshot has been the one on disk. Zero for a fresh sync. */
|
|
26
|
+
ageMs: number;
|
|
27
|
+
};
|
|
28
|
+
declare function publicationRecord(publication: NewsPublication): PublicationRecord;
|
|
29
|
+
declare function snapshotFromRecord(record: PublicationRecord, syncedAt?: string): PublicationSnapshot;
|
|
30
|
+
declare function parseSnapshot(raw: string): PublicationSnapshot | null;
|
|
31
|
+
declare function acceptSnapshot(value: unknown): PublicationSnapshot | null;
|
|
32
|
+
/** A remote payload replaces the snapshot only when it is a complete record for this paper. */
|
|
33
|
+
declare function acceptRemoteRecord(value: unknown, expectedSlug?: string): PublicationRecord | null;
|
|
34
|
+
declare function publicationFromSnapshot(snapshot: PublicationSnapshot, hooks?: SnapshotHooks): NewsPublication;
|
|
35
|
+
/**
|
|
36
|
+
* Fetch the application config. Any failure, timeout, or malformed body keeps
|
|
37
|
+
* the previous snapshot. Nothing here expires a snapshot because the API was
|
|
38
|
+
* unreachable for hours or days.
|
|
39
|
+
*/
|
|
40
|
+
declare function syncPublication(input: {
|
|
41
|
+
previous: PublicationSnapshot | null;
|
|
42
|
+
fetchRecord: () => Promise<unknown>;
|
|
43
|
+
expectedSlug?: string;
|
|
44
|
+
now?: number;
|
|
45
|
+
hooks?: SnapshotHooks;
|
|
46
|
+
}): Promise<SyncedPublication>;
|
|
47
|
+
|
|
48
|
+
export { type PublicationSnapshot as P, type SnapshotHooks as S, type SyncedPublication as a, type PublicationRecord as b, type SyncSource as c, acceptRemoteRecord as d, acceptSnapshot as e, publicationFromSnapshot as f, publicationRecord as g, syncPublication as h, parseSnapshot as p, snapshotFromRecord as s };
|
package/dist/snapshot-file.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-
|
|
1
|
+
import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-C0IGpt-B.cjs';
|
|
2
|
+
import './types-DFDCZHbS.cjs';
|
|
2
3
|
|
|
3
4
|
declare function readSnapshotFile(file: string): Promise<PublicationSnapshot | null>;
|
|
4
5
|
/**
|