@openrails/contentkit-ui 0.104.0-rc.1 → 0.104.0-rc.2

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 (210) hide show
  1. package/README.md +6 -6
  2. package/dist/client/beacons.js +4 -1
  3. package/dist/client/beacons.js.map +1 -1
  4. package/dist/client/contents.d.ts +7 -1
  5. package/dist/client/contents.js +8 -0
  6. package/dist/client/contents.js.map +1 -1
  7. package/dist/client/discovery.d.ts +6 -2
  8. package/dist/client/discovery.js +1 -1
  9. package/dist/client/discovery.js.map +1 -1
  10. package/dist/client/generated/routes.d.ts +41 -28
  11. package/dist/client/generated/routes.js +63 -61
  12. package/dist/client/generated/routes.js.map +1 -1
  13. package/dist/client/generated/wire.d.ts +36 -74
  14. package/dist/client/generated/wire.js.map +1 -1
  15. package/dist/client/index.d.ts +1 -1
  16. package/dist/client/media/windows.d.ts +2 -0
  17. package/dist/client/media/windows.js +4 -1
  18. package/dist/client/media/windows.js.map +1 -1
  19. package/dist/client/stats.d.ts +4 -20
  20. package/dist/client/stats.js +0 -11
  21. package/dist/client/stats.js.map +1 -1
  22. package/dist/client/versions.d.ts +8 -6
  23. package/dist/client/versions.js +15 -10
  24. package/dist/client/versions.js.map +1 -1
  25. package/dist/components/admin/bulk-drafts.d.ts +17 -0
  26. package/dist/components/admin/bulk-drafts.js +41 -0
  27. package/dist/components/admin/bulk-drafts.js.map +1 -0
  28. package/dist/components/admin/content-admin.d.ts +1 -1
  29. package/dist/components/admin/content-admin.js.map +1 -1
  30. package/dist/components/admin/content-manager.d.ts +11 -3
  31. package/dist/components/admin/content-manager.js +739 -380
  32. package/dist/components/admin/content-manager.js.map +1 -1
  33. package/dist/components/admin/group-manager.d.ts +8 -3
  34. package/dist/components/admin/group-manager.js +218 -146
  35. package/dist/components/admin/group-manager.js.map +1 -1
  36. package/dist/components/admin/page-notices.d.ts +16 -0
  37. package/dist/components/admin/page-notices.js +113 -0
  38. package/dist/components/admin/page-notices.js.map +1 -0
  39. package/dist/components/admin/page-picker.d.ts +38 -0
  40. package/dist/components/admin/page-picker.js +159 -0
  41. package/dist/components/admin/page-picker.js.map +1 -0
  42. package/dist/components/admin/reader-content-editor.js +537 -500
  43. package/dist/components/admin/reader-content-editor.js.map +1 -1
  44. package/dist/components/admin/reader-manifest.js +181 -179
  45. package/dist/components/admin/reader-manifest.js.map +1 -1
  46. package/dist/components/admin/shell.d.ts +10 -2
  47. package/dist/components/admin/shell.js +114 -42
  48. package/dist/components/admin/shell.js.map +1 -1
  49. package/dist/components/admin/stats-dashboard.d.ts +4 -9
  50. package/dist/components/admin/stats-dashboard.js +300 -202
  51. package/dist/components/admin/stats-dashboard.js.map +1 -1
  52. package/dist/components/admin/upload-queue.d.ts +14 -0
  53. package/dist/components/admin/upload-queue.js +115 -98
  54. package/dist/components/admin/upload-queue.js.map +1 -1
  55. package/dist/components/content/card.d.ts +31 -3
  56. package/dist/components/content/card.js +324 -158
  57. package/dist/components/content/card.js.map +1 -1
  58. package/dist/components/content/daily-releases.js +14 -12
  59. package/dist/components/content/daily-releases.js.map +1 -1
  60. package/dist/components/content/editor-save.js +27 -25
  61. package/dist/components/content/editor-save.js.map +1 -1
  62. package/dist/components/content/editors.js +274 -253
  63. package/dist/components/content/editors.js.map +1 -1
  64. package/dist/components/content/favorites.d.ts +1 -1
  65. package/dist/components/content/favorites.js +36 -22
  66. package/dist/components/content/favorites.js.map +1 -1
  67. package/dist/components/content/grid.d.ts +11 -4
  68. package/dist/components/content/grid.js +52 -43
  69. package/dist/components/content/grid.js.map +1 -1
  70. package/dist/components/content/history.js +83 -63
  71. package/dist/components/content/history.js.map +1 -1
  72. package/dist/components/content/index.d.ts +5 -3
  73. package/dist/components/content/listing.d.ts +14 -3
  74. package/dist/components/content/listing.js +65 -56
  75. package/dist/components/content/listing.js.map +1 -1
  76. package/dist/components/content/offer-fields.js +6 -6
  77. package/dist/components/content/paged-grid.d.ts +10 -0
  78. package/dist/components/content/paged-grid.js +116 -0
  79. package/dist/components/content/paged-grid.js.map +1 -0
  80. package/dist/components/content/pagination.d.ts +19 -0
  81. package/dist/components/content/pagination.js +97 -0
  82. package/dist/components/content/pagination.js.map +1 -0
  83. package/dist/components/content/paywall.d.ts +9 -0
  84. package/dist/components/content/paywall.js +27 -3
  85. package/dist/components/content/paywall.js.map +1 -1
  86. package/dist/components/content/post-list.d.ts +37 -0
  87. package/dist/components/content/post-list.js +203 -0
  88. package/dist/components/content/post-list.js.map +1 -0
  89. package/dist/components/content/reader-settings.d.ts +2 -0
  90. package/dist/components/content/reader-settings.js +1 -1
  91. package/dist/components/content/reader-settings.js.map +1 -1
  92. package/dist/components/groups/banner.d.ts +6 -2
  93. package/dist/components/groups/banner.js +79 -61
  94. package/dist/components/groups/banner.js.map +1 -1
  95. package/dist/components/groups/directory.d.ts +12 -2
  96. package/dist/components/groups/directory.js +168 -105
  97. package/dist/components/groups/directory.js.map +1 -1
  98. package/dist/components/groups/editor.js +10 -10
  99. package/dist/components/groups/editor.js.map +1 -1
  100. package/dist/components/groups/entity-chips.d.ts +61 -0
  101. package/dist/components/groups/entity-chips.js +382 -0
  102. package/dist/components/groups/entity-chips.js.map +1 -0
  103. package/dist/components/groups/group-members.d.ts +29 -0
  104. package/dist/components/groups/group-members.js +474 -0
  105. package/dist/components/groups/group-members.js.map +1 -0
  106. package/dist/components/groups/header.js +53 -45
  107. package/dist/components/groups/header.js.map +1 -1
  108. package/dist/components/groups/index.d.ts +3 -1
  109. package/dist/components/groups/memberships.d.ts +3 -36
  110. package/dist/components/groups/memberships.js +105 -480
  111. package/dist/components/groups/memberships.js.map +1 -1
  112. package/dist/components/groups/scoped.d.ts +23 -0
  113. package/dist/components/groups/scoped.js +92 -0
  114. package/dist/components/groups/scoped.js.map +1 -0
  115. package/dist/components/media-folder-editor.d.ts +5 -0
  116. package/dist/components/media-folder-editor.js +418 -230
  117. package/dist/components/media-folder-editor.js.map +1 -1
  118. package/dist/components/sortable-list.d.ts +5 -1
  119. package/dist/components/sortable-list.js +97 -81
  120. package/dist/components/sortable-list.js.map +1 -1
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.js +41 -37
  123. package/dist/locales/admin/de.js +78 -45
  124. package/dist/locales/admin/de.js.map +1 -1
  125. package/dist/locales/admin/en.d.ts +74 -41
  126. package/dist/locales/admin/en.js +78 -45
  127. package/dist/locales/admin/en.js.map +1 -1
  128. package/dist/locales/admin/es.js +78 -45
  129. package/dist/locales/admin/es.js.map +1 -1
  130. package/dist/locales/admin/ja.js +78 -45
  131. package/dist/locales/admin/ja.js.map +1 -1
  132. package/dist/locales/admin/ko.js +78 -45
  133. package/dist/locales/admin/ko.js.map +1 -1
  134. package/dist/locales/admin/zh.js +78 -45
  135. package/dist/locales/admin/zh.js.map +1 -1
  136. package/dist/locales/de.js +1 -0
  137. package/dist/locales/de.js.map +1 -1
  138. package/dist/locales/discovery/de.js +25 -1
  139. package/dist/locales/discovery/de.js.map +1 -1
  140. package/dist/locales/discovery/en.d.ts +24 -0
  141. package/dist/locales/discovery/en.js +25 -1
  142. package/dist/locales/discovery/en.js.map +1 -1
  143. package/dist/locales/discovery/es.js +25 -1
  144. package/dist/locales/discovery/es.js.map +1 -1
  145. package/dist/locales/discovery/ja.js +25 -1
  146. package/dist/locales/discovery/ja.js.map +1 -1
  147. package/dist/locales/discovery/ko.js +25 -1
  148. package/dist/locales/discovery/ko.js.map +1 -1
  149. package/dist/locales/discovery/zh.js +25 -1
  150. package/dist/locales/discovery/zh.js.map +1 -1
  151. package/dist/locales/en.d.ts +119 -43
  152. package/dist/locales/en.js +1 -0
  153. package/dist/locales/en.js.map +1 -1
  154. package/dist/locales/es.js +1 -0
  155. package/dist/locales/es.js.map +1 -1
  156. package/dist/locales/groups/de.js +22 -4
  157. package/dist/locales/groups/de.js.map +1 -1
  158. package/dist/locales/groups/en.d.ts +20 -2
  159. package/dist/locales/groups/en.js +22 -4
  160. package/dist/locales/groups/en.js.map +1 -1
  161. package/dist/locales/groups/es.js +22 -4
  162. package/dist/locales/groups/es.js.map +1 -1
  163. package/dist/locales/groups/ja.js +22 -4
  164. package/dist/locales/groups/ja.js.map +1 -1
  165. package/dist/locales/groups/ko.js +22 -4
  166. package/dist/locales/groups/ko.js.map +1 -1
  167. package/dist/locales/groups/zh.js +22 -4
  168. package/dist/locales/groups/zh.js.map +1 -1
  169. package/dist/locales/ja.js +1 -0
  170. package/dist/locales/ja.js.map +1 -1
  171. package/dist/locales/ko.js +1 -0
  172. package/dist/locales/ko.js.map +1 -1
  173. package/dist/locales/zh.js +1 -0
  174. package/dist/locales/zh.js.map +1 -1
  175. package/dist/react/discovery.d.ts +16 -0
  176. package/dist/react/discovery.js +69 -37
  177. package/dist/react/discovery.js.map +1 -1
  178. package/dist/react/groups.d.ts +19 -3
  179. package/dist/react/groups.js +49 -22
  180. package/dist/react/groups.js.map +1 -1
  181. package/dist/react/index.d.ts +6 -4
  182. package/dist/react/page-notices.d.ts +13 -0
  183. package/dist/react/page-notices.js +38 -0
  184. package/dist/react/page-notices.js.map +1 -0
  185. package/dist/react/post-reads.d.ts +23 -0
  186. package/dist/react/post-reads.js +90 -0
  187. package/dist/react/post-reads.js.map +1 -0
  188. package/dist/react/remembered-sort.d.ts +5 -0
  189. package/dist/react/remembered-sort.js +29 -0
  190. package/dist/react/remembered-sort.js.map +1 -0
  191. package/dist/react/resources.js +2 -2
  192. package/dist/react/resources.js.map +1 -1
  193. package/dist/react/stats.d.ts +3 -7
  194. package/dist/react/stats.js +2 -6
  195. package/dist/react/stats.js.map +1 -1
  196. package/dist/react/use-engagement.d.ts +2 -0
  197. package/dist/react/use-engagement.js +8 -3
  198. package/dist/react/use-engagement.js.map +1 -1
  199. package/dist/react.js +29 -27
  200. package/dist/styles.css +1 -1
  201. package/dist/urls/index.d.ts +1 -1
  202. package/dist/urls.js +2 -2
  203. package/dist/urls.js.map +1 -1
  204. package/package.json +1 -1
  205. package/dist/components/admin/page-review.d.ts +0 -13
  206. package/dist/components/admin/page-review.js +0 -225
  207. package/dist/components/admin/page-review.js.map +0 -1
  208. package/dist/react/page-review.d.ts +0 -14
  209. package/dist/react/page-review.js +0 -37
  210. package/dist/react/page-review.js.map +0 -1
