@openrails/contentkit-ui 0.85.1 → 0.87.0-rc.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.
Files changed (211) hide show
  1. package/README.md +28 -7
  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/content/types.d.ts +4 -2
  12. package/dist/client/content/types.js.map +1 -1
  13. package/dist/client/discovery.js +3 -0
  14. package/dist/client/discovery.js.map +1 -1
  15. package/dist/client/errors.d.ts +6 -0
  16. package/dist/client/errors.js +6 -2
  17. package/dist/client/errors.js.map +1 -1
  18. package/dist/client/gallery.d.ts +3 -5
  19. package/dist/client/gallery.js +16 -20
  20. package/dist/client/gallery.js.map +1 -1
  21. package/dist/client/generated/error-codes.d.ts +1 -1
  22. package/dist/client/generated/error-codes.js +9 -1
  23. package/dist/client/generated/error-codes.js.map +1 -1
  24. package/dist/client/generated/routes.d.ts +29 -3
  25. package/dist/client/generated/routes.js +44 -3
  26. package/dist/client/generated/routes.js.map +1 -1
  27. package/dist/client/generated/wire.d.ts +60 -10
  28. package/dist/client/generated/wire.js.map +1 -1
  29. package/dist/client/index.d.ts +2 -1
  30. package/dist/client/media/queue.d.ts +4 -2
  31. package/dist/client/media/queue.js +18 -12
  32. package/dist/client/media/queue.js.map +1 -1
  33. package/dist/client/media/rules.d.ts +7 -1
  34. package/dist/client/media/rules.js +27 -21
  35. package/dist/client/media/rules.js.map +1 -1
  36. package/dist/client/media/windows.js +1 -1
  37. package/dist/client/media/windows.js.map +1 -1
  38. package/dist/client/stats.d.ts +32 -0
  39. package/dist/client/stats.js +35 -0
  40. package/dist/client/stats.js.map +1 -0
  41. package/dist/client/versions.d.ts +6 -4
  42. package/dist/client/versions.js +24 -26
  43. package/dist/client/versions.js.map +1 -1
  44. package/dist/client.js +30 -29
  45. package/dist/components/admin/content-manager.js +206 -206
  46. package/dist/components/admin/content-manager.js.map +1 -1
  47. package/dist/components/admin/group-manager.js +71 -70
  48. package/dist/components/admin/group-manager.js.map +1 -1
  49. package/dist/components/admin/media-item-panel.d.ts +24 -7
  50. package/dist/components/admin/media-item-panel.js +209 -197
  51. package/dist/components/admin/media-item-panel.js.map +1 -1
  52. package/dist/components/admin/poll-manager.js +25 -25
  53. package/dist/components/admin/stats-dashboard.d.ts +22 -0
  54. package/dist/components/admin/stats-dashboard.js +257 -0
  55. package/dist/components/admin/stats-dashboard.js.map +1 -0
  56. package/dist/components/content/card.d.ts +5 -3
  57. package/dist/components/content/card.js +155 -133
  58. package/dist/components/content/card.js.map +1 -1
  59. package/dist/components/content/download-button.d.ts +26 -0
  60. package/dist/components/content/download-button.js +51 -0
  61. package/dist/components/content/download-button.js.map +1 -0
  62. package/dist/components/content/editors.js +455 -427
  63. package/dist/components/content/editors.js.map +1 -1
  64. package/dist/components/content/grid.d.ts +8 -1
  65. package/dist/components/content/grid.js +153 -104
  66. package/dist/components/content/grid.js.map +1 -1
  67. package/dist/components/content/index.d.ts +4 -2
  68. package/dist/components/content/listing.d.ts +3 -1
  69. package/dist/components/content/listing.js +34 -34
  70. package/dist/components/content/listing.js.map +1 -1
  71. package/dist/components/content/paywall.d.ts +17 -5
  72. package/dist/components/content/paywall.js +115 -100
  73. package/dist/components/content/paywall.js.map +1 -1
  74. package/dist/components/content/reader-stats.d.ts +12 -0
  75. package/dist/components/content/reader-stats.js +47 -0
  76. package/dist/components/content/reader-stats.js.map +1 -0
  77. package/dist/components/groups/banner.js +33 -33
  78. package/dist/components/groups/directory.js +11 -11
  79. package/dist/components/groups/editor.js +293 -293
  80. package/dist/components/groups/editor.js.map +1 -1
  81. package/dist/components/groups/header.js +27 -27
  82. package/dist/components/groups/header.js.map +1 -1
  83. package/dist/components/groups/memberships.js +180 -180
  84. package/dist/components/groups/parts.d.ts +0 -2
  85. package/dist/components/groups/parts.js +9 -27
  86. package/dist/components/groups/parts.js.map +1 -1
  87. package/dist/components/media-folder-editor.js +239 -189
  88. package/dist/components/media-folder-editor.js.map +1 -1
  89. package/dist/components/media-gallery.d.ts +1 -4
  90. package/dist/components/media-gallery.js +19 -34
  91. package/dist/components/media-gallery.js.map +1 -1
  92. package/dist/components/notifications.js +17 -17
  93. package/dist/components/slot-upload.d.ts +2 -2
  94. package/dist/components/slot-upload.js +84 -79
  95. package/dist/components/slot-upload.js.map +1 -1
  96. package/dist/components/social/bans.js +34 -34
  97. package/dist/components/social/comments.js +1 -1
  98. package/dist/components/social/comments.js.map +1 -1
  99. package/dist/components/social/engagement.d.ts +2 -1
  100. package/dist/components/social/engagement.js +5 -3
  101. package/dist/components/social/engagement.js.map +1 -1
  102. package/dist/components/social/moderation.js +52 -48
  103. package/dist/components/social/moderation.js.map +1 -1
  104. package/dist/components/video-player.d.ts +2 -0
  105. package/dist/components/video-player.js +318 -314
  106. package/dist/components/video-player.js.map +1 -1
  107. package/dist/i18n/context.d.ts +2 -0
  108. package/dist/i18n/context.js +23 -5
  109. package/dist/i18n/context.js.map +1 -1
  110. package/dist/index.d.ts +2 -1
  111. package/dist/index.js +13 -10
  112. package/dist/locales/admin/de.js +27 -0
  113. package/dist/locales/admin/de.js.map +1 -1
  114. package/dist/locales/admin/en.d.ts +27 -0
  115. package/dist/locales/admin/en.js +27 -0
  116. package/dist/locales/admin/en.js.map +1 -1
  117. package/dist/locales/admin/es.js +27 -0
  118. package/dist/locales/admin/es.js.map +1 -1
  119. package/dist/locales/admin/ja.js +27 -0
  120. package/dist/locales/admin/ja.js.map +1 -1
  121. package/dist/locales/admin/ko.js +27 -0
  122. package/dist/locales/admin/ko.js.map +1 -1
  123. package/dist/locales/admin/zh.js +27 -0
  124. package/dist/locales/admin/zh.js.map +1 -1
  125. package/dist/locales/content/de.js +6 -0
  126. package/dist/locales/content/de.js.map +1 -1
  127. package/dist/locales/content/en.d.ts +6 -0
  128. package/dist/locales/content/en.js +6 -0
  129. package/dist/locales/content/en.js.map +1 -1
  130. package/dist/locales/content/es.js +6 -0
  131. package/dist/locales/content/es.js.map +1 -1
  132. package/dist/locales/content/ja.js +6 -0
  133. package/dist/locales/content/ja.js.map +1 -1
  134. package/dist/locales/content/ko.js +6 -0
  135. package/dist/locales/content/ko.js.map +1 -1
  136. package/dist/locales/content/zh.js +6 -0
  137. package/dist/locales/content/zh.js.map +1 -1
  138. package/dist/locales/de.js +14 -2
  139. package/dist/locales/de.js.map +1 -1
  140. package/dist/locales/discovery/de.js +3 -0
  141. package/dist/locales/discovery/de.js.map +1 -1
  142. package/dist/locales/discovery/en.d.ts +3 -0
  143. package/dist/locales/discovery/en.js +3 -0
  144. package/dist/locales/discovery/en.js.map +1 -1
  145. package/dist/locales/discovery/es.js +3 -0
  146. package/dist/locales/discovery/es.js.map +1 -1
  147. package/dist/locales/discovery/ja.js +3 -0
  148. package/dist/locales/discovery/ja.js.map +1 -1
  149. package/dist/locales/discovery/ko.js +3 -0
  150. package/dist/locales/discovery/ko.js.map +1 -1
  151. package/dist/locales/discovery/zh.js +3 -0
  152. package/dist/locales/discovery/zh.js.map +1 -1
  153. package/dist/locales/en.d.ts +50 -2
  154. package/dist/locales/en.js +14 -2
  155. package/dist/locales/en.js.map +1 -1
  156. package/dist/locales/es.js +14 -2
  157. package/dist/locales/es.js.map +1 -1
  158. package/dist/locales/ja.js +14 -2
  159. package/dist/locales/ja.js.map +1 -1
  160. package/dist/locales/ko.js +14 -2
  161. package/dist/locales/ko.js.map +1 -1
  162. package/dist/locales/zh.js +14 -2
  163. package/dist/locales/zh.js.map +1 -1
  164. package/dist/react/admin.js +22 -23
  165. package/dist/react/admin.js.map +1 -1
  166. package/dist/react/canonical.d.ts +4 -3
  167. package/dist/react/canonical.js +34 -33
  168. package/dist/react/canonical.js.map +1 -1
  169. package/dist/react/comments.d.ts +1 -1
  170. package/dist/react/comments.js +3 -2
  171. package/dist/react/comments.js.map +1 -1
  172. package/dist/react/contents.js +144 -144
  173. package/dist/react/contents.js.map +1 -1
  174. package/dist/react/context.d.ts +1 -1
  175. package/dist/react/context.js.map +1 -1
  176. package/dist/react/detail.d.ts +12 -6
  177. package/dist/react/detail.js +31 -24
  178. package/dist/react/detail.js.map +1 -1
  179. package/dist/react/discovery.d.ts +11 -0
  180. package/dist/react/discovery.js +49 -30
  181. package/dist/react/discovery.js.map +1 -1
  182. package/dist/react/engagement.d.ts +6 -2
  183. package/dist/react/engagement.js +2 -6
  184. package/dist/react/engagement.js.map +1 -1
  185. package/dist/react/folder.d.ts +7 -2
  186. package/dist/react/folder.js +165 -147
  187. package/dist/react/folder.js.map +1 -1
  188. package/dist/react/groups.d.ts +0 -1
  189. package/dist/react/groups.js +13 -12
  190. package/dist/react/groups.js.map +1 -1
  191. package/dist/react/index.d.ts +6 -4
  192. package/dist/react/location.d.ts +11 -0
  193. package/dist/react/location.js +18 -0
  194. package/dist/react/location.js.map +1 -0
  195. package/dist/react/public.d.ts +8 -0
  196. package/dist/react/public.js +32 -27
  197. package/dist/react/public.js.map +1 -1
  198. package/dist/react/stats.d.ts +24 -0
  199. package/dist/react/stats.js +28 -0
  200. package/dist/react/stats.js.map +1 -0
  201. package/dist/react/use-resource.js +8 -7
  202. package/dist/react/use-resource.js.map +1 -1
  203. package/dist/react/versions.d.ts +25 -5
  204. package/dist/react/versions.js +61 -40
  205. package/dist/react/versions.js.map +1 -1
  206. package/dist/react.js +26 -24
  207. package/dist/styles.css +1 -1
  208. package/dist/urls/index.d.ts +35 -10
  209. package/dist/urls.js +100 -70
  210. package/dist/urls.js.map +1 -1
  211. 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";
