@openrails/contentkit-ui 0.86.0 → 0.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +39 -14
  2. package/dist/client/beacons.js +5 -1
  3. package/dist/client/beacons.js.map +1 -1
  4. package/dist/client/content/types.d.ts +4 -2
  5. package/dist/client/content/types.js.map +1 -1
  6. package/dist/client/contents.d.ts +8 -6
  7. package/dist/client/contents.js +10 -7
  8. package/dist/client/contents.js.map +1 -1
  9. package/dist/client/discovery.js +3 -0
  10. package/dist/client/discovery.js.map +1 -1
  11. package/dist/client/errors.d.ts +21 -3
  12. package/dist/client/errors.js +44 -14
  13. package/dist/client/errors.js.map +1 -1
  14. package/dist/client/gallery.d.ts +3 -5
  15. package/dist/client/gallery.js +16 -20
  16. package/dist/client/gallery.js.map +1 -1
  17. package/dist/client/generated/error-codes.d.ts +1 -1
  18. package/dist/client/generated/error-codes.js +8 -0
  19. package/dist/client/generated/error-codes.js.map +1 -1
  20. package/dist/client/generated/routes.d.ts +16 -3
  21. package/dist/client/generated/routes.js +28 -3
  22. package/dist/client/generated/routes.js.map +1 -1
  23. package/dist/client/generated/wire.d.ts +43 -14
  24. package/dist/client/generated/wire.js.map +1 -1
  25. package/dist/client/http.js +27 -27
  26. package/dist/client/http.js.map +1 -1
  27. package/dist/client/index.d.ts +2 -2
  28. package/dist/client/media/client.d.ts +10 -4
  29. package/dist/client/media/client.js +33 -7
  30. package/dist/client/media/client.js.map +1 -1
  31. package/dist/client/media/rules.d.ts +1 -1
  32. package/dist/client/media/rules.js +6 -6
  33. package/dist/client/media/rules.js.map +1 -1
  34. package/dist/client/media/transport.d.ts +1 -1
  35. package/dist/client/media/transport.js +16 -16
  36. package/dist/client/media/transport.js.map +1 -1
  37. package/dist/client/media/windows.js +1 -1
  38. package/dist/client/media/windows.js.map +1 -1
  39. package/dist/client/public.d.ts +2 -2
  40. package/dist/client/public.js +2 -2
  41. package/dist/client/public.js.map +1 -1
  42. package/dist/client/versions.d.ts +6 -4
  43. package/dist/client/versions.js +24 -26
  44. package/dist/client/versions.js.map +1 -1
  45. package/dist/client.js +25 -25
  46. package/dist/components/admin/content-manager.js +233 -232
  47. package/dist/components/admin/content-manager.js.map +1 -1
  48. package/dist/components/content/card.d.ts +5 -3
  49. package/dist/components/content/card.js +131 -109
  50. package/dist/components/content/card.js.map +1 -1
  51. package/dist/components/content/download-button.d.ts +26 -0
  52. package/dist/components/content/download-button.js +51 -0
  53. package/dist/components/content/download-button.js.map +1 -0
  54. package/dist/components/content/editors.d.ts +8 -0
  55. package/dist/components/content/editors.js +299 -229
  56. package/dist/components/content/editors.js.map +1 -1
  57. package/dist/components/content/grid.d.ts +8 -1
  58. package/dist/components/content/grid.js +125 -76
  59. package/dist/components/content/grid.js.map +1 -1
  60. package/dist/components/content/index.d.ts +3 -2
  61. package/dist/components/content/listing.d.ts +3 -1
  62. package/dist/components/content/listing.js.map +1 -1
  63. package/dist/components/content/paywall.js +61 -61
  64. package/dist/components/content/paywall.js.map +1 -1
  65. package/dist/components/media-gallery.d.ts +1 -4
  66. package/dist/components/media-gallery.js +19 -34
  67. package/dist/components/media-gallery.js.map +1 -1
  68. package/dist/components/social/comments.js +1 -1
  69. package/dist/components/social/comments.js.map +1 -1
  70. package/dist/components/social/engagement.d.ts +2 -1
  71. package/dist/components/social/engagement.js +5 -3
  72. package/dist/components/social/engagement.js.map +1 -1
  73. package/dist/i18n/messages.js +49 -46
  74. package/dist/i18n/messages.js.map +1 -1
  75. package/dist/index.js +12 -11
  76. package/dist/locales/de.js +15 -3
  77. package/dist/locales/de.js.map +1 -1
  78. package/dist/locales/discovery/de.js +3 -0
  79. package/dist/locales/discovery/de.js.map +1 -1
  80. package/dist/locales/discovery/en.d.ts +3 -0
  81. package/dist/locales/discovery/en.js +3 -0
  82. package/dist/locales/discovery/en.js.map +1 -1
  83. package/dist/locales/discovery/es.js +3 -0
  84. package/dist/locales/discovery/es.js.map +1 -1
  85. package/dist/locales/discovery/ja.js +3 -0
  86. package/dist/locales/discovery/ja.js.map +1 -1
  87. package/dist/locales/discovery/ko.js +3 -0
  88. package/dist/locales/discovery/ko.js.map +1 -1
  89. package/dist/locales/discovery/zh.js +3 -0
  90. package/dist/locales/discovery/zh.js.map +1 -1
  91. package/dist/locales/en.d.ts +17 -2
  92. package/dist/locales/en.js +15 -3
  93. package/dist/locales/en.js.map +1 -1
  94. package/dist/locales/es.js +15 -3
  95. package/dist/locales/es.js.map +1 -1
  96. package/dist/locales/ja.js +15 -3
  97. package/dist/locales/ja.js.map +1 -1
  98. package/dist/locales/ko.js +15 -3
  99. package/dist/locales/ko.js.map +1 -1
  100. package/dist/locales/zh.js +15 -3
  101. package/dist/locales/zh.js.map +1 -1
  102. package/dist/react/comments.d.ts +1 -1
  103. package/dist/react/comments.js +3 -2
  104. package/dist/react/comments.js.map +1 -1
  105. package/dist/react/contents.d.ts +13 -5
  106. package/dist/react/contents.js +163 -149
  107. package/dist/react/contents.js.map +1 -1
  108. package/dist/react/context.d.ts +1 -1
  109. package/dist/react/context.js.map +1 -1
  110. package/dist/react/detail.d.ts +7 -1
  111. package/dist/react/detail.js +9 -6
  112. package/dist/react/detail.js.map +1 -1
  113. package/dist/react/discovery.d.ts +11 -0
  114. package/dist/react/discovery.js +49 -30
  115. package/dist/react/discovery.js.map +1 -1
  116. package/dist/react/engagement.d.ts +6 -2
  117. package/dist/react/engagement.js +2 -6
  118. package/dist/react/engagement.js.map +1 -1
  119. package/dist/react/index.d.ts +3 -3
  120. package/dist/react/public.js +17 -16
  121. package/dist/react/public.js.map +1 -1
  122. package/dist/react/versions.d.ts +25 -5
  123. package/dist/react/versions.js +58 -37
  124. package/dist/react/versions.js.map +1 -1
  125. package/dist/react.js +9 -9
  126. package/dist/styles.css +1 -1
  127. package/package.json +1 -1
@@ -84,11 +84,11 @@ function H(e) {
84
84
  ctx: j,
85
85
  index: F,
86
86
  onIndex: O
87
- }), t.renderDetails?.(g[F])] }) : /* @__PURE__ */ T(ee, {
87
+ }), t.renderDetails?.(g[F])] }) : /* @__PURE__ */ T($, {
88
88
  ctx: j,
89
89
  onOpen: A
90
90
  }),
91
- /* @__PURE__ */ T(re, {
91
+ /* @__PURE__ */ T(ne, {
92
92
  ctx: j,
93
93
  index: k,
94
94
  onIndex: A
@@ -218,7 +218,7 @@ function K({ side: e, hidden: t, last: n, onClick: r, label: i, lightbox: a }) {
218
218
  }
219
219
  function q({ ctx: e, item: t, position: n, active: i, lightbox: a }) {
220
220
  let { t: o } = r();
221
- if (t.kind === "locked") return /* @__PURE__ */ T(Q, {
221
+ if (t.kind === "locked") return /* @__PURE__ */ T(Z, {
222
222
  ctx: e,
223
223
  item: t
224
224
  });
@@ -240,10 +240,7 @@ function q({ ctx: e, item: t, position: n, active: i, lightbox: a }) {
240
240
  decoding: "async",
241
241
  draggable: !1,
242
242
  className: "absolute inset-0 size-full object-contain"
243
- }), r && /* @__PURE__ */ T($, {
244
- ctx: e,
245
- item: r
246
- })] });
243
+ }), r && /* @__PURE__ */ T(Q, { ctx: e })] });
247
244
  }
248
245
  let { poster: c, start: l } = B(e, t);
249
246
  return /* @__PURE__ */ T(_, {
@@ -346,10 +343,7 @@ function X({ children: e }) {
346
343
  }), e]
347
344
  });
348
345
  }
349
- function Z(e, t) {
350
- return t === 1 ? e("gallery.lockedOne") : e("gallery.lockedMany", { count: t });
351
- }
352
- function Q({ ctx: e, item: t, tile: n }) {
346
+ function Z({ ctx: e, item: t, tile: n }) {
353
347
  let { t: i } = r();
354
348
  return /* @__PURE__ */ E("div", {
355
349
  className: "absolute inset-0 overflow-hidden bg-muted",
@@ -365,24 +359,18 @@ function Q({ ctx: e, item: t, tile: n }) {
365
359
  icon: L,
366
360
  className: n ? "size-6" : "size-8",
367
361
  strokeWidth: 1.75
368
- }), n ? /* @__PURE__ */ E("span", {
369
- className: "text-lg font-semibold tabular-nums",
370
- children: ["+", t.count]
371
- }) : /* @__PURE__ */ E(w, { children: [/* @__PURE__ */ T("p", {
362
+ }), !n && /* @__PURE__ */ E(w, { children: [/* @__PURE__ */ T("p", {
372
363
  className: "text-sm font-medium text-balance",
373
- children: Z(i, t.count)
364
+ children: i("gallery.locked")
374
365
  }), e.renderLocked && /* @__PURE__ */ T("div", {
375
366
  "data-ckui-noswipe": "",
376
- children: e.renderLocked({
377
- count: t.count,
378
- videos: t.videos
379
- })
367
+ children: e.renderLocked()
380
368
  })] })]
381
369
  })]
382
370
  });
383
371
  }
384
- function $({ ctx: e, item: t }) {
385
- let { t: n } = r();
372
+ function Q({ ctx: e }) {
373
+ let { t } = r();
386
374
  return /* @__PURE__ */ E("div", {
387
375
  className: "absolute inset-x-0 bottom-0 flex flex-wrap items-center justify-between gap-x-3 gap-y-2 bg-linear-to-t from-black/75 via-black/45 to-transparent px-4 pt-10 pb-4 text-white",
388
376
  "data-ckui": "locked-bar",
@@ -392,22 +380,19 @@ function $({ ctx: e, item: t }) {
392
380
  icon: L,
393
381
  className: "size-5 shrink-0",
394
382
  strokeWidth: 1.75
395
- }), Z(n, t.count)]
383
+ }), t("gallery.locked")]
396
384
  }), e.renderLocked && /* @__PURE__ */ T("div", {
397
385
  "data-ckui-noswipe": "",
398
- children: e.renderLocked({
399
- count: t.count,
400
- videos: t.videos
401
- })
386
+ children: e.renderLocked()
402
387
  })]
403
388
  });
404
389
  }
