@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,113 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { Button as t } from "../../ui/button.js";
3
+ import { ErrorLine as n, useAction as r } from "../social/parts.js";
4
+ import { usePageNotices as i } from "../../react/page-notices.js";
5
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
6
+ import { cn as s } from "cn";
7
+ //#region src/components/admin/page-notices.tsx
8
+ var c = 8, l = {
9
+ blank: "pageNotices.reason_blank",
10
+ duplicate: "pageNotices.reason_duplicate",
11
+ spread: "pageNotices.reason_spread"
12
+ };
13
+ function u(e) {
14
+ if (e) try {
15
+ let t = new URL(e, globalThis.location?.href ?? "http://localhost/");
16
+ if (t.protocol === "http:" || t.protocol === "https:") return e;
17
+ } catch {}
18
+ }
19
+ function d({ content: d, version: f, client: p, onError: m, className: h }) {
20
+ let g = e(), { t: _ } = g, v = i(d, f, { client: p }), y = r("reader.save", m);
21
+ if (!v.notices.length) return null;
22
+ let b = (e) => {
23
+ let t = e.slice(0, c).map((e) => e.source_name).join(", ");
24
+ return e.length > c ? `${t} ${_("pageNotices.more", { count: e.length - c })}` : t;
25
+ };
26
+ return /* @__PURE__ */ o("details", {
27
+ className: s("text-sm text-muted-foreground", h),
28
+ "data-ckui": "page-notices",
29
+ children: [
30
+ /* @__PURE__ */ a("summary", {
31
+ className: "cursor-pointer select-none",
32
+ children: g.plural("pageNotices.notes", v.notices.length)
33
+ }),
34
+ /* @__PURE__ */ a("ul", {
35
+ className: "mt-2 grid gap-3",
36
+ children: v.notices.map((e) => /* @__PURE__ */ o("li", {
37
+ className: "grid gap-1.5",
38
+ "data-ckui": "page-notice",
39
+ "data-kind": e.kind,
40
+ children: [/* @__PURE__ */ o("div", {
41
+ className: "flex flex-wrap items-center gap-x-3 gap-y-1",
42
+ children: [/* @__PURE__ */ a("span", {
43
+ className: "text-foreground/80",
44
+ children: e.kind === "removed" ? g.plural("pageNotices.removed", e.pages.length) : _("pageNotices.reordered")
45
+ }), /* @__PURE__ */ o("span", {
46
+ className: "ms-auto flex gap-1",
47
+ children: [e.kind === "removed" && /* @__PURE__ */ a(t, {
48
+ type: "button",
49
+ size: "xs",
50
+ variant: "ghost",
51
+ disabled: y.pending,
52
+ onClick: () => void y.run(() => v.restore(e.id)),
53
+ children: _("pageNotices.restore")
54
+ }), /* @__PURE__ */ a(t, {
55
+ type: "button",
56
+ size: "xs",
57
+ variant: "ghost",
58
+ disabled: y.pending,
59
+ onClick: () => void y.run(() => v.dismiss(e.id)),
60
+ children: _("pageNotices.dismiss")
61
+ })]
62
+ })]
63
+ }), e.kind === "removed" ? /* @__PURE__ */ a("ul", {
64
+ className: "flex flex-wrap gap-2",
65
+ children: e.pages.map((e) => {
66
+ let t = u(e.preview_url);
67
+ return /* @__PURE__ */ o("li", {
68
+ className: "grid w-16 gap-0.5 text-[11px]",
69
+ title: e.source_name,
70
+ children: [
71
+ t ? /* @__PURE__ */ a("img", {
72
+ src: t,
73
+ alt: "",
74
+ loading: "lazy",
75
+ decoding: "async",
76
+ className: "aspect-[3/4] w-16 rounded bg-muted object-cover opacity-80"
77
+ }) : /* @__PURE__ */ a("span", { className: "aspect-[3/4] w-16 rounded bg-muted" }),
78
+ /* @__PURE__ */ a("span", {
79
+ className: "truncate",
80
+ children: e.source_name
81
+ }),
82
+ /* @__PURE__ */ a("span", {
83
+ className: "truncate",
84
+ children: _(l[e.reason] ?? l.blank)
85
+ })
86
+ ]
87
+ }, e.id);
88
+ })
89
+ }) : /* @__PURE__ */ o("dl", {
90
+ className: "grid grid-cols-[auto_minmax(0,1fr)] gap-x-2 text-xs",
91
+ children: [
92
+ /* @__PURE__ */ a("dt", { children: _("pageNotices.archiveOrder") }),
93
+ /* @__PURE__ */ a("dd", {
94
+ className: "truncate",
95
+ children: b([...e.pages].sort((e, t) => (e.from ?? 0) - (t.from ?? 0)))
96
+ }),
97
+ /* @__PURE__ */ a("dt", { children: _("pageNotices.appliedOrder") }),
98
+ /* @__PURE__ */ a("dd", {
99
+ className: "truncate",
100
+ children: b(e.pages)
101
+ })
102
+ ]
103
+ })]
104
+ }, e.id))
105
+ }),
106
+ /* @__PURE__ */ a(n, { error: y.error })
107
+ ]
108
+ });
109
+ }
110
+ //#endregion
111
+ export { d as PageNotices };
112
+
113
+ //# sourceMappingURL=page-notices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-notices.js","names":[],"sources":["../../../src/components/admin/page-notices.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { PageNotice } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport type { ContentKitErrorHandler } from \"../../react/context.js\";\nimport { usePageNotices } from \"../../react/page-notices.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { ErrorLine, useAction } from \"../social/parts.js\";\n\nexport interface PageNoticesProps {\n content: string;\n version: string;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n}\n\nconst SHOWN = 8;\nconst REASON = { blank: \"pageNotices.reason_blank\", duplicate: \"pageNotices.reason_duplicate\", spread: \"pageNotices.reason_spread\" } as const;\n\n/** Only link the editor media URLs supplied by the server. */\nfunction imageURL(value?: string): string | undefined {\n if (!value) return;\n try {\n const url = new URL(value, globalThis.location?.href ?? \"http://localhost/\");\n if (url.protocol === \"http:\" || url.protocol === \"https:\") return value;\n } catch { /* An invalid or unsupported URL has no preview. */ }\n}\n\n/**\n * Low-key notes about what automatic page processing did: pages removed as\n * blank, duplicate or covered by a two-page spread (restorable), and an\n * archive put into name order. Collapsed by default; nothing here blocks\n * saving or publishing. Renders nothing without notices.\n */\nexport function PageNotices({ content, version, client, onError, className }: PageNoticesProps) {\n const m = useMessages();\n const { t } = m;\n const r = usePageNotices(content, version, { client });\n const act = useAction(\"reader.save\", onError);\n if (!r.notices.length) return null;\n const names = (pages: PageNotice[\"pages\"]) => {\n const shown = pages.slice(0, SHOWN).map((p) => p.source_name).join(\", \");\n return pages.length > SHOWN ? `${shown} ${t(\"pageNotices.more\", { count: pages.length - SHOWN })}` : shown;\n };\n return (\n <details className={cn(\"text-sm text-muted-foreground\", className)} data-ckui=\"page-notices\">\n <summary className=\"cursor-pointer select-none\">{m.plural(\"pageNotices.notes\", r.notices.length)}</summary>\n <ul className=\"mt-2 grid gap-3\">\n {r.notices.map((n) => (\n <li key={n.id} className=\"grid gap-1.5\" data-ckui=\"page-notice\" data-kind={n.kind}>\n <div className=\"flex flex-wrap items-center gap-x-3 gap-y-1\">\n <span className=\"text-foreground/80\">{n.kind === \"removed\" ? m.plural(\"pageNotices.removed\", n.pages.length) : t(\"pageNotices.reordered\")}</span>\n <span className=\"ms-auto flex gap-1\">\n {n.kind === \"removed\" && (\n <Button type=\"button\" size=\"xs\" variant=\"ghost\" disabled={act.pending} onClick={() => void act.run(() => r.restore(n.id))}>\n {t(\"pageNotices.restore\")}\n </Button>\n )}\n <Button type=\"button\" size=\"xs\" variant=\"ghost\" disabled={act.pending} onClick={() => void act.run(() => r.dismiss(n.id))}>\n {t(\"pageNotices.dismiss\")}\n </Button>\n </span>\n </div>\n {n.kind === \"removed\" ? (\n <ul className=\"flex flex-wrap gap-2\">\n {n.pages.map((p) => {\n const preview = imageURL(p.preview_url);\n return (\n <li key={p.id} className=\"grid w-16 gap-0.5 text-[11px]\" title={p.source_name}>\n {preview ? <img src={preview} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"aspect-[3/4] w-16 rounded bg-muted object-cover opacity-80\" /> : <span className=\"aspect-[3/4] w-16 rounded bg-muted\" />}\n <span className=\"truncate\">{p.source_name}</span>\n <span className=\"truncate\">{t(REASON[p.reason as keyof typeof REASON] ?? REASON.blank)}</span>\n </li>\n );\n })}\n </ul>\n ) : (\n <dl className=\"grid grid-cols-[auto_minmax(0,1fr)] gap-x-2 text-xs\">\n <dt>{t(\"pageNotices.archiveOrder\")}</dt>\n <dd className=\"truncate\">{names([...n.pages].sort((a, b) => (a.from ?? 0) - (b.from ?? 0)))}</dd>\n <dt>{t(\"pageNotices.appliedOrder\")}</dt>\n <dd className=\"truncate\">{names(n.pages)}</dd>\n </dl>\n )}\n </li>\n ))}\n </ul>\n <ErrorLine error={act.error} />\n </details>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { ContentKitClient } from '../../client/client.js';
2
+ import { ReaderPlan } from '../../client/generated/wire.js';
3
+ /** One choosable page: its name, 1-based number and thumbnail when signed. */
4
+ export interface PickerPage {
5
+ name: string;
6
+ url?: string;
7
+ }
8
+ /** A non-page choice shown above the grid (an anchor, "start"). */
9
+ export interface PickerExtra {
10
+ value: string;
11
+ label: string;
12
+ }
13
+ export interface PlanMedia {
14
+ /** Each published page in reading order, with its thumbnail URL once read. */
15
+ pages: PickerPage[];
16
+ /** Audio track name → playable URL. */
17
+ audio: ReadonlyMap<string, string>;
18
+ }
19
+ /** A reader plan's pages with thumbnails and its audio with URLs, read once per plan (thumbnails are a convenience: names work without them). */
20
+ export declare function usePlanMedia(plan: ReaderPlan | null, client: ContentKitClient): PlanMedia;
21
+ /**
22
+ * Picks one page from thumbnails: the trigger shows the current page, the
23
+ * dialog the whole version in reading order. value is a page name or one of
24
+ * extras' values.
25
+ */
26
+ export declare function PagePicker({ label, pages, value, extras, onChange, className }: {
27
+ label: string;
28
+ pages: readonly PickerPage[];
29
+ value: string;
30
+ extras?: readonly PickerExtra[];
31
+ onChange: (value: string) => void;
32
+ className?: string;
33
+ }): import('react').JSX.Element;
34
+ /** A play/pause button for one audio URL; one preview plays at a time. */
35
+ export declare function AudioPreview({ url, label }: {
36
+ url?: string;
37
+ label: string;
38
+ }): import('react').JSX.Element | null;
@@ -0,0 +1,159 @@
1
+ import { useMessages as e } from "../../i18n/context.js";
2
+ import { Button as t } from "../../ui/button.js";
3
+ import { Dialog as n, DialogContent as r, DialogHeader as i, DialogTitle as a } from "../../ui/dialog.js";
4
+ import { useEffect as o, useRef as s, useState as c } from "react";
5
+ import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
6
+ import { cn as f } from "cn";
7
+ import { PauseIcon as p, PlayIcon as m } from "@hugeicons/core-free-icons";
8
+ import { HugeiconsIcon as h } from "@hugeicons/react";
9
+ //#region src/components/admin/page-picker.tsx
10
+ var g = (e) => e.thumb ?? e[Object.keys(e).sort().at(-1) ?? ""], _ = (e) => e.slice(0, e.lastIndexOf("/") + 1);
11
+ function v(e, t) {
12
+ let n = (e?.segments ?? []).flatMap((e) => (e.pages ?? []).map((e) => e.name)), [r, i] = c({
13
+ plan: null,
14
+ pages: /* @__PURE__ */ new Map(),
15
+ audio: /* @__PURE__ */ new Map()
16
+ });
17
+ o(() => {
18
+ if (!e) return;
19
+ let n = new AbortController(), r = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o = e.segments.flatMap((e) => {
20
+ let i = e.pages?.[0] && g(e.pages[0].files);
21
+ return !e.item || !i ? [] : [t.media.read(e.item, {
22
+ prefix: _(i),
23
+ limit: e.pages.length,
24
+ signal: n.signal
25
+ }).then((t) => {
26
+ let n = new Map(t.files.flatMap((e) => e.url ? [[e.path, e.url]] : []));
27
+ for (let t of e.pages ?? []) {
28
+ let e = n.get(g(t.files) ?? "");
29
+ e && r.set(t.name, e);
30
+ }
31
+ })];
32
+ }), s = e.playback;
33
+ return s?.tracks.length && o.push(t.media.read(s.item, {
34
+ limit: s.tracks.length,
35
+ signal: n.signal
36
+ }).then((e) => {
37
+ let t = new Map(e.files.flatMap((e) => e.url ? [[e.path, e.url]] : []));
38
+ for (let e of s.tracks) {
39
+ let n = t.get(e.path);
40
+ n && a.set(e.name, n);
41
+ }
42
+ })), Promise.allSettled(o).then(() => !n.signal.aborted && i({
43
+ plan: e,
44
+ pages: r,
45
+ audio: a
46
+ })), () => n.abort();
47
+ }, [e, t]);
48
+ let a = r.plan === e;
49
+ return {
50
+ pages: n.map((e) => ({
51
+ name: e,
52
+ url: a ? r.pages.get(e) : void 0
53
+ })),
54
+ audio: a ? r.audio : /* @__PURE__ */ new Map()
55
+ };
56
+ }
57
+ function y({ label: o, pages: s, value: p, extras: m = [], onChange: h, className: g }) {
58
+ let { t: _ } = e(), [v, y] = c(!1), b = s.findIndex((e) => e.name === p), x = b >= 0 ? s[b] : void 0, S = m.find((e) => e.value === p), C = (e) => {
59
+ h(e), y(!1);
60
+ };
61
+ return /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ d(t, {
62
+ type: "button",
63
+ size: "sm",
64
+ variant: "outline",
65
+ "aria-label": o,
66
+ className: f("h-auto gap-2 py-1 ps-1", g),
67
+ onClick: () => y(!0),
68
+ "data-ckui": "page-picker",
69
+ children: [/* @__PURE__ */ u("span", {
70
+ className: "flex h-10 w-7 shrink-0 items-center justify-center overflow-hidden rounded-sm bg-muted text-[10px] tabular-nums text-muted-foreground",
71
+ children: x?.url ? /* @__PURE__ */ u("img", {
72
+ src: x.url,
73
+ alt: "",
74
+ className: "size-full object-cover"
75
+ }) : x ? b + 1 : null
76
+ }), /* @__PURE__ */ u("span", {
77
+ className: "tabular-nums",
78
+ children: S ? S.label : x ? _("pagePicker.page", { n: b + 1 }) : _("pagePicker.choose")
79
+ })]
80
+ }), /* @__PURE__ */ u(n, {
81
+ open: v,
82
+ onOpenChange: y,
83
+ children: /* @__PURE__ */ d(r, {
84
+ className: "sm:max-w-3xl",
85
+ "data-ckui": "page-picker-dialog",
86
+ children: [
87
+ /* @__PURE__ */ u(i, { children: /* @__PURE__ */ u(a, { children: o }) }),
88
+ m.length > 0 && /* @__PURE__ */ u("div", {
89
+ className: "flex flex-wrap gap-2",
90
+ children: m.map((e) => /* @__PURE__ */ u(t, {
91
+ type: "button",
92
+ size: "sm",
93
+ variant: e.value === p ? "default" : "outline",
94
+ onClick: () => C(e.value),
95
+ children: e.label
96
+ }, e.value))
97
+ }),
98
+ /* @__PURE__ */ u("ul", {
99
+ className: "grid max-h-[60vh] grid-cols-[repeat(auto-fill,minmax(5.5rem,1fr))] gap-2 overflow-y-auto p-1",
100
+ children: s.map((e, t) => /* @__PURE__ */ u("li", { children: /* @__PURE__ */ d("button", {
101
+ type: "button",
102
+ "aria-label": _("pagePicker.pageNamed", {
103
+ n: t + 1,
104
+ name: e.name
105
+ }),
106
+ "aria-pressed": e.name === p,
107
+ className: f("grid w-full gap-1 rounded-md p-1 text-center text-xs tabular-nums outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring", e.name === p && "bg-muted ring-2 ring-primary"),
108
+ onClick: () => C(e.name),
109
+ children: [/* @__PURE__ */ u("span", {
110
+ className: "flex aspect-[7071/10000] items-center justify-center overflow-hidden rounded-sm bg-muted text-muted-foreground",
111
+ children: e.url ? /* @__PURE__ */ u("img", {
112
+ src: e.url,
113
+ alt: "",
114
+ loading: "lazy",
115
+ className: "size-full object-cover"
116
+ }) : t + 1
117
+ }), t + 1]
118
+ }) }, e.name))
119
+ })
120
+ ]
121
+ })
122
+ })] });
123
+ }
124
+ function b({ url: n, label: r }) {
125
+ let { t: i } = e(), a = s(null), [f, g] = c(!1);
126
+ return o(() => () => a.current?.pause(), [n]), n ? /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ u(t, {
127
+ type: "button",
128
+ size: "icon-sm",
129
+ variant: "ghost",
130
+ "aria-label": i(f ? "pagePicker.pause" : "pagePicker.play", { name: r }),
131
+ "aria-pressed": f,
132
+ onClick: () => {
133
+ let e = a.current;
134
+ if (e) {
135
+ if (e.paused) {
136
+ for (let t of document.querySelectorAll("audio[data-ckui=audio-preview]")) t !== e && t.pause();
137
+ e.play().catch(() => g(!1));
138
+ } else e.pause();
139
+ }
140
+ },
141
+ children: /* @__PURE__ */ u(h, {
142
+ icon: f ? p : m,
143
+ strokeWidth: 2,
144
+ className: "size-4"
145
+ })
146
+ }), /* @__PURE__ */ u("audio", {
147
+ ref: a,
148
+ src: n,
149
+ preload: "none",
150
+ "data-ckui": "audio-preview",
151
+ onPlay: () => g(!0),
152
+ onPause: () => g(!1),
153
+ onEnded: () => g(!1)
154
+ })] }) : null;
155
+ }
156
+ //#endregion
157
+ export { b as AudioPreview, y as PagePicker, v as usePlanMedia };
158
+
159
+ //# sourceMappingURL=page-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-picker.js","names":[],"sources":["../../../src/components/admin/page-picker.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { PauseIcon, PlayIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ReaderPlan } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from \"#ckui/ui/dialog\";\n\n/** One choosable page: its name, 1-based number and thumbnail when signed. */\nexport interface PickerPage {\n name: string;\n url?: string;\n}\n\n/** A non-page choice shown above the grid (an anchor, \"start\"). */\nexport interface PickerExtra {\n value: string;\n label: string;\n}\n\nexport interface PlanMedia {\n /** Each published page in reading order, with its thumbnail URL once read. */\n pages: PickerPage[];\n /** Audio track name → playable URL. */\n audio: ReadonlyMap<string, string>;\n}\n\n// The smallest rendition: \"thumb\" when configured, else the last preset by name.\nconst thumbOf = (files: Record<string, string>) => files.thumb ?? files[Object.keys(files).sort().at(-1) ?? \"\"];\nconst dir = (path: string) => path.slice(0, path.lastIndexOf(\"/\") + 1);\n\n/** A reader plan's pages with thumbnails and its audio with URLs, read once per plan (thumbnails are a convenience: names work without them). */\nexport function usePlanMedia(plan: ReaderPlan | null, client: ContentKitClient): PlanMedia {\n const names = (plan?.segments ?? []).flatMap((s) => (s.pages ?? []).map((p) => p.name));\n const [urls, setUrls] = useState<{ plan: ReaderPlan | null; pages: Map<string, string>; audio: Map<string, string> }>({ plan: null, pages: new Map(), audio: new Map() });\n useEffect(() => {\n if (!plan) return;\n const ctrl = new AbortController();\n const pages = new Map<string, string>(),\n audio = new Map<string, string>();\n const reads = plan.segments.flatMap((s) => {\n const first = s.pages?.[0] && thumbOf(s.pages[0].files);\n if (!s.item || !first) return [];\n return [\n client.media.read(s.item, { prefix: dir(first), limit: s.pages!.length, signal: ctrl.signal }).then((r) => {\n const byPath = new Map(r.files.flatMap((f) => (f.url ? [[f.path, f.url] as const] : [])));\n for (const p of s.pages ?? []) {\n const url = byPath.get(thumbOf(p.files) ?? \"\");\n if (url) pages.set(p.name, url);\n }\n }),\n ];\n });\n const playback = plan.playback;\n if (playback?.tracks.length)\n reads.push(\n client.media.read(playback.item, { limit: playback.tracks.length, signal: ctrl.signal }).then((r) => {\n const byPath = new Map(r.files.flatMap((f) => (f.url ? [[f.path, f.url] as const] : [])));\n for (const t of playback.tracks) {\n const url = byPath.get(t.path);\n if (url) audio.set(t.name, url);\n }\n }),\n );\n void Promise.allSettled(reads).then(() => !ctrl.signal.aborted && setUrls({ plan, pages, audio }));\n return () => ctrl.abort();\n }, [plan, client]);\n const ready = urls.plan === plan;\n return { pages: names.map((name) => ({ name, url: ready ? urls.pages.get(name) : undefined })), audio: ready ? urls.audio : new Map() };\n}\n\n/**\n * Picks one page from thumbnails: the trigger shows the current page, the\n * dialog the whole version in reading order. value is a page name or one of\n * extras' values.\n */\nexport function PagePicker({ label, pages, value, extras = [], onChange, className }: {\n label: string;\n pages: readonly PickerPage[];\n value: string;\n extras?: readonly PickerExtra[];\n onChange: (value: string) => void;\n className?: string;\n}) {\n const { t } = useMessages();\n const [open, setOpen] = useState(false);\n const index = pages.findIndex((p) => p.name === value);\n const current = index >= 0 ? pages[index] : undefined;\n const extra = extras.find((e) => e.value === value);\n const choose = (v: string) => {\n onChange(v);\n setOpen(false);\n };\n return (\n <>\n <Button type=\"button\" size=\"sm\" variant=\"outline\" aria-label={label} className={cn(\"h-auto gap-2 py-1 ps-1\", className)} onClick={() => setOpen(true)} data-ckui=\"page-picker\">\n <span className=\"flex h-10 w-7 shrink-0 items-center justify-center overflow-hidden rounded-sm bg-muted text-[10px] tabular-nums text-muted-foreground\">\n {current?.url ? <img src={current.url} alt=\"\" className=\"size-full object-cover\" /> : current ? index + 1 : null}\n </span>\n <span className=\"tabular-nums\">{extra ? extra.label : current ? t(\"pagePicker.page\", { n: index + 1 }) : t(\"pagePicker.choose\")}</span>\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent className=\"sm:max-w-3xl\" data-ckui=\"page-picker-dialog\">\n <DialogHeader>\n <DialogTitle>{label}</DialogTitle>\n </DialogHeader>\n {extras.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {extras.map((e) => (\n <Button key={e.value} type=\"button\" size=\"sm\" variant={e.value === value ? \"default\" : \"outline\"} onClick={() => choose(e.value)}>\n {e.label}\n </Button>\n ))}\n </div>\n )}\n <ul className=\"grid max-h-[60vh] grid-cols-[repeat(auto-fill,minmax(5.5rem,1fr))] gap-2 overflow-y-auto p-1\">\n {pages.map((p, i) => (\n <li key={p.name}>\n <button\n type=\"button\"\n aria-label={t(\"pagePicker.pageNamed\", { n: i + 1, name: p.name })}\n aria-pressed={p.name === value}\n className={cn(\n \"grid w-full gap-1 rounded-md p-1 text-center text-xs tabular-nums outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring\",\n p.name === value && \"bg-muted ring-2 ring-primary\",\n )}\n onClick={() => choose(p.name)}\n >\n <span className=\"flex aspect-[7071/10000] items-center justify-center overflow-hidden rounded-sm bg-muted text-muted-foreground\">\n {p.url ? <img src={p.url} alt=\"\" loading=\"lazy\" className=\"size-full object-cover\" /> : i + 1}\n </span>\n {i + 1}\n </button>\n </li>\n ))}\n </ul>\n </DialogContent>\n </Dialog>\n </>\n );\n}\n\n/** A play/pause button for one audio URL; one preview plays at a time. */\nexport function AudioPreview({ url, label }: { url?: string; label: string }) {\n const { t } = useMessages();\n const audio = useRef<HTMLAudioElement | null>(null);\n const [playing, setPlaying] = useState(false);\n useEffect(() => () => audio.current?.pause(), [url]);\n if (!url) return null;\n const toggle = () => {\n const a = audio.current;\n if (!a) return;\n if (a.paused) {\n for (const other of document.querySelectorAll<HTMLAudioElement>(\"audio[data-ckui=audio-preview]\")) if (other !== a) other.pause();\n void a.play().catch(() => setPlaying(false));\n } else a.pause();\n };\n return (\n <>\n <Button type=\"button\" size=\"icon-sm\" variant=\"ghost\" aria-label={t(playing ? \"pagePicker.pause\" : \"pagePicker.play\", { name: label })} aria-pressed={playing} onClick={toggle}>\n <HugeiconsIcon icon={playing ? PauseIcon : PlayIcon} strokeWidth={2} className=\"size-4\" />\n </Button>\n <audio ref={audio} src={url} preload=\"none\" data-ckui=\"audio-preview\" onPlay={() => setPlaying(true)} onPause={() => setPlaying(false)} onEnded={() => setPlaying(false)} />\n </>\n );\n}\n"],"mappings":";;;;;;;;;AA8BA,IAAM,KAAW,MAAkC,EAAM,SAAS,EAAM,OAAO,KAAK,CAAK,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,KAAK,KACtG,KAAO,MAAiB,EAAK,MAAM,GAAG,EAAK,YAAY,GAAG,IAAI,CAAC;AAGrE,SAAgB,EAAa,GAAyB,GAAqC;CACzF,IAAM,KAAS,GAAM,YAAY,CAAC,EAAA,CAAG,SAAS,OAAO,EAAE,SAAS,CAAC,EAAA,CAAG,KAAK,MAAM,EAAE,IAAI,CAAC,GAChF,CAAC,GAAM,KAAW,EAA8F;EAAE,MAAM;EAAM,uBAAO,IAAI,IAAI;EAAG,uBAAO,IAAI,IAAI;CAAE,CAAC;CACxK,QAAgB;EACd,IAAI,CAAC,GAAM;EACX,IAAM,IAAO,IAAI,gBAAgB,GAC3B,oBAAQ,IAAI,IAAoB,GACpC,oBAAQ,IAAI,IAAoB,GAC5B,IAAQ,EAAK,SAAS,SAAS,MAAM;GACzC,IAAM,IAAQ,EAAE,QAAQ,MAAM,EAAQ,EAAE,MAAM,EAAE,CAAC,KAAK;GAEtD,OADI,CAAC,EAAE,QAAQ,CAAC,IAAc,CAAC,IACxB,CACL,EAAO,MAAM,KAAK,EAAE,MAAM;IAAE,QAAQ,EAAI,CAAK;IAAG,OAAO,EAAE,MAAO;IAAQ,QAAQ,EAAK;GAAO,CAAC,CAAC,CAAC,MAAM,MAAM;IACzG,IAAM,IAAS,IAAI,IAAI,EAAE,MAAM,SAAS,MAAO,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAU,IAAI,CAAC,CAAE,CAAC;IACxF,KAAK,IAAM,KAAK,EAAE,SAAS,CAAC,GAAG;KAC7B,IAAM,IAAM,EAAO,IAAI,EAAQ,EAAE,KAAK,KAAK,EAAE;KAC7C,AAAI,KAAK,EAAM,IAAI,EAAE,MAAM,CAAG;IAChC;GACF,CAAC,CACH;EACF,CAAC,GACK,IAAW,EAAK;EAYtB,OAXI,GAAU,OAAO,UACnB,EAAM,KACJ,EAAO,MAAM,KAAK,EAAS,MAAM;GAAE,OAAO,EAAS,OAAO;GAAQ,QAAQ,EAAK;EAAO,CAAC,CAAC,CAAC,MAAM,MAAM;GACnG,IAAM,IAAS,IAAI,IAAI,EAAE,MAAM,SAAS,MAAO,EAAE,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAU,IAAI,CAAC,CAAE,CAAC;GACxF,KAAK,IAAM,KAAK,EAAS,QAAQ;IAC/B,IAAM,IAAM,EAAO,IAAI,EAAE,IAAI;IAC7B,AAAI,KAAK,EAAM,IAAI,EAAE,MAAM,CAAG;GAChC;EACF,CAAC,CACH,GACF,QAAa,WAAW,CAAK,CAAC,CAAC,WAAW,CAAC,EAAK,OAAO,WAAW,EAAQ;GAAE;GAAM;GAAO;EAAM,CAAC,CAAC,SACpF,EAAK,MAAM;CAC1B,GAAG,CAAC,GAAM,CAAM,CAAC;CACjB,IAAM,IAAQ,EAAK,SAAS;CAC5B,OAAO;EAAE,OAAO,EAAM,KAAK,OAAU;GAAE;GAAM,KAAK,IAAQ,EAAK,MAAM,IAAI,CAAI,IAAI,KAAA;EAAU,EAAE;EAAG,OAAO,IAAQ,EAAK,wBAAQ,IAAI,IAAI;CAAE;AACxI;AAOA,SAAgB,EAAW,EAAE,UAAO,UAAO,UAAO,YAAS,CAAC,GAAG,aAAU,gBAOtE;CACD,IAAM,EAAE,SAAM,EAAY,GACpB,CAAC,GAAM,KAAW,EAAS,EAAK,GAChC,IAAQ,EAAM,WAAW,MAAM,EAAE,SAAS,CAAK,GAC/C,IAAU,KAAS,IAAI,EAAM,KAAS,KAAA,GACtC,IAAQ,EAAO,MAAM,MAAM,EAAE,UAAU,CAAK,GAC5C,KAAU,MAAc;EAE5B,AADA,EAAS,CAAC,GACV,EAAQ,EAAK;CACf;CACA,OACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EAAQ,MAAK;EAAS,MAAK;EAAK,SAAQ;EAAU,cAAY;EAAO,WAAW,EAAG,0BAA0B,CAAS;EAAG,eAAe,EAAQ,EAAI;EAAG,aAAU;EAAjK,UAAA,CACE,kBAAC,QAAD;GAAM,WAAU;GACb,UAAA,GAAS,MAAM,kBAAC,OAAD;IAAK,KAAK,EAAQ;IAAK,KAAI;IAAG,WAAU;GAA0B,CAAA,IAAI,IAAU,IAAQ,IAAI;EACxG,CAAA,GACN,kBAAC,QAAD;GAAM,WAAU;GAAgB,UAAA,IAAQ,EAAM,QAAQ,IAAU,EAAE,mBAAmB,EAAE,GAAG,IAAQ,EAAE,CAAC,IAAI,EAAE,mBAAmB;EAAQ,CAAA,CAChI;CACR,CAAA,GAAA,kBAAC,GAAD;EAAc;EAAM,cAAc;EAChC,UAAA,kBAAC,GAAD;GAAe,WAAU;GAAe,aAAU;GAAlD,UAAA;IACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAc,EAAmB,CAAA,EACrB,CAAA;IACb,EAAO,SAAS,KACf,kBAAC,OAAD;KAAK,WAAU;KACZ,UAAA,EAAO,KAAK,MACX,kBAAC,GAAD;MAAsB,MAAK;MAAS,MAAK;MAAK,SAAS,EAAE,UAAU,IAAQ,YAAY;MAAW,eAAe,EAAO,EAAE,KAAK;MAC5H,UAAA,EAAE;KACG,GAFK,EAAE,KAEP,CACT;IACE,CAAA;IAEP,kBAAC,MAAD;KAAI,WAAU;KACX,UAAA,EAAM,KAAK,GAAG,MACb,kBAAC,MAAD,EAAA,UACE,kBAAC,UAAD;MACE,MAAK;MACL,cAAY,EAAE,wBAAwB;OAAE,GAAG,IAAI;OAAG,MAAM,EAAE;MAAK,CAAC;MAChE,gBAAc,EAAE,SAAS;MACzB,WAAW,EACT,8IACA,EAAE,SAAS,KAAS,8BACtB;MACA,eAAe,EAAO,EAAE,IAAI;MAR9B,UAAA,CAUE,kBAAC,QAAD;OAAM,WAAU;OACb,UAAA,EAAE,MAAM,kBAAC,OAAD;QAAK,KAAK,EAAE;QAAK,KAAI;QAAG,SAAQ;QAAO,WAAU;OAA0B,CAAA,IAAI,IAAI;MACxF,CAAA,GACL,IAAI,CACC;KACN,CAAA,EAAA,GAhBK,EAAE,IAgBP,CACL;IACC,CAAA;GACS;;CACT,CAAA,CACR,EAAA,CAAA;AAEN;AAGA,SAAgB,EAAa,EAAE,QAAK,YAA0C;CAC5E,IAAM,EAAE,SAAM,EAAY,GACpB,IAAQ,EAAgC,IAAI,GAC5C,CAAC,GAAS,KAAc,EAAS,EAAK;CAW5C,OAVA,cAAsB,EAAM,SAAS,MAAM,GAAG,CAAC,CAAG,CAAC,GAC9C,IAUH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EAAQ,MAAK;EAAS,MAAK;EAAU,SAAQ;EAAQ,cAAY,EAAE,IAAU,qBAAqB,mBAAmB,EAAE,MAAM,EAAM,CAAC;EAAG,gBAAc;EAAS,eAV7I;GACnB,IAAM,IAAI,EAAM;GACX,OACL;QAAI,EAAE,QAAQ;KACZ,KAAK,IAAM,KAAS,SAAS,iBAAmC,gCAAgC,GAAG,AAAI,MAAU,KAAG,EAAM,MAAM;KAChI,EAAO,KAAK,CAAC,CAAC,YAAY,EAAW,EAAK,CAAC;IAC7C,OAAO,EAAE,MAAM;GAAA;EACjB;EAIM,UAAA,kBAAC,GAAD;GAAe,MAAM,IAAU,IAAY;GAAU,aAAa;GAAG,WAAU;EAAU,CAAA;CACnF,CAAA,GACR,kBAAC,SAAD;EAAO,KAAK;EAAO,KAAK;EAAK,SAAQ;EAAO,aAAU;EAAgB,cAAc,EAAW,EAAI;EAAG,eAAe,EAAW,EAAK;EAAG,eAAe,EAAW,EAAK;CAAI,CAAA,CAC3K,EAAA,CAAA,IAfa;AAiBnB"}
@@ -0,0 +1,14 @@
1
+ import { Card, GroupCard } from '../../client/generated/wire.js';
2
+ import { SearchBoxProps } from '../content/search.js';
3
+ export interface AdminQuickSearchProps extends Omit<SearchBoxProps, "onSearch" | "href" | "kind" | "groupKinds" | "recent" | "requestSuggestions" | "selectionOnly" | "disabled"> {
4
+ /** Content kinds for which the host offers an authorized editor. */
5
+ contentKinds: readonly string[];
6
+ /** Entity kinds for which the host offers an authorized editor. */
7
+ entityKinds: readonly string[];
8
+ /** An editor URL; returning undefined omits that suggestion. Public paths are never used. */
9
+ contentHref: (item: Card) => string | undefined;
10
+ entityHref: (entity: GroupCard) => string | undefined;
11
+ disabled?: boolean;
12
+ }
13
+ /** Quick navigation to authorized editors, with the same combobox and header geometry as public search. */
14
+ export declare function AdminQuickSearch(p: AdminQuickSearchProps): import('react').JSX.Element;
@@ -0,0 +1,60 @@
1
+ import { useContentScope as e } from "../../react/use-resource.js";
2
+ import { SearchBox as t } from "../content/search.js";
3
+ import { useCallback as n, useState as r } from "react";
4
+ import { jsx as i } from "react/jsx-runtime";
5
+ //#region src/components/admin/quick-search.tsx
6
+ function a(t) {
7
+ let { client: n, scope: a } = e(t.client), s = t.disabled || t.contentKinds.length === 0 && t.entityKinds.length === 0, c = JSON.stringify([t.contentKinds, t.entityKinds]), [l, u] = r({
8
+ client: n,
9
+ scope: a,
10
+ disabled: s,
11
+ targets: c,
12
+ key: 0
13
+ }), d = l;
14
+ return (l.client !== n || l.scope !== a || l.disabled !== s || l.targets !== c) && (d = {
15
+ client: n,
16
+ scope: a,
17
+ disabled: s,
18
+ targets: c,
19
+ key: l.key + 1
20
+ }, u(d)), /* @__PURE__ */ i(o, {
21
+ ...t,
22
+ client: n,
23
+ disabled: s
24
+ }, d.key);
25
+ }
26
+ function o({ contentKinds: e, entityKinds: r, contentHref: a, entityHref: o, ...s }) {
27
+ let c = n(async (t, n) => {
28
+ let i = await s.client.discovery.adminSuggestions({
29
+ q: t.q,
30
+ display_language: t.display_language,
31
+ content_kinds: e,
32
+ entity_kinds: r,
33
+ include_contents: e.length > 0,
34
+ include_entities: r.length > 0,
35
+ limit: t.limit
36
+ }, n);
37
+ return {
38
+ contents: i.contents.filter((t) => e.includes(t.kind) && !!a(t)),
39
+ entities: i.entities.filter((e) => r.includes(e.kind) && !!o(e))
40
+ };
41
+ }, [
42
+ s.client,
43
+ e,
44
+ r,
45
+ a,
46
+ o
47
+ ]);
48
+ return /* @__PURE__ */ i(t, {
49
+ ...s,
50
+ recent: !1,
51
+ selectionOnly: !0,
52
+ requestSuggestions: c,
53
+ onSearch: () => {},
54
+ href: (e) => "title" in e ? a(e) : o(e)
55
+ });
56
+ }
57
+ //#endregion
58
+ export { a as AdminQuickSearch };
59
+
60
+ //# sourceMappingURL=quick-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quick-search.js","names":[],"sources":["../../../src/components/admin/quick-search.tsx"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { TypeaheadQuery } from \"../../client/discovery.js\";\nimport type { Card, GroupCard } from \"../../client/generated/wire.js\";\nimport { useContentScope } from \"../../react/use-resource.js\";\nimport { SearchBox, type SearchBoxProps } from \"../content/search.js\";\n\nexport interface AdminQuickSearchProps extends Omit<SearchBoxProps, \"onSearch\" | \"href\" | \"kind\" | \"groupKinds\" | \"recent\" | \"requestSuggestions\" | \"selectionOnly\" | \"disabled\"> {\n /** Content kinds for which the host offers an authorized editor. */\n contentKinds: readonly string[];\n /** Entity kinds for which the host offers an authorized editor. */\n entityKinds: readonly string[];\n /** An editor URL; returning undefined omits that suggestion. Public paths are never used. */\n contentHref: (item: Card) => string | undefined;\n entityHref: (entity: GroupCard) => string | undefined;\n disabled?: boolean;\n}\n\n/** Quick navigation to authorized editors, with the same combobox and header geometry as public search. */\nexport function AdminQuickSearch(p: AdminQuickSearchProps) {\n const { client, scope } = useContentScope(p.client);\n const disabled = p.disabled || (p.contentKinds.length === 0 && p.entityKinds.length === 0);\n const targets = JSON.stringify([p.contentKinds, p.entityKinds]);\n const [boundary, setBoundary] = useState({ client, scope, disabled, targets, key: 0 });\n let current = boundary;\n if (boundary.client !== client || boundary.scope !== scope || boundary.disabled !== disabled || boundary.targets !== targets) {\n current = { client, scope, disabled, targets, key: boundary.key + 1 };\n setBoundary(current);\n }\n return <QuickSearch key={current.key} {...p} client={client} disabled={disabled} />;\n}\n\nfunction QuickSearch({ contentKinds, entityKinds, contentHref, entityHref, ...p }: AdminQuickSearchProps & { client: ContentKitClient }) {\n const request = useCallback(async (query: TypeaheadQuery, signal: AbortSignal) => {\n const found = await p.client.discovery.adminSuggestions({\n q: query.q,\n display_language: query.display_language,\n content_kinds: contentKinds,\n entity_kinds: entityKinds,\n include_contents: contentKinds.length > 0,\n include_entities: entityKinds.length > 0,\n limit: query.limit,\n }, signal);\n return {\n contents: found.contents.filter((item) => contentKinds.includes(item.kind) && !!contentHref(item)),\n entities: found.entities.filter((entity) => entityKinds.includes(entity.kind) && !!entityHref(entity)),\n };\n }, [p.client, contentKinds, entityKinds, contentHref, entityHref]);\n return <SearchBox {...p} recent={false} selectionOnly requestSuggestions={request} onSearch={() => {}}\n href={(item) => \"title\" in item ? contentHref(item) : entityHref(item)} />;\n}\n"],"mappings":";;;;;AAmBA,SAAgB,EAAiB,GAA0B;CACzD,IAAM,EAAE,WAAQ,aAAU,EAAgB,EAAE,MAAM,GAC5C,IAAW,EAAE,YAAa,EAAE,aAAa,WAAW,KAAK,EAAE,YAAY,WAAW,GAClF,IAAU,KAAK,UAAU,CAAC,EAAE,cAAc,EAAE,WAAW,CAAC,GACxD,CAAC,GAAU,KAAe,EAAS;EAAE;EAAQ;EAAO;EAAU;EAAS,KAAK;CAAE,CAAC,GACjF,IAAU;CAKd,QAJI,EAAS,WAAW,KAAU,EAAS,UAAU,KAAS,EAAS,aAAa,KAAY,EAAS,YAAY,OACnH,IAAU;EAAE;EAAQ;EAAO;EAAU;EAAS,KAAK,EAAS,MAAM;CAAE,GACpE,EAAY,CAAO,IAEd,kBAAC,GAAD;EAA+B,GAAI;EAAW;EAAkB;CAAW,GAAzD,EAAQ,GAAiD;AACpF;AAEA,SAAS,EAAY,EAAE,iBAAc,gBAAa,gBAAa,eAAY,GAAG,KAA2D;CACvI,IAAM,IAAU,EAAY,OAAO,GAAuB,MAAwB;EAChF,IAAM,IAAQ,MAAM,EAAE,OAAO,UAAU,iBAAiB;GACtD,GAAG,EAAM;GACT,kBAAkB,EAAM;GACxB,eAAe;GACf,cAAc;GACd,kBAAkB,EAAa,SAAS;GACxC,kBAAkB,EAAY,SAAS;GACvC,OAAO,EAAM;EACf,GAAG,CAAM;EACT,OAAO;GACL,UAAU,EAAM,SAAS,QAAQ,MAAS,EAAa,SAAS,EAAK,IAAI,KAAK,CAAC,CAAC,EAAY,CAAI,CAAC;GACjG,UAAU,EAAM,SAAS,QAAQ,MAAW,EAAY,SAAS,EAAO,IAAI,KAAK,CAAC,CAAC,EAAW,CAAM,CAAC;EACvG;CACF,GAAG;EAAC,EAAE;EAAQ;EAAc;EAAa;EAAa;CAAU,CAAC;CACjE,OAAO,kBAAC,GAAD;EAAW,GAAI;EAAG,QAAQ;EAAO,eAAA;EAAc,oBAAoB;EAAS,gBAAgB,CAAC;EAClG,OAAO,MAAS,WAAW,IAAO,EAAY,CAAI,IAAI,EAAW,CAAI;CAAI,CAAA;AAC7E"}