@@ -11,148 +11,162 @@ import { cn as h } from "cn";
11
11
  import { FavouriteIcon as g, LockIcon as _, ThumbsUpIcon as v } from "@hugeicons/core-free-icons";
12
12
  import { HugeiconsIcon as y } from "@hugeicons/react";
13
13
  //#region src/components/content/card.tsx
14
- var b = (e, t) => new Intl.NumberFormat(t, {
14
+ function b({ card: e, decorate: t }) {
15
+ let n = t?.(e);
16
+ return n == null || n === !1 ? null : /* @__PURE__ */ p("span", {
17
+ className: "pointer-events-none absolute inset-0",
18
+ "data-ckui": "card-decorations",
19
+ children: n
20
+ });
21
+ }
22
+ var x = (e, t) => new Intl.NumberFormat(t, {
15
23
  notation: "compact",
16
24
  maximumFractionDigits: 1
17
25
  }).format(e);
18
- function x(e, t, n = [], r) {
26
+ function S(e, t, n = [], r) {
19
27
  if (t.allowed) return null;
20
28
  if (t.unknown) return e.t("contents.unknownAccess");
21
29
  let i = t.offers?.[0];
22
30
  if (i) return i.target.kind === "group" && Number(i.terms.amount) === 0 ? e.t("contents.joinFree") : u(e, i.terms);
23
31
  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");
32
+ 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
33
  }
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));
34
+ function C(e, t) {
35
+ let n = f(r)?.navigate;
36
+ return d((r) => {
37
+ t?.(), !(!n || !e || r.defaultPrevented || r.button !== 0 || r.metaKey || r.ctrlKey || r.shiftKey || r.altKey) && (r.preventDefault(), n(e));
30
38
  }, [
31
- r,
39
+ n,
32
40
  e,
33
41
  t
34
42
  ]);
35
43
  }
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 = [
38
- ...L.languages.map(N),
39
- ...L.traits.map((e) => c(E, e)),
40
- L.pages ? j.plural("contents.pages", L.pages) : null,
41
- B ? j.plural("contents.minutes", B) : null
42
- ].filter(Boolean), H = n.title || n.code;
44
+ function w({ card: r, href: a, onOpen: s, shape: c = "portrait", groupKinds: u, chips: f = 3, tiers: w, labels: D, owner: O = !0, ownerHref: k, decorate: A, children: j, className: M }) {
45
+ let N = n(), { t: P } = N, F = t(), I = a ? a(r) : r.path, L = C(I, d(() => s?.(r), [s, r])), R = S(N, r.access, w, D), z = r.version, B = r.viewer && r.viewer.progress_max > 0 ? Math.min(1, r.viewer.progress / r.viewer.progress_max) : 0, V = u ?? Object.keys(r.groups), H = z.duration_ms ? Math.round(z.duration_ms / 6e4) : 0, U = [
46
+ ...z.languages.map(F),
47
+ ...z.traits.map((e) => l(D, e)),
48
+ z.pages ? N.plural("contents.pages", z.pages) : null,
49
+ H ? N.plural("contents.minutes", H) : null
50
+ ].filter(Boolean), W = r.title || r.code;
43
51
  return /* @__PURE__ */ m("article", {
44
- className: h("group/card grid content-start gap-2", A),
52
+ className: h("group/card grid content-start gap-2", M),
45
53
  "data-ckui": "content-card",
46
- "data-id": n.id,
47
- "data-locked": I ? "" : void 0,
48
- lang: n.language || void 0,
49
- children: [/* @__PURE__ */ m("a", {
50
- href: P,
51
- onClick: F,
52
- className: "relative block overflow-hidden rounded-lg bg-muted",
53
- tabIndex: -1,
54
- "aria-hidden": !0,
55
- children: [
56
- /* @__PURE__ */ p("div", {
57
- className: s === "landscape" ? "aspect-video" : "aspect-[3/4]",
58
- children: /* @__PURE__ */ p(e, {
59
- outputs: n.cover,
60
- alt: "",
61
- fit: "cover",
62
- skeleton: !0,
63
- className: "size-full transition-transform group-hover/card:scale-[1.02]",
64
- fallback: /* @__PURE__ */ p("span", {
65
- className: "grid size-full place-items-center text-2xl font-semibold text-muted-foreground",
66
- children: H.charAt(0).toUpperCase()
54
+ "data-id": r.id,
55
+ "data-locked": R ? "" : void 0,
56
+ lang: r.language || void 0,
57
+ children: [/* @__PURE__ */ m("div", {
58
+ className: "relative overflow-hidden rounded-lg",
59
+ children: [/* @__PURE__ */ m("a", {
60
+ href: I,
61
+ onClick: L,
62
+ className: "relative block overflow-hidden rounded-lg bg-muted",
63
+ tabIndex: -1,
64
+ "aria-hidden": !0,
65
+ children: [
66
+ /* @__PURE__ */ p("div", {
67
+ className: c === "landscape" ? "aspect-video" : "aspect-[3/4]",
68
+ children: /* @__PURE__ */ p(e, {
69
+ outputs: r.cover,
70
+ alt: "",
71
+ fit: "cover",
72
+ skeleton: !0,
73
+ className: "size-full transition-transform group-hover/card:scale-[1.02]",
74
+ fallback: /* @__PURE__ */ p("span", {
75
+ className: "grid size-full place-items-center text-2xl font-semibold text-muted-foreground",
76
+ children: W.charAt(0).toUpperCase()
77
+ })
78
+ })
79
+ }),
80
+ R && /* @__PURE__ */ m("span", {
81
+ 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",
82
+ "data-ckui": "card-lock",
83
+ children: [/* @__PURE__ */ p(y, {
84
+ icon: _,
85
+ strokeWidth: 2,
86
+ className: "size-3.5",
87
+ "aria-hidden": !0
88
+ }), R]
89
+ }),
90
+ B > 0 && /* @__PURE__ */ p("span", {
91
+ className: "absolute inset-x-0 bottom-0 h-1 bg-background/60",
92
+ "data-ckui": "card-progress",
93
+ children: /* @__PURE__ */ p("span", {
94
+ className: "block h-full bg-primary",
95
+ style: { width: `${Math.round(B * 100)}%` }
67
96
  })
68
97
  })
69
- }),
70
- I && /* @__PURE__ */ m("span", {
71
- 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",
72
- "data-ckui": "card-lock",
73
- children: [/* @__PURE__ */ p(y, {
74
- icon: _,
75
- strokeWidth: 2,
76
- className: "size-3.5",
77
- "aria-hidden": !0
78
- }), I]
79
- }),
80
- R > 0 && /* @__PURE__ */ p("span", {
81
- className: "absolute inset-x-0 bottom-0 h-1 bg-background/60",
82
- "data-ckui": "card-progress",
83
- children: /* @__PURE__ */ p("span", {
84
- className: "block h-full bg-primary",
85
- style: { width: `${Math.round(R * 100)}%` }
86
- })
87
- })
88
- ]
98
+ ]
99
+ }), /* @__PURE__ */ p(b, {
100
+ card: r,
101
+ decorate: A
102
+ })]
89
103
  }), /* @__PURE__ */ m("div", {
90
104
  className: "grid gap-1",
91
105
  children: [
92
106
  /* @__PURE__ */ m("a", {
93
- href: P,
94
- onClick: F,
107
+ href: I,
108
+ onClick: L,
95
109
  className: "line-clamp-2 font-medium break-words hover:underline",
96
110
  "data-ckui": "card-title",
97
- children: [H, I && /* @__PURE__ */ m("span", {
111
+ children: [W, R && /* @__PURE__ */ m("span", {
98
112
  className: "sr-only",
99
113
  children: [
100
114
  " (",
101
- I,
115
+ R,
102
116
  ")"
103
117
  ]
104
118
  })]
105
119
  }),
106
- D && n.owner && /* @__PURE__ */ p(w, {
107
- group: n.owner,
108
- href: O
120
+ O && r.owner && /* @__PURE__ */ p(T, {
121
+ group: r.owner,
122
+ href: k
109
123
  }),
110
- V.length > 0 && /* @__PURE__ */ p("p", {
124
+ U.length > 0 && /* @__PURE__ */ p("p", {
111
125
  className: "text-xs text-muted-foreground",
112
- children: V.join(" · ")
126
+ children: U.join(" · ")
113
127
  }),
114
- n.viewer?.completed && /* @__PURE__ */ p(r, {
128
+ r.viewer?.completed && /* @__PURE__ */ p(i, {
115
129
  variant: "secondary",
116
130
  className: "justify-self-start",
117
- children: M("contents.completed")
131
+ children: P("contents.completed")
118
132
  }),
119
- !n.access.allowed && n.access.free_after && /* @__PURE__ */ p("p", {
133
+ !r.access.allowed && r.access.free_after && /* @__PURE__ */ p("p", {
120
134
  className: "text-xs text-muted-foreground",
121
- children: M("contents.freeFrom", { date: a(j, n.access.free_after, !1) })
135
+ children: P("contents.freeFrom", { date: o(N, r.access.free_after, !1) })
122
136
  }),
123
- z.some((e) => n.groups[e]?.length) && /* @__PURE__ */ p("ul", {
137
+ V.some((e) => r.groups[e]?.length) && /* @__PURE__ */ p("ul", {
124
138
  className: "flex flex-wrap gap-1",
125
139
  "data-ckui": "card-groups",
126
- children: z.flatMap((e) => (n.groups[e] ?? []).slice(0, f).map((t) => /* @__PURE__ */ p("li", {
140
+ children: V.flatMap((e) => (r.groups[e] ?? []).slice(0, f).map((t) => /* @__PURE__ */ p("li", {
127
141
  "data-kind": e,
128
- children: /* @__PURE__ */ p(T, {
142
+ children: /* @__PURE__ */ p(E, {
129
143
  url: t.path,
130
144
  name: t.name || t.slug
131
145
  })
132
146
  }, `${e}:${t.id}`)))
133
147
  }),
134
- /* @__PURE__ */ m("p", {
148
+ (r.counts.favorites !== void 0 || r.counts.likes !== void 0) && /* @__PURE__ */ m("p", {
135
149
  className: "flex items-center gap-3 text-xs text-muted-foreground tabular-nums",
136
150
  "data-ckui": "card-counts",
137
- children: [/* @__PURE__ */ m("span", {
151
+ children: [r.counts.favorites !== void 0 && /* @__PURE__ */ m("span", {
138
152
  className: "inline-flex items-center gap-1",
139
- title: M("contents.favorites"),
153
+ title: P("contents.favorites"),
140
154
  children: [
141
155
  /* @__PURE__ */ p(y, {
142
156
  icon: g,
143
157
  strokeWidth: 2,
144
- className: h("size-3.5", n.viewer?.favorited && "fill-current text-primary"),
158
+ className: h("size-3.5", r.viewer?.favorited && "fill-current text-primary"),
145
159
  "aria-hidden": !0
146
160
  }),
147
161
  /* @__PURE__ */ p("span", {
148
162
  className: "sr-only",
149
- children: M("contents.favorites")
163
+ children: P("contents.favorites")
150
164
  }),
151
- b(n.counts.favorites, j.language)
165
+ x(r.counts.favorites, N.language)
152
166
  ]
153
- }), /* @__PURE__ */ m("span", {
167
+ }), r.counts.likes !== void 0 && /* @__PURE__ */ m("span", {
154
168
  className: "inline-flex items-center gap-1",
155
- title: M("contents.likes"),
169
+ title: P("contents.likes"),
156
170
  children: [
157
171
  /* @__PURE__ */ p(y, {
158
172
  icon: v,
@@ -162,27 +176,27 @@ function C({ card: n, href: i, onOpen: o, shape: s = "portrait", groupKinds: u,
162
176
  }),
163
177
  /* @__PURE__ */ p("span", {
164
178
  className: "sr-only",
165
- children: M("contents.likes")
179
+ children: P("contents.likes")
166
180
  }),
167
- b(n.counts.likes, j.language)
181
+ x(r.counts.likes, N.language)
168
182
  ]
169
183
  })]
170
184
  }),
171
- k
185
+ j
172
186
  ]
173
187
  })]
174
188
  });
175
189
  }
176
- function w({ group: e, href: t, className: n }) {
190
+ function T({ group: e, href: t, className: n }) {
177
191
  return /* @__PURE__ */ p("span", {
178
192
  className: h("flex min-w-0 text-xs text-muted-foreground", n),
179
193
  "data-ckui": "card-owner",
180
194
  "data-id": e.id,
181
- children: /* @__PURE__ */ m(s, {
195
+ children: /* @__PURE__ */ m(c, {
182
196
  group: e,
183
197
  href: t,
184
198
  className: "inline-flex min-w-0 items-center gap-1.5 hover:text-foreground",
185
- children: [/* @__PURE__ */ p(o, {
199
+ children: [/* @__PURE__ */ p(s, {
186
200
  group: e,
187
201
  className: "size-5 shrink-0 text-[0.625rem]"
188
202
  }), /* @__PURE__ */ p("span", {
@@ -192,8 +206,8 @@ function w({ group: e, href: t, className: n }) {
192
206
  })
193
207
  });
194
208
  }
195
- function T({ url: e, name: t }) {
196
- let n = S(e), r = "inline-block rounded-md border border-border px-1.5 py-0.5 text-xs text-muted-foreground";
209
+ function E({ url: e, name: t }) {
210
+ let n = C(e), r = "inline-block rounded-md border border-border px-1.5 py-0.5 text-xs text-muted-foreground";
197
211
  return e ? /* @__PURE__ */ p("a", {
198
212
  href: e,
199
213
  onClick: n,
@@ -204,82 +218,90 @@ function T({ url: e, name: t }) {
204
218
  children: t
205
219
  });
206
220
  }
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;
221
+ function D({ card: t, href: r, onOpen: i, tiers: o, labels: s, byline: c, owner: l = !0, ownerHref: u, decorate: f, children: v, className: w }) {
222
+ let E = n(), D = r ? r(t) : t.path, O = C(D, d(() => i?.(t), [i, t])), k = S(E, t.access, o, s), A = t.title || t.code;
209
223
  return /* @__PURE__ */ m("article", {
210
- className: h("grid gap-3 rounded-lg border border-border p-3", v),
224
+ className: h("grid gap-3 rounded-lg border border-border p-3", w),
211
225
  "data-ckui": "post-card",
212
- "data-id": n.id,
213
- "data-locked": D ? "" : void 0,
214
- lang: n.language || void 0,
226
+ "data-id": t.id,
227
+ "data-locked": k ? "" : void 0,
228
+ lang: t.language || void 0,
215
229
  children: [
216
- c ?? (l && n.owner && /* @__PURE__ */ p(w, {
217
- group: n.owner,
230
+ c ?? (l && t.owner && /* @__PURE__ */ p(T, {
231
+ group: t.owner,
218
232
  href: u,
219
233
  className: "text-sm"
220
234
  })),
221
235
  /* @__PURE__ */ m("a", {
222
- href: T,
223
- onClick: E,
236
+ href: D,
237
+ onClick: O,
224
238
  className: "grid gap-2",
225
239
  children: [/* @__PURE__ */ p("span", {
226
240
  className: "font-medium break-words hover:underline",
227
241
  "data-ckui": "card-title",
228
- children: O
229
- }), n.cover.length > 0 && /* @__PURE__ */ m("span", {
242
+ children: A
243
+ }), t.cover.length > 0 && /* @__PURE__ */ m("span", {
230
244
  className: "relative block aspect-video overflow-hidden rounded-md bg-muted",
231
- children: [/* @__PURE__ */ p(e, {
232
- outputs: n.cover,
233
- alt: "",
234
- fit: "cover",
235
- skeleton: !0,
236
- className: h("size-full", D && "blur-md")
237
- }), D && /* @__PURE__ */ p("span", {
238
- className: "absolute inset-0 grid place-items-center",
239
- children: /* @__PURE__ */ m("span", {
240
- className: "inline-flex items-center gap-1 rounded-md bg-background/85 px-2 py-1 text-xs font-medium shadow-sm",
241
- "data-ckui": "card-lock",
242
- children: [/* @__PURE__ */ p(y, {
243
- icon: _,
244
- strokeWidth: 2,
245
- className: "size-3.5",
246
- "aria-hidden": !0
247
- }), D]
245
+ children: [
246
+ /* @__PURE__ */ p(e, {
247
+ outputs: t.cover,
248
+ alt: "",
249
+ fit: "cover",
250
+ skeleton: !0,
251
+ className: h("size-full", k && "blur-md")
252
+ }),
253
+ /* @__PURE__ */ p(b, {
254
+ card: t,
255
+ decorate: f
256
+ }),
257
+ k && /* @__PURE__ */ p("span", {
258
+ className: "absolute inset-0 grid place-items-center",
259
+ children: /* @__PURE__ */ m("span", {
260
+ className: "inline-flex items-center gap-1 rounded-md bg-background/85 px-2 py-1 text-xs font-medium shadow-sm",
261
+ "data-ckui": "card-lock",
262
+ children: [/* @__PURE__ */ p(y, {
263
+ icon: _,
264
+ strokeWidth: 2,
265
+ className: "size-3.5",
266
+ "aria-hidden": !0
267
+ }), k]
268
+ })
248
269
  })
249
- })]
270
+ ]
250
271
  })]
251
272
  }),
252
273
  /* @__PURE__ */ m("p", {
253
274
  className: "flex flex-wrap items-center gap-3 text-xs text-muted-foreground",
254
275
  children: [
255
- n.published_at && /* @__PURE__ */ p(i, { at: n.published_at }),
256
- !n.cover.length && D && /* @__PURE__ */ p("span", {
276
+ t.published_at && /* @__PURE__ */ p(a, { at: t.published_at }),
277
+ !t.cover.length && k && /* @__PURE__ */ p("span", {
257
278
  "data-ckui": "card-lock",
258
- children: D
279
+ children: k
259
280
  }),
260
- /* @__PURE__ */ m("span", {
281
+ t.counts.favorites !== void 0 && /* @__PURE__ */ m("span", {
261
282
  className: "inline-flex items-center gap-1 tabular-nums",
283
+ "data-ckui": "card-counts",
262
284
  children: [
263
285
  /* @__PURE__ */ p(y, {
264
286
  icon: g,
265
287
  strokeWidth: 2,
266
- className: h("size-3.5", n.viewer?.favorited && "fill-current text-primary"),
288
+ className: h("size-3.5", t.viewer?.favorited && "fill-current text-primary"),
267
289
  "aria-hidden": !0
268
290
  }),
269
291
  /* @__PURE__ */ p("span", {
270
292
  className: "sr-only",
271
- children: C.t("contents.favorites")
293
+ children: E.t("contents.favorites")
272
294
  }),
273
- b(n.counts.favorites, C.language)
295
+ x(t.counts.favorites, E.language)
274
296
  ]
275
297
  })
276
298
  ]
277
299
  }),
278
- f
300
+ v
279
301
  ]
280
302
  });
281
303
  }
282
304
  //#endregion
283
- export { C as ContentCard, w as OwnerChip, E as PostCard, x as lockLabel };
305
+ export { w as ContentCard, T as OwnerChip, D as PostCard, S as lockLabel };
284
306
 
285
307
  //# sourceMappingURL=card.js.map
@@ -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 /** Over the cover: the host's badges for the card, placed in the cover's box; clicks reach the card. */\n decorate?: (card: Card) => ReactNode;\n /** Below the meta line: the host's own. */\n children?: ReactNode;\n className?: string;\n}\n\n/** The host's badges over a cover. */\nfunction Decorations({ card, decorate }: { card: Card; decorate?: (card: Card) => ReactNode }) {\n const badges = decorate?.(card);\n if (badges == null || badges === false) return null;\n return (\n <span className=\"pointer-events-none absolute inset-0\" data-ckui=\"card-decorations\">\n {badges}\n </span>\n );\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; what its kind's locked view hides is absent. */\nexport function ContentCard({ card, href, onOpen, shape = \"portrait\", groupKinds, chips = 3, tiers, labels, owner = true, ownerHref, decorate, 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 <div className=\"relative overflow-hidden rounded-lg\">\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 <Decorations card={card} decorate={decorate} />\n </div>\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 {(card.counts.favorites !== undefined || card.counts.likes !== undefined) && (\n <p className=\"flex items-center gap-3 text-xs text-muted-foreground tabular-nums\" data-ckui=\"card-counts\">\n {card.counts.favorites !== undefined && (\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 )}\n {card.counts.likes !== undefined && (\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 )}\n </p>\n )}\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, decorate, 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 <Decorations card={card} decorate={decorate} />\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 {card.counts.favorites !== undefined && (\n <span className=\"inline-flex items-center gap-1 tabular-nums\" data-ckui=\"card-counts\">\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 )}\n </p>\n {children}\n </article>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { ContentKitClient } from '../../client/client.js';
2
+ import { FileDownload } from '../../client/generated/wire.js';
3
+ import { ContentKitUiAppearance } from '../../appearance.js';
4
+ import { ContentKitErrorHandler } from '../../react/context.js';
5
+ export interface VersionDownloadButtonProps {
6
+ content: string;
7
+ version: string;
8
+ /** Hears each granted download. */
9
+ onDownload?: (d: FileDownload) => void;
10
+ /** Called instead of showing the locked notice (a paywall, sign in). */
11
+ onLocked?: () => void;
12
+ size?: "sm" | "default";
13
+ variant?: "default" | "secondary" | "outline" | "ghost";
14
+ client?: ContentKitClient;
15
+ onError?: ContentKitErrorHandler;
16
+ className?: string;
17
+ appearance?: ContentKitUiAppearance;
18
+ }
19
+ /**
20
+ * Downloads a version, as its kind serves it (the original video, a
21
+ * rendition, its pages as a zip): the browser saves the file, charged to the
22
+ * viewer's download quota. It waits while the file is prepared, says when a
23
+ * quota refused and for how long, when the version is locked, and when the
24
+ * site's download policy holds it back ("available to download in N days").
25
+ */
26
+ export declare function VersionDownloadButton({ content, version, onDownload, onLocked, size, variant, client, onError, className, appearance }: VersionDownloadButtonProps): import('react').JSX.Element;
@@ -0,0 +1,51 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { ContentKitUiRoot as t } from "../../scope.js";
3
+ import { useErrorReporter as n } from "../../react/context.js";
4
+ import { Button as r } from "../../ui/button.js";
5
+ import { useVersionDownload as i } from "../../react/versions.js";
6
+ import { useEffect as a, useState as o } from "react";
7
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
8
+ import { cn as l } from "cn";
9
+ import { Download01Icon as u } from "@hugeicons/core-free-icons";
10
+ import { HugeiconsIcon as d } from "@hugeicons/react";
11
+ //#region src/components/content/download-button.tsx
12
+ function f({ content: f, version: p, onDownload: m, onLocked: h, size: g = "default", variant: _ = "secondary", client: v, onError: y, className: b, appearance: x }) {
13
+ let { t: S } = e(), C = i(f, p, { client: v }), w = n(y), [T, E] = o(() => Date.now());
14
+ a(() => {
15
+ if (C.retryAt === void 0) return;
16
+ let e = () => E(Date.now()), t = setTimeout(e, 0), n = setInterval(e, 15e3);
17
+ return () => {
18
+ clearTimeout(t), clearInterval(n);
19
+ };
20
+ }, [C.retryAt]);
21
+ let D = C.retryAt === void 0 ? 0 : C.retryAt - T, O = D > 0, k = () => {
22
+ C.download().then(m, (e) => {
23
+ if (e.code === "forbidden" && h) return h();
24
+ w(e, "version.download");
25
+ });
26
+ }, A = O ? S("versionDownload.limited", { minutes: Math.max(1, Math.ceil(D / 6e4)) }) : C.error?.code === "rate_limited" || C.error?.code === "forbidden" && h ? void 0 : C.message;
27
+ return /* @__PURE__ */ c(t, {
28
+ appearance: x,
29
+ className: l("inline-flex flex-col items-start gap-1", b),
30
+ "data-ckui": "version-download",
31
+ children: [/* @__PURE__ */ c(r, {
32
+ variant: _,
33
+ size: g,
34
+ disabled: C.preparing || O,
35
+ "aria-busy": C.preparing,
36
+ onClick: k,
37
+ children: [/* @__PURE__ */ s(d, {
38
+ icon: u,
39
+ strokeWidth: 2
40
+ }), /* @__PURE__ */ s("span", { children: C.preparing ? S("versionDownload.preparing") : S("versionDownload.label") })]
41
+ }), A && /* @__PURE__ */ s("p", {
42
+ role: "status",
43
+ className: "text-xs text-muted-foreground",
44
+ children: A
45
+ })]
46
+ });
47
+ }
48
+ //#endregion
49
+ export { f as VersionDownloadButton };
50
+
51
+ //# sourceMappingURL=download-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-button.js","names":[],"sources":["../../../src/components/content/download-button.tsx"],"sourcesContent":["import { Download01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useEffect, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { FileDownload } from \"../../client/generated/wire.js\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useVersionDownload } from \"../../react/versions.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\n\nexport interface VersionDownloadButtonProps {\n content: string;\n version: string;\n /** Hears each granted download. */\n onDownload?: (d: FileDownload) => void;\n /** Called instead of showing the locked notice (a paywall, sign in). */\n onLocked?: () => void;\n size?: \"sm\" | \"default\";\n variant?: \"default\" | \"secondary\" | \"outline\" | \"ghost\";\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/**\n * Downloads a version, as its kind serves it (the original video, a\n * rendition, its pages as a zip): the browser saves the file, charged to the\n * viewer's download quota. It waits while the file is prepared, says when a\n * quota refused and for how long, when the version is locked, and when the\n * site's download policy holds it back (\"available to download in N days\").\n */\nexport function VersionDownloadButton({ content, version, onDownload, onLocked, size = \"default\", variant = \"secondary\", client, onError, className, appearance }: VersionDownloadButtonProps) {\n const { t } = useMessages();\n const d = useVersionDownload(content, version, { client });\n const report = useErrorReporter(onError);\n // The clock the quota's countdown reads, kept while one holds.\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (d.retryAt === undefined) return;\n const tick = () => setNow(Date.now());\n const first = setTimeout(tick, 0);\n const every = setInterval(tick, 15_000);\n return () => {\n clearTimeout(first);\n clearInterval(every);\n };\n }, [d.retryAt]);\n const left = d.retryAt === undefined ? 0 : d.retryAt - now;\n const limited = left > 0;\n const run = () => {\n d.download().then(onDownload, (err) => {\n if (err.code === \"forbidden\" && onLocked) return onLocked();\n report(err, \"version.download\");\n });\n };\n // A quota's notice counts down; a locked version goes to the host's onLocked.\n const notice = limited\n ? t(\"versionDownload.limited\", { minutes: Math.max(1, Math.ceil(left / 60_000)) })\n : d.error?.code === \"rate_limited\" || (d.error?.code === \"forbidden\" && onLocked)\n ? undefined\n : d.message;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"inline-flex flex-col items-start gap-1\", className)} data-ckui=\"version-download\">\n <Button variant={variant} size={size} disabled={d.preparing || limited} aria-busy={d.preparing} onClick={run}>\n <HugeiconsIcon icon={Download01Icon} strokeWidth={2} />\n <span>{d.preparing ? t(\"versionDownload.preparing\") : t(\"versionDownload.label\")}</span>\n </Button>\n {notice && (\n <p role=\"status\" className=\"text-xs text-muted-foreground\">\n {notice}\n </p>\n )}\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;;;AAmCA,SAAgB,EAAsB,EAAE,YAAS,YAAS,eAAY,aAAU,UAAO,WAAW,aAAU,aAAa,WAAQ,YAAS,cAAW,iBAA0C;CAC7L,IAAM,EAAE,SAAM,EAAY,GACpB,IAAI,EAAmB,GAAS,GAAS,EAAE,UAAO,CAAC,GACnD,IAAS,EAAiB,CAAO,GAEjC,CAAC,GAAK,KAAU,QAAe,KAAK,IAAI,CAAC;CAC/C,QAAgB;EACd,IAAI,EAAE,YAAY,KAAA,GAAW;EAC7B,IAAM,UAAa,EAAO,KAAK,IAAI,CAAC,GAC9B,IAAQ,WAAW,GAAM,CAAC,GAC1B,IAAQ,YAAY,GAAM,IAAM;EACtC,aAAa;GAEX,AADA,aAAa,CAAK,GAClB,cAAc,CAAK;EACrB;CACF,GAAG,CAAC,EAAE,OAAO,CAAC;CACd,IAAM,IAAO,EAAE,YAAY,KAAA,IAAY,IAAI,EAAE,UAAU,GACjD,IAAU,IAAO,GACjB,UAAY;EAChB,EAAE,SAAS,CAAC,CAAC,KAAK,IAAa,MAAQ;GACrC,IAAI,EAAI,SAAS,eAAe,GAAU,OAAO,EAAS;GAC1D,EAAO,GAAK,kBAAkB;EAChC,CAAC;CACH,GAEM,IAAS,IACX,EAAE,2BAA2B,EAAE,SAAS,KAAK,IAAI,GAAG,KAAK,KAAK,IAAO,GAAM,CAAC,EAAE,CAAC,IAC/E,EAAE,OAAO,SAAS,kBAAmB,EAAE,OAAO,SAAS,eAAe,IACpE,KAAA,IACA,EAAE;CACR,OACE,kBAAC,GAAD;EAA8B;EAAY,WAAW,EAAG,0CAA0C,CAAS;EAAG,aAAU;EAAxH,UAAA,CACE,kBAAC,GAAD;GAAiB;GAAe;GAAM,UAAU,EAAE,aAAa;GAAS,aAAW,EAAE;GAAW,SAAS;GAAzG,UAAA,CACE,kBAAC,GAAD;IAAe,MAAM;IAAgB,aAAa;GAAI,CAAA,GACtD,kBAAC,QAAD,EAAA,UAAO,EAAE,YAAY,EAAE,2BAA2B,IAAI,EAAE,uBAAuB,EAAQ,CAAA,CACjF;EACP,CAAA,GAAA,KACC,kBAAC,KAAD;GAAG,MAAK;GAAS,WAAU;GACxB,UAAA;EACA,CAAA,CAEW;;AAEtB"}