@openrails/contentkit-ui 0.85.1 → 0.86.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.
Files changed (154) hide show
  1. package/README.md +7 -4
  2. package/dist/client/beacons.d.ts +13 -4
  3. package/dist/client/beacons.js +14 -7
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/client.d.ts +3 -0
  6. package/dist/client/client.js +29 -27
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/codes.d.ts +6 -1
  9. package/dist/client/codes.js +4 -1
  10. package/dist/client/codes.js.map +1 -1
  11. package/dist/client/generated/error-codes.js +1 -1
  12. package/dist/client/generated/error-codes.js.map +1 -1
  13. package/dist/client/generated/routes.d.ts +14 -1
  14. package/dist/client/generated/routes.js +17 -1
  15. package/dist/client/generated/routes.js.map +1 -1
  16. package/dist/client/generated/wire.d.ts +29 -0
  17. package/dist/client/generated/wire.js.map +1 -1
  18. package/dist/client/index.d.ts +2 -1
  19. package/dist/client/media/queue.d.ts +4 -2
  20. package/dist/client/media/queue.js +18 -12
  21. package/dist/client/media/queue.js.map +1 -1
  22. package/dist/client/media/rules.d.ts +7 -1
  23. package/dist/client/media/rules.js +27 -21
  24. package/dist/client/media/rules.js.map +1 -1
  25. package/dist/client/stats.d.ts +32 -0
  26. package/dist/client/stats.js +35 -0
  27. package/dist/client/stats.js.map +1 -0
  28. package/dist/client.js +30 -29
  29. package/dist/components/admin/content-manager.js +206 -206
  30. package/dist/components/admin/content-manager.js.map +1 -1
  31. package/dist/components/admin/group-manager.js +71 -70
  32. package/dist/components/admin/group-manager.js.map +1 -1
  33. package/dist/components/admin/media-item-panel.d.ts +24 -7
  34. package/dist/components/admin/media-item-panel.js +209 -197
  35. package/dist/components/admin/media-item-panel.js.map +1 -1
  36. package/dist/components/admin/poll-manager.js +25 -25
  37. package/dist/components/admin/stats-dashboard.d.ts +22 -0
  38. package/dist/components/admin/stats-dashboard.js +257 -0
  39. package/dist/components/admin/stats-dashboard.js.map +1 -0
  40. package/dist/components/content/card.js +42 -42
  41. package/dist/components/content/card.js.map +1 -1
  42. package/dist/components/content/editors.js +455 -427
  43. package/dist/components/content/editors.js.map +1 -1
  44. package/dist/components/content/grid.js +47 -47
  45. package/dist/components/content/index.d.ts +2 -1
  46. package/dist/components/content/listing.js +34 -34
  47. package/dist/components/content/listing.js.map +1 -1
  48. package/dist/components/content/paywall.d.ts +17 -5
  49. package/dist/components/content/paywall.js +115 -100
  50. package/dist/components/content/paywall.js.map +1 -1
  51. package/dist/components/content/reader-stats.d.ts +12 -0
  52. package/dist/components/content/reader-stats.js +47 -0
  53. package/dist/components/content/reader-stats.js.map +1 -0
  54. package/dist/components/groups/banner.js +33 -33
  55. package/dist/components/groups/directory.js +11 -11
  56. package/dist/components/groups/editor.js +293 -293
  57. package/dist/components/groups/editor.js.map +1 -1
  58. package/dist/components/groups/header.js +27 -27
  59. package/dist/components/groups/header.js.map +1 -1
  60. package/dist/components/groups/memberships.js +180 -180
  61. package/dist/components/groups/parts.d.ts +0 -2
  62. package/dist/components/groups/parts.js +9 -27
  63. package/dist/components/groups/parts.js.map +1 -1
  64. package/dist/components/media-folder-editor.js +239 -189
  65. package/dist/components/media-folder-editor.js.map +1 -1
  66. package/dist/components/notifications.js +17 -17
  67. package/dist/components/slot-upload.d.ts +2 -2
  68. package/dist/components/slot-upload.js +84 -79
  69. package/dist/components/slot-upload.js.map +1 -1
  70. package/dist/components/social/bans.js +34 -34
  71. package/dist/components/social/moderation.js +52 -48
  72. package/dist/components/social/moderation.js.map +1 -1
  73. package/dist/components/video-player.d.ts +2 -0
  74. package/dist/components/video-player.js +318 -314
  75. package/dist/components/video-player.js.map +1 -1
  76. package/dist/i18n/context.d.ts +2 -0
  77. package/dist/i18n/context.js +23 -5
  78. package/dist/i18n/context.js.map +1 -1
  79. package/dist/index.d.ts +2 -1
  80. package/dist/index.js +12 -10
  81. package/dist/locales/admin/de.js +27 -0
  82. package/dist/locales/admin/de.js.map +1 -1
  83. package/dist/locales/admin/en.d.ts +27 -0
  84. package/dist/locales/admin/en.js +27 -0
  85. package/dist/locales/admin/en.js.map +1 -1
  86. package/dist/locales/admin/es.js +27 -0
  87. package/dist/locales/admin/es.js.map +1 -1
  88. package/dist/locales/admin/ja.js +27 -0
  89. package/dist/locales/admin/ja.js.map +1 -1
  90. package/dist/locales/admin/ko.js +27 -0
  91. package/dist/locales/admin/ko.js.map +1 -1
  92. package/dist/locales/admin/zh.js +27 -0
  93. package/dist/locales/admin/zh.js.map +1 -1
  94. package/dist/locales/content/de.js +6 -0
  95. package/dist/locales/content/de.js.map +1 -1
  96. package/dist/locales/content/en.d.ts +6 -0
  97. package/dist/locales/content/en.js +6 -0
  98. package/dist/locales/content/en.js.map +1 -1
  99. package/dist/locales/content/es.js +6 -0
  100. package/dist/locales/content/es.js.map +1 -1
  101. package/dist/locales/content/ja.js +6 -0
  102. package/dist/locales/content/ja.js.map +1 -1
  103. package/dist/locales/content/ko.js +6 -0
  104. package/dist/locales/content/ko.js.map +1 -1
  105. package/dist/locales/content/zh.js +6 -0
  106. package/dist/locales/content/zh.js.map +1 -1
  107. package/dist/locales/de.js +1 -0
  108. package/dist/locales/de.js.map +1 -1
  109. package/dist/locales/en.d.ts +34 -0
  110. package/dist/locales/en.js +1 -0
  111. package/dist/locales/en.js.map +1 -1
  112. package/dist/locales/es.js +1 -0
  113. package/dist/locales/es.js.map +1 -1
  114. package/dist/locales/ja.js +1 -0
  115. package/dist/locales/ja.js.map +1 -1
  116. package/dist/locales/ko.js +1 -0
  117. package/dist/locales/ko.js.map +1 -1
  118. package/dist/locales/zh.js +1 -0
  119. package/dist/locales/zh.js.map +1 -1
  120. package/dist/react/admin.js +22 -23
  121. package/dist/react/admin.js.map +1 -1
  122. package/dist/react/canonical.d.ts +4 -3
  123. package/dist/react/canonical.js +34 -33
  124. package/dist/react/canonical.js.map +1 -1
  125. package/dist/react/contents.js +144 -144
  126. package/dist/react/contents.js.map +1 -1
  127. package/dist/react/detail.d.ts +12 -6
  128. package/dist/react/detail.js +31 -24
  129. package/dist/react/detail.js.map +1 -1
  130. package/dist/react/folder.d.ts +7 -2
  131. package/dist/react/folder.js +165 -147
  132. package/dist/react/folder.js.map +1 -1
  133. package/dist/react/groups.d.ts +0 -1
  134. package/dist/react/groups.js +13 -12
  135. package/dist/react/groups.js.map +1 -1
  136. package/dist/react/index.d.ts +4 -2
  137. package/dist/react/location.d.ts +11 -0
  138. package/dist/react/location.js +18 -0
  139. package/dist/react/location.js.map +1 -0
  140. package/dist/react/public.d.ts +8 -0
  141. package/dist/react/public.js +32 -27
  142. package/dist/react/public.js.map +1 -1
  143. package/dist/react/stats.d.ts +24 -0
  144. package/dist/react/stats.js +28 -0
  145. package/dist/react/stats.js.map +1 -0
  146. package/dist/react/use-resource.js +8 -7
  147. package/dist/react/use-resource.js.map +1 -1
  148. package/dist/react/versions.js +20 -20
  149. package/dist/react.js +26 -24
  150. package/dist/styles.css +1 -1
  151. package/dist/urls/index.d.ts +35 -10
  152. package/dist/urls.js +100 -70
  153. package/dist/urls.js.map +1 -1
  154. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
