@openrails/contentkit-ui 0.98.6 → 0.99.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.
- package/README.md +26 -0
- package/dist/client/content/comments.d.ts +4 -2
- package/dist/client/content/comments.js +10 -6
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/generated/routes.d.ts +28 -2
- package/dist/client/generated/routes.js +39 -2
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +85 -0
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/media/client.d.ts +4 -0
- package/dist/client/media/client.js +8 -4
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/queue.js +42 -26
- package/dist/client/media/queue.js.map +1 -1
- package/dist/client/media/rules.d.ts +6 -0
- package/dist/client/media/rules.js +64 -10
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/versions.d.ts +6 -2
- package/dist/client/versions.js +26 -0
- package/dist/client/versions.js.map +1 -1
- package/dist/components/admin/page-review.d.ts +13 -0
- package/dist/components/admin/page-review.js +216 -0
- package/dist/components/admin/page-review.js.map +1 -0
- package/dist/components/content/card.js +98 -103
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/daily-releases.d.ts +8 -1
- package/dist/components/content/daily-releases.js +291 -133
- package/dist/components/content/daily-releases.js.map +1 -1
- package/dist/components/content/editors.js +216 -210
- package/dist/components/content/editors.js.map +1 -1
- package/dist/components/content/filter-dialog.d.ts +11 -0
- package/dist/components/content/filter-dialog.js +29 -0
- package/dist/components/content/filter-dialog.js.map +1 -0
- package/dist/components/content/filters.d.ts +10 -0
- package/dist/components/content/filters.js +5 -2
- package/dist/components/content/filters.js.map +1 -1
- package/dist/components/content/grid.js +19 -14
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/index.d.ts +2 -1
- package/dist/components/content/release-reveal.d.ts +5 -0
- package/dist/components/content/release-reveal.js +62 -0
- package/dist/components/content/release-reveal.js.map +1 -0
- package/dist/components/social/comments.d.ts +1 -1
- package/dist/components/social/comments.js +15 -15
- package/dist/components/social/comments.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +24 -22
- package/dist/locales/admin/de.js +23 -0
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +23 -0
- package/dist/locales/admin/en.js +23 -0
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +23 -0
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +23 -0
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +23 -0
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +23 -0
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/discovery/de.js +11 -0
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +11 -0
- package/dist/locales/discovery/en.js +11 -0
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +11 -0
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +11 -0
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +11 -0
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +11 -0
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +34 -0
- package/dist/locales/social/en.js +2 -2
- package/dist/locales/social/en.js.map +1 -1
- package/dist/react/comments.d.ts +2 -1
- package/dist/react/comments.js +10 -8
- package/dist/react/comments.js.map +1 -1
- package/dist/react/folder.js +39 -39
- package/dist/react/folder.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/page-review.d.ts +14 -0
- package/dist/react/page-review.js +36 -0
- package/dist/react/page-review.js.map +1 -0
- package/dist/react/public.d.ts +3 -1
- package/dist/react/public.js +76 -72
- package/dist/react/public.js.map +1 -1
- package/dist/react/resources.d.ts +5 -13
- package/dist/react/resources.js +58 -49
- package/dist/react/resources.js.map +1 -1
- package/dist/react/use-resource.d.ts +1 -1
- package/dist/react/use-resource.js +23 -13
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react/versions.js +70 -42
- package/dist/react/versions.js.map +1 -1
- package/dist/react.js +8 -7
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid.js","names":[],"sources":["../../../src/components/content/grid.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useContext, useEffect, useMemo, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentQuery, HistoryQuery, RailQuery, SearchQuery } from \"../../client/discovery.js\";\nimport type { Card } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useContents, useExposure, useHistory, useLockedCount, useRecommendations, useSearch, useSimilar, type UseRail } from \"../../react/discovery.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport type { UseList } from \"../../react/use-resource.js\";\nimport { useNearViewport } from \"../../react/viewport.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { contentError, useAction } from \"../social/parts.js\";\nimport { ContentCard, type ContentCardProps } from \"./card.js\";\n\nexport interface CardListProps extends Omit<ContentCardProps, \"card\" | \"onOpen\" | \"children\" | \"className\"> {\n /** Where the list is, for exposures and click attribution: \"home\", \"search\", \"similar\". */\n surface?: string;\n /** Loads the next page as the end nears, instead of a button. */\n infinite?: boolean;\n /** A card's entry instead of ContentCard. */\n renderCard?: (card: Card, position: number) => ReactNode;\n /** Shown when nothing matches. */\n empty?: ReactNode;\n pageSize?: number;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n /** Opt into a fluid grid, optionally with a desktop column ceiling. */\n grid?: CardGridLayout;\n}\n\nconst GRID = \"grid grid-cols-[repeat(auto-fill,minmax(min(100%,10rem),1fr))] gap-x-4 gap-y-6\";\n\nexport interface CardGridLayout {\n /** Minimum desktop card width: CSS pixels or a CSS length such as clamp(...). Cards fill the available space. */\n minCardWidth: number | string;\n /** Maximum desktop columns. Omit to use all available width without a ceiling. */\n maxColumns?: number;\n /** Equal-width columns below the desktop breakpoint. Default 2. */\n mobileColumns?: number;\n}\n\n/** Shared by complete lists, release-day sections and grid-style previews. */\nexport function cardGridLayout(layout?: CardGridLayout): { className: string; style?: CSSProperties } {\n if (!layout) return { className: GRID };\n const width = typeof layout.minCardWidth === \"string\" ? layout.minCardWidth.trim() || \"160px\" : `${Number.isFinite(layout.minCardWidth) ? Math.max(1, layout.minCardWidth) : 160}px`;\n const columns = layout.mobileColumns !== undefined && Number.isFinite(layout.mobileColumns) ? Math.max(1, Math.floor(layout.mobileColumns)) : 2;\n const maximum = layout.maxColumns !== undefined && Number.isFinite(layout.maxColumns) ? Math.max(1, Math.floor(layout.maxColumns)) : undefined;\n const trackWidth = maximum === undefined ? width : `max(${width}, calc((100% - ${maximum - 1}rem) / ${maximum}))`;\n return {\n className: \"grid grid-cols-[repeat(var(--ckui-card-mobile-columns),minmax(0,1fr))] gap-x-4 gap-y-6 md:grid-cols-[repeat(auto-fill,minmax(min(100%,var(--ckui-card-min-width)),1fr))]\",\n style: { \"--ckui-card-min-width\": trackWidth, \"--ckui-card-mobile-columns\": columns } as CSSProperties,\n };\n}\n\n/** A list's cards in a responsive grid, paged, with exposures and click signals. */\nexport function CardGrid({ list, render, ...p }: CardListProps & { list: UseList<Card>; render: string }) {\n const m = useMessages();\n const { t } = m;\n const { kinds } = useKinds({ client: p.client });\n const exposure = useExposure(list.items, { surface: p.surface ?? \"list\", render, client: p.client });\n const [sentinel, near] = useNearViewport({ margin: \"600px 0px\", leaveMargin: \"1200px 0px\" });\n const { hasMore, loadingMore, loadMore } = list;\n useEffect(() => {\n if (p.infinite && near && hasMore && !loadingMore) void loadMore();\n }, [p.infinite, near, hasMore, loadingMore, loadMore]);\n const tiers = p.tiers ?? kinds?.tiers;\n const layout = cardGridLayout(p.grid);\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"content-grid\" aria-busy={list.loading}>\n {list.loading && !list.items.length ? (\n <div className={layout.className} style={layout.style}>\n {Array.from({ length: 8 }, (_, i) => (\n <Skeleton key={i} className={p.aspectRatio === undefined ? p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\" : \"w-full\"} style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }} />\n ))}\n </div>\n ) : list.error && !list.items.length ? (\n <div role=\"alert\" className=\"grid justify-items-start gap-2\">\n <p className=\"text-destructive\">{contentError(m, list.error)}</p>\n <Button size=\"sm\" variant=\"outline\" onClick={list.reload}>\n {t(\"common.retry\")}\n </Button>\n </div>\n ) : !list.items.length ? (\n <div className=\"text-muted-foreground\" role=\"status\">\n {p.empty ?? t(\"contents.empty\")}\n </div>\n ) : (\n <ol className={layout.className} style={layout.style}>\n {list.items.map((card, i) => (\n <li key={card.id} ref={exposure.track(card.id, i + 1)}>\n {p.renderCard ? (\n p.renderCard(card, i + 1)\n ) : (\n <ContentCard\n card={card}\n client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\n metadata={p.metadata}\n showCounts={p.showCounts}\n tiers={tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n onOpen={() => exposure.click(card.id, i + 1)}\n />\n )}\n </li>\n ))}\n </ol>\n )}\n {list.hasMore && (\n <div ref={sentinel} className=\"grid justify-items-center\">\n <Button variant=\"outline\" size=\"sm\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {list.loadingMore ? t(\"contents.loading\") : t(\"contents.loadMore\")}\n </Button>\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n\n/**\n * What a list does with cards the reader doesn't open: shows them marked\n * (show), leaves them out (hide), or lists them apart, counted, behind a\n * toggle that loads them when opened (collapse).\n */\nexport type LockedCards = \"show\" | \"hide\" | \"collapse\";\n\nconst LOCKED_LEVELS = [\"tier\", \"group\", \"ppv\", \"group_ppv\"] as const;\n\n/** Contents as cards (GET /v1/contents): any sort and filter, paged. */\nexport function ContentGrid({ query, locked = \"show\", ...p }: CardListProps & { query: ContentQuery; locked?: LockedCards }) {\n const q = useMemo<ContentQuery>(\n () => ({ ...query, limit: query.limit ?? p.pageSize, ...(locked === \"show\" ? {} : { mode: \"hide\" }) }),\n [query, p.pageSize, locked],\n );\n const list = useContents(q, { client: p.client });\n return (\n <>\n {locked === \"collapse\" && <LockedSection {...p} query={query} />}\n <CardGrid {...p} list={list} render={JSON.stringify(q)} />\n </>\n );\n}\n\n/** A list's locked cards, counted, loaded and shown on opening. */\nfunction LockedSection({ query, ...p }: CardListProps & { query: ContentQuery }) {\n const m = useMessages();\n const [open, setOpen] = useState(false);\n const { count } = useLockedCount(query, { client: p.client });\n const q = useMemo<ContentQuery | null>(\n () => (open ? { ...query, mode: \"lock\", access: query.access ?? LOCKED_LEVELS, limit: query.limit ?? p.pageSize } : null),\n [open, query, p.pageSize],\n );\n const list = useContents(q, { client: p.client });\n const items = useMemo(() => list.items.filter((c) => !c.access.allowed), [list.items]);\n if (!count) return null;\n return (\n <ContentKitUiRoot appearance={p.appearance} className=\"grid gap-3 text-sm\" data-ckui=\"locked-cards\">\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-start\" aria-expanded={open} onClick={() => setOpen(!open)}>\n {open ? m.t(\"contents.lockedHide\") : m.plural(\"contents.lockedShow\", count)}\n </Button>\n {open && <CardGrid {...p} list={{ ...list, items }} render={JSON.stringify(q)} />}\n </ContentKitUiRoot>\n );\n}\n\n/** Search results as cards; nothing until the query has text. */\nexport function SearchResults({ query, ...p }: CardListProps & { query: SearchQuery }) {\n const { t } = useMessages();\n const q = useMemo(() => ({ ...query, limit: query.limit ?? p.pageSize }), [query, p.pageSize]);\n const list = useSearch(q, { client: p.client });\n if (!query.q.trim()) return null;\n return <CardGrid surface=\"search\" empty={t(\"contents.noResults\", { q: query.q.trim() })} {...p} list={list} render={JSON.stringify(q)} />;\n}\n\n/** The signed-in reader's history as cards, each removable, with a clear-all. */\nexport function ContentHistory({ query = {}, ...p }: CardListProps & { query?: HistoryQuery }) {\n const { t } = useMessages();\n const ctx = useContext(ContentKitContext);\n const q = useMemo(() => ({ ...query, limit: query.limit ?? p.pageSize }), [query, p.pageSize]);\n const list = useHistory(q, { client: p.client });\n const forget = useAction(\"history.forget\");\n if (ctx?.viewer === null) return <p className=\"text-muted-foreground\">{t(\"contents.historySignedOut\")}</p>;\n return (\n <div className=\"grid gap-3\" data-ckui=\"content-history\">\n {list.items.length > 0 && (\n <Button size=\"sm\" variant=\"outline\" className=\"justify-self-end\" disabled={forget.pending} onClick={() => void forget.run(() => list.forget(query.kind ? { kind: query.kind } : {}))}>\n {t(\"contents.clearHistory\")}\n </Button>\n )}\n <CardGrid\n surface=\"history\"\n empty={t(\"contents.historyEmpty\")}\n {...p}\n list={list}\n render={JSON.stringify(q)}\n renderCard={\n p.renderCard ??\n ((card) => (\n <ContentCard\n card={card}\n client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\n metadata={p.metadata}\n showCounts={p.showCounts}\n tiers={p.tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n >\n <Button size=\"xs\" variant=\"ghost\" className=\"justify-self-start\" disabled={forget.pending} onClick={() => void forget.run(() => list.forget({ content_id: card.id }))}>\n {t(\"contents.removeFromHistory\")}\n </Button>\n </ContentCard>\n ))\n }\n />\n </div>\n );\n}\n\nexport interface ContentRailProps extends Omit<CardListProps, \"infinite\" | \"empty\"> {\n /** The rail's heading. */\n title: ReactNode;\n /** Contents like this one. */\n similar?: string;\n /** For the signed-in reader. */\n recommendations?: boolean;\n /** The reader's history: { status: \"in_progress\" } is a continue-watching rail. */\n history?: HistoryQuery;\n /** An ordered group's members in its order: a featured rail (its windows apply). */\n group?: string;\n /** Any list: a sort and filters. */\n list?: ContentQuery;\n /** Filters and reader options of a similar or recommended rail. */\n query?: RailQuery;\n /** A link beside the heading, e.g. to the whole list. */\n more?: ReactNode;\n}\n\n/**\n * A horizontal row of contents: similar, recommended, the reader's history,\n * an ordered group's members or any list. It hides itself when it has none or\n * the site runs without what it needs.\n */\nexport function ContentRail({ title, similar, recommendations, history, group, list: listQuery, query = {}, more, ...p }: ContentRailProps) {\n const limit = query.limit ?? p.pageSize ?? 12;\n const q = useMemo(() => ({ ...query, limit }), [query, limit]);\n const listing = useMemo(\n () => (listQuery ? { ...listQuery, limit: listQuery.limit ?? limit } : group ? { groups: [group], sort: \"position\" as const, limit } : null),\n [listQuery, group, limit],\n );\n const like = useSimilar(recommendations || history || listing ? null : similar, q, { client: p.client });\n const mine = useRecommendations(recommendations ? q : null, { client: p.client });\n const seen = useHistory(history ? { ...history, limit: history.limit ?? limit } : null, { client: p.client });\n const listed = useContents(listing, { client: p.client });\n const rail: UseRail = recommendations\n ? mine\n : history || listing\n ? (() => {\n const l = history ? seen : listed;\n return { items: l.items, loading: l.loading, error: l.error, unavailable: l.error?.code === \"not_configured\", reload: l.reload };\n })()\n : like;\n const { kinds } = useKinds({ client: p.client });\n const surface = p.surface ?? (recommendations ? \"recommendations\" : history ? \"history\" : group ? \"featured\" : listing ? \"list\" : \"similar\");\n const exposure = useExposure(rail.items, { surface, render: JSON.stringify([similar, recommendations, history, group, listing, q]), client: p.client });\n const source = similar || recommendations || history || group || listing;\n const layout = p.grid ? cardGridLayout(p.grid) : undefined;\n if (!source || rail.unavailable || (!rail.loading && !rail.items.length)) return null;\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-3 text-sm\", p.className)} data-ckui=\"content-rail\" data-surface={surface}>\n <div className=\"flex items-baseline justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{title}</h2>\n {more}\n </div>\n <ol className={layout?.className ?? \"flex snap-x gap-4 overflow-x-auto pb-2\"} style={layout?.style}>\n {(rail.loading && !rail.items.length ? Array.from({ length: 6 }, () => null) : rail.items).map((card, i) => (\n <li key={card?.id ?? i} className={layout ? undefined : \"w-36 shrink-0 snap-start sm:w-44\"} ref={card ? exposure.track(card.id, i + 1) : undefined}>\n {!card ? (\n <Skeleton className={p.aspectRatio === undefined ? p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\" : \"w-full\"} style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }} />\n ) : p.renderCard ? (\n p.renderCard(card, i + 1)\n ) : (\n <ContentCard\n card={card}\n client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\n metadata={p.metadata}\n showCounts={p.showCounts}\n tiers={p.tiers ?? kinds?.tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n onOpen={() => exposure.click(card.id, i + 1)}\n />\n )}\n </li>\n ))}\n </ol>\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"grid.js","names":[],"sources":["../../../src/components/content/grid.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useContext, useEffect, useMemo, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentQuery, HistoryQuery, RailQuery, SearchQuery } from \"../../client/discovery.js\";\nimport type { Card } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useContents, useExposure, useHistory, useLockedCount, useRecommendations, useSearch, useSimilar, type UseRail } from \"../../react/discovery.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport type { UseList } from \"../../react/use-resource.js\";\nimport { useNearViewport } from \"../../react/viewport.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { contentError, useAction } from \"../social/parts.js\";\nimport { ContentCard, type ContentCardProps } from \"./card.js\";\n\nexport interface CardListProps extends Omit<ContentCardProps, \"card\" | \"onOpen\" | \"children\" | \"className\"> {\n /** Where the list is, for exposures and click attribution: \"home\", \"search\", \"similar\". */\n surface?: string;\n /** Loads the next page as the end nears, instead of a button. */\n infinite?: boolean;\n /** A card's entry instead of ContentCard. */\n renderCard?: (card: Card, position: number) => ReactNode;\n /** Shown when nothing matches. */\n empty?: ReactNode;\n pageSize?: number;\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n /** Opt into a fluid grid, optionally with a desktop column ceiling. */\n grid?: CardGridLayout;\n}\n\nconst GRID = \"grid grid-cols-[repeat(auto-fill,minmax(min(100%,10rem),1fr))] gap-x-4 gap-y-6\";\n\nexport interface CardGridLayout {\n /** Minimum desktop card width: CSS pixels or a CSS length such as clamp(...). Cards fill the available space. */\n minCardWidth: number | string;\n /** Maximum desktop columns. Omit to use all available width without a ceiling. */\n maxColumns?: number;\n /** Equal-width columns below the desktop breakpoint. Default 2. */\n mobileColumns?: number;\n}\n\n/** Shared by complete lists, release-day sections and grid-style previews. */\nexport function cardGridLayout(layout?: CardGridLayout): { className: string; style?: CSSProperties } {\n if (!layout) return { className: GRID };\n const width = typeof layout.minCardWidth === \"string\" ? layout.minCardWidth.trim() || \"160px\" : `${Number.isFinite(layout.minCardWidth) ? Math.max(1, layout.minCardWidth) : 160}px`;\n const columns = layout.mobileColumns !== undefined && Number.isFinite(layout.mobileColumns) ? Math.max(1, Math.floor(layout.mobileColumns)) : 2;\n const maximum = layout.maxColumns !== undefined && Number.isFinite(layout.maxColumns) ? Math.max(1, Math.floor(layout.maxColumns)) : undefined;\n const trackWidth = maximum === undefined ? width : `max(${width}, calc((100% - ${maximum - 1}rem) / ${maximum}))`;\n return {\n className: \"grid grid-cols-[repeat(var(--ckui-card-mobile-columns),minmax(0,1fr))] gap-x-4 gap-y-6 md:grid-cols-[repeat(auto-fill,minmax(min(100%,var(--ckui-card-min-width)),1fr))]\",\n style: { \"--ckui-card-min-width\": trackWidth, \"--ckui-card-mobile-columns\": columns } as CSSProperties,\n };\n}\n\n/** A list's cards in a responsive grid, paged, with exposures and click signals. */\nexport function CardGrid({ list, render, ...p }: CardListProps & { list: UseList<Card>; render: string }) {\n const m = useMessages();\n const { t } = m;\n const { kinds } = useKinds({ client: p.client });\n const exposure = useExposure(list.items, { surface: p.surface ?? \"list\", render, client: p.client });\n const [sentinel, near] = useNearViewport({ margin: \"600px 0px\", leaveMargin: \"1200px 0px\" });\n const { hasMore, loadingMore, loadMore, error } = list;\n useEffect(() => {\n if (p.infinite && near && hasMore && !loadingMore && !error) void loadMore();\n }, [p.infinite, near, hasMore, loadingMore, loadMore, error]);\n const tiers = p.tiers ?? kinds?.tiers;\n const layout = cardGridLayout(p.grid);\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-4 text-sm\", p.className)} data-ckui=\"content-grid\" aria-busy={list.loading}>\n {list.loading && !list.items.length ? (\n <div className={layout.className} style={layout.style}>\n {Array.from({ length: 8 }, (_, i) => (\n <Skeleton key={i} className={p.aspectRatio === undefined ? p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\" : \"w-full\"} style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }} />\n ))}\n </div>\n ) : list.error && !list.items.length ? (\n <div role=\"alert\" className=\"grid justify-items-start gap-2\">\n <p className=\"text-destructive\">{contentError(m, list.error)}</p>\n <Button size=\"sm\" variant=\"outline\" onClick={list.reload}>\n {t(\"common.retry\")}\n </Button>\n </div>\n ) : !list.items.length ? (\n <div className=\"text-muted-foreground\" role=\"status\">\n {p.empty ?? t(\"contents.empty\")}\n </div>\n ) : (\n <ol className={layout.className} style={layout.style}>\n {list.items.map((card, i) => (\n <li key={card.id} ref={exposure.track(card.id, i + 1)}>\n {p.renderCard ? (\n p.renderCard(card, i + 1)\n ) : (\n <ContentCard\n card={card}\n client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\n metadata={p.metadata}\n showCounts={p.showCounts}\n tiers={tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n onOpen={() => exposure.click(card.id, i + 1)}\n />\n )}\n </li>\n ))}\n </ol>\n )}\n {list.hasMore && (\n <div ref={sentinel} className=\"grid justify-items-center gap-2\">\n {list.error && list.items.length > 0 && <p role=\"alert\" className=\"text-destructive\">{contentError(m, list.error)}</p>}\n <Button variant=\"outline\" size=\"sm\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {list.loadingMore ? t(\"contents.loading\") : list.error ? t(\"common.retry\") : t(\"contents.loadMore\")}\n </Button>\n </div>\n )}\n </ContentKitUiRoot>\n );\n}\n\n/**\n * What a list does with cards the reader doesn't open: shows them marked\n * (show), leaves them out (hide), or lists them apart, counted, behind a\n * toggle that loads them when opened (collapse).\n */\nexport type LockedCards = \"show\" | \"hide\" | \"collapse\";\n\nconst LOCKED_LEVELS = [\"tier\", \"group\", \"ppv\", \"group_ppv\"] as const;\n\n/** Contents as cards (GET /v1/contents): any sort and filter, paged. */\nexport function ContentGrid({ query, locked = \"show\", ...p }: CardListProps & { query: ContentQuery; locked?: LockedCards }) {\n const q = useMemo<ContentQuery>(\n () => ({ ...query, limit: query.limit ?? p.pageSize, ...(locked === \"show\" ? {} : { mode: \"hide\" }) }),\n [query, p.pageSize, locked],\n );\n const list = useContents(q, { client: p.client });\n return (\n <>\n {locked === \"collapse\" && <LockedSection {...p} query={query} />}\n <CardGrid {...p} list={list} render={JSON.stringify(q)} />\n </>\n );\n}\n\n/** A list's locked cards, counted, loaded and shown on opening. */\nfunction LockedSection({ query, ...p }: CardListProps & { query: ContentQuery }) {\n const m = useMessages();\n const [open, setOpen] = useState(false);\n const { count } = useLockedCount(query, { client: p.client });\n const q = useMemo<ContentQuery | null>(\n () => (open ? { ...query, mode: \"lock\", access: query.access ?? LOCKED_LEVELS, limit: query.limit ?? p.pageSize } : null),\n [open, query, p.pageSize],\n );\n const list = useContents(q, { client: p.client });\n const items = useMemo(() => list.items.filter((c) => !c.access.allowed), [list.items]);\n if (!count) return null;\n return (\n <ContentKitUiRoot appearance={p.appearance} className=\"grid gap-3 text-sm\" data-ckui=\"locked-cards\">\n <Button variant=\"outline\" size=\"sm\" className=\"justify-self-start\" aria-expanded={open} onClick={() => setOpen(!open)}>\n {open ? m.t(\"contents.lockedHide\") : m.plural(\"contents.lockedShow\", count)}\n </Button>\n {open && <CardGrid {...p} list={{ ...list, items }} render={JSON.stringify(q)} />}\n </ContentKitUiRoot>\n );\n}\n\n/** Search results as cards; nothing until the query has text. */\nexport function SearchResults({ query, ...p }: CardListProps & { query: SearchQuery }) {\n const { t } = useMessages();\n const q = useMemo(() => ({ ...query, limit: query.limit ?? p.pageSize }), [query, p.pageSize]);\n const list = useSearch(q, { client: p.client });\n if (!query.q.trim()) return null;\n return <CardGrid surface=\"search\" empty={t(\"contents.noResults\", { q: query.q.trim() })} {...p} list={list} render={JSON.stringify(q)} />;\n}\n\n/** The signed-in reader's history as cards, each removable, with a clear-all. */\nexport function ContentHistory({ query = {}, ...p }: CardListProps & { query?: HistoryQuery }) {\n const { t } = useMessages();\n const ctx = useContext(ContentKitContext);\n const q = useMemo(() => ({ ...query, limit: query.limit ?? p.pageSize }), [query, p.pageSize]);\n const list = useHistory(q, { client: p.client });\n const forget = useAction(\"history.forget\");\n if (ctx?.viewer === null) return <p className=\"text-muted-foreground\">{t(\"contents.historySignedOut\")}</p>;\n return (\n <div className=\"grid gap-3\" data-ckui=\"content-history\">\n {list.items.length > 0 && (\n <Button size=\"sm\" variant=\"outline\" className=\"justify-self-end\" disabled={forget.pending} onClick={() => void forget.run(() => list.forget(query.kind ? { kind: query.kind } : {}))}>\n {t(\"contents.clearHistory\")}\n </Button>\n )}\n <CardGrid\n surface=\"history\"\n empty={t(\"contents.historyEmpty\")}\n {...p}\n list={list}\n render={JSON.stringify(q)}\n renderCard={\n p.renderCard ??\n ((card) => (\n <ContentCard\n card={card}\n client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\n metadata={p.metadata}\n showCounts={p.showCounts}\n tiers={p.tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n >\n <Button size=\"xs\" variant=\"ghost\" className=\"justify-self-start\" disabled={forget.pending} onClick={() => void forget.run(() => list.forget({ content_id: card.id }))}>\n {t(\"contents.removeFromHistory\")}\n </Button>\n </ContentCard>\n ))\n }\n />\n </div>\n );\n}\n\nexport interface ContentRailProps extends Omit<CardListProps, \"infinite\" | \"empty\"> {\n /** The rail's heading. */\n title: ReactNode;\n /** Contents like this one. */\n similar?: string;\n /** For the signed-in reader. */\n recommendations?: boolean;\n /** The reader's history: { status: \"in_progress\" } is a continue-watching rail. */\n history?: HistoryQuery;\n /** An ordered group's members in its order: a featured rail (its windows apply). */\n group?: string;\n /** Any list: a sort and filters. */\n list?: ContentQuery;\n /** Filters and reader options of a similar or recommended rail. */\n query?: RailQuery;\n /** A link beside the heading, e.g. to the whole list. */\n more?: ReactNode;\n}\n\n/**\n * A horizontal row of contents: similar, recommended, the reader's history,\n * an ordered group's members or any list. It hides itself when it has none or\n * the site runs without what it needs.\n */\nexport function ContentRail({ title, similar, recommendations, history, group, list: listQuery, query = {}, more, ...p }: ContentRailProps) {\n const limit = query.limit ?? p.pageSize ?? 12;\n const q = useMemo(() => ({ ...query, limit }), [query, limit]);\n const listing = useMemo(\n () => (listQuery ? { ...listQuery, limit: listQuery.limit ?? limit } : group ? { groups: [group], sort: \"position\" as const, limit } : null),\n [listQuery, group, limit],\n );\n const like = useSimilar(recommendations || history || listing ? null : similar, q, { client: p.client });\n const mine = useRecommendations(recommendations ? q : null, { client: p.client });\n const seen = useHistory(history ? { ...history, limit: history.limit ?? limit } : null, { client: p.client });\n const listed = useContents(listing, { client: p.client });\n const rail: UseRail = recommendations\n ? mine\n : history || listing\n ? (() => {\n const l = history ? seen : listed;\n return { items: l.items, loading: l.loading, error: l.error, unavailable: l.error?.code === \"not_configured\", reload: l.reload };\n })()\n : like;\n const { kinds } = useKinds({ client: p.client });\n const surface = p.surface ?? (recommendations ? \"recommendations\" : history ? \"history\" : group ? \"featured\" : listing ? \"list\" : \"similar\");\n const exposure = useExposure(rail.items, { surface, render: JSON.stringify([similar, recommendations, history, group, listing, q]), client: p.client });\n const source = similar || recommendations || history || group || listing;\n const layout = p.grid ? cardGridLayout(p.grid) : undefined;\n if (!source || rail.unavailable || (!rail.loading && !rail.items.length)) return null;\n return (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-3 text-sm\", p.className)} data-ckui=\"content-rail\" data-surface={surface}>\n <div className=\"flex items-baseline justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{title}</h2>\n {more}\n </div>\n <ol className={layout?.className ?? \"flex snap-x gap-4 overflow-x-auto pb-2\"} style={layout?.style}>\n {(rail.loading && !rail.items.length ? Array.from({ length: 6 }, () => null) : rail.items).map((card, i) => (\n <li key={card?.id ?? i} className={layout ? undefined : \"w-36 shrink-0 snap-start sm:w-44\"} ref={card ? exposure.track(card.id, i + 1) : undefined}>\n {!card ? (\n <Skeleton className={p.aspectRatio === undefined ? p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\" : \"w-full\"} style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }} />\n ) : p.renderCard ? (\n p.renderCard(card, i + 1)\n ) : (\n <ContentCard\n card={card}\n client={p.client}\n href={p.href}\n shape={p.shape}\n aspectRatio={p.aspectRatio}\n titlePosition={p.titlePosition}\n loadMedia={p.loadMedia}\n groupKinds={p.groupKinds}\n chips={p.chips}\n filterEntity={p.filterEntity}\n entityStyle={p.entityStyle}\n entityLayout={p.entityLayout}\n metadata={p.metadata}\n showCounts={p.showCounts}\n tiers={p.tiers ?? kinds?.tiers}\n labels={p.labels}\n owner={p.owner}\n ownerHref={p.ownerHref}\n decorate={p.decorate}\n onOpen={() => exposure.click(card.id, i + 1)}\n />\n )}\n </li>\n ))}\n </ol>\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAmCA,IAAM,IAAO;AAYb,SAAgB,EAAe,GAAuE;CACpG,IAAI,CAAC,GAAQ,OAAO,EAAE,WAAW,EAAK;CACtC,IAAM,IAAQ,OAAO,EAAO,gBAAiB,WAAW,EAAO,aAAa,KAAK,KAAK,UAAU,GAAG,OAAO,SAAS,EAAO,YAAY,IAAI,KAAK,IAAI,GAAG,EAAO,YAAY,IAAI,IAAI,KAC3K,IAAU,EAAO,kBAAkB,KAAA,KAAa,OAAO,SAAS,EAAO,aAAa,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,EAAO,aAAa,CAAC,IAAI,GACxI,IAAU,EAAO,eAAe,KAAA,KAAa,OAAO,SAAS,EAAO,UAAU,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,EAAO,UAAU,CAAC,IAAI,KAAA;CAErI,OAAO;EACL,WAAW;EACX,OAAO;GAAE,yBAHQ,MAAY,KAAA,IAAY,IAAQ,OAAO,EAAM,iBAAiB,IAAU,EAAE,SAAS,EAAQ;GAG9D,8BAA8B;EAAQ;CACtF;AACF;AAGA,SAAgB,EAAS,EAAE,SAAM,WAAQ,GAAG,KAA8D;CACxG,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,EAAE,aAAU,EAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,GACzC,IAAW,EAAY,EAAK,OAAO;EAAE,SAAS,EAAE,WAAW;EAAQ;EAAQ,QAAQ,EAAE;CAAO,CAAC,GAC7F,CAAC,GAAU,KAAQ,EAAgB;EAAE,QAAQ;EAAa,aAAa;CAAa,CAAC,GACrF,EAAE,YAAS,gBAAa,aAAU,aAAU;CAClD,QAAgB;EACd,AAAI,EAAE,YAAY,KAAQ,KAAW,CAAC,KAAe,CAAC,KAAO,EAAc;CAC7E,GAAG;EAAC,EAAE;EAAU;EAAM;EAAS;EAAa;EAAU;CAAK,CAAC;CAC5D,IAAM,IAAQ,EAAE,SAAS,GAAO,OAC1B,IAAS,EAAe,EAAE,IAAI;CACpC,OACE,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EAAe,aAAW,EAAK;EAAvI,UAAA,CACG,EAAK,WAAW,CAAC,EAAK,MAAM,SAC3B,kBAAC,OAAD;GAAK,WAAW,EAAO;GAAW,OAAO,EAAO;GAC7C,UAAA,MAAM,KAAK,EAAE,QAAQ,EAAE,IAAI,GAAG,MAC7B,kBAAC,GAAD;IAAkB,WAAW,EAAE,gBAAgB,KAAA,IAAY,EAAE,UAAU,cAAc,wBAAwB,wBAAwB;IAAU,OAAO,EAAE,gBAAgB,KAAA,IAAY,KAAA,IAAY,EAAE,aAAa,EAAE,YAAY;GAAI,GAAlN,CAAkN,CAClO;EACE,CAAA,IACH,EAAK,SAAS,CAAC,EAAK,MAAM,SAC5B,kBAAC,OAAD;GAAK,MAAK;GAAQ,WAAU;GAA5B,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;IAAoB,UAAA,EAAa,GAAG,EAAK,KAAK;GAAK,CAAA,GAChE,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,SAAS,EAAK;IAC/C,UAAA,EAAE,cAAc;GACX,CAAA,CACL;EACH,CAAA,IAAC,EAAK,MAAM,SAKd,kBAAC,MAAD;GAAI,WAAW,EAAO;GAAW,OAAO,EAAO;GAC5C,UAAA,EAAK,MAAM,KAAK,GAAM,MACrB,kBAAC,MAAD;IAAkB,KAAK,EAAS,MAAM,EAAK,IAAI,IAAI,CAAC;IACjD,UAAA,EAAE,aACD,EAAE,WAAW,GAAM,IAAI,CAAC,IAExB,kBAAC,GAAD;KACQ;KACN,QAAQ,EAAE;KACV,MAAM,EAAE;KACR,OAAO,EAAE;KACT,aAAa,EAAE;KACf,eAAe,EAAE;KACjB,WAAW,EAAE;KACb,YAAY,EAAE;KACd,OAAO,EAAE;KACT,cAAc,EAAE;KAChB,aAAa,EAAE;KACf,cAAc,EAAE;KAChB,UAAU,EAAE;KACZ,YAAY,EAAE;KACP;KACP,QAAQ,EAAE;KACV,OAAO,EAAE;KACT,WAAW,EAAE;KACb,UAAU,EAAE;KACZ,cAAc,EAAS,MAAM,EAAK,IAAI,IAAI,CAAC;IAC5C,CAAA;GAED,GA3BK,EAAK,EA2BV,CACL;EACC,CAAA,IAnCJ,kBAAC,OAAD;GAAK,WAAU;GAAwB,MAAK;GACzC,UAAA,EAAE,SAAS,EAAE,gBAAgB;EAC3B,CAAA,GAmCN,EAAK,WACJ,kBAAC,OAAD;GAAK,KAAK;GAAU,WAAU;GAA9B,UAAA,CACG,EAAK,SAAS,EAAK,MAAM,SAAS,KAAK,kBAAC,KAAD;IAAG,MAAK;IAAQ,WAAU;IAAoB,UAAA,EAAa,GAAG,EAAK,KAAK;GAAK,CAAA,GACrH,kBAAC,GAAD;IAAQ,SAAQ;IAAU,MAAK;IAAK,UAAU,EAAK;IAAa,eAAe,KAAK,EAAK,SAAS;IAC/F,UAAA,EAAK,cAAc,EAAE,kBAAkB,IAAI,EAAK,QAAQ,EAAE,cAAc,IAAI,EAAE,mBAAmB;GAC5F,CAAA,CACL;EAES,CAAA,CAAA;;AAEtB;AASA,IAAM,IAAgB;CAAC;CAAQ;CAAS;CAAO;AAAW;AAG1D,SAAgB,EAAY,EAAE,UAAO,YAAS,QAAQ,GAAG,KAAoE;CAC3H,IAAM,IAAI,SACD;EAAE,GAAG;EAAO,OAAO,EAAM,SAAS,EAAE;EAAU,GAAI,MAAW,SAAS,CAAC,IAAI,EAAE,MAAM,OAAO;CAAG,IACpG;EAAC;EAAO,EAAE;EAAU;CAAM,CAC5B,GACM,IAAO,EAAY,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC;CAChD,OACE,kBAAA,GAAA,EAAA,UAAA,CACG,MAAW,cAAc,kBAAC,GAAD;EAAe,GAAI;EAAU;CAAQ,CAAA,GAC/D,kBAAC,GAAD;EAAU,GAAI;EAAS;EAAM,QAAQ,KAAK,UAAU,CAAC;CAAI,CAAA,CACzD,EAAA,CAAA;AAEN;AAGA,SAAS,EAAc,EAAE,UAAO,GAAG,KAA8C;CAC/E,IAAM,IAAI,EAAY,GAChB,CAAC,GAAM,KAAW,EAAS,EAAK,GAChC,EAAE,aAAU,EAAe,GAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,GACtD,IAAI,QACD,IAAO;EAAE,GAAG;EAAO,MAAM;EAAQ,QAAQ,EAAM,UAAU;EAAe,OAAO,EAAM,SAAS,EAAE;CAAS,IAAI,MACpH;EAAC;EAAM;EAAO,EAAE;CAAQ,CAC1B,GACM,IAAO,EAAY,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,GAC1C,IAAQ,QAAc,EAAK,MAAM,QAAQ,MAAM,CAAC,EAAE,OAAO,OAAO,GAAG,CAAC,EAAK,KAAK,CAAC;CAErF,OADK,IAEH,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAU;EAAqB,aAAU;EAArF,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,MAAK;GAAK,WAAU;GAAqB,iBAAe;GAAM,eAAe,EAAQ,CAAC,CAAI;GACjH,UAAA,IAAO,EAAE,EAAE,qBAAqB,IAAI,EAAE,OAAO,uBAAuB,CAAK;EACpE,CAAA,GACP,KAAQ,kBAAC,GAAD;GAAU,GAAI;GAAG,MAAM;IAAE,GAAG;IAAM;GAAM;GAAG,QAAQ,KAAK,UAAU,CAAC;EAAI,CAAA,CAChE;MAPD;AASrB;AAGA,SAAgB,EAAc,EAAE,UAAO,GAAG,KAA6C;CACrF,IAAM,EAAE,SAAM,EAAY,GACpB,IAAI,SAAe;EAAE,GAAG;EAAO,OAAO,EAAM,SAAS,EAAE;CAAS,IAAI,CAAC,GAAO,EAAE,QAAQ,CAAC,GACvF,IAAO,EAAU,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC;CAE9C,OADK,EAAM,EAAE,KAAK,IACX,kBAAC,GAAD;EAAU,SAAQ;EAAS,OAAO,EAAE,sBAAsB,EAAE,GAAG,EAAM,EAAE,KAAK,EAAE,CAAC;EAAG,GAAI;EAAS;EAAM,QAAQ,KAAK,UAAU,CAAC;CAAI,CAAA,IAD5G;AAE9B;AAGA,SAAgB,EAAe,EAAE,WAAQ,CAAC,GAAG,GAAG,KAA+C;CAC7F,IAAM,EAAE,SAAM,EAAY,GACpB,IAAM,EAAW,CAAiB,GAClC,IAAI,SAAe;EAAE,GAAG;EAAO,OAAO,EAAM,SAAS,EAAE;CAAS,IAAI,CAAC,GAAO,EAAE,QAAQ,CAAC,GACvF,IAAO,EAAW,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,GACzC,IAAS,EAAU,gBAAgB;CAEzC,OADI,GAAK,WAAW,OAAa,kBAAC,KAAD;EAAG,WAAU;EAAyB,UAAA,EAAE,2BAA2B;CAAK,CAAA,IAEvG,kBAAC,OAAD;EAAK,WAAU;EAAa,aAAU;EAAtC,UAAA,CACG,EAAK,MAAM,SAAS,KACnB,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAQ;GAAU,WAAU;GAAmB,UAAU,EAAO;GAAS,eAAe,KAAK,EAAO,UAAU,EAAK,OAAO,EAAM,OAAO,EAAE,MAAM,EAAM,KAAK,IAAI,CAAC,CAAC,CAAC;GAChL,UAAA,EAAE,uBAAuB;EACpB,CAAA,GAEV,kBAAC,GAAD;GACE,SAAQ;GACR,OAAO,EAAE,uBAAuB;GAChC,GAAI;GACE;GACN,QAAQ,KAAK,UAAU,CAAC;GACxB,YACE,EAAE,gBACA,MACA,kBAAC,GAAD;IACQ;IACN,QAAQ,EAAE;IACV,MAAM,EAAE;IACR,OAAO,EAAE;IACT,aAAa,EAAE;IACf,eAAe,EAAE;IACjB,WAAW,EAAE;IACb,YAAY,EAAE;IACd,OAAO,EAAE;IACT,cAAc,EAAE;IAChB,aAAa,EAAE;IACf,cAAc,EAAE;IAChB,UAAU,EAAE;IACZ,YAAY,EAAE;IACd,OAAO,EAAE;IACT,QAAQ,EAAE;IACV,OAAO,EAAE;IACT,WAAW,EAAE;IACb,UAAU,EAAE;IAEZ,UAAA,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAQ,WAAU;KAAqB,UAAU,EAAO;KAAS,eAAe,KAAK,EAAO,UAAU,EAAK,OAAO,EAAE,YAAY,EAAK,GAAG,CAAC,CAAC;KACjK,UAAA,EAAE,4BAA4B;IACzB,CAAA;GACG,CAAA;EAGlB,CAAA,CACE;;AAET;AA0BA,SAAgB,EAAY,EAAE,UAAO,YAAS,oBAAiB,YAAS,UAAO,MAAM,GAAW,WAAQ,CAAC,GAAG,SAAM,GAAG,KAAuB;CAC1I,IAAM,IAAQ,EAAM,SAAS,EAAE,YAAY,IACrC,IAAI,SAAe;EAAE,GAAG;EAAO;CAAM,IAAI,CAAC,GAAO,CAAK,CAAC,GACvD,IAAU,QACP,IAAY;EAAE,GAAG;EAAW,OAAO,EAAU,SAAS;CAAM,IAAI,IAAQ;EAAE,QAAQ,CAAC,CAAK;EAAG,MAAM;EAAqB;CAAM,IAAI,MACvI;EAAC;EAAW;EAAO;CAAK,CAC1B,GACM,IAAO,EAAW,KAAmB,KAAW,IAAU,OAAO,GAAS,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,GACjG,IAAO,EAAmB,IAAkB,IAAI,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,GAC1E,IAAO,EAAW,IAAU;EAAE,GAAG;EAAS,OAAO,EAAQ,SAAS;CAAM,IAAI,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,GACtG,IAAS,EAAY,GAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,GAClD,IAAgB,IAClB,IACA,KAAW,WACF;EACL,IAAM,IAAI,IAAU,IAAO;EAC3B,OAAO;GAAE,OAAO,EAAE;GAAO,SAAS,EAAE;GAAS,OAAO,EAAE;GAAO,aAAa,EAAE,OAAO,SAAS;GAAkB,QAAQ,EAAE;EAAO;CACjI,EAAA,CAAG,IACH,GACA,EAAE,aAAU,EAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,GACzC,IAAU,EAAE,YAAY,IAAkB,oBAAoB,IAAU,YAAY,IAAQ,aAAa,IAAU,SAAS,YAC5H,IAAW,EAAY,EAAK,OAAO;EAAE;EAAS,QAAQ,KAAK,UAAU;GAAC;GAAS;GAAiB;GAAS;GAAO;GAAS;EAAC,CAAC;EAAG,QAAQ,EAAE;CAAO,CAAC,GAChJ,IAAS,KAAW,KAAmB,KAAW,KAAS,GAC3D,IAAS,EAAE,OAAO,EAAe,EAAE,IAAI,IAAI,KAAA;CAEjD,OADI,CAAC,KAAU,EAAK,eAAgB,CAAC,EAAK,WAAW,CAAC,EAAK,MAAM,SAAgB,OAE/E,kBAAC,GAAD;EAAkB,YAAY,EAAE;EAAY,WAAW,EAAG,sBAAsB,EAAE,SAAS;EAAG,aAAU;EAAe,gBAAc;EAArI,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;IAA2B,UAAA;GAAU,CAAA,GAClD,CACE;EACL,CAAA,GAAA,kBAAC,MAAD;GAAI,WAAW,GAAQ,aAAa;GAA0C,OAAO,GAAQ;GACzF,WAAA,EAAK,WAAW,CAAC,EAAK,MAAM,SAAS,MAAM,KAAK,EAAE,QAAQ,EAAE,SAAS,IAAI,IAAI,EAAK,MAAA,CAAO,KAAK,GAAM,MACpG,kBAAC,MAAD;IAAwB,WAAW,IAAS,KAAA,IAAY;IAAoC,KAAK,IAAO,EAAS,MAAM,EAAK,IAAI,IAAI,CAAC,IAAI,KAAA;IACtI,UAAC,IAEE,EAAE,aACJ,EAAE,WAAW,GAAM,IAAI,CAAC,IAExB,kBAAC,GAAD;KACQ;KACN,QAAQ,EAAE;KACV,MAAM,EAAE;KACR,OAAO,EAAE;KACT,aAAa,EAAE;KACf,eAAe,EAAE;KACjB,WAAW,EAAE;KACb,YAAY,EAAE;KACd,OAAO,EAAE;KACT,cAAc,EAAE;KAChB,aAAa,EAAE;KACf,cAAc,EAAE;KAChB,UAAU,EAAE;KACZ,YAAY,EAAE;KACd,OAAO,EAAE,SAAS,GAAO;KACzB,QAAQ,EAAE;KACV,OAAO,EAAE;KACT,WAAW,EAAE;KACb,UAAU,EAAE;KACZ,cAAc,EAAS,MAAM,EAAK,IAAI,IAAI,CAAC;IAC5C,CAAA,IAzBD,kBAAC,GAAD;KAAU,WAAW,EAAE,gBAAgB,KAAA,IAAY,EAAE,UAAU,cAAc,wBAAwB,wBAAwB;KAAU,OAAO,EAAE,gBAAgB,KAAA,IAAY,KAAA,IAAY,EAAE,aAAa,EAAE,YAAY;IAAI,CAAA;GA2BzN,GA7BK,GAAM,MAAM,CA6BjB,CACL;EACC,CAAA,CACY;;AAEtB"}
|
|
@@ -4,7 +4,8 @@ export { DailyReleases, type DailyReleasesProps } from './daily-releases.js';
|
|
|
4
4
|
export { PopularContentGrid, type PopularContentGridProps } from './popular.js';
|
|
5
5
|
export { SearchBox, type SearchBoxProps } from './search.js';
|
|
6
6
|
export { ReaderSettings, type ReaderSettingsProps } from './reader-settings.js';
|
|
7
|
-
export {
|
|
7
|
+
export { ContentFilterDialog, type ContentFilterDialogProps } from './filter-dialog.js';
|
|
8
|
+
export { ContentFilterSettings, ReleaseDateIndex, type ContentFilterSettingsProps, type ContentFilterCount, type ReleaseDateIndexProps } from './filters.js';
|
|
8
9
|
export { ContentListing, sortLabel, type ContentListingProps } from './listing.js';
|
|
9
10
|
export { Paywall, VersionPicker, priceLabel, useVersionMenuItems, type PaywallProps, type VersionMenuOptions, type VersionPickerProps } from './paywall.js';
|
|
10
11
|
export { AccessEditor, ContentDetailsEditor, ContentSections, SegmentsEditor, VersionCover, VersionsEditor, versionLabel } from './editors.js';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useReducedMotion as e } from "../../react/inline-preview.js";
|
|
2
|
+
import { useCallback as t, useEffect as n, useLayoutEffect as r, useRef as i } from "react";
|
|
3
|
+
//#region src/components/content/release-reveal.ts
|
|
4
|
+
function a(a) {
|
|
5
|
+
let o = i(null), s = i(null), c = i([]), l = e(), u = t((e = !1) => {
|
|
6
|
+
let t = o.current;
|
|
7
|
+
t && (s.current = {
|
|
8
|
+
height: t.getBoundingClientRect().height,
|
|
9
|
+
positions: new Map([...t.children].map((e) => [e.getAttribute("data-release-key") ?? "", e.getBoundingClientRect()])),
|
|
10
|
+
focus: e
|
|
11
|
+
});
|
|
12
|
+
}, []);
|
|
13
|
+
return r(() => {
|
|
14
|
+
let e = s.current;
|
|
15
|
+
s.current = null;
|
|
16
|
+
let t = o.current;
|
|
17
|
+
if (!e || !t) return;
|
|
18
|
+
c.current.forEach((e) => e.cancel()), c.current = [];
|
|
19
|
+
let n = [...t.children].filter((e) => e instanceof HTMLElement), r = n.filter((t) => !e.positions.has(t.dataset.releaseKey ?? ""));
|
|
20
|
+
if (e.focus && r[0]?.querySelector("a[href]:not([tabindex='-1']), button:not([disabled])")?.focus({ preventScroll: !0 }), l || typeof t.animate != "function") return;
|
|
21
|
+
let i = "cubic-bezier(0.22, 1, 0.36, 1)", a = 0;
|
|
22
|
+
for (let t of n) {
|
|
23
|
+
let n = e.positions.get(t.dataset.releaseKey ?? "");
|
|
24
|
+
if (n) {
|
|
25
|
+
let e = t.getBoundingClientRect(), r = n.left - e.left, a = n.top - e.top;
|
|
26
|
+
(r || a) && c.current.push(t.animate([{ transform: `translate(${r}px, ${a}px)` }, { transform: "translate(0, 0)" }], {
|
|
27
|
+
duration: 400,
|
|
28
|
+
easing: i
|
|
29
|
+
}));
|
|
30
|
+
} else c.current.push(t.animate([{
|
|
31
|
+
opacity: 0,
|
|
32
|
+
transform: "translateY(16px) scale(0.97)"
|
|
33
|
+
}, {
|
|
34
|
+
opacity: 1,
|
|
35
|
+
transform: "translateY(0) scale(1)"
|
|
36
|
+
}], {
|
|
37
|
+
duration: 400,
|
|
38
|
+
delay: Math.min(a++, 5) * 45,
|
|
39
|
+
easing: i,
|
|
40
|
+
fill: "backwards"
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
let u = t.getBoundingClientRect().height;
|
|
44
|
+
Math.abs(u - e.height) > 1 && c.current.push(t.animate([{
|
|
45
|
+
height: `${e.height}px`,
|
|
46
|
+
overflow: "clip"
|
|
47
|
+
}, {
|
|
48
|
+
height: `${u}px`,
|
|
49
|
+
overflow: "clip"
|
|
50
|
+
}], {
|
|
51
|
+
duration: 400,
|
|
52
|
+
easing: i
|
|
53
|
+
}));
|
|
54
|
+
}, [a, l]), n(() => () => c.current.forEach((e) => e.cancel()), []), {
|
|
55
|
+
grid: o,
|
|
56
|
+
prepare: u
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { a as useReleaseReveal };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=release-reveal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"release-reveal.js","names":[],"sources":["../../../src/components/content/release-reveal.ts"],"sourcesContent":["import { useCallback, useEffect, useLayoutEffect, useRef } from \"react\";\nimport { useReducedMotion } from \"../../react/inline-preview.js\";\n\n/** A local layout transition for a cached reveal, not an image-loading policy. */\nexport function useReleaseReveal(identity: string) {\n const grid = useRef<HTMLOListElement>(null);\n const pending = useRef<{ height: number; positions: Map<string, DOMRect>; focus: boolean } | null>(null);\n const animations = useRef<Animation[]>([]);\n const reduced = useReducedMotion();\n const prepare = useCallback((focus = false) => {\n const element = grid.current;\n if (!element) return;\n pending.current = {\n height: element.getBoundingClientRect().height,\n positions: new Map([...element.children].map((child) => [child.getAttribute(\"data-release-key\") ?? \"\", child.getBoundingClientRect()])),\n focus,\n };\n }, []);\n useLayoutEffect(() => {\n const snapshot = pending.current;\n pending.current = null;\n const element = grid.current;\n if (!snapshot || !element) return;\n animations.current.forEach((animation) => animation.cancel());\n animations.current = [];\n const children = [...element.children].filter((child): child is HTMLElement => child instanceof HTMLElement);\n const added = children.filter((child) => !snapshot.positions.has(child.dataset.releaseKey ?? \"\"));\n if (snapshot.focus) added[0]?.querySelector<HTMLElement>(\"a[href]:not([tabindex='-1']), button:not([disabled])\")?.focus({ preventScroll: true });\n if (reduced || typeof element.animate !== \"function\") return;\n const easing = \"cubic-bezier(0.22, 1, 0.36, 1)\";\n let revealed = 0;\n for (const child of children) {\n const previous = snapshot.positions.get(child.dataset.releaseKey ?? \"\");\n if (previous) {\n const next = child.getBoundingClientRect();\n const x = previous.left - next.left;\n const y = previous.top - next.top;\n if (x || y) animations.current.push(child.animate([{ transform: `translate(${x}px, ${y}px)` }, { transform: \"translate(0, 0)\" }], { duration: 400, easing }));\n } else {\n animations.current.push(child.animate([{ opacity: 0, transform: \"translateY(16px) scale(0.97)\" }, { opacity: 1, transform: \"translateY(0) scale(1)\" }], { duration: 400, delay: Math.min(revealed++, 5) * 45, easing, fill: \"backwards\" }));\n }\n }\n const height = element.getBoundingClientRect().height;\n if (Math.abs(height - snapshot.height) > 1) animations.current.push(element.animate([{ height: `${snapshot.height}px`, overflow: \"clip\" }, { height: `${height}px`, overflow: \"clip\" }], { duration: 400, easing }));\n }, [identity, reduced]);\n useEffect(() => () => animations.current.forEach((animation) => animation.cancel()), []);\n return { grid, prepare };\n}\n"],"mappings":";;;AAIA,SAAgB,EAAiB,GAAkB;CACjD,IAAM,IAAO,EAAyB,IAAI,GACpC,IAAU,EAAmF,IAAI,GACjG,IAAa,EAAoB,CAAC,CAAC,GACnC,IAAU,EAAiB,GAC3B,IAAU,GAAa,IAAQ,OAAU;EAC7C,IAAM,IAAU,EAAK;EAChB,MACL,EAAQ,UAAU;GAChB,QAAQ,EAAQ,sBAAsB,CAAC,CAAC;GACxC,WAAW,IAAI,IAAI,CAAC,GAAG,EAAQ,QAAQ,CAAC,CAAC,KAAK,MAAU,CAAC,EAAM,aAAa,kBAAkB,KAAK,IAAI,EAAM,sBAAsB,CAAC,CAAC,CAAC;GACtI;EACF;CACF,GAAG,CAAC,CAAC;CA6BL,OA5BA,QAAsB;EACpB,IAAM,IAAW,EAAQ;EACzB,EAAQ,UAAU;EAClB,IAAM,IAAU,EAAK;EACrB,IAAI,CAAC,KAAY,CAAC,GAAS;EAE3B,AADA,EAAW,QAAQ,SAAS,MAAc,EAAU,OAAO,CAAC,GAC5D,EAAW,UAAU,CAAC;EACtB,IAAM,IAAW,CAAC,GAAG,EAAQ,QAAQ,CAAC,CAAC,QAAQ,MAAgC,aAAiB,WAAW,GACrG,IAAQ,EAAS,QAAQ,MAAU,CAAC,EAAS,UAAU,IAAI,EAAM,QAAQ,cAAc,EAAE,CAAC;EAEhG,IADI,EAAS,SAAO,EAAM,EAAE,EAAE,cAA2B,sDAAsD,CAAC,EAAE,MAAM,EAAE,eAAe,GAAK,CAAC,GAC3I,KAAW,OAAO,EAAQ,WAAY,YAAY;EACtD,IAAM,IAAS,kCACX,IAAW;EACf,KAAK,IAAM,KAAS,GAAU;GAC5B,IAAM,IAAW,EAAS,UAAU,IAAI,EAAM,QAAQ,cAAc,EAAE;GACtE,IAAI,GAAU;IACZ,IAAM,IAAO,EAAM,sBAAsB,GACnC,IAAI,EAAS,OAAO,EAAK,MACzB,IAAI,EAAS,MAAM,EAAK;IAC9B,CAAI,KAAK,MAAG,EAAW,QAAQ,KAAK,EAAM,QAAQ,CAAC,EAAE,WAAW,aAAa,EAAE,MAAM,EAAE,KAAK,GAAG,EAAE,WAAW,kBAAkB,CAAC,GAAG;KAAE,UAAU;KAAK;IAAO,CAAC,CAAC;GAC9J,OACE,EAAW,QAAQ,KAAK,EAAM,QAAQ,CAAC;IAAE,SAAS;IAAG,WAAW;GAA+B,GAAG;IAAE,SAAS;IAAG,WAAW;GAAyB,CAAC,GAAG;IAAE,UAAU;IAAK,OAAO,KAAK,IAAI,KAAY,CAAC,IAAI;IAAI;IAAQ,MAAM;GAAY,CAAC,CAAC;EAE9O;EACA,IAAM,IAAS,EAAQ,sBAAsB,CAAC,CAAC;EAC/C,AAAI,KAAK,IAAI,IAAS,EAAS,MAAM,IAAI,KAAG,EAAW,QAAQ,KAAK,EAAQ,QAAQ,CAAC;GAAE,QAAQ,GAAG,EAAS,OAAO;GAAK,UAAU;EAAO,GAAG;GAAE,QAAQ,GAAG,EAAO;GAAK,UAAU;EAAO,CAAC,GAAG;GAAE,UAAU;GAAK;EAAO,CAAC,CAAC;CACrN,GAAG,CAAC,GAAU,CAAO,CAAC,GACtB,cAAsB,EAAW,QAAQ,SAAS,MAAc,EAAU,OAAO,CAAC,GAAG,CAAC,CAAC,GAChF;EAAE;EAAM;CAAQ;AACzB"}
|
|
@@ -9,7 +9,7 @@ export interface CommentsProps {
|
|
|
9
9
|
item: Target;
|
|
10
10
|
/** One language's thread: shown, and joined by new comments. */
|
|
11
11
|
language?: string;
|
|
12
|
-
/** The item's comment count
|
|
12
|
+
/** The item's total comment count. Otherwise inferred when the whole thread is loaded. */
|
|
13
13
|
count?: number;
|
|
14
14
|
/** Initial order: newest (default) or best (top). */
|
|
15
15
|
sort?: Sort;
|
|
@@ -20,14 +20,17 @@ import { cn as H } from "cn";
|
|
|
20
20
|
import { MoreHorizontalIcon as U, ThumbsDownIcon as W, ThumbsUpIcon as G } from "@hugeicons/core-free-icons";
|
|
21
21
|
import { HugeiconsIcon as K } from "@hugeicons/react";
|
|
22
22
|
//#region src/components/social/comments.tsx
|
|
23
|
-
var q = N(null), J = () => P(q), Y = typeof navigator < "u" && /Mac|iP(hone|ad)/.test(navigator.platform), X = 300,
|
|
24
|
-
function
|
|
23
|
+
var q = N(null), J = () => P(q), Y = typeof navigator < "u" && /Mac|iP(hone|ad)/.test(navigator.platform), X = 300, ee = (e, t) => e.author?.username ?? e.anon_name ?? t;
|
|
24
|
+
function Z(e) {
|
|
25
25
|
let o = t(), { t: s } = o, c = P(r), [l, f] = R(e.sort ?? "newest"), h = p(e.item, {
|
|
26
26
|
sort: l,
|
|
27
27
|
language: e.language,
|
|
28
28
|
pageSize: e.pageSize ?? 20,
|
|
29
29
|
client: e.client
|
|
30
|
-
}), { standing: g, reload: _ } = u(e.item, {
|
|
30
|
+
}), { standing: g, reload: _ } = u(e.item, {
|
|
31
|
+
language: e.language,
|
|
32
|
+
client: e.client
|
|
33
|
+
}), { config: C } = m({ client: e.client }), w = i(e.onError), E = e.onSignIn ?? c?.onSignIn, D = c?.viewer, O = g ? !!g.user_id : D ? !0 : D !== null && void 0, k = I(() => g?.ban_scopes ?? [], [g]), A = d({
|
|
31
34
|
scope: "owner",
|
|
32
35
|
pageSize: 100,
|
|
33
36
|
enabled: k.includes("owner"),
|
|
@@ -71,7 +74,7 @@ function Q(e) {
|
|
|
71
74
|
G(e), _();
|
|
72
75
|
},
|
|
73
76
|
onLocked: _
|
|
74
|
-
}, X = g?.ban ?? W,
|
|
77
|
+
}, X = g?.ban ?? W, ee = !g || g.can_comment, Z = e.count ?? g?.count ?? (!h.loading && !h.error && !h.hasMore ? h.items.length : void 0), Q = e.heading === void 0 ? Z === void 0 ? s("comments.title") : o.plural("comments.count", Z) : e.heading;
|
|
75
78
|
return g?.comments === "hidden" ? null : g?.locked ? /* @__PURE__ */ V(n, {
|
|
76
79
|
appearance: e.appearance,
|
|
77
80
|
className: H("grid gap-4 text-sm", e.className),
|
|
@@ -125,7 +128,7 @@ function Q(e) {
|
|
|
125
128
|
}) : /* @__PURE__ */ B("p", {
|
|
126
129
|
className: "rounded-lg border border-border p-3 text-muted-foreground",
|
|
127
130
|
children: s("comments.signIn")
|
|
128
|
-
}) :
|
|
131
|
+
}) : ee ? /* @__PURE__ */ B($, {
|
|
129
132
|
placeholder: s("comments.placeholder"),
|
|
130
133
|
submit: s("comments.post"),
|
|
131
134
|
askName: O === !1,
|
|
@@ -135,7 +138,7 @@ function Q(e) {
|
|
|
135
138
|
className: "rounded-lg border border-border p-3 text-muted-foreground",
|
|
136
139
|
children: s("comments.locked")
|
|
137
140
|
}),
|
|
138
|
-
/* @__PURE__ */ B(
|
|
141
|
+
/* @__PURE__ */ B(te, {}),
|
|
139
142
|
F && /* @__PURE__ */ B(M, {
|
|
140
143
|
open: !0,
|
|
141
144
|
onOpenChange: (e) => !e && L(null),
|
|
@@ -159,7 +162,7 @@ function Q(e) {
|
|
|
159
162
|
})
|
|
160
163
|
});
|
|
161
164
|
}
|
|
162
|
-
function
|
|
165
|
+
function te() {
|
|
163
166
|
let { t: e } = t(), { comments: n } = J();
|
|
164
167
|
return n.loading && !n.items.length ? /* @__PURE__ */ B("div", {
|
|
165
168
|
className: "grid gap-4",
|
|
@@ -190,7 +193,7 @@ function ee() {
|
|
|
190
193
|
className: "grid divide-y divide-border",
|
|
191
194
|
children: n.items.map((e) => /* @__PURE__ */ B("li", {
|
|
192
195
|
className: "py-3 first:pt-0",
|
|
193
|
-
children: /* @__PURE__ */ B(
|
|
196
|
+
children: /* @__PURE__ */ B(Q, { comment: e })
|
|
194
197
|
}, e.id))
|
|
195
198
|
}),
|
|
196
199
|
n.error && /* @__PURE__ */ B(E, { error: n.error }),
|
|
@@ -201,12 +204,9 @@ function ee() {
|
|
|
201
204
|
onClick: () => void n.loadMore(),
|
|
202
205
|
children: n.loadingMore ? e("comments.loading") : e("comments.loadMore")
|
|
203
206
|
})
|
|
204
|
-
] }) :
|
|
205
|
-
className: "text-muted-foreground",
|
|
206
|
-
children: e("comments.empty")
|
|
207
|
-
});
|
|
207
|
+
] }) : null;
|
|
208
208
|
}
|
|
209
|
-
function
|
|
209
|
+
function Q({ comment: e }) {
|
|
210
210
|
let { t: n, plural: r } = t(), i = J(), [o, s] = R(!1), [c, l] = R(null), u = f(e.id, {
|
|
211
211
|
pageSize: i.repliesPageSize,
|
|
212
212
|
enabled: o
|
|
@@ -273,7 +273,7 @@ function te({ comment: e }) {
|
|
|
273
273
|
});
|
|
274
274
|
}
|
|
275
275
|
function ne({ comment: n, onReply: r }) {
|
|
276
|
-
let { t: i } = t(), u = J(), { standing: d } = u, f =
|
|
276
|
+
let { t: i } = t(), u = J(), { standing: d } = u, f = ee(n, i("comments.anonymous")), p = !!d?.user_id && n.user_id === d.user_id, m = !n.deleted && (p || !!d?.moderate), g = !n.deleted && !!n.user_id && !p && (d?.ban_scopes.length ?? 0) > 0, _ = n.user_id ? u.banned.get(n.user_id) : void 0, [v, y] = R(!1), [b, x] = R(!1), [S, C] = R(!1), [w, A] = R(null), j = async (t, n) => {
|
|
277
277
|
A(null);
|
|
278
278
|
try {
|
|
279
279
|
await n();
|
|
@@ -530,6 +530,6 @@ function $(n) {
|
|
|
530
530
|
});
|
|
531
531
|
}
|
|
532
532
|
//#endregion
|
|
533
|
-
export {
|
|
533
|
+
export { Z as Comments };
|
|
534
534
|
|
|
535
535
|
//# sourceMappingURL=comments.js.map
|
|
@@ -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\">{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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"}
|
|
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 total comment count. Otherwise inferred when the whole thread is loaded. */\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, { language: p.language, 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 // Standing counts the entire visible thread, even while only page one is\n // loaded. Older servers can infer a total only after the list is complete.\n const count = p.count ?? standing?.count ?? (!comments.loading && !comments.error && !comments.hasMore ? comments.items.length : undefined);\n const heading = p.heading === undefined ? (count === undefined ? t(\"comments.title\") : m.plural(\"comments.count\", 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 null;\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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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export { GroupManager, useGroupManagerAccess, type GroupManagerProps } from './c
|
|
|
29
29
|
export { MediaItemPanel, VideoMediaEditor, videoPaths, type MediaItemPanelProps, type VideoMediaEditorProps } from './components/admin/media-item-panel.js';
|
|
30
30
|
export { StatsDashboard, type StatsDashboardProps } from './components/admin/stats-dashboard.js';
|
|
31
31
|
export { ReaderManifestEditor, type ReaderManifestEditorProps } from './components/admin/reader-manifest.js';
|
|
32
|
+
export { PageReview, type PageReviewProps } from './components/admin/page-review.js';
|
|
32
33
|
export { UploadQueue, type UploadQueueProps } from './components/admin/upload-queue.js';
|
|
33
34
|
export { NoAccess, Pager, useConfirm, type AdminFlowProps, type AdminRouteProps, type ConfirmOptions, type ContentKitGuard } from './components/admin/shell.js';
|
|
34
35
|
export type { AdminNavigation, ContentKitAuth, StaffAccess, StaffArea } from './react/admin.js';
|