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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +105 -43
  2. package/dist/client/beacons.d.ts +3 -13
  3. package/dist/client/beacons.js +4 -45
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/client.d.ts +3 -0
  6. package/dist/client/client.js +45 -43
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/content/favorites.d.ts +2 -0
  9. package/dist/client/content/favorites.js.map +1 -1
  10. package/dist/client/content/types.d.ts +3 -0
  11. package/dist/client/content/types.js.map +1 -1
  12. package/dist/client/discovery.d.ts +28 -3
  13. package/dist/client/discovery.js +22 -1
  14. package/dist/client/discovery.js.map +1 -1
  15. package/dist/client/engagement.d.ts +70 -0
  16. package/dist/client/engagement.js +191 -0
  17. package/dist/client/engagement.js.map +1 -0
  18. package/dist/client/generated/routes.d.ts +35 -9
  19. package/dist/client/generated/routes.js +59 -2
  20. package/dist/client/generated/routes.js.map +1 -1
  21. package/dist/client/generated/wire.d.ts +53 -12
  22. package/dist/client/generated/wire.js.map +1 -1
  23. package/dist/client/guest-reading-history.d.ts +40 -0
  24. package/dist/client/guest-reading-history.js +144 -0
  25. package/dist/client/guest-reading-history.js.map +1 -0
  26. package/dist/client/index.d.ts +4 -2
  27. package/dist/client/stats.d.ts +5 -5
  28. package/dist/client/stats.js.map +1 -1
  29. package/dist/client.js +24 -22
  30. package/dist/components/admin/content-manager.d.ts +1 -1
  31. package/dist/components/admin/content-manager.js +342 -302
  32. package/dist/components/admin/content-manager.js.map +1 -1
  33. package/dist/components/admin/page-review.js +106 -97
  34. package/dist/components/admin/page-review.js.map +1 -1
  35. package/dist/components/admin/quick-search.d.ts +14 -0
  36. package/dist/components/admin/quick-search.js +60 -0
  37. package/dist/components/admin/quick-search.js.map +1 -0
  38. package/dist/components/admin/reader-content-editor.d.ts +6 -0
  39. package/dist/components/admin/reader-content-editor.js +733 -0
  40. package/dist/components/admin/reader-content-editor.js.map +1 -0
  41. package/dist/components/admin/reader-manifest.js +120 -105
  42. package/dist/components/admin/reader-manifest.js.map +1 -1
  43. package/dist/components/admin/stats-dashboard.d.ts +4 -4
  44. package/dist/components/admin/stats-dashboard.js +132 -134
  45. package/dist/components/admin/stats-dashboard.js.map +1 -1
  46. package/dist/components/content/daily-releases.js +143 -191
  47. package/dist/components/content/daily-releases.js.map +1 -1
  48. package/dist/components/content/editor-save.d.ts +32 -0
  49. package/dist/components/content/editor-save.js +51 -0
  50. package/dist/components/content/editor-save.js.map +1 -0
  51. package/dist/components/content/editors.d.ts +17 -2
  52. package/dist/components/content/editors.js +359 -328
  53. package/dist/components/content/editors.js.map +1 -1
  54. package/dist/components/content/favorites.d.ts +6 -0
  55. package/dist/components/content/favorites.js +33 -0
  56. package/dist/components/content/favorites.js.map +1 -0
  57. package/dist/components/content/filter-dialog.js +19 -15
  58. package/dist/components/content/filter-dialog.js.map +1 -1
  59. package/dist/components/content/filters.d.ts +2 -0
  60. package/dist/components/content/filters.js +5 -1
  61. package/dist/components/content/filters.js.map +1 -1
  62. package/dist/components/content/grid.d.ts +34 -13
  63. package/dist/components/content/grid.js +300 -271
  64. package/dist/components/content/grid.js.map +1 -1
  65. package/dist/components/content/history.d.ts +6 -0
  66. package/dist/components/content/history.js +91 -0
  67. package/dist/components/content/history.js.map +1 -0
  68. package/dist/components/content/index.d.ts +1 -0
  69. package/dist/components/content/offer-fields.d.ts +1 -1
  70. package/dist/components/content/offer-fields.js +86 -64
  71. package/dist/components/content/offer-fields.js.map +1 -1
  72. package/dist/components/content/search.d.ts +11 -4
  73. package/dist/components/content/search.js +22 -11
  74. package/dist/components/content/search.js.map +1 -1
  75. package/dist/components/content/summary-tile.d.ts +14 -0
  76. package/dist/components/content/summary-tile.js +63 -0
  77. package/dist/components/content/summary-tile.js.map +1 -0
  78. package/dist/components/groups/memberships.d.ts +11 -1
  79. package/dist/components/groups/memberships.js +1 -1
  80. package/dist/components/groups/memberships.js.map +1 -1
  81. package/dist/components/media-folder-editor.d.ts +4 -0
  82. package/dist/components/media-folder-editor.js +178 -168
  83. package/dist/components/media-folder-editor.js.map +1 -1
  84. package/dist/components/video-mini-player.d.ts +4 -1
  85. package/dist/components/video-mini-player.js +77 -71
  86. package/dist/components/video-mini-player.js.map +1 -1
  87. package/dist/components/video-player.d.ts +4 -1
  88. package/dist/components/video-player.js +280 -276
  89. package/dist/components/video-player.js.map +1 -1
  90. package/dist/index.d.ts +1 -0
  91. package/dist/index.js +17 -14
  92. package/dist/locales/admin/de.js +33 -8
  93. package/dist/locales/admin/de.js.map +1 -1
  94. package/dist/locales/admin/en.d.ts +29 -4
  95. package/dist/locales/admin/en.js +33 -8
  96. package/dist/locales/admin/en.js.map +1 -1
  97. package/dist/locales/admin/es.js +33 -8
  98. package/dist/locales/admin/es.js.map +1 -1
  99. package/dist/locales/admin/ja.js +33 -8
  100. package/dist/locales/admin/ja.js.map +1 -1
  101. package/dist/locales/admin/ko.js +33 -8
  102. package/dist/locales/admin/ko.js.map +1 -1
  103. package/dist/locales/admin/zh.js +33 -8
  104. package/dist/locales/admin/zh.js.map +1 -1
  105. package/dist/locales/discovery/de.js +0 -1
  106. package/dist/locales/discovery/de.js.map +1 -1
  107. package/dist/locales/discovery/en.d.ts +0 -1
  108. package/dist/locales/discovery/en.js +0 -1
  109. package/dist/locales/discovery/en.js.map +1 -1
  110. package/dist/locales/discovery/es.js +0 -1
  111. package/dist/locales/discovery/es.js.map +1 -1
  112. package/dist/locales/discovery/ja.js +0 -1
  113. package/dist/locales/discovery/ja.js.map +1 -1
  114. package/dist/locales/discovery/ko.js +0 -1
  115. package/dist/locales/discovery/ko.js.map +1 -1
  116. package/dist/locales/discovery/zh.js +0 -1
  117. package/dist/locales/discovery/zh.js.map +1 -1
  118. package/dist/locales/en.d.ts +29 -5
  119. package/dist/react/discovery.d.ts +20 -17
  120. package/dist/react/discovery.js +273 -221
  121. package/dist/react/discovery.js.map +1 -1
  122. package/dist/react/index.d.ts +2 -1
  123. package/dist/react/page-review.d.ts +1 -1
  124. package/dist/react/page-review.js +6 -5
  125. package/dist/react/page-review.js.map +1 -1
  126. package/dist/react/resources.d.ts +3 -1
  127. package/dist/react/resources.js +4 -0
  128. package/dist/react/resources.js.map +1 -1
  129. package/dist/react/stats.d.ts +2 -2
  130. package/dist/react/stats.js.map +1 -1
  131. package/dist/react/use-draft.d.ts +8 -0
  132. package/dist/react/use-draft.js +35 -0
  133. package/dist/react/use-draft.js.map +1 -0
  134. package/dist/react/use-engagement.d.ts +26 -0
  135. package/dist/react/use-engagement.js +221 -0
  136. package/dist/react/use-engagement.js.map +1 -0
  137. package/dist/react/use-resource.d.ts +4 -3
  138. package/dist/react/use-resource.js +2 -1
  139. package/dist/react/use-resource.js.map +1 -1
  140. package/dist/react.js +12 -11
  141. package/dist/styles.css +1 -1
  142. package/dist/ui/dialog.d.ts +2 -1
  143. package/dist/ui/dialog.js +23 -20
  144. package/dist/ui/dialog.js.map +1 -1
  145. 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, 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 ?? \"relative 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 { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { AccessMode, ContentQuery, HistoryQuery, RailQuery, SearchQuery } from \"../../client/discovery.js\";\nimport type { Card, ListingSummary } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useContents, useExposure, useHistory, useRecommendations, useSearch, useSimilar, type UseRail } from \"../../react/discovery.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport type { UseList } from \"../../react/use-resource.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useContentScope } 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 } from \"../social/parts.js\";\nimport { ContentFilterDialog } from \"./filter-dialog.js\";\nimport { useReleaseReveal } from \"./release-reveal.js\";\nimport { ContentSummaryTile } from \"./summary-tile.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 /** Collapse paid/locked cards into a counted tile, or hide them; default show. */\n locked?: LockedCards;\n /** Optional-filter summary tiles are on by default; hosts may suppress them in previews. */\n showFilteredSummary?: boolean;\n /** Show the collapsed locked tile only at this measured grid width. Default 0. */\n lockedSummaryMinColumns?: number;\n /** Limit rendered preview tiles without changing the matching selection or summary counts. */\n cardLimit?: number;\n /** Limit cards in mobile previews without mounting their images. */\n mobileCardLimit?: number;\n /** Host-specific nouns for summary controls. */\n summaryLabels?: { premium?: (count: number) => string; filtered?: (count: number) => string };\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 =\n 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:\n \"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 carries exact selection-wide counts when the API was asked for them. */\ntype SummaryList = UseList<Card> & { summary?: ListingSummary };\ntype GridProps = CardListProps & { list: SummaryList; render: string; filterKinds?: readonly string[]; filterLanguage?: string; horizontal?: boolean };\n\n/** A list's cards and summary controls share one responsive, animated grid. */\nexport function CardGrid(p: GridProps) {\n const { client, viewer, language } = useContentScope(p.client);\n const context = useContext(ContentKitContext);\n const [boundary, setBoundary] = useState({ client, viewer, language, access: context?.accessRevision, render: p.render, key: 0 });\n let session = boundary;\n if (boundary.client !== client || boundary.viewer !== viewer || boundary.language !== language || boundary.access !== context?.accessRevision || boundary.render !== p.render) {\n session = { client, viewer, language, access: context?.accessRevision, render: p.render, key: boundary.key + 1 };\n setBoundary(session);\n }\n return <CardGridBody key={session.key} {...p} />;\n}\n\nfunction CardGridBody({ list, render, filterKinds, filterLanguage, horizontal, ...p }: GridProps) {\n const m = useMessages();\n const { t } = m;\n const { kinds } = useKinds({ client: p.client });\n const [expanded, setExpanded] = useState(false);\n const [filtersOpen, setFiltersOpen] = useState(false);\n const filterTrigger = useRef<HTMLElement | null>(null);\n const [element, setElement] = useState<HTMLOListElement | null>(null);\n const [columns, setColumns] = useState(0);\n const [mobile, setMobile] = useState(() => globalThis.matchMedia?.(\"(max-width: 767px)\").matches ?? false);\n useLayoutEffect(() => {\n if (!element) return;\n const measure = () => {\n const tracks = getComputedStyle(element).gridTemplateColumns.trim();\n setColumns(tracks && tracks !== \"none\" ? tracks.split(/\\s+/).length : 0);\n setMobile(globalThis.matchMedia?.(\"(max-width: 767px)\").matches ?? false);\n };\n measure();\n const observer = typeof ResizeObserver === \"function\" ? new ResizeObserver(measure) : undefined;\n observer?.observe(element);\n addEventListener(\"resize\", measure);\n return () => {\n observer?.disconnect();\n removeEventListener(\"resize\", measure);\n };\n }, [element]);\n const lockedCount = list.summary?.locked_count ?? 0;\n const filteredCount = p.showFilteredSummary === false ? 0 : (list.summary?.filtered_count ?? 0);\n const collapse = p.locked === \"collapse\" && !expanded && lockedCount > 0;\n const lockedTile = collapse && lockedCount > 0 && columns >= Math.max(0, p.lockedSummaryMinColumns ?? 0);\n const entries = useMemo(() => {\n const result: ({ key: string; card: Card; position: number } | { key: string; premium: true; card?: Card })[] = [];\n let inserted = false;\n for (const [i, card] of list.items.entries()) {\n if (isLockedCard(card) && (collapse || p.locked === \"hide\")) {\n if (lockedTile && !inserted) {\n result.push({ key: \"premium-summary\", premium: true, card });\n inserted = true;\n }\n continue;\n }\n result.push({ key: card.id, card, position: i + 1 });\n }\n if (lockedTile && !inserted) result.push({ key: \"premium-summary\", premium: true });\n const limit = mobile && p.mobileCardLimit !== undefined ? p.mobileCardLimit : p.cardLimit;\n return limit !== undefined && Number.isFinite(limit) ? result.slice(0, Math.max(0, Math.floor(limit))) : result;\n }, [list.items, collapse, p.locked, lockedTile, mobile, p.mobileCardLimit, p.cardLimit]);\n const shown = useMemo(() => entries.flatMap((entry) => (\"premium\" in entry ? [] : [{ ...entry.card, position: entry.position }])), [entries]);\n const exposure = useExposure(shown, { 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 { grid: animationGrid, prepare } = useReleaseReveal(entries.map((entry) => entry.key).join(\"\\0\"));\n const grid = useCallback(\n (node: HTMLOListElement | null) => {\n animationGrid.current = node;\n setElement(node);\n },\n [animationGrid],\n );\n const layout: ReturnType<typeof cardGridLayout> = horizontal\n ? { className: \"relative flex snap-x gap-4 overflow-x-auto pb-2 [&>li]:w-36 [&>li]:shrink-0 [&>li]:snap-start sm:[&>li]:w-44\" }\n : cardGridLayout(p.grid);\n const lowerBound = list.summary?.truncated ? \"≥ \" : \"\";\n const premiumLabel = lowerBound + (p.summaryLabels?.premium?.(lockedCount) ?? m.plural(\"contents.lockedShow\", lockedCount));\n const filteredLabel = lowerBound + (p.summaryLabels?.filtered?.(filteredCount) ?? m.plural(\"releases.filteredMore\", filteredCount));\n const showGrid = entries.length > 0 || filteredCount > 0 || (collapse && lockedCount > 0);\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 <ol aria-hidden className={layout.className} style={layout.style}>\n {Array.from({ length: 8 }, (_, i) => (\n <li key={i}>\n <Skeleton\n className={p.aspectRatio === undefined ? (p.shape === \"landscape\" ? \"aspect-video w-full\" : \"aspect-[3/4] w-full\") : \"w-full\"}\n style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }}\n />\n </li>\n ))}\n </ol>\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 ) : !showGrid ? (\n <div className=\"text-muted-foreground\" role=\"status\">\n {p.empty ?? t(\"contents.empty\")}\n </div>\n ) : (\n <ol ref={grid} tabIndex={-1} className={layout.className} style={layout.style}>\n {entries.map((entry) =>\n \"premium\" in entry ? (\n <ContentSummaryTile\n key={entry.key}\n tileKey={entry.key}\n kind=\"premium\"\n contentKind={filterKinds?.[0] ?? entry.card?.kind}\n preview={entry.card}\n open\n cardProps={p}\n label={premiumLabel}\n onClick={(event) => {\n prepare(event.detail === 0);\n setExpanded(true);\n }}\n />\n ) : (\n <li key={entry.key} data-release-key={entry.key} ref={exposure.track(entry.card.id, entry.position)}>\n {p.renderCard ? (\n p.renderCard(entry.card, entry.position)\n ) : (\n <ContentCard\n card={entry.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(entry.card.id, entry.position)}\n />\n )}\n </li>\n ),\n )}\n {filteredCount > 0 && (\n <ContentSummaryTile\n tileKey=\"filtered-summary\"\n kind=\"filtered\"\n contentKind={filterKinds?.[0] ?? list.items[0]?.kind}\n open\n cardProps={p}\n label={filteredLabel}\n onClick={(event) => {\n filterTrigger.current = event.currentTarget;\n setFiltersOpen(true);\n }}\n />\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 && (\n <p role=\"alert\" className=\"text-destructive\">\n {contentError(m, list.error)}\n </p>\n )}\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 <ContentFilterDialog\n open={filtersOpen}\n onOpenChange={setFiltersOpen}\n returnFocus={() => (filterTrigger.current?.isConnected ? filterTrigger.current : element)}\n client={p.client}\n kinds={filterKinds}\n language={filterLanguage}\n counts={list.summary?.filtered_entities}\n countsLowerBound={list.summary?.truncated}\n appearance={p.appearance}\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 replaces them with a counted tile\n * that reveals the cached previews in place (collapse).\n */\nexport type LockedCards = \"show\" | \"hide\" | \"collapse\";\n\n/** Settle locked-card admission before pagination, retaining explicit reader modes otherwise. */\nexport function cardListMode(locked: LockedCards | undefined, mode?: AccessMode): AccessMode | undefined {\n return locked === \"hide\" ? \"hide\" : locked === \"collapse\" ? \"lock\" : mode;\n}\n\nconst LOCKED_LEVELS: readonly string[] = [\"tier\", \"group\", \"ppv\", \"group_ppv\"];\nconst isLockedCard = (card: Card) => !card.access.allowed && !card.access.unknown && LOCKED_LEVELS.includes(card.access.level);\n\n/** Contents as cards (GET /v1/contents): any sort and filter, paged. */\nexport function ContentGrid({ query, locked = \"show\", ...p }: CardListProps & { query: ContentQuery }) {\n const q = useMemo<ContentQuery>(\n () => ({\n ...query,\n summary: query.summary ?? true,\n limit: query.limit ?? p.pageSize,\n mode: cardListMode(locked, query.mode),\n }),\n [query, p.pageSize, locked],\n );\n const list = useContents(q, { client: p.client });\n return (\n <CardGrid\n {...p}\n locked={locked}\n list={list}\n render={JSON.stringify(q)}\n filterKinds={query.kind ? [query.kind] : undefined}\n filterLanguage={query.display_language ?? query.language}\n />\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(\n () => ({ ...query, mode: cardListMode(p.locked, query.mode), summary: query.summary ?? true, limit: query.limit ?? p.pageSize }),\n [query, p.pageSize, p.locked],\n );\n const list = useSearch(q, { client: p.client });\n if (!query.q.trim()) return null;\n return (\n <CardGrid\n surface=\"search\"\n empty={t(\"contents.noResults\", { q: query.q.trim() })}\n {...p}\n list={list}\n render={JSON.stringify(q)}\n filterKinds={query.kind ? [query.kind] : undefined}\n filterLanguage={query.display_language ?? query.language}\n />\n );\n}\n\nexport { ContentHistory } from \"./history.js\";\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, mode: cardListMode(p.locked, query.mode), summary: query.summary ?? true, limit }), [query, limit, p.locked]);\n const listing = useMemo(\n () =>\n listQuery\n ? { ...listQuery, mode: cardListMode(p.locked, listQuery.mode), summary: listQuery.summary ?? true, limit: listQuery.limit ?? limit }\n : group\n ? { groups: [group], sort: \"position\" as const, mode: cardListMode(p.locked), summary: true, limit }\n : null,\n [listQuery, group, limit, p.locked],\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, mode: cardListMode(p.locked, history.mode), summary: history.summary ?? true, limit: history.limit ?? limit } : null, {\n client: p.client,\n });\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, summary: l.summary, loading: l.loading, error: l.error, unavailable: l.error?.code === \"not_configured\", reload: l.reload };\n })()\n : like;\n const surface = p.surface ?? (recommendations ? \"recommendations\" : history ? \"history\" : group ? \"featured\" : listing ? \"list\" : \"similar\");\n const source = similar || recommendations || history || group || listing;\n if (!source || rail.unavailable || (!rail.loading && !rail.items.length && !rail.summary?.filtered_count && !rail.summary?.locked_count)) return null;\n const list: SummaryList = { ...rail, loadingMore: false, hasMore: false, loadMore: async () => {} };\n const kind = listQuery?.kind ?? history?.kind ?? q.kind;\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 <CardGrid\n {...p}\n className={undefined}\n surface={surface}\n horizontal={!p.grid}\n list={list}\n render={JSON.stringify([similar, recommendations, history, group, listing, q])}\n filterKinds={kind ? [kind] : undefined}\n filterLanguage={listQuery?.display_language ?? history?.display_language ?? q.display_language ?? listQuery?.language ?? history?.language ?? q.language}\n />\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { HistoryQuery } from '../../client/discovery.js';