@@ -145,7 +145,31 @@ function M(e, t, n) {
145
145
  c
146
146
  ]);
147
147
  }
148
- function N({ versions: r, value: i, onChange: a, labels: f, className: p, appearance: m }) {
148
+ function N({ versions: r, value: i, onChange: a, language: s, className: c, appearance: l }) {
149
+ let { t: u } = t(), d = e(), { current: f, languages: p, change: m } = M(r, i, a);
150
+ if (!f || !s || f.languages.includes(s)) return null;
151
+ let h = [...r].sort((e, t) => e.position - t.position);
152
+ return /* @__PURE__ */ w(n, {
153
+ appearance: l,
154
+ role: "note",
155
+ className: T("flex flex-wrap items-center gap-2 text-sm text-muted-foreground", c),
156
+ "data-ckui": "language-fallback",
157
+ children: [/* @__PURE__ */ C("span", { children: u("contents.notInLanguage", {
158
+ language: d(s),
159
+ shown: d(j(f))
160
+ }) }), p.length > 1 && p.map((e) => {
161
+ let t = h.find((t) => j(t) === e);
162
+ return /* @__PURE__ */ C(o, {
163
+ size: "xs",
164
+ variant: e === j(f) ? "secondary" : "outline",
165
+ "aria-pressed": e === j(f),
166
+ onClick: () => m(t),
167
+ children: d(e)
168
+ }, e);
169
+ })]
170
+ });
171
+ }
172
+ function P({ versions: r, value: i, onChange: a, labels: f, className: p, appearance: m }) {
149
173
  let { t: h } = t(), g = e(), { current: _, language: v, languages: y, variants: b, change: x } = M(r, i, a);
150
174
  return r.length < 2 || !_ ? null : /* @__PURE__ */ w(n, {
151
175
  appearance: m,
@@ -187,7 +211,7 @@ function N({ versions: r, value: i, onChange: a, labels: f, className: p, appear
187
211
  })]
188
212
  });
189
213
  }