1
  import { RenditionImg as e } from "../rendition-img.js";
2
- import { useMessages as t } from "../../i18n/context.js";
3
- import { ContentKitContext as n } from "../../react/context.js";
4
- import { Badge as r } from "../../ui/badge.js";
5
- import { RelativeTime as i, formatDate as a } from "../social/parts.js";
6
- import { GroupAvatar as o, GroupLink as s, labelOf as c, useLanguageName as l } from "../groups/parts.js";
2
+ import { useLanguageName as t, useMessages as n } from "../../i18n/context.js";
3
+ import { ContentKitContext as r } from "../../react/context.js";
4
+ import { Badge as i } from "../../ui/badge.js";
5
+ import { RelativeTime as a, formatDate as o } from "../social/parts.js";
6
+ import { GroupAvatar as s, GroupLink as c, labelOf as l } from "../groups/parts.js";
7
7
  import { priceLabel as u } from "./paywall.js";
8
8
  import { useCallback as d, useContext as f } from "react";
9
9
  import { jsx as p, jsxs as m } from "react/jsx-runtime";
@@ -21,31 +21,31 @@ function x(e, t, n = [], r) {
21
21
  let i = t.offers?.[0];
22
22
  if (i) return i.target.kind === "group" && Number(i.terms.amount) === 0 ? e.t("contents.joinFree") : u(e, i.terms);
23
23
  let a = t.sell?.find((e) => n.includes(e));
24
- return a ? c(r, a) : t.sell?.some((e) => e.includes(":group:")) ? e.t("contents.members") : t.sell?.length ? e.t("contents.forSale") : e.t("contents.locked");
24
+ return a ? l(r, a) : t.sell?.some((e) => e.includes(":group:")) ? e.t("contents.members") : t.sell?.length ? e.t("contents.forSale") : e.t("contents.locked");
25
25
  }
