@scalemule/news-publication 0.0.20 → 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 +279 -0
- package/dist/index.d.cts +48 -3
- package/dist/index.d.ts +48 -3
- package/dist/index.js +54 -1
- 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
|
@@ -1836,6 +1836,279 @@ function readerSources(input) {
|
|
|
1836
1836
|
return output;
|
|
1837
1837
|
}
|
|
1838
1838
|
|
|
1839
|
+
// src/listening.ts
|
|
1840
|
+
function publicationListeningTrack(article, publicationId, publicationName, origin) {
|
|
1841
|
+
const articleUrl2 = new URL(`/news/${encodeURIComponent(article.slug)}`, origin).href;
|
|
1842
|
+
let storyId = articleUrl2;
|
|
1843
|
+
if (article.canonical_url) {
|
|
1844
|
+
try {
|
|
1845
|
+
const canonical = new URL(article.canonical_url);
|
|
1846
|
+
if (canonical.protocol === "https:" && !canonical.username && !canonical.password && /^\/news\/[^/]+\/?$/.test(canonical.pathname) && localPublications.some((item) => new URL(item.url).hostname === canonical.hostname.replace(/^www\./, ""))) {
|
|
1847
|
+
canonical.search = "";
|
|
1848
|
+
canonical.hash = "";
|
|
1849
|
+
canonical.hostname = canonical.hostname.replace(/^www\./, "");
|
|
1850
|
+
canonical.pathname = canonical.pathname.replace(/\/$/, "");
|
|
1851
|
+
storyId = canonical.href;
|
|
1852
|
+
}
|
|
1853
|
+
} catch {
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const revision = article.editorial?.modified_at;
|
|
1857
|
+
return {
|
|
1858
|
+
id: article.id,
|
|
1859
|
+
publicationId,
|
|
1860
|
+
publicationName,
|
|
1861
|
+
title: article.title,
|
|
1862
|
+
articleUrl: articleUrl2,
|
|
1863
|
+
storyId,
|
|
1864
|
+
...revision && Number.isFinite(Date.parse(revision)) ? { revision } : {},
|
|
1865
|
+
...article.audio?.duration_ms && article.audio.duration_ms > 0 ? { durationSeconds: article.audio.duration_ms / 1e3 } : {},
|
|
1866
|
+
...article.section ? { section: article.section } : {},
|
|
1867
|
+
...article.published_at ? { publishedAt: article.published_at } : {}
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
async function publicationListeningCandidates(items, read, publicationId, publicationName, origin) {
|
|
1871
|
+
const output = [];
|
|
1872
|
+
const recent = items.filter((item) => !item.editorial?.duplicate_url).slice(0, 30);
|
|
1873
|
+
let failures = 0;
|
|
1874
|
+
for (let i = 0; i < recent.length; i += 4) {
|
|
1875
|
+
const batch = await Promise.allSettled(recent.slice(i, i + 4).map((item) => read(item.slug)));
|
|
1876
|
+
for (const result of batch) {
|
|
1877
|
+
if (result.status === "rejected") {
|
|
1878
|
+
failures++;
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
const article = result.value;
|
|
1882
|
+
if (!article.editorial?.duplicate_url && article.audio?.url && article.audio.duration_ms && article.audio.duration_ms > 0)
|
|
1883
|
+
output.push(publicationListeningTrack(article, publicationId, publicationName, origin));
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
if (!output.length && failures) throw new Error("Audio inventory could not be loaded");
|
|
1887
|
+
return output.sort((a, b) => (b.publishedAt ?? "").localeCompare(a.publishedAt ?? ""));
|
|
1888
|
+
}
|
|
1889
|
+
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
|
+
}
|
|
2111
|
+
|
|
1839
2112
|
Object.defineProperty(exports, "COOKIE_CONSENT_BACKUP_KEY", {
|
|
1840
2113
|
enumerable: true,
|
|
1841
2114
|
get: function () { return compliance.COOKIE_CONSENT_BACKUP_KEY; }
|
|
@@ -1945,6 +2218,8 @@ exports.THEME_STORAGE_KEY = THEME_STORAGE_KEY;
|
|
|
1945
2218
|
exports.ThemeToggle = ThemeToggle;
|
|
1946
2219
|
exports.UTM_FIELDS = UTM_FIELDS;
|
|
1947
2220
|
exports.UUID = UUID;
|
|
2221
|
+
exports.VerifiedCardComponent = VerifiedCardComponent;
|
|
2222
|
+
exports.VerifiedCardSection = VerifiedCardSection;
|
|
1948
2223
|
exports.acceptRemoteRecord = acceptRemoteRecord;
|
|
1949
2224
|
exports.acceptSnapshot = acceptSnapshot;
|
|
1950
2225
|
exports.activeNearby = activeNearby;
|
|
@@ -1979,6 +2254,7 @@ exports.feedCacheOptions = feedCacheOptions;
|
|
|
1979
2254
|
exports.filterNews = filterNews;
|
|
1980
2255
|
exports.findPublicationByHost = findPublicationByHost;
|
|
1981
2256
|
exports.findPublicationBySlug = findPublicationBySlug;
|
|
2257
|
+
exports.getArticleCards = getArticleCards;
|
|
1982
2258
|
exports.getNetworkPublicationUrl = getNetworkPublicationUrl;
|
|
1983
2259
|
exports.getStoredTheme = getStoredTheme;
|
|
1984
2260
|
exports.getSystemTheme = getSystemTheme;
|
|
@@ -1986,6 +2262,7 @@ exports.inventoryCacheOptions = inventoryCacheOptions;
|
|
|
1986
2262
|
exports.isAdult = isAdult;
|
|
1987
2263
|
exports.isClassifiedCategory = isClassifiedCategory;
|
|
1988
2264
|
exports.isEmploymentType = isEmploymentType;
|
|
2265
|
+
exports.isSafeActionUrl = isSafeActionUrl;
|
|
1989
2266
|
exports.jobEmployerBody = jobEmployerBody;
|
|
1990
2267
|
exports.jobOpeningBody = jobOpeningBody;
|
|
1991
2268
|
exports.jobPostingBody = jobPostingBody;
|
|
@@ -2007,6 +2284,8 @@ exports.publicJobsPath = publicJobsPath;
|
|
|
2007
2284
|
exports.publicUrl = publicUrl;
|
|
2008
2285
|
exports.publicationFromSnapshot = publicationFromSnapshot;
|
|
2009
2286
|
exports.publicationJsonLd = publicationJsonLd;
|
|
2287
|
+
exports.publicationListeningCandidates = publicationListeningCandidates;
|
|
2288
|
+
exports.publicationListeningTrack = publicationListeningTrack;
|
|
2010
2289
|
exports.publicationMeta = publicationMeta;
|
|
2011
2290
|
exports.publicationRecord = publicationRecord;
|
|
2012
2291
|
exports.readerSources = readerSources;
|
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;
|
|
@@ -311,4 +313,47 @@ declare function readerSources(input: unknown): {
|
|
|
311
313
|
publisher?: string;
|
|
312
314
|
}[];
|
|
313
315
|
|
|
314
|
-
|
|
316
|
+
interface ListeningArticle {
|
|
317
|
+
id: string;
|
|
318
|
+
slug: string;
|
|
319
|
+
title: string;
|
|
320
|
+
canonical_url?: string | null;
|
|
321
|
+
section?: string | null;
|
|
322
|
+
published_at?: string;
|
|
323
|
+
editorial?: {
|
|
324
|
+
duplicate_url?: string;
|
|
325
|
+
modified_at?: string;
|
|
326
|
+
} | null;
|
|
327
|
+
audio?: {
|
|
328
|
+
url?: string | null;
|
|
329
|
+
duration_ms?: number | null;
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/** Public metadata only. Never cache signed recording URLs in reader history. */
|
|
333
|
+
declare function publicationListeningTrack(article: ListeningArticle, publicationId: string, publicationName: string, origin: string): {
|
|
334
|
+
publishedAt?: string | undefined;
|
|
335
|
+
section?: string | undefined;
|
|
336
|
+
durationSeconds?: number | undefined;
|
|
337
|
+
revision?: string | undefined;
|
|
338
|
+
id: string;
|
|
339
|
+
publicationId: string;
|
|
340
|
+
publicationName: string;
|
|
341
|
+
title: string;
|
|
342
|
+
articleUrl: string;
|
|
343
|
+
storyId: string;
|
|
344
|
+
};
|
|
345
|
+
/** Bounded public read fan-out, initiated only when a reader requests a catch-up queue. */
|
|
346
|
+
declare function publicationListeningCandidates<T extends ListeningArticle>(items: T[], read: (slug: string) => Promise<ListeningArticle>, publicationId: string, publicationName: string, origin: string): Promise<{
|
|
347
|
+
publishedAt?: string | undefined;
|
|
348
|
+
section?: string | undefined;
|
|
349
|
+
durationSeconds?: number | undefined;
|
|
350
|
+
revision?: string | undefined;
|
|
351
|
+
id: string;
|
|
352
|
+
publicationId: string;
|
|
353
|
+
publicationName: string;
|
|
354
|
+
title: string;
|
|
355
|
+
articleUrl: string;
|
|
356
|
+
storyId: string;
|
|
357
|
+
}[]>;
|
|
358
|
+
|
|
359
|
+
export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, type ListeningArticle, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
|