@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.
@@ -1,4 +1,5 @@
1
- import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-OZFP3-Up.js';
1
+ import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-CxuyMF39.js';
2
+ import './types-DFDCZHbS.js';
2
3
 
3
4
  declare function readSnapshotFile(file: string): Promise<PublicationSnapshot | null>;
4
5
  /**
@@ -134,6 +134,44 @@ type ArticleCitation = {
134
134
  title?: string;
135
135
  publisher?: string;
136
136
  };
137
+ type CardType = "PLACE" | "ACTIVITY" | "EVENT" | "ORGANIZATION" | "PRODUCT";
138
+ type VerifiedCardAction = {
139
+ label: string;
140
+ url: string;
141
+ primary?: boolean;
142
+ };
143
+ type VerifiedCardFact = {
144
+ label: string;
145
+ value: string;
146
+ href?: string;
147
+ };
148
+ type VerifiedCardImage = {
149
+ file_id?: string;
150
+ master_url?: string;
151
+ url?: string;
152
+ width?: number;
153
+ height?: number;
154
+ alt?: string;
155
+ caption?: string;
156
+ attribution?: string;
157
+ label?: string;
158
+ ai_generated?: boolean;
159
+ };
160
+ type VerifiedCard = {
161
+ id: string;
162
+ cardType: CardType;
163
+ kicker: string;
164
+ title: string;
165
+ subtitle?: string;
166
+ summary?: string;
167
+ badge?: string;
168
+ facts: VerifiedCardFact[];
169
+ actions: VerifiedCardAction[];
170
+ verificationNote: string;
171
+ verifiedAt: string;
172
+ image?: VerifiedCardImage;
173
+ rating?: number | string;
174
+ };
137
175
  type NewsArticle = NewsItem & {
138
176
  content_html: string;
139
177
  subtitle?: string;
@@ -167,6 +205,7 @@ type NewsArticle = NewsItem & {
167
205
  attribution: string;
168
206
  ai_generated: boolean;
169
207
  }[];
208
+ cards?: VerifiedCard[] | unknown[];
170
209
  };
171
210
  };
172
211
  type StoryDisplay = {
@@ -199,49 +238,4 @@ type EditorialSection = {
199
238
  items: NewsItem[];
200
239
  };
201
240
 
202
- /**
203
- * Serializable application config. Code hooks (`matchTag`, `coupons.resolve`)
204
- * stay in the site and are attached after the snapshot loads.
205
- */
206
- type PublicationRecord = Omit<PublicationInput, "matchTag" | "coupons"> & {
207
- coupons?: Omit<NonNullable<PublicationInput["coupons"]>, "resolve">;
208
- };
209
- /** Last config the application accepted. Age never invalidates it. */
210
- type PublicationSnapshot = {
211
- version: 1;
212
- syncedAt: string;
213
- record: PublicationRecord;
214
- };
215
- type SnapshotHooks = {
216
- matchTag?: PublicationInput["matchTag"];
217
- resolveCoupon?: NonNullable<PublicationInput["coupons"]>["resolve"];
218
- };
219
- type SyncSource = "fresh" | "retained";
220
- type SyncedPublication = {
221
- publication: NewsPublication;
222
- snapshot: PublicationSnapshot;
223
- source: SyncSource;
224
- /** How long this snapshot has been the one on disk. Zero for a fresh sync. */
225
- ageMs: number;
226
- };
227
- declare function publicationRecord(publication: NewsPublication): PublicationRecord;
228
- declare function snapshotFromRecord(record: PublicationRecord, syncedAt?: string): PublicationSnapshot;
229
- declare function parseSnapshot(raw: string): PublicationSnapshot | null;
230
- declare function acceptSnapshot(value: unknown): PublicationSnapshot | null;
231
- /** A remote payload replaces the snapshot only when it is a complete record for this paper. */
232
- declare function acceptRemoteRecord(value: unknown, expectedSlug?: string): PublicationRecord | null;
233
- declare function publicationFromSnapshot(snapshot: PublicationSnapshot, hooks?: SnapshotHooks): NewsPublication;
234
- /**
235
- * Fetch the application config. Any failure, timeout, or malformed body keeps
236
- * the previous snapshot. Nothing here expires a snapshot because the API was
237
- * unreachable for hours or days.
238
- */
239
- declare function syncPublication(input: {
240
- previous: PublicationSnapshot | null;
241
- fetchRecord: () => Promise<unknown>;
242
- expectedSlug?: string;
243
- now?: number;
244
- hooks?: SnapshotHooks;
245
- }): Promise<SyncedPublication>;
246
-
247
- export { type ArticleCitation as A, type CouponOffer as C, type EditorialSection as E, type FeatureName as F, type HomepageSelection as H, type NewsPublication as N, type PublicationSnapshot as P, type ReferralSettings as R, type SnapshotHooks as S, type TagRef as T, type SyncedPublication as a, type PublicationTheme as b, type PublicationInput as c, type NearbyUpdate as d, type NewsItem as e, type NewsArticle as f, type CategoryRef as g, type CouponSettings as h, type CoverImageMetadata as i, FEATURE_NAMES as j, type FeatureFlags as k, type NewsFeed as l, type NewsLayoutId as m, type PublicationModules as n, type PublicationRecord as o, type StoryDisplay as p, type SyncSource as q, acceptRemoteRecord as r, acceptSnapshot as s, parseSnapshot as t, publicationFromSnapshot as u, publicationRecord as v, snapshotFromRecord as w, syncPublication as x };
241
+ export { type ArticleCitation as A, type CouponOffer as C, type EditorialSection as E, type FeatureName as F, type HomepageSelection as H, type NewsPublication as N, type PublicationInput as P, type ReferralSettings as R, type StoryDisplay as S, type TagRef as T, type VerifiedCard as V, type PublicationTheme as a, type NearbyUpdate as b, type NewsItem as c, type NewsArticle as d, type CardType as e, type CategoryRef as f, type CouponSettings as g, type CoverImageMetadata as h, FEATURE_NAMES as i, type FeatureFlags as j, type NewsFeed as k, type NewsLayoutId as l, type PublicationModules as m, type VerifiedCardAction as n, type VerifiedCardFact as o, type VerifiedCardImage as p };
@@ -134,6 +134,44 @@ type ArticleCitation = {
134
134
  title?: string;
135
135
  publisher?: string;
136
136
  };