190
- function P(n, r = {}) {
214
+ function F(n, r = {}) {
191
215
  let { t: i } = t(), a = e(), { current: o, language: s, languages: c, variants: l, change: u } = M(n, r.value, r.onChange), { labels: d } = r;
192
216
  return S(() => {
193
217
  if (n.length < 2 || !o) return [];
@@ -231,6 +255,6 @@ function P(n, r = {}) {
231
255
  ]);
232
256
  }
233
257
  //#endregion
234
- export { k as Paywall, N as VersionPicker, O as priceLabel, P as useVersionMenuItems };
258
+ export { N as LanguageFallbackNotice, k as Paywall, P as VersionPicker, O as priceLabel, F as useVersionMenuItems };
235
259
 
236
260
  //# sourceMappingURL=paywall.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paywall.js","names":[],"sources":["../../../src/components/content/paywall.tsx"],"sourcesContent":["import { LockIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useContext, useMemo, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Decision, Sale, Terms, Version } from \"../../client/generated/wire.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { ContentKitContext, useContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { editionOf, findVersion } from \"../../react/detail.js\";\nimport { useVersionParam } from \"../../react/location.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { contentError, ErrorLine, formatDate, useAction } from \"../social/parts.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\nimport type { PlayerMenuItem } from \"../video-player.js\";\n\n/** An offer's price and period in words: \"$2.99\", \"$4.99 / 30 days\", \"$1.99 for 48 hours\". */\nexport function priceLabel(m: Translator, terms: Terms): string {\n let price: string;\n try {\n price = new Intl.NumberFormat(m.language, { style: \"currency\", currency: terms.currency }).format(Number(terms.amount));\n } catch {\n price = `${terms.amount} ${terms.currency}`;\n }\n const period = (hours: number) => (hours % 24 === 0 ? m.plural(\"paywall.days\", hours / 24) : m.plural(\"paywall.hours\", hours));\n if (terms.renew_hours) return m.t(\"paywall.renews\", { price, period: period(terms.renew_hours) });\n if (terms.access_hours) return m.t(\"paywall.rents\", { price, period: period(terms.access_hours) });\n return price;\n}\n\nexport interface PaywallProps {\n /** The locked version's (or card's) decision. */\n decision: Decision;\n /**\n * Starts a purchase: the host creates the order through billing-ui\n * (createOrder with offer.price_id) and shows OrderCheckout; for a tier\n * (no offer), its subscription page. A free group offer joins here instead.\n */\n onCheckout: (o: { key: string; offer?: Sale }) => void;\n /** Tiers' names and prices from the host's catalog, by tier key. */\n tiers?: Readonly<Record<string, { label?: string; price?: string }>>;\n /** Asks a signed-out reader to sign in (they may hold it already); default the provider's. */\n onSignIn?: () => void;\n labels?: Labels;\n /** Below the offers: the host's own. */\n children?: ReactNode;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Why content is locked and how to open it: its offers, the tiers that include it, previews and when it turns free. */\nexport function Paywall({ decision, onCheckout, tiers, onSignIn, labels, children, client: own, onError, className, appearance }: PaywallProps) {\n const m = useMessages();\n const { t } = m;\n const ctx = useContext(ContentKitContext);\n const client = useContentKitClient(own);\n const { kinds } = useKinds({ client: own });\n const join = useAction(\"membership.join\", onError);\n if (decision.allowed) return null;\n const signIn = onSignIn ?? ctx?.onSignIn;\n const offers = decision.offers ?? [];\n const sold = new Set(offers.map((o) => o.key));\n const siteTiers = kinds?.tiers ?? Object.keys(tiers ?? {});\n const tierKeys = (decision.sell ?? []).filter((k) => !sold.has(k) && (siteTiers.includes(k) || !!tiers?.[k]));\n const required = decision.requires;\n // A required key nothing here offers gets its own button; the others wait for it.\n const requires = required && !offers.some((o) => o.key === required) ? required : undefined;\n const waits = (key: string) => !!required && key !== required;\n const preview = decision.preview;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-3 rounded-lg border border-border p-4 text-sm\", className)} data-ckui=\"paywall\">\n <p className=\"flex items-center gap-2 font-medium\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-4\" aria-hidden />\n {decision.unknown ? t(\"paywall.unknown\") : required ? t(\"paywall.requiresFirst\") : t(\"paywall.locked\")}\n </p>\n {preview?.pages ? <p className=\"text-muted-foreground\">{m.plural(\"paywall.previewPages\", preview.pages)}</p> : null}\n {preview?.segments ? <p className=\"text-muted-foreground\">{m.plural(\"paywall.previewSegments\", preview.segments)}</p> : null}\n {decision.free_after && <p className=\"text-muted-foreground\">{t(\"paywall.freeFrom\", { date: formatDate(m, decision.free_after, false) })}</p>}\n {!decision.unknown && (\n <div className=\"flex flex-wrap gap-2\" data-ckui=\"paywall-offers\">\n {requires && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => onCheckout({ key: requires })}>\n {t(\"paywall.joinFirst\")}\n </Button>\n )}\n {offers.map((o) =>\n o.target.kind === \"group\" && Number(o.terms.amount) === 0 ? (\n <Button\n key={o.key}\n size=\"sm\"\n disabled={waits(o.key) || join.pending}\n onClick={() => (ctx?.viewer === null && signIn ? signIn() : void join.run(() => client.offers.join(o.target.id)))}\n data-ckui=\"paywall-offer\"\n data-target=\"group\"\n data-free=\"\"\n >\n {t(\"paywall.joinFirst\")}\n </Button>\n ) : (\n <Button key={o.key} size=\"sm\" disabled={waits(o.key)} onClick={() => onCheckout({ key: o.key, offer: o })} data-ckui=\"paywall-offer\" data-target={o.target.kind}>\n {t(o.target.kind === \"group\" ? \"paywall.joinFor\" : o.target.kind === \"version\" ? \"paywall.unlockVersionFor\" : \"paywall.unlockFor\", { price: priceLabel(m, o.terms) })}\n </Button>\n ),\n )}\n {tierKeys.map((k) => (\n <Button key={k} size=\"sm\" variant={offers.length ? \"outline\" : \"default\"} onClick={() => onCheckout({ key: k })} data-ckui=\"paywall-tier\" data-tier={k}>\n {t(\"paywall.subscribe\", { tier: tiers?.[k]?.label ?? labelOf(labels, k) })}\n {tiers?.[k]?.price && <span className=\"ms-1 opacity-80\">{tiers[k]!.price}</span>}\n </Button>\n ))}\n </div>\n )}\n {join.error && <ErrorLine>{contentError(m, join.error)}</ErrorLine>}\n {ctx?.viewer === null && signIn && (\n <p className=\"text-muted-foreground\">\n {t(\"paywall.haveIt\")}{\" \"}\n <button type=\"button\" className=\"font-medium text-foreground underline-offset-4 hover:underline\" onClick={signIn}>\n {t(\"paywall.signIn\")}\n </button>\n </p>\n )}\n {children}\n </ContentKitUiRoot>\n );\n}\n\nexport interface VersionPickerProps {\n /** The content's live versions (useContentDetail().versions). */\n versions: readonly Version[];\n /** The version shown, by code or id; default the page's ?v=, else the reader's language's default. */\n value?: string | null;\n /** Receives the chosen version; default writes its code to the page's ?v=. */\n onChange?: (version: Version) => void;\n labels?: Labels;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A version's name: its label, else its traits, else its languages. */\nfunction versionName(v: Version, languageName: (l: string) => string, labels?: Labels): string {\n return v.label || (v.traits.length ? v.traits.map((x) => labelOf(labels, x)).join(\" · \") : v.languages.map(languageName).join(\", \"));\n}\n\nconst primary = (v: Version) => v.languages[0] ?? \"\";\n\n/**\n * The shown version (value, else the page's ?v=, else the reader's\n * language's default), the versions' languages, the shown language's\n * variants, and the change handler (default: write ?v=).\n */\nfunction useChoices(versions: readonly Version[], value: string | null | undefined, onChange: ((v: Version) => void) | undefined) {\n const [param, setParam] = useVersionParam();\n const reader = useContext(ContentKitContext)?.language;\n const selected = value === undefined ? param : value;\n const change = useCallback((v: Version) => (onChange ? onChange(v) : setParam(v.code)), [onChange, setParam]);\n return useMemo(() => {\n const sorted = [...versions].sort((a, b) => a.position - b.position);\n const current = findVersion(versions, selected) ?? editionOf(sorted, reader ?? undefined);\n const language = current ? primary(current) : \"\";\n return { current, language, languages: [...new Set(sorted.map(primary))], variants: sorted.filter((v) => primary(v) === language), change };\n }, [versions, selected, reader, change]);\n}\n\n/**\n * Chooses among a content's versions: a language first when they differ in\n * language, then its variants (traits, labels). The choice is the page's ?v=\n * unless value and onChange say otherwise.\n */\nexport function VersionPicker({ versions, value, onChange, labels, className, appearance }: VersionPickerProps) {\n const { t } = useMessages();\n const languageName = useLanguageName();\n const { current, language, languages, variants, change } = useChoices(versions, value, onChange);\n if (versions.length < 2 || !current) return null;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"flex flex-wrap items-center gap-2 text-sm\", className)} data-ckui=\"version-picker\">\n {languages.length > 1 && (\n <Select\n value={language}\n onValueChange={(l) => {\n const v = [...versions].sort((a, b) => a.position - b.position).find((x) => primary(x) === l);\n if (v) change(v);\n }}\n >\n <SelectTrigger size=\"sm\" aria-label={t(\"versions.language\")}>\n <SelectValue>{(l: string) => languageName(l)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n {variants.length > 1 && (\n <div role=\"radiogroup\" aria-label={t(\"versions.version\")} className=\"flex flex-wrap gap-1\">\n {variants.map((v) => (\n <Button\n key={v.id}\n size=\"sm\"\n role=\"radio\"\n aria-checked={v.id === current.id}\n variant={v.id === current.id ? \"default\" : \"outline\"}\n onClick={() => change(v)}\n data-ckui=\"version-option\"\n data-locked={v.decision.allowed ? undefined : \"\"}\n >\n {!v.decision.allowed && <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-label={t(\"versions.locked\")} />}\n {versionName(v, languageName, labels)}\n </Button>\n ))}\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n\nexport interface VersionMenuOptions {\n /** The version shown, by code or id; default the page's ?v=, else the reader's language's default. */\n value?: string | null;\n /** Receives the chosen version; default writes its code to the page's ?v=. */\n onChange?: (version: Version) => void;\n labels?: Labels;\n}\n\n/** VersionPicker as VideoPlayer settings entries (menuItems), for a watch page. */\nexport function useVersionMenuItems(versions: readonly Version[], o: VersionMenuOptions = {}): PlayerMenuItem[] {\n const { t } = useMessages();\n const languageName = useLanguageName();\n const { current, language, languages, variants, change } = useChoices(versions, o.value, o.onChange);\n const { labels } = o;\n return useMemo(() => {\n if (versions.length < 2 || !current) return [];\n const out: PlayerMenuItem[] = [];\n if (languages.length > 1)\n out.push({\n label: t(\"versions.language\"),\n value: language,\n options: languages.map((l) => ({ value: l, label: languageName(l) })),\n onChange: (l) => {\n const v = versions.find((x) => primary(x) === l);\n if (v) change(v);\n },\n });\n if (variants.length > 1)\n out.push({\n label: t(\"versions.version\"),\n value: current.id,\n options: variants.map((v) => {\n const name = versionName(v, languageName, labels);\n return { value: v.id, label: v.decision.allowed ? name : t(\"versions.lockedOption\", { name }) };\n }),\n onChange: (id) => {\n const v = versions.find((x) => x.id === id);\n if (v) change(v);\n },\n });\n return out;\n }, [versions, current, language, languages, variants, change, labels, t, languageName]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqBA,SAAgB,EAAW,GAAe,GAAsB;CAC9D,IAAI;CACJ,IAAI;EACF,IAAQ,IAAI,KAAK,aAAa,EAAE,UAAU;GAAE,OAAO;GAAY,UAAU,EAAM;EAAS,CAAC,CAAC,CAAC,OAAO,OAAO,EAAM,MAAM,CAAC;CACxH,QAAQ;EACN,IAAQ,GAAG,EAAM,OAAO,GAAG,EAAM;CACnC;CACA,IAAM,KAAU,MAAmB,IAAQ,MAAO,IAAI,EAAE,OAAO,gBAAgB,IAAQ,EAAE,IAAI,EAAE,OAAO,iBAAiB,CAAK;CAG5H,OAFI,EAAM,cAAoB,EAAE,EAAE,kBAAkB;EAAE;EAAO,QAAQ,EAAO,EAAM,WAAW;CAAE,CAAC,IAC5F,EAAM,eAAqB,EAAE,EAAE,iBAAiB;EAAE;EAAO,QAAQ,EAAO,EAAM,YAAY;CAAE,CAAC,IAC1F;AACT;AAyBA,SAAgB,EAAQ,EAAE,aAAU,eAAY,UAAO,aAAU,WAAQ,aAAU,QAAQ,GAAK,YAAS,cAAW,iBAA4B;CAC9I,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAM,EAAW,CAAiB,GAClC,IAAS,EAAoB,CAAG,GAChC,EAAE,aAAU,EAAS,EAAE,QAAQ,EAAI,CAAC,GACpC,IAAO,EAAU,mBAAmB,CAAO;CACjD,IAAI,EAAS,SAAS,OAAO;CAC7B,IAAM,IAAS,KAAY,GAAK,UAC1B,IAAS,EAAS,UAAU,CAAC,GAC7B,IAAO,IAAI,IAAI,EAAO,KAAK,MAAM,EAAE,GAAG,CAAC,GACvC,IAAY,GAAO,SAAS,OAAO,KAAK,KAAS,CAAC,CAAC,GACnD,KAAY,EAAS,QAAQ,CAAC,EAAA,CAAG,QAAQ,MAAM,CAAC,EAAK,IAAI,CAAC,MAAM,EAAU,SAAS,CAAC,KAAK,CAAC,CAAC,IAAQ,GAAG,GACtG,IAAW,EAAS,UAEpB,IAAW,KAAY,CAAC,EAAO,MAAM,MAAM,EAAE,QAAQ,CAAQ,IAAI,IAAW,KAAA,GAC5E,KAAS,MAAgB,CAAC,CAAC,KAAY,MAAQ,GAC/C,IAAU,EAAS;CACzB,OACE,kBAAC,GAAD;EAA8B;EAAY,WAAW,EAAG,0DAA0D,CAAS;EAAG,aAAU;EAAxI,UAAA;GACE,kBAAC,KAAD;IAAG,WAAU;IAAb,UAAA,CACE,kBAAC,GAAD;KAAe,MAAM;KAAU,aAAa;KAAG,WAAU;KAAS,eAAA;IAAa,CAAA,GAC9E,EAAS,UAAU,EAAE,iBAAiB,IAAe,EAAX,IAAa,0BAA6B,gBAAgB,CACpG;;GACF,GAAS,QAAQ,kBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,EAAE,OAAO,wBAAwB,EAAQ,KAAK;GAAK,CAAA,IAAI;GAC9G,GAAS,WAAW,kBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,EAAE,OAAO,2BAA2B,EAAQ,QAAQ;GAAK,CAAA,IAAI;GACvH,EAAS,cAAc,kBAAC,KAAD;IAAG,WAAU;IAAyB,UAAA,EAAE,oBAAoB,EAAE,MAAM,EAAW,GAAG,EAAS,YAAY,EAAK,EAAE,CAAC;GAAK,CAAA;GAC3I,CAAC,EAAS,WACT,kBAAC,OAAD;IAAK,WAAU;IAAuB,aAAU;IAAhD,UAAA;KACG,KACC,kBAAC,GAAD;MAAQ,MAAK;MAAK,SAAQ;MAAU,eAAe,EAAW,EAAE,KAAK,EAAS,CAAC;MAC5E,UAAA,EAAE,mBAAmB;KAChB,CAAA;KAET,EAAO,KAAK,MACX,EAAE,OAAO,SAAS,WAAW,OAAO,EAAE,MAAM,MAAM,MAAM,IACtD,kBAAC,GAAD;MAEE,MAAK;MACL,UAAU,EAAM,EAAE,GAAG,KAAK,EAAK;MAC/B,eAAgB,GAAK,WAAW,QAAQ,IAAS,EAAO,IAAI,KAAK,EAAK,UAAU,EAAO,OAAO,KAAK,EAAE,OAAO,EAAE,CAAC;MAC/G,aAAU;MACV,eAAY;MACZ,aAAU;MAET,UAAA,EAAE,mBAAmB;KAChB,GATD,EAAE,GASD,IAER,kBAAC,GAAD;MAAoB,MAAK;MAAK,UAAU,EAAM,EAAE,GAAG;MAAG,eAAe,EAAW;OAAE,KAAK,EAAE;OAAK,OAAO;MAAE,CAAC;MAAG,aAAU;MAAgB,eAAa,EAAE,OAAO;MACxJ,UAAA,EAAE,EAAE,OAAO,SAAS,UAAU,oBAAoB,EAAE,OAAO,SAAS,YAAY,6BAA6B,qBAAqB,EAAE,OAAO,EAAW,GAAG,EAAE,KAAK,EAAE,CAAC;KAC9J,GAFK,EAAE,GAEP,CAEZ;KACC,EAAS,KAAK,MACb,kBAAC,GAAD;MAAgB,MAAK;MAAK,SAAS,EAAO,SAAS,YAAY;MAAW,eAAe,EAAW,EAAE,KAAK,EAAE,CAAC;MAAG,aAAU;MAAe,aAAW;MAArJ,UAAA,CACG,EAAE,qBAAqB,EAAE,MAAM,IAAQ,EAAE,EAAE,SAAS,EAAQ,GAAQ,CAAC,EAAE,CAAC,GACxE,IAAQ,EAAE,EAAE,SAAS,kBAAC,QAAD;OAAM,WAAU;OAAmB,UAAA,EAAM,EAAE,CAAE;MAAY,CAAA,CACzE;KAHK,GAAA,CAGL,CACT;IACE;;GAEN,EAAK,SAAS,kBAAC,GAAD,EAAA,UAAY,EAAa,GAAG,EAAK,KAAK,EAAa,CAAA;GACjE,GAAK,WAAW,QAAQ,KACvB,kBAAC,KAAD;IAAG,WAAU;IAAb,UAAA;KACG,EAAE,gBAAgB;KAAG;KACtB,kBAAC,UAAD;MAAQ,MAAK;MAAS,WAAU;MAAiE,SAAS;MACvG,UAAA,EAAE,gBAAgB;KACb,CAAA;IACP;;GAEJ;EACe;;AAEtB;AAeA,SAAS,EAAY,GAAY,GAAqC,GAAyB;CAC7F,OAAO,EAAE,UAAU,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK,MAAM,EAAQ,GAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,EAAE,UAAU,IAAI,CAAY,CAAC,CAAC,KAAK,IAAI;AACpI;AAEA,IAAM,KAAW,MAAe,EAAE,UAAU,MAAM;AAOlD,SAAS,EAAW,GAA8B,GAAkC,GAA8C;CAChI,IAAM,CAAC,GAAO,KAAY,EAAgB,GACpC,IAAS,EAAW,CAAiB,CAAC,EAAE,UACxC,IAAW,MAAU,KAAA,IAAY,IAAQ,GACzC,IAAS,GAAa,MAAgB,IAAW,EAAS,CAAC,IAAI,EAAS,EAAE,IAAI,GAAI,CAAC,GAAU,CAAQ,CAAC;CAC5G,OAAO,QAAc;EACnB,IAAM,IAAS,CAAC,GAAG,CAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,GAC7D,IAAU,EAAY,GAAU,CAAQ,KAAK,EAAU,GAAQ,KAAU,KAAA,CAAS,GAClF,IAAW,IAAU,EAAQ,CAAO,IAAI;EAC9C,OAAO;GAAE;GAAS;GAAU,WAAW,CAAC,GAAG,IAAI,IAAI,EAAO,IAAI,CAAO,CAAC,CAAC;GAAG,UAAU,EAAO,QAAQ,MAAM,EAAQ,CAAC,MAAM,CAAQ;GAAG;EAAO;CAC5I,GAAG;EAAC;EAAU;EAAU;EAAQ;CAAM,CAAC;AACzC;AAOA,SAAgB,EAAc,EAAE,aAAU,UAAO,aAAU,WAAQ,cAAW,iBAAkC;CAC9G,IAAM,EAAE,SAAM,EAAY,GACpB,IAAe,EAAgB,GAC/B,EAAE,YAAS,aAAU,cAAW,aAAU,cAAW,EAAW,GAAU,GAAO,CAAQ;CAE/F,OADI,EAAS,SAAS,KAAK,CAAC,IAAgB,OAE1C,kBAAC,GAAD;EAA8B;EAAY,WAAW,EAAG,6CAA6C,CAAS;EAAG,aAAU;EAA3H,UAAA,CACG,EAAU,SAAS,KAClB,kBAAC,GAAD;GACE,OAAO;GACP,gBAAgB,MAAM;IACpB,IAAM,IAAI,CAAC,GAAG,CAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,MAAM,MAAM,EAAQ,CAAC,MAAM,CAAC;IAC5F,AAAI,KAAG,EAAO,CAAC;GACjB;GALF,UAAA,CAOE,kBAAC,GAAD;IAAe,MAAK;IAAK,cAAY,EAAE,mBAAmB;IACxD,UAAA,kBAAC,GAAD,EAAA,WAAe,MAAc,EAAa,CAAC,EAAe,CAAA;GAC7C,CAAA,GACf,kBAAC,GAAD,EAAA,UACG,EAAU,KAAK,MACd,kBAAC,GAAD;IAAoB,OAAO;IACxB,UAAA,EAAa,CAAC;GACL,GAFK,CAEL,CACb,EACY,CAAA,CACT;EAET,CAAA,GAAA,EAAS,SAAS,KACjB,kBAAC,OAAD;GAAK,MAAK;GAAa,cAAY,EAAE,kBAAkB;GAAG,WAAU;GACjE,UAAA,EAAS,KAAK,MACb,kBAAC,GAAD;IAEE,MAAK;IACL,MAAK;IACL,gBAAc,EAAE,OAAO,EAAQ;IAC/B,SAAS,EAAE,OAAO,EAAQ,KAAK,YAAY;IAC3C,eAAe,EAAO,CAAC;IACvB,aAAU;IACV,eAAa,EAAE,SAAS,UAAU,KAAA,IAAY;IARhD,UAAA,CAUG,CAAC,EAAE,SAAS,WAAW,kBAAC,GAAD;KAAe,MAAM;KAAU,aAAa;KAAG,WAAU;KAAW,cAAY,EAAE,iBAAiB;IAAI,CAAA,GAC9H,EAAY,GAAG,GAAc,CAAM,CAC9B;GAXD,GAAA,EAAE,EAWD,CACT;EACE,CAAA,CAES;;AAEtB;AAWA,SAAgB,EAAoB,GAA8B,IAAwB,CAAC,GAAqB;CAC9G,IAAM,EAAE,SAAM,EAAY,GACpB,IAAe,EAAgB,GAC/B,EAAE,YAAS,aAAU,cAAW,aAAU,cAAW,EAAW,GAAU,EAAE,OAAO,EAAE,QAAQ,GAC7F,EAAE,cAAW;CACnB,OAAO,QAAc;EACnB,IAAI,EAAS,SAAS,KAAK,CAAC,GAAS,OAAO,CAAC;EAC7C,IAAM,IAAwB,CAAC;EAwB/B,OAvBI,EAAU,SAAS,KACrB,EAAI,KAAK;GACP,OAAO,EAAE,mBAAmB;GAC5B,OAAO;GACP,SAAS,EAAU,KAAK,OAAO;IAAE,OAAO;IAAG,OAAO,EAAa,CAAC;GAAE,EAAE;GACpE,WAAW,MAAM;IACf,IAAM,IAAI,EAAS,MAAM,MAAM,EAAQ,CAAC,MAAM,CAAC;IAC/C,AAAI,KAAG,EAAO,CAAC;GACjB;EACF,CAAC,GACC,EAAS,SAAS,KACpB,EAAI,KAAK;GACP,OAAO,EAAE,kBAAkB;GAC3B,OAAO,EAAQ;GACf,SAAS,EAAS,KAAK,MAAM;IAC3B,IAAM,IAAO,EAAY,GAAG,GAAc,CAAM;IAChD,OAAO;KAAE,OAAO,EAAE;KAAI,OAAO,EAAE,SAAS,UAAU,IAAO,EAAE,yBAAyB,EAAE,QAAK,CAAC;IAAE;GAChG,CAAC;GACD,WAAW,MAAO;IAChB,IAAM,IAAI,EAAS,MAAM,MAAM,EAAE,OAAO,CAAE;IAC1C,AAAI,KAAG,EAAO,CAAC;GACjB;EACF,CAAC,GACI;CACT,GAAG;EAAC;EAAU;EAAS;EAAU;EAAW;EAAU;EAAQ;EAAQ;EAAG;CAAY,CAAC;AACxF"}
1
+ {"version":3,"file":"paywall.js","names":[],"sources":["../../../src/components/content/paywall.tsx"],"sourcesContent":["import { LockIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useContext, useMemo, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Decision, Sale, Terms, Version } from \"../../client/generated/wire.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { ContentKitContext, useContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { editionOf, findVersion } from \"../../react/detail.js\";\nimport { useVersionParam } from \"../../react/location.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { contentError, ErrorLine, formatDate, useAction } from \"../social/parts.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\nimport type { PlayerMenuItem } from \"../video-player.js\";\n\n/** An offer's price and period in words: \"$2.99\", \"$4.99 / 30 days\", \"$1.99 for 48 hours\". */\nexport function priceLabel(m: Translator, terms: Terms): string {\n let price: string;\n try {\n price = new Intl.NumberFormat(m.language, { style: \"currency\", currency: terms.currency }).format(Number(terms.amount));\n } catch {\n price = `${terms.amount} ${terms.currency}`;\n }\n const period = (hours: number) => (hours % 24 === 0 ? m.plural(\"paywall.days\", hours / 24) : m.plural(\"paywall.hours\", hours));\n if (terms.renew_hours) return m.t(\"paywall.renews\", { price, period: period(terms.renew_hours) });\n if (terms.access_hours) return m.t(\"paywall.rents\", { price, period: period(terms.access_hours) });\n return price;\n}\n\nexport interface PaywallProps {\n /** The locked version's (or card's) decision. */\n decision: Decision;\n /**\n * Starts a purchase: the host creates the order through billing-ui\n * (createOrder with offer.price_id) and shows OrderCheckout; for a tier\n * (no offer), its subscription page. A free group offer joins here instead.\n */\n onCheckout: (o: { key: string; offer?: Sale }) => void;\n /** Tiers' names and prices from the host's catalog, by tier key. */\n tiers?: Readonly<Record<string, { label?: string; price?: string }>>;\n /** Asks a signed-out reader to sign in (they may hold it already); default the provider's. */\n onSignIn?: () => void;\n labels?: Labels;\n /** Below the offers: the host's own. */\n children?: ReactNode;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Why content is locked and how to open it: its offers, the tiers that include it, previews and when it turns free. */\nexport function Paywall({ decision, onCheckout, tiers, onSignIn, labels, children, client: own, onError, className, appearance }: PaywallProps) {\n const m = useMessages();\n const { t } = m;\n const ctx = useContext(ContentKitContext);\n const client = useContentKitClient(own);\n const { kinds } = useKinds({ client: own });\n const join = useAction(\"membership.join\", onError);\n if (decision.allowed) return null;\n const signIn = onSignIn ?? ctx?.onSignIn;\n const offers = decision.offers ?? [];\n const sold = new Set(offers.map((o) => o.key));\n const siteTiers = kinds?.tiers ?? Object.keys(tiers ?? {});\n const tierKeys = (decision.sell ?? []).filter((k) => !sold.has(k) && (siteTiers.includes(k) || !!tiers?.[k]));\n const required = decision.requires;\n // A required key nothing here offers gets its own button; the others wait for it.\n const requires = required && !offers.some((o) => o.key === required) ? required : undefined;\n const waits = (key: string) => !!required && key !== required;\n const preview = decision.preview;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-3 rounded-lg border border-border p-4 text-sm\", className)} data-ckui=\"paywall\">\n <p className=\"flex items-center gap-2 font-medium\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-4\" aria-hidden />\n {decision.unknown ? t(\"paywall.unknown\") : required ? t(\"paywall.requiresFirst\") : t(\"paywall.locked\")}\n </p>\n {preview?.pages ? <p className=\"text-muted-foreground\">{m.plural(\"paywall.previewPages\", preview.pages)}</p> : null}\n {preview?.segments ? <p className=\"text-muted-foreground\">{m.plural(\"paywall.previewSegments\", preview.segments)}</p> : null}\n {decision.free_after && <p className=\"text-muted-foreground\">{t(\"paywall.freeFrom\", { date: formatDate(m, decision.free_after, false) })}</p>}\n {!decision.unknown && (\n <div className=\"flex flex-wrap gap-2\" data-ckui=\"paywall-offers\">\n {requires && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => onCheckout({ key: requires })}>\n {t(\"paywall.joinFirst\")}\n </Button>\n )}\n {offers.map((o) =>\n o.target.kind === \"group\" && Number(o.terms.amount) === 0 ? (\n <Button\n key={o.key}\n size=\"sm\"\n disabled={waits(o.key) || join.pending}\n onClick={() => (ctx?.viewer === null && signIn ? signIn() : void join.run(() => client.offers.join(o.target.id)))}\n data-ckui=\"paywall-offer\"\n data-target=\"group\"\n data-free=\"\"\n >\n {t(\"paywall.joinFirst\")}\n </Button>\n ) : (\n <Button key={o.key} size=\"sm\" disabled={waits(o.key)} onClick={() => onCheckout({ key: o.key, offer: o })} data-ckui=\"paywall-offer\" data-target={o.target.kind}>\n {t(o.target.kind === \"group\" ? \"paywall.joinFor\" : o.target.kind === \"version\" ? \"paywall.unlockVersionFor\" : \"paywall.unlockFor\", { price: priceLabel(m, o.terms) })}\n </Button>\n ),\n )}\n {tierKeys.map((k) => (\n <Button key={k} size=\"sm\" variant={offers.length ? \"outline\" : \"default\"} onClick={() => onCheckout({ key: k })} data-ckui=\"paywall-tier\" data-tier={k}>\n {t(\"paywall.subscribe\", { tier: tiers?.[k]?.label ?? labelOf(labels, k) })}\n {tiers?.[k]?.price && <span className=\"ms-1 opacity-80\">{tiers[k]!.price}</span>}\n </Button>\n ))}\n </div>\n )}\n {join.error && <ErrorLine>{contentError(m, join.error)}</ErrorLine>}\n {ctx?.viewer === null && signIn && (\n <p className=\"text-muted-foreground\">\n {t(\"paywall.haveIt\")}{\" \"}\n <button type=\"button\" className=\"font-medium text-foreground underline-offset-4 hover:underline\" onClick={signIn}>\n {t(\"paywall.signIn\")}\n </button>\n </p>\n )}\n {children}\n </ContentKitUiRoot>\n );\n}\n\nexport interface VersionPickerProps {\n /** The content's live versions (useContentDetail().versions). */\n versions: readonly Version[];\n /** The version shown, by code or id; default the page's ?v=, else the reader's language's default. */\n value?: string | null;\n /** Receives the chosen version; default writes its code to the page's ?v=. */\n onChange?: (version: Version) => void;\n labels?: Labels;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** A version's name: its label, else its traits, else its languages. */\nfunction versionName(v: Version, languageName: (l: string) => string, labels?: Labels): string {\n return v.label || (v.traits.length ? v.traits.map((x) => labelOf(labels, x)).join(\" · \") : v.languages.map(languageName).join(\", \"));\n}\n\nconst primary = (v: Version) => v.languages[0] ?? \"\";\n\n/**\n * The shown version (value, else the page's ?v=, else the reader's\n * language's default), the versions' languages, the shown language's\n * variants, and the change handler (default: write ?v=).\n */\nfunction useChoices(versions: readonly Version[], value: string | null | undefined, onChange: ((v: Version) => void) | undefined) {\n const [param, setParam] = useVersionParam();\n const reader = useContext(ContentKitContext)?.language;\n const selected = value === undefined ? param : value;\n const change = useCallback((v: Version) => (onChange ? onChange(v) : setParam(v.code)), [onChange, setParam]);\n return useMemo(() => {\n const sorted = [...versions].sort((a, b) => a.position - b.position);\n const current = findVersion(versions, selected) ?? editionOf(sorted, reader ?? undefined);\n const language = current ? primary(current) : \"\";\n return { current, language, languages: [...new Set(sorted.map(primary))], variants: sorted.filter((v) => primary(v) === language), change };\n }, [versions, selected, reader, change]);\n}\n\nexport interface LanguageFallbackNoticeProps extends Omit<VersionPickerProps, \"labels\"> {\n /** The page's language: the notice shows when the shown version doesn't serve it. */\n language: string;\n}\n\n/**\n * A quiet note that the content isn't available in the page's language, which\n * version it shows instead, and each language it does serve to switch to.\n */\nexport function LanguageFallbackNotice({ versions, value, onChange, language, className, appearance }: LanguageFallbackNoticeProps) {\n const { t } = useMessages();\n const languageName = useLanguageName();\n const { current, languages, change } = useChoices(versions, value, onChange);\n if (!current || !language || current.languages.includes(language)) return null;\n const sorted = [...versions].sort((a, b) => a.position - b.position);\n return (\n <ContentKitUiRoot appearance={appearance} role=\"note\" className={cn(\"flex flex-wrap items-center gap-2 text-sm text-muted-foreground\", className)} data-ckui=\"language-fallback\">\n <span>{t(\"contents.notInLanguage\", { language: languageName(language), shown: languageName(primary(current)) })}</span>\n {languages.length > 1 &&\n languages.map((l) => {\n const v = sorted.find((x) => primary(x) === l)!;\n return (\n <Button key={l} size=\"xs\" variant={l === primary(current) ? \"secondary\" : \"outline\"} aria-pressed={l === primary(current)} onClick={() => change(v)}>\n {languageName(l)}\n </Button>\n );\n })}\n </ContentKitUiRoot>\n );\n}\n\n/**\n * Chooses among a content's versions: a language first when they differ in\n * language, then its variants (traits, labels). The choice is the page's ?v=\n * unless value and onChange say otherwise.\n */\nexport function VersionPicker({ versions, value, onChange, labels, className, appearance }: VersionPickerProps) {\n const { t } = useMessages();\n const languageName = useLanguageName();\n const { current, language, languages, variants, change } = useChoices(versions, value, onChange);\n if (versions.length < 2 || !current) return null;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"flex flex-wrap items-center gap-2 text-sm\", className)} data-ckui=\"version-picker\">\n {languages.length > 1 && (\n <Select\n value={language}\n onValueChange={(l) => {\n const v = [...versions].sort((a, b) => a.position - b.position).find((x) => primary(x) === l);\n if (v) change(v);\n }}\n >\n <SelectTrigger size=\"sm\" aria-label={t(\"versions.language\")}>\n <SelectValue>{(l: string) => languageName(l)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l} value={l}>\n {languageName(l)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n {variants.length > 1 && (\n <div role=\"radiogroup\" aria-label={t(\"versions.version\")} className=\"flex flex-wrap gap-1\">\n {variants.map((v) => (\n <Button\n key={v.id}\n size=\"sm\"\n role=\"radio\"\n aria-checked={v.id === current.id}\n variant={v.id === current.id ? \"default\" : \"outline\"}\n onClick={() => change(v)}\n data-ckui=\"version-option\"\n data-locked={v.decision.allowed ? undefined : \"\"}\n >\n {!v.decision.allowed && <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-label={t(\"versions.locked\")} />}\n {versionName(v, languageName, labels)}\n </Button>\n ))}\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n\nexport interface VersionMenuOptions {\n /** The version shown, by code or id; default the page's ?v=, else the reader's language's default. */\n value?: string | null;\n /** Receives the chosen version; default writes its code to the page's ?v=. */\n onChange?: (version: Version) => void;\n labels?: Labels;\n}\n\n/** VersionPicker as VideoPlayer settings entries (menuItems), for a watch page. */\nexport function useVersionMenuItems(versions: readonly Version[], o: VersionMenuOptions = {}): PlayerMenuItem[] {\n const { t } = useMessages();\n const languageName = useLanguageName();\n const { current, language, languages, variants, change } = useChoices(versions, o.value, o.onChange);\n const { labels } = o;\n return useMemo(() => {\n if (versions.length < 2 || !current) return [];\n const out: PlayerMenuItem[] = [];\n if (languages.length > 1)\n out.push({\n label: t(\"versions.language\"),\n value: language,\n options: languages.map((l) => ({ value: l, label: languageName(l) })),\n onChange: (l) => {\n const v = versions.find((x) => primary(x) === l);\n if (v) change(v);\n },\n });\n if (variants.length > 1)\n out.push({\n label: t(\"versions.version\"),\n value: current.id,\n options: variants.map((v) => {\n const name = versionName(v, languageName, labels);\n return { value: v.id, label: v.decision.allowed ? name : t(\"versions.lockedOption\", { name }) };\n }),\n onChange: (id) => {\n const v = versions.find((x) => x.id === id);\n if (v) change(v);\n },\n });\n return out;\n }, [versions, current, language, languages, variants, change, labels, t, languageName]);\n}\n"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { ReactNode } from 'react';
2
+ import { ContentKitUiAppearance } from '../../appearance.js';
3
+ import { ContentKitClient } from '../../client/client.js';
4
+ import { Period } from '../../client/discovery.js';
5
+ import { Card } from '../../client/generated/wire.js';
6
+ import { PostCardProps } from './card.js';
7
+ export interface PostListProps {
8
+ /** The post kind. */
9
+ kind: string;
10
+ /** Display language for titles, excerpts and paths; default the provider's. */
11
+ language?: string;
12
+ href?: (card: Card) => string;
13
+ /** Posts per page; default 10. */
14
+ pageSize?: number;
15
+ /** Trending window for the sidebar, or false to leave it out; default 30d. */
16
+ trending?: Period | false;
17
+ /** Posts per sidebar section; default 5, 0 hides the sidebar. */
18
+ sidebarSize?: number;
19
+ /** Excerpt length in characters; default 220. */
20
+ excerptLength?: number;
21
+ /** Signed in, posts unread within this many days are marked; default 30. */
22
+ unreadDays?: number;
23
+ tiers?: readonly string[];
24
+ labels?: PostCardProps["labels"];
25
+ empty?: ReactNode;
26
+ client?: ContentKitClient;
27
+ appearance?: ContentKitUiAppearance;
28
+ className?: string;
29
+ }
30
+ /**
31
+ * Posts newest first with cover, date, excerpt and an unread mark, beside a
32
+ * Newest/Trending sidebar. Trending is the kind's popular window; it never
33
+ * counts views.
34
+ */
35
+ export declare function PostList({ kind, language, href, pageSize, trending, sidebarSize, excerptLength, unreadDays, tiers, labels, empty, client, appearance, className }: PostListProps): import('react').JSX.Element;
36
+ /** At most length characters, cut at a word boundary with an ellipsis. */
37
+ export declare function excerpt(text: string, length: number): string;
@@ -0,0 +1,203 @@
1
+ import { RenditionImg as e } from "../rendition-img.js";
2
+ import { useMessages as t } from "../../i18n/context.js";
3
+ import { ContentKitUiRoot as n } from "../../scope.js";
4
+ import { Button as r } from "../../ui/button.js";
5
+ import { Skeleton as i } from "../../ui/skeleton.js";
6
+ import { RelativeTime as a } from "../social/parts.js";
7
+ import { useContents as o } from "../../react/discovery.js";
8
+ import { lockLabel as s } from "./card.js";
9
+ import { usePostReads as c } from "../../react/post-reads.js";
10
+ import { useMemo as l } from "react";
11
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
12
+ import { cn as f } from "cn";
13
+ import { LockIcon as p } from "@hugeicons/core-free-icons";
14
+ import { HugeiconsIcon as m } from "@hugeicons/react";
15
+ //#region src/components/content/post-list.tsx
16
+ function h({ kind: e, language: a, href: p, pageSize: m = 10, trending: h = "30d", sidebarSize: v = 5, excerptLength: y = 220, unreadDays: b = 30, tiers: x, labels: S, empty: C, client: w, appearance: T, className: E }) {
17
+ let D = t(), { t: O } = D, k = o(l(() => ({
18
+ kind: e,
19
+ sort: "newest",
20
+ limit: m,
21
+ display_language: a
22
+ }), [
23
+ e,
24
+ m,
25
+ a
26
+ ]), { client: w }), A = c(b), j = (e) => p ? p(e) : e.path, M = v > 0;
27
+ return /* @__PURE__ */ d(n, {
28
+ appearance: T,
29
+ className: f("grid gap-6 text-sm", M && "lg:grid-cols-[minmax(0,1fr)_18rem]", E),
30
+ "data-ckui": "post-list",
31
+ children: [/* @__PURE__ */ d("div", {
32
+ className: "grid min-w-0 content-start gap-3",
33
+ children: [k.loading ? /* @__PURE__ */ u("ol", {
34
+ className: "grid divide-y divide-border",
35
+ "aria-busy": !0,
36
+ children: Array.from({ length: Math.min(m, 4) }, (e, t) => /* @__PURE__ */ d("li", {
37
+ className: "grid gap-4 py-4 sm:grid-cols-[14rem_minmax(0,1fr)]",
38
+ children: [/* @__PURE__ */ u(i, { className: "aspect-[331/232] w-full rounded-lg" }), /* @__PURE__ */ d("div", {
39
+ className: "grid content-start gap-2",
40
+ children: [
41
+ /* @__PURE__ */ u(i, { className: "h-5 w-3/4" }),
42
+ /* @__PURE__ */ u(i, { className: "h-3 w-24" }),
43
+ /* @__PURE__ */ u(i, { className: "h-3 w-full" }),
44
+ /* @__PURE__ */ u(i, { className: "h-3 w-5/6" })
45
+ ]
46
+ })]
47
+ }, t))
48
+ }) : k.items.length === 0 ? /* @__PURE__ */ u("p", {
49
+ className: "text-muted-foreground",
50
+ children: C ?? O("contents.empty")
51
+ }) : /* @__PURE__ */ u("ol", {
52
+ className: "grid divide-y divide-border",
53
+ children: k.items.map((e) => /* @__PURE__ */ u(g, {
54
+ card: e,
55
+ url: j(e),
56
+ unread: A.unread(e.id, e.published_at, e.viewer?.seen || e.viewer?.completed),
57
+ excerptLength: y,
58
+ lock: s(D, e.access, x, S)
59
+ }, e.id))
60
+ }), k.hasMore && /* @__PURE__ */ u(r, {
61
+ variant: "outline",
62
+ className: "justify-self-center",
63
+ disabled: k.loadingMore,
64
+ onClick: () => void k.loadMore(),
65
+ children: k.loadingMore ? O("contents.loading") : O("contents.loadMore")
66
+ })]
67
+ }), M && /* @__PURE__ */ d("aside", {
68
+ className: "grid content-start gap-6",
69
+ "data-ckui": "post-list-sidebar",
70
+ children: [/* @__PURE__ */ u(_, {
71
+ title: O("postList.newest"),
72
+ kind: e,
73
+ sort: "newest",
74
+ size: v,
75
+ language: a,
76
+ url: j,
77
+ client: w
78
+ }), h && /* @__PURE__ */ u(_, {
79
+ title: O("postList.trending"),
80
+ kind: e,
81
+ sort: `popular:${h}`,
82
+ size: v,
83
+ language: a,
84
+ url: j,
85
+ client: w
86
+ })]
87
+ })]
88
+ });
89
+ }
90
+ function g({ card: n, url: r, unread: i, lock: o, excerptLength: s }) {
91
+ let { t: c } = t(), l = v(n.excerpt ?? "", s);
92
+ return /* @__PURE__ */ d("li", {
93
+ className: "grid gap-4 py-4 sm:grid-cols-[14rem_minmax(0,1fr)]",
94
+ "data-ckui": "post-row",
95
+ "data-id": n.id,
96
+ "data-unread": i || void 0,
97
+ lang: n.language || void 0,
98
+ children: [n.cover.length > 0 && /* @__PURE__ */ u("a", {
99
+ href: r,
100
+ className: "relative block aspect-[331/232] overflow-hidden rounded-lg bg-muted",
101
+ tabIndex: -1,
102
+ "aria-hidden": !0,
103
+ children: /* @__PURE__ */ u(e, {
104
+ outputs: n.cover,
105
+ alt: "",
106
+ fit: "cover",
107
+ skeleton: !0,
108
+ loading: "lazy",
109
+ className: f("size-full", o && "blur-md")
110
+ })
111
+ }), /* @__PURE__ */ d("div", {
112
+ className: f("grid min-w-0 content-start gap-1.5", n.cover.length === 0 && "sm:col-span-2"),
113
+ children: [
114
+ /* @__PURE__ */ d("a", {
115
+ href: r,
116
+ className: "flex items-start gap-2 text-lg leading-snug font-semibold break-words hover:underline",
117
+ children: [i && /* @__PURE__ */ u("span", {
118
+ className: "mt-2 size-2 shrink-0 rounded-full bg-primary",
119
+ "data-ckui": "post-unread",
120
+ children: /* @__PURE__ */ u("span", {
121
+ className: "sr-only",
122
+ children: c("postList.unread")
123
+ })
124
+ }), /* @__PURE__ */ u("span", { children: n.title || n.code })]
125
+ }),
126
+ /* @__PURE__ */ d("p", {
127
+ className: "flex flex-wrap items-center gap-3 text-xs text-muted-foreground",
128
+ children: [n.published_at && /* @__PURE__ */ u(a, { at: n.published_at }), o && /* @__PURE__ */ d("span", {
129
+ className: "inline-flex items-center gap-1",
130
+ "data-ckui": "card-lock",
131
+ children: [/* @__PURE__ */ u(m, {
132
+ icon: p,
133
+ strokeWidth: 2,
134
+ className: "size-3.5",
135
+ "aria-hidden": !0
136
+ }), o]
137
+ })]
138
+ }),
139
+ l && /* @__PURE__ */ u("p", {
140
+ className: "line-clamp-3 leading-relaxed text-muted-foreground",
141
+ children: l
142
+ })
143
+ ]
144
+ })]
145
+ });
146
+ }
147
+ function _({ title: t, kind: n, sort: r, size: s, language: c, url: p, client: m }) {
148
+ let h = o(l(() => ({
149
+ kind: n,
150
+ sort: r,
151
+ limit: s,
152
+ display_language: c
153
+ }), [
154
+ n,
155
+ r,
156
+ s,
157
+ c
158
+ ]), { client: m });
159
+ return !h.loading && h.items.length === 0 ? null : /* @__PURE__ */ d("section", {
160
+ className: "grid content-start gap-3",
161
+ "aria-label": t,
162
+ children: [/* @__PURE__ */ u("h2", {
163
+ className: "text-base font-semibold",
164
+ children: t
165
+ }), /* @__PURE__ */ u("ol", {
166
+ className: "grid gap-3",
167
+ children: h.loading ? Array.from({ length: Math.min(s, 3) }, (e, t) => /* @__PURE__ */ u(i, { className: "h-14 w-full" }, t)) : h.items.slice(0, s).map((t) => /* @__PURE__ */ u("li", { children: /* @__PURE__ */ d("a", {
168
+ href: p(t),
169
+ className: f("grid items-center gap-3 hover:underline", t.cover.length > 0 && "grid-cols-[5rem_minmax(0,1fr)]"),
170
+ children: [t.cover.length > 0 && /* @__PURE__ */ u("span", {
171
+ className: "block aspect-[3/2] overflow-hidden rounded-md bg-muted",
172
+ children: /* @__PURE__ */ u(e, {
173
+ outputs: t.cover,
174
+ alt: "",
175
+ fit: "cover",
176
+ skeleton: !0,
177
+ loading: "lazy",
178
+ className: f("size-full", !t.access.allowed && "blur-sm")
179
+ })
180
+ }), /* @__PURE__ */ d("span", {
181
+ className: "grid min-w-0 gap-0.5",
182
+ children: [/* @__PURE__ */ u("span", {
183
+ className: "line-clamp-2 font-medium break-words",
184
+ children: t.title || t.code
185
+ }), t.published_at && /* @__PURE__ */ u(a, {
186
+ at: t.published_at,
187
+ className: "text-xs text-muted-foreground"
188
+ })]
189
+ })]
190
+ }) }, t.id))
191
+ })]
192
+ });
193
+ }
194
+ function v(e, t) {
195
+ let n = e.replace(/\s+/g, " ").trim();
196
+ if (n.length <= t) return n;
197
+ let r = n.slice(0, t + 1), i = r.lastIndexOf(" ");
198
+ return `${(i > t * .6 ? r.slice(0, i) : n.slice(0, t)).replace(/[\s.,;:!?-]+$/, "")}…`;
199
+ }
200
+ //#endregion
201
+ export { h as PostList, v as excerpt };
202
+
203
+ //# sourceMappingURL=post-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-list.js","names":[],"sources":["../../../src/components/content/post-list.tsx"],"sourcesContent":["import { HugeiconsIcon } from \"@hugeicons/react\";\nimport { LockIcon } from \"@hugeicons/core-free-icons\";\nimport { cn } from \"cn\";\nimport { useMemo, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Period } from \"../../client/discovery.js\";\nimport type { Card } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useContents } from \"../../react/discovery.js\";\nimport { usePostReads } from \"../../react/post-reads.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { RelativeTime } from \"../social/parts.js\";\nimport { lockLabel, type PostCardProps } from \"./card.js\";\n\nexport interface PostListProps {\n /** The post kind. */\n kind: string;\n /** Display language for titles, excerpts and paths; default the provider's. */\n language?: string;\n href?: (card: Card) => string;\n /** Posts per page; default 10. */\n pageSize?: number;\n /** Trending window for the sidebar, or false to leave it out; default 30d. */\n trending?: Period | false;\n /** Posts per sidebar section; default 5, 0 hides the sidebar. */\n sidebarSize?: number;\n /** Excerpt length in characters; default 220. */\n excerptLength?: number;\n /** Signed in, posts unread within this many days are marked; default 30. */\n unreadDays?: number;\n tiers?: readonly string[];\n labels?: PostCardProps[\"labels\"];\n empty?: ReactNode;\n client?: ContentKitClient;\n appearance?: ContentKitUiAppearance;\n className?: string;\n}\n\n/**\n * Posts newest first with cover, date, excerpt and an unread mark, beside a\n * Newest/Trending sidebar. Trending is the kind's popular window; it never\n * counts views.\n */\nexport function PostList({ kind, language, href, pageSize = 10, trending = \"30d\", sidebarSize = 5, excerptLength = 220, unreadDays = 30, tiers, labels, empty, client, appearance, className }: PostListProps) {\n const m = useMessages();\n const { t } = m;\n const list = useContents(useMemo(() => ({ kind, sort: \"newest\" as const, limit: pageSize, display_language: language }), [kind, pageSize, language]), { client });\n const reads = usePostReads(unreadDays);\n const url = (card: Card) => (href ? href(card) : card.path);\n const sidebar = sidebarSize > 0;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-6 text-sm\", sidebar && \"lg:grid-cols-[minmax(0,1fr)_18rem]\", className)} data-ckui=\"post-list\">\n <div className=\"grid min-w-0 content-start gap-3\">\n {list.loading ? (\n <ol className=\"grid divide-y divide-border\" aria-busy>\n {Array.from({ length: Math.min(pageSize, 4) }, (_, i) => (\n <li key={i} className=\"grid gap-4 py-4 sm:grid-cols-[14rem_minmax(0,1fr)]\">\n <Skeleton className=\"aspect-[331/232] w-full rounded-lg\" />\n <div className=\"grid content-start gap-2\">\n <Skeleton className=\"h-5 w-3/4\" />\n <Skeleton className=\"h-3 w-24\" />\n <Skeleton className=\"h-3 w-full\" />\n <Skeleton className=\"h-3 w-5/6\" />\n </div>\n </li>\n ))}\n </ol>\n ) : list.items.length === 0 ? (\n <p className=\"text-muted-foreground\">{empty ?? t(\"contents.empty\")}</p>\n ) : (\n <ol className=\"grid divide-y divide-border\">\n {list.items.map((card) => (\n <PostRow\n key={card.id}\n card={card}\n url={url(card)}\n unread={reads.unread(card.id, card.published_at, card.viewer?.seen || card.viewer?.completed)}\n excerptLength={excerptLength}\n lock={lockLabel(m, card.access, tiers, labels)}\n />\n ))}\n </ol>\n )}\n {list.hasMore && (\n <Button variant=\"outline\" className=\"justify-self-center\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {list.loadingMore ? t(\"contents.loading\") : t(\"contents.loadMore\")}\n </Button>\n )}\n </div>\n {sidebar && (\n <aside className=\"grid content-start gap-6\" data-ckui=\"post-list-sidebar\">\n <SidebarSection title={t(\"postList.newest\")} kind={kind} sort=\"newest\" size={sidebarSize} language={language} url={url} client={client} />\n {trending && <SidebarSection title={t(\"postList.trending\")} kind={kind} sort={`popular:${trending}`} size={sidebarSize} language={language} url={url} client={client} />}\n </aside>\n )}\n </ContentKitUiRoot>\n );\n}\n\nfunction PostRow({ card, url, unread, lock, excerptLength }: { card: Card; url: string; unread: boolean; lock: string | null; excerptLength: number }) {\n const { t } = useMessages();\n const text = excerpt(card.excerpt ?? \"\", excerptLength);\n return (\n <li className=\"grid gap-4 py-4 sm:grid-cols-[14rem_minmax(0,1fr)]\" data-ckui=\"post-row\" data-id={card.id} data-unread={unread || undefined} lang={card.language || undefined}>\n {card.cover.length > 0 && (\n <a href={url} className=\"relative block aspect-[331/232] overflow-hidden rounded-lg bg-muted\" tabIndex={-1} aria-hidden>\n <RenditionImg outputs={card.cover} alt=\"\" fit=\"cover\" skeleton loading=\"lazy\" className={cn(\"size-full\", lock && \"blur-md\")} />\n </a>\n )}\n <div className={cn(\"grid min-w-0 content-start gap-1.5\", card.cover.length === 0 && \"sm:col-span-2\")}>\n <a href={url} className=\"flex items-start gap-2 text-lg leading-snug font-semibold break-words hover:underline\">\n {unread && (\n <span className=\"mt-2 size-2 shrink-0 rounded-full bg-primary\" data-ckui=\"post-unread\">\n <span className=\"sr-only\">{t(\"postList.unread\")}</span>\n </span>\n )}\n <span>{card.title || card.code}</span>\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 {lock && (\n <span className=\"inline-flex items-center gap-1\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n )}\n </p>\n {text && <p className=\"line-clamp-3 leading-relaxed text-muted-foreground\">{text}</p>}\n </div>\n </li>\n );\n}\n\nfunction SidebarSection({ title, kind, sort, size, language, url, client }: { title: string; kind: string; sort: \"newest\" | `popular:${Period}`; size: number; language?: string; url: (card: Card) => string; client?: ContentKitClient }) {\n const list = useContents(useMemo(() => ({ kind, sort, limit: size, display_language: language }), [kind, sort, size, language]), { client });\n if (!list.loading && list.items.length === 0) return null;\n return (\n <section className=\"grid content-start gap-3\" aria-label={title}>\n <h2 className=\"text-base font-semibold\">{title}</h2>\n <ol className=\"grid gap-3\">\n {list.loading\n ? Array.from({ length: Math.min(size, 3) }, (_, i) => <Skeleton key={i} className=\"h-14 w-full\" />)\n : list.items.slice(0, size).map((card) => (\n <li key={card.id}>\n <a href={url(card)} className={cn(\"grid items-center gap-3 hover:underline\", card.cover.length > 0 && \"grid-cols-[5rem_minmax(0,1fr)]\")}>\n {card.cover.length > 0 && (\n <span className=\"block aspect-[3/2] overflow-hidden rounded-md bg-muted\">\n <RenditionImg outputs={card.cover} alt=\"\" fit=\"cover\" skeleton loading=\"lazy\" className={cn(\"size-full\", !card.access.allowed && \"blur-sm\")} />\n </span>\n )}\n <span className=\"grid min-w-0 gap-0.5\">\n <span className=\"line-clamp-2 font-medium break-words\">{card.title || card.code}</span>\n {card.published_at && <RelativeTime at={card.published_at} className=\"text-xs text-muted-foreground\" />}\n </span>\n </a>\n </li>\n ))}\n </ol>\n </section>\n );\n}\n\n/** At most length characters, cut at a word boundary with an ellipsis. */\nexport function excerpt(text: string, length: number): string {\n const s = text.replace(/\\s+/g, \" \").trim();\n if (s.length <= length) return s;\n const cut = s.slice(0, length + 1);\n const space = cut.lastIndexOf(\" \");\n return `${(space > length * 0.6 ? cut.slice(0, space) : s.slice(0, length)).replace(/[\\s.,;:!?-]+$/, \"\")}…`;\n}\n"],"mappings":"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"}
@@ -5,6 +5,8 @@ import { ContentFilterSettingsProps } from './filters.js';
5
5
  export interface ReaderSettingsProps {
6
6
  /** Content kinds offered in this dialog; omitted shows all configured kinds. */
7
7
  kinds?: ContentFilterSettingsProps["kinds"];
8
+ /** Offers reader content filters (default); false shows only the language, e.g. on admin screens. */
9
+ filters?: boolean;
8
10
  languages: readonly {
9
11
  value: string;
10
12
  label: string;
@@ -70,7 +70,7 @@ function j(y) {
70
70
  }, e.value)) })]
71
71
  })]
