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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/README.md +111 -49
  2. package/dist/client/beacons.d.ts +3 -13
  3. package/dist/client/beacons.js +8 -46
  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/contents.d.ts +7 -1
  13. package/dist/client/contents.js +8 -0
  14. package/dist/client/contents.js.map +1 -1
  15. package/dist/client/discovery.d.ts +34 -5
  16. package/dist/client/discovery.js +23 -2
  17. package/dist/client/discovery.js.map +1 -1
  18. package/dist/client/engagement.d.ts +70 -0
  19. package/dist/client/engagement.js +191 -0
  20. package/dist/client/engagement.js.map +1 -0
  21. package/dist/client/generated/routes.d.ts +74 -35
  22. package/dist/client/generated/routes.js +119 -60
  23. package/dist/client/generated/routes.js.map +1 -1
  24. package/dist/client/generated/wire.d.ts +88 -85
  25. package/dist/client/generated/wire.js.map +1 -1
  26. package/dist/client/guest-reading-history.d.ts +40 -0
  27. package/dist/client/guest-reading-history.js +144 -0
  28. package/dist/client/guest-reading-history.js.map +1 -0
  29. package/dist/client/index.d.ts +5 -3
  30. package/dist/client/media/windows.d.ts +2 -0
  31. package/dist/client/media/windows.js +4 -1
  32. package/dist/client/media/windows.js.map +1 -1
  33. package/dist/client/stats.d.ts +4 -20
  34. package/dist/client/stats.js +0 -11
  35. package/dist/client/stats.js.map +1 -1
  36. package/dist/client/versions.d.ts +8 -6
  37. package/dist/client/versions.js +15 -10
  38. package/dist/client/versions.js.map +1 -1
  39. package/dist/client.js +24 -22
  40. package/dist/components/admin/bulk-drafts.d.ts +17 -0
  41. package/dist/components/admin/bulk-drafts.js +41 -0
  42. package/dist/components/admin/bulk-drafts.js.map +1 -0
  43. package/dist/components/admin/content-admin.d.ts +1 -1
  44. package/dist/components/admin/content-admin.js.map +1 -1
  45. package/dist/components/admin/content-manager.d.ts +11 -3
  46. package/dist/components/admin/content-manager.js +751 -394
  47. package/dist/components/admin/content-manager.js.map +1 -1
  48. package/dist/components/admin/group-manager.d.ts +8 -3
  49. package/dist/components/admin/group-manager.js +218 -146
  50. package/dist/components/admin/group-manager.js.map +1 -1
  51. package/dist/components/admin/page-notices.d.ts +16 -0
  52. package/dist/components/admin/page-notices.js +113 -0
  53. package/dist/components/admin/page-notices.js.map +1 -0
  54. package/dist/components/admin/page-picker.d.ts +38 -0
  55. package/dist/components/admin/page-picker.js +159 -0
  56. package/dist/components/admin/page-picker.js.map +1 -0
  57. package/dist/components/admin/quick-search.d.ts +14 -0
  58. package/dist/components/admin/quick-search.js +60 -0
  59. package/dist/components/admin/quick-search.js.map +1 -0
  60. package/dist/components/admin/reader-content-editor.js +382 -321
  61. package/dist/components/admin/reader-content-editor.js.map +1 -1
  62. package/dist/components/admin/reader-manifest.js +181 -179
  63. package/dist/components/admin/reader-manifest.js.map +1 -1
  64. package/dist/components/admin/shell.d.ts +10 -2
  65. package/dist/components/admin/shell.js +114 -42
  66. package/dist/components/admin/shell.js.map +1 -1
  67. package/dist/components/admin/stats-dashboard.d.ts +4 -9
  68. package/dist/components/admin/stats-dashboard.js +303 -207
  69. package/dist/components/admin/stats-dashboard.js.map +1 -1
  70. package/dist/components/admin/upload-queue.d.ts +14 -0
  71. package/dist/components/admin/upload-queue.js +115 -98
  72. package/dist/components/admin/upload-queue.js.map +1 -1
  73. package/dist/components/content/card.d.ts +31 -3
  74. package/dist/components/content/card.js +324 -158
  75. package/dist/components/content/card.js.map +1 -1
  76. package/dist/components/content/daily-releases.js +144 -190
  77. package/dist/components/content/daily-releases.js.map +1 -1
  78. package/dist/components/content/editor-save.js +27 -25
  79. package/dist/components/content/editor-save.js.map +1 -1
  80. package/dist/components/content/editors.js +274 -253
  81. package/dist/components/content/editors.js.map +1 -1
  82. package/dist/components/content/favorites.d.ts +6 -0
  83. package/dist/components/content/favorites.js +47 -0
  84. package/dist/components/content/favorites.js.map +1 -0
  85. package/dist/components/content/filter-dialog.js +19 -15
  86. package/dist/components/content/filter-dialog.js.map +1 -1
  87. package/dist/components/content/filters.d.ts +2 -0
  88. package/dist/components/content/filters.js +5 -1
  89. package/dist/components/content/filters.js.map +1 -1
  90. package/dist/components/content/grid.d.ts +44 -16
  91. package/dist/components/content/grid.js +308 -270
  92. package/dist/components/content/grid.js.map +1 -1
  93. package/dist/components/content/history.d.ts +6 -0
  94. package/dist/components/content/history.js +111 -0
  95. package/dist/components/content/history.js.map +1 -0
  96. package/dist/components/content/index.d.ts +6 -3
  97. package/dist/components/content/listing.d.ts +14 -3
  98. package/dist/components/content/listing.js +65 -56
  99. package/dist/components/content/listing.js.map +1 -1
  100. package/dist/components/content/offer-fields.js +6 -6
  101. package/dist/components/content/paged-grid.d.ts +10 -0
  102. package/dist/components/content/paged-grid.js +116 -0
  103. package/dist/components/content/paged-grid.js.map +1 -0
  104. package/dist/components/content/pagination.d.ts +19 -0
  105. package/dist/components/content/pagination.js +97 -0
  106. package/dist/components/content/pagination.js.map +1 -0
  107. package/dist/components/content/paywall.d.ts +9 -0
  108. package/dist/components/content/paywall.js +27 -3
  109. package/dist/components/content/paywall.js.map +1 -1
  110. package/dist/components/content/post-list.d.ts +37 -0
  111. package/dist/components/content/post-list.js +203 -0
  112. package/dist/components/content/post-list.js.map +1 -0
  113. package/dist/components/content/reader-settings.d.ts +2 -0
  114. package/dist/components/content/reader-settings.js +1 -1
  115. package/dist/components/content/reader-settings.js.map +1 -1
  116. package/dist/components/content/search.d.ts +11 -4
  117. package/dist/components/content/search.js +22 -11
  118. package/dist/components/content/search.js.map +1 -1
  119. package/dist/components/content/summary-tile.d.ts +14 -0
  120. package/dist/components/content/summary-tile.js +63 -0
  121. package/dist/components/content/summary-tile.js.map +1 -0
  122. package/dist/components/groups/banner.d.ts +6 -2
  123. package/dist/components/groups/banner.js +79 -61
  124. package/dist/components/groups/banner.js.map +1 -1
  125. package/dist/components/groups/directory.d.ts +12 -2
  126. package/dist/components/groups/directory.js +168 -105
  127. package/dist/components/groups/directory.js.map +1 -1
  128. package/dist/components/groups/editor.js +10 -10
  129. package/dist/components/groups/editor.js.map +1 -1
  130. package/dist/components/groups/entity-chips.d.ts +61 -0
  131. package/dist/components/groups/entity-chips.js +382 -0
  132. package/dist/components/groups/entity-chips.js.map +1 -0
  133. package/dist/components/groups/group-members.d.ts +29 -0
  134. package/dist/components/groups/group-members.js +474 -0
  135. package/dist/components/groups/group-members.js.map +1 -0
  136. package/dist/components/groups/header.js +53 -45
  137. package/dist/components/groups/header.js.map +1 -1
  138. package/dist/components/groups/index.d.ts +3 -1
  139. package/dist/components/groups/memberships.d.ts +3 -36
  140. package/dist/components/groups/memberships.js +105 -480
  141. package/dist/components/groups/memberships.js.map +1 -1
  142. package/dist/components/groups/scoped.d.ts +23 -0
  143. package/dist/components/groups/scoped.js +92 -0
  144. package/dist/components/groups/scoped.js.map +1 -0
  145. package/dist/components/media-folder-editor.d.ts +5 -0
  146. package/dist/components/media-folder-editor.js +419 -231
  147. package/dist/components/media-folder-editor.js.map +1 -1
  148. package/dist/components/sortable-list.d.ts +5 -1
  149. package/dist/components/sortable-list.js +97 -81
  150. package/dist/components/sortable-list.js.map +1 -1
  151. package/dist/components/video-mini-player.d.ts +4 -1
  152. package/dist/components/video-mini-player.js +77 -71
  153. package/dist/components/video-mini-player.js.map +1 -1
  154. package/dist/components/video-player.d.ts +4 -1
  155. package/dist/components/video-player.js +280 -276
  156. package/dist/components/video-player.js.map +1 -1
  157. package/dist/index.d.ts +2 -1
  158. package/dist/index.js +41 -34
  159. package/dist/locales/admin/de.js +80 -47
  160. package/dist/locales/admin/de.js.map +1 -1
  161. package/dist/locales/admin/en.d.ts +74 -41
  162. package/dist/locales/admin/en.js +80 -47
  163. package/dist/locales/admin/en.js.map +1 -1
  164. package/dist/locales/admin/es.js +80 -47
  165. package/dist/locales/admin/es.js.map +1 -1
  166. package/dist/locales/admin/ja.js +80 -47
  167. package/dist/locales/admin/ja.js.map +1 -1
  168. package/dist/locales/admin/ko.js +80 -47
  169. package/dist/locales/admin/ko.js.map +1 -1
  170. package/dist/locales/admin/zh.js +80 -47
  171. package/dist/locales/admin/zh.js.map +1 -1
  172. package/dist/locales/de.js +1 -0
  173. package/dist/locales/de.js.map +1 -1
  174. package/dist/locales/discovery/de.js +25 -2
  175. package/dist/locales/discovery/de.js.map +1 -1
  176. package/dist/locales/discovery/en.d.ts +24 -1
  177. package/dist/locales/discovery/en.js +25 -2
  178. package/dist/locales/discovery/en.js.map +1 -1
  179. package/dist/locales/discovery/es.js +25 -2
  180. package/dist/locales/discovery/es.js.map +1 -1
  181. package/dist/locales/discovery/ja.js +25 -2
  182. package/dist/locales/discovery/ja.js.map +1 -1
  183. package/dist/locales/discovery/ko.js +25 -2
  184. package/dist/locales/discovery/ko.js.map +1 -1
  185. package/dist/locales/discovery/zh.js +25 -2
  186. package/dist/locales/discovery/zh.js.map +1 -1
  187. package/dist/locales/en.d.ts +119 -44
  188. package/dist/locales/en.js +1 -0
  189. package/dist/locales/en.js.map +1 -1
  190. package/dist/locales/es.js +1 -0
  191. package/dist/locales/es.js.map +1 -1
  192. package/dist/locales/groups/de.js +22 -4
  193. package/dist/locales/groups/de.js.map +1 -1
  194. package/dist/locales/groups/en.d.ts +20 -2
  195. package/dist/locales/groups/en.js +22 -4
  196. package/dist/locales/groups/en.js.map +1 -1
  197. package/dist/locales/groups/es.js +22 -4
  198. package/dist/locales/groups/es.js.map +1 -1
  199. package/dist/locales/groups/ja.js +22 -4
  200. package/dist/locales/groups/ja.js.map +1 -1
  201. package/dist/locales/groups/ko.js +22 -4
  202. package/dist/locales/groups/ko.js.map +1 -1
  203. package/dist/locales/groups/zh.js +22 -4
  204. package/dist/locales/groups/zh.js.map +1 -1
  205. package/dist/locales/ja.js +1 -0
  206. package/dist/locales/ja.js.map +1 -1
  207. package/dist/locales/ko.js +1 -0
  208. package/dist/locales/ko.js.map +1 -1
  209. package/dist/locales/zh.js +1 -0
  210. package/dist/locales/zh.js.map +1 -1
  211. package/dist/react/discovery.d.ts +36 -17
  212. package/dist/react/discovery.js +305 -221
  213. package/dist/react/discovery.js.map +1 -1
  214. package/dist/react/groups.d.ts +19 -3
  215. package/dist/react/groups.js +49 -22
  216. package/dist/react/groups.js.map +1 -1
  217. package/dist/react/index.d.ts +7 -4
  218. package/dist/react/page-notices.d.ts +13 -0
  219. package/dist/react/page-notices.js +38 -0
  220. package/dist/react/page-notices.js.map +1 -0
  221. package/dist/react/post-reads.d.ts +23 -0
  222. package/dist/react/post-reads.js +90 -0
  223. package/dist/react/post-reads.js.map +1 -0
  224. package/dist/react/remembered-sort.d.ts +5 -0
  225. package/dist/react/remembered-sort.js +29 -0
  226. package/dist/react/remembered-sort.js.map +1 -0
  227. package/dist/react/resources.d.ts +3 -1
  228. package/dist/react/resources.js +6 -2
  229. package/dist/react/resources.js.map +1 -1
  230. package/dist/react/stats.d.ts +3 -7
  231. package/dist/react/stats.js +2 -6
  232. package/dist/react/stats.js.map +1 -1
  233. package/dist/react/use-engagement.d.ts +28 -0
  234. package/dist/react/use-engagement.js +226 -0
  235. package/dist/react/use-engagement.js.map +1 -0
  236. package/dist/react/use-resource.d.ts +4 -3
  237. package/dist/react/use-resource.js +2 -1
  238. package/dist/react/use-resource.js.map +1 -1
  239. package/dist/react.js +29 -26
  240. package/dist/styles.css +1 -1
  241. package/dist/ui/dialog.d.ts +2 -1
  242. package/dist/ui/dialog.js +23 -20
  243. package/dist/ui/dialog.js.map +1 -1
  244. package/dist/urls/index.d.ts +1 -1
  245. package/dist/urls.js +2 -2
  246. package/dist/urls.js.map +1 -1
  247. package/package.json +1 -1
  248. package/dist/components/admin/page-review.d.ts +0 -13
  249. package/dist/components/admin/page-review.js +0 -225
  250. package/dist/components/admin/page-review.js.map +0 -1
  251. package/dist/react/page-review.d.ts +0 -14
  252. package/dist/react/page-review.js +0 -37
  253. package/dist/react/page-review.js.map +0 -1