137
+ type CardType = "PLACE" | "ACTIVITY" | "EVENT" | "ORGANIZATION" | "PRODUCT";
138
+ type VerifiedCardAction = {
139
+ label: string;
140
+ url: string;
141
+ primary?: boolean;
142
+ };
143
+ type VerifiedCardFact = {
144
+ label: string;
145
+ value: string;
146
+ href?: string;
147
+ };
148
+ type VerifiedCardImage = {
149
+ file_id?: string;
150
+ master_url?: string;
151
+ url?: string;
152
+ width?: number;
153
+ height?: number;
154
+ alt?: string;
155
+ caption?: string;
156
+ attribution?: string;
157
+ label?: string;
158
+ ai_generated?: boolean;
159
+ };
160
+ type VerifiedCard = {
161
+ id: string;
162
+ cardType: CardType;
163
+ kicker: string;
164
+ title: string;
165
+ subtitle?: string;
166
+ summary?: string;
167
+ badge?: string;
168
+ facts: VerifiedCardFact[];
169
+ actions: VerifiedCardAction[];
170
+ verificationNote: string;
171
+ verifiedAt: string;
172
+ image?: VerifiedCardImage;
173
+ rating?: number | string;
174
+ };
137
175
  type NewsArticle = NewsItem & {
138
176
  content_html: string;
139
177
  subtitle?: string;
@@ -167,6 +205,7 @@ type NewsArticle = NewsItem & {
167
205
  attribution: string;
168
206
  ai_generated: boolean;
169
207
  }[];
208
+ cards?: VerifiedCard[] | unknown[];
170
209
  };
171
210
  };
