@wexio/messenger-widget-react 1.0.0
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/LICENSE +21 -0
- package/README.md +133 -0
- package/dist/widget-react-AgErOR36.js +325 -0
- package/dist/widget-react-AgErOR36.js.map +1 -0
- package/dist/widget-react-BAOIDvhV.js +4250 -0
- package/dist/widget-react-BAOIDvhV.js.map +1 -0
- package/dist/widget-react-BfPdgAQ-.js +5767 -0
- package/dist/widget-react-BfPdgAQ-.js.map +1 -0
- package/dist/widget-react-BxvW64LH.js +4429 -0
- package/dist/widget-react-BxvW64LH.js.map +1 -0
- package/dist/widget-react-C5COLy3m.js +23847 -0
- package/dist/widget-react-C5COLy3m.js.map +1 -0
- package/dist/widget-react-CAWxn8I0.js +247 -0
- package/dist/widget-react-CAWxn8I0.js.map +1 -0
- package/dist/widget-react-CDPzwV1i.js +11685 -0
- package/dist/widget-react-CDPzwV1i.js.map +1 -0
- package/dist/widget-react-CPYwD6FR.js +498 -0
- package/dist/widget-react-CPYwD6FR.js.map +1 -0
- package/dist/widget-react-CUBFPjaV.js +23 -0
- package/dist/widget-react-CUBFPjaV.js.map +1 -0
- package/dist/widget-react-CUuKGDZN.js +355 -0
- package/dist/widget-react-CUuKGDZN.js.map +1 -0
- package/dist/widget-react-CZs0xRrr.js +1336 -0
- package/dist/widget-react-CZs0xRrr.js.map +1 -0
- package/dist/widget-react-Cj_zbk_F.js +325 -0
- package/dist/widget-react-Cj_zbk_F.js.map +1 -0
- package/dist/widget-react-D-QrEVpv.js +125 -0
- package/dist/widget-react-D-QrEVpv.js.map +1 -0
- package/dist/widget-react-D8TPnM9r.js +26 -0
- package/dist/widget-react-D8TPnM9r.js.map +1 -0
- package/dist/widget-react-DE7AVrCo.js +5737 -0
- package/dist/widget-react-DE7AVrCo.js.map +1 -0
- package/dist/widget-react-DOX6j7_Q.js +30 -0
- package/dist/widget-react-DOX6j7_Q.js.map +1 -0
- package/dist/widget-react-DSw03RG7.js +262 -0
- package/dist/widget-react-DSw03RG7.js.map +1 -0
- package/dist/widget-react-Dew7YaLU.js +16 -0
- package/dist/widget-react-Dew7YaLU.js.map +1 -0
- package/dist/widget-react-DtOeNw5z.js +325 -0
- package/dist/widget-react-DtOeNw5z.js.map +1 -0
- package/dist/widget-react-DwL_rsx_.js +630 -0
- package/dist/widget-react-DwL_rsx_.js.map +1 -0
- package/dist/widget-react.d.ts +301 -0
- package/dist/widget-react.js +22285 -0
- package/dist/widget-react.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import "react";
|
|
2
|
+
import "react-dom";
|
|
3
|
+
import "react/jsx-runtime";
|
|
4
|
+
import "react-dom/client";
|
|
5
|
+
import { E as e, n as t, x as n } from "./widget-react-BfPdgAQ-.js";
|
|
6
|
+
import { a as r, c as i, i as a, l as o, n as s, o as c, r as l, t as u } from "./widget-react-CDPzwV1i.js";
|
|
7
|
+
import { i as d, n as f, o as p } from "./widget-react-BxvW64LH.js";
|
|
8
|
+
import { h as m, n as h, o as g, r as _, s as v } from "./widget-react-DwL_rsx_.js";
|
|
9
|
+
import { t as y } from "./widget-react-CUBFPjaV.js";
|
|
10
|
+
import { useState as b } from "react";
|
|
11
|
+
import { jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
12
|
+
//#region components/widget/news-article-view/helpers.ts
|
|
13
|
+
function C(e) {
|
|
14
|
+
if (!e || typeof e != "object") return null;
|
|
15
|
+
let t = e, n = String(t._id ?? t.id ?? "");
|
|
16
|
+
if (!n) return null;
|
|
17
|
+
let r = t.coverImageUrl?.url, i = t.externalCoverImageUrl, a = typeof r == "string" ? r : typeof i == "string" ? i : void 0, o = Array.isArray(t.tags) ? t.tags.map((e) => {
|
|
18
|
+
if (!e || typeof e != "object") return null;
|
|
19
|
+
let t = e.slug, n = e.label;
|
|
20
|
+
return typeof t != "string" || typeof n != "string" ? null : {
|
|
21
|
+
slug: t,
|
|
22
|
+
label: n
|
|
23
|
+
};
|
|
24
|
+
}).filter((e) => !!e) : void 0, s = (e) => {
|
|
25
|
+
if (!Array.isArray(e)) return;
|
|
26
|
+
let t = [];
|
|
27
|
+
for (let n of e) if (n) {
|
|
28
|
+
if (typeof n == "string") t.push(n);
|
|
29
|
+
else if (typeof n == "object") {
|
|
30
|
+
let e = n._id ?? n.id;
|
|
31
|
+
typeof e == "string" && t.push(e);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return t.length > 0 ? t : void 0;
|
|
35
|
+
}, c = Array.isArray(t.translations) ? t.translations.map((e) => {
|
|
36
|
+
if (!e || typeof e != "object") return null;
|
|
37
|
+
let t = String(e._id ?? e.id ?? ""), n = e.locale;
|
|
38
|
+
return !t || typeof n != "string" ? null : {
|
|
39
|
+
id: t,
|
|
40
|
+
locale: n
|
|
41
|
+
};
|
|
42
|
+
}).filter((e) => !!e) : void 0, l = Array.isArray(t.authors) ? t.authors.map((e) => {
|
|
43
|
+
if (!e || typeof e != "object") return null;
|
|
44
|
+
let t = e.name;
|
|
45
|
+
if (typeof t != "string") return null;
|
|
46
|
+
let n = e._id, r = e.photo?.url;
|
|
47
|
+
return {
|
|
48
|
+
_id: String(n ?? t),
|
|
49
|
+
name: t,
|
|
50
|
+
photo: { url: typeof r == "string" ? r : "" }
|
|
51
|
+
};
|
|
52
|
+
}).filter((e) => !!e) : void 0;
|
|
53
|
+
return {
|
|
54
|
+
id: n,
|
|
55
|
+
title: typeof t.title == "string" ? t.title : "",
|
|
56
|
+
excerpt: typeof t.excerpt == "string" ? t.excerpt : "",
|
|
57
|
+
contentMarkdown: typeof t.contentMarkdown == "string" ? t.contentMarkdown : void 0,
|
|
58
|
+
bodyFormat: typeof t.bodyFormat == "string" ? t.bodyFormat : void 0,
|
|
59
|
+
coverImageUrl: a,
|
|
60
|
+
publishedAt: typeof t.publishedAt == "string" ? t.publishedAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
61
|
+
upstreamUpdatedAt: typeof t.upstreamUpdatedAt == "string" ? t.upstreamUpdatedAt : void 0,
|
|
62
|
+
sourceUrl: typeof t.sourceUrl == "string" ? t.sourceUrl : void 0,
|
|
63
|
+
reactionCounts: t.reactionCounts ?? null,
|
|
64
|
+
viewerReaction: typeof t.viewerReaction == "number" ? t.viewerReaction : null,
|
|
65
|
+
tags: o,
|
|
66
|
+
authors: l,
|
|
67
|
+
categoryIds: s(t.categories),
|
|
68
|
+
tagIds: s(t.tags),
|
|
69
|
+
locale: typeof t.locale == "string" ? t.locale : void 0,
|
|
70
|
+
translations: c
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region components/widget/news-article-view/related-news-list.tsx
|
|
75
|
+
function w({ currentId: e, currentCategoryIds: t, currentTagIds: n, previewPostsById: r, visitorRelatedRaw: i, isDummy: a, onOpen: o }) {
|
|
76
|
+
let s = d("news"), c = (() => {
|
|
77
|
+
if (a) return [];
|
|
78
|
+
if (Array.isArray(i) && i.length > 0) return i.filter((e) => !!e && typeof e == "object").map((e) => {
|
|
79
|
+
let t = e.coverImageUrl?.url ?? e.externalCoverImageUrl;
|
|
80
|
+
return {
|
|
81
|
+
id: String(e._id ?? e.id ?? ""),
|
|
82
|
+
title: typeof e.title == "string" ? e.title : "",
|
|
83
|
+
coverImageUrl: typeof t == "string" ? t : void 0
|
|
84
|
+
};
|
|
85
|
+
}).filter((e) => e.id && e.title);
|
|
86
|
+
if (r && Object.keys(r).length > 0 && (t.length > 0 || n.length > 0)) {
|
|
87
|
+
let i = new Set(t), a = new Set(n), o = [];
|
|
88
|
+
for (let [t, n] of Object.entries(r)) {
|
|
89
|
+
if (t === e || !n || typeof n != "object") continue;
|
|
90
|
+
let r = n, s = Array.isArray(r.categories) ? r.categories : [], c = Array.isArray(r.tags) ? r.tags : [], l = (e) => {
|
|
91
|
+
let t = /* @__PURE__ */ new Set();
|
|
92
|
+
for (let n of e) if (n && typeof n == "object") {
|
|
93
|
+
let e = n._id ?? n.id;
|
|
94
|
+
typeof e == "string" && t.add(e);
|
|
95
|
+
}
|
|
96
|
+
return t;
|
|
97
|
+
}, u = l(s), d = l(c), f = 0;
|
|
98
|
+
for (let e of u) i.has(e) && f++;
|
|
99
|
+
for (let e of d) a.has(e) && f++;
|
|
100
|
+
if (f === 0) continue;
|
|
101
|
+
let p = r.coverImageUrl?.url ?? r.externalCoverImageUrl;
|
|
102
|
+
o.push({
|
|
103
|
+
id: t,
|
|
104
|
+
title: typeof r.title == "string" ? r.title : "",
|
|
105
|
+
coverImageUrl: typeof p == "string" ? p : void 0,
|
|
106
|
+
overlap: f,
|
|
107
|
+
publishedAt: typeof r.publishedAt == "string" ? r.publishedAt : ""
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return o.sort((e, t) => t.overlap === e.overlap ? t.publishedAt.localeCompare(e.publishedAt) : t.overlap - e.overlap), o.slice(0, 5).map(({ id: e, title: t, coverImageUrl: n }) => ({
|
|
111
|
+
id: e,
|
|
112
|
+
title: t,
|
|
113
|
+
coverImageUrl: n
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
return [];
|
|
117
|
+
})();
|
|
118
|
+
return c.length === 0 ? null : /* @__PURE__ */ S("section", {
|
|
119
|
+
className: "mt-6",
|
|
120
|
+
children: [/* @__PURE__ */ x("p", {
|
|
121
|
+
className: "mb-2 text-xs font-semibold tracking-wide text-wx-fg-muted uppercase",
|
|
122
|
+
children: s("relatedTitle")
|
|
123
|
+
}), /* @__PURE__ */ x("ul", {
|
|
124
|
+
className: "flex flex-col gap-2",
|
|
125
|
+
children: c.map((e) => /* @__PURE__ */ x("li", { children: /* @__PURE__ */ S("button", {
|
|
126
|
+
type: "button",
|
|
127
|
+
onClick: () => o(e.id),
|
|
128
|
+
className: "flex w-full items-center gap-3 rounded-wx-lg bg-wx-bg-elevated px-3 py-2 text-left transition-colors hover:bg-wx-bg-elevated/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-wx-primary",
|
|
129
|
+
children: [/* @__PURE__ */ x("span", {
|
|
130
|
+
className: "aspect-square w-12 shrink-0 rounded-wx-sm bg-cover bg-center",
|
|
131
|
+
style: e.coverImageUrl ? { backgroundImage: `url(${e.coverImageUrl})` } : { background: "linear-gradient(135deg, #1e1f21, #121314)" },
|
|
132
|
+
"aria-hidden": "true"
|
|
133
|
+
}), /* @__PURE__ */ x("span", {
|
|
134
|
+
className: "min-w-0 flex-1 truncate text-sm font-medium text-wx-fg",
|
|
135
|
+
children: e.title
|
|
136
|
+
})]
|
|
137
|
+
}) }, e.id))
|
|
138
|
+
})]
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region components/widget/news-article-view/index.tsx
|
|
143
|
+
function T({ id: T, isDummy: D, onOpenRelated: O, onTitleResolved: k }) {
|
|
144
|
+
let A = d("news"), j = d("demo"), M = p(), N = e(), { config: P } = n(), F = P.messenger?.showRelatedNews ?? !0, I = P.messenger?.showReactionCounts ?? !0, [L, R] = b(M), z = r(L, j), B = D ? null : N.previewData?.newsPostsById?.[T], V = !!B, { data: H, loading: U } = g({
|
|
145
|
+
variables: { args: {
|
|
146
|
+
id: T,
|
|
147
|
+
locale: M
|
|
148
|
+
} },
|
|
149
|
+
skip: D || V || !T
|
|
150
|
+
}), { data: W } = v({
|
|
151
|
+
variables: {
|
|
152
|
+
postId: T,
|
|
153
|
+
limit: 5,
|
|
154
|
+
locale: M
|
|
155
|
+
},
|
|
156
|
+
skip: D || V || !T || !F
|
|
157
|
+
}), G = D ? m(z).find((e) => e.id === T) ?? null : null, K = G ? {
|
|
158
|
+
id: G.id,
|
|
159
|
+
title: G.title,
|
|
160
|
+
excerpt: G.excerpt,
|
|
161
|
+
contentMarkdown: G.contentMarkdown,
|
|
162
|
+
bodyFormat: "markdown",
|
|
163
|
+
coverImageUrl: G.coverImageUrl,
|
|
164
|
+
coverGradient: G.coverGradient,
|
|
165
|
+
publishedAt: G.publishedAt,
|
|
166
|
+
upstreamUpdatedAt: G.upstreamUpdatedAt,
|
|
167
|
+
tags: G.tags,
|
|
168
|
+
authors: G.authors,
|
|
169
|
+
category: G.category,
|
|
170
|
+
locale: L
|
|
171
|
+
} : C(V ? B : H?.visitorNewsPost);
|
|
172
|
+
if (c(K?.title, k), !K) return U ? /* @__PURE__ */ x(E, {}) : /* @__PURE__ */ x("div", {
|
|
173
|
+
className: "flex flex-1 items-center justify-center px-6 py-12 text-center",
|
|
174
|
+
children: /* @__PURE__ */ x("p", {
|
|
175
|
+
className: "text-sm text-wx-fg-muted",
|
|
176
|
+
children: A("articleNotFound")
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
let q = K.coverGradient ?? ["#1e1f21", "#121314"];
|
|
180
|
+
return /* @__PURE__ */ x(t, {
|
|
181
|
+
className: "flex-1",
|
|
182
|
+
children: /* @__PURE__ */ S("div", {
|
|
183
|
+
className: "flex flex-col",
|
|
184
|
+
children: [/* @__PURE__ */ x("div", {
|
|
185
|
+
className: f("relative w-full"),
|
|
186
|
+
style: {
|
|
187
|
+
aspectRatio: "16 / 9",
|
|
188
|
+
background: K.coverImageUrl ? `url(${K.coverImageUrl}) center/cover` : `linear-gradient(135deg, ${q[0]}, ${q[1]})`
|
|
189
|
+
},
|
|
190
|
+
"aria-hidden": "true"
|
|
191
|
+
}), /* @__PURE__ */ S("div", {
|
|
192
|
+
className: "flex flex-col gap-3 px-5 pt-5 pb-12",
|
|
193
|
+
children: [
|
|
194
|
+
K.category && /* @__PURE__ */ x("p", {
|
|
195
|
+
className: "text-[11px] font-semibold tracking-[0.14em] text-wx-primary uppercase",
|
|
196
|
+
children: K.category.label
|
|
197
|
+
}),
|
|
198
|
+
/* @__PURE__ */ S("p", {
|
|
199
|
+
className: "flex items-center gap-2 text-xs text-wx-fg-subtle",
|
|
200
|
+
children: [/* @__PURE__ */ x("span", { children: new Date(K.publishedAt).toLocaleDateString(M, {
|
|
201
|
+
month: "long",
|
|
202
|
+
day: "numeric",
|
|
203
|
+
year: "numeric"
|
|
204
|
+
}) }), _(K) && K.upstreamUpdatedAt && /* @__PURE__ */ x("span", {
|
|
205
|
+
className: "rounded-full bg-wx-bg-elevated px-1.5 py-0.5 text-[10px] font-medium text-wx-fg-muted",
|
|
206
|
+
title: new Date(K.upstreamUpdatedAt).toLocaleString(M),
|
|
207
|
+
children: A("editedAt", { when: h(K.upstreamUpdatedAt) })
|
|
208
|
+
})]
|
|
209
|
+
}),
|
|
210
|
+
/* @__PURE__ */ x("h2", {
|
|
211
|
+
className: "text-2xl leading-tight font-bold text-wx-fg",
|
|
212
|
+
children: K.title
|
|
213
|
+
}),
|
|
214
|
+
K.authors && K.authors.length > 0 && /* @__PURE__ */ S("p", {
|
|
215
|
+
className: "flex items-center gap-2 text-xs text-wx-fg-muted",
|
|
216
|
+
children: [/* @__PURE__ */ x("span", {
|
|
217
|
+
className: "flex -space-x-1.5",
|
|
218
|
+
children: K.authors.slice(0, 3).map((e) => /* @__PURE__ */ x("span", {
|
|
219
|
+
className: "h-5 w-5 overflow-hidden rounded-full bg-wx-bg-elevated ring-1 ring-wx-bg",
|
|
220
|
+
"aria-hidden": "true",
|
|
221
|
+
children: /* @__PURE__ */ x("img", {
|
|
222
|
+
src: e.photo.url,
|
|
223
|
+
alt: "",
|
|
224
|
+
className: "h-full w-full object-cover"
|
|
225
|
+
})
|
|
226
|
+
}, e._id))
|
|
227
|
+
}), /* @__PURE__ */ x("span", { children: A("writtenBy", { names: K.authors.map((e) => e.name).join(", ") }) })]
|
|
228
|
+
}),
|
|
229
|
+
K.tags && K.tags.length > 0 && /* @__PURE__ */ x("div", {
|
|
230
|
+
className: "flex flex-wrap gap-1",
|
|
231
|
+
children: K.tags.map((e) => /* @__PURE__ */ x("span", {
|
|
232
|
+
className: "rounded-full bg-wx-bg-elevated px-2 py-0.5 text-[11px] font-medium text-wx-fg-muted",
|
|
233
|
+
children: e.label
|
|
234
|
+
}, e.slug))
|
|
235
|
+
}),
|
|
236
|
+
K.excerpt && /* @__PURE__ */ x("p", {
|
|
237
|
+
className: "text-base leading-relaxed text-wx-fg-muted",
|
|
238
|
+
children: K.excerpt
|
|
239
|
+
}),
|
|
240
|
+
K.contentMarkdown && /* @__PURE__ */ x(l, {
|
|
241
|
+
content: K.contentMarkdown,
|
|
242
|
+
isHtml: K.bodyFormat === "html" || o(K.contentMarkdown),
|
|
243
|
+
markdownMarginClass: "mt-3"
|
|
244
|
+
}),
|
|
245
|
+
K.sourceUrl && /* @__PURE__ */ S("a", {
|
|
246
|
+
href: K.sourceUrl,
|
|
247
|
+
target: "_blank",
|
|
248
|
+
rel: "noopener noreferrer",
|
|
249
|
+
className: "mt-4 flex items-center justify-center gap-1.5 text-xs text-wx-primary underline underline-offset-2 transition-opacity hover:opacity-80",
|
|
250
|
+
children: [/* @__PURE__ */ x(y, { size: 12 }), /* @__PURE__ */ x("span", { children: A("readOriginal") })]
|
|
251
|
+
}),
|
|
252
|
+
F && /* @__PURE__ */ x(w, {
|
|
253
|
+
currentId: K.id,
|
|
254
|
+
currentCategoryIds: K.categoryIds ?? [],
|
|
255
|
+
currentTagIds: K.tagIds ?? [],
|
|
256
|
+
previewPostsById: D ? void 0 : N.previewData?.newsPostsById,
|
|
257
|
+
visitorRelatedRaw: W?.visitorNewsRelated,
|
|
258
|
+
isDummy: D,
|
|
259
|
+
onOpen: O ?? (() => {})
|
|
260
|
+
}),
|
|
261
|
+
K.id && /* @__PURE__ */ x(u, {
|
|
262
|
+
surface: i.NEWS,
|
|
263
|
+
itemId: K.id,
|
|
264
|
+
reactionCounts: K.reactionCounts ?? null,
|
|
265
|
+
viewerReaction: K.viewerReaction ?? null,
|
|
266
|
+
groupItemIds: [K.id, ...(K.translations ?? []).map((e) => e.id)],
|
|
267
|
+
isDummy: D,
|
|
268
|
+
showCounts: I,
|
|
269
|
+
className: "mt-6"
|
|
270
|
+
}),
|
|
271
|
+
/* @__PURE__ */ x(s, {
|
|
272
|
+
currentLocale: D ? L : K.locale ?? M,
|
|
273
|
+
locales: D ? [...a] : (K.translations ?? []).map((e) => e.locale),
|
|
274
|
+
onSelect: D ? (e) => R(e) : (e) => {
|
|
275
|
+
let t = (K.translations ?? []).find((t) => t.locale === e);
|
|
276
|
+
t && O?.(t.id);
|
|
277
|
+
},
|
|
278
|
+
className: "mt-6"
|
|
279
|
+
})
|
|
280
|
+
]
|
|
281
|
+
})]
|
|
282
|
+
})
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function E() {
|
|
286
|
+
return /* @__PURE__ */ x(t, {
|
|
287
|
+
className: "flex-1",
|
|
288
|
+
children: /* @__PURE__ */ S("div", {
|
|
289
|
+
className: "flex flex-col",
|
|
290
|
+
"aria-busy": "true",
|
|
291
|
+
"aria-live": "polite",
|
|
292
|
+
children: [/* @__PURE__ */ x("div", {
|
|
293
|
+
className: "w-full animate-pulse bg-wx-bg-elevated",
|
|
294
|
+
style: { aspectRatio: "16 / 9" }
|
|
295
|
+
}), /* @__PURE__ */ S("div", {
|
|
296
|
+
className: "px-5 pt-5 pb-12",
|
|
297
|
+
children: [
|
|
298
|
+
/* @__PURE__ */ S("div", {
|
|
299
|
+
className: "animate-pulse space-y-2.5",
|
|
300
|
+
children: [/* @__PURE__ */ x("div", { className: "h-5 w-3/4 rounded-md bg-wx-bg-elevated" }), /* @__PURE__ */ x("div", { className: "h-5 w-1/2 rounded-md bg-wx-bg-elevated" })]
|
|
301
|
+
}),
|
|
302
|
+
/* @__PURE__ */ x("div", {
|
|
303
|
+
className: "mt-4 flex items-center gap-2 animate-pulse",
|
|
304
|
+
children: /* @__PURE__ */ x("div", { className: "h-3 w-24 rounded-md bg-wx-bg-elevated" })
|
|
305
|
+
}),
|
|
306
|
+
/* @__PURE__ */ x("div", {
|
|
307
|
+
className: "mt-6 space-y-2.5 animate-pulse",
|
|
308
|
+
children: [
|
|
309
|
+
"w-full",
|
|
310
|
+
"w-11/12",
|
|
311
|
+
"w-10/12",
|
|
312
|
+
"w-full",
|
|
313
|
+
"w-9/12",
|
|
314
|
+
"w-7/12"
|
|
315
|
+
].map((e, t) => /* @__PURE__ */ x("div", { className: f("h-3 rounded-md bg-wx-bg-elevated", e) }, t))
|
|
316
|
+
})
|
|
317
|
+
]
|
|
318
|
+
})]
|
|
319
|
+
})
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
export { T as NewsArticleView };
|
|
324
|
+
|
|
325
|
+
//# sourceMappingURL=widget-react-DtOeNw5z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget-react-DtOeNw5z.js","names":[],"sources":["../components/widget/news-article-view/helpers.ts","../components/widget/news-article-view/related-news-list.tsx","../components/widget/news-article-view/index.tsx"],"sourcesContent":["/**\n * Normalised news-post projection + the extractor that produces it.\n * The same shape is rendered whether the source is mock data, the\n * dashboard's prefetch, or the visitor `visitorNewsPost({ id })` query.\n */\nexport interface NewsArticle {\n id: string;\n title: string;\n excerpt: string;\n contentMarkdown?: string;\n bodyFormat?: \"html\" | \"markdown\" | string;\n coverImageUrl?: string;\n coverGradient?: [string, string];\n publishedAt: string;\n upstreamUpdatedAt?: string | null;\n sourceUrl?: string;\n reactionCounts?: Record<string, number> | null;\n /** The visitor's own reaction slotIndex on this post's group (or null).\n * Drives the reaction-bar active highlight; from the BE read query. */\n viewerReaction?: number | null;\n tags?: { slug: string; label: string }[];\n /** Used by client-side \"related\" computation in preview mode — the\n * BE's `visitorNewsRelated` query handles this in real mode. We\n * project the category id list so the preview can do an overlap\n * count without hitting the visitor surface. */\n categoryIds?: string[];\n tagIds?: string[];\n /** Post byline — one or more authors (demo content; live news posts\n * may not carry authors). */\n authors?: { _id: string; name: string; photo: { url: string } }[];\n /** Section label shown as an eyebrow above the title. */\n category?: { slug: string; label: string };\n /** Locale of THIS post — drives the active chip in the language\n * selector. */\n locale?: string;\n /** Published locale variants (incl. self). The widget opens a\n * variant by `id` (the visitor news lookup is id-keyed, so locale\n * doesn't need to travel with it). */\n translations?: { id: string; locale: string }[];\n}\n\n/** Coerce the BE's raw post JSON (or an operator-side post row) into\n * the `NewsArticle` shape. Defensive on every field — the same\n * extractor handles `_id`-vs-`id` and the populated-Media cover vs\n * the externally-pulled URL fallback. */\nexport function extractArticle(raw: unknown): NewsArticle | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const id = String(o._id ?? o.id ?? \"\");\n if (!id) return null;\n const mediaCover = (o.coverImageUrl as Record<string, unknown> | undefined)?.url;\n const externalCover = o.externalCoverImageUrl;\n const cover =\n typeof mediaCover === \"string\"\n ? mediaCover\n : typeof externalCover === \"string\"\n ? externalCover\n : undefined;\n const tags = Array.isArray(o.tags)\n ? (o.tags as unknown[])\n .map((t) => {\n if (!t || typeof t !== \"object\") return null;\n const slug = (t as { slug?: unknown }).slug;\n const label = (t as { label?: unknown }).label;\n if (typeof slug !== \"string\" || typeof label !== \"string\") {\n return null;\n }\n return { slug, label };\n })\n .filter((t): t is { slug: string; label: string } => !!t)\n : undefined;\n const idsFromObjList = (list: unknown): string[] | undefined => {\n if (!Array.isArray(list)) return undefined;\n const out: string[] = [];\n for (const x of list) {\n if (!x) continue;\n if (typeof x === \"string\") {\n out.push(x);\n } else if (typeof x === \"object\") {\n const xid = (x as { _id?: unknown; id?: unknown })._id ?? (x as { id?: unknown }).id;\n if (typeof xid === \"string\") out.push(xid);\n }\n }\n return out.length > 0 ? out : undefined;\n };\n // Published locale variants → `{ id, locale }`. The widget opens a\n // variant by id, so we only need those two fields.\n const translations = Array.isArray(o.translations)\n ? (o.translations as unknown[])\n .map((tr) => {\n if (!tr || typeof tr !== \"object\") return null;\n const tid = String(\n (tr as { _id?: unknown; id?: unknown })._id ?? (tr as { id?: unknown }).id ?? \"\",\n );\n const loc = (tr as { locale?: unknown }).locale;\n if (!tid || typeof loc !== \"string\") return null;\n return { id: tid, locale: loc };\n })\n .filter((tr): tr is { id: string; locale: string } => !!tr)\n : undefined;\n // Authors (operator byline) — `{ _id, name, photo: { url } }`. BE\n // posts may carry 0..N; demo content sets its own. The article view\n // renders up to 3 avatars + a \"Written by …\" line.\n const authors = Array.isArray(o.authors)\n ? (o.authors as unknown[])\n .map((a) => {\n if (!a || typeof a !== \"object\") return null;\n const name = (a as { name?: unknown }).name;\n if (typeof name !== \"string\") return null;\n const aid = (a as { _id?: unknown })._id;\n const url = (a as { photo?: { url?: unknown } }).photo?.url;\n return {\n _id: String(aid ?? name),\n name,\n photo: { url: typeof url === \"string\" ? url : \"\" },\n };\n })\n .filter((a): a is { _id: string; name: string; photo: { url: string } } => !!a)\n : undefined;\n return {\n id,\n title: typeof o.title === \"string\" ? o.title : \"\",\n excerpt: typeof o.excerpt === \"string\" ? o.excerpt : \"\",\n contentMarkdown: typeof o.contentMarkdown === \"string\" ? o.contentMarkdown : undefined,\n bodyFormat: typeof o.bodyFormat === \"string\" ? o.bodyFormat : undefined,\n coverImageUrl: cover,\n publishedAt: typeof o.publishedAt === \"string\" ? o.publishedAt : new Date().toISOString(),\n upstreamUpdatedAt: typeof o.upstreamUpdatedAt === \"string\" ? o.upstreamUpdatedAt : undefined,\n sourceUrl: typeof o.sourceUrl === \"string\" ? o.sourceUrl : undefined,\n reactionCounts: (o.reactionCounts as Record<string, number> | null | undefined) ?? null,\n viewerReaction: typeof o.viewerReaction === \"number\" ? o.viewerReaction : null,\n tags,\n authors,\n categoryIds: idsFromObjList(o.categories),\n tagIds: idsFromObjList(o.tags),\n locale: typeof o.locale === \"string\" ? o.locale : undefined,\n translations,\n };\n}\n","\"use client\";\n\nimport { useTranslations } from \"next-intl\";\n\n/**\n * Related-articles list rendered at the foot of the article detail\n * view. Two data paths:\n * - **Real mode** → `visitorNewsRelated` (BE pre-sorted by\n * shared-taxonomy overlap, then `publishedAt` desc).\n * - **Preview mode** → client-side overlap pass against\n * `env.previewData.newsPostsById` so the dashboard mirrors what\n * visitors would see without a BE round-trip.\n * Dummy / preview-without-overlap render nothing.\n */\nexport function RelatedNewsList({\n currentId,\n currentCategoryIds,\n currentTagIds,\n previewPostsById,\n visitorRelatedRaw,\n isDummy,\n onOpen,\n}: {\n currentId: string;\n currentCategoryIds: string[];\n currentTagIds: string[];\n previewPostsById: Record<string, unknown> | undefined;\n visitorRelatedRaw: unknown;\n isDummy: boolean;\n onOpen: (id: string) => void;\n}) {\n const t = useTranslations(\"news\");\n\n const items: { id: string; title: string; coverImageUrl?: string }[] = (() => {\n if (isDummy) return [];\n\n // BE result wins when it's a non-empty array. The visitor surface's\n // `visitorNewsRelated` is the authoritative source — we prefer it\n // over the client-side overlap fallback even when both are reachable\n // (i.e. dashboard preview pane with prefetch AND a successful\n // visitor-side fetch).\n if (Array.isArray(visitorRelatedRaw) && visitorRelatedRaw.length > 0) {\n return visitorRelatedRaw\n .filter((x): x is Record<string, unknown> => !!x && typeof x === \"object\")\n .map((r) => {\n const cover =\n (r.coverImageUrl as Record<string, unknown> | undefined)?.url ??\n r.externalCoverImageUrl;\n return {\n id: String(r._id ?? r.id ?? \"\"),\n title: typeof r.title === \"string\" ? r.title : \"\",\n coverImageUrl: typeof cover === \"string\" ? cover : undefined,\n };\n })\n .filter((r) => r.id && r.title);\n }\n\n // Fallback: client-side overlap against the dashboard's prefetched\n // lookup. Skipped when the lookup is missing / empty, or when the\n // seed article has no taxonomy refs.\n if (\n previewPostsById &&\n Object.keys(previewPostsById).length > 0 &&\n (currentCategoryIds.length > 0 || currentTagIds.length > 0)\n ) {\n const seedCats = new Set(currentCategoryIds);\n const seedTags = new Set(currentTagIds);\n const scored: {\n id: string;\n title: string;\n coverImageUrl?: string;\n overlap: number;\n publishedAt: string;\n }[] = [];\n for (const [pid, raw] of Object.entries(previewPostsById)) {\n if (pid === currentId) continue;\n if (!raw || typeof raw !== \"object\") continue;\n const r = raw as Record<string, unknown>;\n const cats = Array.isArray(r.categories) ? r.categories : [];\n const tagsArr = Array.isArray(r.tags) ? r.tags : [];\n const idsFrom = (arr: unknown[]): Set<string> => {\n const s = new Set<string>();\n for (const x of arr) {\n if (x && typeof x === \"object\") {\n const xid = (x as { _id?: unknown; id?: unknown })._id ?? (x as { id?: unknown }).id;\n if (typeof xid === \"string\") s.add(xid);\n }\n }\n return s;\n };\n const catIds = idsFrom(cats);\n const tagIds = idsFrom(tagsArr);\n let overlap = 0;\n for (const c of catIds) if (seedCats.has(c)) overlap++;\n for (const ti of tagIds) if (seedTags.has(ti)) overlap++;\n if (overlap === 0) continue;\n const cover =\n (r.coverImageUrl as Record<string, unknown> | undefined)?.url ?? r.externalCoverImageUrl;\n scored.push({\n id: pid,\n title: typeof r.title === \"string\" ? r.title : \"\",\n coverImageUrl: typeof cover === \"string\" ? cover : undefined,\n overlap,\n publishedAt: typeof r.publishedAt === \"string\" ? r.publishedAt : \"\",\n });\n }\n scored.sort((a, b) =>\n b.overlap !== a.overlap\n ? b.overlap - a.overlap\n : b.publishedAt.localeCompare(a.publishedAt),\n );\n return scored\n .slice(0, 5)\n .map(({ id, title, coverImageUrl }) => ({ id, title, coverImageUrl }));\n }\n\n return [];\n })();\n\n if (items.length === 0) return null;\n\n return (\n <section className=\"mt-6\">\n <p className=\"mb-2 text-xs font-semibold tracking-wide text-wx-fg-muted uppercase\">\n {t(\"relatedTitle\")}\n </p>\n <ul className=\"flex flex-col gap-2\">\n {items.map((item) => (\n <li key={item.id}>\n <button\n type=\"button\"\n onClick={() => onOpen(item.id)}\n className=\"flex w-full items-center gap-3 rounded-wx-lg bg-wx-bg-elevated px-3 py-2 text-left transition-colors hover:bg-wx-bg-elevated/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-wx-primary\"\n >\n <span\n className=\"aspect-square w-12 shrink-0 rounded-wx-sm bg-cover bg-center\"\n style={\n item.coverImageUrl\n ? { backgroundImage: `url(${item.coverImageUrl})` }\n : { background: \"linear-gradient(135deg, #1e1f21, #121314)\" }\n }\n aria-hidden=\"true\"\n />\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-wx-fg\">\n {item.title}\n </span>\n </button>\n </li>\n ))}\n </ul>\n </section>\n );\n}\n","\"use client\";\n\nimport { ExternalLink } from \"lucide-react\";\nimport { useLocale, useTranslations } from \"next-intl\";\nimport { useState } from \"react\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { getMockNewsItems } from \"@/dummy\";\nimport { looksLikeHtml } from \"@/lib/detect-html\";\nimport { ReactionSurface } from \"@/lib/graphql/generated/types\";\nimport {\n useVisitorNewsPostQuery,\n useVisitorNewsRelatedQuery,\n} from \"@/lib/graphql/queries/generated/news.generated\";\nimport { useArticleTitleSink } from \"@/lib/use-article-title-sink\";\nimport { DEMO_LOCALES, useDemoTranslator } from \"@/lib/use-demo-translator\";\nimport { useWidgetConfig } from \"@/lib/use-widget-config\";\nimport { useWidgetEnv } from \"@/lib/use-widget-env\";\nimport { cn } from \"@/lib/utils\";\nimport { ArticleBody } from \"../article-body\";\nimport { LanguageSelector } from \"../language-selector\";\nimport { ReactionBar } from \"../reaction-bar\";\nimport { formatNewsEditedAgo, isNewsPostEdited } from \"../tabs/news-tab\";\nimport { extractArticle, type NewsArticle } from \"./helpers\";\nimport { RelatedNewsList } from \"./related-news-list\";\n\ninterface NewsArticleViewProps {\n id: string;\n isDummy: boolean;\n /** Tap a related-article tile → swap the view to that post. The\n * shell wires this to the same `news-article` route swap the news\n * card click already uses, so back-navigation lands on the feed. */\n onOpenRelated?: (id: string) => void;\n /** Title sink for the widget shell's sticky header. Called with\n * the article title once data resolves, and with `null` on\n * unmount to clear the header back to the parent tab's name. */\n onTitleResolved?: (title: string | null) => void;\n}\n\n/**\n * News post detail view. Hydration order:\n * 1. **Dummy mode** → match `getMockNewsItems()` by id.\n * 2. **Preview mode** → use `env.previewData.newsPostsById[id]` (the\n * dashboard prefetched the full post via its operator GraphQL).\n * 3. **Live mode** → call `useVisitorNewsPostByIdQuery`.\n *\n * Body renders via `react-markdown` when `bodyFormat` is markdown (or\n * unset — markdown is the default). HTML-format posts route through\n * `dangerouslySetInnerHTML` since the BE ingest already sanitises them.\n *\n * Back navigation lives on the shell's `TabTitleHeader` — this\n * component just owns the scroll surface.\n */\nexport function NewsArticleView({\n id,\n isDummy,\n onOpenRelated,\n onTitleResolved,\n}: NewsArticleViewProps) {\n const t = useTranslations(\"news\");\n const tDemo = useTranslations(\"demo\");\n const locale = useLocale();\n const env = useWidgetEnv();\n const { config } = useWidgetConfig();\n const showRelated = config.messenger?.showRelatedNews ?? true;\n const showReactionCounts = config.messenger?.showReactionCounts ?? true;\n\n // Article-scoped language switch in demo mode — defaults to the widget\n // locale, changed by the below-article dropdown without touching the\n // widget's own locale.\n const [demoLocale, setDemoLocale] = useState(locale);\n const demoT = useDemoTranslator(demoLocale, tDemo);\n\n // Preview takes priority over the live query — the dashboard has\n // already loaded the full post, so we skip the visitor round-trip.\n const previewRaw = !isDummy ? env.previewData?.newsPostsById?.[id] : null;\n const hasPreview = !!previewRaw;\n\n const { data, loading } = useVisitorNewsPostQuery({\n variables: { args: { id, locale } },\n skip: isDummy || hasPreview || !id,\n });\n\n // Related posts — visitor surface in real mode. Preview mode falls\n // back to a client-side overlap pass against the prefetched feed\n // (BE-equivalent: shared category-or-tag id count, then publishedAt\n // desc as tiebreaker) so the dashboard pane mirrors what visitors\n // would see.\n const { data: relatedData } = useVisitorNewsRelatedQuery({\n // Pass the widget's active locale (not the article's) so related\n // follows the reader's language — fixes wrong-language related when\n // reading a fallback (default-locale) article.\n variables: { postId: id, limit: 5, locale },\n skip: isDummy || hasPreview || !id || !showRelated,\n });\n\n const mockArticle = isDummy ? (getMockNewsItems(demoT).find((n) => n.id === id) ?? null) : null;\n\n const article: NewsArticle | null = mockArticle\n ? {\n id: mockArticle.id,\n title: mockArticle.title,\n excerpt: mockArticle.excerpt,\n contentMarkdown: mockArticle.contentMarkdown,\n // `bodyFormat` stays markdown; HTML demo bodies are auto-detected\n // by `looksLikeHtml` below and routed through `ArticleHtml`.\n bodyFormat: \"markdown\",\n coverImageUrl: mockArticle.coverImageUrl,\n coverGradient: mockArticle.coverGradient,\n publishedAt: mockArticle.publishedAt,\n upstreamUpdatedAt: mockArticle.upstreamUpdatedAt,\n tags: mockArticle.tags,\n authors: mockArticle.authors,\n category: mockArticle.category,\n // Demo: article shown in the selected demo locale.\n locale: demoLocale,\n }\n : hasPreview\n ? extractArticle(previewRaw)\n : extractArticle(data?.visitorNewsPost);\n\n // Surface the title up to the widget shell's sticky header (swaps\n // \"News\" → article name). See `useArticleTitleSink` for why the\n // mount/unmount ownership is the single source of truth.\n useArticleTitleSink(article?.title, onTitleResolved);\n\n if (!article) {\n if (loading) {\n // Article-shaped skeleton — see `NewsArticleSkeleton` below\n // for the structure. Same `animate-pulse` opacity-only loop\n // as the help-article skeleton so layouts stay consistent.\n return <NewsArticleSkeleton />;\n }\n return (\n <div className=\"flex flex-1 items-center justify-center px-6 py-12 text-center\">\n <p className=\"text-sm text-wx-fg-muted\">{t(\"articleNotFound\")}</p>\n </div>\n );\n }\n\n const fallbackGradient: [string, string] = article.coverGradient ?? [\"#1e1f21\", \"#121314\"];\n\n return (\n <ScrollArea className=\"flex-1\">\n {/* Plain `<div>` — the widget shell's route slide already\n covers the enter animation. Stacking a second opacity+y\n keyframe on top thrashed the main thread during article\n navigation: panel width animating + route sliding + body\n fading + Apollo query rendering, all on the same frame.\n The route slide alone is enough and visibly snappier. */}\n <div className=\"flex flex-col\">\n <div\n className={cn(\"relative w-full\")}\n style={{\n aspectRatio: \"16 / 9\",\n background: article.coverImageUrl\n ? `url(${article.coverImageUrl}) center/cover`\n : `linear-gradient(135deg, ${fallbackGradient[0]}, ${fallbackGradient[1]})`,\n }}\n aria-hidden=\"true\"\n />\n <div className=\"flex flex-col gap-3 px-5 pt-5 pb-12\">\n {article.category && (\n <p className=\"text-[11px] font-semibold tracking-[0.14em] text-wx-primary uppercase\">\n {article.category.label}\n </p>\n )}\n <p className=\"flex items-center gap-2 text-xs text-wx-fg-subtle\">\n <span>\n {new Date(article.publishedAt).toLocaleDateString(locale, {\n month: \"long\",\n day: \"numeric\",\n year: \"numeric\",\n })}\n </span>\n {isNewsPostEdited(article) && article.upstreamUpdatedAt && (\n <span\n className=\"rounded-full bg-wx-bg-elevated px-1.5 py-0.5 text-[10px] font-medium text-wx-fg-muted\"\n title={new Date(article.upstreamUpdatedAt).toLocaleString(locale)}\n >\n {t(\"editedAt\", {\n when: formatNewsEditedAgo(article.upstreamUpdatedAt),\n })}\n </span>\n )}\n </p>\n <h2 className=\"text-2xl leading-tight font-bold text-wx-fg\">{article.title}</h2>\n {article.authors && article.authors.length > 0 && (\n <p className=\"flex items-center gap-2 text-xs text-wx-fg-muted\">\n <span className=\"flex -space-x-1.5\">\n {article.authors.slice(0, 3).map((a) => (\n <span\n key={a._id}\n className=\"h-5 w-5 overflow-hidden rounded-full bg-wx-bg-elevated ring-1 ring-wx-bg\"\n aria-hidden=\"true\"\n >\n {/* biome-ignore lint/performance/noImgElement: tiny avatar, no LCP impact */}\n <img src={a.photo.url} alt=\"\" className=\"h-full w-full object-cover\" />\n </span>\n ))}\n </span>\n <span>\n {t(\"writtenBy\", { names: article.authors.map((a) => a.name).join(\", \") })}\n </span>\n </p>\n )}\n {article.tags && article.tags.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {article.tags.map((tag) => (\n <span\n key={tag.slug}\n className=\"rounded-full bg-wx-bg-elevated px-2 py-0.5 text-[11px] font-medium text-wx-fg-muted\"\n >\n {tag.label}\n </span>\n ))}\n </div>\n )}\n {article.excerpt && (\n <p className=\"text-base leading-relaxed text-wx-fg-muted\">{article.excerpt}</p>\n )}\n {article.contentMarkdown && (\n <ArticleBody\n content={article.contentMarkdown}\n // BE-stamped `bodyFormat` wins when set explicitly to\n // `html` (RSS `<content:encoded>`), otherwise we sniff\n // the content — TipTap-authored native posts save with\n // the default `markdown` despite producing HTML.\n isHtml={article.bodyFormat === \"html\" || looksLikeHtml(article.contentMarkdown)}\n markdownMarginClass=\"mt-3\"\n />\n )}\n {article.sourceUrl && (\n <a\n href={article.sourceUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mt-4 flex items-center justify-center gap-1.5 text-xs text-wx-primary underline underline-offset-2 transition-opacity hover:opacity-80\"\n >\n <ExternalLink size={12} />\n <span>{t(\"readOriginal\")}</span>\n </a>\n )}\n\n {showRelated && (\n <RelatedNewsList\n currentId={article.id}\n currentCategoryIds={article.categoryIds ?? []}\n currentTagIds={article.tagIds ?? []}\n previewPostsById={!isDummy ? env.previewData?.newsPostsById : undefined}\n visitorRelatedRaw={relatedData?.visitorNewsRelated}\n isDummy={isDummy}\n onOpen={onOpenRelated ?? (() => {})}\n />\n )}\n\n {/* Reactions bar — unified surface; operator-curated emoji\n palette via the News reactions settings. Defaults seed\n to 🎉 / ❤️ / 👍 NEUTRAL so the row never renders empty\n before an operator customises. */}\n {article.id && (\n <ReactionBar\n surface={ReactionSurface.NEWS}\n itemId={article.id}\n reactionCounts={article.reactionCounts ?? null}\n viewerReaction={article.viewerReaction ?? null}\n // All locale variants of this post — a reaction is per-group,\n // so keep their cached counts/highlight in sync.\n groupItemIds={[article.id, ...(article.translations ?? []).map((tr) => tr.id)]}\n isDummy={isDummy}\n showCounts={showReactionCounts}\n className=\"mt-6\"\n />\n )}\n\n {/* Language switcher — in demo it swaps the post content in\n place (article-scoped); in live mode it opens the chosen\n variant by id. Hidden when the post exists in one language. */}\n <LanguageSelector\n currentLocale={isDummy ? demoLocale : (article.locale ?? locale)}\n locales={\n isDummy ? [...DEMO_LOCALES] : (article.translations ?? []).map((tr) => tr.locale)\n }\n onSelect={\n isDummy\n ? (loc) => setDemoLocale(loc)\n : (loc) => {\n const target = (article.translations ?? []).find((tr) => tr.locale === loc);\n if (target) onOpenRelated?.(target.id);\n }\n }\n className=\"mt-6\"\n />\n </div>\n </div>\n </ScrollArea>\n );\n}\n\n/**\n * Loading state for the news article view. Mirrors the real\n * article's geometry: a 16:9 cover block, then a 2-line title and\n * body lines, all `animate-pulse`-driven (opacity only, no layout\n * thrash). Sits inside a `ScrollArea` so the surrounding shell's\n * scrollbar / chrome doesn't shift between skeleton and real\n * content.\n */\nfunction NewsArticleSkeleton() {\n return (\n <ScrollArea className=\"flex-1\">\n <div className=\"flex flex-col\" aria-busy=\"true\" aria-live=\"polite\">\n {/* Cover block — 16:9 to match the actual hero image. */}\n <div className=\"w-full animate-pulse bg-wx-bg-elevated\" style={{ aspectRatio: \"16 / 9\" }} />\n <div className=\"px-5 pt-5 pb-12\">\n {/* Title — two lines of varying width. */}\n <div className=\"animate-pulse space-y-2.5\">\n <div className=\"h-5 w-3/4 rounded-md bg-wx-bg-elevated\" />\n <div className=\"h-5 w-1/2 rounded-md bg-wx-bg-elevated\" />\n </div>\n {/* Date / source line. */}\n <div className=\"mt-4 flex items-center gap-2 animate-pulse\">\n <div className=\"h-3 w-24 rounded-md bg-wx-bg-elevated\" />\n </div>\n {/* Body — six lines of varying length for the paragraph\n cadence the article will paint into. */}\n <div className=\"mt-6 space-y-2.5 animate-pulse\">\n {[\"w-full\", \"w-11/12\", \"w-10/12\", \"w-full\", \"w-9/12\", \"w-7/12\"].map((w, i) => (\n // biome-ignore lint/suspicious/noArrayIndexKey: static order-stable skeleton with duplicate width values — index is the only stable key\n <div key={i} className={cn(\"h-3 rounded-md bg-wx-bg-elevated\", w)} />\n ))}\n </div>\n </div>\n </div>\n </ScrollArea>\n );\n}\n"],"mappings":"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"}
|