@@ -0,0 +1,203 @@
1
+ import { RenditionImg as e } from "../rendition-img.js";
2
+ import { useMessages as t } from "../../i18n/context.js";
3
+ import { ContentKitUiRoot as n } from "../../scope.js";
4
+ import { Button as r } from "../../ui/button.js";
5
+ import { Skeleton as i } from "../../ui/skeleton.js";
6
+ import { RelativeTime as a } from "../social/parts.js";
7
+ import { useContents as o } from "../../react/discovery.js";
8
+ import { lockLabel as s } from "./card.js";
9
+ import { usePostReads as c } from "../../react/post-reads.js";
10
+ import { useMemo as l } from "react";
11
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
12
+ import { cn as f } from "cn";
13
+ import { LockIcon as p } from "@hugeicons/core-free-icons";
14
+ import { HugeiconsIcon as m } from "@hugeicons/react";
15
+ //#region src/components/content/post-list.tsx
16
+ function h({ kind: e, language: a, href: p, pageSize: m = 10, trending: h = "30d", sidebarSize: v = 5, excerptLength: y = 220, unreadDays: b = 30, tiers: x, labels: S, empty: C, client: w, appearance: T, className: E }) {
17
+ let D = t(), { t: O } = D, k = o(l(() => ({
18
+ kind: e,
19
+ sort: "newest",
20
+ limit: m,
21
+ display_language: a
22
+ }), [
23
+ e,
24
+ m,
25
+ a
26
+ ]), { client: w }), A = c(b), j = (e) => p ? p(e) : e.path, M = v > 0;
27
+ return /* @__PURE__ */ d(n, {
28
+ appearance: T,
29
+ className: f("grid gap-6 text-sm", M && "lg:grid-cols-[minmax(0,1fr)_18rem]", E),
30
+ "data-ckui": "post-list",
31
+ children: [/* @__PURE__ */ d("div", {
32
+ className: "grid min-w-0 content-start gap-3",
33
+ children: [k.loading ? /* @__PURE__ */ u("ol", {
34
+ className: "grid divide-y divide-border",
35
+ "aria-busy": !0,
36
+ children: Array.from({ length: Math.min(m, 4) }, (e, t) => /* @__PURE__ */ d("li", {
37
+ className: "grid gap-4 py-4 sm:grid-cols-[14rem_minmax(0,1fr)]",
38
+ children: [/* @__PURE__ */ u(i, { className: "aspect-[331/232] w-full rounded-lg" }), /* @__PURE__ */ d("div", {
39
+ className: "grid content-start gap-2",
40
+ children: [
41
+ /* @__PURE__ */ u(i, { className: "h-5 w-3/4" }),
42
+ /* @__PURE__ */ u(i, { className: "h-3 w-24" }),
43
+ /* @__PURE__ */ u(i, { className: "h-3 w-full" }),
44
+ /* @__PURE__ */ u(i, { className: "h-3 w-5/6" })
45
+ ]
46
+ })]
47
+ }, t))
48
+ }) : k.items.length === 0 ? /* @__PURE__ */ u("p", {
49
+ className: "text-muted-foreground",
50
+ children: C ?? O("contents.empty")
51
+ }) : /* @__PURE__ */ u("ol", {
52
+ className: "grid divide-y divide-border",
53
+ children: k.items.map((e) => /* @__PURE__ */ u(g, {
54
+ card: e,
55
+ url: j(e),
56
+ unread: A.unread(e.id, e.published_at, e.viewer?.seen || e.viewer?.completed),
57
+ excerptLength: y,
58
+ lock: s(D, e.access, x, S)
59
+ }, e.id))
60
+ }), k.hasMore && /* @__PURE__ */ u(r, {
61
+ variant: "outline",
62
+ className: "justify-self-center",
63
+ disabled: k.loadingMore,
64
+ onClick: () => void k.loadMore(),
65
+ children: k.loadingMore ? O("contents.loading") : O("contents.loadMore")
66
+ })]
67
+ }), M && /* @__PURE__ */ d("aside", {
68
+ className: "grid content-start gap-6",
69
+ "data-ckui": "post-list-sidebar",
70
+ children: [/* @__PURE__ */ u(_, {
71
+ title: O("postList.newest"),
72
+ kind: e,
73
+ sort: "newest",
74
+ size: v,
75
+ language: a,
76
+ url: j,
77
+ client: w
78
+ }), h && /* @__PURE__ */ u(_, {
79
+ title: O("postList.trending"),
80
+ kind: e,
81
+ sort: `popular:${h}`,
82
+ size: v,
83
+ language: a,
84
+ url: j,
85
+ client: w
86
+ })]
87
+ })]
88
+ });
89
+ }
90
+ function g({ card: n, url: r, unread: i, lock: o, excerptLength: s }) {
91
+ let { t: c } = t(), l = v(n.excerpt ?? "", s);
92
+ return /* @__PURE__ */ d("li", {
93
+ className: "grid gap-4 py-4 sm:grid-cols-[14rem_minmax(0,1fr)]",
94
+ "data-ckui": "post-row",
95
+ "data-id": n.id,
96
+ "data-unread": i || void 0,
97
+ lang: n.language || void 0,
98
+ children: [n.cover.length > 0 && /* @__PURE__ */ u("a", {
99
+ href: r,
100
+ className: "relative block aspect-[331/232] overflow-hidden rounded-lg bg-muted",
101
+ tabIndex: -1,
102
+ "aria-hidden": !0,
103
+ children: /* @__PURE__ */ u(e, {
104
+ outputs: n.cover,
105
+ alt: "",
106
+ fit: "cover",
107
+ skeleton: !0,
108
+ loading: "lazy",
109
+ className: f("size-full", o && "blur-md")
110
+ })
111
+ }), /* @__PURE__ */ d("div", {
112
+ className: f("grid min-w-0 content-start gap-1.5", n.cover.length === 0 && "sm:col-span-2"),
113
+ children: [
114
+ /* @__PURE__ */ d("a", {
115
+ href: r,
116
+ className: "flex items-start gap-2 text-lg leading-snug font-semibold break-words hover:underline",
117
+ children: [i && /* @__PURE__ */ u("span", {
118
+ className: "mt-2 size-2 shrink-0 rounded-full bg-primary",
119
+ "data-ckui": "post-unread",
120
+ children: /* @__PURE__ */ u("span", {
121
+ className: "sr-only",
122
+ children: c("postList.unread")
123
+ })
124
+ }), /* @__PURE__ */ u("span", { children: n.title || n.code })]
125
+ }),
126
+ /* @__PURE__ */ d("p", {
127
+ className: "flex flex-wrap items-center gap-3 text-xs text-muted-foreground",
128
+ children: [n.published_at && /* @__PURE__ */ u(a, { at: n.published_at }), o && /* @__PURE__ */ d("span", {
129
+ className: "inline-flex items-center gap-1",
130
+ "data-ckui": "card-lock",
131
+ children: [/* @__PURE__ */ u(m, {
132
+ icon: p,
133
+ strokeWidth: 2,
134
+ className: "size-3.5",
135
+ "aria-hidden": !0
136
+ }), o]
137
+ })]
138
+ }),
139
+ l && /* @__PURE__ */ u("p", {
140
+ className: "line-clamp-3 leading-relaxed text-muted-foreground",
141
+ children: l
142
+ })
143
+ ]
144
+ })]
145
+ });
146
+ }
147
+ function _({ title: t, kind: n, sort: r, size: s, language: c, url: p, client: m }) {
148
+ let h = o(l(() => ({
149
+ kind: n,
150
+ sort: r,
151
+ limit: s,
152
+ display_language: c
153
+ }), [
154
+ n,
155
+ r,
156
+ s,
157
+ c
158
+ ]), { client: m });
159
+ return !h.loading && h.items.length === 0 ? null : /* @__PURE__ */ d("section", {
160
+ className: "grid content-start gap-3",
161
+ "aria-label": t,
162
+ children: [/* @__PURE__ */ u("h2", {
163
+ className: "text-base font-semibold",
164
+ children: t
165
+ }), /* @__PURE__ */ u("ol", {
166
+ className: "grid gap-3",
167
+ children: h.loading ? Array.from({ length: Math.min(s, 3) }, (e, t) => /* @__PURE__ */ u(i, { className: "h-14 w-full" }, t)) : h.items.slice(0, s).map((t) => /* @__PURE__ */ u("li", { children: /* @__PURE__ */ d("a", {
168
+ href: p(t),
169
+ className: f("grid items-center gap-3 hover:underline", t.cover.length > 0 && "grid-cols-[5rem_minmax(0,1fr)]"),
170
+ children: [t.cover.length > 0 && /* @__PURE__ */ u("span", {
171
+ className: "block aspect-[3/2] overflow-hidden rounded-md bg-muted",
172
+ children: /* @__PURE__ */ u(e, {
173
+ outputs: t.cover,
174
+ alt: "",
175
+ fit: "cover",
176
+ skeleton: !0,
177
+ loading: "lazy",
178
+ className: f("size-full", !t.access.allowed && "blur-sm")
179
+ })
180
+ }), /* @__PURE__ */ d("span", {
181
+ className: "grid min-w-0 gap-0.5",
182
+ children: [/* @__PURE__ */ u("span", {
183
+ className: "line-clamp-2 font-medium break-words",
184
+ children: t.title || t.code
185
+ }), t.published_at && /* @__PURE__ */ u(a, {
186
+ at: t.published_at,
187
+ className: "text-xs text-muted-foreground"
188
+ })]
189
+ })]
190
+ }) }, t.id))
191
+ })]
192
+ });
193
+ }
194
+ function v(e, t) {
195
+ let n = e.replace(/\s+/g, " ").trim();
196
+ if (n.length <= t) return n;
197
+ let r = n.slice(0, t + 1), i = r.lastIndexOf(" ");
198
+ return `${(i > t * .6 ? r.slice(0, i) : n.slice(0, t)).replace(/[\s.,;:!?-]+$/, "")}…`;
199
+ }
200
+ //#endregion
201
+ export { h as PostList, v as excerpt };
202
+
203
+ //# sourceMappingURL=post-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-list.js","names":[],"sources":["../../../src/components/content/post-list.tsx"],"sourcesContent":["import { HugeiconsIcon } from \"@hugeicons/react\";\nimport { LockIcon } from \"@hugeicons/core-free-icons\";\nimport { cn } from \"cn\";\nimport { useMemo, type ReactNode } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Period } from \"../../client/discovery.js\";\nimport type { Card } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useContents } from \"../../react/discovery.js\";\nimport { usePostReads } from \"../../react/post-reads.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { RelativeTime } from \"../social/parts.js\";\nimport { lockLabel, type PostCardProps } from \"./card.js\";\n\nexport interface PostListProps {\n /** The post kind. */\n kind: string;\n /** Display language for titles, excerpts and paths; default the provider's. */\n language?: string;\n href?: (card: Card) => string;\n /** Posts per page; default 10. */\n pageSize?: number;\n /** Trending window for the sidebar, or false to leave it out; default 30d. */\n trending?: Period | false;\n /** Posts per sidebar section; default 5, 0 hides the sidebar. */\n sidebarSize?: number;\n /** Excerpt length in characters; default 220. */\n excerptLength?: number;\n /** Signed in, posts unread within this many days are marked; default 30. */\n unreadDays?: number;\n tiers?: readonly string[];\n labels?: PostCardProps[\"labels\"];\n empty?: ReactNode;\n client?: ContentKitClient;\n appearance?: ContentKitUiAppearance;\n className?: string;\n}\n\n/**\n * Posts newest first with cover, date, excerpt and an unread mark, beside a\n * Newest/Trending sidebar. Trending is the kind's popular window; it never\n * counts views.\n */\nexport function PostList({ kind, language, href, pageSize = 10, trending = \"30d\", sidebarSize = 5, excerptLength = 220, unreadDays = 30, tiers, labels, empty, client, appearance, className }: PostListProps) {\n const m = useMessages();\n const { t } = m;\n const list = useContents(useMemo(() => ({ kind, sort: \"newest\" as const, limit: pageSize, display_language: language }), [kind, pageSize, language]), { client });\n const reads = usePostReads(unreadDays);\n const url = (card: Card) => (href ? href(card) : card.path);\n const sidebar = sidebarSize > 0;\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-6 text-sm\", sidebar && \"lg:grid-cols-[minmax(0,1fr)_18rem]\", className)} data-ckui=\"post-list\">\n <div className=\"grid min-w-0 content-start gap-3\">\n {list.loading ? (\n <ol className=\"grid divide-y divide-border\" aria-busy>\n {Array.from({ length: Math.min(pageSize, 4) }, (_, i) => (\n <li key={i} className=\"grid gap-4 py-4 sm:grid-cols-[14rem_minmax(0,1fr)]\">\n <Skeleton className=\"aspect-[331/232] w-full rounded-lg\" />\n <div className=\"grid content-start gap-2\">\n <Skeleton className=\"h-5 w-3/4\" />\n <Skeleton className=\"h-3 w-24\" />\n <Skeleton className=\"h-3 w-full\" />\n <Skeleton className=\"h-3 w-5/6\" />\n </div>\n </li>\n ))}\n </ol>\n ) : list.items.length === 0 ? (\n <p className=\"text-muted-foreground\">{empty ?? t(\"contents.empty\")}</p>\n ) : (\n <ol className=\"grid divide-y divide-border\">\n {list.items.map((card) => (\n <PostRow\n key={card.id}\n card={card}\n url={url(card)}\n unread={reads.unread(card.id, card.published_at, card.viewer?.seen || card.viewer?.completed)}\n excerptLength={excerptLength}\n lock={lockLabel(m, card.access, tiers, labels)}\n />\n ))}\n </ol>\n )}\n {list.hasMore && (\n <Button variant=\"outline\" className=\"justify-self-center\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {list.loadingMore ? t(\"contents.loading\") : t(\"contents.loadMore\")}\n </Button>\n )}\n </div>\n {sidebar && (\n <aside className=\"grid content-start gap-6\" data-ckui=\"post-list-sidebar\">\n <SidebarSection title={t(\"postList.newest\")} kind={kind} sort=\"newest\" size={sidebarSize} language={language} url={url} client={client} />\n {trending && <SidebarSection title={t(\"postList.trending\")} kind={kind} sort={`popular:${trending}`} size={sidebarSize} language={language} url={url} client={client} />}\n </aside>\n )}\n </ContentKitUiRoot>\n );\n}\n\nfunction PostRow({ card, url, unread, lock, excerptLength }: { card: Card; url: string; unread: boolean; lock: string | null; excerptLength: number }) {\n const { t } = useMessages();\n const text = excerpt(card.excerpt ?? \"\", excerptLength);\n return (\n <li className=\"grid gap-4 py-4 sm:grid-cols-[14rem_minmax(0,1fr)]\" data-ckui=\"post-row\" data-id={card.id} data-unread={unread || undefined} lang={card.language || undefined}>\n {card.cover.length > 0 && (\n <a href={url} className=\"relative block aspect-[331/232] overflow-hidden rounded-lg bg-muted\" tabIndex={-1} aria-hidden>\n <RenditionImg outputs={card.cover} alt=\"\" fit=\"cover\" skeleton loading=\"lazy\" className={cn(\"size-full\", lock && \"blur-md\")} />\n </a>\n )}\n <div className={cn(\"grid min-w-0 content-start gap-1.5\", card.cover.length === 0 && \"sm:col-span-2\")}>\n <a href={url} className=\"flex items-start gap-2 text-lg leading-snug font-semibold break-words hover:underline\">\n {unread && (\n <span className=\"mt-2 size-2 shrink-0 rounded-full bg-primary\" data-ckui=\"post-unread\">\n <span className=\"sr-only\">{t(\"postList.unread\")}</span>\n </span>\n )}\n <span>{card.title || card.code}</span>\n </a>\n <p className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {card.published_at && <RelativeTime at={card.published_at} />}\n {lock && (\n <span className=\"inline-flex items-center gap-1\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n )}\n </p>\n {text && <p className=\"line-clamp-3 leading-relaxed text-muted-foreground\">{text}</p>}\n </div>\n </li>\n );\n}\n\nfunction SidebarSection({ title, kind, sort, size, language, url, client }: { title: string; kind: string; sort: \"newest\" | `popular:${Period}`; size: number; language?: string; url: (card: Card) => string; client?: ContentKitClient }) {\n const list = useContents(useMemo(() => ({ kind, sort, limit: size, display_language: language }), [kind, sort, size, language]), { client });\n if (!list.loading && list.items.length === 0) return null;\n return (\n <section className=\"grid content-start gap-3\" aria-label={title}>\n <h2 className=\"text-base font-semibold\">{title}</h2>\n <ol className=\"grid gap-3\">\n {list.loading\n ? Array.from({ length: Math.min(size, 3) }, (_, i) => <Skeleton key={i} className=\"h-14 w-full\" />)\n : list.items.slice(0, size).map((card) => (\n <li key={card.id}>\n <a href={url(card)} className={cn(\"grid items-center gap-3 hover:underline\", card.cover.length > 0 && \"grid-cols-[5rem_minmax(0,1fr)]\")}>\n {card.cover.length > 0 && (\n <span className=\"block aspect-[3/2] overflow-hidden rounded-md bg-muted\">\n <RenditionImg outputs={card.cover} alt=\"\" fit=\"cover\" skeleton loading=\"lazy\" className={cn(\"size-full\", !card.access.allowed && \"blur-sm\")} />\n </span>\n )}\n <span className=\"grid min-w-0 gap-0.5\">\n <span className=\"line-clamp-2 font-medium break-words\">{card.title || card.code}</span>\n {card.published_at && <RelativeTime at={card.published_at} className=\"text-xs text-muted-foreground\" />}\n </span>\n </a>\n </li>\n ))}\n </ol>\n </section>\n );\n}\n\n/** At most length characters, cut at a word boundary with an ellipsis. */\nexport function excerpt(text: string, length: number): string {\n const s = text.replace(/\\s+/g, \" \").trim();\n if (s.length <= length) return s;\n const cut = s.slice(0, length + 1);\n const space = cut.lastIndexOf(\" \");\n return `${(space > length * 0.6 ? cut.slice(0, space) : s.slice(0, length)).replace(/[\\s.,;:!?-]+$/, \"\")}…`;\n}\n"],"mappings":"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"}
@@ -5,6 +5,8 @@ import { ContentFilterSettingsProps } from './filters.js';
5
5
  export interface ReaderSettingsProps {
6
6
  /** Content kinds offered in this dialog; omitted shows all configured kinds. */
7
7
  kinds?: ContentFilterSettingsProps["kinds"];
8
+ /** Offers reader content filters (default); false shows only the language, e.g. on admin screens. */
9
+ filters?: boolean;
8
10
  languages: readonly {
9
11
  value: string;
10
12
  label: string;
@@ -70,7 +70,7 @@ function j(y) {
70
70
  }, e.value)) })]