172
211
  type StoryDisplay = {
@@ -199,49 +238,4 @@ type EditorialSection = {
199
238
  items: NewsItem[];
200
239
  };
201
240
 
202
- /**
203
- * Serializable application config. Code hooks (`matchTag`, `coupons.resolve`)
204
- * stay in the site and are attached after the snapshot loads.
205
- */
206
- type PublicationRecord = Omit<PublicationInput, "matchTag" | "coupons"> & {
207
- coupons?: Omit<NonNullable<PublicationInput["coupons"]>, "resolve">;
208
- };
209
- /** Last config the application accepted. Age never invalidates it. */
210
- type PublicationSnapshot = {
211
- version: 1;
212
- syncedAt: string;
213
- record: PublicationRecord;
214
- };
215
- type SnapshotHooks = {
216
- matchTag?: PublicationInput["matchTag"];
217
- resolveCoupon?: NonNullable<PublicationInput["coupons"]>["resolve"];
218
- };
219
- type SyncSource = "fresh" | "retained";
220
- type SyncedPublication = {
221
- publication: NewsPublication;
222
- snapshot: PublicationSnapshot;
223
- source: SyncSource;
224
- /** How long this snapshot has been the one on disk. Zero for a fresh sync. */
225
- ageMs: number;
226
- };
227
- declare function publicationRecord(publication: NewsPublication): PublicationRecord;
228
- declare function snapshotFromRecord(record: PublicationRecord, syncedAt?: string): PublicationSnapshot;
229
- declare function parseSnapshot(raw: string): PublicationSnapshot | null;
230
- declare function acceptSnapshot(value: unknown): PublicationSnapshot | null;
231
- /** A remote payload replaces the snapshot only when it is a complete record for this paper. */
232
- declare function acceptRemoteRecord(value: unknown, expectedSlug?: string): PublicationRecord | null;
233
- declare function publicationFromSnapshot(snapshot: PublicationSnapshot, hooks?: SnapshotHooks): NewsPublication;
234
- /**
235
- * Fetch the application config. Any failure, timeout, or malformed body keeps
236
- * the previous snapshot. Nothing here expires a snapshot because the API was
237
- * unreachable for hours or days.
238
- */
239
- declare function syncPublication(input: {
240
- previous: PublicationSnapshot | null;
241
- fetchRecord: () => Promise<unknown>;
242
- expectedSlug?: string;
243
- now?: number;
244
- hooks?: SnapshotHooks;
245
- }): Promise<SyncedPublication>;
246
-
247
- export { type ArticleCitation as A, type CouponOffer as C, type EditorialSection as E, type FeatureName as F, type HomepageSelection as H, type NewsPublication as N, type PublicationSnapshot as P, type ReferralSettings as R, type SnapshotHooks as S, type TagRef as T, type SyncedPublication as a, type PublicationTheme as b, type PublicationInput as c, type NearbyUpdate as d, type NewsItem as e, type NewsArticle as f, type CategoryRef as g, type CouponSettings as h, type CoverImageMetadata as i, FEATURE_NAMES as j, type FeatureFlags as k, type NewsFeed as l, type NewsLayoutId as m, type PublicationModules as n, type PublicationRecord as o, type StoryDisplay as p, type SyncSource as q, acceptRemoteRecord as r, acceptSnapshot as s, parseSnapshot as t, publicationFromSnapshot as u, publicationRecord as v, snapshotFromRecord as w, syncPublication as x };
241
+ export { type ArticleCitation as A, type CouponOffer as C, type EditorialSection as E, type FeatureName as F, type HomepageSelection as H, type NewsPublication as N, type PublicationInput as P, type ReferralSettings as R, type StoryDisplay as S, type TagRef as T, type VerifiedCard as V, type PublicationTheme as a, type NearbyUpdate as b, type NewsItem as c, type NewsArticle as d, type CardType as e, type CategoryRef as f, type CouponSettings as g, type CoverImageMetadata as h, FEATURE_NAMES as i, type FeatureFlags as j, type NewsFeed as k, type NewsLayoutId as l, type PublicationModules as m, type VerifiedCardAction as n, type VerifiedCardFact as o, type VerifiedCardImage as p };
@@ -0,0 +1,232 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+
5
+ // src/verified-card.tsx
6
+ var ALLOWED_CARD_TYPES = /* @__PURE__ */ new Set([
7
+ "PLACE",
8
+ "ACTIVITY",
9
+ "EVENT",
10
+ "ORGANIZATION",
11
+ "PRODUCT"
12
+ ]);
13
+ function clip(val, maxChars) {
14
+ if (typeof val !== "string") return void 0;
15
+ const trimmed = val.trim();
16
+ if (!trimmed) return void 0;
17
+ return Array.from(trimmed).slice(0, maxChars).join("");
18
+ }
19
+ function isSafeActionUrl(raw) {
20
+ if (typeof raw !== "string") return false;
21
+ const trimmed = raw.trim();
22
+ if (!trimmed || trimmed.length > 2048) return false;
23
+ if (trimmed.startsWith("tel:") || trimmed.startsWith("mailto:")) {
24
+ return /^tel:\+?[\d\-().\s]+$/.test(trimmed) || /^mailto:[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed);
25
+ }
26
+ try {
27
+ const url = new URL(trimmed);
28
+ if (url.protocol !== "https:" && url.protocol !== "http:") return false;
29
+ if (url.username || url.password) return false;
30
+ const hostname = url.hostname.toLowerCase();
31
+ if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".internal") || hostname.endsWith(".local") || hostname === "127.0.0.1" || hostname === "169.254.169.254") {
32
+ return false;
33
+ }
34
+ for (const [key] of url.searchParams.entries()) {
35
+ const cleanKey = key.toLowerCase().replace(/[^a-z0-9]/g, "");
36
+ if (cleanKey === "token" || cleanKey === "apikey" || cleanKey === "accesstoken" || cleanKey === "secret" || cleanKey === "clientsecret" || cleanKey === "authorization" || cleanKey === "signature" || cleanKey === "password" || cleanKey === "xamzsignature" || cleanKey === "xamzsecuritytoken") {
37
+ return false;
38
+ }
39
+ }
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+ function getArticleCards(rawCards) {
46
+ if (!Array.isArray(rawCards) || rawCards.length === 0) return [];
47
+ const cards = [];
48
+ for (const item of rawCards) {
49
+ if (!item || typeof item !== "object") continue;
50
+ const c = item;
51
+ const id = clip(c.id, 64);
52
+ const title = clip(c.title, 300);
53
+ if (!id || !title) continue;
54
+ const rawType = typeof c.card_type === "string" ? c.card_type : c.cardType;
55
+ const cardType = ALLOWED_CARD_TYPES.has(rawType) ? rawType : "PLACE";
56
+ 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");
57
+ const subtitle = clip(c.subtitle, 300);
58
+ const summary = clip(c.summary, 1e3);
59
+ const badge = clip(c.badge, 64);
60
+ const facts = [];
61
+ if (Array.isArray(c.facts)) {
62
+ for (const f of c.facts) {
63
+ if (!f || typeof f !== "object") continue;
64
+ const label = clip(f.label, 80);
65
+ const value = clip(f.value, 500);
66
+ if (!label || !value) continue;
67
+ let href = void 0;
68
+ const rawHref = f.href;
69
+ if (typeof rawHref === "string" && isSafeActionUrl(rawHref)) {
70
+ href = rawHref;
71
+ }
72
+ facts.push({ label, value, href });
73
+ }
74
+ } else {
75
+ const timeframe = clip(c.timeframe, 200);
76
+ if (timeframe) facts.push({ label: "Timeframe", value: timeframe });
77
+ const address = clip(c.address, 300);
78
+ if (address) {
79
+ facts.push({
80
+ label: "Address",
81
+ value: address,
82
+ href: `https://maps.google.com/?q=${encodeURIComponent(address)}`
83
+ });
84
+ }
85
+ const hours = clip(c.hours, 300);
86
+ if (hours) facts.push({ label: "Hours", value: hours });
87
+ const phone = clip(c.phone, 80);
88
+ if (phone) {
89
+ facts.push({
90
+ label: "Phone",
91
+ value: phone,
92
+ href: `tel:${phone.replace(/[^+\d]/g, "")}`
93
+ });
94
+ }
95
+ const cost = clip(c.cost, 120);
96
+ if (cost) facts.push({ label: "Cost", value: cost });
97
+ }
98
+ const actions = [];
99
+ if (Array.isArray(c.actions)) {
100
+ for (const act of c.actions) {
101
+ if (!act || typeof act !== "object") continue;
102
+ const actObj = act;
103
+ const label = clip(actObj.label, 80);
104
+ const url = typeof actObj.url === "string" ? actObj.url.trim() : "";
105
+ if (!label || !url || !isSafeActionUrl(url)) continue;
106
+ actions.push({
107
+ label,
108
+ url,
109
+ primary: Boolean(actObj.primary)
110
+ });
111
+ if (actions.length >= 5) break;
112
+ }
113
+ }
114
+ let image = void 0;
115
+ if (c.image && typeof c.image === "object") {
116
+ const img = c.image;
117
+ const masterUrl = typeof img.master_url === "string" && isSafeActionUrl(img.master_url) ? img.master_url : void 0;
118
+ const url = typeof img.url === "string" && isSafeActionUrl(img.url) ? img.url : masterUrl;
119
+ if (url) {
120
+ image = {
121
+ file_id: clip(img.file_id, 64),
122
+ master_url: masterUrl,
123
+ url,
124
+ width: typeof img.width === "number" ? img.width : void 0,
125
+ height: typeof img.height === "number" ? img.height : void 0,
126
+ alt: clip(img.alt, 500),
127
+ caption: clip(img.caption, 1e3),
128
+ attribution: clip(img.attribution, 500),
129
+ label: clip(img.label, 100),
130
+ ai_generated: Boolean(img.ai_generated)
131
+ };
132
+ }
133
+ }
134
+ const verificationNote = clip(c.verification_note || c.verificationNote, 300) || "Verified by publication reporting staff";
135
+ const verifiedAt = clip(c.verified_at || c.verifiedAt, 100) || "October 2026";
136
+ let rating = void 0;
137
+ if (typeof c.rating === "number" || typeof c.rating === "string") {
138
+ rating = c.rating;
139
+ }
140
+ cards.push({
141
+ id,
142
+ cardType,
143
+ kicker,
144
+ title,
145
+ subtitle,
146
+ summary,
147
+ badge,
148
+ facts,
149
+ actions,
150
+ verificationNote,
151
+ verifiedAt,
152
+ image,
153
+ rating
154
+ });
155
+ if (cards.length >= 10) break;
156
+ }
157
+ return cards;
158
+ }
159
+ function VerifiedCardComponent({
160
+ card,
161
+ className
162
+ }) {
163
+ return /* @__PURE__ */ jsxRuntime.jsxs(
164
+ "aside",
165
+ {
166
+ className: `article-verified-card ${className || ""}`.trim(),
167
+ "aria-label": `Verified Factsheet: ${card.title}`,
168
+ children: [
169
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "article-verified-card__header", children: [
170
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__kicker", children: card.kicker }),
171
+ card.badge && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__badge", children: card.badge })
172
+ ] }),
173
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "article-verified-card__title", children: card.title }),
174
+ card.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "article-verified-card__subtitle", children: card.subtitle }),
175
+ card.summary && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "article-verified-card__summary", children: card.summary }),
176
+ 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: [
177
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "article-verified-card__fact-label", children: fact.label }),
178
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "article-verified-card__fact-value", children: fact.href ? /* @__PURE__ */ jsxRuntime.jsx(
179
+ "a",
180
+ {
181
+ className: "article-verified-card__fact-link",
182
+ href: fact.href,
183
+ rel: "noopener noreferrer",
184
+ target: fact.href.startsWith("http") ? "_blank" : void 0,
185
+ children: fact.value
186
+ }
187
+ ) : fact.value })
188
+ ] }, i)) }),
189
+ card.actions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "article-verified-card__actions", children: card.actions.map((act, i) => /* @__PURE__ */ jsxRuntime.jsxs(
190
+ "a",
191
+ {
192
+ className: `article-verified-card__action ${act.primary ? "article-verified-card__action--primary" : "article-verified-card__action--secondary"}`,
193
+ href: act.url,
194
+ target: "_blank",
195
+ rel: "noopener noreferrer",
196
+ children: [
197
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: act.label }),
198
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "article-verified-card__action-icon", children: "\u2197" })
199
+ ]
200
+ },
201
+ i
202
+ )) }),
203
+ /* @__PURE__ */ jsxRuntime.jsxs("footer", { className: "article-verified-card__footer", children: [
204
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "article-verified-card__seal", children: [
205
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__seal-check", "aria-hidden": "true", children: "\u2713" }),
206
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: card.verificationNote })
207
+ ] }),
208
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "article-verified-card__date", children: card.verifiedAt })
209
+ ] })
210
+ ]
211
+ }
212
+ );
213
+ }
214
+ function VerifiedCardSection({
215
+ cards,
216
+ className
217
+ }) {
218
+ if (!cards || cards.length === 0) return null;
219
+ return /* @__PURE__ */ jsxRuntime.jsx(
220
+ "section",
221
+ {
222
+ className: `article-verified-card-section ${className || ""}`.trim(),
223
+ "aria-label": "Verified Community Information",
224
+ children: cards.map((card) => /* @__PURE__ */ jsxRuntime.jsx(VerifiedCardComponent, { card }, card.id))
225
+ }
226
+ );
227
+ }
228
+
229
+ exports.VerifiedCardComponent = VerifiedCardComponent;
230
+ exports.VerifiedCardSection = VerifiedCardSection;
231
+ exports.getArticleCards = getArticleCards;
232
+ exports.isSafeActionUrl = isSafeActionUrl;
@@ -0,0 +1,22 @@
1
+ import * as react from 'react';
2
+ import { V as VerifiedCard } from './types-DFDCZHbS.cjs';
3
+
4
+ /**
5
+ * Validate that an action link is safe for readers.
6
+ * Rejects javascript:, data:, blob:, credentials, and loopback/internal hosts.
7
+ */
8
+ declare function isSafeActionUrl(raw: unknown): boolean;
9
+ /**
10
+ * Sanitize and project raw API metadata cards into strictly validated `VerifiedCard[]`.
11
+ */
12
+ declare function getArticleCards(rawCards?: unknown): VerifiedCard[];
13
+ declare function VerifiedCardComponent({ card, className, }: {
14
+ card: VerifiedCard;
15
+ className?: string;
16
+ }): react.JSX.Element;
17
+ declare function VerifiedCardSection({ cards, className, }: {
18
+ cards?: VerifiedCard[];
19
+ className?: string;
20
+ }): react.JSX.Element | null;
21
+
22
+ export { VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl };
@@ -0,0 +1,22 @@
1
+ import * as react from 'react';
2
+ import { V as VerifiedCard } from './types-DFDCZHbS.js';
3
+
4
+ /**
5
+ * Validate that an action link is safe for readers.
6
+ * Rejects javascript:, data:, blob:, credentials, and loopback/internal hosts.
7
+ */
8
+ declare function isSafeActionUrl(raw: unknown): boolean;
9
+ /**
10
+ * Sanitize and project raw API metadata cards into strictly validated `VerifiedCard[]`.
11
+ */
12
+ declare function getArticleCards(rawCards?: unknown): VerifiedCard[];
13
+ declare function VerifiedCardComponent({ card, className, }: {
14
+ card: VerifiedCard;
15
+ className?: string;
16
+ }): react.JSX.Element;
17
+ declare function VerifiedCardSection({ cards, className, }: {
18
+ cards?: VerifiedCard[];
19
+ className?: string;
20
+ }): react.JSX.Element | null;
21
+
22
+ export { VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl };
@@ -0,0 +1 @@
1
+ export { VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-I3ZTQCV3.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
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
  "main": "./dist/index.cjs",
@@ -69,13 +69,20 @@
69
69
  "types": "./dist/network-bar.d.ts",
70
70
  "import": "./dist/network-bar.js",
71
71
  "require": "./dist/network-bar.cjs"
72
- }
72
+ },
73
+ "./verified-card": {
74
+ "types": "./dist/verified-card.d.ts",
75
+ "import": "./dist/verified-card.js",
76
+ "require": "./dist/verified-card.cjs"
77
+ },
78
+ "./verified-card.css": "./verified-card.css"
73
79
  },
74
80
  "files": [
75
81
  "dist",
76
82
  "tokens",
77
83
  "marketplace.css",
78
84
  "cookie-consent.css",
85
+ "verified-card.css",
79
86
  "README.md",
80
87
  "LICENSE"
81
88
  ],