72
72
  }),
73
- j && /* @__PURE__ */ w(M, {
73
+ j && y.filters !== !1 && /* @__PURE__ */ w(M, {
74
74
  language: y.language,
75
75
  kinds: y.kinds,
76
76
  client: j,
@@ -1 +1 @@
1
- {"version":3,"file":"reader-settings.js","names":[],"sources":["../../../src/components/content/reader-settings.tsx"],"sourcesContent":["import { Settings01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { useContext, useId, useSyncExternalStore } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useOptionalContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContentFilters } from \"../../react/discovery.js\";\nimport { AppearanceContext, ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from \"#ckui/ui/dialog\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { ContentFilterSettings, type ContentFilterSettingsProps } from \"./filters.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport type { ReaderPreferenceState } from \"../../client/reader-preferences.js\";\n\nconst EMPTY_PREFERENCES: ReaderPreferenceState = Object.freeze({ loading: false, saving: false, pending: false });\nconst noPreferences = () => EMPTY_PREFERENCES;\nconst noSubscription = () => () => {};\n\nexport interface ReaderSettingsProps {\n /** Content kinds offered in this dialog; omitted shows all configured kinds. */\n kinds?: ContentFilterSettingsProps[\"kinds\"];\n languages: readonly { value: string; label: string }[];\n /** The current language, controlled by the host. */\n language: string;\n /** The host applies its own route and preference changes. */\n onLanguageChange: (language: string) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Language preferences and filters for both guests and signed-in readers. */\nexport function ReaderSettings(p: ReaderSettingsProps) {\n const messages = useMessages();\n const { t } = messages;\n const id = useId();\n const client = useOptionalContentKitClient(p.client);\n const preferences = client?.preferences;\n const state = useSyncExternalStore(preferences?.subscribe ?? noSubscription, preferences?.snapshot ?? noPreferences, noPreferences);\n // The selector describes the current view. A saved default must not make a\n // localized deep link look selected in a language it is not displaying.\n const selectedLanguage = p.language;\n const save = useAction(\"preferences.save\", p.onError);\n const inheritedAppearance = useContext(AppearanceContext);\n const appearance = p.appearance ?? inheritedAppearance;\n return (\n <AppearanceContext.Provider value={appearance}>\n <ContentKitUiRoot className={p.className} appearance={appearance} data-ckui=\"reader-settings\">\n <Dialog>\n <DialogTrigger render={<Button type=\"button\" variant=\"ghost\" size=\"sm\" aria-label={t(\"readerSettings.title\")} data-ckui=\"reader-settings-trigger\" />}>\n <HugeiconsIcon icon={Settings01Icon} strokeWidth={2} className=\"size-4\" aria-hidden />\n <span>{p.language.toUpperCase()}</span>\n </DialogTrigger>\n <DialogContent className=\"max-h-[calc(100dvh-2rem)] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t(\"readerSettings.title\")}</DialogTitle>\n <DialogDescription className=\"sr-only\">{t(\"readerSettings.description\")}</DialogDescription>\n </DialogHeader>\n <div className=\"grid gap-3\">\n <Label htmlFor={`${id}-language`}>{t(\"listing.language\")}</Label>\n <Select value={selectedLanguage} disabled={save.pending || state.loading} onValueChange={(value) => {\n if (!value || value === selectedLanguage) return;\n void save.run(async () => { await preferences?.setLanguage(value); p.onLanguageChange(value); });\n }}>\n <SelectTrigger id={`${id}-language`} className=\"w-full\">\n <SelectValue>{p.languages.find((item) => item.value === selectedLanguage)?.label ?? selectedLanguage}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {p.languages.map((item) => <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>)}\n </SelectContent>\n </Select>\n </div>\n {client && <ReaderFilters language={p.language} kinds={p.kinds} client={client} onError={p.onError} />}\n {(state.pending || save.pending) && !state.error && <p className=\"text-sm text-muted-foreground\" role=\"status\">{t(\"readerSettings.savingPreferences\")}</p>}\n <ErrorLine>{state.error || save.error ? t(\"readerSettings.preferencesNotSaved\") : null}</ErrorLine>\n {state.error && preferences && <Button type=\"button\" variant=\"outline\" onClick={() => { void save.run(() => preferences.retry()); }}>{t(\"common.retry\")}</Button>}\n </DialogContent>\n </Dialog>\n </ContentKitUiRoot>\n </AppearanceContext.Provider>\n );\n}\n\nfunction ReaderFilters(p: Pick<ReaderSettingsProps, \"language\" | \"client\" | \"onError\" | \"kinds\">) {\n const { t } = useMessages();\n const id = useId();\n const { filters } = useContentFilters({ language: p.language, client: p.client });\n if (filters && (!filters.filters.length || p.kinds && !p.kinds.some(kind=>filters.applies_to.includes(kind)))) return null;\n return (\n <section aria-labelledby={id} className=\"grid gap-3\">\n <h3 id={id} className=\"font-medium\">{t(\"readerSettings.filters\")}</h3>\n <ContentFilterSettings {...p} includeDeleted={false} />\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reader-settings.js","names":[],"sources":["../../../src/components/content/reader-settings.tsx"],"sourcesContent":["import { Settings01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { useContext, useId, useSyncExternalStore } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useOptionalContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContentFilters } from \"../../react/discovery.js\";\nimport { AppearanceContext, ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from \"#ckui/ui/dialog\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { ContentFilterSettings, type ContentFilterSettingsProps } from \"./filters.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport type { ReaderPreferenceState } from \"../../client/reader-preferences.js\";\n\nconst EMPTY_PREFERENCES: ReaderPreferenceState = Object.freeze({ loading: false, saving: false, pending: false });\nconst noPreferences = () => EMPTY_PREFERENCES;\nconst noSubscription = () => () => {};\n\nexport interface ReaderSettingsProps {\n /** Content kinds offered in this dialog; omitted shows all configured kinds. */\n kinds?: ContentFilterSettingsProps[\"kinds\"];\n /** Offers reader content filters (default); false shows only the language, e.g. on admin screens. */\n filters?: boolean;\n languages: readonly { value: string; label: string }[];\n /** The current language, controlled by the host. */\n language: string;\n /** The host applies its own route and preference changes. */\n onLanguageChange: (language: string) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Language preferences and filters for both guests and signed-in readers. */\nexport function ReaderSettings(p: ReaderSettingsProps) {\n const messages = useMessages();\n const { t } = messages;\n const id = useId();\n const client = useOptionalContentKitClient(p.client);\n const preferences = client?.preferences;\n const state = useSyncExternalStore(preferences?.subscribe ?? noSubscription, preferences?.snapshot ?? noPreferences, noPreferences);\n // The selector describes the current view. A saved default must not make a\n // localized deep link look selected in a language it is not displaying.\n const selectedLanguage = p.language;\n const save = useAction(\"preferences.save\", p.onError);\n const inheritedAppearance = useContext(AppearanceContext);\n const appearance = p.appearance ?? inheritedAppearance;\n return (\n <AppearanceContext.Provider value={appearance}>\n <ContentKitUiRoot className={p.className} appearance={appearance} data-ckui=\"reader-settings\">\n <Dialog>\n <DialogTrigger render={<Button type=\"button\" variant=\"ghost\" size=\"sm\" aria-label={t(\"readerSettings.title\")} data-ckui=\"reader-settings-trigger\" />}>\n <HugeiconsIcon icon={Settings01Icon} strokeWidth={2} className=\"size-4\" aria-hidden />\n <span>{p.language.toUpperCase()}</span>\n </DialogTrigger>\n <DialogContent className=\"max-h-[calc(100dvh-2rem)] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t(\"readerSettings.title\")}</DialogTitle>\n <DialogDescription className=\"sr-only\">{t(\"readerSettings.description\")}</DialogDescription>\n </DialogHeader>\n <div className=\"grid gap-3\">\n <Label htmlFor={`${id}-language`}>{t(\"listing.language\")}</Label>\n <Select value={selectedLanguage} disabled={save.pending || state.loading} onValueChange={(value) => {\n if (!value || value === selectedLanguage) return;\n void save.run(async () => { await preferences?.setLanguage(value); p.onLanguageChange(value); });\n }}>\n <SelectTrigger id={`${id}-language`} className=\"w-full\">\n <SelectValue>{p.languages.find((item) => item.value === selectedLanguage)?.label ?? selectedLanguage}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {p.languages.map((item) => <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>)}\n </SelectContent>\n </Select>\n </div>\n {client && p.filters !== false && <ReaderFilters language={p.language} kinds={p.kinds} client={client} onError={p.onError} />}\n {(state.pending || save.pending) && !state.error && <p className=\"text-sm text-muted-foreground\" role=\"status\">{t(\"readerSettings.savingPreferences\")}</p>}\n <ErrorLine>{state.error || save.error ? t(\"readerSettings.preferencesNotSaved\") : null}</ErrorLine>\n {state.error && preferences && <Button type=\"button\" variant=\"outline\" onClick={() => { void save.run(() => preferences.retry()); }}>{t(\"common.retry\")}</Button>}\n </DialogContent>\n </Dialog>\n </ContentKitUiRoot>\n </AppearanceContext.Provider>\n );\n}\n\nfunction ReaderFilters(p: Pick<ReaderSettingsProps, \"language\" | \"client\" | \"onError\" | \"kinds\">) {\n const { t } = useMessages();\n const id = useId();\n const { filters } = useContentFilters({ language: p.language, client: p.client });\n if (filters && (!filters.filters.length || p.kinds && !p.kinds.some(kind=>filters.applies_to.includes(kind)))) return null;\n return (\n <section aria-labelledby={id} className=\"grid gap-3\">\n <h3 id={id} className=\"font-medium\">{t(\"readerSettings.filters\")}</h3>\n <ContentFilterSettings {...p} includeDeleted={false} />\n </section>\n );\n}\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import { ContentKitClient } from '../../client/client.js';
4
4
  import { GroupAccess, GroupDetail, Sale } from '../../client/generated/wire.js';
5
5
  import { EntityImageSlot } from '../../client/entities.js';
6
6
  import { ContentKitErrorHandler } from '../../react/context.js';
7
- import { GroupLike } from './parts.js';
7
+ import { GroupLike, Labels } from './parts.js';
8
8
  export interface MembershipBoxProps {
9
9
  group: Pick<GroupDetail, "id" | "kind" | "name"> & {
10
10
  access?: GroupAccess;
@@ -29,13 +29,17 @@ export interface GroupBannerProps {
29
29
  id?: string;
30
30
  language?: string;
31
31
  onCheckout?: MembershipBoxProps["onCheckout"];
32
+ /** Section titles of scoped entities (e.g. "characters"); default humanized. */
33
+ labels?: Labels;
34
+ /** A group's link (scope, scoped entities); default its canonical path. */
35
+ href?: (group: GroupLike) => string | undefined;
32
36
  /** Beside the membership box: the host's actions (follow, share, edit). */
33
37
  children?: ReactNode;
34
38
  client?: ContentKitClient;
35
39
  className?: string;
36
40
  appearance?: ContentKitUiAppearance;
37
41
  }
38
- /** A group's page head: its cover across, its avatar, name, description, content count and membership. */
42
+ /** A group's page head: its cover across, its avatar, scope, name, description, content count and membership, then the entities it scopes. */
39
43
  export declare function GroupBanner(p: GroupBannerProps): import('react').JSX.Element;
40
44
  export interface GroupImageEditorProps {
41
45
  group: GroupLike;