@burdenoff/microfe-theculturepost 2026.802.1 → 2026.830.1
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/dist/theculturepost/components/CitationList.d.ts +2 -1
- package/dist/theculturepost/components/CitationList.d.ts.map +1 -1
- package/dist/theculturepost/components/CitationList.js +114 -95
- package/dist/theculturepost/components/CitationList.js.map +1 -1
- package/dist/theculturepost/components/SourceScanLink.d.ts +25 -0
- package/dist/theculturepost/components/SourceScanLink.d.ts.map +1 -0
- package/dist/theculturepost/components/SourceScanLink.js +36 -0
- package/dist/theculturepost/components/SourceScanLink.js.map +1 -0
- package/dist/theculturepost/components/index.d.ts +1 -0
- package/dist/theculturepost/components/index.d.ts.map +1 -1
- package/dist/theculturepost/components/index.js +1 -0
- package/dist/theculturepost/hooks/useTr.js +2 -2
- package/dist/theculturepost/hooks/useTr.js.map +1 -1
- package/dist/theculturepost/pages/HomePage.d.ts.map +1 -1
- package/dist/theculturepost/pages/HomePage.js +145 -127
- package/dist/theculturepost/pages/HomePage.js.map +1 -1
- package/dist/theculturepost/pages/governance/GovernancePage.d.ts +3 -1
- package/dist/theculturepost/pages/governance/GovernancePage.d.ts.map +1 -1
- package/dist/theculturepost/pages/governance/GovernancePage.js +122 -99
- package/dist/theculturepost/pages/governance/GovernancePage.js.map +1 -1
- package/dist/theculturepost/pages/stories/StoryDetailPage.d.ts +3 -0
- package/dist/theculturepost/pages/stories/StoryDetailPage.d.ts.map +1 -1
- package/dist/theculturepost/pages/stories/StoryDetailPage.js +202 -132
- package/dist/theculturepost/pages/stories/StoryDetailPage.js.map +1 -1
- package/dist/theculturepost/translations/ar.d.ts +43 -0
- package/dist/theculturepost/translations/ar.d.ts.map +1 -1
- package/dist/theculturepost/translations/ar.js +43 -0
- package/dist/theculturepost/translations/ar.js.map +1 -1
- package/dist/theculturepost/translations/en.d.ts +43 -0
- package/dist/theculturepost/translations/en.d.ts.map +1 -1
- package/dist/theculturepost/translations/en.js +43 -0
- package/dist/theculturepost/translations/en.js.map +1 -1
- package/dist/theculturepost/translations/es.d.ts +43 -0
- package/dist/theculturepost/translations/es.d.ts.map +1 -1
- package/dist/theculturepost/translations/es.js +43 -0
- package/dist/theculturepost/translations/es.js.map +1 -1
- package/dist/theculturepost/translations/hi.d.ts +43 -0
- package/dist/theculturepost/translations/hi.d.ts.map +1 -1
- package/dist/theculturepost/translations/hi.js +43 -0
- package/dist/theculturepost/translations/hi.js.map +1 -1
- package/dist/theculturepost/translations/ta.d.ts +43 -0
- package/dist/theculturepost/translations/ta.d.ts.map +1 -1
- package/dist/theculturepost/translations/ta.js +43 -0
- package/dist/theculturepost/translations/ta.js.map +1 -1
- package/dist/theculturepost/translations/zh.d.ts +43 -0
- package/dist/theculturepost/translations/zh.d.ts.map +1 -1
- package/dist/theculturepost/translations/zh.js +43 -0
- package/dist/theculturepost/translations/zh.js.map +1 -1
- package/package.json +2 -2
|
@@ -21,7 +21,8 @@ interface CitationListProps {
|
|
|
21
21
|
storyId: string;
|
|
22
22
|
/** Narrow reading-rail rendering: smaller cards, icon-only segments, no legend. */
|
|
23
23
|
compact?: boolean;
|
|
24
|
+
className?: string;
|
|
24
25
|
}
|
|
25
|
-
export declare function CitationList({ storyId, compact }: CitationListProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare function CitationList({ storyId, compact, className }: CitationListProps): import("react/jsx-runtime").JSX.Element;
|
|
26
27
|
export {};
|
|
27
28
|
//# sourceMappingURL=CitationList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CitationList.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/CitationList.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAiBL,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"CitationList.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/CitationList.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAiBL,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAWtB,OAAO,KAAK,EAAe,aAAa,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAMlF,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,aAAa,EAAE,UAAU,CAS9D,CAAC;AAEF,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAE9D;AAED,KAAK,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAE9E,UAAU,qBAAqB;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,CAyBlF,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,aAAa,EAAE,qBAAqB,EAKhD,CAAC;AAiPF,UAAU,iBAAiB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,OAAe,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CAiCtF"}
|
|
@@ -6,68 +6,69 @@ import { useTcpCitations as o, useTcpVerifyCitation as s } from "../hooks/useTcp
|
|
|
6
6
|
import { describeError as c, notify as l } from "../lib/toast.js";
|
|
7
7
|
import "../hooks/index.js";
|
|
8
8
|
import { EmptyState as u } from "./EmptyState.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
9
|
+
import { SourceScanLink as d } from "./SourceScanLink.js";
|
|
10
|
+
import { createElement as f } from "react";
|
|
11
|
+
import { AlertCircle as p, BookMarked as m, BookOpen as h, Box as g, CheckCircle2 as _, ExternalLink as v, FileText as y, Globe as b, GraduationCap as x, HelpCircle as S, Landmark as C, Mic as w, Newspaper as T, Quote as E, ShieldCheck as D, XCircle as O } from "lucide-react";
|
|
12
|
+
import { jsx as k, jsxs as A } from "react/jsx-runtime";
|
|
13
|
+
import { Spinner as j } from "@burdenoff/fe-libs/ui";
|
|
13
14
|
//#region src/theculturepost/components/CitationList.tsx
|
|
14
|
-
var
|
|
15
|
-
ACADEMIC:
|
|
16
|
-
ARCHIVAL_RECORD:
|
|
17
|
-
ARTIFACT:
|
|
18
|
-
BOOK:
|
|
19
|
-
INTERVIEW:
|
|
20
|
-
PERIODICAL:
|
|
21
|
-
PRIMARY_DOCUMENT:
|
|
22
|
-
WEB:
|
|
15
|
+
var M = {
|
|
16
|
+
ACADEMIC: x,
|
|
17
|
+
ARCHIVAL_RECORD: C,
|
|
18
|
+
ARTIFACT: g,
|
|
19
|
+
BOOK: h,
|
|
20
|
+
INTERVIEW: w,
|
|
21
|
+
PERIODICAL: T,
|
|
22
|
+
PRIMARY_DOCUMENT: y,
|
|
23
|
+
WEB: b
|
|
23
24
|
};
|
|
24
|
-
function
|
|
25
|
-
return
|
|
25
|
+
function N(e) {
|
|
26
|
+
return M[e] ?? y;
|
|
26
27
|
}
|
|
27
|
-
var
|
|
28
|
+
var P = {
|
|
28
29
|
CORROBORATED: {
|
|
29
|
-
icon:
|
|
30
|
+
icon: _,
|
|
30
31
|
accent: "success",
|
|
31
32
|
label: "Corroborated",
|
|
32
33
|
blurb: "Strong agreement among reliable sources."
|
|
33
34
|
},
|
|
34
35
|
DISPUTED: {
|
|
35
|
-
icon:
|
|
36
|
+
icon: p,
|
|
36
37
|
accent: "warning",
|
|
37
38
|
label: "Disputed",
|
|
38
39
|
blurb: "Conflicting or insufficient evidence."
|
|
39
40
|
},
|
|
40
41
|
RETRACTED: {
|
|
41
|
-
icon:
|
|
42
|
+
icon: O,
|
|
42
43
|
accent: "destructive",
|
|
43
44
|
label: "Retracted",
|
|
44
45
|
blurb: "The source has since been withdrawn."
|
|
45
46
|
},
|
|
46
47
|
UNVERIFIED: {
|
|
47
|
-
icon:
|
|
48
|
+
icon: S,
|
|
48
49
|
accent: "secondary",
|
|
49
50
|
label: "Unverified",
|
|
50
51
|
blurb: "Not yet reviewed by an editor."
|
|
51
52
|
}
|
|
52
|
-
},
|
|
53
|
+
}, F = [
|
|
53
54
|
"CORROBORATED",
|
|
54
55
|
"DISPUTED",
|
|
55
56
|
"RETRACTED",
|
|
56
57
|
"UNVERIFIED"
|
|
57
|
-
],
|
|
58
|
+
], I = {
|
|
58
59
|
success: "bg-status-success-bg-subtle text-status-success-text shadow-elevation-1",
|
|
59
60
|
warning: "bg-status-warning-bg-subtle text-status-warning-text shadow-elevation-1",
|
|
60
61
|
destructive: "bg-status-error-bg-subtle text-status-error-text shadow-elevation-1",
|
|
61
62
|
secondary: "bg-bg-sunken text-text-primary shadow-elevation-1"
|
|
62
63
|
};
|
|
63
|
-
function
|
|
64
|
-
return /* @__PURE__ */
|
|
64
|
+
function L({ value: t, onChange: n, pending: r = !1, compact: i = !1, className: a }) {
|
|
65
|
+
return /* @__PURE__ */ k("div", {
|
|
65
66
|
role: "radiogroup",
|
|
66
67
|
"aria-label": "Verification status",
|
|
67
68
|
className: e("grid grid-cols-2 gap-1 rounded-xl border border-border-seam bg-bg-surface p-1", i ? "grid-cols-4" : "sm:inline-grid sm:auto-cols-max sm:grid-flow-col", a),
|
|
68
|
-
children:
|
|
69
|
-
let o =
|
|
70
|
-
return /* @__PURE__ */
|
|
69
|
+
children: F.map((a) => {
|
|
70
|
+
let o = P[a], s = o.icon, c = a === t;
|
|
71
|
+
return /* @__PURE__ */ A("button", {
|
|
71
72
|
type: "button",
|
|
72
73
|
role: "radio",
|
|
73
74
|
"aria-checked": c,
|
|
@@ -75,11 +76,11 @@ function I({ value: t, onChange: n, pending: r = !1, compact: i = !1, className:
|
|
|
75
76
|
title: o.label,
|
|
76
77
|
disabled: r,
|
|
77
78
|
onClick: () => n(a),
|
|
78
|
-
className: e("inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors disabled:opacity-60", c ?
|
|
79
|
-
children: [/* @__PURE__ */
|
|
79
|
+
className: e("inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors disabled:opacity-60", c ? I[o.accent] : "text-text-tertiary hover:bg-bg-sunken hover:text-text-primary"),
|
|
80
|
+
children: [/* @__PURE__ */ k(s, {
|
|
80
81
|
className: "h-3.5 w-3.5 shrink-0",
|
|
81
82
|
"aria-hidden": "true"
|
|
82
|
-
}), /* @__PURE__ */
|
|
83
|
+
}), /* @__PURE__ */ k("span", {
|
|
83
84
|
className: i ? "sr-only" : "hidden sm:inline",
|
|
84
85
|
children: o.label
|
|
85
86
|
})]
|
|
@@ -87,7 +88,7 @@ function I({ value: t, onChange: n, pending: r = !1, compact: i = !1, className:
|
|
|
87
88
|
})
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
|
-
function
|
|
91
|
+
function R({ citation: t, compact: n }) {
|
|
91
92
|
let o = s(), u = async (e) => {
|
|
92
93
|
if (e !== t.verification) try {
|
|
93
94
|
await o.mutateAsync({
|
|
@@ -97,48 +98,48 @@ function L({ citation: t, compact: n }) {
|
|
|
97
98
|
} catch (e) {
|
|
98
99
|
l.error("Could not update verification", c(e));
|
|
99
100
|
}
|
|
100
|
-
},
|
|
101
|
+
}, p = [
|
|
101
102
|
t.author,
|
|
102
103
|
t.publisher,
|
|
103
104
|
r(t.publishedOn)
|
|
104
105
|
].filter((e) => e && e !== "—").join(" · ");
|
|
105
|
-
return /* @__PURE__ */
|
|
106
|
+
return /* @__PURE__ */ A("li", {
|
|
106
107
|
className: e("rounded-2xl border border-border-seam bg-bg-surface transition-colors hover:border-border-strong", n ? "p-3.5" : "p-4 sm:p-5"),
|
|
107
|
-
children: [/* @__PURE__ */
|
|
108
|
+
children: [/* @__PURE__ */ A("div", {
|
|
108
109
|
className: e("flex gap-3", n ? "items-start" : "sm:gap-4"),
|
|
109
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ k("span", {
|
|
110
111
|
className: e("grid shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary", n ? "h-9 w-9" : "h-12 w-12"),
|
|
111
112
|
"aria-hidden": "true",
|
|
112
|
-
children:
|
|
113
|
-
}), /* @__PURE__ */
|
|
113
|
+
children: f(N(t.sourceType), { className: n ? "h-4 w-4" : "h-6 w-6" })
|
|
114
|
+
}), /* @__PURE__ */ A("div", {
|
|
114
115
|
className: "min-w-0 flex-1",
|
|
115
116
|
children: [
|
|
116
|
-
/* @__PURE__ */
|
|
117
|
+
/* @__PURE__ */ A("div", {
|
|
117
118
|
className: "mb-1 flex flex-wrap items-center gap-1.5",
|
|
118
|
-
children: [/* @__PURE__ */
|
|
119
|
+
children: [/* @__PURE__ */ k(a, {
|
|
119
120
|
status: t.sourceType,
|
|
120
|
-
icon:
|
|
121
|
-
}), n && /* @__PURE__ */
|
|
121
|
+
icon: f(N(t.sourceType), { className: "h-3 w-3" })
|
|
122
|
+
}), n && /* @__PURE__ */ k(a, { status: t.verification })]
|
|
122
123
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ k("p", {
|
|
124
125
|
className: e("font-display font-semibold leading-snug text-text-primary", n ? "text-sm" : "text-base"),
|
|
125
126
|
children: t.title
|
|
126
127
|
}),
|
|
127
|
-
|
|
128
|
+
p && /* @__PURE__ */ k("p", {
|
|
128
129
|
className: "mt-0.5 text-xs text-text-tertiary",
|
|
129
|
-
children:
|
|
130
|
+
children: p
|
|
130
131
|
}),
|
|
131
|
-
t.excerpt && !n && /* @__PURE__ */
|
|
132
|
+
t.excerpt && !n && /* @__PURE__ */ A("blockquote", {
|
|
132
133
|
className: "mt-3 flex gap-2 rounded-lg bg-bg-sunken/60 px-3 py-2 text-sm text-text-secondary",
|
|
133
|
-
children: [/* @__PURE__ */
|
|
134
|
+
children: [/* @__PURE__ */ k(E, {
|
|
134
135
|
className: "h-3.5 w-3.5 shrink-0 text-text-tertiary",
|
|
135
136
|
"aria-hidden": "true"
|
|
136
|
-
}), /* @__PURE__ */
|
|
137
|
+
}), /* @__PURE__ */ k("span", { children: t.excerpt })]
|
|
137
138
|
}),
|
|
138
|
-
t.provenance && !n && /* @__PURE__ */
|
|
139
|
+
t.provenance && !n && /* @__PURE__ */ A("p", {
|
|
139
140
|
className: "mt-2 text-xs text-text-tertiary",
|
|
140
141
|
children: [
|
|
141
|
-
/* @__PURE__ */
|
|
142
|
+
/* @__PURE__ */ k("span", {
|
|
142
143
|
className: "font-medium text-text-secondary",
|
|
143
144
|
children: "Provenance:"
|
|
144
145
|
}),
|
|
@@ -146,34 +147,40 @@ function L({ citation: t, compact: n }) {
|
|
|
146
147
|
t.provenance
|
|
147
148
|
]
|
|
148
149
|
}),
|
|
149
|
-
t.url && /* @__PURE__ */
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
className: "
|
|
156
|
-
children:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
150
|
+
(t.url || t.sourceFile) && /* @__PURE__ */ A("div", {
|
|
151
|
+
className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1",
|
|
152
|
+
children: [t.url && /* @__PURE__ */ A("a", {
|
|
153
|
+
href: t.url,
|
|
154
|
+
target: "_blank",
|
|
155
|
+
rel: "noreferrer noopener",
|
|
156
|
+
className: "inline-flex max-w-full items-center gap-1 text-xs font-medium text-accent hover:underline",
|
|
157
|
+
children: [/* @__PURE__ */ k("span", {
|
|
158
|
+
className: "truncate",
|
|
159
|
+
children: n ? "View source" : t.url
|
|
160
|
+
}), /* @__PURE__ */ k(v, {
|
|
161
|
+
className: "h-3 w-3 shrink-0",
|
|
162
|
+
"aria-hidden": "true"
|
|
163
|
+
})]
|
|
164
|
+
}), /* @__PURE__ */ k(d, {
|
|
165
|
+
fileId: t.sourceFile?.id,
|
|
166
|
+
label: n ? "View scan" : "View source scan"
|
|
160
167
|
})]
|
|
161
168
|
})
|
|
162
169
|
]
|
|
163
170
|
})]
|
|
164
|
-
}), /* @__PURE__ */
|
|
171
|
+
}), /* @__PURE__ */ A("div", {
|
|
165
172
|
className: e("mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-border-seam pt-3", n ? "" : "sm:justify-between"),
|
|
166
|
-
children: [/* @__PURE__ */
|
|
173
|
+
children: [/* @__PURE__ */ A("span", {
|
|
167
174
|
className: "inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary",
|
|
168
175
|
children: [
|
|
169
|
-
/* @__PURE__ */
|
|
176
|
+
/* @__PURE__ */ k(D, {
|
|
170
177
|
className: "h-3.5 w-3.5 text-text-tertiary",
|
|
171
178
|
"aria-hidden": "true"
|
|
172
179
|
}),
|
|
173
180
|
"Verification",
|
|
174
|
-
o.isPending && /* @__PURE__ */
|
|
181
|
+
o.isPending && /* @__PURE__ */ k(j, { size: "sm" })
|
|
175
182
|
]
|
|
176
|
-
}), /* @__PURE__ */
|
|
183
|
+
}), /* @__PURE__ */ k(L, {
|
|
177
184
|
value: t.verification,
|
|
178
185
|
onChange: u,
|
|
179
186
|
pending: o.isPending,
|
|
@@ -182,32 +189,32 @@ function L({ citation: t, compact: n }) {
|
|
|
182
189
|
})]
|
|
183
190
|
});
|
|
184
191
|
}
|
|
185
|
-
function
|
|
186
|
-
return /* @__PURE__ */
|
|
192
|
+
function z() {
|
|
193
|
+
return /* @__PURE__ */ A("div", {
|
|
187
194
|
className: "mt-4 rounded-2xl border border-border-seam bg-bg-sunken/50 p-4 sm:p-5",
|
|
188
|
-
children: [/* @__PURE__ */
|
|
195
|
+
children: [/* @__PURE__ */ A("div", {
|
|
189
196
|
className: "flex items-start gap-3",
|
|
190
|
-
children: [/* @__PURE__ */
|
|
197
|
+
children: [/* @__PURE__ */ k("span", {
|
|
191
198
|
className: "grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary",
|
|
192
199
|
"aria-hidden": "true",
|
|
193
|
-
children: /* @__PURE__ */
|
|
194
|
-
}), /* @__PURE__ */
|
|
200
|
+
children: /* @__PURE__ */ k(D, { className: "h-5 w-5" })
|
|
201
|
+
}), /* @__PURE__ */ k("p", {
|
|
195
202
|
className: "text-sm text-text-secondary",
|
|
196
203
|
children: "Every source is evaluated for accuracy, relevance, and cultural sensitivity through a transparent, multi-step review by editors and subject-area contributors."
|
|
197
204
|
})]
|
|
198
|
-
}), /* @__PURE__ */
|
|
205
|
+
}), /* @__PURE__ */ k("dl", {
|
|
199
206
|
className: "mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4",
|
|
200
|
-
children:
|
|
201
|
-
let n =
|
|
202
|
-
return /* @__PURE__ */
|
|
207
|
+
children: F.map((t) => {
|
|
208
|
+
let n = P[t], r = n.icon;
|
|
209
|
+
return /* @__PURE__ */ A("div", {
|
|
203
210
|
className: "flex items-start gap-2",
|
|
204
|
-
children: [/* @__PURE__ */
|
|
211
|
+
children: [/* @__PURE__ */ k(r, {
|
|
205
212
|
className: e("mt-0.5 h-4 w-4 shrink-0", n.accent === "success" && "text-status-success-text", n.accent === "warning" && "text-status-warning-text", n.accent === "destructive" && "text-status-error-text", n.accent === "secondary" && "text-text-tertiary"),
|
|
206
213
|
"aria-hidden": "true"
|
|
207
|
-
}), /* @__PURE__ */
|
|
214
|
+
}), /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ k("dt", {
|
|
208
215
|
className: "text-xs font-semibold text-text-primary",
|
|
209
216
|
children: n.label
|
|
210
|
-
}), /* @__PURE__ */
|
|
217
|
+
}), /* @__PURE__ */ k("dd", {
|
|
211
218
|
className: "mt-0.5 text-xs text-text-tertiary",
|
|
212
219
|
children: n.blurb
|
|
213
220
|
})] })]
|
|
@@ -216,24 +223,36 @@ function R() {
|
|
|
216
223
|
})]
|
|
217
224
|
});
|
|
218
225
|
}
|
|
219
|
-
function
|
|
220
|
-
let { data:
|
|
221
|
-
return
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}) :
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
226
|
+
function B({ storyId: r, compact: i = !1, className: a }) {
|
|
227
|
+
let { data: s, isLoading: c, isError: l, refetch: d } = o(r);
|
|
228
|
+
return c ? /* @__PURE__ */ k(n, {
|
|
229
|
+
label: "Loading citations…",
|
|
230
|
+
className: a
|
|
231
|
+
}) : l ? /* @__PURE__ */ k("div", {
|
|
232
|
+
className: a,
|
|
233
|
+
children: /* @__PURE__ */ k(t, {
|
|
234
|
+
message: "We couldn't load citations.",
|
|
235
|
+
onRetry: () => d()
|
|
236
|
+
})
|
|
237
|
+
}) : !s || s.length === 0 ? /* @__PURE__ */ k("div", {
|
|
238
|
+
className: a,
|
|
239
|
+
children: /* @__PURE__ */ k(u, {
|
|
240
|
+
icon: /* @__PURE__ */ k(m, { className: "h-9 w-9" }),
|
|
241
|
+
title: "No citations yet",
|
|
242
|
+
description: "Add verifiable sources to ground this story in the record."
|
|
243
|
+
})
|
|
244
|
+
}) : /* @__PURE__ */ A("div", {
|
|
245
|
+
className: a,
|
|
246
|
+
children: [/* @__PURE__ */ k("ul", {
|
|
247
|
+
className: e("flex flex-col", i ? "gap-3" : "gap-3 sm:gap-4"),
|
|
248
|
+
children: s.map((e) => /* @__PURE__ */ k(R, {
|
|
249
|
+
citation: e,
|
|
250
|
+
compact: i
|
|
251
|
+
}, e.id))
|
|
252
|
+
}), !i && /* @__PURE__ */ k(z, {})]
|
|
253
|
+
});
|
|
235
254
|
}
|
|
236
255
|
//#endregion
|
|
237
|
-
export {
|
|
256
|
+
export { B as CitationList, F as VERIFICATIONS, P as VERIFICATION_META, N as sourceTypeIcon };
|
|
238
257
|
|
|
239
258
|
//# sourceMappingURL=CitationList.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CitationList.js","names":[],"sources":["../../../src/theculturepost/components/CitationList.tsx"],"sourcesContent":["/**\n * CitationList — the verifiable sources that ground a story (tcp-05).\n * @module theculturepost/components\n *\n * Reads `tcpListCitations(storyId)`; each entry can be moved through its\n * verification state (`tcpVerifyCitation`) via a **segmented control** — the\n * archival-integrity control at the heart of a serious cultural publication.\n *\n * Each source shows a source-type icon, its bibliographic detail, an optional\n * excerpt + link, and (in the full variant) closes with a static methodology\n * legend. There is deliberately NO \"evidence trail\" mapping citations to\n * individual claims — the backend exposes no such linkage, so we never fake it.\n *\n * Two layouts, same data:\n * - default (full): roomy cards + labelled segments + methodology legend.\n * - `compact`: narrow-rail cards + icon-only segments, no legend.\n */\n\nimport {\n BookOpen,\n GraduationCap,\n Landmark,\n FileText,\n Globe,\n Newspaper,\n Mic,\n Box,\n ExternalLink,\n Quote,\n ShieldCheck,\n CheckCircle2,\n AlertCircle,\n XCircle,\n HelpCircle,\n BookMarked,\n type LucideIcon,\n} from 'lucide-react';\nimport { createElement } from 'react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { LoadingView, ErrorView } from './StateViews';\nimport { EmptyState } from './EmptyState';\nimport { useTcpCitations, useTcpVerifyCitation } from '../hooks';\nimport { cn } from '../utils/cn';\nimport { formatDate, formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpCitation, TcpSourceType, TcpVerificationStatus } from '../types';\n\n// ---------------------------------------------------------------------------\n// Source-type + verification metadata (exported for reuse in the editorial table)\n// ---------------------------------------------------------------------------\n\n/** A distinct lucide motif per source type (icon, not hue — per the design rules). */\nexport const SOURCE_TYPE_ICON: Record<TcpSourceType, LucideIcon> = {\n ACADEMIC: GraduationCap,\n ARCHIVAL_RECORD: Landmark,\n ARTIFACT: Box,\n BOOK: BookOpen,\n INTERVIEW: Mic,\n PERIODICAL: Newspaper,\n PRIMARY_DOCUMENT: FileText,\n WEB: Globe,\n};\n\n/** Resolve a source-type's motif icon, falling back to a neutral document. */\nexport function sourceTypeIcon(type: TcpSourceType): LucideIcon {\n return SOURCE_TYPE_ICON[type] ?? FileText;\n}\n\ntype VerificationAccent = 'success' | 'warning' | 'destructive' | 'secondary';\n\ninterface VerificationMetaEntry {\n icon: LucideIcon;\n accent: VerificationAccent;\n label: string;\n /** Static, factual description of what the state means — shown in the legend. */\n blurb: string;\n}\n\n/** Ordered, tone-mapped metadata for each verification state. */\nexport const VERIFICATION_META: Record<TcpVerificationStatus, VerificationMetaEntry> = {\n CORROBORATED: {\n icon: CheckCircle2,\n accent: 'success',\n label: 'Corroborated',\n blurb: 'Strong agreement among reliable sources.',\n },\n DISPUTED: {\n icon: AlertCircle,\n accent: 'warning',\n label: 'Disputed',\n blurb: 'Conflicting or insufficient evidence.',\n },\n RETRACTED: {\n icon: XCircle,\n accent: 'destructive',\n label: 'Retracted',\n blurb: 'The source has since been withdrawn.',\n },\n UNVERIFIED: {\n icon: HelpCircle,\n accent: 'secondary',\n label: 'Unverified',\n blurb: 'Not yet reviewed by an editor.',\n },\n};\n\n/** The order the segmented control presents states in. */\nexport const VERIFICATIONS: TcpVerificationStatus[] = [\n 'CORROBORATED',\n 'DISPUTED',\n 'RETRACTED',\n 'UNVERIFIED',\n];\n\n/** Active-segment styling per accent — semantic tokens only. */\nconst SEGMENT_ACTIVE: Record<VerificationAccent, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text shadow-elevation-1',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text shadow-elevation-1',\n destructive: 'bg-status-error-bg-subtle text-status-error-text shadow-elevation-1',\n secondary: 'bg-bg-sunken text-text-primary shadow-elevation-1',\n};\n\n// ---------------------------------------------------------------------------\n// Segmented verification control\n// ---------------------------------------------------------------------------\n\ninterface VerificationSegmentsProps {\n value: TcpVerificationStatus;\n onChange: (value: TcpVerificationStatus) => void;\n pending?: boolean;\n /** Icon-only segments (for the narrow reading rail). */\n compact?: boolean;\n className?: string;\n}\n\nfunction VerificationSegments({\n value,\n onChange,\n pending = false,\n compact = false,\n className,\n}: VerificationSegmentsProps) {\n return (\n <div\n role=\"radiogroup\"\n aria-label=\"Verification status\"\n className={cn(\n 'grid grid-cols-2 gap-1 rounded-xl border border-border-seam bg-bg-surface p-1',\n compact ? 'grid-cols-4' : 'sm:inline-grid sm:auto-cols-max sm:grid-flow-col',\n className\n )}\n >\n {VERIFICATIONS.map((v) => {\n const meta = VERIFICATION_META[v];\n const Icon = meta.icon;\n const active = v === value;\n return (\n <button\n key={v}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n aria-label={meta.label}\n title={meta.label}\n disabled={pending}\n onClick={() => onChange(v)}\n className={cn(\n 'inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors disabled:opacity-60',\n active\n ? SEGMENT_ACTIVE[meta.accent]\n : 'text-text-tertiary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n <Icon className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className={compact ? 'sr-only' : 'hidden sm:inline'}>{meta.label}</span>\n </button>\n );\n })}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Citation row\n// ---------------------------------------------------------------------------\n\nfunction CitationRow({ citation, compact }: { citation: TcpCitation; compact: boolean }) {\n const verify = useTcpVerifyCitation();\n\n const changeVerification = async (verification: TcpVerificationStatus) => {\n if (verification === citation.verification) return;\n try {\n await verify.mutateAsync({ id: citation.id, verification });\n notify.success('Verification updated', formatEnumLabel(verification));\n } catch (err) {\n notify.error('Could not update verification', describeError(err));\n }\n };\n\n const meta = [citation.author, citation.publisher, formatDate(citation.publishedOn)]\n .filter((v) => v && v !== '—')\n .join(' · ');\n\n return (\n <li\n className={cn(\n 'rounded-2xl border border-border-seam bg-bg-surface transition-colors hover:border-border-strong',\n compact ? 'p-3.5' : 'p-4 sm:p-5'\n )}\n >\n <div className={cn('flex gap-3', compact ? 'items-start' : 'sm:gap-4')}>\n {/* Source-type motif tile */}\n <span\n className={cn(\n 'grid shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary',\n compact ? 'h-9 w-9' : 'h-12 w-12'\n )}\n aria-hidden=\"true\"\n >\n {createElement(sourceTypeIcon(citation.sourceType), {\n className: compact ? 'h-4 w-4' : 'h-6 w-6',\n })}\n </span>\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"mb-1 flex flex-wrap items-center gap-1.5\">\n <StatusBadge\n status={citation.sourceType}\n icon={createElement(sourceTypeIcon(citation.sourceType), { className: 'h-3 w-3' })}\n />\n {compact && <StatusBadge status={citation.verification} />}\n </div>\n\n <p\n className={cn(\n 'font-display font-semibold leading-snug text-text-primary',\n compact ? 'text-sm' : 'text-base'\n )}\n >\n {citation.title}\n </p>\n {meta && <p className=\"mt-0.5 text-xs text-text-tertiary\">{meta}</p>}\n\n {citation.excerpt && !compact && (\n <blockquote className=\"mt-3 flex gap-2 rounded-lg bg-bg-sunken/60 px-3 py-2 text-sm text-text-secondary\">\n <Quote className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary\" aria-hidden=\"true\" />\n <span>{citation.excerpt}</span>\n </blockquote>\n )}\n\n {citation.provenance && !compact && (\n <p className=\"mt-2 text-xs text-text-tertiary\">\n <span className=\"font-medium text-text-secondary\">Provenance:</span>{' '}\n {citation.provenance}\n </p>\n )}\n\n {citation.url && (\n <a\n href={citation.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"mt-2 inline-flex max-w-full items-center gap-1 text-xs font-medium text-accent hover:underline\"\n >\n <span className=\"truncate\">{compact ? 'View source' : citation.url}</span>\n <ExternalLink className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" />\n </a>\n )}\n </div>\n </div>\n\n {/* Verification control */}\n <div\n className={cn(\n 'mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-border-seam pt-3',\n compact ? '' : 'sm:justify-between'\n )}\n >\n <span className=\"inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary\">\n <ShieldCheck className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden=\"true\" />\n Verification\n {verify.isPending && <Spinner size=\"sm\" />}\n </span>\n <VerificationSegments\n value={citation.verification}\n onChange={changeVerification}\n pending={verify.isPending}\n compact={compact}\n />\n </div>\n </li>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Methodology legend (static — no fabricated data)\n// ---------------------------------------------------------------------------\n\nfunction MethodologyLegend() {\n return (\n <div className=\"mt-4 rounded-2xl border border-border-seam bg-bg-sunken/50 p-4 sm:p-5\">\n <div className=\"flex items-start gap-3\">\n <span\n className=\"grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <p className=\"text-sm text-text-secondary\">\n Every source is evaluated for accuracy, relevance, and cultural sensitivity through a\n transparent, multi-step review by editors and subject-area contributors.\n </p>\n </div>\n <dl className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n {VERIFICATIONS.map((v) => {\n const m = VERIFICATION_META[v];\n const Icon = m.icon;\n return (\n <div key={v} className=\"flex items-start gap-2\">\n <Icon\n className={cn(\n 'mt-0.5 h-4 w-4 shrink-0',\n m.accent === 'success' && 'text-status-success-text',\n m.accent === 'warning' && 'text-status-warning-text',\n m.accent === 'destructive' && 'text-status-error-text',\n m.accent === 'secondary' && 'text-text-tertiary'\n )}\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs font-semibold text-text-primary\">{m.label}</dt>\n <dd className=\"mt-0.5 text-xs text-text-tertiary\">{m.blurb}</dd>\n </div>\n </div>\n );\n })}\n </dl>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// CitationList\n// ---------------------------------------------------------------------------\n\ninterface CitationListProps {\n storyId: string;\n /** Narrow reading-rail rendering: smaller cards, icon-only segments, no legend. */\n compact?: boolean;\n}\n\nexport function CitationList({ storyId, compact = false }: CitationListProps) {\n const { data: citations, isLoading, isError, refetch } = useTcpCitations(storyId);\n\n if (isLoading) return <LoadingView label=\"Loading citations…\" />;\n if (isError) return <ErrorView message=\"We couldn't load citations.\" onRetry={() => refetch()} />;\n\n if (!citations || citations.length === 0) {\n return (\n <EmptyState\n icon={<BookMarked className=\"h-9 w-9\" />}\n title=\"No citations yet\"\n description=\"Add verifiable sources to ground this story in the record.\"\n />\n );\n }\n\n return (\n <div>\n <ul className={cn('flex flex-col', compact ? 'gap-3' : 'gap-3 sm:gap-4')}>\n {citations.map((citation) => (\n <CitationRow key={citation.id} citation={citation} compact={compact} />\n ))}\n </ul>\n {!compact && <MethodologyLegend />}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CitationList.js","names":[],"sources":["../../../src/theculturepost/components/CitationList.tsx"],"sourcesContent":["/**\n * CitationList — the verifiable sources that ground a story (tcp-05).\n * @module theculturepost/components\n *\n * Reads `tcpListCitations(storyId)`; each entry can be moved through its\n * verification state (`tcpVerifyCitation`) via a **segmented control** — the\n * archival-integrity control at the heart of a serious cultural publication.\n *\n * Each source shows a source-type icon, its bibliographic detail, an optional\n * excerpt + link, and (in the full variant) closes with a static methodology\n * legend. There is deliberately NO \"evidence trail\" mapping citations to\n * individual claims — the backend exposes no such linkage, so we never fake it.\n *\n * Two layouts, same data:\n * - default (full): roomy cards + labelled segments + methodology legend.\n * - `compact`: narrow-rail cards + icon-only segments, no legend.\n */\n\nimport {\n BookOpen,\n GraduationCap,\n Landmark,\n FileText,\n Globe,\n Newspaper,\n Mic,\n Box,\n ExternalLink,\n Quote,\n ShieldCheck,\n CheckCircle2,\n AlertCircle,\n XCircle,\n HelpCircle,\n BookMarked,\n type LucideIcon,\n} from 'lucide-react';\nimport { createElement } from 'react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { SourceScanLink } from './SourceScanLink';\nimport { LoadingView, ErrorView } from './StateViews';\nimport { EmptyState } from './EmptyState';\nimport { useTcpCitations, useTcpVerifyCitation } from '../hooks';\nimport { cn } from '../utils/cn';\nimport { formatDate, formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpCitation, TcpSourceType, TcpVerificationStatus } from '../types';\n\n// ---------------------------------------------------------------------------\n// Source-type + verification metadata (exported for reuse in the editorial table)\n// ---------------------------------------------------------------------------\n\n/** A distinct lucide motif per source type (icon, not hue — per the design rules). */\nexport const SOURCE_TYPE_ICON: Record<TcpSourceType, LucideIcon> = {\n ACADEMIC: GraduationCap,\n ARCHIVAL_RECORD: Landmark,\n ARTIFACT: Box,\n BOOK: BookOpen,\n INTERVIEW: Mic,\n PERIODICAL: Newspaper,\n PRIMARY_DOCUMENT: FileText,\n WEB: Globe,\n};\n\n/** Resolve a source-type's motif icon, falling back to a neutral document. */\nexport function sourceTypeIcon(type: TcpSourceType): LucideIcon {\n return SOURCE_TYPE_ICON[type] ?? FileText;\n}\n\ntype VerificationAccent = 'success' | 'warning' | 'destructive' | 'secondary';\n\ninterface VerificationMetaEntry {\n icon: LucideIcon;\n accent: VerificationAccent;\n label: string;\n /** Static, factual description of what the state means — shown in the legend. */\n blurb: string;\n}\n\n/** Ordered, tone-mapped metadata for each verification state. */\nexport const VERIFICATION_META: Record<TcpVerificationStatus, VerificationMetaEntry> = {\n CORROBORATED: {\n icon: CheckCircle2,\n accent: 'success',\n label: 'Corroborated',\n blurb: 'Strong agreement among reliable sources.',\n },\n DISPUTED: {\n icon: AlertCircle,\n accent: 'warning',\n label: 'Disputed',\n blurb: 'Conflicting or insufficient evidence.',\n },\n RETRACTED: {\n icon: XCircle,\n accent: 'destructive',\n label: 'Retracted',\n blurb: 'The source has since been withdrawn.',\n },\n UNVERIFIED: {\n icon: HelpCircle,\n accent: 'secondary',\n label: 'Unverified',\n blurb: 'Not yet reviewed by an editor.',\n },\n};\n\n/** The order the segmented control presents states in. */\nexport const VERIFICATIONS: TcpVerificationStatus[] = [\n 'CORROBORATED',\n 'DISPUTED',\n 'RETRACTED',\n 'UNVERIFIED',\n];\n\n/** Active-segment styling per accent — semantic tokens only. */\nconst SEGMENT_ACTIVE: Record<VerificationAccent, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text shadow-elevation-1',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text shadow-elevation-1',\n destructive: 'bg-status-error-bg-subtle text-status-error-text shadow-elevation-1',\n secondary: 'bg-bg-sunken text-text-primary shadow-elevation-1',\n};\n\n// ---------------------------------------------------------------------------\n// Segmented verification control\n// ---------------------------------------------------------------------------\n\ninterface VerificationSegmentsProps {\n value: TcpVerificationStatus;\n onChange: (value: TcpVerificationStatus) => void;\n pending?: boolean;\n /** Icon-only segments (for the narrow reading rail). */\n compact?: boolean;\n className?: string;\n}\n\nfunction VerificationSegments({\n value,\n onChange,\n pending = false,\n compact = false,\n className,\n}: VerificationSegmentsProps) {\n return (\n <div\n role=\"radiogroup\"\n aria-label=\"Verification status\"\n className={cn(\n 'grid grid-cols-2 gap-1 rounded-xl border border-border-seam bg-bg-surface p-1',\n compact ? 'grid-cols-4' : 'sm:inline-grid sm:auto-cols-max sm:grid-flow-col',\n className\n )}\n >\n {VERIFICATIONS.map((v) => {\n const meta = VERIFICATION_META[v];\n const Icon = meta.icon;\n const active = v === value;\n return (\n <button\n key={v}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n aria-label={meta.label}\n title={meta.label}\n disabled={pending}\n onClick={() => onChange(v)}\n className={cn(\n 'inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors disabled:opacity-60',\n active\n ? SEGMENT_ACTIVE[meta.accent]\n : 'text-text-tertiary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n <Icon className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className={compact ? 'sr-only' : 'hidden sm:inline'}>{meta.label}</span>\n </button>\n );\n })}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Citation row\n// ---------------------------------------------------------------------------\n\nfunction CitationRow({ citation, compact }: { citation: TcpCitation; compact: boolean }) {\n const verify = useTcpVerifyCitation();\n\n const changeVerification = async (verification: TcpVerificationStatus) => {\n if (verification === citation.verification) return;\n try {\n await verify.mutateAsync({ id: citation.id, verification });\n notify.success('Verification updated', formatEnumLabel(verification));\n } catch (err) {\n notify.error('Could not update verification', describeError(err));\n }\n };\n\n const meta = [citation.author, citation.publisher, formatDate(citation.publishedOn)]\n .filter((v) => v && v !== '—')\n .join(' · ');\n\n return (\n <li\n className={cn(\n 'rounded-2xl border border-border-seam bg-bg-surface transition-colors hover:border-border-strong',\n compact ? 'p-3.5' : 'p-4 sm:p-5'\n )}\n >\n <div className={cn('flex gap-3', compact ? 'items-start' : 'sm:gap-4')}>\n {/* Source-type motif tile */}\n <span\n className={cn(\n 'grid shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary',\n compact ? 'h-9 w-9' : 'h-12 w-12'\n )}\n aria-hidden=\"true\"\n >\n {createElement(sourceTypeIcon(citation.sourceType), {\n className: compact ? 'h-4 w-4' : 'h-6 w-6',\n })}\n </span>\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"mb-1 flex flex-wrap items-center gap-1.5\">\n <StatusBadge\n status={citation.sourceType}\n icon={createElement(sourceTypeIcon(citation.sourceType), { className: 'h-3 w-3' })}\n />\n {compact && <StatusBadge status={citation.verification} />}\n </div>\n\n <p\n className={cn(\n 'font-display font-semibold leading-snug text-text-primary',\n compact ? 'text-sm' : 'text-base'\n )}\n >\n {citation.title}\n </p>\n {meta && <p className=\"mt-0.5 text-xs text-text-tertiary\">{meta}</p>}\n\n {citation.excerpt && !compact && (\n <blockquote className=\"mt-3 flex gap-2 rounded-lg bg-bg-sunken/60 px-3 py-2 text-sm text-text-secondary\">\n <Quote className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary\" aria-hidden=\"true\" />\n <span>{citation.excerpt}</span>\n </blockquote>\n )}\n\n {citation.provenance && !compact && (\n <p className=\"mt-2 text-xs text-text-tertiary\">\n <span className=\"font-medium text-text-secondary\">Provenance:</span>{' '}\n {citation.provenance}\n </p>\n )}\n\n {(citation.url || citation.sourceFile) && (\n <div className=\"mt-2 flex flex-wrap items-center gap-x-3 gap-y-1\">\n {citation.url && (\n <a\n href={citation.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"inline-flex max-w-full items-center gap-1 text-xs font-medium text-accent hover:underline\"\n >\n <span className=\"truncate\">{compact ? 'View source' : citation.url}</span>\n <ExternalLink className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" />\n </a>\n )}\n <SourceScanLink\n fileId={citation.sourceFile?.id}\n label={compact ? 'View scan' : 'View source scan'}\n />\n </div>\n )}\n </div>\n </div>\n\n {/* Verification control */}\n <div\n className={cn(\n 'mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-border-seam pt-3',\n compact ? '' : 'sm:justify-between'\n )}\n >\n <span className=\"inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary\">\n <ShieldCheck className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden=\"true\" />\n Verification\n {verify.isPending && <Spinner size=\"sm\" />}\n </span>\n <VerificationSegments\n value={citation.verification}\n onChange={changeVerification}\n pending={verify.isPending}\n compact={compact}\n />\n </div>\n </li>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Methodology legend (static — no fabricated data)\n// ---------------------------------------------------------------------------\n\nfunction MethodologyLegend() {\n return (\n <div className=\"mt-4 rounded-2xl border border-border-seam bg-bg-sunken/50 p-4 sm:p-5\">\n <div className=\"flex items-start gap-3\">\n <span\n className=\"grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <p className=\"text-sm text-text-secondary\">\n Every source is evaluated for accuracy, relevance, and cultural sensitivity through a\n transparent, multi-step review by editors and subject-area contributors.\n </p>\n </div>\n <dl className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n {VERIFICATIONS.map((v) => {\n const m = VERIFICATION_META[v];\n const Icon = m.icon;\n return (\n <div key={v} className=\"flex items-start gap-2\">\n <Icon\n className={cn(\n 'mt-0.5 h-4 w-4 shrink-0',\n m.accent === 'success' && 'text-status-success-text',\n m.accent === 'warning' && 'text-status-warning-text',\n m.accent === 'destructive' && 'text-status-error-text',\n m.accent === 'secondary' && 'text-text-tertiary'\n )}\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs font-semibold text-text-primary\">{m.label}</dt>\n <dd className=\"mt-0.5 text-xs text-text-tertiary\">{m.blurb}</dd>\n </div>\n </div>\n );\n })}\n </dl>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// CitationList\n// ---------------------------------------------------------------------------\n\ninterface CitationListProps {\n storyId: string;\n /** Narrow reading-rail rendering: smaller cards, icon-only segments, no legend. */\n compact?: boolean;\n className?: string;\n}\n\nexport function CitationList({ storyId, compact = false, className }: CitationListProps) {\n const { data: citations, isLoading, isError, refetch } = useTcpCitations(storyId);\n\n if (isLoading) return <LoadingView label=\"Loading citations…\" className={className} />;\n if (isError)\n return (\n <div className={className}>\n <ErrorView message=\"We couldn't load citations.\" onRetry={() => refetch()} />\n </div>\n );\n\n if (!citations || citations.length === 0) {\n return (\n <div className={className}>\n <EmptyState\n icon={<BookMarked className=\"h-9 w-9\" />}\n title=\"No citations yet\"\n description=\"Add verifiable sources to ground this story in the record.\"\n />\n </div>\n );\n }\n\n return (\n <div className={className}>\n <ul className={cn('flex flex-col', compact ? 'gap-3' : 'gap-3 sm:gap-4')}>\n {citations.map((citation) => (\n <CitationRow key={citation.id} citation={citation} compact={compact} />\n ))}\n </ul>\n {!compact && <MethodologyLegend />}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SourceScanLink — open the scanned primary source attached to a citation.
|
|
3
|
+
* @module theculturepost/components
|
|
4
|
+
*
|
|
5
|
+
* `TcpCitation.sourceFile` is an id-only wspace-files-svc reference — the scan
|
|
6
|
+
* an editor attaches through {@link AddCitationDialog}. Until now that field
|
|
7
|
+
* was write-only: the dialog persisted `sourceFileId`, every citation query
|
|
8
|
+
* selected `sourceFile { id }`, and no surface ever offered a way back to the
|
|
9
|
+
* document. This resolves the id to a presigned URL via {@link useFileUrl}.
|
|
10
|
+
*
|
|
11
|
+
* States are deliberate: NOTHING renders when the citation carries no scan, a
|
|
12
|
+
* non-interactive note renders while the URL resolves, and an honest
|
|
13
|
+
* "unavailable" note renders if resolution fails — never a link that looks live
|
|
14
|
+
* but leads nowhere.
|
|
15
|
+
*/
|
|
16
|
+
interface SourceScanLinkProps {
|
|
17
|
+
/** `citation.sourceFile?.id` — nullish when no scan was attached. */
|
|
18
|
+
fileId?: string | null;
|
|
19
|
+
/** Visible link text. */
|
|
20
|
+
label?: string;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function SourceScanLink({ fileId, label, className, }: SourceScanLinkProps): import("react/jsx-runtime").JSX.Element | null;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=SourceScanLink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SourceScanLink.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/SourceScanLink.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,UAAU,mBAAmB;IAC3B,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,KAA0B,EAC1B,SAAS,GACV,EAAE,mBAAmB,kDAoCrB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { useFileUrl as t } from "../hooks/useFiles.js";
|
|
3
|
+
import { ScanLine as n } from "lucide-react";
|
|
4
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
|
+
import { Spinner as a } from "@burdenoff/fe-libs/ui";
|
|
6
|
+
//#region src/theculturepost/components/SourceScanLink.tsx
|
|
7
|
+
function o({ fileId: o, label: s = "View source scan", className: c }) {
|
|
8
|
+
let { data: l, isLoading: u, isError: d } = t(o);
|
|
9
|
+
return o ? u ? /* @__PURE__ */ i("span", {
|
|
10
|
+
className: e("inline-flex items-center gap-1.5 text-xs text-text-tertiary", c),
|
|
11
|
+
children: [/* @__PURE__ */ r(a, { size: "sm" }), " Opening scan…"]
|
|
12
|
+
}) : d || !l ? /* @__PURE__ */ i("span", {
|
|
13
|
+
className: e("inline-flex items-center gap-1 text-xs text-text-tertiary", c),
|
|
14
|
+
children: [/* @__PURE__ */ r(n, {
|
|
15
|
+
className: "h-3 w-3 shrink-0",
|
|
16
|
+
"aria-hidden": "true"
|
|
17
|
+
}), " Source scan unavailable"]
|
|
18
|
+
}) : /* @__PURE__ */ i("a", {
|
|
19
|
+
href: l,
|
|
20
|
+
target: "_blank",
|
|
21
|
+
rel: "noreferrer noopener",
|
|
22
|
+
className: e("inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline", c),
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ r(n, {
|
|
25
|
+
className: "h-3 w-3 shrink-0",
|
|
26
|
+
"aria-hidden": "true"
|
|
27
|
+
}),
|
|
28
|
+
" ",
|
|
29
|
+
s
|
|
30
|
+
]
|
|
31
|
+
}) : null;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { o as SourceScanLink };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=SourceScanLink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SourceScanLink.js","names":[],"sources":["../../../src/theculturepost/components/SourceScanLink.tsx"],"sourcesContent":["/**\n * SourceScanLink — open the scanned primary source attached to a citation.\n * @module theculturepost/components\n *\n * `TcpCitation.sourceFile` is an id-only wspace-files-svc reference — the scan\n * an editor attaches through {@link AddCitationDialog}. Until now that field\n * was write-only: the dialog persisted `sourceFileId`, every citation query\n * selected `sourceFile { id }`, and no surface ever offered a way back to the\n * document. This resolves the id to a presigned URL via {@link useFileUrl}.\n *\n * States are deliberate: NOTHING renders when the citation carries no scan, a\n * non-interactive note renders while the URL resolves, and an honest\n * \"unavailable\" note renders if resolution fails — never a link that looks live\n * but leads nowhere.\n */\n\nimport { ScanLine } from 'lucide-react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useFileUrl } from '../hooks/useFiles';\n\ninterface SourceScanLinkProps {\n /** `citation.sourceFile?.id` — nullish when no scan was attached. */\n fileId?: string | null;\n /** Visible link text. */\n label?: string;\n className?: string;\n}\n\nexport function SourceScanLink({\n fileId,\n label = 'View source scan',\n className,\n}: SourceScanLinkProps) {\n const { data: url, isLoading, isError } = useFileUrl(fileId);\n\n if (!fileId) return null;\n\n if (isLoading) {\n return (\n <span\n className={cn('inline-flex items-center gap-1.5 text-xs text-text-tertiary', className)}\n >\n <Spinner size=\"sm\" /> Opening scan…\n </span>\n );\n }\n\n if (isError || !url) {\n return (\n <span className={cn('inline-flex items-center gap-1 text-xs text-text-tertiary', className)}>\n <ScanLine className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" /> Source scan unavailable\n </span>\n );\n }\n\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className={cn(\n 'inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline',\n className\n )}\n >\n <ScanLine className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" /> {label}\n </a>\n );\n}\n"],"mappings":";;;;;;AA6BA,SAAgB,EAAe,EAC7B,WACA,WAAQ,oBACR,gBACsB;CACtB,IAAM,EAAE,MAAM,GAAK,cAAW,eAAY,EAAW,EAAO;AAsB5D,QApBK,IAED,IAEA,kBAAC,QAAD;EACE,WAAW,EAAG,+DAA+D,EAAU;YADzF,CAGE,kBAAC,GAAD,EAAS,MAAK,MAAO,CAAA,EAAA,iBAChB;MAIP,KAAW,CAAC,IAEZ,kBAAC,QAAD;EAAM,WAAW,EAAG,6DAA6D,EAAU;YAA3F,CACE,kBAAC,GAAD;GAAU,WAAU;GAAmB,eAAY;GAAS,CAAA,EAAA,2BACvD;MAKT,kBAAC,KAAD;EACE,MAAM;EACN,QAAO;EACP,KAAI;EACJ,WAAW,EACT,kFACA,EACD;YAPH;GASE,kBAAC,GAAD;IAAU,WAAU;IAAmB,eAAY;IAAS,CAAA;;GAAE;GAC5D;MA/Bc"}
|
|
@@ -32,6 +32,7 @@ export { CollectionCard } from './CollectionCard';
|
|
|
32
32
|
export { ArchiveItemCard } from './ArchiveItemCard';
|
|
33
33
|
export { OralHistoryCard } from './OralHistoryCard';
|
|
34
34
|
export { CitationList } from './CitationList';
|
|
35
|
+
export { SourceScanLink } from './SourceScanLink';
|
|
35
36
|
export { ReplyThread } from './ReplyThread';
|
|
36
37
|
export { CreateStoryDialog } from './CreateStoryDialog';
|
|
37
38
|
export { CreateTopicDialog } from './CreateTopicDialog';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGnG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGnG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -2,11 +2,11 @@ import { useCallback as e } from "react";
|
|
|
2
2
|
import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
3
3
|
//#region src/theculturepost/hooks/useTr.ts
|
|
4
4
|
function n() {
|
|
5
|
-
let { t: n
|
|
5
|
+
let { t: n } = t();
|
|
6
6
|
return e((e, t, r) => {
|
|
7
7
|
let i = n(e), a = i === e ? t : i;
|
|
8
8
|
return r ? Object.entries(r).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), a) : a;
|
|
9
|
-
}, [n
|
|
9
|
+
}, [n]);
|
|
10
10
|
}
|
|
11
11
|
//#endregion
|
|
12
12
|
export { n as useTr };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTr.js","names":[],"sources":["../../../src/theculturepost/hooks/useTr.ts"],"sourcesContent":["/**\n * Translation hook for TheCulturePost microfrontend.\n *\n * Wraps the shell's `useI18n()` and provides an inline English fallback plus\n * simple `{{param}}` interpolation, matching the VibeControls MFE pattern.\n */\n\nimport { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport function useTr() {\n const { t
|
|
1
|
+
{"version":3,"file":"useTr.js","names":[],"sources":["../../../src/theculturepost/hooks/useTr.ts"],"sourcesContent":["/**\n * Translation hook for TheCulturePost microfrontend.\n *\n * Wraps the shell's `useI18n()` and provides an inline English fallback plus\n * simple `{{param}}` interpolation, matching the VibeControls MFE pattern.\n */\n\nimport { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport function useTr() {\n const { t } = useI18n();\n\n return useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key);\n const source = translated === key ? fallback : translated;\n if (!params) return source;\n return Object.entries(params).reduce(\n (acc, [paramKey, value]) =>\n acc.replace(new RegExp(`\\\\{\\\\{${paramKey}\\\\}\\\\}`, 'g'), String(value)),\n source\n );\n },\n [t]\n );\n}\n"],"mappings":";;;AAUA,SAAgB,IAAQ;CACtB,IAAM,EAAE,SAAM,GAAS;AAEvB,QAAO,GACJ,GAAa,GAAkB,MAAqD;EACnF,IAAM,IAAa,EAAE,EAAI,EACnB,IAAS,MAAe,IAAM,IAAW;AAE/C,SADK,IACE,OAAO,QAAQ,EAAO,CAAC,QAC3B,GAAK,CAAC,GAAU,OACf,EAAI,QAAY,OAAO,SAAS,EAAS,SAAS,IAAI,EAAE,OAAO,EAAM,CAAC,EACxE,EACD,GALmB;IAOtB,CAAC,EAAE,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HomePage.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/pages/HomePage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"HomePage.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/pages/HomePage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAoKH,wBAAgB,QAAQ,4CAiPvB"}
|