26
26
  function S(e, t) {
27
- let r = f(n)?.navigate;
28
- return d((n) => {
29
- t?.(), !(!r || !e || n.defaultPrevented || n.button !== 0 || n.metaKey || n.ctrlKey || n.shiftKey || n.altKey) && (n.preventDefault(), r(e));
27
+ let n = f(r)?.navigate;
28
+ return d((r) => {
29
+ t?.(), !(!n || !e || r.defaultPrevented || r.button !== 0 || r.metaKey || r.ctrlKey || r.shiftKey || r.altKey) && (r.preventDefault(), n(e));
30
30
  }, [
31
- r,
31
+ n,
32
32
  e,
33
33
  t
34
34
  ]);
35
35
  }
36
- function C({ card: n, href: i, onOpen: o, shape: s = "portrait", groupKinds: u, chips: f = 3, tiers: C, labels: E, owner: D = !0, ownerHref: O, children: k, className: A }) {
37
- let j = t(), { t: M } = j, N = l(), P = i ? i(n) : n.path, F = S(P, d(() => o?.(n), [o, n])), I = x(j, n.access, C, E), L = n.version, R = n.viewer && n.viewer.progress_max > 0 ? Math.min(1, n.viewer.progress / n.viewer.progress_max) : 0, z = u ?? Object.keys(n.groups), B = L.duration_ms ? Math.round(L.duration_ms / 6e4) : 0, V = [
36
+ function C({ card: r, href: a, onOpen: s, shape: c = "portrait", groupKinds: u, chips: f = 3, tiers: C, labels: E, owner: D = !0, ownerHref: O, children: k, className: A }) {
37
+ let j = n(), { t: M } = j, N = t(), P = a ? a(r) : r.path, F = S(P, d(() => s?.(r), [s, r])), I = x(j, r.access, C, E), L = r.version, R = r.viewer && r.viewer.progress_max > 0 ? Math.min(1, r.viewer.progress / r.viewer.progress_max) : 0, z = u ?? Object.keys(r.groups), B = L.duration_ms ? Math.round(L.duration_ms / 6e4) : 0, V = [
38
38
  ...L.languages.map(N),
39
- ...L.traits.map((e) => c(E, e)),
39
+ ...L.traits.map((e) => l(E, e)),
40
40
  L.pages ? j.plural("contents.pages", L.pages) : null,
41
41
  B ? j.plural("contents.minutes", B) : null
42
- ].filter(Boolean), H = n.title || n.code;
42
+ ].filter(Boolean), H = r.title || r.code;
43
43
  return /* @__PURE__ */ m("article", {
44
44
  className: h("group/card grid content-start gap-2", A),
45
45
  "data-ckui": "content-card",
46
- "data-id": n.id,
46
+ "data-id": r.id,
47
47
  "data-locked": I ? "" : void 0,
48
- lang: n.language || void 0,
48
+ lang: r.language || void 0,
49
49
  children: [/* @__PURE__ */ m("a", {
50
50
  href: P,
51
51
  onClick: F,
@@ -54,9 +54,9 @@ function C({ card: n, href: i, onOpen: o, shape: s = "portrait", groupKinds: u,
54
54
  "aria-hidden": !0,
55
55
  children: [
56
56
  /* @__PURE__ */ p("div", {
57
- className: s === "landscape" ? "aspect-video" : "aspect-[3/4]",
57
+ className: c === "landscape" ? "aspect-video" : "aspect-[3/4]",
58
58
  children: /* @__PURE__ */ p(e, {
59
- outputs: n.cover,
59
+ outputs: r.cover,
60
60
  alt: "",
61
61
  fit: "cover",
62
62
  skeleton: !0,
@@ -103,27 +103,27 @@ function C({ card: n, href: i, onOpen: o, shape: s = "portrait", groupKinds: u,
103
103
  ]
104
104
  })]
105
105
  }),
106
- D && n.owner && /* @__PURE__ */ p(w, {
107
- group: n.owner,
106
+ D && r.owner && /* @__PURE__ */ p(w, {
107
+ group: r.owner,
108
108
  href: O
109
109
  }),
110
110
  V.length > 0 && /* @__PURE__ */ p("p", {
111
111
  className: "text-xs text-muted-foreground",
112
112
  children: V.join(" · ")
113
113
  }),
114
- n.viewer?.completed && /* @__PURE__ */ p(r, {
114
+ r.viewer?.completed && /* @__PURE__ */ p(i, {
115
115
  variant: "secondary",
116
116
  className: "justify-self-start",
117
117
  children: M("contents.completed")
118
118
  }),
119
- !n.access.allowed && n.access.free_after && /* @__PURE__ */ p("p", {
119
+ !r.access.allowed && r.access.free_after && /* @__PURE__ */ p("p", {
120
120
  className: "text-xs text-muted-foreground",
121
- children: M("contents.freeFrom", { date: a(j, n.access.free_after, !1) })
121
+ children: M("contents.freeFrom", { date: o(j, r.access.free_after, !1) })
122
122
  }),
123
- z.some((e) => n.groups[e]?.length) && /* @__PURE__ */ p("ul", {
123
+ z.some((e) => r.groups[e]?.length) && /* @__PURE__ */ p("ul", {
124
124
  className: "flex flex-wrap gap-1",
125
125
  "data-ckui": "card-groups",
126
- children: z.flatMap((e) => (n.groups[e] ?? []).slice(0, f).map((t) => /* @__PURE__ */ p("li", {
126
+ children: z.flatMap((e) => (r.groups[e] ?? []).slice(0, f).map((t) => /* @__PURE__ */ p("li", {
127
127
  "data-kind": e,
128
128
  children: /* @__PURE__ */ p(T, {
129
129
  url: t.path,
@@ -141,14 +141,14 @@ function C({ card: n, href: i, onOpen: o, shape: s = "portrait", groupKinds: u,
141
141
  /* @__PURE__ */ p(y, {
142
142
  icon: g,
143
143
  strokeWidth: 2,
144
- className: h("size-3.5", n.viewer?.favorited && "fill-current text-primary"),
144
+ className: h("size-3.5", r.viewer?.favorited && "fill-current text-primary"),
145
145
  "aria-hidden": !0
146
146
  }),
147
147
  /* @__PURE__ */ p("span", {
148
148
  className: "sr-only",
149
149
  children: M("contents.favorites")
150
150
  }),
151
- b(n.counts.favorites, j.language)
151
+ b(r.counts.favorites, j.language)
152
152
  ]
153
153
  }), /* @__PURE__ */ m("span", {
154
154
  className: "inline-flex items-center gap-1",
@@ -164,7 +164,7 @@ function C({ card: n, href: i, onOpen: o, shape: s = "portrait", groupKinds: u,
164
164
  className: "sr-only",
165
165
  children: M("contents.likes")
166
166
  }),
167
- b(n.counts.likes, j.language)
167
+ b(r.counts.likes, j.language)
168
168
  ]
169
169
  })]
170
170
  }),
@@ -178,11 +178,11 @@ function w({ group: e, href: t, className: n }) {
178
178
  className: h("flex min-w-0 text-xs text-muted-foreground", n),
179
179
  "data-ckui": "card-owner",
180
180
  "data-id": e.id,
181
- children: /* @__PURE__ */ m(s, {
181
+ children: /* @__PURE__ */ m(c, {
182
182
  group: e,
183
183
  href: t,
184
184
  className: "inline-flex min-w-0 items-center gap-1.5 hover:text-foreground",
185
- children: [/* @__PURE__ */ p(o, {
185
+ children: [/* @__PURE__ */ p(s, {
186
186
  group: e,
187
187
  className: "size-5 shrink-0 text-[0.625rem]"
188
188
  }), /* @__PURE__ */ p("span", {
@@ -204,17 +204,17 @@ function T({ url: e, name: t }) {
204
204
  children: t
205
205
  });
206
206
  }
207
- function E({ card: n, href: r, onOpen: a, tiers: o, labels: s, byline: c, owner: l = !0, ownerHref: u, children: f, className: v }) {
208
- let C = t(), T = r ? r(n) : n.path, E = S(T, d(() => a?.(n), [a, n])), D = x(C, n.access, o, s), O = n.title || n.code;
207
+ function E({ card: t, href: r, onOpen: i, tiers: o, labels: s, byline: c, owner: l = !0, ownerHref: u, children: f, className: v }) {
208
+ let C = n(), T = r ? r(t) : t.path, E = S(T, d(() => i?.(t), [i, t])), D = x(C, t.access, o, s), O = t.title || t.code;
209
209
  return /* @__PURE__ */ m("article", {
210
210
  className: h("grid gap-3 rounded-lg border border-border p-3", v),
211
211
  "data-ckui": "post-card",
212
- "data-id": n.id,
212
+ "data-id": t.id,
213
213
  "data-locked": D ? "" : void 0,
214
- lang: n.language || void 0,
214
+ lang: t.language || void 0,
215
215
  children: [
216
- c ?? (l && n.owner && /* @__PURE__ */ p(w, {
217
- group: n.owner,
216
+ c ?? (l && t.owner && /* @__PURE__ */ p(w, {
217
+ group: t.owner,
218
218
  href: u,
219
219
  className: "text-sm"
220
220
  })),
@@ -226,10 +226,10 @@ function E({ card: n, href: r, onOpen: a, tiers: o, labels: s, byline: c, owner:
226
226
  className: "font-medium break-words hover:underline",
227
227
  "data-ckui": "card-title",
228
228
  children: O
229
- }), n.cover.length > 0 && /* @__PURE__ */ m("span", {
229
+ }), t.cover.length > 0 && /* @__PURE__ */ m("span", {
230
230
  className: "relative block aspect-video overflow-hidden rounded-md bg-muted",
231
231
  children: [/* @__PURE__ */ p(e, {
232
- outputs: n.cover,
232
+ outputs: t.cover,
233
233
  alt: "",
234
234
  fit: "cover",
235
235
  skeleton: !0,
@@ -252,8 +252,8 @@ function E({ card: n, href: r, onOpen: a, tiers: o, labels: s, byline: c, owner:
252
252
  /* @__PURE__ */ m("p", {
253
253
  className: "flex flex-wrap items-center gap-3 text-xs text-muted-foreground",
254
254
  children: [
255
- n.published_at && /* @__PURE__ */ p(i, { at: n.published_at }),
256
- !n.cover.length && D && /* @__PURE__ */ p("span", {
255
+ t.published_at && /* @__PURE__ */ p(a, { at: t.published_at }),
256
+ !t.cover.length && D && /* @__PURE__ */ p("span", {
257
257
  "data-ckui": "card-lock",
258
258
  children: D
259
259
  }),
@@ -263,14 +263,14 @@ function E({ card: n, href: r, onOpen: a, tiers: o, labels: s, byline: c, owner:
263
263
  /* @__PURE__ */ p(y, {
264
264
  icon: g,
265
265
  strokeWidth: 2,
266
- className: h("size-3.5", n.viewer?.favorited && "fill-current text-primary"),
266
+ className: h("size-3.5", t.viewer?.favorited && "fill-current text-primary"),
267
267
  "aria-hidden": !0
268
268
  }),
269
269
  /* @__PURE__ */ p("span", {
270
270
  className: "sr-only",
271
271
  children: C.t("contents.favorites")
272
272
  }),
273
- b(n.counts.favorites, C.language)
273
+ b(t.counts.favorites, C.language)
274
274
  ]
275
275
  })
276
276
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"card.js","names":[],"sources":["../../../src/components/content/card.tsx"],"sourcesContent":["import { FavouriteIcon, LockIcon, ThumbsUpIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useContext, type MouseEvent, type ReactNode } from \"react\";\nimport type { Card, CardAccess } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { formatDate, RelativeTime } from \"../social/parts.js\";\nimport { GroupAvatar, GroupLink, labelOf, useLanguageName, type GroupLike, type Labels } from \"../groups/parts.js\";\nimport { priceLabel } from \"./paywall.js\";\n\nexport interface ContentCardProps {\n card: Card;\n /** The card's link; default its canonical path. */\n href?: (card: Card) => string | undefined;\n /** Before the card's link is followed: a click signal. */\n onOpen?: (card: Card) => void;\n /** Cover shape: \"portrait\" (3:4, readers) or \"landscape\" (16:9, players). Default portrait. */\n shape?: \"portrait\" | \"landscape\";\n /** The group kinds whose chips show, in order; default every kind the card names. */\n groupKinds?: readonly string[];\n /** Chips per group kind. Default 3. */\n chips?: number;\n /** Tiers the site sells, so a lock names them; others show as \"for sale\". */\n tiers?: readonly string[];\n /** Display names of kinds, traits and tiers by identifier. */\n labels?: Labels;\n /** Show the owner group (a channel) when the card has one; default true, false on that group's own feed. */\n owner?: boolean;\n /** The owner group's link; default its canonical path. */\n ownerHref?: (g: GroupLike) => string | undefined;\n /** Below the meta line: the host's own. */\n children?: ReactNode;\n className?: string;\n}\n\nconst compact = (n: number, language?: string) => new Intl.NumberFormat(language, { notation: \"compact\", maximumFractionDigits: 1 }).format(n);\n\n/** Why a card is locked, in words (its price when it is on sale); null when it opens. */\nexport function lockLabel(m: Translator, a: CardAccess, tiers: readonly string[] = [], labels?: Labels): string | null {\n if (a.allowed) return null;\n if (a.unknown) return m.t(\"contents.unknownAccess\");\n const offer = a.offers?.[0];\n if (offer) return offer.target.kind === \"group\" && Number(offer.terms.amount) === 0 ? m.t(\"contents.joinFree\") : priceLabel(m, offer.terms);\n const tier = a.sell?.find((k) => tiers.includes(k));\n if (tier) return labelOf(labels, tier);\n if (a.sell?.some((k) => k.includes(\":group:\"))) return m.t(\"contents.members\");\n if (a.sell?.length) return m.t(\"contents.forSale\");\n return m.t(\"contents.locked\");\n}\n\n/** A link followed in-app through the provider's navigate. */\nfunction useLink(url: string | undefined, before?: () => void) {\n const navigate = useContext(ContentKitContext)?.navigate;\n return useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n before?.();\n if (!navigate || !url || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n e.preventDefault();\n navigate(url);\n },\n [navigate, url, before],\n );\n}\n\n/** One content in a listing: cover, title, version, groups, counts, the reader's progress and why it is locked. */\nexport function ContentCard({ card, href, onOpen, shape = \"portrait\", groupKinds, chips = 3, tiers, labels, owner = true, ownerHref, children, className }: ContentCardProps) {\n const m = useMessages();\n const { t } = m;\n const languageName = useLanguageName();\n const url = href ? href(card) : card.path;\n const open = useCallback(() => onOpen?.(card), [onOpen, card]);\n const onClick = useLink(url, open);\n const lock = lockLabel(m, card.access, tiers, labels);\n const v = card.version;\n const progress = card.viewer && card.viewer.progress_max > 0 ? Math.min(1, card.viewer.progress / card.viewer.progress_max) : 0;\n const kinds = groupKinds ?? Object.keys(card.groups);\n const minutes = v.duration_ms ? Math.round(v.duration_ms / 60_000) : 0;\n const meta = [\n ...v.languages.map(languageName),\n ...v.traits.map((x) => labelOf(labels, x)),\n v.pages ? m.plural(\"contents.pages\", v.pages) : null,\n minutes ? m.plural(\"contents.minutes\", minutes) : null,\n ].filter(Boolean);\n const title = card.title || card.code;\n return (\n <article className={cn(\"group/card grid content-start gap-2\", className)} data-ckui=\"content-card\" data-id={card.id} data-locked={lock ? \"\" : undefined} lang={card.language || undefined}>\n <a href={url} onClick={onClick} className=\"relative block overflow-hidden rounded-lg bg-muted\" tabIndex={-1} aria-hidden>\n <div className={shape === \"landscape\" ? \"aspect-video\" : \"aspect-[3/4]\"}>\n <RenditionImg\n outputs={card.cover}\n alt=\"\"\n fit=\"cover\"\n skeleton\n className=\"size-full transition-transform group-hover/card:scale-[1.02]\"\n fallback={<span className=\"grid size-full place-items-center text-2xl font-semibold text-muted-foreground\">{title.charAt(0).toUpperCase()}</span>}\n />\n </div>\n {lock && (\n <span className=\"absolute top-2 left-2 inline-flex items-center gap-1 rounded-md bg-background/85 px-1.5 py-0.5 text-xs font-medium text-foreground shadow-sm\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n )}\n {progress > 0 && (\n <span className=\"absolute inset-x-0 bottom-0 h-1 bg-background/60\" data-ckui=\"card-progress\">\n <span className=\"block h-full bg-primary\" style={{ width: `${Math.round(progress * 100)}%` }} />\n </span>\n )}\n </a>\n <div className=\"grid gap-1\">\n <a href={url} onClick={onClick} className=\"line-clamp-2 font-medium break-words hover:underline\" data-ckui=\"card-title\">\n {title}\n {lock && <span className=\"sr-only\"> ({lock})</span>}\n </a>\n {owner && card.owner && <OwnerChip group={card.owner} href={ownerHref} />}\n {meta.length > 0 && <p className=\"text-xs text-muted-foreground\">{meta.join(\" · \")}</p>}\n {card.viewer?.completed && (\n <Badge variant=\"secondary\" className=\"justify-self-start\">\n {t(\"contents.completed\")}\n </Badge>\n )}\n {!card.access.allowed && card.access.free_after && <p className=\"text-xs text-muted-foreground\">{t(\"contents.freeFrom\", { date: formatDate(m, card.access.free_after, false) })}</p>}\n {kinds.some((k) => card.groups[k]?.length) && (\n <ul className=\"flex flex-wrap gap-1\" data-ckui=\"card-groups\">\n {kinds.flatMap((k) =>\n (card.groups[k] ?? []).slice(0, chips).map((g) => (\n <li key={`${k}:${g.id}`} data-kind={k}>\n <Chip url={g.path} name={g.name || g.slug} />\n </li>\n )),\n )}\n </ul>\n )}\n <p className=\"flex items-center gap-3 text-xs text-muted-foreground tabular-nums\" data-ckui=\"card-counts\">\n <span className=\"inline-flex items-center gap-1\" title={t(\"contents.favorites\")}>\n <HugeiconsIcon icon={FavouriteIcon} strokeWidth={2} className={cn(\"size-3.5\", card.viewer?.favorited && \"fill-current text-primary\")} aria-hidden />\n <span className=\"sr-only\">{t(\"contents.favorites\")}</span>\n {compact(card.counts.favorites, m.language)}\n </span>\n <span className=\"inline-flex items-center gap-1\" title={t(\"contents.likes\")}>\n <HugeiconsIcon icon={ThumbsUpIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n <span className=\"sr-only\">{t(\"contents.likes\")}</span>\n {compact(card.counts.likes, m.language)}\n </span>\n </p>\n {children}\n </div>\n </article>\n );\n}\n\n/** The group that owns a content (a channel): its avatar and name, linking to its page. */\nexport function OwnerChip({ group, href, className }: { group: GroupLike; href?: (g: GroupLike) => string | undefined; className?: string }) {\n return (\n <span className={cn(\"flex min-w-0 text-xs text-muted-foreground\", className)} data-ckui=\"card-owner\" data-id={group.id}>\n <GroupLink group={group} href={href} className=\"inline-flex min-w-0 items-center gap-1.5 hover:text-foreground\">\n <GroupAvatar group={group} className=\"size-5 shrink-0 text-[0.625rem]\" />\n <span className=\"truncate\">{group.name || group.slug}</span>\n </GroupLink>\n </span>\n );\n}\n\nfunction Chip({ url, name }: { url?: string; name: string }) {\n const onClick = useLink(url);\n const cls = \"inline-block rounded-md border border-border px-1.5 py-0.5 text-xs text-muted-foreground\";\n return url ? (\n <a href={url} onClick={onClick} className={cn(cls, \"hover:bg-muted hover:text-foreground\")}>\n {name}\n </a>\n ) : (\n <span className={cls}>{name}</span>\n );\n}\n\nexport interface PostCardProps extends Omit<ContentCardProps, \"shape\" | \"chips\" | \"groupKinds\"> {\n /** Above the title: the post's channel or author; default its owner group's chip. */\n byline?: ReactNode;\n}\n\n/** A post in a feed: a wide cover, its title, when it was published and what unlocking it costs. */\nexport function PostCard({ card, href, onOpen, tiers, labels, byline, owner = true, ownerHref, children, className }: PostCardProps) {\n const m = useMessages();\n const url = href ? href(card) : card.path;\n const open = useCallback(() => onOpen?.(card), [onOpen, card]);\n const onClick = useLink(url, open);\n const lock = lockLabel(m, card.access, tiers, labels);\n const title = card.title || card.code;\n return (\n <article className={cn(\"grid gap-3 rounded-lg border border-border p-3\", className)} data-ckui=\"post-card\" data-id={card.id} data-locked={lock ? \"\" : undefined} lang={card.language || undefined}>\n {byline ?? (owner && card.owner && <OwnerChip group={card.owner} href={ownerHref} className=\"text-sm\" />)}\n <a href={url} onClick={onClick} className=\"grid gap-2\">\n <span className=\"font-medium break-words hover:underline\" data-ckui=\"card-title\">\n {title}\n </span>\n {card.cover.length > 0 && (\n <span className=\"relative block aspect-video overflow-hidden rounded-md bg-muted\">\n <RenditionImg outputs={card.cover} alt=\"\" fit=\"cover\" skeleton className={cn(\"size-full\", lock && \"blur-md\")} />\n {lock && (\n <span className=\"absolute inset-0 grid place-items-center\">\n <span className=\"inline-flex items-center gap-1 rounded-md bg-background/85 px-2 py-1 text-xs font-medium shadow-sm\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n </span>\n )}\n </span>\n )}\n </a>\n <p className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {card.published_at && <RelativeTime at={card.published_at} />}\n {!card.cover.length && lock && <span data-ckui=\"card-lock\">{lock}</span>}\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <HugeiconsIcon icon={FavouriteIcon} strokeWidth={2} className={cn(\"size-3.5\", card.viewer?.favorited && \"fill-current text-primary\")} aria-hidden />\n <span className=\"sr-only\">{m.t(\"contents.favorites\")}</span>\n {compact(card.counts.favorites, m.language)}\n </span>\n </p>\n {children}\n </article>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"card.js","names":[],"sources":["../../../src/components/content/card.tsx"],"sourcesContent":["import { FavouriteIcon, LockIcon, ThumbsUpIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useContext, type MouseEvent, type ReactNode } from \"react\";\nimport type { Card, CardAccess } from \"../../client/generated/wire.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { formatDate, RelativeTime } from \"../social/parts.js\";\nimport { GroupAvatar, GroupLink, labelOf, type GroupLike, type Labels } from \"../groups/parts.js\";\nimport { priceLabel } from \"./paywall.js\";\n\nexport interface ContentCardProps {\n card: Card;\n /** The card's link; default its canonical path. */\n href?: (card: Card) => string | undefined;\n /** Before the card's link is followed: a click signal. */\n onOpen?: (card: Card) => void;\n /** Cover shape: \"portrait\" (3:4, readers) or \"landscape\" (16:9, players). Default portrait. */\n shape?: \"portrait\" | \"landscape\";\n /** The group kinds whose chips show, in order; default every kind the card names. */\n groupKinds?: readonly string[];\n /** Chips per group kind. Default 3. */\n chips?: number;\n /** Tiers the site sells, so a lock names them; others show as \"for sale\". */\n tiers?: readonly string[];\n /** Display names of kinds, traits and tiers by identifier. */\n labels?: Labels;\n /** Show the owner group (a channel) when the card has one; default true, false on that group's own feed. */\n owner?: boolean;\n /** The owner group's link; default its canonical path. */\n ownerHref?: (g: GroupLike) => string | undefined;\n /** Below the meta line: the host's own. */\n children?: ReactNode;\n className?: string;\n}\n\nconst compact = (n: number, language?: string) => new Intl.NumberFormat(language, { notation: \"compact\", maximumFractionDigits: 1 }).format(n);\n\n/** Why a card is locked, in words (its price when it is on sale); null when it opens. */\nexport function lockLabel(m: Translator, a: CardAccess, tiers: readonly string[] = [], labels?: Labels): string | null {\n if (a.allowed) return null;\n if (a.unknown) return m.t(\"contents.unknownAccess\");\n const offer = a.offers?.[0];\n if (offer) return offer.target.kind === \"group\" && Number(offer.terms.amount) === 0 ? m.t(\"contents.joinFree\") : priceLabel(m, offer.terms);\n const tier = a.sell?.find((k) => tiers.includes(k));\n if (tier) return labelOf(labels, tier);\n if (a.sell?.some((k) => k.includes(\":group:\"))) return m.t(\"contents.members\");\n if (a.sell?.length) return m.t(\"contents.forSale\");\n return m.t(\"contents.locked\");\n}\n\n/** A link followed in-app through the provider's navigate. */\nfunction useLink(url: string | undefined, before?: () => void) {\n const navigate = useContext(ContentKitContext)?.navigate;\n return useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n before?.();\n if (!navigate || !url || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n e.preventDefault();\n navigate(url);\n },\n [navigate, url, before],\n );\n}\n\n/** One content in a listing: cover, title, version, groups, counts, the reader's progress and why it is locked. */\nexport function ContentCard({ card, href, onOpen, shape = \"portrait\", groupKinds, chips = 3, tiers, labels, owner = true, ownerHref, children, className }: ContentCardProps) {\n const m = useMessages();\n const { t } = m;\n const languageName = useLanguageName();\n const url = href ? href(card) : card.path;\n const open = useCallback(() => onOpen?.(card), [onOpen, card]);\n const onClick = useLink(url, open);\n const lock = lockLabel(m, card.access, tiers, labels);\n const v = card.version;\n const progress = card.viewer && card.viewer.progress_max > 0 ? Math.min(1, card.viewer.progress / card.viewer.progress_max) : 0;\n const kinds = groupKinds ?? Object.keys(card.groups);\n const minutes = v.duration_ms ? Math.round(v.duration_ms / 60_000) : 0;\n const meta = [\n ...v.languages.map(languageName),\n ...v.traits.map((x) => labelOf(labels, x)),\n v.pages ? m.plural(\"contents.pages\", v.pages) : null,\n minutes ? m.plural(\"contents.minutes\", minutes) : null,\n ].filter(Boolean);\n const title = card.title || card.code;\n return (\n <article className={cn(\"group/card grid content-start gap-2\", className)} data-ckui=\"content-card\" data-id={card.id} data-locked={lock ? \"\" : undefined} lang={card.language || undefined}>\n <a href={url} onClick={onClick} className=\"relative block overflow-hidden rounded-lg bg-muted\" tabIndex={-1} aria-hidden>\n <div className={shape === \"landscape\" ? \"aspect-video\" : \"aspect-[3/4]\"}>\n <RenditionImg\n outputs={card.cover}\n alt=\"\"\n fit=\"cover\"\n skeleton\n className=\"size-full transition-transform group-hover/card:scale-[1.02]\"\n fallback={<span className=\"grid size-full place-items-center text-2xl font-semibold text-muted-foreground\">{title.charAt(0).toUpperCase()}</span>}\n />\n </div>\n {lock && (\n <span className=\"absolute top-2 left-2 inline-flex items-center gap-1 rounded-md bg-background/85 px-1.5 py-0.5 text-xs font-medium text-foreground shadow-sm\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n )}\n {progress > 0 && (\n <span className=\"absolute inset-x-0 bottom-0 h-1 bg-background/60\" data-ckui=\"card-progress\">\n <span className=\"block h-full bg-primary\" style={{ width: `${Math.round(progress * 100)}%` }} />\n </span>\n )}\n </a>\n <div className=\"grid gap-1\">\n <a href={url} onClick={onClick} className=\"line-clamp-2 font-medium break-words hover:underline\" data-ckui=\"card-title\">\n {title}\n {lock && <span className=\"sr-only\"> ({lock})</span>}\n </a>\n {owner && card.owner && <OwnerChip group={card.owner} href={ownerHref} />}\n {meta.length > 0 && <p className=\"text-xs text-muted-foreground\">{meta.join(\" · \")}</p>}\n {card.viewer?.completed && (\n <Badge variant=\"secondary\" className=\"justify-self-start\">\n {t(\"contents.completed\")}\n </Badge>\n )}\n {!card.access.allowed && card.access.free_after && <p className=\"text-xs text-muted-foreground\">{t(\"contents.freeFrom\", { date: formatDate(m, card.access.free_after, false) })}</p>}\n {kinds.some((k) => card.groups[k]?.length) && (\n <ul className=\"flex flex-wrap gap-1\" data-ckui=\"card-groups\">\n {kinds.flatMap((k) =>\n (card.groups[k] ?? []).slice(0, chips).map((g) => (\n <li key={`${k}:${g.id}`} data-kind={k}>\n <Chip url={g.path} name={g.name || g.slug} />\n </li>\n )),\n )}\n </ul>\n )}\n <p className=\"flex items-center gap-3 text-xs text-muted-foreground tabular-nums\" data-ckui=\"card-counts\">\n <span className=\"inline-flex items-center gap-1\" title={t(\"contents.favorites\")}>\n <HugeiconsIcon icon={FavouriteIcon} strokeWidth={2} className={cn(\"size-3.5\", card.viewer?.favorited && \"fill-current text-primary\")} aria-hidden />\n <span className=\"sr-only\">{t(\"contents.favorites\")}</span>\n {compact(card.counts.favorites, m.language)}\n </span>\n <span className=\"inline-flex items-center gap-1\" title={t(\"contents.likes\")}>\n <HugeiconsIcon icon={ThumbsUpIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n <span className=\"sr-only\">{t(\"contents.likes\")}</span>\n {compact(card.counts.likes, m.language)}\n </span>\n </p>\n {children}\n </div>\n </article>\n );\n}\n\n/** The group that owns a content (a channel): its avatar and name, linking to its page. */\nexport function OwnerChip({ group, href, className }: { group: GroupLike; href?: (g: GroupLike) => string | undefined; className?: string }) {\n return (\n <span className={cn(\"flex min-w-0 text-xs text-muted-foreground\", className)} data-ckui=\"card-owner\" data-id={group.id}>\n <GroupLink group={group} href={href} className=\"inline-flex min-w-0 items-center gap-1.5 hover:text-foreground\">\n <GroupAvatar group={group} className=\"size-5 shrink-0 text-[0.625rem]\" />\n <span className=\"truncate\">{group.name || group.slug}</span>\n </GroupLink>\n </span>\n );\n}\n\nfunction Chip({ url, name }: { url?: string; name: string }) {\n const onClick = useLink(url);\n const cls = \"inline-block rounded-md border border-border px-1.5 py-0.5 text-xs text-muted-foreground\";\n return url ? (\n <a href={url} onClick={onClick} className={cn(cls, \"hover:bg-muted hover:text-foreground\")}>\n {name}\n </a>\n ) : (\n <span className={cls}>{name}</span>\n );\n}\n\nexport interface PostCardProps extends Omit<ContentCardProps, \"shape\" | \"chips\" | \"groupKinds\"> {\n /** Above the title: the post's channel or author; default its owner group's chip. */\n byline?: ReactNode;\n}\n\n/** A post in a feed: a wide cover, its title, when it was published and what unlocking it costs. */\nexport function PostCard({ card, href, onOpen, tiers, labels, byline, owner = true, ownerHref, children, className }: PostCardProps) {\n const m = useMessages();\n const url = href ? href(card) : card.path;\n const open = useCallback(() => onOpen?.(card), [onOpen, card]);\n const onClick = useLink(url, open);\n const lock = lockLabel(m, card.access, tiers, labels);\n const title = card.title || card.code;\n return (\n <article className={cn(\"grid gap-3 rounded-lg border border-border p-3\", className)} data-ckui=\"post-card\" data-id={card.id} data-locked={lock ? \"\" : undefined} lang={card.language || undefined}>\n {byline ?? (owner && card.owner && <OwnerChip group={card.owner} href={ownerHref} className=\"text-sm\" />)}\n <a href={url} onClick={onClick} className=\"grid gap-2\">\n <span className=\"font-medium break-words hover:underline\" data-ckui=\"card-title\">\n {title}\n </span>\n {card.cover.length > 0 && (\n <span className=\"relative block aspect-video overflow-hidden rounded-md bg-muted\">\n <RenditionImg outputs={card.cover} alt=\"\" fit=\"cover\" skeleton className={cn(\"size-full\", lock && \"blur-md\")} />\n {lock && (\n <span className=\"absolute inset-0 grid place-items-center\">\n <span className=\"inline-flex items-center gap-1 rounded-md bg-background/85 px-2 py-1 text-xs font-medium shadow-sm\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n </span>\n )}\n </span>\n )}\n </a>\n <p className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {card.published_at && <RelativeTime at={card.published_at} />}\n {!card.cover.length && lock && <span data-ckui=\"card-lock\">{lock}</span>}\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <HugeiconsIcon icon={FavouriteIcon} strokeWidth={2} className={cn(\"size-3.5\", card.viewer?.favorited && \"fill-current text-primary\")} aria-hidden />\n <span className=\"sr-only\">{m.t(\"contents.favorites\")}</span>\n {compact(card.counts.favorites, m.language)}\n </span>\n </p>\n {children}\n </article>\n );\n}\n"],"mappings":"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"}