2
+ import { CardListProps } from './grid.js';
3
+ /** Account history or this browser's guest history, each removable, with a clear-all. */
4
+ export declare function ContentHistory({ query, ...p }: CardListProps & {
5
+ query?: HistoryQuery;
6
+ }): import('react').JSX.Element;
@@ -0,0 +1,91 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { Button as t } from "../../ui/button.js";
3
+ import { useAction as n } from "../social/parts.js";
4
+ import { useHistory as r } from "../../react/discovery.js";
5
+ import { ContentCard as i } from "./card.js";
6
+ import { ContentFilterDialog as a } from "./filter-dialog.js";
7
+ import { CardGrid as o } from "./grid.js";
8
+ import { useMemo as s, useRef as c, useState as l } from "react";
9
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
10
+ //#region src/components/content/history.tsx
11
+ function f({ query: f = {}, ...p }) {
12
+ let { t: m } = e(), h = s(() => ({
13
+ ...f,
14
+ summary: !0,
15
+ mode: p.locked === "collapse" ? "lock" : p.locked === "hide" ? "hide" : f.mode,
16
+ limit: f.limit ?? p.pageSize
17
+ }), [
18
+ f,
19
+ p.pageSize,
20
+ p.locked
21
+ ]), g = r(h, { client: p.client }), _ = n("history.forget"), [v, y] = l(!1), b = c(null);
22
+ return /* @__PURE__ */ d("div", {
23
+ className: "grid gap-3",
24
+ "data-ckui": "content-history",
25
+ children: [
26
+ g.items.length > 0 && /* @__PURE__ */ u(t, {
27
+ size: "sm",
28
+ variant: "outline",
29
+ className: "justify-self-end",
30
+ disabled: _.pending,
31
+ onClick: () => void _.run(() => g.forget(f.kind ? { kind: f.kind } : {})),
32
+ children: m("contents.clearHistory")
33
+ }),
34
+ g.filterSummaryUnknown && p.showFilteredSummary !== !1 && /* @__PURE__ */ u(t, {
35
+ ref: b,
36
+ variant: "outline",
37
+ className: "justify-self-start",
38
+ onClick: () => y(!0),
39
+ children: m("releases.manageFilters")
40
+ }),
41
+ /* @__PURE__ */ u(a, {
42
+ client: p.client,
43
+ language: h.language,
44
+ open: v,
45
+ onOpenChange: y,
46
+ returnFocus: () => b.current
47
+ }),
48
+ /* @__PURE__ */ u(o, {
49
+ surface: "history",
50
+ empty: m("contents.historyEmpty"),
51
+ ...p,
52
+ list: g,
53
+ render: JSON.stringify(h),
54
+ filterLanguage: h.language,
55
+ renderCard: p.renderCard ?? ((e) => /* @__PURE__ */ u(i, {
56
+ card: e,
57
+ client: p.client,
58
+ href: p.href,
59
+ shape: p.shape,
60
+ aspectRatio: p.aspectRatio,
61
+ titlePosition: p.titlePosition,
62
+ loadMedia: p.loadMedia,
63
+ groupKinds: p.groupKinds,
64
+ chips: p.chips,
65
+ filterEntity: p.filterEntity,
66
+ entityStyle: p.entityStyle,
67
+ entityLayout: p.entityLayout,
68
+ metadata: p.metadata,
69
+ showCounts: p.showCounts,
70
+ tiers: p.tiers,
71
+ labels: p.labels,
72
+ owner: p.owner,
73
+ ownerHref: p.ownerHref,
74
+ decorate: p.decorate,
75
+ children: /* @__PURE__ */ u(t, {
76
+ size: "xs",
77
+ variant: "ghost",
78
+ className: "justify-self-start",
79
+ disabled: _.pending,
80
+ onClick: () => void _.run(() => g.forget({ content_id: e.id })),
81
+ children: m("contents.removeFromHistory")
82
+ })
83
+ }))
84
+ })
85
+ ]
86
+ });
87
+ }
88
+ //#endregion
89
+ export { f as ContentHistory };
90
+
91
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","names":[],"sources":["../../../src/components/content/history.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from \"react\";\nimport type { HistoryQuery } from \"../../client/discovery.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useHistory } from \"../../react/discovery.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { useAction } from \"../social/parts.js\";\nimport { ContentFilterDialog } from \"./filter-dialog.js\";\nimport { ContentCard } from \"./card.js\";\nimport { CardGrid, type CardListProps } from \"./grid.js\";\n\n/** Account history or this browser's guest history, each removable, with a clear-all. */\nexport function ContentHistory({ query = {}, ...p }: CardListProps & { query?: HistoryQuery }) {\n const { t } = useMessages();\n const q = useMemo(() => ({ ...query, summary: true, mode: p.locked === \"collapse\" ? \"lock\" as const : p.locked === \"hide\" ? \"hide\" as const : query.mode, limit: query.limit ?? p.pageSize }), [query, p.pageSize, p.locked]);\n const list = useHistory(q, { client: p.client });\n const forget = useAction(\"history.forget\");\n const [filtersOpen, setFiltersOpen] = useState(false);\n const filterTrigger = useRef<HTMLButtonElement | null>(null);\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 {list.filterSummaryUnknown && p.showFilteredSummary !== false && (\n <Button ref={filterTrigger} variant=\"outline\" className=\"justify-self-start\" onClick={() => setFiltersOpen(true)}>{t(\"releases.manageFilters\")}</Button>\n )}\n <ContentFilterDialog client={p.client} language={q.language} open={filtersOpen} onOpenChange={setFiltersOpen} returnFocus={() => filterTrigger.current} />\n <CardGrid\n surface=\"history\"\n empty={t(\"contents.historyEmpty\")}\n {...p}\n list={list}\n render={JSON.stringify(q)}\n filterLanguage={q.language}\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"],"mappings":";;;;;;;;;;AAWA,SAAgB,EAAe,EAAE,WAAQ,CAAC,GAAG,GAAG,KAA+C;CAC7F,IAAM,EAAE,SAAM,EAAY,GACpB,IAAI,SAAe;EAAE,GAAG;EAAO,SAAS;EAAM,MAAM,EAAE,WAAW,aAAa,SAAkB,EAAE,WAAW,SAAS,SAAkB,EAAM;EAAM,OAAO,EAAM,SAAS,EAAE;CAAS,IAAI;EAAC;EAAO,EAAE;EAAU,EAAE;CAAM,CAAC,GACtN,IAAO,EAAW,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,GACzC,IAAS,EAAU,gBAAgB,GACnC,CAAC,GAAa,KAAkB,EAAS,EAAK,GAC9C,IAAgB,EAAiC,IAAI;CAC3D,OACE,kBAAC,OAAD;EAAK,WAAU;EAAa,aAAU;EAAtC,UAAA;GACG,EAAK,MAAM,SAAS,KACnB,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAU,WAAU;IAAmB,UAAU,EAAO;IAAS,eAAe,KAAK,EAAO,UAAU,EAAK,OAAO,EAAM,OAAO,EAAE,MAAM,EAAM,KAAK,IAAI,CAAC,CAAC,CAAC;IAChL,UAAA,EAAE,uBAAuB;GACpB,CAAA;GAET,EAAK,wBAAwB,EAAE,wBAAwB,MACtD,kBAAC,GAAD;IAAQ,KAAK;IAAe,SAAQ;IAAU,WAAU;IAAqB,eAAe,EAAe,EAAI;IAAI,UAAA,EAAE,wBAAwB;GAAU,CAAA;GAEzJ,kBAAC,GAAD;IAAqB,QAAQ,EAAE;IAAQ,UAAU,EAAE;IAAU,MAAM;IAAa,cAAc;IAAgB,mBAAmB,EAAc;GAAU,CAAA;GACzJ,kBAAC,GAAD;IACE,SAAQ;IACR,OAAO,EAAE,uBAAuB;IAChC,GAAI;IACE;IACN,QAAQ,KAAK,UAAU,CAAC;IACxB,gBAAgB,EAAE;IAClB,YACE,EAAE,gBACA,MACA,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;KACT,QAAQ,EAAE;KACV,OAAO,EAAE;KACT,WAAW,EAAE;KACb,UAAU,EAAE;KAEZ,UAAA,kBAAC,GAAD;MAAQ,MAAK;MAAK,SAAQ;MAAQ,WAAU;MAAqB,UAAU,EAAO;MAAS,eAAe,KAAK,EAAO,UAAU,EAAK,OAAO,EAAE,YAAY,EAAK,GAAG,CAAC,CAAC;MACjK,UAAA,EAAE,4BAA4B;KACzB,CAAA;IACG,CAAA;GAGlB,CAAA;EACE;;AAET"}
@@ -13,3 +13,4 @@ export { AccessEditor, ContentDetailsEditor, ContentSections, SegmentsEditor, Ve
13
13
  export { OfferFields } from './offer-fields.js';
14
14
  export { ReaderStats, type ReaderStatsProps } from './reader-stats.js';
15
15
  export { VersionDownloadButton, type VersionDownloadButtonProps } from './download-button.js';
16
+ export { ContentFavorites } from './favorites.js';
@@ -2,7 +2,7 @@ import { Offer, Terms } from '../../client/generated/wire.js';
2
2
  /** An offer's form: price, currency and whether it rents or renews; the current offer with its withdrawal. */
3
3
  export declare function OfferFields({ offer, onSell, onWithdraw, pending }: {
4
4
  offer?: Pick<Offer, "terms"> & Partial<Pick<Offer, "state" | "synced">>;
5
- onSell: (t: Terms) => void;
5
+ onSell: (t: Terms) => unknown | Promise<unknown>;
6
6
  onWithdraw: () => void;
7
7
  pending: boolean;
8
8
  }): import('react').JSX.Element;
@@ -4,124 +4,146 @@ import { Badge as n } from "../../ui/badge.js";
4
4
  import { Input as r } from "../../ui/input.js";
5
5
  import { Label as i } from "../../ui/label.js";
6
6
  import { Select as a, SelectContent as o, SelectItem as s, SelectTrigger as c, SelectValue as l } from "../../ui/select.js";
7
- import { priceLabel as u } from "./paywall.js";
8
- import { useId as d, useState as f } from "react";
9
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
7
+ import { useDraft as u } from "../../react/use-draft.js";
8
+ import { useEditorSaveTask as d } from "./editor-save.js";
9
+ import { priceLabel as f } from "./paywall.js";
10
+ import { useId as p, useMemo as m } from "react";
11
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
10
12
  //#region src/components/content/offer-fields.tsx
11
- function h({ offer: h, onSell: g, onWithdraw: _, pending: v }) {
12
- let y = e(), { t: b } = y, x = d(), S = h && (h.state ?? "active") === "active" ? h : void 0, [C, w] = f(S?.terms.amount ?? ""), [T, E] = f(S?.terms.currency ?? "USD"), [D, O] = f(S?.terms.renew_hours ? "renew" : S?.terms.access_hours ? "rent" : "once"), [k, A] = f(String((S?.terms.renew_hours ?? S?.terms.access_hours ?? 720) / 24 | 0));
13
- return /* @__PURE__ */ m("div", {
13
+ function _({ offer: _, onSell: v, onWithdraw: y, pending: b }) {
14
+ let x = e(), { t: S } = x, C = p(), w = _ && (_.state ?? "active") === "active" ? _ : void 0, T = u(m(() => ({
15
+ amount: w?.terms.amount ?? "",
16
+ currency: w?.terms.currency ?? "USD",
17
+ mode: w?.terms.renew_hours ? "renew" : w?.terms.access_hours ? "rent" : "once",
18
+ days: String((w?.terms.renew_hours ?? w?.terms.access_hours ?? 720) / 24 | 0)
19
+ }), [w?.terms])), { amount: E, currency: D, mode: O, days: k } = T.value, A = (e) => T.edit((t) => ({
20
+ ...t,
21
+ ...e
22
+ })), j = /^\d+(\.\d{1,2})?$/.test(E.trim()) && D.length === 3 && (O === "once" || Number(k) > 0), M = async () => {
23
+ if (!j) return !1;
24
+ let e = T.checkpoint(), t = Math.max(1, Math.trunc(Number(k) || 0)) * 24, n = {
25
+ amount: E.trim(),
26
+ currency: D,
27
+ ...O === "rent" ? { access_hours: t } : O === "renew" ? { renew_hours: t } : {}
28
+ };
29
+ return await v(n) ? e({
30
+ amount: n.amount,
31
+ currency: n.currency,
32
+ mode: O,
33
+ days: k
34
+ }) : !1;
35
+ };
36
+ return d(`offer:${C}`, {
37
+ label: S("contentEditor.price"),
38
+ dirty: T.dirty.length > 0,
39
+ pending: b,
40
+ valid: j,
41
+ save: M,
42
+ order: 40
43
+ }), /* @__PURE__ */ g("div", {
14
44
  className: "grid gap-2",
15
45
  "data-ckui": "offer-fields",
16
- children: [S ? /* @__PURE__ */ m("p", {
46
+ children: [w ? /* @__PURE__ */ g("p", {
17
47
  className: "flex flex-wrap items-center gap-2",
18
48
  "data-ckui": "current-offer",
19
49
  children: [
20
- /* @__PURE__ */ p(n, {
50
+ /* @__PURE__ */ h(n, {
21
51
  variant: "secondary",
22
- children: b("contentEditor.onSale")
52
+ children: S("contentEditor.onSale")
23
53
  }),
24
- /* @__PURE__ */ p("span", { children: u(y, S.terms) }),
25
- S.synced === !1 && /* @__PURE__ */ p("span", {
54
+ /* @__PURE__ */ h("span", { children: f(x, w.terms) }),
55
+ w.synced === !1 && /* @__PURE__ */ h("span", {
26
56
  className: "text-xs text-muted-foreground",
27
- children: b("contentEditor.syncing")
57
+ children: S("contentEditor.syncing")
28
58
  }),
29
- /* @__PURE__ */ p(t, {
59
+ /* @__PURE__ */ h(t, {
30
60
  type: "button",
31
61
  size: "xs",
32
62
  variant: "ghost",
33
- disabled: v,
34
- onClick: _,
35
- children: b("contentEditor.withdraw")
63
+ disabled: b,
64
+ onClick: y,
65
+ children: S("contentEditor.withdraw")
36
66
  })
37
67
  ]
38
- }) : /* @__PURE__ */ p("p", {
68
+ }) : /* @__PURE__ */ h("p", {
39
69
  className: "text-muted-foreground",
40
- children: b("contentEditor.notForSale")
41
- }), /* @__PURE__ */ m("div", {
70
+ children: S("contentEditor.notForSale")
71
+ }), /* @__PURE__ */ g("div", {
42
72
  className: "flex flex-wrap items-end gap-2",
43
73
  children: [
44
- /* @__PURE__ */ m("div", {
74
+ /* @__PURE__ */ g("div", {
45
75
  className: "grid gap-1",
46
- children: [/* @__PURE__ */ p(i, {
47
- htmlFor: `${x}-amount`,
76
+ children: [/* @__PURE__ */ h(i, {
77
+ htmlFor: `${C}-amount`,
48
78
  className: "text-xs text-muted-foreground",
49
- children: b("contentEditor.price")
50
- }), /* @__PURE__ */ p(r, {
51
- id: `${x}-amount`,
79
+ children: S("contentEditor.price")
80
+ }), /* @__PURE__ */ h(r, {
81
+ id: `${C}-amount`,
52
82
  className: "h-8 w-28",
53
83
  inputMode: "decimal",
54
- value: C,
84
+ value: E,
55
85
  placeholder: "4.99",
56
- onChange: (e) => w(e.target.value)
86
+ onChange: (e) => A({ amount: e.target.value })
57
87
  })]
58
88
  }),
59
- /* @__PURE__ */ m("div", {
89
+ /* @__PURE__ */ g("div", {
60
90
  className: "grid gap-1",
61
- children: [/* @__PURE__ */ p(i, {
62
- htmlFor: `${x}-currency`,
91
+ children: [/* @__PURE__ */ h(i, {
92
+ htmlFor: `${C}-currency`,
63
93
  className: "text-xs text-muted-foreground",
64
- children: b("contentEditor.currency")
65
- }), /* @__PURE__ */ p(r, {
66
- id: `${x}-currency`,
94
+ children: S("contentEditor.currency")
95
+ }), /* @__PURE__ */ h(r, {
96
+ id: `${C}-currency`,
67
97
  className: "h-8 w-20 uppercase",
68
98
  maxLength: 3,
69
- value: T,
70
- onChange: (e) => E(e.target.value.toUpperCase())
99
+ value: D,
100
+ onChange: (e) => A({ currency: e.target.value.toUpperCase() })
71
101
  })]
72
102
  }),
73
- /* @__PURE__ */ m(a, {
74
- value: D,
75
- onValueChange: (e) => O(e),
76
- children: [/* @__PURE__ */ p(c, {
103
+ /* @__PURE__ */ g(a, {
104
+ value: O,
105
+ onValueChange: (e) => A({ mode: e }),
106
+ children: [/* @__PURE__ */ h(c, {
77
107
  size: "sm",
78
- "aria-label": b("contentEditor.priceMode"),
79
- children: /* @__PURE__ */ p(l, { children: (e) => b(`contentEditor.mode_${e}`) })
80
- }), /* @__PURE__ */ p(o, { children: [
108
+ "aria-label": S("contentEditor.priceMode"),
109
+ children: /* @__PURE__ */ h(l, { children: (e) => S(`contentEditor.mode_${e}`) })
110
+ }), /* @__PURE__ */ h(o, { children: [
81
111
  "once",
82
112
  "rent",
83
113
  "renew"
84
- ].map((e) => /* @__PURE__ */ p(s, {
114
+ ].map((e) => /* @__PURE__ */ h(s, {
85
115
  value: e,
86
- children: b(`contentEditor.mode_${e}`)
116
+ children: S(`contentEditor.mode_${e}`)
87
117
  }, e)) })]
88
118
  }),
89
- D !== "once" && /* @__PURE__ */ m("div", {
119
+ O !== "once" && /* @__PURE__ */ g("div", {
90
120
  className: "grid gap-1",
91
- children: [/* @__PURE__ */ p(i, {
92
- htmlFor: `${x}-days`,
121
+ children: [/* @__PURE__ */ h(i, {
122
+ htmlFor: `${C}-days`,
93
123
  className: "text-xs text-muted-foreground",
94
- children: b("contentEditor.days")
95
- }), /* @__PURE__ */ p(r, {
96
- id: `${x}-days`,
124
+ children: S("contentEditor.days")
125
+ }), /* @__PURE__ */ h(r, {
126
+ id: `${C}-days`,
97
127
  className: "h-8 w-20",
98
128
  type: "number",
99
129
  min: 1,
100
130
  value: k,
101
- onChange: (e) => A(e.target.value)
131
+ onChange: (e) => A({ days: e.target.value })
102
132
  })]
103
133
  }),
104
- /* @__PURE__ */ p(t, {
134
+ /* @__PURE__ */ h(t, {
105
135
  type: "button",
106
136
  size: "sm",
107
137
  variant: "outline",
108
- disabled: v || !/^\d+(\.\d{1,2})?$/.test(C.trim()) || T.length !== 3,
109
- onClick: () => {
110
- let e = Math.max(1, Math.trunc(Number(k) || 0)) * 24;
111
- g({
112
- amount: C.trim(),
113
- currency: T,
114
- access_hours: D === "rent" ? e : void 0,
115
- renew_hours: D === "renew" ? e : void 0
116
- });
117
- },
118
- children: b(S ? "contentEditor.reprice" : "contentEditor.sell")
138
+ disabled: b || !j,
139
+ onClick: () => void M(),
140
+ children: S(w ? "contentEditor.reprice" : "contentEditor.sell")
119
141
  })
120
142
  ]
121
143
  })]
122
144
  });
123
145
  }
124
146
  //#endregion
125
- export { h as OfferFields };
147
+ export { _ as OfferFields };
126
148
 
127
149
  //# sourceMappingURL=offer-fields.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"offer-fields.js","names":[],"sources":["../../../src/components/content/offer-fields.tsx"],"sourcesContent":["import { useId, useState } from \"react\";\nimport type { Offer, Terms } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { priceLabel } from \"./paywall.js\";\n\ntype Mode = \"once\" | \"rent\" | \"renew\";\n\n/** An offer's form: price, currency and whether it rents or renews; the current offer with its withdrawal. */\nexport function OfferFields({ offer, onSell, onWithdraw, pending }: { offer?: Pick<Offer, \"terms\"> & Partial<Pick<Offer, \"state\" | \"synced\">>; onSell: (t: Terms) => void; onWithdraw: () => void; pending: boolean }) {\n const m = useMessages();\n const { t } = m;\n const id = useId();\n const active = offer && (offer.state ?? \"active\") === \"active\" ? offer : undefined;\n const [amount, setAmount] = useState(active?.terms.amount ?? \"\");\n const [currency, setCurrency] = useState(active?.terms.currency ?? \"USD\");\n const [mode, setMode] = useState<Mode>(active?.terms.renew_hours ? \"renew\" : active?.terms.access_hours ? \"rent\" : \"once\");\n const [days, setDays] = useState(String(((active?.terms.renew_hours ?? active?.terms.access_hours ?? 720) / 24) | 0));\n return (\n <div className=\"grid gap-2\" data-ckui=\"offer-fields\">\n {active ? (\n <p className=\"flex flex-wrap items-center gap-2\" data-ckui=\"current-offer\">\n <Badge variant=\"secondary\">{t(\"contentEditor.onSale\")}</Badge>\n <span>{priceLabel(m, active.terms)}</span>\n {active.synced === false && <span className=\"text-xs text-muted-foreground\">{t(\"contentEditor.syncing\")}</span>}\n <Button type=\"button\" size=\"xs\" variant=\"ghost\" disabled={pending} onClick={onWithdraw}>\n {t(\"contentEditor.withdraw\")}\n </Button>\n </p>\n ) : (\n <p className=\"text-muted-foreground\">{t(\"contentEditor.notForSale\")}</p>\n )}\n <div className=\"flex flex-wrap items-end gap-2\">\n <div className=\"grid gap-1\">\n <Label htmlFor={`${id}-amount`} className=\"text-xs text-muted-foreground\">\n {t(\"contentEditor.price\")}\n </Label>\n <Input id={`${id}-amount`} className=\"h-8 w-28\" inputMode=\"decimal\" value={amount} placeholder=\"4.99\" onChange={(e) => setAmount(e.target.value)} />\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor={`${id}-currency`} className=\"text-xs text-muted-foreground\">\n {t(\"contentEditor.currency\")}\n </Label>\n <Input id={`${id}-currency`} className=\"h-8 w-20 uppercase\" maxLength={3} value={currency} onChange={(e) => setCurrency(e.target.value.toUpperCase())} />\n </div>\n <Select value={mode} onValueChange={(v) => setMode(v as Mode)}>\n <SelectTrigger size=\"sm\" aria-label={t(\"contentEditor.priceMode\")}>\n <SelectValue>{(v: string) => t(`contentEditor.mode_${v as Mode}`)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {([\"once\", \"rent\", \"renew\"] as const).map((x) => (\n <SelectItem key={x} value={x}>\n {t(`contentEditor.mode_${x}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {mode !== \"once\" && (\n <div className=\"grid gap-1\">\n <Label htmlFor={`${id}-days`} className=\"text-xs text-muted-foreground\">\n {t(\"contentEditor.days\")}\n </Label>\n <Input id={`${id}-days`} className=\"h-8 w-20\" type=\"number\" min={1} value={days} onChange={(e) => setDays(e.target.value)} />\n </div>\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={pending || !/^\\d+(\\.\\d{1,2})?$/.test(amount.trim()) || currency.length !== 3}\n onClick={() => {\n const hours = Math.max(1, Math.trunc(Number(days) || 0)) * 24;\n onSell({ amount: amount.trim(), currency, access_hours: mode === \"rent\" ? hours : undefined, renew_hours: mode === \"renew\" ? hours : undefined });\n }}\n >\n {t(active ? \"contentEditor.reprice\" : \"contentEditor.sell\")}\n </Button>\n </div>\n </div>\n );\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"offer-fields.js","names":[],"sources":["../../../src/components/content/offer-fields.tsx"],"sourcesContent":["import { useId, useMemo } from \"react\";\nimport type { Offer, Terms } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { useDraft } from \"../../react/use-draft.js\";\nimport { useEditorSaveTask } from \"./editor-save.js\";\nimport { priceLabel } from \"./paywall.js\";\n\ntype Mode = \"once\" | \"rent\" | \"renew\";\n\n/** An offer's form: price, currency and whether it rents or renews; the current offer with its withdrawal. */\nexport function OfferFields({ offer, onSell, onWithdraw, pending }: { offer?: Pick<Offer, \"terms\"> & Partial<Pick<Offer, \"state\" | \"synced\">>; onSell: (t: Terms) => unknown | Promise<unknown>; onWithdraw: () => void; pending: boolean }) {\n const m = useMessages();\n const { t } = m;\n const id = useId();\n const active = offer && (offer.state ?? \"active\") === \"active\" ? offer : undefined;\n const fields=useDraft(useMemo(()=>({amount:active?.terms.amount??\"\",currency:active?.terms.currency??\"USD\",mode:(active?.terms.renew_hours?\"renew\":active?.terms.access_hours?\"rent\":\"once\")as Mode,days:String(((active?.terms.renew_hours??active?.terms.access_hours??720)/24)|0)}),[active?.terms]));\n const {amount,currency,mode,days}=fields.value;\n const edit=(patch:Partial<typeof fields.value>)=>fields.edit((old)=>({...old,...patch}));\n const valid=/^\\d+(\\.\\d{1,2})?$/.test(amount.trim())&&currency.length===3&&(mode===\"once\"||Number(days)>0);\n const submit=async()=>{\n if(!valid)return false;\n const settle=fields.checkpoint();\n const hours=Math.max(1,Math.trunc(Number(days)||0))*24;\n const terms:Terms={amount:amount.trim(),currency,...(mode===\"rent\"?{access_hours:hours}:mode===\"renew\"?{renew_hours:hours}:{})};\n const out=await onSell(terms);\n if(!out)return false;\n return settle({amount:terms.amount,currency:terms.currency,mode,days});\n };\n useEditorSaveTask(`offer:${id}`,{label:t(\"contentEditor.price\"),dirty:fields.dirty.length>0,pending,valid,save:submit,order:40});\n return (\n <div className=\"grid gap-2\" data-ckui=\"offer-fields\">\n {active ? (\n <p className=\"flex flex-wrap items-center gap-2\" data-ckui=\"current-offer\">\n <Badge variant=\"secondary\">{t(\"contentEditor.onSale\")}</Badge>\n <span>{priceLabel(m, active.terms)}</span>\n {active.synced === false && <span className=\"text-xs text-muted-foreground\">{t(\"contentEditor.syncing\")}</span>}\n <Button type=\"button\" size=\"xs\" variant=\"ghost\" disabled={pending} onClick={onWithdraw}>\n {t(\"contentEditor.withdraw\")}\n </Button>\n </p>\n ) : (\n <p className=\"text-muted-foreground\">{t(\"contentEditor.notForSale\")}</p>\n )}\n <div className=\"flex flex-wrap items-end gap-2\">\n <div className=\"grid gap-1\">\n <Label htmlFor={`${id}-amount`} className=\"text-xs text-muted-foreground\">\n {t(\"contentEditor.price\")}\n </Label>\n <Input id={`${id}-amount`} className=\"h-8 w-28\" inputMode=\"decimal\" value={amount} placeholder=\"4.99\" onChange={(e) => edit({amount:e.target.value})} />\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor={`${id}-currency`} className=\"text-xs text-muted-foreground\">\n {t(\"contentEditor.currency\")}\n </Label>\n <Input id={`${id}-currency`} className=\"h-8 w-20 uppercase\" maxLength={3} value={currency} onChange={(e) => edit({currency:e.target.value.toUpperCase()})} />\n </div>\n <Select value={mode} onValueChange={(v) => edit({mode:v as Mode})}>\n <SelectTrigger size=\"sm\" aria-label={t(\"contentEditor.priceMode\")}>\n <SelectValue>{(v: string) => t(`contentEditor.mode_${v as Mode}`)}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {([\"once\", \"rent\", \"renew\"] as const).map((x) => (\n <SelectItem key={x} value={x}>\n {t(`contentEditor.mode_${x}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {mode !== \"once\" && (\n <div className=\"grid gap-1\">\n <Label htmlFor={`${id}-days`} className=\"text-xs text-muted-foreground\">\n {t(\"contentEditor.days\")}\n </Label>\n <Input id={`${id}-days`} className=\"h-8 w-20\" type=\"number\" min={1} value={days} onChange={(e) => edit({days:e.target.value})} />\n </div>\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={pending || !valid}\n onClick={()=>void submit()}\n >\n {t(active ? \"contentEditor.reprice\" : \"contentEditor.sell\")}\n </Button>\n </div>\n </div>\n );\n}\n\n"],"mappings":";;;;;;;;;;;;AAeA,SAAgB,EAAY,EAAE,UAAO,WAAQ,eAAY,cAAoL;CAC3O,IAAM,IAAI,EAAY,GAChB,EAAE,SAAM,GACR,IAAK,EAAM,GACX,IAAS,MAAU,EAAM,SAAS,cAAc,WAAW,IAAQ,KAAA,GACnE,IAAO,EAAS,SAAa;EAAC,QAAO,GAAQ,MAAM,UAAQ;EAAG,UAAS,GAAQ,MAAM,YAAU;EAAM,MAAM,GAAQ,MAAM,cAAY,UAAQ,GAAQ,MAAM,eAAa,SAAO;EAAe,MAAK,QAAS,GAAQ,MAAM,eAAa,GAAQ,MAAM,gBAAc,OAAK,KAAI,CAAC;CAAC,IAAG,CAAC,GAAQ,KAAK,CAAC,CAAC,GACjS,EAAC,WAAO,aAAS,SAAK,YAAM,EAAO,OACnC,KAAM,MAAqC,EAAO,MAAM,OAAO;EAAC,GAAG;EAAI,GAAG;CAAK,EAAE,GACjF,IAAM,oBAAoB,KAAK,EAAO,KAAK,CAAC,KAAG,EAAS,WAAS,MAAI,MAAO,UAAQ,OAAO,CAAI,IAAE,IACjG,IAAO,YAAS;EACpB,IAAG,CAAC,GAAM,OAAO;EACjB,IAAM,IAAO,EAAO,WAAW,GACzB,IAAM,KAAK,IAAI,GAAE,KAAK,MAAM,OAAO,CAAI,KAAG,CAAC,CAAC,IAAE,IAC9C,IAAY;GAAC,QAAO,EAAO,KAAK;GAAE;GAAS,GAAI,MAAO,SAAO,EAAC,cAAa,EAAK,IAAE,MAAO,UAAQ,EAAC,aAAY,EAAK,IAAE,CAAC;EAAE;EAG9H,OADI,MADY,EAAO,CAAK,IAErB,EAAO;GAAC,QAAO,EAAM;GAAO,UAAS,EAAM;GAAS;GAAK;EAAI,CAAC,IADtD;CAEjB;CAEA,OADA,EAAkB,SAAS,KAAK;EAAC,OAAM,EAAE,qBAAqB;EAAE,OAAM,EAAO,MAAM,SAAO;EAAE;EAAQ;EAAM,MAAK;EAAO,OAAM;CAAE,CAAC,GAE7H,kBAAC,OAAD;EAAK,WAAU;EAAa,aAAU;EAAtC,UAAA,CACG,IACC,kBAAC,KAAD;GAAG,WAAU;GAAoC,aAAU;GAA3D,UAAA;IACE,kBAAC,GAAD;KAAO,SAAQ;KAAa,UAAA,EAAE,sBAAsB;IAAS,CAAA;IAC7D,kBAAC,QAAD,EAAA,UAAO,EAAW,GAAG,EAAO,KAAK,EAAQ,CAAA;IACxC,EAAO,WAAW,MAAS,kBAAC,QAAD;KAAM,WAAU;KAAiC,UAAA,EAAE,uBAAuB;IAAQ,CAAA;IAC9G,kBAAC,GAAD;KAAQ,MAAK;KAAS,MAAK;KAAK,SAAQ;KAAQ,UAAU;KAAS,SAAS;KACzE,UAAA,EAAE,wBAAwB;IACrB,CAAA;GACP;EAEH,CAAA,IAAA,kBAAC,KAAD;GAAG,WAAU;GAAyB,UAAA,EAAE,0BAA0B;EAAK,CAAA,GAEzE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,GAAD;MAAO,SAAS,GAAG,EAAG;MAAU,WAAU;MACvC,UAAA,EAAE,qBAAqB;KACnB,CAAA,GACP,kBAAC,GAAD;MAAO,IAAI,GAAG,EAAG;MAAU,WAAU;MAAW,WAAU;MAAU,OAAO;MAAQ,aAAY;MAAO,WAAW,MAAM,EAAK,EAAC,QAAO,EAAE,OAAO,MAAK,CAAC;KAAI,CAAA,CACpJ;;IACL,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,GAAD;MAAO,SAAS,GAAG,EAAG;MAAY,WAAU;MACzC,UAAA,EAAE,wBAAwB;KACtB,CAAA,GACP,kBAAC,GAAD;MAAO,IAAI,GAAG,EAAG;MAAY,WAAU;MAAqB,WAAW;MAAG,OAAO;MAAU,WAAW,MAAM,EAAK,EAAC,UAAS,EAAE,OAAO,MAAM,YAAY,EAAC,CAAC;KAAI,CAAA,CACzJ;;IACL,kBAAC,GAAD;KAAQ,OAAO;KAAM,gBAAgB,MAAM,EAAK,EAAC,MAAK,EAAS,CAAC;KAAhE,UAAA,CACE,kBAAC,GAAD;MAAe,MAAK;MAAK,cAAY,EAAE,yBAAyB;MAC9D,UAAA,kBAAC,GAAD,EAAA,WAAe,MAAc,EAAE,sBAAsB,GAAW,EAAe,CAAA;KAClE,CAAA,GACf,kBAAC,GAAD,EAAA,UACI;MAAC;MAAQ;MAAQ;KAAO,CAAC,CAAW,KAAK,MACzC,kBAAC,GAAD;MAAoB,OAAO;MACxB,UAAA,EAAE,sBAAsB,GAAG;KAClB,GAFK,CAEL,CACb,EACY,CAAA,CACT;;IACP,MAAS,UACR,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,GAAD;MAAO,SAAS,GAAG,EAAG;MAAQ,WAAU;MACrC,UAAA,EAAE,oBAAoB;KAClB,CAAA,GACP,kBAAC,GAAD;MAAO,IAAI,GAAG,EAAG;MAAQ,WAAU;MAAW,MAAK;MAAS,KAAK;MAAG,OAAO;MAAM,WAAW,MAAM,EAAK,EAAC,MAAK,EAAE,OAAO,MAAK,CAAC;KAAI,CAAA,CAC7H;;IAEP,kBAAC,GAAD;KACE,MAAK;KACL,MAAK;KACL,SAAQ;KACR,UAAU,KAAW,CAAC;KACtB,eAAa,KAAK,EAAO;KAExB,UAAA,EAAE,IAAS,0BAA0B,oBAAoB;IACpD,CAAA;GACL;EACF,CAAA,CAAA;;AAET"}
@@ -1,6 +1,7 @@
1
1
  import { ContentKitUiAppearance } from '../../appearance.js';
2
2
  import { ContentKitClient } from '../../client/client.js';
3
- import { Card, GroupCard } from '../../client/generated/wire.js';
3
+ import { TypeaheadQuery } from '../../client/discovery.js';
4
+ import { Card, GroupCard, Typeahead } from '../../client/generated/wire.js';
4
5
  import { Labels } from '../groups/parts.js';
5
6
  export interface SearchBoxProps {
6
7
  /** Submits the text: the host shows its search page. */
@@ -14,8 +15,13 @@ export interface SearchBoxProps {
14
15
  /** A suggestion's link; default its canonical path. */
15
16
  href?: (item: Card | GroupCard) => string | undefined;
16
17
  labels?: Labels;
17
- /** Recent searches first, prefix-filtered while typing. Default true. */
18
+ /** Matching recent searches first, with prefixes before interior matches. Default true. */
18
19
  recent?: boolean;
20
+ /** An authenticated or host-specific suggestion source; public typeahead by default. */
21
+ requestSuggestions?: (query: TypeaheadQuery, signal: AbortSignal) => Promise<Typeahead>;
22
+ /** Enter opens an explicitly selected suggestion; free text never submits a results search. */
23
+ selectionOnly?: boolean;
24
+ disabled?: boolean;
19
25
  /** A portal dropdown anchored to the header field; default stays inline. */
20
26
  presentation?: "inline" | "header";
21
27
  client?: ContentKitClient;
@@ -23,7 +29,8 @@ export interface SearchBoxProps {
23
29
  appearance?: ContentKitUiAppearance;
24
30
  }
25
31
  /**
26
- * Recent query prefixes first, followed by entity and title prefixes. Enter
27
- * searches, or opens the chosen suggestion; full search can match more deeply.
32
+ * Matching recent queries first, followed by entity/title substrings. Prefixes
33
+ * are preferred. Enter searches or opens the chosen suggestion; full search can
34
+ * match contextual fields more deeply.
28
35
  */
29
36
  export declare function SearchBox(p: SearchBoxProps): import('react').JSX.Element;