71
71
  })]
72
72
  }),
73
- j && /* @__PURE__ */ w(M, {
73
+ j && y.filters !== !1 && /* @__PURE__ */ w(M, {
74
74
  language: y.language,
75
75
  kinds: y.kinds,
76
76
  client: j,
@@ -1 +1 @@
1
- {"version":3,"file":"reader-settings.js","names":[],"sources":["../../../src/components/content/reader-settings.tsx"],"sourcesContent":["import { Settings01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { useContext, useId, useSyncExternalStore } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useOptionalContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContentFilters } from \"../../react/discovery.js\";\nimport { AppearanceContext, ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from \"#ckui/ui/dialog\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { ContentFilterSettings, type ContentFilterSettingsProps } from \"./filters.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport type { ReaderPreferenceState } from \"../../client/reader-preferences.js\";\n\nconst EMPTY_PREFERENCES: ReaderPreferenceState = Object.freeze({ loading: false, saving: false, pending: false });\nconst noPreferences = () => EMPTY_PREFERENCES;\nconst noSubscription = () => () => {};\n\nexport interface ReaderSettingsProps {\n /** Content kinds offered in this dialog; omitted shows all configured kinds. */\n kinds?: ContentFilterSettingsProps[\"kinds\"];\n languages: readonly { value: string; label: string }[];\n /** The current language, controlled by the host. */\n language: string;\n /** The host applies its own route and preference changes. */\n onLanguageChange: (language: string) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Language preferences and filters for both guests and signed-in readers. */\nexport function ReaderSettings(p: ReaderSettingsProps) {\n const messages = useMessages();\n const { t } = messages;\n const id = useId();\n const client = useOptionalContentKitClient(p.client);\n const preferences = client?.preferences;\n const state = useSyncExternalStore(preferences?.subscribe ?? noSubscription, preferences?.snapshot ?? noPreferences, noPreferences);\n // The selector describes the current view. A saved default must not make a\n // localized deep link look selected in a language it is not displaying.\n const selectedLanguage = p.language;\n const save = useAction(\"preferences.save\", p.onError);\n const inheritedAppearance = useContext(AppearanceContext);\n const appearance = p.appearance ?? inheritedAppearance;\n return (\n <AppearanceContext.Provider value={appearance}>\n <ContentKitUiRoot className={p.className} appearance={appearance} data-ckui=\"reader-settings\">\n <Dialog>\n <DialogTrigger render={<Button type=\"button\" variant=\"ghost\" size=\"sm\" aria-label={t(\"readerSettings.title\")} data-ckui=\"reader-settings-trigger\" />}>\n <HugeiconsIcon icon={Settings01Icon} strokeWidth={2} className=\"size-4\" aria-hidden />\n <span>{p.language.toUpperCase()}</span>\n </DialogTrigger>\n <DialogContent className=\"max-h-[calc(100dvh-2rem)] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t(\"readerSettings.title\")}</DialogTitle>\n <DialogDescription className=\"sr-only\">{t(\"readerSettings.description\")}</DialogDescription>\n </DialogHeader>\n <div className=\"grid gap-3\">\n <Label htmlFor={`${id}-language`}>{t(\"listing.language\")}</Label>\n <Select value={selectedLanguage} disabled={save.pending || state.loading} onValueChange={(value) => {\n if (!value || value === selectedLanguage) return;\n void save.run(async () => { await preferences?.setLanguage(value); p.onLanguageChange(value); });\n }}>\n <SelectTrigger id={`${id}-language`} className=\"w-full\">\n <SelectValue>{p.languages.find((item) => item.value === selectedLanguage)?.label ?? selectedLanguage}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {p.languages.map((item) => <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>)}\n </SelectContent>\n </Select>\n </div>\n {client && <ReaderFilters language={p.language} kinds={p.kinds} client={client} onError={p.onError} />}\n {(state.pending || save.pending) && !state.error && <p className=\"text-sm text-muted-foreground\" role=\"status\">{t(\"readerSettings.savingPreferences\")}</p>}\n <ErrorLine>{state.error || save.error ? t(\"readerSettings.preferencesNotSaved\") : null}</ErrorLine>\n {state.error && preferences && <Button type=\"button\" variant=\"outline\" onClick={() => { void save.run(() => preferences.retry()); }}>{t(\"common.retry\")}</Button>}\n </DialogContent>\n </Dialog>\n </ContentKitUiRoot>\n </AppearanceContext.Provider>\n );\n}\n\nfunction ReaderFilters(p: Pick<ReaderSettingsProps, \"language\" | \"client\" | \"onError\" | \"kinds\">) {\n const { t } = useMessages();\n const id = useId();\n const { filters } = useContentFilters({ language: p.language, client: p.client });\n if (filters && (!filters.filters.length || p.kinds && !p.kinds.some(kind=>filters.applies_to.includes(kind)))) return null;\n return (\n <section aria-labelledby={id} className=\"grid gap-3\">\n <h3 id={id} className=\"font-medium\">{t(\"readerSettings.filters\")}</h3>\n <ContentFilterSettings {...p} includeDeleted={false} />\n </section>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reader-settings.js","names":[],"sources":["../../../src/components/content/reader-settings.tsx"],"sourcesContent":["import { Settings01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { useContext, useId, useSyncExternalStore } from \"react\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useOptionalContentKitClient, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContentFilters } from \"../../react/discovery.js\";\nimport { AppearanceContext, ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from \"#ckui/ui/dialog\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { ContentFilterSettings, type ContentFilterSettingsProps } from \"./filters.js\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\nimport type { ReaderPreferenceState } from \"../../client/reader-preferences.js\";\n\nconst EMPTY_PREFERENCES: ReaderPreferenceState = Object.freeze({ loading: false, saving: false, pending: false });\nconst noPreferences = () => EMPTY_PREFERENCES;\nconst noSubscription = () => () => {};\n\nexport interface ReaderSettingsProps {\n /** Content kinds offered in this dialog; omitted shows all configured kinds. */\n kinds?: ContentFilterSettingsProps[\"kinds\"];\n /** Offers reader content filters (default); false shows only the language, e.g. on admin screens. */\n filters?: boolean;\n languages: readonly { value: string; label: string }[];\n /** The current language, controlled by the host. */\n language: string;\n /** The host applies its own route and preference changes. */\n onLanguageChange: (language: string) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/** Language preferences and filters for both guests and signed-in readers. */\nexport function ReaderSettings(p: ReaderSettingsProps) {\n const messages = useMessages();\n const { t } = messages;\n const id = useId();\n const client = useOptionalContentKitClient(p.client);\n const preferences = client?.preferences;\n const state = useSyncExternalStore(preferences?.subscribe ?? noSubscription, preferences?.snapshot ?? noPreferences, noPreferences);\n // The selector describes the current view. A saved default must not make a\n // localized deep link look selected in a language it is not displaying.\n const selectedLanguage = p.language;\n const save = useAction(\"preferences.save\", p.onError);\n const inheritedAppearance = useContext(AppearanceContext);\n const appearance = p.appearance ?? inheritedAppearance;\n return (\n <AppearanceContext.Provider value={appearance}>\n <ContentKitUiRoot className={p.className} appearance={appearance} data-ckui=\"reader-settings\">\n <Dialog>\n <DialogTrigger render={<Button type=\"button\" variant=\"ghost\" size=\"sm\" aria-label={t(\"readerSettings.title\")} data-ckui=\"reader-settings-trigger\" />}>\n <HugeiconsIcon icon={Settings01Icon} strokeWidth={2} className=\"size-4\" aria-hidden />\n <span>{p.language.toUpperCase()}</span>\n </DialogTrigger>\n <DialogContent className=\"max-h-[calc(100dvh-2rem)] overflow-y-auto\">\n <DialogHeader>\n <DialogTitle>{t(\"readerSettings.title\")}</DialogTitle>\n <DialogDescription className=\"sr-only\">{t(\"readerSettings.description\")}</DialogDescription>\n </DialogHeader>\n <div className=\"grid gap-3\">\n <Label htmlFor={`${id}-language`}>{t(\"listing.language\")}</Label>\n <Select value={selectedLanguage} disabled={save.pending || state.loading} onValueChange={(value) => {\n if (!value || value === selectedLanguage) return;\n void save.run(async () => { await preferences?.setLanguage(value); p.onLanguageChange(value); });\n }}>\n <SelectTrigger id={`${id}-language`} className=\"w-full\">\n <SelectValue>{p.languages.find((item) => item.value === selectedLanguage)?.label ?? selectedLanguage}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {p.languages.map((item) => <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>)}\n </SelectContent>\n </Select>\n </div>\n {client && p.filters !== false && <ReaderFilters language={p.language} kinds={p.kinds} client={client} onError={p.onError} />}\n {(state.pending || save.pending) && !state.error && <p className=\"text-sm text-muted-foreground\" role=\"status\">{t(\"readerSettings.savingPreferences\")}</p>}\n <ErrorLine>{state.error || save.error ? t(\"readerSettings.preferencesNotSaved\") : null}</ErrorLine>\n {state.error && preferences && <Button type=\"button\" variant=\"outline\" onClick={() => { void save.run(() => preferences.retry()); }}>{t(\"common.retry\")}</Button>}\n </DialogContent>\n </Dialog>\n </ContentKitUiRoot>\n </AppearanceContext.Provider>\n );\n}\n\nfunction ReaderFilters(p: Pick<ReaderSettingsProps, \"language\" | \"client\" | \"onError\" | \"kinds\">) {\n const { t } = useMessages();\n const id = useId();\n const { filters } = useContentFilters({ language: p.language, client: p.client });\n if (filters && (!filters.filters.length || p.kinds && !p.kinds.some(kind=>filters.applies_to.includes(kind)))) return null;\n return (\n <section aria-labelledby={id} className=\"grid gap-3\">\n <h3 id={id} className=\"font-medium\">{t(\"readerSettings.filters\")}</h3>\n <ContentFilterSettings {...p} includeDeleted={false} />\n </section>\n );\n}\n"],"mappings":"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"}
@@ -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;
@@ -12,7 +12,7 @@ import { Cancel01Icon as g, Clock01Icon as _, File01Icon as v, Folder01Icon as y
12
12
  import { HugeiconsIcon as w } from "@hugeicons/react";
13
13
  import { createPortal as re } from "react-dom";
14
14
  //#region src/components/content/search.tsx
15
- var T = (e) => e.type === "content" ? `c:${e.card.id}` : e.type === "group" ? `g:${e.group.id}` : `r:${e.query}`, E = (e) => e.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").normalize("NFC").toLowerCase().trim().replace(/\s+/g, " "), D = {
15
+ var T = (e) => e.type === "content" ? `c:${e.card.id}` : e.type === "group" ? `g:${e.group.id}` : `r:${e.query}`, E = (e) => e.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").normalize("NFC").toLowerCase().replace(/(^|[^\p{L}\p{N}\p{M}_])(?:the|a|an)(?=$|[^\p{L}\p{N}\p{M}_])/gu, "$1").trim().replace(/\s+/g, " "), D = {
16
16
  artist: {
17
17
  icon: C,
18
18
  color: "text-blue-500 dark:text-blue-400"
@@ -46,7 +46,7 @@ var T = (e) => e.type === "content" ? `c:${e.card.id}` : e.type === "group" ? `g
46
46
  color: "text-orange-500 dark:text-orange-400"
47
47
  }
48
48
  };
49
- function O(e, t, n, r) {
49
+ function ie(e, t, n, r) {
50
50
  let [i, a] = d({
51
51
  top: 0,
52
52
  left: 0,
@@ -76,7 +76,7 @@ function O(e, t, n, r) {
76
76
  r
77
77
  ]), i;
78
78
  }
79
- function ie({ kind: e }) {
79
+ function O({ kind: e }) {
80
80
  let t = D[e];
81
81
  return /* @__PURE__ */ p(w, {
82
82
  icon: t?.icon ?? x,
@@ -87,14 +87,19 @@ function ie({ kind: e }) {
87
87
  });
88
88
  }
89
89
  function k(l) {
90
- let { t: v, error: y } = t(), b = te(), S = c(r)?.navigate, [C, D] = d(l.defaultValue ?? ""), [k, A] = d(!1), [j, M] = d(), N = u(null), P = u(null), F = u(null), I = l.presentation === "header", L = O(k, I, N, F), { top: R } = L, z = !C.trim(), B = o(C, {
90
+ let { t: v, error: y } = t(), b = te(), S = c(r)?.navigate, [C, D] = d(l.defaultValue ?? ""), [k, A] = d(!1), [j, M] = d(), N = u(null), P = u(null), F = u(null), I = l.presentation === "header", L = ie(k, I, N, F), { top: R } = L, z = !C.trim(), B = o(C, {
91
91
  kind: l.kind,
92
92
  entity_kinds: l.groupKinds,
93
- client: l.client
93
+ client: l.client,
94
+ request: l.requestSuggestions,
95
+ minLength: l.disabled ? Infinity : 1
94
96
  }), V = a({
95
- enabled: l.recent !== !1 && k,
97
+ enabled: l.recent !== !1 && k && !l.disabled,
96
98
  client: l.client
97
- }), H = E(C), U = B.query === C.trim(), W = ne(() => [...l.recent === !1 ? [] : V.searches.filter((e) => E(e.query).startsWith(H)).map((e) => ({
99
+ }), H = E(C), U = B.query === C.trim(), W = ne(() => [...l.recent === !1 ? [] : V.searches.map((e) => ({
100
+ ...e,
101
+ normalized: E(e.query)
102
+ })).filter((e) => z || H && e.normalized.includes(H)).sort((e, t) => Number(t.normalized.startsWith(H)) - Number(e.normalized.startsWith(H))).map((e) => ({
98
103
  type: "recent",
99
104
  query: e.query
100
105
  })), ...!z && U ? [...B.entities.map((e) => ({
@@ -123,7 +128,7 @@ function k(l) {
123
128
  return document.addEventListener("pointerdown", e), () => document.removeEventListener("pointerdown", e);
124
129
  }, [k]);
125
130
  let Y = () => {
126
- A(!0);
131
+ l.disabled || A(!0);
127
132
  }, X = (e) => {
128
133
  A(!1), e && l.onSearch(e);
129
134
  }, Z = (e) => {
@@ -162,6 +167,7 @@ function k(l) {
162
167
  children: W.map((t, n) => /* @__PURE__ */ p("li", {
163
168
  id: `${b}-${n}`,
164
169
  role: "option",
170
+ "aria-label": I && t.type !== "recent" ? `${s(l.labels, t.type === "content" ? t.card.kind : t.group.kind)} ${t.type === "content" ? t.card.title || t.card.code : t.group.name || t.group.slug}` : void 0,
165
171
  "aria-selected": n === G,
166
172
  "data-ckui": "suggestion",
167
173
  "data-type": t.type,
@@ -171,7 +177,7 @@ function k(l) {
171
177
  e.preventDefault(), Z(t);
172
178
  },
173
179
  children: I && t.type !== "recent" ? /* @__PURE__ */ m(f, { children: [
174
- /* @__PURE__ */ p(ie, { kind: t.type === "content" ? t.card.kind : t.group.kind }),
180
+ /* @__PURE__ */ p(O, { kind: t.type === "content" ? t.card.kind : t.group.kind }),
175
181
  /* @__PURE__ */ p("span", {
176
182
  className: "sr-only",
177
183
  children: s(l.labels, t.type === "content" ? t.card.kind : t.group.kind)
@@ -263,12 +269,15 @@ function k(l) {
263
269
  role: "search",
264
270
  className: "relative",
265
271
  onSubmit: (e) => {
266
- if (e.preventDefault(), k && K) return Z(K);
267
- X(C.trim());
272
+ if (e.preventDefault(), !l.disabled) {
273
+ if (k && K) return Z(K);
274
+ l.selectionOnly || X(C.trim());
275
+ }
268
276
  },
269
277
  children: [
270
278
  I ? /* @__PURE__ */ p("button", {
271
279
  type: "submit",
280
+ disabled: l.disabled,
272
281
  className: "absolute top-1/2 left-2.5 -translate-y-1/2 rounded text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring",
273
282
  "aria-label": v("contents.search"),
274
283
  children: /* @__PURE__ */ p(w, {
@@ -293,6 +302,7 @@ function k(l) {
293
302
  "aria-activedescendant": k && K ? `${b}-${G}` : void 0,
294
303
  "aria-autocomplete": "list",
295
304
  autoComplete: "off",
305
+ disabled: l.disabled,
296
306
  className: h("h-9 ps-8", I && "rounded-full border-border/50 bg-foreground/10 pe-9 text-base shadow-none md:text-base"),
297
307
  value: C,
298
308
  placeholder: l.placeholder ?? v("contents.searchPlaceholder"),
@@ -311,6 +321,7 @@ function k(l) {
311
321
  }),
312
322
  I && (C || k) && /* @__PURE__ */ p("button", {
313
323
  type: "button",
324
+ disabled: l.disabled,
314
325
  className: "absolute top-1/2 right-2.5 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring",
315
326
  "aria-label": v(C ? "contents.clearSearch" : "common.close"),
316
327
  onMouseDown: (e) => e.preventDefault(),
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","names":[],"sources":["../../../src/components/content/search.tsx"],"sourcesContent":["import { Cancel01Icon, Clock01Icon, File01Icon, Folder01Icon, Image01Icon, Search01Icon, TagsIcon, UserIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type RefObject, type KeyboardEvent } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Card, GroupCard } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useRecentSearches, useTypeahead } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Input } from \"#ckui/ui/input\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\n\nexport interface SearchBoxProps {\n /** Submits the text: the host shows its search page. */\n onSearch: (q: string) => void;\n /** Suggest contents of this kind only. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n groupKinds?: readonly string[];\n defaultValue?: string;\n placeholder?: string;\n /** A suggestion's link; default its canonical path. */\n href?: (item: Card | GroupCard) => string | undefined;\n labels?: Labels;\n /** Recent searches first, prefix-filtered while typing. Default true. */\n recent?: boolean;\n /** A portal dropdown anchored to the header field; default stays inline. */\n presentation?: \"inline\" | \"header\";\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ntype Suggestion = { type: \"content\"; card: Card } | { type: \"group\"; group: GroupCard } | { type: \"recent\"; query: string };\nconst suggestionKey = (s: Suggestion) => s.type === \"content\" ? `c:${s.card.id}` : s.type === \"group\" ? `g:${s.group.id}` : `r:${s.query}`;\n\n// Match the keyword index's case, Latin accent and whitespace normalization.\nconst prefixText = (text: string) => text.normalize(\"NFKD\").replace(/[\\u0300-\\u036f]/g, \"\").normalize(\"NFC\").toLowerCase().trim().replace(/\\s+/g, \" \");\n\nconst TYPE_ICONS: Record<string, { icon: IconSvgElement; color: string }> = {\n artist: { icon: UserIcon, color: \"text-blue-500 dark:text-blue-400\" },\n series: { icon: Folder01Icon, color: \"text-yellow-600 dark:text-yellow-400\" },\n gallery: { icon: Image01Icon, color: \"text-green-600 dark:text-green-400\" },\n post: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n blog: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n poll: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n character: { icon: UserIcon, color: \"text-purple-500 dark:text-purple-400\" },\n tag: { icon: TagsIcon, color: \"text-orange-500 dark:text-orange-400\" },\n};\n\nfunction useHeaderAnchor(open: boolean, enabled: boolean, root: RefObject<HTMLDivElement | null>, input: RefObject<HTMLInputElement | null>) {\n const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });\n useLayoutEffect(() => {\n if (!open || !enabled || !root.current) return;\n const anchor = input.current ?? root.current;\n const view = root.current.ownerDocument.defaultView;\n if (!view) return;\n const measure = () => {\n const rect = anchor.getBoundingClientRect();\n setPosition({ top: Math.max(0, Math.min(view.innerHeight, rect.bottom + 4)), left: rect.left, width: rect.width });\n };\n measure();\n const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(measure);\n observer?.observe(anchor);\n view.addEventListener(\"resize\", measure);\n view.addEventListener(\"scroll\", measure, true);\n view.visualViewport?.addEventListener(\"resize\", measure);\n view.visualViewport?.addEventListener(\"scroll\", measure);\n return () => {\n observer?.disconnect();\n view.removeEventListener(\"resize\", measure);\n view.removeEventListener(\"scroll\", measure, true);\n view.visualViewport?.removeEventListener(\"resize\", measure);\n view.visualViewport?.removeEventListener(\"scroll\", measure);\n };\n }, [open, enabled, root, input]);\n return position;\n}\n\nfunction SearchTypeIcon({ kind }: { kind: string }) {\n const type = TYPE_ICONS[kind];\n return <HugeiconsIcon icon={type?.icon ?? Search01Icon} strokeWidth={2} className={cn(\"size-4 shrink-0\", type?.color ?? \"text-muted-foreground\")} data-search-kind={kind} aria-hidden />;\n}\n\n/**\n * Recent query prefixes first, followed by entity and title prefixes. Enter\n * searches, or opens the chosen suggestion; full search can match more deeply.\n */\nexport function SearchBox(p: SearchBoxProps) {\n const { t, error } = useMessages();\n const id = useId();\n const navigate = useContext(ContentKitContext)?.navigate;\n const [text, setText] = useState(p.defaultValue ?? \"\");\n const [open, setOpen] = useState(false);\n // null requests the first result when opening an as-yet-unloaded panel.\n const [active, setActive] = useState<string | null>();\n const root = useRef<HTMLDivElement>(null);\n const panel = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const header = p.presentation === \"header\";\n const anchor = useHeaderAnchor(open, header, root, input);\n const { top } = anchor;\n const empty = !text.trim();\n const found = useTypeahead(text, { kind: p.kind, entity_kinds: p.groupKinds, client: p.client });\n const recent = useRecentSearches({ enabled: p.recent !== false && open, client: p.client });\n const prefix = prefixText(text);\n const current = found.query === text.trim();\n const visible = useMemo<Suggestion[]>(\n () => [\n ...(p.recent === false ? [] : recent.searches.filter((r) => prefixText(r.query).startsWith(prefix)).map((r) => ({ type: \"recent\" as const, query: r.query }))),\n ...(!empty && current ? [\n ...found.entities.map((group) => ({ type: \"group\" as const, group })),\n ...found.contents.map((card) => ({ type: \"content\" as const, card })),\n ] : []),\n ],\n [p.recent, prefix, empty, current, recent.searches, found.contents, found.entities],\n );\n // History and live suggestions can arrive independently. Track identity so\n // prepending history never changes what Enter will open.\n const activeIndex = active === null ? Math.min(0, visible.length - 1) : visible.findIndex((s) => suggestionKey(s) === active);\n const selected = visible[activeIndex];\n const activate = (index: number) => {\n const suggestion = visible[index];\n setActive(suggestion ? suggestionKey(suggestion) : index >= 0 ? null : undefined);\n };\n const loading = recent.loading || (!empty && (!current || found.loading));\n const showRecent = empty && p.recent !== false;\n const emptyMessage = empty ? !header && showRecent && !recent.error ? \"contents.noRecentSearches\" : \"contents.searchHint\" : \"contents.noResults\";\n useEffect(() => {\n if (!open) return;\n const outside = (event: PointerEvent) => {\n if (!root.current?.contains(event.target as Node) && !panel.current?.contains(event.target as Node)) setOpen(false);\n };\n document.addEventListener(\"pointerdown\", outside);\n return () => document.removeEventListener(\"pointerdown\", outside);\n }, [open]);\n const reveal = () => {\n setOpen(true);\n };\n const search = (q: string) => {\n setOpen(false);\n if (q) p.onSearch(q);\n };\n const go = (s: Suggestion) => {\n if (s.type === \"recent\") {\n setText(s.query);\n return search(s.query);\n }\n const url = p.href ? p.href(s.type === \"content\" ? s.card : s.group) : s.type === \"content\" ? s.card.path : s.group.path;\n setOpen(false);\n if (!url) return;\n if (navigate) navigate(url);\n else location.assign(url);\n };\n const panelKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n input.current?.focus();\n setOpen(false);\n setActive(undefined);\n };\n const results = (<>\n {showRecent && (\n <div className={cn(\"flex items-center justify-between gap-2 text-muted-foreground\", header ? \"mb-4 text-base font-semibold\" : \"px-2 py-1 text-xs\")}>\n <span id={`${id}-recent`} className=\"flex items-center gap-2\">{header && <HugeiconsIcon icon={Clock01Icon} strokeWidth={2} className=\"size-5\" aria-hidden />}{t(\"contents.recentSearches\")}</span>\n {visible.length > 0 && <button\n type=\"button\"\n className=\"rounded px-1 hover:text-foreground\"\n onMouseDown={(e) => {\n e.preventDefault();\n }}\n onClick={() => void recent.clear()}\n >\n {t(header ? \"contents.clearAllRecent\" : \"contents.clearRecent\")}\n </button>}\n </div>\n )}\n <ul id={`${id}-list`} role=\"listbox\" aria-label={empty ? t(\"contents.recentSearches\") : t(\"contents.suggestions\")} className={cn(\"grid\", header && \"gap-2\")}>\n {visible.map((s, i) => (\n <li\n key={suggestionKey(s)}\n id={`${id}-${i}`}\n role=\"option\"\n aria-selected={i === activeIndex}\n data-ckui=\"suggestion\"\n data-type={s.type}\n className={cn(\"flex cursor-pointer items-center\", header ? s.type === \"recent\" ? \"gap-2 rounded-md p-3 text-base hover:bg-foreground/10\" : \"gap-3 rounded-md px-4 py-1 text-base font-medium hover:bg-foreground/10\" : \"gap-2 px-2 py-1.5\", i === activeIndex && \"bg-accent text-accent-foreground\")}\n onMouseEnter={() => setActive(suggestionKey(s))}\n onMouseDown={(e) => {\n e.preventDefault();\n go(s);\n }}\n >\n {header && s.type !== \"recent\" ? (\n <>\n <SearchTypeIcon kind={s.type === \"content\" ? s.card.kind : s.group.kind} />\n <span className=\"sr-only\">{labelOf(p.labels, s.type === \"content\" ? s.card.kind : s.group.kind)}</span>\n <span className=\"min-w-0 flex-1 truncate\">{s.type === \"content\" ? s.card.title || s.card.code : s.group.name || s.group.slug}</span>\n </>\n ) : s.type === \"content\" ? (\n <>\n <span className=\"h-10 w-8 shrink-0 overflow-hidden rounded bg-muted\">\n <RenditionImg outputs={s.card.cover} alt=\"\" fit=\"cover\" className=\"size-full\" />\n </span>\n <span className=\"min-w-0 truncate\">{s.card.title || s.card.code}</span>\n </>\n ) : s.type === \"group\" ? (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{s.group.name || s.group.slug}</span>\n <span className=\"text-xs text-muted-foreground\">{labelOf(p.labels, s.group.kind)}</span>\n </>\n ) : (\n <>\n <HugeiconsIcon icon={header ? Search01Icon : Clock01Icon} strokeWidth={2} className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 truncate\">{s.query}</span>\n <button\n type=\"button\"\n className=\"rounded p-0.5 text-muted-foreground hover:text-foreground\"\n aria-label={t(\"contents.removeRecent\", { q: s.query })}\n onMouseDown={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n onClick={() => void recent.clear(s.query)}\n >\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n </button>\n </>\n )}\n </li>\n ))}\n </ul>\n {loading ? <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex justify-center py-4\" : \"px-3 py-2\")}>{header ? <><span className=\"size-6 animate-spin rounded-full border-2 border-muted border-t-foreground motion-reduce:animate-none\" aria-hidden /><span className=\"sr-only\">{t(\"contents.loading\")}</span></> : t(\"contents.loading\")}</div>\n : !empty && found.error ? <p role=\"alert\" className={cn(\"px-3 py-2 text-destructive\", header && \"text-center\")}>{error(found.error)}</p>\n : visible.length === 0 && <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex flex-col items-center gap-2 py-8 text-center\" : \"px-3 py-2\")}>{header && <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-8\" aria-hidden />}<p>{t(emptyMessage, { q: text.trim() })}</p></div>}\n </>);\n return (\n <ContentKitUiRoot ref={root} appearance={p.appearance} className={cn(\"relative text-sm\", p.className)} data-ckui=\"search-box\" onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget) && !panel.current?.contains(event.relatedTarget)) setOpen(false); }}>\n <form\n role=\"search\"\n className=\"relative\"\n onSubmit={(e) => {\n e.preventDefault();\n if (open && selected) return go(selected);\n search(text.trim());\n }}\n >\n {header ? <button type=\"submit\" className=\"absolute top-1/2 left-2.5 -translate-y-1/2 rounded text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\" aria-label={t(\"contents.search\")}><HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n : <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\" aria-hidden />}\n <Input\n ref={input}\n type={header ? \"text\" : \"search\"}\n role=\"combobox\"\n aria-label={t(\"contents.search\")}\n aria-expanded={open}\n aria-controls={`${id}-list`}\n aria-activedescendant={open && selected ? `${id}-${activeIndex}` : undefined}\n aria-autocomplete=\"list\"\n autoComplete=\"off\"\n className={cn(\"h-9 ps-8\", header && \"rounded-full border-border/50 bg-foreground/10 pe-9 text-base shadow-none md:text-base\")}\n value={text}\n placeholder={p.placeholder ?? t(\"contents.searchPlaceholder\")}\n onChange={(e) => {\n setText(e.target.value);\n setOpen(true);\n setActive(undefined);\n }}\n onFocus={reveal}\n onClick={reveal}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n setActive(undefined);\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n reveal();\n activate(open ? Math.min(visible.length - 1, activeIndex + 1) : 0);\n } else if (e.key === \"ArrowUp\" && open) {\n e.preventDefault();\n activate(Math.max(-1, activeIndex - 1));\n }\n }}\n />\n {header && (text || open) && (\n <button\n type=\"button\"\n className=\"absolute top-1/2 right-2.5 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\"\n aria-label={text ? t(\"contents.clearSearch\") : t(\"common.close\")}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n input.current?.focus();\n setActive(undefined);\n if (text) { setText(\"\"); setOpen(true); }\n else setOpen(false);\n }}\n ><HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n )}\n </form>\n {open && (header ? createPortal(\n <ContentKitUiRoot appearance={p.appearance} className=\"fixed inset-x-0 bottom-0 z-[70] text-sm\" style={{ top }} data-ckui=\"search-layer\">\n <div className=\"absolute inset-0 bg-black/30\" data-ckui=\"search-backdrop\" aria-hidden />\n <div ref={panel} style={{ width: anchor.width, marginLeft: anchor.left }} className=\"relative origin-top animate-[search-expand_300ms_ease-out] rounded-lg border border-border bg-background text-foreground shadow-lg transition-none motion-reduce:animate-none\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>\n <div className=\"w-full px-3 pt-3\">\n <div className=\"[--search-max-height:70vh] overflow-y-auto pb-7 md:[--search-max-height:60vh]\" style={{ maxHeight: `min(var(--search-max-height), calc(100dvh - ${top}px - 1.875rem))` }}>\n <div>{results}</div>\n </div>\n </div>\n </div>\n </ContentKitUiRoot>, document.body,\n ) : <div ref={panel} className=\"absolute top-full z-20 mt-1 grid max-h-96 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>{results}</div>)}\n\n </ContentKitUiRoot>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"search.js","names":[],"sources":["../../../src/components/content/search.tsx"],"sourcesContent":["import { Cancel01Icon, Clock01Icon, File01Icon, Folder01Icon, Image01Icon, Search01Icon, TagsIcon, UserIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type RefObject, type KeyboardEvent } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { TypeaheadQuery } from \"../../client/discovery.js\";\nimport type { Card, GroupCard, Typeahead } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useRecentSearches, useTypeahead } from \"../../react/discovery.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Input } from \"#ckui/ui/input\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\n\nexport interface SearchBoxProps {\n /** Submits the text: the host shows its search page. */\n onSearch: (q: string) => void;\n /** Suggest contents of this kind only. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n groupKinds?: readonly string[];\n defaultValue?: string;\n placeholder?: string;\n /** A suggestion's link; default its canonical path. */\n href?: (item: Card | GroupCard) => string | undefined;\n labels?: Labels;\n /** Matching recent searches first, with prefixes before interior matches. Default true. */\n recent?: boolean;\n /** An authenticated or host-specific suggestion source; public typeahead by default. */\n requestSuggestions?: (query: TypeaheadQuery, signal: AbortSignal) => Promise<Typeahead>;\n /** Enter opens an explicitly selected suggestion; free text never submits a results search. */\n selectionOnly?: boolean;\n disabled?: boolean;\n /** A portal dropdown anchored to the header field; default stays inline. */\n presentation?: \"inline\" | \"header\";\n client?: ContentKitClient;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ntype Suggestion = { type: \"content\"; card: Card } | { type: \"group\"; group: GroupCard } | { type: \"recent\"; query: string };\nconst suggestionKey = (s: Suggestion) => s.type === \"content\" ? `c:${s.card.id}` : s.type === \"group\" ? `g:${s.group.id}` : `r:${s.query}`;\n\n// Mirror search-only normalization. Identity/slug normalization stays separate;\n// complete English filler words never change Japanese/CJK or interior letters.\nconst searchText = (text: string) => text.normalize(\"NFKD\").replace(/[\\u0300-\\u036f]/g, \"\").normalize(\"NFC\").toLowerCase()\n .replace(/(^|[^\\p{L}\\p{N}\\p{M}_])(?:the|a|an)(?=$|[^\\p{L}\\p{N}\\p{M}_])/gu, \"$1\").trim().replace(/\\s+/g, \" \");\n\nconst TYPE_ICONS: Record<string, { icon: IconSvgElement; color: string }> = {\n artist: { icon: UserIcon, color: \"text-blue-500 dark:text-blue-400\" },\n series: { icon: Folder01Icon, color: \"text-yellow-600 dark:text-yellow-400\" },\n gallery: { icon: Image01Icon, color: \"text-green-600 dark:text-green-400\" },\n post: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n blog: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n poll: { icon: File01Icon, color: \"text-purple-500 dark:text-purple-400\" },\n character: { icon: UserIcon, color: \"text-purple-500 dark:text-purple-400\" },\n tag: { icon: TagsIcon, color: \"text-orange-500 dark:text-orange-400\" },\n};\n\nfunction useHeaderAnchor(open: boolean, enabled: boolean, root: RefObject<HTMLDivElement | null>, input: RefObject<HTMLInputElement | null>) {\n const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });\n useLayoutEffect(() => {\n if (!open || !enabled || !root.current) return;\n const anchor = input.current ?? root.current;\n const view = root.current.ownerDocument.defaultView;\n if (!view) return;\n const measure = () => {\n const rect = anchor.getBoundingClientRect();\n setPosition({ top: Math.max(0, Math.min(view.innerHeight, rect.bottom + 4)), left: rect.left, width: rect.width });\n };\n measure();\n const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(measure);\n observer?.observe(anchor);\n view.addEventListener(\"resize\", measure);\n view.addEventListener(\"scroll\", measure, true);\n view.visualViewport?.addEventListener(\"resize\", measure);\n view.visualViewport?.addEventListener(\"scroll\", measure);\n return () => {\n observer?.disconnect();\n view.removeEventListener(\"resize\", measure);\n view.removeEventListener(\"scroll\", measure, true);\n view.visualViewport?.removeEventListener(\"resize\", measure);\n view.visualViewport?.removeEventListener(\"scroll\", measure);\n };\n }, [open, enabled, root, input]);\n return position;\n}\n\nfunction SearchTypeIcon({ kind }: { kind: string }) {\n const type = TYPE_ICONS[kind];\n return <HugeiconsIcon icon={type?.icon ?? Search01Icon} strokeWidth={2} className={cn(\"size-4 shrink-0\", type?.color ?? \"text-muted-foreground\")} data-search-kind={kind} aria-hidden />;\n}\n\n/**\n * Matching recent queries first, followed by entity/title substrings. Prefixes\n * are preferred. Enter searches or opens the chosen suggestion; full search can\n * match contextual fields more deeply.\n */\nexport function SearchBox(p: SearchBoxProps) {\n const { t, error } = useMessages();\n const id = useId();\n const navigate = useContext(ContentKitContext)?.navigate;\n const [text, setText] = useState(p.defaultValue ?? \"\");\n const [open, setOpen] = useState(false);\n // null requests the first result when opening an as-yet-unloaded panel.\n const [active, setActive] = useState<string | null>();\n const root = useRef<HTMLDivElement>(null);\n const panel = useRef<HTMLDivElement>(null);\n const input = useRef<HTMLInputElement>(null);\n const header = p.presentation === \"header\";\n const anchor = useHeaderAnchor(open, header, root, input);\n const { top } = anchor;\n const empty = !text.trim();\n const found = useTypeahead(text, { kind: p.kind, entity_kinds: p.groupKinds, client: p.client, request: p.requestSuggestions, minLength: p.disabled ? Infinity : 1 });\n const recent = useRecentSearches({ enabled: p.recent !== false && open && !p.disabled, client: p.client });\n const normalized = searchText(text);\n const current = found.query === text.trim();\n const visible = useMemo<Suggestion[]>(\n () => [\n ...(p.recent === false ? [] : recent.searches\n .map((r) => ({ ...r, normalized: searchText(r.query) }))\n .filter((r) => empty || (normalized && r.normalized.includes(normalized)))\n .sort((a, b) => Number(b.normalized.startsWith(normalized)) - Number(a.normalized.startsWith(normalized)))\n .map((r) => ({ type: \"recent\" as const, query: r.query }))),\n ...(!empty && current ? [\n ...found.entities.map((group) => ({ type: \"group\" as const, group })),\n ...found.contents.map((card) => ({ type: \"content\" as const, card })),\n ] : []),\n ],\n [p.recent, normalized, empty, current, recent.searches, found.contents, found.entities],\n );\n // History and live suggestions can arrive independently. Track identity so\n // prepending history never changes what Enter will open.\n const activeIndex = active === null ? Math.min(0, visible.length - 1) : visible.findIndex((s) => suggestionKey(s) === active);\n const selected = visible[activeIndex];\n const activate = (index: number) => {\n const suggestion = visible[index];\n setActive(suggestion ? suggestionKey(suggestion) : index >= 0 ? null : undefined);\n };\n const loading = recent.loading || (!empty && (!current || found.loading));\n const showRecent = empty && p.recent !== false;\n const emptyMessage = empty ? !header && showRecent && !recent.error ? \"contents.noRecentSearches\" : \"contents.searchHint\" : \"contents.noResults\";\n useEffect(() => {\n if (!open) return;\n const outside = (event: PointerEvent) => {\n if (!root.current?.contains(event.target as Node) && !panel.current?.contains(event.target as Node)) setOpen(false);\n };\n document.addEventListener(\"pointerdown\", outside);\n return () => document.removeEventListener(\"pointerdown\", outside);\n }, [open]);\n const reveal = () => {\n if (!p.disabled) setOpen(true);\n };\n const search = (q: string) => {\n setOpen(false);\n if (q) p.onSearch(q);\n };\n const go = (s: Suggestion) => {\n if (s.type === \"recent\") {\n setText(s.query);\n return search(s.query);\n }\n const url = p.href ? p.href(s.type === \"content\" ? s.card : s.group) : s.type === \"content\" ? s.card.path : s.group.path;\n setOpen(false);\n if (!url) return;\n if (navigate) navigate(url);\n else location.assign(url);\n };\n const panelKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n input.current?.focus();\n setOpen(false);\n setActive(undefined);\n };\n const results = (<>\n {showRecent && (\n <div className={cn(\"flex items-center justify-between gap-2 text-muted-foreground\", header ? \"mb-4 text-base font-semibold\" : \"px-2 py-1 text-xs\")}>\n <span id={`${id}-recent`} className=\"flex items-center gap-2\">{header && <HugeiconsIcon icon={Clock01Icon} strokeWidth={2} className=\"size-5\" aria-hidden />}{t(\"contents.recentSearches\")}</span>\n {visible.length > 0 && <button\n type=\"button\"\n className=\"rounded px-1 hover:text-foreground\"\n onMouseDown={(e) => {\n e.preventDefault();\n }}\n onClick={() => void recent.clear()}\n >\n {t(header ? \"contents.clearAllRecent\" : \"contents.clearRecent\")}\n </button>}\n </div>\n )}\n <ul id={`${id}-list`} role=\"listbox\" aria-label={empty ? t(\"contents.recentSearches\") : t(\"contents.suggestions\")} className={cn(\"grid\", header && \"gap-2\")}>\n {visible.map((s, i) => (\n <li\n key={suggestionKey(s)}\n id={`${id}-${i}`}\n role=\"option\"\n aria-label={header && s.type !== \"recent\" ? `${labelOf(p.labels, s.type === \"content\" ? s.card.kind : s.group.kind)} ${s.type === \"content\" ? s.card.title || s.card.code : s.group.name || s.group.slug}` : undefined}\n aria-selected={i === activeIndex}\n data-ckui=\"suggestion\"\n data-type={s.type}\n className={cn(\"flex cursor-pointer items-center\", header ? s.type === \"recent\" ? \"gap-2 rounded-md p-3 text-base hover:bg-foreground/10\" : \"gap-3 rounded-md px-4 py-1 text-base font-medium hover:bg-foreground/10\" : \"gap-2 px-2 py-1.5\", i === activeIndex && \"bg-accent text-accent-foreground\")}\n onMouseEnter={() => setActive(suggestionKey(s))}\n onMouseDown={(e) => {\n e.preventDefault();\n go(s);\n }}\n >\n {header && s.type !== \"recent\" ? (\n <>\n <SearchTypeIcon kind={s.type === \"content\" ? s.card.kind : s.group.kind} />\n <span className=\"sr-only\">{labelOf(p.labels, s.type === \"content\" ? s.card.kind : s.group.kind)}</span>\n <span className=\"min-w-0 flex-1 truncate\">{s.type === \"content\" ? s.card.title || s.card.code : s.group.name || s.group.slug}</span>\n </>\n ) : s.type === \"content\" ? (\n <>\n <span className=\"h-10 w-8 shrink-0 overflow-hidden rounded bg-muted\">\n <RenditionImg outputs={s.card.cover} alt=\"\" fit=\"cover\" className=\"size-full\" />\n </span>\n <span className=\"min-w-0 truncate\">{s.card.title || s.card.code}</span>\n </>\n ) : s.type === \"group\" ? (\n <>\n <span className=\"min-w-0 flex-1 truncate\">{s.group.name || s.group.slug}</span>\n <span className=\"text-xs text-muted-foreground\">{labelOf(p.labels, s.group.kind)}</span>\n </>\n ) : (\n <>\n <HugeiconsIcon icon={header ? Search01Icon : Clock01Icon} strokeWidth={2} className=\"size-4 shrink-0 text-muted-foreground\" aria-hidden />\n <span className=\"min-w-0 flex-1 truncate\">{s.query}</span>\n <button\n type=\"button\"\n className=\"rounded p-0.5 text-muted-foreground hover:text-foreground\"\n aria-label={t(\"contents.removeRecent\", { q: s.query })}\n onMouseDown={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n onClick={() => void recent.clear(s.query)}\n >\n <HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n </button>\n </>\n )}\n </li>\n ))}\n </ul>\n {loading ? <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex justify-center py-4\" : \"px-3 py-2\")}>{header ? <><span className=\"size-6 animate-spin rounded-full border-2 border-muted border-t-foreground motion-reduce:animate-none\" aria-hidden /><span className=\"sr-only\">{t(\"contents.loading\")}</span></> : t(\"contents.loading\")}</div>\n : !empty && found.error ? <p role=\"alert\" className={cn(\"px-3 py-2 text-destructive\", header && \"text-center\")}>{error(found.error)}</p>\n : visible.length === 0 && <div role=\"status\" className={cn(\"text-muted-foreground\", header ? \"flex flex-col items-center gap-2 py-8 text-center\" : \"px-3 py-2\")}>{header && <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-8\" aria-hidden />}<p>{t(emptyMessage, { q: text.trim() })}</p></div>}\n </>);\n return (\n <ContentKitUiRoot ref={root} appearance={p.appearance} className={cn(\"relative text-sm\", p.className)} data-ckui=\"search-box\" onBlur={(event) => { if (!event.currentTarget.contains(event.relatedTarget) && !panel.current?.contains(event.relatedTarget)) setOpen(false); }}>\n <form\n role=\"search\"\n className=\"relative\"\n onSubmit={(e) => {\n e.preventDefault();\n if (p.disabled) return;\n if (open && selected) return go(selected);\n if (p.selectionOnly) return;\n search(text.trim());\n }}\n >\n {header ? <button type=\"submit\" disabled={p.disabled} className=\"absolute top-1/2 left-2.5 -translate-y-1/2 rounded text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\" aria-label={t(\"contents.search\")}><HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n : <HugeiconsIcon icon={Search01Icon} strokeWidth={2} className=\"pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-muted-foreground\" aria-hidden />}\n <Input\n ref={input}\n type={header ? \"text\" : \"search\"}\n role=\"combobox\"\n aria-label={t(\"contents.search\")}\n aria-expanded={open}\n aria-controls={`${id}-list`}\n aria-activedescendant={open && selected ? `${id}-${activeIndex}` : undefined}\n aria-autocomplete=\"list\"\n autoComplete=\"off\"\n disabled={p.disabled}\n className={cn(\"h-9 ps-8\", header && \"rounded-full border-border/50 bg-foreground/10 pe-9 text-base shadow-none md:text-base\")}\n value={text}\n placeholder={p.placeholder ?? t(\"contents.searchPlaceholder\")}\n onChange={(e) => {\n setText(e.target.value);\n setOpen(true);\n setActive(undefined);\n }}\n onFocus={reveal}\n onClick={reveal}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n setActive(undefined);\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n reveal();\n activate(open ? Math.min(visible.length - 1, activeIndex + 1) : 0);\n } else if (e.key === \"ArrowUp\" && open) {\n e.preventDefault();\n activate(Math.max(-1, activeIndex - 1));\n }\n }}\n />\n {header && (text || open) && (\n <button\n type=\"button\"\n disabled={p.disabled}\n className=\"absolute top-1/2 right-2.5 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring\"\n aria-label={text ? t(\"contents.clearSearch\") : t(\"common.close\")}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n input.current?.focus();\n setActive(undefined);\n if (text) { setText(\"\"); setOpen(true); }\n else setOpen(false);\n }}\n ><HugeiconsIcon icon={Cancel01Icon} strokeWidth={2} className=\"size-4\" aria-hidden /></button>\n )}\n </form>\n {open && (header ? createPortal(\n <ContentKitUiRoot appearance={p.appearance} className=\"fixed inset-x-0 bottom-0 z-[70] text-sm\" style={{ top }} data-ckui=\"search-layer\">\n <div className=\"absolute inset-0 bg-black/30\" data-ckui=\"search-backdrop\" aria-hidden />\n <div ref={panel} style={{ width: anchor.width, marginLeft: anchor.left }} className=\"relative origin-top animate-[search-expand_300ms_ease-out] rounded-lg border border-border bg-background text-foreground shadow-lg transition-none motion-reduce:animate-none\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>\n <div className=\"w-full px-3 pt-3\">\n <div className=\"[--search-max-height:70vh] overflow-y-auto pb-7 md:[--search-max-height:60vh]\" style={{ maxHeight: `min(var(--search-max-height), calc(100dvh - ${top}px - 1.875rem))` }}>\n <div>{results}</div>\n </div>\n </div>\n </div>\n </ContentKitUiRoot>, document.body,\n ) : <div ref={panel} className=\"absolute top-full z-20 mt-1 grid max-h-96 w-full overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>{results}</div>)}\n\n </ContentKitUiRoot>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AA4CA,IAAM,KAAiB,MAAkB,EAAE,SAAS,YAAY,KAAK,EAAE,KAAK,OAAO,EAAE,SAAS,UAAU,KAAK,EAAE,MAAM,OAAO,KAAK,EAAE,SAI7H,KAAc,MAAiB,EAAK,UAAU,MAAM,CAAC,CAAC,QAAQ,oBAAoB,EAAE,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,YAAY,CAAC,CACvH,QAAQ,kEAAkE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,QAAQ,GAAG,GAEvG,IAAsE;CAC1E,QAAQ;EAAE,MAAM;EAAU,OAAO;CAAmC;CACpE,QAAQ;EAAE,MAAM;EAAc,OAAO;CAAuC;CAC5E,SAAS;EAAE,MAAM;EAAa,OAAO;CAAqC;CAC1E,MAAM;EAAE,MAAM;EAAY,OAAO;CAAuC;CACxE,MAAM;EAAE,MAAM;EAAY,OAAO;CAAuC;CACxE,MAAM;EAAE,MAAM;EAAY,OAAO;CAAuC;CACxE,WAAW;EAAE,MAAM;EAAU,OAAO;CAAuC;CAC3E,KAAK;EAAE,MAAM;EAAU,OAAO;CAAuC;AACvE;AAEA,SAAS,GAAgB,GAAe,GAAkB,GAAwC,GAA2C;CAC3I,IAAM,CAAC,GAAU,KAAe,EAAS;EAAE,KAAK;EAAG,MAAM;EAAG,OAAO;CAAE,CAAC;CAyBtE,OAxBA,QAAsB;EACpB,IAAI,CAAC,KAAQ,CAAC,KAAW,CAAC,EAAK,SAAS;EACxC,IAAM,IAAS,EAAM,WAAW,EAAK,SAC/B,IAAO,EAAK,QAAQ,cAAc;EACxC,IAAI,CAAC,GAAM;EACX,IAAM,UAAgB;GACpB,IAAM,IAAO,EAAO,sBAAsB;GAC1C,EAAY;IAAE,KAAK,KAAK,IAAI,GAAG,KAAK,IAAI,EAAK,aAAa,EAAK,SAAS,CAAC,CAAC;IAAG,MAAM,EAAK;IAAM,OAAO,EAAK;GAAM,CAAC;EACnH;EACA,EAAQ;EACR,IAAM,IAAW,OAAO,iBAAmB,MAAc,KAAA,IAAY,IAAI,eAAe,CAAO;EAM/F,OALA,GAAU,QAAQ,CAAM,GACxB,EAAK,iBAAiB,UAAU,CAAO,GACvC,EAAK,iBAAiB,UAAU,GAAS,EAAI,GAC7C,EAAK,gBAAgB,iBAAiB,UAAU,CAAO,GACvD,EAAK,gBAAgB,iBAAiB,UAAU,CAAO,SAC1C;GAKX,AAJA,GAAU,WAAW,GACrB,EAAK,oBAAoB,UAAU,CAAO,GAC1C,EAAK,oBAAoB,UAAU,GAAS,EAAI,GAChD,EAAK,gBAAgB,oBAAoB,UAAU,CAAO,GAC1D,EAAK,gBAAgB,oBAAoB,UAAU,CAAO;EAC5D;CACF,GAAG;EAAC;EAAM;EAAS;EAAM;CAAK,CAAC,GACxB;AACT;AAEA,SAAS,EAAe,EAAE,WAA0B;CAClD,IAAM,IAAO,EAAW;CACxB,OAAO,kBAAC,GAAD;EAAe,MAAM,GAAM,QAAQ;EAAc,aAAa;EAAG,WAAW,EAAG,mBAAmB,GAAM,SAAS,uBAAuB;EAAG,oBAAkB;EAAM,eAAA;CAAa,CAAA;AACzL;AAOA,SAAgB,EAAU,GAAmB;CAC3C,IAAM,EAAE,MAAG,aAAU,EAAY,GAC3B,IAAK,GAAM,GACX,IAAW,EAAW,CAAiB,CAAC,EAAE,UAC1C,CAAC,GAAM,KAAW,EAAS,EAAE,gBAAgB,EAAE,GAC/C,CAAC,GAAM,KAAW,EAAS,EAAK,GAEhC,CAAC,GAAQ,KAAa,EAAwB,GAC9C,IAAO,EAAuB,IAAI,GAClC,IAAQ,EAAuB,IAAI,GACnC,IAAQ,EAAyB,IAAI,GACrC,IAAS,EAAE,iBAAiB,UAC5B,IAAS,GAAgB,GAAM,GAAQ,GAAM,CAAK,GAClD,EAAE,WAAQ,GACV,IAAQ,CAAC,EAAK,KAAK,GACnB,IAAQ,EAAa,GAAM;EAAE,MAAM,EAAE;EAAM,cAAc,EAAE;EAAY,QAAQ,EAAE;EAAQ,SAAS,EAAE;EAAoB,WAAW,EAAE,WAAW,WAAW;CAAE,CAAC,GAC9J,IAAS,EAAkB;EAAE,SAAS,EAAE,WAAW,MAAS,KAAQ,CAAC,EAAE;EAAU,QAAQ,EAAE;CAAO,CAAC,GACnG,IAAa,EAAW,CAAI,GAC5B,IAAU,EAAM,UAAU,EAAK,KAAK,GACpC,IAAU,SACR,CACJ,GAAI,EAAE,WAAW,KAAQ,CAAC,IAAI,EAAO,SAClC,KAAK,OAAO;EAAE,GAAG;EAAG,YAAY,EAAW,EAAE,KAAK;CAAE,EAAE,CAAC,CACvD,QAAQ,MAAM,KAAU,KAAc,EAAE,WAAW,SAAS,CAAU,CAAE,CAAC,CACzE,MAAM,GAAG,MAAM,OAAO,EAAE,WAAW,WAAW,CAAU,CAAC,IAAI,OAAO,EAAE,WAAW,WAAW,CAAU,CAAC,CAAC,CAAC,CACzG,KAAK,OAAO;EAAE,MAAM;EAAmB,OAAO,EAAE;CAAM,EAAE,GAC3D,GAAI,CAAC,KAAS,IAAU,CACtB,GAAG,EAAM,SAAS,KAAK,OAAW;EAAE,MAAM;EAAkB;CAAM,EAAE,GACpE,GAAG,EAAM,SAAS,KAAK,OAAU;EAAE,MAAM;EAAoB;CAAK,EAAE,CACtE,IAAI,CAAC,CACP,GACA;EAAC,EAAE;EAAQ;EAAY;EAAO;EAAS,EAAO;EAAU,EAAM;EAAU,EAAM;CAAQ,CACxF,GAGM,IAAc,MAAW,OAAO,KAAK,IAAI,GAAG,EAAQ,SAAS,CAAC,IAAI,EAAQ,WAAW,MAAM,EAAc,CAAC,MAAM,CAAM,GACtH,IAAW,EAAQ,IACnB,KAAY,MAAkB;EAClC,IAAM,IAAa,EAAQ;EAC3B,EAAU,IAAa,EAAc,CAAU,IAAI,KAAS,IAAI,OAAO,KAAA,CAAS;CAClF,GACM,KAAU,EAAO,WAAY,CAAC,MAAU,CAAC,KAAW,EAAM,UAC1D,IAAa,KAAS,EAAE,WAAW,IACnC,KAAe,IAAQ,CAAC,KAAU,KAAc,CAAC,EAAO,QAAQ,8BAA8B,wBAAwB;CAC5H,SAAgB;EACd,IAAI,CAAC,GAAM;EACX,IAAM,KAAW,MAAwB;GACvC,AAAI,CAAC,EAAK,SAAS,SAAS,EAAM,MAAc,KAAK,CAAC,EAAM,SAAS,SAAS,EAAM,MAAc,KAAG,EAAQ,EAAK;EACpH;EAEA,OADA,SAAS,iBAAiB,eAAe,CAAO,SACnC,SAAS,oBAAoB,eAAe,CAAO;CAClE,GAAG,CAAC,CAAI,CAAC;CACT,IAAM,UAAe;EACnB,AAAK,EAAE,YAAU,EAAQ,EAAI;CAC/B,GACM,KAAU,MAAc;EAE5B,AADA,EAAQ,EAAK,GACT,KAAG,EAAE,SAAS,CAAC;CACrB,GACM,KAAM,MAAkB;EAC5B,IAAI,EAAE,SAAS,UAEb,OADA,EAAQ,EAAE,KAAK,GACR,EAAO,EAAE,KAAK;EAEvB,IAAM,IAAM,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,YAAY,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM;EACpH,EAAQ,EAAK,GACR,MACD,IAAU,EAAS,CAAG,IACrB,SAAS,OAAO,CAAG;CAC1B,GACM,KAAgB,MAAyB;EACzC,EAAM,QAAQ,aAClB,EAAM,eAAe,GACrB,EAAM,SAAS,MAAM,GACrB,EAAQ,EAAK,GACb,EAAU,KAAA,CAAS;CACrB,GACM,IAAW,kBAAA,GAAA,EAAA,UAAA;EACd,KACC,kBAAC,OAAD;GAAK,WAAW,EAAG,iEAAiE,IAAS,iCAAiC,mBAAmB;GAAjJ,UAAA,CACE,kBAAC,QAAD;IAAM,IAAI,GAAG,EAAG;IAAU,WAAU;IAApC,UAAA,CAA+D,KAAU,kBAAC,GAAD;KAAe,MAAM;KAAa,aAAa;KAAG,WAAU;KAAS,eAAA;IAAa,CAAA,GAAG,EAAE,yBAAyB,CAAQ;GAChM,CAAA,GAAA,EAAQ,SAAS,KAAK,kBAAC,UAAD;IACrB,MAAK;IACL,WAAU;IACV,cAAc,MAAM;KAClB,EAAE,eAAe;IACnB;IACA,eAAe,KAAK,EAAO,MAAM;IAEhC,UAAA,EAAE,IAAS,4BAA4B,sBAAsB;GACxD,CAAA,CACL;;EAEP,kBAAC,MAAD;GAAI,IAAI,GAAG,EAAG;GAAQ,MAAK;GAAU,cAAoB,EAAR,IAAU,4BAA+B,sBAAsB;GAAG,WAAW,EAAG,QAAQ,KAAU,OAAO;GACvJ,UAAA,EAAQ,KAAK,GAAG,MACf,kBAAC,MAAD;IAEE,IAAI,GAAG,EAAG,GAAG;IACb,MAAK;IACL,cAAY,KAAU,EAAE,SAAS,WAAW,GAAG,EAAQ,EAAE,QAAQ,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,IAAI,EAAE,GAAG,EAAE,SAAS,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM,SAAS,KAAA;IAC7M,iBAAe,MAAM;IACrB,aAAU;IACV,aAAW,EAAE;IACb,WAAW,EAAG,oCAAoC,IAAS,EAAE,SAAS,WAAW,0DAA0D,4EAA4E,qBAAqB,MAAM,KAAe,kCAAkC;IACnS,oBAAoB,EAAU,EAAc,CAAC,CAAC;IAC9C,cAAc,MAAM;KAElB,AADA,EAAE,eAAe,GACjB,EAAG,CAAC;IACN;IAEC,UAAA,KAAU,EAAE,SAAS,WACpB,kBAAA,GAAA,EAAA,UAAA;KACE,kBAAC,GAAD,EAAgB,MAAM,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,KAAO,CAAA;KAC1E,kBAAC,QAAD;MAAM,WAAU;MAAW,UAAA,EAAQ,EAAE,QAAQ,EAAE,SAAS,YAAY,EAAE,KAAK,OAAO,EAAE,MAAM,IAAI;KAAQ,CAAA;KACtG,kBAAC,QAAD;MAAM,WAAU;MAA2B,UAAA,EAAE,SAAS,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,EAAE,MAAM;KAAW,CAAA;IACnI,EAAA,CAAA,IACA,EAAE,SAAS,YACb,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;KACd,UAAA,kBAAC,GAAD;MAAc,SAAS,EAAE,KAAK;MAAO,KAAI;MAAG,KAAI;MAAQ,WAAU;KAAa,CAAA;IAC3E,CAAA,GACN,kBAAC,QAAD;KAAM,WAAU;KAAoB,UAAA,EAAE,KAAK,SAAS,EAAE,KAAK;IAAW,CAAA,CACtE,EAAA,CAAA,IACA,EAAE,SAAS,UACb,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;KAA2B,UAAA,EAAE,MAAM,QAAQ,EAAE,MAAM;IAAW,CAAA,GAC9E,kBAAC,QAAD;KAAM,WAAU;KAAiC,UAAA,EAAQ,EAAE,QAAQ,EAAE,MAAM,IAAI;IAAQ,CAAA,CACvF,EAAA,CAAA,IAEF,kBAAA,GAAA,EAAA,UAAA;KACE,kBAAC,GAAD;MAAe,MAAM,IAAS,IAAe;MAAa,aAAa;MAAG,WAAU;MAAwC,eAAA;KAAa,CAAA;KACzI,kBAAC,QAAD;MAAM,WAAU;MAA2B,UAAA,EAAE;KAAY,CAAA;KACzD,kBAAC,UAAD;MACE,MAAK;MACL,WAAU;MACV,cAAY,EAAE,yBAAyB,EAAE,GAAG,EAAE,MAAM,CAAC;MACrD,cAAc,MAAM;OAElB,AADA,EAAE,eAAe,GACjB,EAAE,gBAAgB;MACpB;MACA,eAAe,KAAK,EAAO,MAAM,EAAE,KAAK;MAExC,UAAA,kBAAC,GAAD;OAAe,MAAM;OAAc,aAAa;OAAG,WAAU;OAAW,eAAA;MAAa,CAAA;KAC/E,CAAA;IACR,EAAA,CAAA;GAEF,GAlDG,EAAc,CAAC,CAkDlB,CACL;EACC,CAAA;EACH,KAAU,kBAAC,OAAD;GAAK,MAAK;GAAS,WAAW,EAAG,yBAAyB,IAAS,6BAA6B,WAAW;GAAI,UAAA,IAAS,kBAAA,GAAA,EAAA,UAAA,CAAE,kBAAC,QAAD;IAAM,WAAU;IAAwG,eAAA;GAAa,CAAA,GAAC,kBAAC,QAAD;IAAM,WAAU;IAAW,UAAA,EAAE,kBAAkB;GAAQ,CAAA,CAAG,EAAA,CAAA,IAAI,EAAE,kBAAkB;EAAO,CAAA,IAChW,CAAC,KAAS,EAAM,QAAQ,kBAAC,KAAD;GAAG,MAAK;GAAQ,WAAW,EAAG,8BAA8B,KAAU,aAAa;GAAI,UAAA,EAAM,EAAM,KAAK;EAAK,CAAA,IACrI,EAAQ,WAAW,KAAK,kBAAC,OAAD;GAAK,MAAK;GAAS,WAAW,EAAG,yBAAyB,IAAS,sDAAsD,WAAW;GAApI,UAAA,CAAwI,KAAU,kBAAC,GAAD;IAAe,MAAM;IAAc,aAAa;IAAG,WAAU;IAAS,eAAA;GAAa,CAAA,GAAE,kBAAC,KAAD,EAAA,UAAI,EAAE,IAAc,EAAE,GAAG,EAAK,KAAK,EAAE,CAAC,EAAK,CAAA,CAAM;;CACpT,EAAA,CAAA;CACF,OACE,kBAAC,GAAD;EAAkB,KAAK;EAAM,YAAY,EAAE;EAAY,WAAW,EAAG,oBAAoB,EAAE,SAAS;EAAG,aAAU;EAAa,SAAS,MAAU;GAAE,AAAI,CAAC,EAAM,cAAc,SAAS,EAAM,aAAa,KAAK,CAAC,EAAM,SAAS,SAAS,EAAM,aAAa,KAAG,EAAQ,EAAK;EAAG;EAA5Q,UAAA,CACE,kBAAC,QAAD;GACE,MAAK;GACL,WAAU;GACV,WAAW,MAAM;IACf,MAAE,eAAe,GACb,GAAE,UACN;SAAI,KAAQ,GAAU,OAAO,EAAG,CAAQ;KACpC,EAAE,iBACN,EAAO,EAAK,KAAK,CAAC;IAFsB;GAG1C;GATF,UAAA;IAWG,IAAS,kBAAC,UAAD;KAAQ,MAAK;KAAS,UAAU,EAAE;KAAU,WAAU;KAAoJ,cAAY,EAAE,iBAAiB;KAAG,UAAA,kBAAC,GAAD;MAAe,MAAM;MAAc,aAAa;MAAG,WAAU;MAAS,eAAA;KAAa,CAAA;IAAS,CAAA,IAC9U,kBAAC,GAAD;KAAe,MAAM;KAAc,aAAa;KAAG,WAAU;KAA8F,eAAA;IAAa,CAAA;IAC5K,kBAAC,GAAD;KACE,KAAK;KACL,MAAM,IAAS,SAAS;KACxB,MAAK;KACL,cAAY,EAAE,iBAAiB;KAC/B,iBAAe;KACf,iBAAe,GAAG,EAAG;KACrB,yBAAuB,KAAQ,IAAW,GAAG,EAAG,GAAG,MAAgB,KAAA;KACnE,qBAAkB;KAClB,cAAa;KACb,UAAU,EAAE;KACZ,WAAW,EAAG,YAAY,KAAU,wFAAwF;KAC5H,OAAO;KACP,aAAa,EAAE,eAAe,EAAE,4BAA4B;KAC5D,WAAW,MAAM;MAGf,AAFA,EAAQ,EAAE,OAAO,KAAK,GACtB,EAAQ,EAAI,GACZ,EAAU,KAAA,CAAS;KACrB;KACA,SAAS;KACT,SAAS;KACT,YAAY,MAAM;MAChB,IAAI,EAAE,QAAQ,UAAU;OAGtB,AAFA,EAAE,eAAe,GACjB,EAAQ,EAAK,GACb,EAAU,KAAA,CAAS;OACnB;MACF;MACA,AAAI,EAAE,QAAQ,eACZ,EAAE,eAAe,GACjB,EAAO,GACP,EAAS,IAAO,KAAK,IAAI,EAAQ,SAAS,GAAG,IAAc,CAAC,IAAI,CAAC,KACxD,EAAE,QAAQ,aAAa,MAChC,EAAE,eAAe,GACjB,EAAS,KAAK,IAAI,IAAI,IAAc,CAAC,CAAC;KAE1C;IACD,CAAA;IACA,MAAW,KAAQ,MAClB,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,EAAE;KACZ,WAAU;KACV,cAAmB,EAAP,IAAS,yBAA4B,cAAc;KAC/D,cAAc,MAAU,EAAM,eAAe;KAC7C,eAAe;MAGb,AAFA,EAAM,SAAS,MAAM,GACrB,EAAU,KAAA,CAAS,GACf,KAAQ,EAAQ,EAAE,GAAG,EAAQ,EAAI,KAChC,EAAQ,EAAK;KACpB;KACD,UAAA,kBAAC,GAAD;MAAe,MAAM;MAAc,aAAa;MAAG,WAAU;MAAS,eAAA;KAAa,CAAA;IAAS,CAAA;GAE3F;EACL,CAAA,GAAA,MAAS,IAAS,GACjB,kBAAC,GAAD;GAAkB,YAAY,EAAE;GAAY,WAAU;GAA0C,OAAO,EAAE,OAAI;GAAG,aAAU;GAA1H,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;IAA+B,aAAU;IAAkB,eAAA;GAAa,CAAA,GACvF,kBAAC,OAAD;IAAK,KAAK;IAAO,OAAO;KAAE,OAAO,EAAO;KAAO,YAAY,EAAO;IAAK;IAAG,WAAU;IAAgL,aAAU;IAAe,WAAW;IACtS,UAAA,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,OAAD;MAAK,WAAU;MAAgF,OAAO,EAAE,WAAW,+CAA+C,EAAI,iBAAiB;MACrL,UAAA,kBAAC,OAAD,EAAA,UAAM,EAAa,CAAA;KAChB,CAAA;IACF,CAAA;GACF,CAAA,CACW;EAAG,CAAA,GAAA,SAAS,IAChC,IAAI,kBAAC,OAAD;GAAK,KAAK;GAAO,WAAU;GAAmJ,aAAU;GAAe,WAAW;GAAe,UAAA;EAAa,CAAA,EAElO;;AAEtB"}
@@ -0,0 +1,14 @@
1
+ import { MouseEvent } from 'react';
2
+ import { Card } from '../../client/generated/wire.js';
3
+ import { CardListProps } from './grid.js';
4
+ /** One cover-sized control shared by complete lists and daily release sections. */
5
+ export declare function ContentSummaryTile({ tileKey, kind, contentKind, preview, open, label, onClick, cardProps: p, }: {
6
+ tileKey: string;
7
+ kind: "premium" | "filtered";
8
+ contentKind?: string;
9
+ preview?: Card;
10
+ open: boolean;
11
+ label: string;
12
+ onClick: (event: MouseEvent<HTMLButtonElement>) => void;
13
+ cardProps: CardListProps;
14
+ }): import('react').JSX.Element;