405
- function ee({ ctx: e, onOpen: t }) {
390
+ function $({ ctx: e, onOpen: t }) {
406
391
  let { t: n } = r(), i = e.items.length;
407
392
  return /* @__PURE__ */ T("ul", {
408
393
  className: D("grid gap-1", i === 2 || i === 4 ? "grid-cols-2" : "grid-cols-2 @md:grid-cols-3"),
409
394
  "data-ckui": "grid",
410
- children: e.items.map((r, a) => /* @__PURE__ */ T("li", { children: /* @__PURE__ */ T(te, {
395
+ children: e.items.map((r, a) => /* @__PURE__ */ T("li", { children: /* @__PURE__ */ T(ee, {
411
396
  ctx: e,
412
397
  item: r,
413
398
  label: n("gallery.open", {
@@ -418,9 +403,9 @@ function ee({ ctx: e, onOpen: t }) {
418
403
  }) }, r.key))
419
404
  });
420
405
  }
421
- function te({ ctx: e, item: r, label: i, onOpen: a }) {
406
+ function ee({ ctx: e, item: r, label: i, onOpen: a }) {
422
407
  let [o, s] = C(null), c;
423
- if (r.kind === "locked") c = /* @__PURE__ */ T(Q, {
408
+ if (r.kind === "locked") c = /* @__PURE__ */ T(Z, {
424
409
  ctx: e,
425
410
  item: r,
426
411
  tile: !0
@@ -468,7 +453,7 @@ function te({ ctx: e, item: r, label: i, onOpen: a }) {
468
453
  xhrSetup: e.xhrSetup,
469
454
  fit: "cover"
470
455
  }) : null,
471
- d && /* @__PURE__ */ T(ne, {
456
+ d && /* @__PURE__ */ T(te, {
472
457
  ctx: e,
473
458
  base: d,
474
459
  start: s,
@@ -505,7 +490,7 @@ function te({ ctx: e, item: r, label: i, onOpen: a }) {
505
490
  children: c
506
491
  });
507
492
  }
508
- function ne({ ctx: t, base: n, start: r, target: i }) {
493
+ function te({ ctx: t, base: n, start: r, target: i }) {
509
494
  let a = m({
510
495
  src: `${n}master.m3u8`,
511
496
  xhrSetup: t.xhrSetup,
@@ -531,7 +516,7 @@ function ne({ ctx: t, base: n, start: r, target: i }) {
531
516
  "data-ckui": "tile-preview"
532
517
  });
533
518
  }
534
- function re({ ctx: e, index: t, onIndex: n }) {
519
+ function ne({ ctx: e, index: t, onIndex: n }) {
535
520
  let { t: i } = r(), o = a(e.appearance), [s, l] = C(t ?? 0), [u, d] = C(t);
536
521
  t !== u && (d(t), t !== null && l(t));
537
522
  let p = t !== null, m = f({
@@ -1 +1 @@
1
- {"version":3,"file":"media-gallery.js","names":[],"sources":["../../src/components/media-gallery.tsx"],"sourcesContent":["import { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport {\n AlertCircleIcon,\n ArrowLeft01Icon,\n ArrowRight01Icon,\n Cancel01Icon,\n CarouselHorizontalIcon,\n Download01Icon,\n GridViewIcon,\n MusicNote01Icon,\n PlayIcon,\n SquareLock02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useMemo, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { formatDuration, galleryItems, stageAspect, type GalleryItem, type GalleryLockedItem, type GalleryMediaItem } from \"../client/gallery.js\";\nimport { useCarousel, useGalleryView, useHlsPlayer, useRefreshBeforeExpiry, type GalleryViewOptions, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport { ContentKitUiRoot, useScopeProps } from \"../scope.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { useOptionalContentKitClient } from \"../react/context.js\";\nimport { useMediaRead } from \"../react/read.js\";\nimport { previewStartAt, SpriteFrame, VideoPlayer } from \"./video-player.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ToggleGroup, ToggleGroupItem } from \"#ckui/ui/toggle-group\";\n\nexport interface MediaGalleryProps extends GalleryViewOptions, Pick<HlsPlayerOptions, \"xhrSetup\" | \"refresh\" | \"abr\"> {\n /**\n * The item to show, read through the client: its read (refreshed before\n * the URLs expire, after commits), HLS bases, playlist auth and grant\n * refresh. Or pass `read` (and hlsBase, xhrSetup, refresh) yourself.\n */\n item?: RefBody;\n /** With item: only files under this path prefix. */\n prefix?: string;\n /** With item: each audio file's download name signed into its URL. */\n download?: boolean;\n /**\n * The read API result: files in manifest order with this viewer's access\n * (scope it with a prefix). A download read adds each audio file's download.\n * With `refresh`, the gallery reads again shortly before `read.expires`.\n */\n read?: ReadResult | null;\n /** A ladder's HLS folder from the read's `hls` dir: client.media.hlsBase(ref, dir). */\n hlsBase?: (dir: string) => string;\n /** The item's poster (its public preset, or a URL), drawn on the first video. */\n poster?: PublicPreset | string | null;\n /** Where the first video's preview starts, seconds (e.g. the poster's frame); default 10% in. */\n previewStart?: number;\n /** Playable videos preview muted inline (hover, or in view on touch); default the provider's. */\n inlinePreview?: boolean;\n /** The host's unlock call to action, drawn over the locked item. */\n renderLocked?: (locked: { count: number; videos: number }) => ReactNode;\n /** Below the carousel, for its current item (e.g. downloads). */\n renderDetails?: (item: GalleryItem) => ReactNode;\n /** `sizes` for carousel images. Default \"(min-width: 768px) 720px, 100vw\". */\n sizes?: string;\n /** Tallest the carousel gets; taller media letterboxes. Default \"80svh\": every slide at its native aspect, full width, never taller than the screen; \"none\" lifts it. */\n maxHeight?: string;\n label?: string;\n className?: string;\n appearance?: ContentKitUiAppearance;\n /** Overrides the provider's client (item). */\n client?: ContentKitClient;\n}\n\ninterface Ctx extends MediaGalleryProps {\n items: GalleryItem[];\n}\n\n// The item's poster and preview start belong to its first video.\nfunction videoArt(ctx: Ctx, item: GalleryMediaItem): { poster?: PublicPreset | string; start: number } {\n const first = ctx.items.find((i): i is GalleryMediaItem => i.kind === \"video\") === item;\n return { poster: first ? (ctx.poster ?? undefined) : undefined, start: previewStartAt(first ? ctx.previewStart : undefined, item.file.dur) };\n}\n\nconst baseOf = (ctx: Ctx, item: GalleryMediaItem) => (item.dir && ctx.hlsBase ? ctx.hlsBase(item.dir) : null);\n\n/**\n * A post's images and videos as a swipeable carousel or a tile grid that opens\n * a lightbox, with a view toggle. One item renders alone.\n */\nexport function MediaGallery(given: MediaGalleryProps) {\n const props = useItemRead(given);\n const { read, label, className, appearance, view: chosen, defaultView, onViewChange, storageKey } = props;\n const { t } = useMessages();\n const items = useMemo(() => galleryItems(read), [read]);\n useRefreshBeforeExpiry(read?.expires, props.refresh);\n const [view, setView] = useGalleryView({ view: chosen, defaultView, onViewChange, storageKey });\n const [index, setIndex] = useState(0);\n const [lightbox, setLightbox] = useState<number | null>(null);\n if (items.length === 0) return null;\n const ctx: Ctx = { ...props, items };\n const multi = items.length > 1;\n const current = Math.min(index, items.length - 1);\n const shown = multi ? view : \"carousel\";\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"@container grid gap-2\", className)} data-ckui=\"media-gallery\" data-view={shown} role=\"region\" aria-label={label ?? t(\"gallery.label\")}>\n {multi && (\n <div className=\"flex items-center justify-between gap-2\" data-ckui=\"gallery-header\">\n <span className=\"text-sm text-muted-foreground tabular-nums\" data-ckui=\"gallery-count\">\n {shown === \"carousel\" ? t(\"gallery.counter\", { current: current + 1, total: items.length }) : null}\n </span>\n <ToggleGroup aria-label={t(\"gallery.view\")} value={[shown]} onValueChange={(v) => v[0] && setView(v[0] as typeof view)}>\n <ToggleGroupItem value=\"carousel\" aria-label={t(\"gallery.carousel\")} title={t(\"gallery.carousel\")}>\n <HugeiconsIcon icon={CarouselHorizontalIcon} strokeWidth={2} />\n </ToggleGroupItem>\n <ToggleGroupItem value=\"grid\" aria-label={t(\"gallery.grid\")} title={t(\"gallery.grid\")}>\n <HugeiconsIcon icon={GridViewIcon} strokeWidth={2} />\n </ToggleGroupItem>\n </ToggleGroup>\n </div>\n )}\n {shown === \"carousel\" ? (\n <>\n <Carousel ctx={ctx} index={current} onIndex={setIndex} />\n {props.renderDetails?.(items[current]!)}\n </>\n ) : (\n <Grid ctx={ctx} onOpen={setLightbox} />\n )}\n <Lightbox ctx={ctx} index={lightbox} onIndex={setLightbox} />\n </ContentKitUiRoot>\n );\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\n\n// With item, the read, HLS bases, playlist auth and refresh come from the client.\nfunction useItemRead(p: MediaGalleryProps): MediaGalleryProps {\n const client = useOptionalContentKitClient(p.client);\n if (p.item && !client) throw new Error(\"contentkit-ui: MediaGallery item needs `client` or a <ContentKitProvider>\");\n // One refresh timer for the gallery and its players, keyed by r.refresh.\n const r = useMediaRead(p.item ?? NO_ITEM, { prefix: p.prefix, download: p.download, read: p.item ? p.read : null, refresh: false, client });\n const item = p.item;\n const hlsBase = useCallback((dir: string) => client!.media.hlsBase(item!, dir), [client, item]);\n if (!item || !client) return p;\n return { ...p, read: r.read, hlsBase: p.hlsBase ?? hlsBase, xhrSetup: p.xhrSetup ?? client.media.xhrSetup, refresh: p.refresh ?? r.refresh };\n}\n\nfunction Carousel({ ctx, index: given, onIndex, lightbox }: { ctx: Ctx; index: number; onIndex: (i: number) => void; lightbox?: boolean }) {\n const { t } = useMessages();\n const { items } = ctx;\n const c = useCarousel({ count: items.length, index: given, onIndexChange: onIndex });\n const { index } = c;\n const multi = items.length > 1;\n const stage = lightbox ? {} : { aspectRatio: String(stageAspect(items, index)), maxHeight: ctx.maxHeight ?? \"80svh\" };\n return (\n <div\n className={cn(\"group/carousel relative outline-none\", lightbox ? \"size-full\" : \"grid gap-2\")}\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"carousel\" : undefined}\n aria-label={multi ? (ctx.label ?? t(\"gallery.label\")) : undefined}\n tabIndex={multi && !lightbox ? 0 : undefined}\n onKeyDown={multi && !lightbox ? c.onKeyDown : undefined}\n data-ckui=\"carousel\"\n >\n <div\n className={cn(\n \"relative w-full touch-pan-y overflow-hidden select-none\",\n lightbox ? \"h-full\" : \"rounded-lg bg-muted transition-[aspect-ratio] duration-300 ease-out motion-reduce:transition-none\",\n )}\n style={stage}\n data-ckui=\"stage\"\n {...(multi ? c.swipe : {})}\n >\n <div\n className={cn(\"flex h-full\", c.dragging ? \"transition-none\" : \"transition-transform duration-300 ease-out motion-reduce:transition-none\")}\n style={{ transform: `translateX(calc(${-index * 100}% + ${c.offset}px))` }}\n >\n {items.map((item, i) => {\n const near = Math.abs(i - index) <= 1;\n return (\n <div\n key={item.key}\n className=\"relative h-full w-full shrink-0 overflow-hidden\"\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"slide\" : undefined}\n aria-label={multi ? t(\"gallery.slide\", { current: i + 1, total: items.length }) : undefined}\n aria-hidden={i !== index || undefined}\n inert={i !== index}\n data-ckui=\"slide\"\n data-current={i === index ? \"\" : undefined}\n >\n {near && <Slide ctx={ctx} item={item} position={i} active={i === index} lightbox={lightbox} />}\n </div>\n );\n })}\n </div>\n {multi && (\n <>\n <NavButton side=\"prev\" hidden={index === 0} last={index === 1} onClick={c.prev} label={t(\"gallery.previous\")} lightbox={lightbox} />\n <NavButton side=\"next\" hidden={index === items.length - 1} last={index === items.length - 2} onClick={c.next} label={t(\"gallery.next\")} lightbox={lightbox} />\n </>\n )}\n </div>\n {multi && !lightbox && items.length <= 12 && (\n <div className=\"flex justify-center gap-0.5\" data-ckui=\"dots\">\n {items.map((item, i) => (\n <button\n key={item.key}\n type=\"button\"\n className=\"group/dot flex size-4 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n aria-label={t(\"gallery.goTo\", { index: i + 1 })}\n aria-current={i === index || undefined}\n onClick={() => c.go(i)}\n >\n <span className={cn(\"size-1.5 rounded-full transition-colors\", i === index ? \"bg-foreground\" : \"bg-foreground/25 group-hover/dot:bg-foreground/50\")} />\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n// A strip down the stage's side, clear of a video's control bar: the stage's\n// height follows each slide's aspect, so a centred-only target would move\n// between quick clicks. A swipe may start on it. The click that reaches an end\n// removes the button, so focus moves to the carousel and the arrow keys keep working.\nfunction NavButton({ side, hidden, last, onClick, label, lightbox }: { side: \"prev\" | \"next\"; hidden: boolean; last: boolean; onClick: () => void; label: string; lightbox?: boolean }) {\n if (hidden) return null;\n return (\n <button\n type=\"button\"\n aria-label={label}\n data-ckui=\"gallery-nav\"\n data-side={side}\n onClick={(e) => {\n const root = e.currentTarget.closest<HTMLElement>(\"[data-ckui=lightbox], [data-ckui=carousel][tabindex]\");\n onClick();\n if (last && document.activeElement === e.currentTarget) root?.focus({ preventScroll: true });\n }}\n className={cn(\n \"group/nav absolute top-0 bottom-14 z-10 flex items-center outline-none\",\n lightbox ? \"w-16 sm:w-20\" : \"w-12\",\n side === \"prev\" ? (lightbox ? \"left-0 justify-start pl-3 sm:pl-5\" : \"left-0 justify-start pl-2\") : lightbox ? \"right-0 justify-end pr-3 sm:pr-5\" : \"right-0 justify-end pr-2\",\n )}\n >\n <span\n className={cn(\n \"flex items-center justify-center rounded-full backdrop-blur-sm transition-colors group-focus-visible/nav:ring-3 group-focus-visible/nav:ring-ring/60\",\n lightbox ? \"size-10 bg-white/10 text-white group-hover/nav:bg-white/20\" : \"size-8 bg-white/85 text-zinc-900 shadow-md group-hover/nav:bg-white\",\n )}\n >\n <HugeiconsIcon icon={side === \"prev\" ? ArrowLeft01Icon : ArrowRight01Icon} strokeWidth={2} className=\"size-4\" />\n </span>\n </button>\n );\n}\n\nfunction Slide({ ctx, item, position, active, lightbox }: { ctx: Ctx; item: GalleryItem; position: number; active: boolean; lightbox?: boolean }) {\n const { t } = useMessages();\n if (item.kind === \"locked\") return <Locked ctx={ctx} item={item} />;\n const f = item.file;\n if (item.kind === \"audio\") return <AudioSlide item={item} position={position} />;\n if (item.kind === \"image\") {\n if (!f.url) return f.failed ? <ImageFailed file={f} /> : <Processing>{t(\"gallery.processingImage\")}</Processing>;\n // A public teaser of a locked item carries the unlock, so no one swipes past it to find it.\n const locked = item.key.startsWith(\"preview/\") && !lightbox ? ctx.items.find((i): i is GalleryLockedItem => i.kind === \"locked\") : undefined;\n return (\n <>\n <img\n src={f.url}\n alt={t(\"gallery.image\", { index: position + 1 })}\n width={f.w}\n height={f.h}\n sizes={lightbox ? \"100vw\" : (ctx.sizes ?? \"(min-width: 768px) 720px, 100vw\")}\n loading={active ? \"eager\" : \"lazy\"}\n decoding=\"async\"\n draggable={false}\n className=\"absolute inset-0 size-full object-contain\"\n />\n {locked && <LockedBar ctx={ctx} item={locked} />}\n </>\n );\n }\n const { poster, start } = videoArt(ctx, item);\n return (\n <VideoPlayer\n inlinePreview={lightbox ? false : ctx.inlinePreview}\n previewStart={start}\n layout=\"fill\"\n base={baseOf(ctx, item)}\n poster={poster}\n width={f.w}\n height={f.h}\n duration={f.dur}\n active={active}\n xhrSetup={ctx.xhrSetup}\n refresh={ctx.refresh}\n expires={ctx.read?.expires}\n abr={ctx.abr}\n label={t(\"gallery.video\", { index: position + 1 })}\n className={lightbox ? \"bg-transparent\" : undefined}\n />\n );\n}\n\n// An audio file (a ladder's M4A): a native player, its duration and, in a\n// download read, its download.\nfunction AudioSlide({ item, position }: { item: GalleryMediaItem; position: number }) {\n const { t } = useMessages();\n const f = item.file;\n const name = (item.dir ?? f.path).replace(/\\/$/, \"\").split(\"/\").pop();\n const label = t(\"gallery.audio\", { index: position + 1 });\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-muted p-4\" data-ckui=\"audio\">\n <div className=\"flex min-w-0 items-center gap-2 text-sm text-muted-foreground\">\n <HugeiconsIcon icon={MusicNote01Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {name && <span className=\"truncate\">{name}</span>}\n {f.dur ? <span className=\"tabular-nums\">{formatDuration(f.dur)}</span> : null}\n </div>\n {f.failed ? (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {t(\"gallery.failedAudio\")}\n </p>\n ) : f.url ? (\n <audio src={f.url} controls preload=\"metadata\" aria-label={label} className=\"w-full max-w-md\" data-ckui-noswipe=\"\" />\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t(\"gallery.processingAudio\")}</p>\n )}\n {f.download && f.url && (\n <a href={f.url} download={f.download} className=\"inline-flex items-center gap-1.5 text-sm underline-offset-4 hover:underline\" data-ckui-noswipe=\"\">\n <HugeiconsIcon icon={Download01Icon} className=\"size-4\" strokeWidth={2} />\n {t(\"gallery.download\")}\n </a>\n )}\n </div>\n );\n}\n\n// Why an image cannot be shown (editors only: the read API's failed fields).\nfunction ImageFailed({ file }: { file: FileInfo }) {\n const { t, error } = useMessages();\n const f = file.failed;\n const text = f?.code ? error({ code: f.code, message: f.message, details: f.details, refusal: true }) : t(\"gallery.failedImage\");\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted p-4 text-center text-sm text-destructive\" role=\"alert\" data-ckui=\"image-failed\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n <p className=\"max-w-sm text-balance\">{text}</p>\n </div>\n );\n}\n\nfunction Processing({ children }: { children: string }) {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center gap-2 bg-muted text-sm text-muted-foreground\">\n <span aria-hidden className=\"size-4 rounded-full border-2 border-current/30 border-t-current motion-safe:animate-spin\" />\n {children}\n </div>\n );\n}\n\nfunction lockedText(t: ReturnType<typeof useMessages>[\"t\"], count: number) {\n return count === 1 ? t(\"gallery.lockedOne\") : t(\"gallery.lockedMany\", { count });\n}\n\nfunction Locked({ ctx, item, tile }: { ctx: Ctx; item: GalleryLockedItem; tile?: boolean }) {\n const { t } = useMessages();\n return (\n <div className=\"absolute inset-0 overflow-hidden bg-muted\" data-ckui=\"locked\">\n {item.backdrop && <img src={item.backdrop} alt=\"\" draggable={false} className=\"absolute inset-0 size-full scale-110 object-cover blur-xl\" />}\n <div className={cn(\"absolute inset-0 flex flex-col items-center justify-center gap-3 p-4 text-center\", item.backdrop ? \"bg-black/45 text-white\" : \"text-foreground\")}>\n <HugeiconsIcon icon={SquareLock02Icon} className={tile ? \"size-6\" : \"size-8\"} strokeWidth={1.75} />\n {tile ? (\n <span className=\"text-lg font-semibold tabular-nums\">+{item.count}</span>\n ) : (\n <>\n <p className=\"text-sm font-medium text-balance\">{lockedText(t, item.count)}</p>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked({ count: item.count, videos: item.videos })}</div>}\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction LockedBar({ ctx, item }: { ctx: Ctx; item: GalleryLockedItem }) {\n const { t } = useMessages();\n return (\n <div\n className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-center justify-between gap-x-3 gap-y-2 bg-linear-to-t from-black/75 via-black/45 to-transparent px-4 pt-10 pb-4 text-white\"\n data-ckui=\"locked-bar\"\n >\n <span className=\"flex items-center gap-2 text-sm font-medium\">\n <HugeiconsIcon icon={SquareLock02Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {lockedText(t, item.count)}\n </span>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked({ count: item.count, videos: item.videos })}</div>}\n </div>\n );\n}\n\nfunction Grid({ ctx, onOpen }: { ctx: Ctx; onOpen: (i: number) => void }) {\n const { t } = useMessages();\n const n = ctx.items.length;\n return (\n <ul className={cn(\"grid gap-1\", n === 2 || n === 4 ? \"grid-cols-2\" : \"grid-cols-2 @md:grid-cols-3\")} data-ckui=\"grid\">\n {ctx.items.map((item, i) => (\n <li key={item.key}>\n <Tile ctx={ctx} item={item} label={t(\"gallery.open\", { index: i + 1, total: n })} onOpen={() => onOpen(i)} />\n </li>\n ))}\n </ul>\n );\n}\n\nfunction Tile({ ctx, item, label, onOpen }: { ctx: Ctx; item: GalleryItem; label: string; onOpen: () => void }) {\n const [el, setEl] = useState<HTMLButtonElement | null>(null);\n let body: ReactNode;\n if (item.kind === \"locked\") body = <Locked ctx={ctx} item={item} tile />;\n else if (item.kind === \"audio\")\n body = (\n <span className=\"absolute inset-0 flex flex-col items-center justify-center gap-1 bg-muted text-muted-foreground\">\n <HugeiconsIcon icon={item.file.failed ? AlertCircleIcon : MusicNote01Icon} className={cn(\"size-7\", item.file.failed && \"text-destructive\")} strokeWidth={1.75} />\n {item.file.dur ? <span className=\"text-[11px] font-medium tabular-nums\">{formatDuration(item.file.dur)}</span> : null}\n </span>\n );\n else if (item.kind === \"image\")\n body = item.file.url ? (\n <img src={item.file.url} alt=\"\" loading=\"lazy\" decoding=\"async\" draggable={false} className=\"absolute inset-0 size-full object-cover transition-transform duration-300 motion-safe:group-hover:scale-[1.03]\" />\n ) : item.file.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : (\n <Processing>{\"\"}</Processing>\n );\n else {\n const f = item.file;\n const { poster, start } = videoArt(ctx, item);\n const covers = typeof poster === \"string\" ? [] : publicRenditions(poster);\n const base = baseOf(ctx, item);\n body = (\n <>\n {typeof poster === \"string\" ? (\n <img src={poster} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"absolute inset-0 size-full object-cover\" />\n ) : covers.length ? (\n <RenditionImg outputs={covers} loading=\"lazy\" className=\"absolute inset-0 size-full object-cover\" />\n ) : base ? (\n <SpriteFrame vtt={`${base}sprite.vtt`} xhrSetup={ctx.xhrSetup} fit=\"cover\" />\n ) : null}\n {base && <TilePreview ctx={ctx} base={base} start={start} target={el} />}\n {f.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : !base ? (\n <Processing>{\"\"}</Processing>\n ) : (\n <span className=\"pointer-events-none absolute top-1/2 left-1/2 flex size-10 -translate-1/2 items-center justify-center rounded-full bg-black/55 text-white backdrop-blur-sm transition-opacity group-hover:opacity-0\">\n <HugeiconsIcon icon={PlayIcon} className=\"ml-0.5 size-5 fill-current\" strokeWidth={1.5} />\n </span>\n )}\n {f.dur ? (\n <span className=\"pointer-events-none absolute right-1.5 bottom-1.5 rounded bg-black/65 px-1.5 py-0.5 text-[11px] font-medium text-white tabular-nums\">\n {formatDuration(f.dur)}\n </span>\n ) : null}\n </>\n );\n }\n return (\n <button\n type=\"button\"\n aria-label={label}\n onClick={onOpen}\n ref={setEl}\n className=\"group relative block aspect-square w-full overflow-hidden rounded-md bg-muted outline-none focus-visible:ring-3 focus-visible:ring-ring/60\"\n data-ckui=\"tile\"\n data-kind={item.kind}\n >\n {body}\n </button>\n );\n}\n\n// A grid tile's inline preview; the tile opens the lightbox for real playback.\nfunction TilePreview({ ctx, base, start, target }: { ctx: Ctx; base: string; start: number; target: HTMLElement | null }) {\n const player = useHlsPlayer({ src: `${base}master.m3u8`, xhrSetup: ctx.xhrSetup, refresh: ctx.refresh, expires: ctx.read?.expires, abr: ctx.abr, qualityKey: null });\n useInlinePreview({ setting: ctx.inlinePreview, available: true, target, start: () => player.preview(start), stop: player.unload });\n return (\n <video\n ref={player.ref}\n muted\n playsInline\n preload=\"none\"\n aria-hidden\n tabIndex={-1}\n className={cn(\n \"pointer-events-none absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-300 motion-reduce:transition-none\",\n player.previewing && player.status === \"playing\" && \"opacity-100\",\n )}\n data-ckui=\"tile-preview\"\n />\n );\n}\n\nfunction Lightbox({ ctx, index, onIndex }: { ctx: Ctx; index: number | null; onIndex: (i: number | null) => void }) {\n const { t } = useMessages();\n const scope = useScopeProps(ctx.appearance);\n const [shown, setShown] = useState(index ?? 0);\n const [prev, setPrev] = useState(index);\n if (index !== prev) {\n setPrev(index);\n if (index !== null) setShown(index);\n }\n const open = index !== null;\n const c = useCarousel({ count: ctx.items.length, index: shown, onIndexChange: setShown });\n return (\n <DialogPrimitive.Root open={open} onOpenChange={(o) => !o && onIndex(null)}>\n <DialogPrimitive.Portal {...scope} data-ckui-theme=\"dark\">\n <DialogPrimitive.Backdrop className=\"fixed inset-0 z-50 bg-black duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\" />\n <DialogPrimitive.Popup\n className=\"fixed inset-0 z-50 flex flex-col bg-black text-white outline-none duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\"\n data-ckui=\"lightbox\"\n onKeyDown={c.onKeyDown}\n >\n <DialogPrimitive.Title className=\"sr-only\">{ctx.label ?? t(\"gallery.lightbox\")}</DialogPrimitive.Title>\n <div className=\"flex h-14 shrink-0 items-center justify-between px-4\">\n <span className=\"text-sm text-white/80 tabular-nums\">{ctx.items.length > 1 ? t(\"gallery.counter\", { current: shown + 1, total: ctx.items.length }) : null}</span>\n <DialogPrimitive.Close render={<Button variant=\"ghost\" size=\"icon\" className=\"rounded-full text-white hover:bg-white/10 hover:text-white\" />}>\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />\n <span className=\"sr-only\">{t(\"common.close\")}</span>\n </DialogPrimitive.Close>\n </div>\n <div className=\"min-h-0 flex-1 pb-4 sm:px-4 sm:pb-6\">\n {open && <Carousel ctx={ctx} index={shown} onIndex={setShown} lightbox />}\n </div>\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"media-gallery.js","names":[],"sources":["../../src/components/media-gallery.tsx"],"sourcesContent":["import { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport {\n AlertCircleIcon,\n ArrowLeft01Icon,\n ArrowRight01Icon,\n Cancel01Icon,\n CarouselHorizontalIcon,\n Download01Icon,\n GridViewIcon,\n MusicNote01Icon,\n PlayIcon,\n SquareLock02Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useMemo, useState, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport { formatDuration, galleryItems, stageAspect, type GalleryItem, type GalleryLockedItem, type GalleryMediaItem } from \"../client/gallery.js\";\nimport { useCarousel, useGalleryView, useHlsPlayer, useRefreshBeforeExpiry, type GalleryViewOptions, type HlsPlayerOptions } from \"../react/gallery.js\";\nimport { useInlinePreview } from \"../react/inline-preview.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport { ContentKitUiRoot, useScopeProps } from \"../scope.js\";\nimport { RenditionImg } from \"./rendition-img.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { useOptionalContentKitClient } from \"../react/context.js\";\nimport { useMediaRead } from \"../react/read.js\";\nimport { previewStartAt, SpriteFrame, VideoPlayer } from \"./video-player.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ToggleGroup, ToggleGroupItem } from \"#ckui/ui/toggle-group\";\n\nexport interface MediaGalleryProps extends GalleryViewOptions, Pick<HlsPlayerOptions, \"xhrSetup\" | \"refresh\" | \"abr\"> {\n /**\n * The item to show, read through the client: its read (refreshed before\n * the URLs expire, after commits), HLS bases, playlist auth and grant\n * refresh. Or pass `read` (and hlsBase, xhrSetup, refresh) yourself.\n */\n item?: RefBody;\n /** With item: only files under this path prefix. */\n prefix?: string;\n /** With item: each audio file's download name signed into its URL. */\n download?: boolean;\n /**\n * The read API result: files in manifest order with this viewer's access\n * (scope it with a prefix). A download read adds each audio file's download.\n * With `refresh`, the gallery reads again shortly before `read.expires`.\n */\n read?: ReadResult | null;\n /** A ladder's HLS folder from the read's `hls` dir: client.media.hlsBase(ref, dir). */\n hlsBase?: (dir: string) => string;\n /** The item's poster (its public preset, or a URL), drawn on the first video. */\n poster?: PublicPreset | string | null;\n /** Where the first video's preview starts, seconds (e.g. the poster's frame); default 10% in. */\n previewStart?: number;\n /** Playable videos preview muted inline (hover, or in view on touch); default the provider's. */\n inlinePreview?: boolean;\n /** The host's unlock call to action, drawn over the locked item. */\n renderLocked?: () => ReactNode;\n /** Below the carousel, for its current item (e.g. downloads). */\n renderDetails?: (item: GalleryItem) => ReactNode;\n /** `sizes` for carousel images. Default \"(min-width: 768px) 720px, 100vw\". */\n sizes?: string;\n /** Tallest the carousel gets; taller media letterboxes. Default \"80svh\": every slide at its native aspect, full width, never taller than the screen; \"none\" lifts it. */\n maxHeight?: string;\n label?: string;\n className?: string;\n appearance?: ContentKitUiAppearance;\n /** Overrides the provider's client (item). */\n client?: ContentKitClient;\n}\n\ninterface Ctx extends MediaGalleryProps {\n items: GalleryItem[];\n}\n\n// The item's poster and preview start belong to its first video.\nfunction videoArt(ctx: Ctx, item: GalleryMediaItem): { poster?: PublicPreset | string; start: number } {\n const first = ctx.items.find((i): i is GalleryMediaItem => i.kind === \"video\") === item;\n return { poster: first ? (ctx.poster ?? undefined) : undefined, start: previewStartAt(first ? ctx.previewStart : undefined, item.file.dur) };\n}\n\nconst baseOf = (ctx: Ctx, item: GalleryMediaItem) => (item.dir && ctx.hlsBase ? ctx.hlsBase(item.dir) : null);\n\n/**\n * A post's images and videos as a swipeable carousel or a tile grid that opens\n * a lightbox, with a view toggle. One item renders alone.\n */\nexport function MediaGallery(given: MediaGalleryProps) {\n const props = useItemRead(given);\n const { read, label, className, appearance, view: chosen, defaultView, onViewChange, storageKey } = props;\n const { t } = useMessages();\n const items = useMemo(() => galleryItems(read), [read]);\n useRefreshBeforeExpiry(read?.expires, props.refresh);\n const [view, setView] = useGalleryView({ view: chosen, defaultView, onViewChange, storageKey });\n const [index, setIndex] = useState(0);\n const [lightbox, setLightbox] = useState<number | null>(null);\n if (items.length === 0) return null;\n const ctx: Ctx = { ...props, items };\n const multi = items.length > 1;\n const current = Math.min(index, items.length - 1);\n const shown = multi ? view : \"carousel\";\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"@container grid gap-2\", className)} data-ckui=\"media-gallery\" data-view={shown} role=\"region\" aria-label={label ?? t(\"gallery.label\")}>\n {multi && (\n <div className=\"flex items-center justify-between gap-2\" data-ckui=\"gallery-header\">\n <span className=\"text-sm text-muted-foreground tabular-nums\" data-ckui=\"gallery-count\">\n {shown === \"carousel\" ? t(\"gallery.counter\", { current: current + 1, total: items.length }) : null}\n </span>\n <ToggleGroup aria-label={t(\"gallery.view\")} value={[shown]} onValueChange={(v) => v[0] && setView(v[0] as typeof view)}>\n <ToggleGroupItem value=\"carousel\" aria-label={t(\"gallery.carousel\")} title={t(\"gallery.carousel\")}>\n <HugeiconsIcon icon={CarouselHorizontalIcon} strokeWidth={2} />\n </ToggleGroupItem>\n <ToggleGroupItem value=\"grid\" aria-label={t(\"gallery.grid\")} title={t(\"gallery.grid\")}>\n <HugeiconsIcon icon={GridViewIcon} strokeWidth={2} />\n </ToggleGroupItem>\n </ToggleGroup>\n </div>\n )}\n {shown === \"carousel\" ? (\n <>\n <Carousel ctx={ctx} index={current} onIndex={setIndex} />\n {props.renderDetails?.(items[current]!)}\n </>\n ) : (\n <Grid ctx={ctx} onOpen={setLightbox} />\n )}\n <Lightbox ctx={ctx} index={lightbox} onIndex={setLightbox} />\n </ContentKitUiRoot>\n );\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\n\n// With item, the read, HLS bases, playlist auth and refresh come from the client.\nfunction useItemRead(p: MediaGalleryProps): MediaGalleryProps {\n const client = useOptionalContentKitClient(p.client);\n if (p.item && !client) throw new Error(\"contentkit-ui: MediaGallery item needs `client` or a <ContentKitProvider>\");\n // One refresh timer for the gallery and its players, keyed by r.refresh.\n const r = useMediaRead(p.item ?? NO_ITEM, { prefix: p.prefix, download: p.download, read: p.item ? p.read : null, refresh: false, client });\n const item = p.item;\n const hlsBase = useCallback((dir: string) => client!.media.hlsBase(item!, dir), [client, item]);\n if (!item || !client) return p;\n return { ...p, read: r.read, hlsBase: p.hlsBase ?? hlsBase, xhrSetup: p.xhrSetup ?? client.media.xhrSetup, refresh: p.refresh ?? r.refresh };\n}\n\nfunction Carousel({ ctx, index: given, onIndex, lightbox }: { ctx: Ctx; index: number; onIndex: (i: number) => void; lightbox?: boolean }) {\n const { t } = useMessages();\n const { items } = ctx;\n const c = useCarousel({ count: items.length, index: given, onIndexChange: onIndex });\n const { index } = c;\n const multi = items.length > 1;\n const stage = lightbox ? {} : { aspectRatio: String(stageAspect(items, index)), maxHeight: ctx.maxHeight ?? \"80svh\" };\n return (\n <div\n className={cn(\"group/carousel relative outline-none\", lightbox ? \"size-full\" : \"grid gap-2\")}\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"carousel\" : undefined}\n aria-label={multi ? (ctx.label ?? t(\"gallery.label\")) : undefined}\n tabIndex={multi && !lightbox ? 0 : undefined}\n onKeyDown={multi && !lightbox ? c.onKeyDown : undefined}\n data-ckui=\"carousel\"\n >\n <div\n className={cn(\n \"relative w-full touch-pan-y overflow-hidden select-none\",\n lightbox ? \"h-full\" : \"rounded-lg bg-muted transition-[aspect-ratio] duration-300 ease-out motion-reduce:transition-none\",\n )}\n style={stage}\n data-ckui=\"stage\"\n {...(multi ? c.swipe : {})}\n >\n <div\n className={cn(\"flex h-full\", c.dragging ? \"transition-none\" : \"transition-transform duration-300 ease-out motion-reduce:transition-none\")}\n style={{ transform: `translateX(calc(${-index * 100}% + ${c.offset}px))` }}\n >\n {items.map((item, i) => {\n const near = Math.abs(i - index) <= 1;\n return (\n <div\n key={item.key}\n className=\"relative h-full w-full shrink-0 overflow-hidden\"\n role={multi ? \"group\" : undefined}\n aria-roledescription={multi ? \"slide\" : undefined}\n aria-label={multi ? t(\"gallery.slide\", { current: i + 1, total: items.length }) : undefined}\n aria-hidden={i !== index || undefined}\n inert={i !== index}\n data-ckui=\"slide\"\n data-current={i === index ? \"\" : undefined}\n >\n {near && <Slide ctx={ctx} item={item} position={i} active={i === index} lightbox={lightbox} />}\n </div>\n );\n })}\n </div>\n {multi && (\n <>\n <NavButton side=\"prev\" hidden={index === 0} last={index === 1} onClick={c.prev} label={t(\"gallery.previous\")} lightbox={lightbox} />\n <NavButton side=\"next\" hidden={index === items.length - 1} last={index === items.length - 2} onClick={c.next} label={t(\"gallery.next\")} lightbox={lightbox} />\n </>\n )}\n </div>\n {multi && !lightbox && items.length <= 12 && (\n <div className=\"flex justify-center gap-0.5\" data-ckui=\"dots\">\n {items.map((item, i) => (\n <button\n key={item.key}\n type=\"button\"\n className=\"group/dot flex size-4 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n aria-label={t(\"gallery.goTo\", { index: i + 1 })}\n aria-current={i === index || undefined}\n onClick={() => c.go(i)}\n >\n <span className={cn(\"size-1.5 rounded-full transition-colors\", i === index ? \"bg-foreground\" : \"bg-foreground/25 group-hover/dot:bg-foreground/50\")} />\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n// A strip down the stage's side, clear of a video's control bar: the stage's\n// height follows each slide's aspect, so a centred-only target would move\n// between quick clicks. A swipe may start on it. The click that reaches an end\n// removes the button, so focus moves to the carousel and the arrow keys keep working.\nfunction NavButton({ side, hidden, last, onClick, label, lightbox }: { side: \"prev\" | \"next\"; hidden: boolean; last: boolean; onClick: () => void; label: string; lightbox?: boolean }) {\n if (hidden) return null;\n return (\n <button\n type=\"button\"\n aria-label={label}\n data-ckui=\"gallery-nav\"\n data-side={side}\n onClick={(e) => {\n const root = e.currentTarget.closest<HTMLElement>(\"[data-ckui=lightbox], [data-ckui=carousel][tabindex]\");\n onClick();\n if (last && document.activeElement === e.currentTarget) root?.focus({ preventScroll: true });\n }}\n className={cn(\n \"group/nav absolute top-0 bottom-14 z-10 flex items-center outline-none\",\n lightbox ? \"w-16 sm:w-20\" : \"w-12\",\n side === \"prev\" ? (lightbox ? \"left-0 justify-start pl-3 sm:pl-5\" : \"left-0 justify-start pl-2\") : lightbox ? \"right-0 justify-end pr-3 sm:pr-5\" : \"right-0 justify-end pr-2\",\n )}\n >\n <span\n className={cn(\n \"flex items-center justify-center rounded-full backdrop-blur-sm transition-colors group-focus-visible/nav:ring-3 group-focus-visible/nav:ring-ring/60\",\n lightbox ? \"size-10 bg-white/10 text-white group-hover/nav:bg-white/20\" : \"size-8 bg-white/85 text-zinc-900 shadow-md group-hover/nav:bg-white\",\n )}\n >\n <HugeiconsIcon icon={side === \"prev\" ? ArrowLeft01Icon : ArrowRight01Icon} strokeWidth={2} className=\"size-4\" />\n </span>\n </button>\n );\n}\n\nfunction Slide({ ctx, item, position, active, lightbox }: { ctx: Ctx; item: GalleryItem; position: number; active: boolean; lightbox?: boolean }) {\n const { t } = useMessages();\n if (item.kind === \"locked\") return <Locked ctx={ctx} item={item} />;\n const f = item.file;\n if (item.kind === \"audio\") return <AudioSlide item={item} position={position} />;\n if (item.kind === \"image\") {\n if (!f.url) return f.failed ? <ImageFailed file={f} /> : <Processing>{t(\"gallery.processingImage\")}</Processing>;\n // A public teaser of a locked item carries the unlock, so no one swipes past it to find it.\n const locked = item.key.startsWith(\"preview/\") && !lightbox ? ctx.items.find((i): i is GalleryLockedItem => i.kind === \"locked\") : undefined;\n return (\n <>\n <img\n src={f.url}\n alt={t(\"gallery.image\", { index: position + 1 })}\n width={f.w}\n height={f.h}\n sizes={lightbox ? \"100vw\" : (ctx.sizes ?? \"(min-width: 768px) 720px, 100vw\")}\n loading={active ? \"eager\" : \"lazy\"}\n decoding=\"async\"\n draggable={false}\n className=\"absolute inset-0 size-full object-contain\"\n />\n {locked && <LockedBar ctx={ctx} />}\n </>\n );\n }\n const { poster, start } = videoArt(ctx, item);\n return (\n <VideoPlayer\n inlinePreview={lightbox ? false : ctx.inlinePreview}\n previewStart={start}\n layout=\"fill\"\n base={baseOf(ctx, item)}\n poster={poster}\n width={f.w}\n height={f.h}\n duration={f.dur}\n active={active}\n xhrSetup={ctx.xhrSetup}\n refresh={ctx.refresh}\n expires={ctx.read?.expires}\n abr={ctx.abr}\n label={t(\"gallery.video\", { index: position + 1 })}\n className={lightbox ? \"bg-transparent\" : undefined}\n />\n );\n}\n\n// An audio file (a ladder's M4A): a native player, its duration and, in a\n// download read, its download.\nfunction AudioSlide({ item, position }: { item: GalleryMediaItem; position: number }) {\n const { t } = useMessages();\n const f = item.file;\n const name = (item.dir ?? f.path).replace(/\\/$/, \"\").split(\"/\").pop();\n const label = t(\"gallery.audio\", { index: position + 1 });\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-muted p-4\" data-ckui=\"audio\">\n <div className=\"flex min-w-0 items-center gap-2 text-sm text-muted-foreground\">\n <HugeiconsIcon icon={MusicNote01Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {name && <span className=\"truncate\">{name}</span>}\n {f.dur ? <span className=\"tabular-nums\">{formatDuration(f.dur)}</span> : null}\n </div>\n {f.failed ? (\n <p className=\"text-sm text-destructive\" role=\"alert\">\n {t(\"gallery.failedAudio\")}\n </p>\n ) : f.url ? (\n <audio src={f.url} controls preload=\"metadata\" aria-label={label} className=\"w-full max-w-md\" data-ckui-noswipe=\"\" />\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t(\"gallery.processingAudio\")}</p>\n )}\n {f.download && f.url && (\n <a href={f.url} download={f.download} className=\"inline-flex items-center gap-1.5 text-sm underline-offset-4 hover:underline\" data-ckui-noswipe=\"\">\n <HugeiconsIcon icon={Download01Icon} className=\"size-4\" strokeWidth={2} />\n {t(\"gallery.download\")}\n </a>\n )}\n </div>\n );\n}\n\n// Why an image cannot be shown (editors only: the read API's failed fields).\nfunction ImageFailed({ file }: { file: FileInfo }) {\n const { t, error } = useMessages();\n const f = file.failed;\n const text = f?.code ? error({ code: f.code, message: f.message, details: f.details, refusal: true }) : t(\"gallery.failedImage\");\n return (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center gap-2 bg-muted p-4 text-center text-sm text-destructive\" role=\"alert\" data-ckui=\"image-failed\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n <p className=\"max-w-sm text-balance\">{text}</p>\n </div>\n );\n}\n\nfunction Processing({ children }: { children: string }) {\n return (\n <div className=\"absolute inset-0 flex items-center justify-center gap-2 bg-muted text-sm text-muted-foreground\">\n <span aria-hidden className=\"size-4 rounded-full border-2 border-current/30 border-t-current motion-safe:animate-spin\" />\n {children}\n </div>\n );\n}\n\nfunction Locked({ ctx, item, tile }: { ctx: Ctx; item: GalleryLockedItem; tile?: boolean }) {\n const { t } = useMessages();\n return (\n <div className=\"absolute inset-0 overflow-hidden bg-muted\" data-ckui=\"locked\">\n {item.backdrop && <img src={item.backdrop} alt=\"\" draggable={false} className=\"absolute inset-0 size-full scale-110 object-cover blur-xl\" />}\n <div className={cn(\"absolute inset-0 flex flex-col items-center justify-center gap-3 p-4 text-center\", item.backdrop ? \"bg-black/45 text-white\" : \"text-foreground\")}>\n <HugeiconsIcon icon={SquareLock02Icon} className={tile ? \"size-6\" : \"size-8\"} strokeWidth={1.75} />\n {!tile && (\n <>\n <p className=\"text-sm font-medium text-balance\">{t(\"gallery.locked\")}</p>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </>\n )}\n </div>\n </div>\n );\n}\n\nfunction LockedBar({ ctx }: { ctx: Ctx }) {\n const { t } = useMessages();\n return (\n <div\n className=\"absolute inset-x-0 bottom-0 flex flex-wrap items-center justify-between gap-x-3 gap-y-2 bg-linear-to-t from-black/75 via-black/45 to-transparent px-4 pt-10 pb-4 text-white\"\n data-ckui=\"locked-bar\"\n >\n <span className=\"flex items-center gap-2 text-sm font-medium\">\n <HugeiconsIcon icon={SquareLock02Icon} className=\"size-5 shrink-0\" strokeWidth={1.75} />\n {t(\"gallery.locked\")}\n </span>\n {ctx.renderLocked && <div data-ckui-noswipe=\"\">{ctx.renderLocked()}</div>}\n </div>\n );\n}\n\nfunction Grid({ ctx, onOpen }: { ctx: Ctx; onOpen: (i: number) => void }) {\n const { t } = useMessages();\n const n = ctx.items.length;\n return (\n <ul className={cn(\"grid gap-1\", n === 2 || n === 4 ? \"grid-cols-2\" : \"grid-cols-2 @md:grid-cols-3\")} data-ckui=\"grid\">\n {ctx.items.map((item, i) => (\n <li key={item.key}>\n <Tile ctx={ctx} item={item} label={t(\"gallery.open\", { index: i + 1, total: n })} onOpen={() => onOpen(i)} />\n </li>\n ))}\n </ul>\n );\n}\n\nfunction Tile({ ctx, item, label, onOpen }: { ctx: Ctx; item: GalleryItem; label: string; onOpen: () => void }) {\n const [el, setEl] = useState<HTMLButtonElement | null>(null);\n let body: ReactNode;\n if (item.kind === \"locked\") body = <Locked ctx={ctx} item={item} tile />;\n else if (item.kind === \"audio\")\n body = (\n <span className=\"absolute inset-0 flex flex-col items-center justify-center gap-1 bg-muted text-muted-foreground\">\n <HugeiconsIcon icon={item.file.failed ? AlertCircleIcon : MusicNote01Icon} className={cn(\"size-7\", item.file.failed && \"text-destructive\")} strokeWidth={1.75} />\n {item.file.dur ? <span className=\"text-[11px] font-medium tabular-nums\">{formatDuration(item.file.dur)}</span> : null}\n </span>\n );\n else if (item.kind === \"image\")\n body = item.file.url ? (\n <img src={item.file.url} alt=\"\" loading=\"lazy\" decoding=\"async\" draggable={false} className=\"absolute inset-0 size-full object-cover transition-transform duration-300 motion-safe:group-hover:scale-[1.03]\" />\n ) : item.file.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : (\n <Processing>{\"\"}</Processing>\n );\n else {\n const f = item.file;\n const { poster, start } = videoArt(ctx, item);\n const covers = typeof poster === \"string\" ? [] : publicRenditions(poster);\n const base = baseOf(ctx, item);\n body = (\n <>\n {typeof poster === \"string\" ? (\n <img src={poster} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"absolute inset-0 size-full object-cover\" />\n ) : covers.length ? (\n <RenditionImg outputs={covers} loading=\"lazy\" className=\"absolute inset-0 size-full object-cover\" />\n ) : base ? (\n <SpriteFrame vtt={`${base}sprite.vtt`} xhrSetup={ctx.xhrSetup} fit=\"cover\" />\n ) : null}\n {base && <TilePreview ctx={ctx} base={base} start={start} target={el} />}\n {f.failed ? (\n <span className=\"absolute inset-0 flex items-center justify-center bg-muted text-destructive\">\n <HugeiconsIcon icon={AlertCircleIcon} className=\"size-6\" />\n </span>\n ) : !base ? (\n <Processing>{\"\"}</Processing>\n ) : (\n <span className=\"pointer-events-none absolute top-1/2 left-1/2 flex size-10 -translate-1/2 items-center justify-center rounded-full bg-black/55 text-white backdrop-blur-sm transition-opacity group-hover:opacity-0\">\n <HugeiconsIcon icon={PlayIcon} className=\"ml-0.5 size-5 fill-current\" strokeWidth={1.5} />\n </span>\n )}\n {f.dur ? (\n <span className=\"pointer-events-none absolute right-1.5 bottom-1.5 rounded bg-black/65 px-1.5 py-0.5 text-[11px] font-medium text-white tabular-nums\">\n {formatDuration(f.dur)}\n </span>\n ) : null}\n </>\n );\n }\n return (\n <button\n type=\"button\"\n aria-label={label}\n onClick={onOpen}\n ref={setEl}\n className=\"group relative block aspect-square w-full overflow-hidden rounded-md bg-muted outline-none focus-visible:ring-3 focus-visible:ring-ring/60\"\n data-ckui=\"tile\"\n data-kind={item.kind}\n >\n {body}\n </button>\n );\n}\n\n// A grid tile's inline preview; the tile opens the lightbox for real playback.\nfunction TilePreview({ ctx, base, start, target }: { ctx: Ctx; base: string; start: number; target: HTMLElement | null }) {\n const player = useHlsPlayer({ src: `${base}master.m3u8`, xhrSetup: ctx.xhrSetup, refresh: ctx.refresh, expires: ctx.read?.expires, abr: ctx.abr, qualityKey: null });\n useInlinePreview({ setting: ctx.inlinePreview, available: true, target, start: () => player.preview(start), stop: player.unload });\n return (\n <video\n ref={player.ref}\n muted\n playsInline\n preload=\"none\"\n aria-hidden\n tabIndex={-1}\n className={cn(\n \"pointer-events-none absolute inset-0 size-full object-cover opacity-0 transition-opacity duration-300 motion-reduce:transition-none\",\n player.previewing && player.status === \"playing\" && \"opacity-100\",\n )}\n data-ckui=\"tile-preview\"\n />\n );\n}\n\nfunction Lightbox({ ctx, index, onIndex }: { ctx: Ctx; index: number | null; onIndex: (i: number | null) => void }) {\n const { t } = useMessages();\n const scope = useScopeProps(ctx.appearance);\n const [shown, setShown] = useState(index ?? 0);\n const [prev, setPrev] = useState(index);\n if (index !== prev) {\n setPrev(index);\n if (index !== null) setShown(index);\n }\n const open = index !== null;\n const c = useCarousel({ count: ctx.items.length, index: shown, onIndexChange: setShown });\n return (\n <DialogPrimitive.Root open={open} onOpenChange={(o) => !o && onIndex(null)}>\n <DialogPrimitive.Portal {...scope} data-ckui-theme=\"dark\">\n <DialogPrimitive.Backdrop className=\"fixed inset-0 z-50 bg-black duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\" />\n <DialogPrimitive.Popup\n className=\"fixed inset-0 z-50 flex flex-col bg-black text-white outline-none duration-150 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0 motion-reduce:animate-none\"\n data-ckui=\"lightbox\"\n onKeyDown={c.onKeyDown}\n >\n <DialogPrimitive.Title className=\"sr-only\">{ctx.label ?? t(\"gallery.lightbox\")}</DialogPrimitive.Title>\n <div className=\"flex h-14 shrink-0 items-center justify-between px-4\">\n <span className=\"text-sm text-white/80 tabular-nums\">{ctx.items.length > 1 ? t(\"gallery.counter\", { current: shown + 1, total: ctx.items.length }) : null}</span>\n <DialogPrimitive.Close render={<Button variant=\"ghost\" size=\"icon\" className=\"rounded-full text-white hover:bg-white/10 hover:text-white\" />}>\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} />\n <span className=\"sr-only\">{t(\"common.close\")}</span>\n </DialogPrimitive.Close>\n </div>\n <div className=\"min-h-0 flex-1 pb-4 sm:px-4 sm:pb-6\">\n {open && <Carousel ctx={ctx} index={shown} onIndex={setShown} lightbox />}\n </div>\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n}\n"],"mappings":"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"}
@@ -79,7 +79,7 @@ function Q(e) {
79
79
  "data-locked": "",
80
80
  children: [Q !== !1 && /* @__PURE__ */ B("h2", {
81
81
  className: "text-base font-semibold",
82
- children: Q
82
+ children: e.heading ?? s("comments.title")
83
83
  }), /* @__PURE__ */ B("p", {
84
84
  className: "rounded-lg border border-border p-3 text-muted-foreground",
85
85
  "data-ckui": "comments-locked",
@@ -1 +1 @@
1
- {"version":3,"file":"comments.js","names":[],"sources":["../../../src/components/social/comments.tsx"],"sourcesContent":["import { MoreHorizontalIcon, ThumbsDownIcon, ThumbsUpIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { createContext, useContext, useId, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { BanScope, Reaction, Sort } from \"../../client/content/types.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { Target } from \"../../client/content/types.js\";\nimport type { BanNotice as BanNoticeData, Comment, CommentBan, CommentStanding, PublicUser } from \"../../client/generated/wire.js\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useCanComment, useCommentBans, useCommentReplies, useComments, type UseComments } from \"../../react/comments.js\";\nimport { useContentConfig } from \"../../react/config.js\";\nimport { ContentKitContext, useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from \"#ckui/ui/dropdown-menu\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Textarea } from \"#ckui/ui/textarea\";\nimport { BanNotice, CommentBanDialog } from \"./bans.js\";\nimport { ConfirmDialog, contentError, ErrorLine, RelativeTime, UserAvatar, UserName } from \"./parts.js\";\n\nexport interface CommentsProps {\n /** The commented content item, version or group. */\n item: Target;\n /** One language's thread: shown, and joined by new comments. */\n language?: string;\n /** The item's comment count, for the heading; without it the heading has none. */\n count?: number;\n /** Initial order: newest (default) or best (top). */\n sort?: Sort;\n /** Top-level comments per page. Default 20. */\n pageSize?: number;\n /** Replies per page. Default 10. */\n repliesPageSize?: number;\n /**\n * Asks a signed-out visitor to sign in where the server takes no anonymous\n * comments or reactions (default the provider's). Where it does, they\n * comment under a name and react anonymously.\n */\n onSignIn?: () => void;\n /** A body as the host formats it (links, spoilers); default plain text. */\n renderBody?: (body: string, comment: Comment) => ReactNode;\n /** A profile URL for an author. */\n userHref?: (user: PublicUser) => string | undefined;\n /** The heading; false hides it. */\n heading?: ReactNode | false;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ninterface Thread {\n item: Target;\n comments: UseComments;\n standing: CommentStanding | null;\n /** undefined: not known yet. */\n signedIn?: boolean;\n /** Runs fn, or asks to sign in where signed-out visitors may not comment or react. */\n gate: (kind: Act, fn: () => void) => void;\n /** false: signed out where the server refuses it, with no sign-in to offer. */\n can: (kind: Act) => boolean;\n /** The server's longest comment (the standing's); undefined until it loads. */\n maxLength?: number;\n repliesPageSize: number;\n renderBody?: CommentsProps[\"renderBody\"];\n userHref?: CommentsProps[\"userHref\"];\n report: (e: unknown, op: \"comment.post\" | \"comment.edit\" | \"comment.delete\" | \"comment.react\" | \"comment.restore\") => void;\n /** Users banned in each scope the caller holds, with the ban. */\n banned: Map<string, { scope: BanScope; ban: CommentBan }[]>;\n openBan: (user: { id: string; name: string }, ban?: CommentBan) => void;\n liftBan: (user: { id: string; name: string }) => void;\n /** A post was refused by a ban: its notice. */\n onBanned: (ban: BanNoticeData) => void;\n /** A write was refused as locked: the standing changed. */\n onLocked: () => void;\n}\n\ntype Act = \"comment\" | \"react\";\n\nconst ThreadContext = createContext<Thread | null>(null);\nconst useThread = () => useContext(ThreadContext)!;\n\nconst mac = typeof navigator !== \"undefined\" && /Mac|iP(hone|ad)/.test(navigator.platform);\nconst TRUNCATE = 300;\n\nconst authorName = (c: Comment, anonymous: string) => c.author?.username ?? c.anon_name ?? anonymous;\n\n/**\n * An item's comment thread: top-level comments a page at a time with\n * one-level replies, tombstones, held and rejected states shown to their\n * author, reactions, edit, delete, ban and rate-limit notices, and a sign-in\n * prompt. Moderators (as the server says) edit, delete and restore any\n * comment; an item's owner and site staff ban commenters.\n */\nexport function Comments(p: CommentsProps) {\n const m = useMessages();\n const { t } = m;\n const ctx = useContext(ContentKitContext);\n const [sort, setSort] = useState<Sort>(p.sort ?? \"newest\");\n const comments = useComments(p.item, { sort, language: p.language, pageSize: p.pageSize ?? 20, client: p.client });\n const { standing, reload: reloadStanding } = useCanComment(p.item, { client: p.client });\n const { config } = useContentConfig({ client: p.client });\n const report = useErrorReporter(p.onError);\n const signIn = p.onSignIn ?? ctx?.onSignIn;\n const viewer = ctx?.viewer;\n const signedIn = standing ? !!standing.user_id : viewer ? true : viewer === null ? false : undefined;\n const scopes = useMemo(() => standing?.ban_scopes ?? [], [standing]);\n const ownerBans = useCommentBans({ scope: \"owner\", pageSize: 100, enabled: scopes.includes(\"owner\"), client: p.client });\n const globalBans = useCommentBans({ scope: \"global\", pageSize: 100, enabled: scopes.includes(\"global\"), client: p.client });\n const [banning, setBanning] = useState<{ user: { id: string; name: string }; ban?: CommentBan } | null>(null);\n const [lifting, setLifting] = useState<{ id: string; name: string } | null>(null);\n const [refusal, setRefusal] = useState<BanNoticeData | null>(null);\n\n const banned = useMemo(() => {\n const out = new Map<string, { scope: BanScope; ban: CommentBan }[]>();\n const add = (scope: BanScope, list: CommentBan[]) => {\n for (const ban of list) if (!ban.expired) out.set(ban.user_id, [...(out.get(ban.user_id) ?? []), { scope, ban }]);\n };\n if (scopes.includes(\"owner\")) add(\"owner\", ownerBans.items);\n if (scopes.includes(\"global\")) add(\"global\", globalBans.items);\n return out;\n }, [scopes, ownerBans.items, globalBans.items]);\n\n // Signed out where the server refuses it: the standing says for comments, the config for reactions.\n const mustSignIn = (kind: Act) => signedIn === false && (kind === \"comment\" ? standing?.anonymous === false : config?.anonymous.reactions === false);\n const thread: Thread = {\n item: p.item,\n comments,\n standing,\n signedIn,\n gate: (kind, fn) => (mustSignIn(kind) ? signIn?.() : fn()),\n can: (kind) => !mustSignIn(kind) || !!signIn,\n maxLength: standing?.max_length,\n repliesPageSize: p.repliesPageSize ?? 10,\n renderBody: p.renderBody,\n userHref: p.userHref,\n report: (e, op) => report(e, op),\n banned,\n openBan: (user, ban) => setBanning({ user, ban }),\n liftBan: setLifting,\n onBanned: (ban) => {\n setRefusal(ban);\n reloadStanding();\n },\n onLocked: reloadStanding,\n };\n const ban = standing?.ban ?? refusal;\n const canWrite = standing ? standing.can_comment : true;\n const heading = p.heading === undefined ? (p.count === undefined ? t(\"comments.title\") : m.plural(\"comments.count\", p.count)) : p.heading;\n\n if (standing?.comments === \"hidden\") return null;\n if (standing?.locked)\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"comments\" data-locked=\"\">\n {heading !== false && <h2 className=\"text-base font-semibold\">{heading}</h2>}\n <p className=\"rounded-lg border border-border p-3 text-muted-foreground\" data-ckui=\"comments-locked\">\n {t(\"comments.unlock\")}\n </p>\n </ContentKitUiRoot>\n );\n return (\n <ThreadContext.Provider value={thread}>\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"comments\">\n {(heading !== false || comments.items.length > 1) && (\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n {heading !== false && <h2 className=\"text-base font-semibold\">{heading}</h2>}\n <Select value={sort} onValueChange={(v) => setSort(v as Sort)}>\n <SelectTrigger size=\"sm\" aria-label={t(\"comments.sort\")} className=\"ms-auto\">\n <SelectValue>{(v: string) => t(v === \"best\" ? \"comments.best\" : \"comments.newest\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"comments.newest\")}</SelectItem>\n <SelectItem value=\"best\">{t(\"comments.best\")}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n )}\n {standing?.comments === \"closed\" ? (\n <p className=\"rounded-lg border border-border p-3 text-muted-foreground\">{t(\"comments.closed\")}</p>\n ) : ban ? (\n <BanNotice ban={ban} />\n ) : mustSignIn(\"comment\") ? (\n signIn ? (\n <Button variant=\"outline\" className=\"justify-self-start\" onClick={signIn}>\n {t(\"comments.signIn\")}\n </Button>\n ) : (\n <p className=\"rounded-lg border border-border p-3 text-muted-foreground\">{t(\"comments.signIn\")}</p>\n )\n ) : !canWrite ? (\n <p className=\"rounded-lg border border-border p-3 text-muted-foreground\">{t(\"comments.locked\")}</p>\n ) : (\n <Composer\n placeholder={t(\"comments.placeholder\")}\n submit={t(\"comments.post\")}\n askName={signedIn === false}\n onSubmit={async (body, name) => (await comments.post(body, { anonName: name }), true)}\n operation=\"comment.post\"\n />\n )}\n <CommentList />\n {banning && (\n <CommentBanDialog\n open\n onOpenChange={(o) => !o && setBanning(null)}\n user={banning.user}\n scopes={banning.ban ? [banning.ban.scope === \"global\" ? \"global\" : \"owner\"] : scopes}\n ban={banning.ban}\n client={p.client}\n onError={p.onError}\n />\n )}\n <ConfirmDialog\n open={!!lifting}\n onOpenChange={(o) => !o && setLifting(null)}\n title={t(\"bans.liftTitle\", { name: lifting?.name ?? \"\" })}\n description={t(\"bans.liftBody\")}\n confirm={t(\"bans.lift\")}\n onConfirm={() => {\n if (!lifting) return;\n for (const b of banned.get(lifting.id) ?? []) {\n void (b.scope === \"global\" ? globalBans : ownerBans).lift(lifting.id).catch((e) => report(e, \"ban.lift\"));\n }\n }}\n />\n </ContentKitUiRoot>\n </ThreadContext.Provider>\n );\n}\n\nfunction CommentList() {\n const { t } = useMessages();\n const { comments } = useThread();\n if (comments.loading && !comments.items.length) {\n return (\n <div className=\"grid gap-4\" aria-busy aria-label={t(\"comments.loading\")}>\n {[0, 1, 2].map((i) => (\n <div key={i} className=\"flex gap-3\">\n <Skeleton className=\"size-8 rounded-full\" />\n <div className=\"grid flex-1 gap-2\">\n <Skeleton className=\"h-3 w-32\" />\n <Skeleton className=\"h-3 w-full\" />\n </div>\n </div>\n ))}\n </div>\n );\n }\n if (comments.error && !comments.items.length) {\n return (\n <div className=\"grid justify-items-start gap-2\" data-ckui=\"comments-error\">\n <ErrorLine>{t(\"comments.loadFailed\")}</ErrorLine>\n <Button size=\"sm\" variant=\"outline\" onClick={comments.reload}>\n {t(\"common.retry\")}\n </Button>\n </div>\n );\n }\n if (!comments.items.length) return <p className=\"text-muted-foreground\">{t(\"comments.empty\")}</p>;\n return (\n <>\n <ul className=\"grid divide-y divide-border\">\n {comments.items.map((c) => (\n <li key={c.id} className=\"py-3 first:pt-0\">\n <TopLevel comment={c} />\n </li>\n ))}\n </ul>\n {comments.error && <ErrorLine error={comments.error} />}\n {comments.hasMore && (\n <Button variant=\"outline\" className=\"justify-self-center\" disabled={comments.loadingMore} onClick={() => void comments.loadMore()}>\n {comments.loadingMore ? t(\"comments.loading\") : t(\"comments.loadMore\")}\n </Button>\n )}\n </>\n );\n}\n\nfunction TopLevel({ comment }: { comment: Comment }) {\n const { t, plural } = useMessages();\n const thread = useThread();\n const [open, setOpen] = useState(false);\n const [replyTo, setReplyTo] = useState<string | null>(null);\n const replies = useCommentReplies(comment.id, { pageSize: thread.repliesPageSize, enabled: open });\n const startReply = (name: string) =>\n thread.gate(\"comment\", () => {\n setReplyTo(name);\n if (comment.reply_count > 0) setOpen(true);\n });\n return (\n <div className=\"grid gap-2\">\n <Row comment={comment} onReply={comment.deleted || comment.moderation || !thread.can(\"comment\") ? undefined : startReply} />\n {(comment.reply_count > 0 || (open && replies.items.length > 0)) && (\n <Button variant=\"link\" size=\"xs\" className=\"ms-11 justify-self-start px-0\" aria-expanded={open} onClick={() => setOpen(!open)}>\n {open ? t(\"comments.hideReplies\") : plural(\"comments.showReplies\", comment.reply_count)}\n </Button>\n )}\n {open && (\n <ul className=\"ms-11 grid gap-3 border-s border-border ps-3\" aria-busy={replies.loading}>\n {replies.loading && !replies.items.length && <li className=\"text-muted-foreground\">{t(\"comments.loadingReplies\")}</li>}\n {replies.items.map((r) => (\n <li key={r.id}>\n <Row comment={r} onReply={r.deleted || r.moderation || !thread.can(\"comment\") ? undefined : startReply} />\n </li>\n ))}\n {replies.error && (\n <li>\n <ErrorLine error={replies.error} />\n </li>\n )}\n {replies.hasMore && (\n <li>\n <Button variant=\"link\" size=\"xs\" className=\"px-0\" disabled={replies.loadingMore} onClick={() => void replies.loadMore()}>\n {t(\"comments.moreReplies\")}\n </Button>\n </li>\n )}\n </ul>\n )}\n {replyTo !== null && (\n <div className=\"ms-11 grid gap-1\">\n <p className=\"text-xs text-muted-foreground\">{t(\"comments.replyingTo\", { name: replyTo })}</p>\n <Composer\n autoFocus\n placeholder={t(\"comments.replyPlaceholder\")}\n submit={t(\"comments.reply\")}\n askName={thread.signedIn === false}\n onCancel={() => setReplyTo(null)}\n operation=\"comment.post\"\n onSubmit={async (body, name) => {\n await thread.comments.post(body, { replyTo: comment.id, anonName: name });\n setReplyTo(null);\n setOpen(true);\n return true;\n }}\n />\n </div>\n )}\n </div>\n );\n}\n\nfunction Row({ comment: c, onReply }: { comment: Comment; onReply?: (name: string) => void }) {\n const m = useMessages();\n const { t } = m;\n const thread = useThread();\n const { standing } = thread;\n const name = authorName(c, t(\"comments.anonymous\"));\n const own = !!standing?.user_id && c.user_id === standing.user_id;\n const canEdit = !c.deleted && (own || !!standing?.moderate);\n const canBan = !c.deleted && !!c.user_id && !own && (standing?.ban_scopes.length ?? 0) > 0;\n const bans = c.user_id ? thread.banned.get(c.user_id) : undefined;\n const [editing, setEditing] = useState(false);\n const [deleting, setDeleting] = useState(false);\n const [expanded, setExpanded] = useState(false);\n const [error, setError] = useState<ContentKitError | null>(null);\n const act = async (op: \"comment.delete\" | \"comment.react\" | \"comment.restore\", fn: () => Promise<unknown>) => {\n setError(null);\n try {\n await fn();\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n thread.report(err, op);\n }\n };\n const react = (v: Reaction) => thread.gate(\"react\", () => void act(\"comment.react\", () => thread.comments.react(c, c.mine === v ? 0 : v)));\n const long = !thread.renderBody && c.body.length > TRUNCATE + 40;\n const body = long && !expanded ? c.body.slice(0, TRUNCATE).trimEnd() + \"…\" : c.body;\n\n return (\n <article className=\"flex gap-3\" data-ckui=\"comment\" data-comment={c.id} aria-label={name}>\n <UserAvatar user={c.deleted ? undefined : c.author} name={c.deleted ? \"?\" : name} />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <header className=\"flex flex-wrap items-center gap-x-2 gap-y-0.5\">\n {c.deleted ? <span className=\"text-muted-foreground\">{t(\"comments.anonymous\")}</span> : <UserName user={c.author} name={name} href={thread.userHref} />}\n <RelativeTime at={c.created_at} className=\"text-xs text-muted-foreground\" />\n {c.moderation === \"held\" && <Badge variant=\"secondary\">{t(\"comments.heldBadge\")}</Badge>}\n {c.moderation === \"rejected\" && <Badge variant=\"destructive\">{t(\"comments.rejectedBadge\")}</Badge>}\n {(canEdit || canBan || (c.deleted && standing?.moderate)) && (\n <DropdownMenu>\n <DropdownMenuTrigger render={<Button variant=\"ghost\" size=\"icon-xs\" className=\"ms-auto\" aria-label={t(\"comments.actions\")} />}>\n <HugeiconsIcon icon={MoreHorizontalIcon} strokeWidth={2} />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {canEdit && <DropdownMenuItem onClick={() => setEditing(true)}>{t(\"comments.edit\")}</DropdownMenuItem>}\n {canEdit && (\n <DropdownMenuItem className=\"text-destructive\" onClick={() => setDeleting(true)}>\n {t(\"comments.delete\")}\n </DropdownMenuItem>\n )}\n {c.deleted && standing?.moderate && <DropdownMenuItem onClick={() => void act(\"comment.restore\", () => thread.comments.restore(c.id))}>{t(\"comments.restore\")}</DropdownMenuItem>}\n {canBan && !bans && <DropdownMenuItem onClick={() => thread.openBan({ id: c.user_id!, name })}>{t(\"comments.ban\")}</DropdownMenuItem>}\n {canBan && bans && <DropdownMenuItem onClick={() => thread.liftBan({ id: c.user_id!, name })}>{t(\"comments.unban\")}</DropdownMenuItem>}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </header>\n {editing ? (\n <Composer\n autoFocus\n initial={c.body}\n submit={t(\"common.save\")}\n onCancel={() => setEditing(false)}\n operation=\"comment.edit\"\n onSubmit={async (next) => {\n if (next !== c.body) await thread.comments.edit(c.id, next);\n setEditing(false);\n return true;\n }}\n />\n ) : c.deleted ? (\n <p className=\"text-muted-foreground italic\">{t(\"comments.deleted\")}</p>\n ) : (\n <div className=\"break-words whitespace-pre-wrap\">\n {thread.renderBody ? thread.renderBody(c.body, c) : body}\n {long && (\n <Button variant=\"link\" size=\"xs\" className=\"ms-1 h-auto px-0\" onClick={() => setExpanded(!expanded)}>\n {expanded ? t(\"comments.showLess\") : t(\"comments.showMore\")}\n </Button>\n )}\n </div>\n )}\n {c.moderation === \"held\" && <p className=\"text-xs text-muted-foreground\">{t(\"comments.held\")}</p>}\n {c.moderation === \"rejected\" && (\n <p className=\"text-xs text-destructive\">{c.moderation_reason ? t(\"comments.rejected\", { reason: c.moderation_reason }) : t(\"comments.rejectedNoReason\")}</p>\n )}\n {!c.deleted && !c.moderation && !editing && (\n <div className=\"-ms-2 flex flex-wrap items-center gap-1\">\n <Button variant={c.mine === 1 ? \"secondary\" : \"ghost\"} size=\"xs\" aria-pressed={c.mine === 1} aria-label={t(\"comments.like\")} disabled={!thread.can(\"react\")} onClick={() => react(1)}>\n <HugeiconsIcon icon={ThumbsUpIcon} strokeWidth={2} />\n <span>{c.likes}</span>\n </Button>\n <Button variant={c.mine === -1 ? \"secondary\" : \"ghost\"} size=\"xs\" aria-pressed={c.mine === -1} aria-label={t(\"comments.dislike\")} disabled={!thread.can(\"react\")} onClick={() => react(-1)}>\n <HugeiconsIcon icon={ThumbsDownIcon} strokeWidth={2} />\n <span>{c.dislikes}</span>\n </Button>\n {onReply && (\n <Button variant=\"ghost\" size=\"xs\" onClick={() => onReply(name)}>\n {t(\"comments.reply\")}\n </Button>\n )}\n </div>\n )}\n <ErrorLine error={error} />\n </div>\n <ConfirmDialog\n open={deleting}\n onOpenChange={setDeleting}\n title={t(\"comments.deleteTitle\")}\n description={t(\"comments.deleteBody\")}\n confirm={t(\"comments.delete\")}\n onConfirm={() => void act(\"comment.delete\", () => thread.comments.remove(c.id))}\n />\n </article>\n );\n}\n\ninterface ComposerProps {\n initial?: string;\n placeholder?: string;\n submit: string;\n /** A signed-out visitor gives a name. */\n askName?: boolean;\n autoFocus?: boolean;\n onCancel?: () => void;\n operation: \"comment.post\" | \"comment.edit\";\n /** Resolves true to clear the field. */\n onSubmit: (body: string, name?: string) => Promise<boolean>;\n}\n\nfunction Composer(p: ComposerProps) {\n const m = useMessages();\n const { t } = m;\n const thread = useThread();\n const ids = useId();\n const [body, setBody] = useState(p.initial ?? \"\");\n const [name, setName] = useState(\"\");\n const [pending, setPending] = useState(false);\n const [invalid, setInvalid] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const area = useRef<HTMLTextAreaElement>(null);\n const max = thread.maxLength;\n const left = max === undefined ? undefined : max - body.length;\n\n const send = async () => {\n const text = body.trim();\n if (!text) return setInvalid(t(\"comments.required\"));\n if (p.askName && !name.trim()) return setInvalid(t(\"comments.nameRequired\"));\n setInvalid(null);\n setError(null);\n setPending(true);\n try {\n if (await p.onSubmit(text, p.askName ? name.trim() : undefined)) setBody(\"\");\n } catch (e) {\n const err = toContentKitError(e);\n thread.report(err, p.operation);\n if (err.code === \"comment_banned\" && err.ban) thread.onBanned(err.ban);\n else if (err.code === \"comments_locked\") thread.onLocked();\n else if (err.code === \"rate_limited\") setError(err.retryAfter ? t(\"comments.rateLimited\", { seconds: err.retryAfter }) : t(\"comments.rateLimitedSoon\"));\n else setError(contentError(m, err));\n } finally {\n setPending(false);\n }\n };\n const onKey = (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n void send();\n } else if (e.key === \"Escape\" && p.onCancel) p.onCancel();\n };\n return (\n <form\n className=\"grid gap-2\"\n data-ckui=\"comment-composer\"\n onSubmit={(e) => {\n e.preventDefault();\n void send();\n }}\n >\n {p.askName && (\n <div className=\"grid gap-1.5 sm:max-w-xs\">\n <Label htmlFor={`${ids}-name`}>{t(\"comments.name\")}</Label>\n <Input id={`${ids}-name`} value={name} maxLength={60} placeholder={t(\"comments.namePlaceholder\")} onChange={(e) => setName(e.target.value)} autoComplete=\"nickname\" />\n </div>\n )}\n <Textarea\n ref={area}\n value={body}\n rows={p.initial === undefined ? 2 : 3}\n maxLength={max}\n autoFocus={p.autoFocus}\n placeholder={p.placeholder}\n aria-label={p.placeholder ?? p.submit}\n aria-invalid={!!invalid}\n aria-describedby={`${ids}-hint`}\n disabled={pending}\n onChange={(e) => {\n setBody(e.target.value);\n if (invalid) setInvalid(null);\n }}\n onKeyDown={onKey}\n />\n <div className=\"flex flex-wrap items-center gap-2\">\n <span id={`${ids}-hint`} className=\"me-auto text-xs text-muted-foreground\" aria-live=\"polite\">\n {invalid ? (\n <span className=\"text-destructive\">{invalid}</span>\n ) : left !== undefined && max !== undefined && left <= max * 0.2 ? (\n m.plural(\"comments.charsLeft\", left)\n ) : (\n <span className=\"pointer-coarse:hidden\">{t(\"comments.submitHint\", { key: mac ? \"⌘\" : \"Ctrl\" })}</span>\n )}\n </span>\n {p.onCancel && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={p.onCancel} disabled={pending}>\n {t(\"common.cancel\")}\n </Button>\n )}\n <Button type=\"submit\" size=\"sm\" disabled={pending}>\n {pending ? t(\"comments.posting\") : p.submit}\n </Button>\n </div>\n {error && <ErrorLine>{error}</ErrorLine>}\n </form>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"comments.js","names":[],"sources":["../../../src/components/social/comments.tsx"],"sourcesContent":["import { MoreHorizontalIcon, ThumbsDownIcon, ThumbsUpIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { createContext, useContext, useId, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { BanScope, Reaction, Sort } from \"../../client/content/types.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { Target } from \"../../client/content/types.js\";\nimport type { BanNotice as BanNoticeData, Comment, CommentBan, CommentStanding, PublicUser } from \"../../client/generated/wire.js\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useCanComment, useCommentBans, useCommentReplies, useComments, type UseComments } from \"../../react/comments.js\";\nimport { useContentConfig } from \"../../react/config.js\";\nimport { ContentKitContext, useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from \"#ckui/ui/dropdown-menu\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Textarea } from \"#ckui/ui/textarea\";\nimport { BanNotice, CommentBanDialog } from \"./bans.js\";\nimport { ConfirmDialog, contentError, ErrorLine, RelativeTime, UserAvatar, UserName } from \"./parts.js\";\n\nexport interface CommentsProps {\n /** The commented content item, version or group. */\n item: Target;\n /** One language's thread: shown, and joined by new comments. */\n language?: string;\n /** The item's comment count, for the heading; without it the heading has none. */\n count?: number;\n /** Initial order: newest (default) or best (top). */\n sort?: Sort;\n /** Top-level comments per page. Default 20. */\n pageSize?: number;\n /** Replies per page. Default 10. */\n repliesPageSize?: number;\n /**\n * Asks a signed-out visitor to sign in where the server takes no anonymous\n * comments or reactions (default the provider's). Where it does, they\n * comment under a name and react anonymously.\n */\n onSignIn?: () => void;\n /** A body as the host formats it (links, spoilers); default plain text. */\n renderBody?: (body: string, comment: Comment) => ReactNode;\n /** A profile URL for an author. */\n userHref?: (user: PublicUser) => string | undefined;\n /** The heading; false hides it. */\n heading?: ReactNode | false;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ninterface Thread {\n item: Target;\n comments: UseComments;\n standing: CommentStanding | null;\n /** undefined: not known yet. */\n signedIn?: boolean;\n /** Runs fn, or asks to sign in where signed-out visitors may not comment or react. */\n gate: (kind: Act, fn: () => void) => void;\n /** false: signed out where the server refuses it, with no sign-in to offer. */\n can: (kind: Act) => boolean;\n /** The server's longest comment (the standing's); undefined until it loads. */\n maxLength?: number;\n repliesPageSize: number;\n renderBody?: CommentsProps[\"renderBody\"];\n userHref?: CommentsProps[\"userHref\"];\n report: (e: unknown, op: \"comment.post\" | \"comment.edit\" | \"comment.delete\" | \"comment.react\" | \"comment.restore\") => void;\n /** Users banned in each scope the caller holds, with the ban. */\n banned: Map<string, { scope: BanScope; ban: CommentBan }[]>;\n openBan: (user: { id: string; name: string }, ban?: CommentBan) => void;\n liftBan: (user: { id: string; name: string }) => void;\n /** A post was refused by a ban: its notice. */\n onBanned: (ban: BanNoticeData) => void;\n /** A write was refused as locked: the standing changed. */\n onLocked: () => void;\n}\n\ntype Act = \"comment\" | \"react\";\n\nconst ThreadContext = createContext<Thread | null>(null);\nconst useThread = () => useContext(ThreadContext)!;\n\nconst mac = typeof navigator !== \"undefined\" && /Mac|iP(hone|ad)/.test(navigator.platform);\nconst TRUNCATE = 300;\n\nconst authorName = (c: Comment, anonymous: string) => c.author?.username ?? c.anon_name ?? anonymous;\n\n/**\n * An item's comment thread: top-level comments a page at a time with\n * one-level replies, tombstones, held and rejected states shown to their\n * author, reactions, edit, delete, ban and rate-limit notices, and a sign-in\n * prompt. Moderators (as the server says) edit, delete and restore any\n * comment; an item's owner and site staff ban commenters.\n */\nexport function Comments(p: CommentsProps) {\n const m = useMessages();\n const { t } = m;\n const ctx = useContext(ContentKitContext);\n const [sort, setSort] = useState<Sort>(p.sort ?? \"newest\");\n const comments = useComments(p.item, { sort, language: p.language, pageSize: p.pageSize ?? 20, client: p.client });\n const { standing, reload: reloadStanding } = useCanComment(p.item, { client: p.client });\n const { config } = useContentConfig({ client: p.client });\n const report = useErrorReporter(p.onError);\n const signIn = p.onSignIn ?? ctx?.onSignIn;\n const viewer = ctx?.viewer;\n const signedIn = standing ? !!standing.user_id : viewer ? true : viewer === null ? false : undefined;\n const scopes = useMemo(() => standing?.ban_scopes ?? [], [standing]);\n const ownerBans = useCommentBans({ scope: \"owner\", pageSize: 100, enabled: scopes.includes(\"owner\"), client: p.client });\n const globalBans = useCommentBans({ scope: \"global\", pageSize: 100, enabled: scopes.includes(\"global\"), client: p.client });\n const [banning, setBanning] = useState<{ user: { id: string; name: string }; ban?: CommentBan } | null>(null);\n const [lifting, setLifting] = useState<{ id: string; name: string } | null>(null);\n const [refusal, setRefusal] = useState<BanNoticeData | null>(null);\n\n const banned = useMemo(() => {\n const out = new Map<string, { scope: BanScope; ban: CommentBan }[]>();\n const add = (scope: BanScope, list: CommentBan[]) => {\n for (const ban of list) if (!ban.expired) out.set(ban.user_id, [...(out.get(ban.user_id) ?? []), { scope, ban }]);\n };\n if (scopes.includes(\"owner\")) add(\"owner\", ownerBans.items);\n if (scopes.includes(\"global\")) add(\"global\", globalBans.items);\n return out;\n }, [scopes, ownerBans.items, globalBans.items]);\n\n // Signed out where the server refuses it: the standing says for comments, the config for reactions.\n const mustSignIn = (kind: Act) => signedIn === false && (kind === \"comment\" ? standing?.anonymous === false : config?.anonymous.reactions === false);\n const thread: Thread = {\n item: p.item,\n comments,\n standing,\n signedIn,\n gate: (kind, fn) => (mustSignIn(kind) ? signIn?.() : fn()),\n can: (kind) => !mustSignIn(kind) || !!signIn,\n maxLength: standing?.max_length,\n repliesPageSize: p.repliesPageSize ?? 10,\n renderBody: p.renderBody,\n userHref: p.userHref,\n report: (e, op) => report(e, op),\n banned,\n openBan: (user, ban) => setBanning({ user, ban }),\n liftBan: setLifting,\n onBanned: (ban) => {\n setRefusal(ban);\n reloadStanding();\n },\n onLocked: reloadStanding,\n };\n const ban = standing?.ban ?? refusal;\n const canWrite = standing ? standing.can_comment : true;\n const heading = p.heading === undefined ? (p.count === undefined ? t(\"comments.title\") : m.plural(\"comments.count\", p.count)) : p.heading;\n\n if (standing?.comments === \"hidden\") return null;\n if (standing?.locked)\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"comments\" data-locked=\"\">\n {heading !== false && <h2 className=\"text-base font-semibold\">{p.heading ?? t(\"comments.title\")}</h2>}\n <p className=\"rounded-lg border border-border p-3 text-muted-foreground\" data-ckui=\"comments-locked\">\n {t(\"comments.unlock\")}\n </p>\n </ContentKitUiRoot>\n );\n return (\n <ThreadContext.Provider value={thread}>\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"comments\">\n {(heading !== false || comments.items.length > 1) && (\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n {heading !== false && <h2 className=\"text-base font-semibold\">{heading}</h2>}\n <Select value={sort} onValueChange={(v) => setSort(v as Sort)}>\n <SelectTrigger size=\"sm\" aria-label={t(\"comments.sort\")} className=\"ms-auto\">\n <SelectValue>{(v: string) => t(v === \"best\" ? \"comments.best\" : \"comments.newest\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"comments.newest\")}</SelectItem>\n <SelectItem value=\"best\">{t(\"comments.best\")}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n )}\n {standing?.comments === \"closed\" ? (\n <p className=\"rounded-lg border border-border p-3 text-muted-foreground\">{t(\"comments.closed\")}</p>\n ) : ban ? (\n <BanNotice ban={ban} />\n ) : mustSignIn(\"comment\") ? (\n signIn ? (\n <Button variant=\"outline\" className=\"justify-self-start\" onClick={signIn}>\n {t(\"comments.signIn\")}\n </Button>\n ) : (\n <p className=\"rounded-lg border border-border p-3 text-muted-foreground\">{t(\"comments.signIn\")}</p>\n )\n ) : !canWrite ? (\n <p className=\"rounded-lg border border-border p-3 text-muted-foreground\">{t(\"comments.locked\")}</p>\n ) : (\n <Composer\n placeholder={t(\"comments.placeholder\")}\n submit={t(\"comments.post\")}\n askName={signedIn === false}\n onSubmit={async (body, name) => (await comments.post(body, { anonName: name }), true)}\n operation=\"comment.post\"\n />\n )}\n <CommentList />\n {banning && (\n <CommentBanDialog\n open\n onOpenChange={(o) => !o && setBanning(null)}\n user={banning.user}\n scopes={banning.ban ? [banning.ban.scope === \"global\" ? \"global\" : \"owner\"] : scopes}\n ban={banning.ban}\n client={p.client}\n onError={p.onError}\n />\n )}\n <ConfirmDialog\n open={!!lifting}\n onOpenChange={(o) => !o && setLifting(null)}\n title={t(\"bans.liftTitle\", { name: lifting?.name ?? \"\" })}\n description={t(\"bans.liftBody\")}\n confirm={t(\"bans.lift\")}\n onConfirm={() => {\n if (!lifting) return;\n for (const b of banned.get(lifting.id) ?? []) {\n void (b.scope === \"global\" ? globalBans : ownerBans).lift(lifting.id).catch((e) => report(e, \"ban.lift\"));\n }\n }}\n />\n </ContentKitUiRoot>\n </ThreadContext.Provider>\n );\n}\n\nfunction CommentList() {\n const { t } = useMessages();\n const { comments } = useThread();\n if (comments.loading && !comments.items.length) {\n return (\n <div className=\"grid gap-4\" aria-busy aria-label={t(\"comments.loading\")}>\n {[0, 1, 2].map((i) => (\n <div key={i} className=\"flex gap-3\">\n <Skeleton className=\"size-8 rounded-full\" />\n <div className=\"grid flex-1 gap-2\">\n <Skeleton className=\"h-3 w-32\" />\n <Skeleton className=\"h-3 w-full\" />\n </div>\n </div>\n ))}\n </div>\n );\n }\n if (comments.error && !comments.items.length) {\n return (\n <div className=\"grid justify-items-start gap-2\" data-ckui=\"comments-error\">\n <ErrorLine>{t(\"comments.loadFailed\")}</ErrorLine>\n <Button size=\"sm\" variant=\"outline\" onClick={comments.reload}>\n {t(\"common.retry\")}\n </Button>\n </div>\n );\n }\n if (!comments.items.length) return <p className=\"text-muted-foreground\">{t(\"comments.empty\")}</p>;\n return (\n <>\n <ul className=\"grid divide-y divide-border\">\n {comments.items.map((c) => (\n <li key={c.id} className=\"py-3 first:pt-0\">\n <TopLevel comment={c} />\n </li>\n ))}\n </ul>\n {comments.error && <ErrorLine error={comments.error} />}\n {comments.hasMore && (\n <Button variant=\"outline\" className=\"justify-self-center\" disabled={comments.loadingMore} onClick={() => void comments.loadMore()}>\n {comments.loadingMore ? t(\"comments.loading\") : t(\"comments.loadMore\")}\n </Button>\n )}\n </>\n );\n}\n\nfunction TopLevel({ comment }: { comment: Comment }) {\n const { t, plural } = useMessages();\n const thread = useThread();\n const [open, setOpen] = useState(false);\n const [replyTo, setReplyTo] = useState<string | null>(null);\n const replies = useCommentReplies(comment.id, { pageSize: thread.repliesPageSize, enabled: open });\n const startReply = (name: string) =>\n thread.gate(\"comment\", () => {\n setReplyTo(name);\n if (comment.reply_count > 0) setOpen(true);\n });\n return (\n <div className=\"grid gap-2\">\n <Row comment={comment} onReply={comment.deleted || comment.moderation || !thread.can(\"comment\") ? undefined : startReply} />\n {(comment.reply_count > 0 || (open && replies.items.length > 0)) && (\n <Button variant=\"link\" size=\"xs\" className=\"ms-11 justify-self-start px-0\" aria-expanded={open} onClick={() => setOpen(!open)}>\n {open ? t(\"comments.hideReplies\") : plural(\"comments.showReplies\", comment.reply_count)}\n </Button>\n )}\n {open && (\n <ul className=\"ms-11 grid gap-3 border-s border-border ps-3\" aria-busy={replies.loading}>\n {replies.loading && !replies.items.length && <li className=\"text-muted-foreground\">{t(\"comments.loadingReplies\")}</li>}\n {replies.items.map((r) => (\n <li key={r.id}>\n <Row comment={r} onReply={r.deleted || r.moderation || !thread.can(\"comment\") ? undefined : startReply} />\n </li>\n ))}\n {replies.error && (\n <li>\n <ErrorLine error={replies.error} />\n </li>\n )}\n {replies.hasMore && (\n <li>\n <Button variant=\"link\" size=\"xs\" className=\"px-0\" disabled={replies.loadingMore} onClick={() => void replies.loadMore()}>\n {t(\"comments.moreReplies\")}\n </Button>\n </li>\n )}\n </ul>\n )}\n {replyTo !== null && (\n <div className=\"ms-11 grid gap-1\">\n <p className=\"text-xs text-muted-foreground\">{t(\"comments.replyingTo\", { name: replyTo })}</p>\n <Composer\n autoFocus\n placeholder={t(\"comments.replyPlaceholder\")}\n submit={t(\"comments.reply\")}\n askName={thread.signedIn === false}\n onCancel={() => setReplyTo(null)}\n operation=\"comment.post\"\n onSubmit={async (body, name) => {\n await thread.comments.post(body, { replyTo: comment.id, anonName: name });\n setReplyTo(null);\n setOpen(true);\n return true;\n }}\n />\n </div>\n )}\n </div>\n );\n}\n\nfunction Row({ comment: c, onReply }: { comment: Comment; onReply?: (name: string) => void }) {\n const m = useMessages();\n const { t } = m;\n const thread = useThread();\n const { standing } = thread;\n const name = authorName(c, t(\"comments.anonymous\"));\n const own = !!standing?.user_id && c.user_id === standing.user_id;\n const canEdit = !c.deleted && (own || !!standing?.moderate);\n const canBan = !c.deleted && !!c.user_id && !own && (standing?.ban_scopes.length ?? 0) > 0;\n const bans = c.user_id ? thread.banned.get(c.user_id) : undefined;\n const [editing, setEditing] = useState(false);\n const [deleting, setDeleting] = useState(false);\n const [expanded, setExpanded] = useState(false);\n const [error, setError] = useState<ContentKitError | null>(null);\n const act = async (op: \"comment.delete\" | \"comment.react\" | \"comment.restore\", fn: () => Promise<unknown>) => {\n setError(null);\n try {\n await fn();\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n thread.report(err, op);\n }\n };\n const react = (v: Reaction) => thread.gate(\"react\", () => void act(\"comment.react\", () => thread.comments.react(c, c.mine === v ? 0 : v)));\n const long = !thread.renderBody && c.body.length > TRUNCATE + 40;\n const body = long && !expanded ? c.body.slice(0, TRUNCATE).trimEnd() + \"…\" : c.body;\n\n return (\n <article className=\"flex gap-3\" data-ckui=\"comment\" data-comment={c.id} aria-label={name}>\n <UserAvatar user={c.deleted ? undefined : c.author} name={c.deleted ? \"?\" : name} />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <header className=\"flex flex-wrap items-center gap-x-2 gap-y-0.5\">\n {c.deleted ? <span className=\"text-muted-foreground\">{t(\"comments.anonymous\")}</span> : <UserName user={c.author} name={name} href={thread.userHref} />}\n <RelativeTime at={c.created_at} className=\"text-xs text-muted-foreground\" />\n {c.moderation === \"held\" && <Badge variant=\"secondary\">{t(\"comments.heldBadge\")}</Badge>}\n {c.moderation === \"rejected\" && <Badge variant=\"destructive\">{t(\"comments.rejectedBadge\")}</Badge>}\n {(canEdit || canBan || (c.deleted && standing?.moderate)) && (\n <DropdownMenu>\n <DropdownMenuTrigger render={<Button variant=\"ghost\" size=\"icon-xs\" className=\"ms-auto\" aria-label={t(\"comments.actions\")} />}>\n <HugeiconsIcon icon={MoreHorizontalIcon} strokeWidth={2} />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {canEdit && <DropdownMenuItem onClick={() => setEditing(true)}>{t(\"comments.edit\")}</DropdownMenuItem>}\n {canEdit && (\n <DropdownMenuItem className=\"text-destructive\" onClick={() => setDeleting(true)}>\n {t(\"comments.delete\")}\n </DropdownMenuItem>\n )}\n {c.deleted && standing?.moderate && <DropdownMenuItem onClick={() => void act(\"comment.restore\", () => thread.comments.restore(c.id))}>{t(\"comments.restore\")}</DropdownMenuItem>}\n {canBan && !bans && <DropdownMenuItem onClick={() => thread.openBan({ id: c.user_id!, name })}>{t(\"comments.ban\")}</DropdownMenuItem>}\n {canBan && bans && <DropdownMenuItem onClick={() => thread.liftBan({ id: c.user_id!, name })}>{t(\"comments.unban\")}</DropdownMenuItem>}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </header>\n {editing ? (\n <Composer\n autoFocus\n initial={c.body}\n submit={t(\"common.save\")}\n onCancel={() => setEditing(false)}\n operation=\"comment.edit\"\n onSubmit={async (next) => {\n if (next !== c.body) await thread.comments.edit(c.id, next);\n setEditing(false);\n return true;\n }}\n />\n ) : c.deleted ? (\n <p className=\"text-muted-foreground italic\">{t(\"comments.deleted\")}</p>\n ) : (\n <div className=\"break-words whitespace-pre-wrap\">\n {thread.renderBody ? thread.renderBody(c.body, c) : body}\n {long && (\n <Button variant=\"link\" size=\"xs\" className=\"ms-1 h-auto px-0\" onClick={() => setExpanded(!expanded)}>\n {expanded ? t(\"comments.showLess\") : t(\"comments.showMore\")}\n </Button>\n )}\n </div>\n )}\n {c.moderation === \"held\" && <p className=\"text-xs text-muted-foreground\">{t(\"comments.held\")}</p>}\n {c.moderation === \"rejected\" && (\n <p className=\"text-xs text-destructive\">{c.moderation_reason ? t(\"comments.rejected\", { reason: c.moderation_reason }) : t(\"comments.rejectedNoReason\")}</p>\n )}\n {!c.deleted && !c.moderation && !editing && (\n <div className=\"-ms-2 flex flex-wrap items-center gap-1\">\n <Button variant={c.mine === 1 ? \"secondary\" : \"ghost\"} size=\"xs\" aria-pressed={c.mine === 1} aria-label={t(\"comments.like\")} disabled={!thread.can(\"react\")} onClick={() => react(1)}>\n <HugeiconsIcon icon={ThumbsUpIcon} strokeWidth={2} />\n <span>{c.likes}</span>\n </Button>\n <Button variant={c.mine === -1 ? \"secondary\" : \"ghost\"} size=\"xs\" aria-pressed={c.mine === -1} aria-label={t(\"comments.dislike\")} disabled={!thread.can(\"react\")} onClick={() => react(-1)}>\n <HugeiconsIcon icon={ThumbsDownIcon} strokeWidth={2} />\n <span>{c.dislikes}</span>\n </Button>\n {onReply && (\n <Button variant=\"ghost\" size=\"xs\" onClick={() => onReply(name)}>\n {t(\"comments.reply\")}\n </Button>\n )}\n </div>\n )}\n <ErrorLine error={error} />\n </div>\n <ConfirmDialog\n open={deleting}\n onOpenChange={setDeleting}\n title={t(\"comments.deleteTitle\")}\n description={t(\"comments.deleteBody\")}\n confirm={t(\"comments.delete\")}\n onConfirm={() => void act(\"comment.delete\", () => thread.comments.remove(c.id))}\n />\n </article>\n );\n}\n\ninterface ComposerProps {\n initial?: string;\n placeholder?: string;\n submit: string;\n /** A signed-out visitor gives a name. */\n askName?: boolean;\n autoFocus?: boolean;\n onCancel?: () => void;\n operation: \"comment.post\" | \"comment.edit\";\n /** Resolves true to clear the field. */\n onSubmit: (body: string, name?: string) => Promise<boolean>;\n}\n\nfunction Composer(p: ComposerProps) {\n const m = useMessages();\n const { t } = m;\n const thread = useThread();\n const ids = useId();\n const [body, setBody] = useState(p.initial ?? \"\");\n const [name, setName] = useState(\"\");\n const [pending, setPending] = useState(false);\n const [invalid, setInvalid] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const area = useRef<HTMLTextAreaElement>(null);\n const max = thread.maxLength;\n const left = max === undefined ? undefined : max - body.length;\n\n const send = async () => {\n const text = body.trim();\n if (!text) return setInvalid(t(\"comments.required\"));\n if (p.askName && !name.trim()) return setInvalid(t(\"comments.nameRequired\"));\n setInvalid(null);\n setError(null);\n setPending(true);\n try {\n if (await p.onSubmit(text, p.askName ? name.trim() : undefined)) setBody(\"\");\n } catch (e) {\n const err = toContentKitError(e);\n thread.report(err, p.operation);\n if (err.code === \"comment_banned\" && err.ban) thread.onBanned(err.ban);\n else if (err.code === \"comments_locked\") thread.onLocked();\n else if (err.code === \"rate_limited\") setError(err.retryAfter ? t(\"comments.rateLimited\", { seconds: err.retryAfter }) : t(\"comments.rateLimitedSoon\"));\n else setError(contentError(m, err));\n } finally {\n setPending(false);\n }\n };\n const onKey = (e: KeyboardEvent<HTMLTextAreaElement>) => {\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n void send();\n } else if (e.key === \"Escape\" && p.onCancel) p.onCancel();\n };\n return (\n <form\n className=\"grid gap-2\"\n data-ckui=\"comment-composer\"\n onSubmit={(e) => {\n e.preventDefault();\n void send();\n }}\n >\n {p.askName && (\n <div className=\"grid gap-1.5 sm:max-w-xs\">\n <Label htmlFor={`${ids}-name`}>{t(\"comments.name\")}</Label>\n <Input id={`${ids}-name`} value={name} maxLength={60} placeholder={t(\"comments.namePlaceholder\")} onChange={(e) => setName(e.target.value)} autoComplete=\"nickname\" />\n </div>\n )}\n <Textarea\n ref={area}\n value={body}\n rows={p.initial === undefined ? 2 : 3}\n maxLength={max}\n autoFocus={p.autoFocus}\n placeholder={p.placeholder}\n aria-label={p.placeholder ?? p.submit}\n aria-invalid={!!invalid}\n aria-describedby={`${ids}-hint`}\n disabled={pending}\n onChange={(e) => {\n setBody(e.target.value);\n if (invalid) setInvalid(null);\n }}\n onKeyDown={onKey}\n />\n <div className=\"flex flex-wrap items-center gap-2\">\n <span id={`${ids}-hint`} className=\"me-auto text-xs text-muted-foreground\" aria-live=\"polite\">\n {invalid ? (\n <span className=\"text-destructive\">{invalid}</span>\n ) : left !== undefined && max !== undefined && left <= max * 0.2 ? (\n m.plural(\"comments.charsLeft\", left)\n ) : (\n <span className=\"pointer-coarse:hidden\">{t(\"comments.submitHint\", { key: mac ? \"⌘\" : \"Ctrl\" })}</span>\n )}\n </span>\n {p.onCancel && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" onClick={p.onCancel} disabled={pending}>\n {t(\"common.cancel\")}\n </Button>\n )}\n <Button type=\"submit\" size=\"sm\" disabled={pending}>\n {pending ? t(\"comments.posting\") : p.submit}\n </Button>\n </div>\n {error && <ErrorLine>{error}</ErrorLine>}\n </form>\n );\n}\n"],"mappings":"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"}
@@ -21,8 +21,9 @@ export interface ReactionButtonsProps {
21
21
  * Like and dislike toggles with counts: they change at once and roll back if
22
22
  * the server refuses. Signed out, they react anonymously where the server
23
23
  * allows it (`Config.anonymous.reactions`) and ask to sign in elsewhere.
24
+ * Nothing renders where the item's kind's locked view hides reactions.
24
25
  */
25
- export declare function ReactionButtons({ item, counts: initial, dislike, onSignIn, size, client, onError, className, appearance }: ReactionButtonsProps): import('react').JSX.Element;
26
+ export declare function ReactionButtons({ item, counts: initial, dislike, onSignIn, size, client, onError, className, appearance }: ReactionButtonsProps): import('react').JSX.Element | null;
26
27
  export interface FavoriteButtonProps {
27
28
  item: Target;
28
29
  /** Whether it is favorited and the item's count, as the host already has them: both given, nothing is read. */
@@ -27,7 +27,9 @@ function b({ item: c, counts: h, dislike: b = !0, onSignIn: x, size: S = "defaul
27
27
  if (n.code === "unauthorized" && M) return M();
28
28
  I(n), P(n, "reaction.save");
29
29
  });
30
- }, R = S === "sm" ? "sm" : "default", z = N ? O("reactions.signIn") : void 0, B = N && !M;
30
+ };
31
+ if (k.counts.likes === void 0 && !k.loading) return null;
32
+ let R = S === "sm" ? "sm" : "default", z = N ? O("reactions.signIn") : void 0, B = N && !M;
31
33
  return /* @__PURE__ */ p(n, {
32
34
  appearance: E,
33
35
  className: m("inline-flex flex-wrap items-center gap-1 text-sm", T),
@@ -45,7 +47,7 @@ function b({ item: c, counts: h, dislike: b = !0, onSignIn: x, size: S = "defaul
45
47
  children: [/* @__PURE__ */ f(v, {
46
48
  icon: _,
47
49
  strokeWidth: 2
48
- }), /* @__PURE__ */ f("span", { children: y(k.counts.likes, D.language) })]
50
+ }), k.counts.likes !== void 0 && /* @__PURE__ */ f("span", { children: y(k.counts.likes, D.language) })]
49
51
  }), b && /* @__PURE__ */ p(a, {
50
52
  variant: k.counts.mine === -1 ? "secondary" : "ghost",
51
53
  size: R,
@@ -57,7 +59,7 @@ function b({ item: c, counts: h, dislike: b = !0, onSignIn: x, size: S = "defaul
57
59
  children: [/* @__PURE__ */ f(v, {
58
60
  icon: g,
59
61
  strokeWidth: 2
60
- }), /* @__PURE__ */ f("span", { children: y(k.counts.dislikes, D.language) })]
62
+ }), k.counts.dislikes !== void 0 && /* @__PURE__ */ f("span", { children: y(k.counts.dislikes, D.language) })]
61
63
  })]
62
64
  }), F && /* @__PURE__ */ f("span", {
63
65
  role: "alert",