@openrails/contentkit-ui 0.89.2 → 0.90.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/dist/components/admin/content-admin.d.ts +28 -0
- package/dist/components/admin/content-admin.js +191 -0
- package/dist/components/admin/content-admin.js.map +1 -0
- package/dist/components/content/search.js +1 -1
- package/dist/components/content/search.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +11 -10
- package/dist/locales/admin/de.js +10 -0
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +10 -0
- package/dist/locales/admin/en.js +10 -0
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +10 -0
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +10 -0
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +10 -0
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +10 -0
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/en.d.ts +10 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -473,6 +473,61 @@ const content = useContentGroups(contentId); // content.set(memberships, kinds)
|
|
|
473
473
|
show humanized. Covers render `cover.renditions`; `renderCover` stands in
|
|
474
474
|
until group reads carry them.
|
|
475
475
|
|
|
476
|
+
## Reference administration workbench
|
|
477
|
+
|
|
478
|
+
`ContentAdmin` is an optional library component composing the existing content,
|
|
479
|
+
group, moderation, poll and statistics managers. It is not a standalone server
|
|
480
|
+
portal, a sign-in page or a SaaS control plane. A host can continue to compose
|
|
481
|
+
any individual manager in its own administration area.
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
import { ContentAdmin } from "@openrails/contentkit-ui";
|
|
485
|
+
import { richTextEditor } from "@openrails/contentkit-ui/editor";
|
|
486
|
+
import "@openrails/contentkit-ui/styles.css";
|
|
487
|
+
|
|
488
|
+
<ContentAdmin
|
|
489
|
+
client={contentkit}
|
|
490
|
+
basePath="/admin/content"
|
|
491
|
+
navigation={{ pathname, search, navigate }}
|
|
492
|
+
appearance={{ theme: "inherit" }}
|
|
493
|
+
sections={["contents", "groups", "moderation", "polls", "statistics"]}
|
|
494
|
+
contentKinds={["article", "video"]}
|
|
495
|
+
labels={{ article: "Articles", video: "Videos" }}
|
|
496
|
+
content={{ renderEditor: richTextEditor }}
|
|
497
|
+
/>;
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
The client is an existing ContentKit client, or pass `baseUrl` and the host's
|
|
501
|
+
`auth` transport. `guard`, `language`, `messages` and `onError` follow the
|
|
502
|
+
individual managers' contracts below. Without `navigation`, the workbench uses
|
|
503
|
+
browser history; without `basePath` as well, navigation stays in memory.
|
|
504
|
+
|
|
505
|
+
`GET /kinds` supplies languages and configured content and group kinds. Omit
|
|
506
|
+
`contentKinds` to offer all content kinds; omit `groupKinds` to offer the
|
|
507
|
+
site-owned group kinds. Explicit lists restrict and order those choices, and
|
|
508
|
+
unknown kinds are omitted. The routes beneath `basePath` are
|
|
509
|
+
`contents/<kind>[/new|/<id>]`, `groups/<kind>[/new|/<id>]`, `moderation`,
|
|
510
|
+
`polls[/new|/<id>]` and `statistics`; each manager retains its own filters and
|
|
511
|
+
subviews. Unknown or excluded routes show an unavailable view and do not load
|
|
512
|
+
a manager. Only the selected manager mounts. Navigation entries describe
|
|
513
|
+
configured sections, not permission grants: a selected manager can show its
|
|
514
|
+
normal no-access state. Statistics uses its own read permission, independently
|
|
515
|
+
of content editing.
|
|
516
|
+
|
|
517
|
+
The optional `content` props are `renderEditor` and `contentHref`; `groups`
|
|
518
|
+
accepts `renderContent`; `moderation` accepts `itemHref`, `userHref` and
|
|
519
|
+
`sections`; `polls` accepts `freeText` and `maxOptions`. These are the existing
|
|
520
|
+
managers' extension points. Without `renderEditor`, body editing uses the
|
|
521
|
+
manager's HTML field; the rich text editor is an optional peer dependency.
|
|
522
|
+
Changing the client or the provider's viewer/access/language scope resets the
|
|
523
|
+
workbench's local navigation and unsaved forms. A URL already selected by the
|
|
524
|
+
host stays selected and is checked again under the new scope. When reusing
|
|
525
|
+
a transport across sign-ins, keep `ContentKitProvider`'s `viewer` and
|
|
526
|
+
`accessRevision` current, or remount the workbench. Media and
|
|
527
|
+
version editing remain in the content editor. Site-specific import,
|
|
528
|
+
review and publishing workflows can use the individual managers and media
|
|
529
|
+
components without adopting this workbench.
|
|
530
|
+
|
|
476
531
|
## Admin flows
|
|
477
532
|
|
|
478
533
|
Drop-in staff screens for a host's own admin area: each makes its own reads
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Labels } from '../groups/parts.js';
|
|
2
|
+
import { CommentModerationProps } from '../social/moderation.js';
|
|
3
|
+
import { ContentManagerProps } from './content-manager.js';
|
|
4
|
+
import { GroupManagerProps } from './group-manager.js';
|
|
5
|
+
import { PollManagerProps } from './poll-manager.js';
|
|
6
|
+
import { AdminFlowProps, AdminRouteProps } from './shell.js';
|
|
7
|
+
export type ContentAdminSection = "contents" | "groups" | "moderation" | "polls" | "statistics";
|
|
8
|
+
/** An optional reference workbench; each section remains usable as its own component. */
|
|
9
|
+
export interface ContentAdminProps extends AdminFlowProps, AdminRouteProps {
|
|
10
|
+
/** Sections in navigation order. Default all. Navigation does not grant access. */
|
|
11
|
+
sections?: readonly ContentAdminSection[];
|
|
12
|
+
/** Content kinds in picker order; default all configured kinds. Unknown kinds are omitted. */
|
|
13
|
+
contentKinds?: readonly string[];
|
|
14
|
+
/** Group kinds in picker order; default the configured site-owned kinds. Unknown kinds are omitted. */
|
|
15
|
+
groupKinds?: readonly string[];
|
|
16
|
+
labels?: Labels;
|
|
17
|
+
content?: Pick<ContentManagerProps, "renderEditor" | "contentHref">;
|
|
18
|
+
groups?: Pick<GroupManagerProps, "renderContent">;
|
|
19
|
+
moderation?: Pick<CommentModerationProps, "itemHref" | "userHref" | "sections">;
|
|
20
|
+
polls?: Pick<PollManagerProps, "freeText" | "maxOptions">;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Model-driven content, group, moderation, poll and statistics screens, with
|
|
24
|
+
* the host's transport, router and theme. Only the selected manager mounts;
|
|
25
|
+
* its native permissions and refusals remain authoritative. This component
|
|
26
|
+
* does not mount a server portal, sign users in or define a publishing workflow.
|
|
27
|
+
*/
|
|
28
|
+
export declare function ContentAdmin({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: ContentAdminProps): import('react').JSX.Element;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { useMessages as e } from "../../i18n/context.js";
|
|
2
|
+
import { useReadScope as t } from "../../react/context.js";
|
|
3
|
+
import { useKinds as n } from "../../react/groups.js";
|
|
4
|
+
import { Button as r } from "../../ui/button.js";
|
|
5
|
+
import { AdminRoot as i, LoadFailed as a, Rows as o } from "./shell.js";
|
|
6
|
+
import { useAdminLocation as s, useFlowClient as c } from "../../react/admin.js";
|
|
7
|
+
import { CommentModeration as l } from "../social/moderation.js";
|
|
8
|
+
import { labelOf as u } from "../groups/parts.js";
|
|
9
|
+
import { ContentManager as d } from "./content-manager.js";
|
|
10
|
+
import { GroupManager as f } from "./group-manager.js";
|
|
11
|
+
import { PollManager as p } from "./poll-manager.js";
|
|
12
|
+
import { StatsDashboard as m } from "./stats-dashboard.js";
|
|
13
|
+
import { useId as h, useState as g } from "react";
|
|
14
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
15
|
+
//#region src/components/admin/content-admin.tsx
|
|
16
|
+
var b = [
|
|
17
|
+
"contents",
|
|
18
|
+
"groups",
|
|
19
|
+
"moderation",
|
|
20
|
+
"polls",
|
|
21
|
+
"statistics"
|
|
22
|
+
], x = [
|
|
23
|
+
"comments",
|
|
24
|
+
"queue",
|
|
25
|
+
"bans",
|
|
26
|
+
"words",
|
|
27
|
+
"ips"
|
|
28
|
+
];
|
|
29
|
+
function S({ client: e, baseUrl: n, auth: r, guard: a, appearance: o, className: s, language: l, messages: u, ...d }) {
|
|
30
|
+
let f = c({
|
|
31
|
+
client: e,
|
|
32
|
+
baseUrl: n,
|
|
33
|
+
auth: r
|
|
34
|
+
}), p = t(), [m, h] = g({
|
|
35
|
+
client: f,
|
|
36
|
+
scope: p,
|
|
37
|
+
revision: 0
|
|
38
|
+
});
|
|
39
|
+
return (m.client !== f || m.scope !== p) && h({
|
|
40
|
+
client: f,
|
|
41
|
+
scope: p,
|
|
42
|
+
revision: m.revision + 1
|
|
43
|
+
}), /* @__PURE__ */ v(i, {
|
|
44
|
+
flow: "content-admin",
|
|
45
|
+
guard: a,
|
|
46
|
+
appearance: o,
|
|
47
|
+
className: s,
|
|
48
|
+
language: l,
|
|
49
|
+
messages: u,
|
|
50
|
+
children: /* @__PURE__ */ v(D, {
|
|
51
|
+
...d,
|
|
52
|
+
client: f,
|
|
53
|
+
guard: a
|
|
54
|
+
}, m.revision)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function C(e, t) {
|
|
58
|
+
return [...new Set(e ?? Object.keys(t).sort())].filter((e) => Object.hasOwn(t, e));
|
|
59
|
+
}
|
|
60
|
+
function w(e, t) {
|
|
61
|
+
return C(e ?? Object.keys(t.group_kinds).filter((e) => t.group_kinds[e]?.owner !== "user").sort(), t.group_kinds);
|
|
62
|
+
}
|
|
63
|
+
function T(e) {
|
|
64
|
+
if (e !== void 0) try {
|
|
65
|
+
return decodeURIComponent(e);
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function E(e, t, n) {
|
|
71
|
+
return {
|
|
72
|
+
pathname: `${e}/${t}`,
|
|
73
|
+
search: n.query.size ? `?${n.query}` : "",
|
|
74
|
+
navigate(t, r) {
|
|
75
|
+
let i = new URL(t, "https://contentkit.invalid");
|
|
76
|
+
if (i.origin !== "https://contentkit.invalid") return;
|
|
77
|
+
let a = `${e}/`;
|
|
78
|
+
(i.pathname === e || i.pathname.startsWith(a)) && n.go(i.pathname.slice(a.length), Object.fromEntries(i.searchParams), r);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function D({ client: t, basePath: i, navigation: c, sections: g, contentKinds: S, groupKinds: D, labels: O, guard: k, onError: A, content: j, groups: M, moderation: N, polls: P }) {
|
|
83
|
+
let { t: F } = e(), I = h(), L = s(i, c), R = n({ client: t });
|
|
84
|
+
if (R.loading) return /* @__PURE__ */ v(o, {});
|
|
85
|
+
if (!R.kinds) return /* @__PURE__ */ v(a, {
|
|
86
|
+
error: R.error,
|
|
87
|
+
message: F("contentAdmin.modelFailed"),
|
|
88
|
+
retry: R.reload
|
|
89
|
+
});
|
|
90
|
+
let z = C(S, R.kinds.kinds), B = w(D, R.kinds), V = [...new Set(g ?? b)].filter((e) => b.includes(e) && (e !== "contents" && e !== "statistics" || z.length > 0) && (e !== "groups" || B.length > 0) && (e !== "moderation" || N?.sections?.length !== 0));
|
|
91
|
+
if (!V.length) return /* @__PURE__ */ v("p", {
|
|
92
|
+
role: "status",
|
|
93
|
+
className: "text-muted-foreground",
|
|
94
|
+
children: F("contentAdmin.empty")
|
|
95
|
+
});
|
|
96
|
+
let H = L.view.split("/"), U = L.view ? H[0] : V[0], W = H[1] === void 0 ? void 0 : T(H[1]), G = W ?? z[0], K = N?.sections ?? x, q = V.includes(U) && (U !== "contents" || (H[1] === void 0 || W !== void 0) && z.includes(G)) && (U !== "groups" || H[1] === void 0 || W !== void 0 && B.includes(W)) && (U !== "moderation" || !H[1] || K.includes(H[1])) && (U !== "statistics" || H.length === 1 || !L.view), J = i?.replace(/\/+$/, "") ?? "", Y = U === "contents" ? `contents/${encodeURIComponent(G)}` : U, X = L.view || Y, Z = E(J, U === "contents" && H[1] === void 0 ? Y : X, L), Q = {
|
|
97
|
+
client: t,
|
|
98
|
+
guard: k,
|
|
99
|
+
onError: A
|
|
100
|
+
}, $ = {
|
|
101
|
+
contents: F("contentManager.title"),
|
|
102
|
+
groups: F("contentAdmin.groups"),
|
|
103
|
+
moderation: F("moderation.title"),
|
|
104
|
+
polls: F("pollManager.title"),
|
|
105
|
+
statistics: F("stats.title")
|
|
106
|
+
}, ee = (e) => e === "contents" ? `contents/${encodeURIComponent(z[0])}` : e;
|
|
107
|
+
return /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("h1", {
|
|
108
|
+
className: "text-lg font-semibold",
|
|
109
|
+
children: F("contentAdmin.title")
|
|
110
|
+
}), /* @__PURE__ */ y("div", {
|
|
111
|
+
className: "grid min-w-0 gap-4 sm:grid-cols-[12rem_minmax(0,1fr)]",
|
|
112
|
+
children: [/* @__PURE__ */ v("nav", {
|
|
113
|
+
"aria-label": F("contentAdmin.navigation"),
|
|
114
|
+
className: "flex gap-1 overflow-x-auto sm:grid sm:content-start",
|
|
115
|
+
children: V.map((e) => /* @__PURE__ */ v(r, {
|
|
116
|
+
variant: U === e ? "secondary" : "ghost",
|
|
117
|
+
className: "justify-start whitespace-nowrap",
|
|
118
|
+
"aria-current": U === e ? "page" : void 0,
|
|
119
|
+
onClick: () => L.go(ee(e)),
|
|
120
|
+
children: $[e]
|
|
121
|
+
}, e))
|
|
122
|
+
}), /* @__PURE__ */ v("div", {
|
|
123
|
+
className: "grid min-w-0 content-start gap-4",
|
|
124
|
+
"data-ckui": "content-admin-view",
|
|
125
|
+
children: q ? U === "contents" ? /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ y("label", {
|
|
126
|
+
className: "grid justify-items-start gap-1 text-sm",
|
|
127
|
+
htmlFor: `${I}-kind`,
|
|
128
|
+
children: [F("contentAdmin.kind"), /* @__PURE__ */ v("select", {
|
|
129
|
+
id: `${I}-kind`,
|
|
130
|
+
className: "h-8 rounded-md border border-input bg-background px-2 text-sm",
|
|
131
|
+
value: G,
|
|
132
|
+
onChange: (e) => L.go(`contents/${encodeURIComponent(e.target.value)}`),
|
|
133
|
+
children: z.map((e) => /* @__PURE__ */ v("option", {
|
|
134
|
+
value: e,
|
|
135
|
+
children: u(O, e)
|
|
136
|
+
}, e))
|
|
137
|
+
})]
|
|
138
|
+
}), /* @__PURE__ */ v(d, {
|
|
139
|
+
...Q,
|
|
140
|
+
...j,
|
|
141
|
+
kind: G,
|
|
142
|
+
labels: O,
|
|
143
|
+
languages: R.kinds.languages,
|
|
144
|
+
basePath: `${J}/${Y}`,
|
|
145
|
+
navigation: Z
|
|
146
|
+
}, G)] }) : U === "groups" ? /* @__PURE__ */ v(f, {
|
|
147
|
+
...Q,
|
|
148
|
+
...M,
|
|
149
|
+
labels: O,
|
|
150
|
+
groupKinds: B,
|
|
151
|
+
basePath: `${J}/groups`,
|
|
152
|
+
navigation: Z
|
|
153
|
+
}) : U === "moderation" ? /* @__PURE__ */ v(l, {
|
|
154
|
+
...Q,
|
|
155
|
+
...N,
|
|
156
|
+
contentKinds: z.map((e) => ({
|
|
157
|
+
value: e,
|
|
158
|
+
label: u(O, e)
|
|
159
|
+
})),
|
|
160
|
+
basePath: `${J}/moderation`,
|
|
161
|
+
navigation: Z
|
|
162
|
+
}) : U === "polls" ? /* @__PURE__ */ v(p, {
|
|
163
|
+
...Q,
|
|
164
|
+
...P,
|
|
165
|
+
languages: R.kinds.languages,
|
|
166
|
+
basePath: `${J}/polls`,
|
|
167
|
+
navigation: Z
|
|
168
|
+
}) : /* @__PURE__ */ v(m, {
|
|
169
|
+
...Q,
|
|
170
|
+
kinds: z.map((e) => ({
|
|
171
|
+
value: e,
|
|
172
|
+
label: u(O, e)
|
|
173
|
+
}))
|
|
174
|
+
}) : /* @__PURE__ */ y("div", {
|
|
175
|
+
className: "grid justify-items-start gap-2",
|
|
176
|
+
children: [/* @__PURE__ */ v("p", {
|
|
177
|
+
role: "status",
|
|
178
|
+
children: F("contentAdmin.unavailable")
|
|
179
|
+
}), /* @__PURE__ */ v(r, {
|
|
180
|
+
variant: "outline",
|
|
181
|
+
onClick: () => L.go(""),
|
|
182
|
+
children: F("contentAdmin.back")
|
|
183
|
+
})]
|
|
184
|
+
})
|
|
185
|
+
})]
|
|
186
|
+
})] });
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
export { S as ContentAdmin };
|
|
190
|
+
|
|
191
|
+
//# sourceMappingURL=content-admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-admin.js","names":[],"sources":["../../../src/components/admin/content-admin.tsx"],"sourcesContent":["import { useId, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentModel } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, type AdminLocation, type AdminNavigation } from \"../../react/admin.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport { useReadScope } from \"../../react/context.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { labelOf, type Labels } from \"../groups/parts.js\";\nimport { CommentModeration, type CommentModerationProps } from \"../social/moderation.js\";\nimport { ContentManager, type ContentManagerProps } from \"./content-manager.js\";\nimport { GroupManager, type GroupManagerProps } from \"./group-manager.js\";\nimport { PollManager, type PollManagerProps } from \"./poll-manager.js\";\nimport { AdminRoot, LoadFailed, Rows, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\nimport { StatsDashboard } from \"./stats-dashboard.js\";\n\nexport type ContentAdminSection = \"contents\" | \"groups\" | \"moderation\" | \"polls\" | \"statistics\";\n\n/** An optional reference workbench; each section remains usable as its own component. */\nexport interface ContentAdminProps extends AdminFlowProps, AdminRouteProps {\n /** Sections in navigation order. Default all. Navigation does not grant access. */\n sections?: readonly ContentAdminSection[];\n /** Content kinds in picker order; default all configured kinds. Unknown kinds are omitted. */\n contentKinds?: readonly string[];\n /** Group kinds in picker order; default the configured site-owned kinds. Unknown kinds are omitted. */\n groupKinds?: readonly string[];\n labels?: Labels;\n content?: Pick<ContentManagerProps, \"renderEditor\" | \"contentHref\">;\n groups?: Pick<GroupManagerProps, \"renderContent\">;\n moderation?: Pick<CommentModerationProps, \"itemHref\" | \"userHref\" | \"sections\">;\n polls?: Pick<PollManagerProps, \"freeText\" | \"maxOptions\">;\n}\n\nconst SECTIONS: readonly ContentAdminSection[] = [\"contents\", \"groups\", \"moderation\", \"polls\", \"statistics\"];\nconst MODERATION: readonly string[] = [\"comments\", \"queue\", \"bans\", \"words\", \"ips\"];\n\n/**\n * Model-driven content, group, moderation, poll and statistics screens, with\n * the host's transport, router and theme. Only the selected manager mounts;\n * its native permissions and refusals remain authoritative. This component\n * does not mount a server portal, sign users in or define a publishing workflow.\n */\nexport function ContentAdmin({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: ContentAdminProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n const scope = useReadScope();\n const [boundary, setBoundary] = useState({ client, scope, revision: 0 });\n if (boundary.client !== client || boundary.scope !== scope) {\n setBoundary({ client, scope, revision: boundary.revision + 1 });\n }\n return (\n <AdminRoot flow=\"content-admin\" guard={guard} appearance={appearance} className={className} language={language} messages={messages}>\n <Workbench key={boundary.revision} {...p} client={client} guard={guard} />\n </AdminRoot>\n );\n}\n\ntype WorkbenchProps = Omit<ContentAdminProps, \"client\" | \"baseUrl\" | \"auth\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction knownKinds(wanted: readonly string[] | undefined, configured: object): string[] {\n return [...new Set(wanted ?? Object.keys(configured).sort())].filter((kind) => Object.hasOwn(configured, kind));\n}\n\nfunction groupKinds(wanted: readonly string[] | undefined, model: ContentModel): string[] {\n return knownKinds(wanted ?? Object.keys(model.group_kinds).filter((kind) => model.group_kinds[kind]?.owner !== \"user\").sort(), model.group_kinds);\n}\n\nfunction decode(part: string | undefined): string | undefined {\n if (part === undefined) return undefined;\n try { return decodeURIComponent(part); } catch { return undefined; }\n}\n\n/** Routes nested managers through the same outer history, including memory-only use. */\nfunction childNavigation(base: string, view: string, loc: AdminLocation): AdminNavigation {\n return {\n pathname: `${base}/${view}`,\n search: loc.query.size ? `?${loc.query}` : \"\",\n navigate(to, options) {\n const target = new URL(to, \"https://contentkit.invalid\");\n if (target.origin !== \"https://contentkit.invalid\") return;\n const prefix = `${base}/`;\n if (target.pathname !== base && !target.pathname.startsWith(prefix)) return;\n loc.go(target.pathname.slice(prefix.length), Object.fromEntries(target.searchParams), options);\n },\n };\n}\n\nfunction Workbench({ client, basePath, navigation, sections: wanted, contentKinds: requestedContents, groupKinds: requestedGroups, labels, guard, onError, content, groups, moderation, polls }: WorkbenchProps) {\n const { t } = useMessages();\n const id = useId();\n const loc = useAdminLocation(basePath, navigation);\n const model = useKinds({ client });\n if (model.loading) return <Rows />;\n if (!model.kinds) return <LoadFailed error={model.error} message={t(\"contentAdmin.modelFailed\")} retry={model.reload} />;\n\n const kinds = knownKinds(requestedContents, model.kinds.kinds);\n const managedGroups = groupKinds(requestedGroups, model.kinds);\n const sections = [...new Set(wanted ?? SECTIONS)].filter((section) =>\n SECTIONS.includes(section) &&\n (section !== \"contents\" && section !== \"statistics\" || kinds.length > 0) &&\n (section !== \"groups\" || managedGroups.length > 0) &&\n (section !== \"moderation\" || moderation?.sections?.length !== 0),\n );\n if (!sections.length) return <p role=\"status\" className=\"text-muted-foreground\">{t(\"contentAdmin.empty\")}</p>;\n\n const parts = loc.view.split(\"/\");\n const section = (loc.view ? parts[0] : sections[0]) as ContentAdminSection;\n const requestedKind = parts[1] === undefined ? undefined : decode(parts[1]);\n const kind = requestedKind ?? kinds[0]!;\n const moderationSections: readonly string[] = moderation?.sections ?? MODERATION;\n const valid = sections.includes(section) &&\n (section !== \"contents\" || (parts[1] === undefined || requestedKind !== undefined) && kinds.includes(kind)) &&\n (section !== \"groups\" || parts[1] === undefined || requestedKind !== undefined && managedGroups.includes(requestedKind)) &&\n (section !== \"moderation\" || !parts[1] || moderationSections.includes(parts[1])) &&\n (section !== \"statistics\" || parts.length === 1 || !loc.view);\n const base = basePath?.replace(/\\/+$/, \"\") ?? \"\";\n const prefix = section === \"contents\" ? `contents/${encodeURIComponent(kind)}` : section;\n const view = loc.view || prefix;\n // A contents root selects the first configured kind without changing the URL.\n const effectiveView = section === \"contents\" && parts[1] === undefined ? prefix : view;\n const nav = childNavigation(base, effectiveView, loc);\n const shared = { client, guard, onError };\n const names: Record<ContentAdminSection, string> = {\n contents: t(\"contentManager.title\"), groups: t(\"contentAdmin.groups\"), moderation: t(\"moderation.title\"), polls: t(\"pollManager.title\"), statistics: t(\"stats.title\"),\n };\n const destination = (s: ContentAdminSection) => s === \"contents\" ? `contents/${encodeURIComponent(kinds[0]!)}` : s;\n\n return (\n <>\n <h1 className=\"text-lg font-semibold\">{t(\"contentAdmin.title\")}</h1>\n <div className=\"grid min-w-0 gap-4 sm:grid-cols-[12rem_minmax(0,1fr)]\">\n <nav aria-label={t(\"contentAdmin.navigation\")} className=\"flex gap-1 overflow-x-auto sm:grid sm:content-start\">\n {sections.map((s) => (\n <Button key={s} variant={section === s ? \"secondary\" : \"ghost\"} className=\"justify-start whitespace-nowrap\" aria-current={section === s ? \"page\" : undefined} onClick={() => loc.go(destination(s))}>\n {names[s]}\n </Button>\n ))}\n </nav>\n <div className=\"grid min-w-0 content-start gap-4\" data-ckui=\"content-admin-view\">\n {!valid ? (\n <div className=\"grid justify-items-start gap-2\">\n <p role=\"status\">{t(\"contentAdmin.unavailable\")}</p>\n <Button variant=\"outline\" onClick={() => loc.go(\"\")}>{t(\"contentAdmin.back\")}</Button>\n </div>\n ) : section === \"contents\" ? (\n <>\n <label className=\"grid justify-items-start gap-1 text-sm\" htmlFor={`${id}-kind`}>\n {t(\"contentAdmin.kind\")}\n <select id={`${id}-kind`} className=\"h-8 rounded-md border border-input bg-background px-2 text-sm\" value={kind} onChange={(event) => loc.go(`contents/${encodeURIComponent(event.target.value)}`)}>\n {kinds.map((k) => <option key={k} value={k}>{labelOf(labels, k)}</option>)}\n </select>\n </label>\n <ContentManager key={kind} {...shared} {...content} kind={kind} labels={labels} languages={model.kinds.languages} basePath={`${base}/${prefix}`} navigation={nav} />\n </>\n ) : section === \"groups\" ? (\n <GroupManager {...shared} {...groups} labels={labels} groupKinds={managedGroups} basePath={`${base}/groups`} navigation={nav} />\n ) : section === \"moderation\" ? (\n <CommentModeration {...shared} {...moderation} contentKinds={kinds.map((value) => ({ value, label: labelOf(labels, value) }))} basePath={`${base}/moderation`} navigation={nav} />\n ) : section === \"polls\" ? (\n <PollManager {...shared} {...polls} languages={model.kinds.languages} basePath={`${base}/polls`} navigation={nav} />\n ) : (\n <StatsDashboard {...shared} kinds={kinds.map((value) => ({ value, label: labelOf(labels, value) }))} />\n )}\n </div>\n </div>\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -331,7 +331,7 @@ function j(l) {
|
|
|
331
331
|
"aria-hidden": !0
|
|
332
332
|
}), /* @__PURE__ */ m("div", {
|
|
333
333
|
ref: L,
|
|
334
|
-
className: "relative w-full bg-background pt-1.5 text-foreground shadow-lg",
|
|
334
|
+
className: "relative w-full origin-top animate-in fade-in-0 slide-in-from-top-2 bg-background pt-1.5 text-foreground shadow-lg duration-200 ease-out motion-reduce:animate-none",
|
|
335
335
|
"data-ckui": "search-panel",
|
|
336
336
|
onKeyDown: Q,
|
|
337
337
|
children: /* @__PURE__ */ m("div", {
|
|
@@ -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 /** The reader's recent searches while the box is empty. Default true. */\n recent?: boolean;\n /** Header-wide dropdown under the nearest header/banner; 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; searchedAt?: string };\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 useHeaderTop(open: boolean, enabled: boolean, root: RefObject<HTMLDivElement | null>, input: RefObject<HTMLInputElement | null>): number {\n const [top, setTop] = useState(0);\n useLayoutEffect(() => {\n if (!open || !enabled || !root.current) return;\n const anchor = root.current.closest('header, [role=\"banner\"]') ?? input.current ?? root.current;\n const view = root.current.ownerDocument.defaultView;\n if (!view) return;\n const measure = () => setTop(Math.max(0, Math.min(view.innerHeight, anchor.getBoundingClientRect().bottom)));\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 top;\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\nfunction SearchDate({ value, formatter }: { value?: string; formatter: Intl.DateTimeFormat }) {\n if (!value || Number.isNaN(Date.parse(value))) return null;\n return <time dateTime={value} className=\"shrink-0 text-xs text-muted-foreground\">{formatter.format(new Date(value))}</time>;\n}\n\n/**\n * A search field suggesting contents and groups as the reader types, and\n * recent searches while it is empty; Enter searches, or opens the suggestion chosen.\n */\nexport function SearchBox(p: SearchBoxProps) {\n const { t, error, language } = 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 const [active, setActive] = useState(-1);\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 top = useHeaderTop(open, header, root, input);\n const dates = useMemo(() => new Intl.DateTimeFormat(language, { dateStyle: \"short\" }), [language]);\n const empty = !text.trim();\n const found = useTypeahead(text, { kind: p.kind, group_kinds: p.groupKinds, client: p.client });\n const recent = useRecentSearches({ enabled: p.recent !== false && open && empty, client: p.client });\n const items = useMemo<Suggestion[]>(\n () =>\n empty\n ? recent.searches.map((r) => ({ type: \"recent\" as const, query: r.query, searchedAt: r.searched_at }))\n : [...found.contents.map((card) => ({ type: \"content\" as const, card })), ...found.groups.map((group) => ({ type: \"group\" as const, group }))],\n [empty, recent.searches, found.contents, found.groups],\n );\n const current = empty || found.query === text.trim();\n const visible = current ? items : [];\n const selected = visible[active];\n const loading = empty ? recent.loading : !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(-1);\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={s.type === \"content\" ? `c:${s.card.id}` : s.type === \"group\" ? `g:${s.group.id}` : `r:${s.query}`}\n id={`${id}-${i}`}\n role=\"option\"\n aria-selected={i === active}\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 === active && \"bg-accent text-accent-foreground\")}\n onMouseEnter={() => setActive(i)}\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 {header && <SearchDate value={s.searchedAt} formatter={dates} />}\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}-${active}` : 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(-1);\n }}\n onFocus={reveal}\n onClick={reveal}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n setActive(-1);\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n reveal();\n setActive((a) => open ? Math.min(visible.length - 1, a + 1) : 0);\n } else if (e.key === \"ArrowUp\" && open) {\n e.preventDefault();\n setActive((a) => Math.max(-1, a - 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(-1);\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 backdrop-blur-sm\" data-ckui=\"search-backdrop\" aria-hidden />\n <div ref={panel} className=\"relative w-full bg-background pt-1.5 text-foreground shadow-lg\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>\n <div className=\"mx-auto w-full max-w-[71rem] px-4 pt-5 md:px-[10%]\">\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 className={empty ? \"px-4 py-4\" : \"px-4\"}>{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":";;;;;;;;;;;;;;AAuCA,IAAM,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,EAAa,GAAe,GAAkB,GAAwC,GAAmD;CAChJ,IAAM,CAAC,GAAK,KAAU,EAAS,CAAC;CAsBhC,OArBA,QAAsB;EACpB,IAAI,CAAC,KAAQ,CAAC,KAAW,CAAC,EAAK,SAAS;EACxC,IAAM,IAAS,EAAK,QAAQ,QAAQ,2BAAyB,KAAK,EAAM,WAAW,EAAK,SAClF,IAAO,EAAK,QAAQ,cAAc;EACxC,IAAI,CAAC,GAAM;EACX,IAAM,UAAgB,EAAO,KAAK,IAAI,GAAG,KAAK,IAAI,EAAK,aAAa,EAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;EAC3G,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;AAEA,SAAS,EAAW,EAAE,UAAO,gBAAiE;CAE5F,OADI,CAAC,KAAS,OAAO,MAAM,KAAK,MAAM,CAAK,CAAC,IAAU,OAC/C,kBAAC,QAAD;EAAM,UAAU;EAAO,WAAU;EAA0C,UAAA,EAAU,OAAO,IAAI,KAAK,CAAK,CAAC;CAAQ,CAAA;AAC5H;AAMA,SAAgB,EAAU,GAAmB;CAC3C,IAAM,EAAE,MAAG,UAAO,gBAAa,EAAY,GACrC,IAAK,GAAM,GACX,IAAW,EAAW,CAAiB,CAAC,EAAE,UAC1C,CAAC,GAAM,KAAW,EAAS,EAAE,gBAAgB,EAAE,GAC/C,CAAC,GAAM,KAAW,EAAS,EAAK,GAChC,CAAC,GAAQ,KAAa,EAAS,EAAE,GACjC,IAAO,EAAuB,IAAI,GAClC,IAAQ,EAAuB,IAAI,GACnC,IAAQ,EAAyB,IAAI,GACrC,IAAS,EAAE,iBAAiB,UAC5B,IAAM,EAAa,GAAM,GAAQ,GAAM,CAAK,GAC5C,IAAQ,QAAc,IAAI,KAAK,eAAe,GAAU,EAAE,WAAW,QAAQ,CAAC,GAAG,CAAC,CAAQ,CAAC,GAC3F,IAAQ,CAAC,EAAK,KAAK,GACnB,IAAQ,EAAa,GAAM;EAAE,MAAM,EAAE;EAAM,aAAa,EAAE;EAAY,QAAQ,EAAE;CAAO,CAAC,GACxF,IAAS,EAAkB;EAAE,SAAS,EAAE,WAAW,MAAS,KAAQ;EAAO,QAAQ,EAAE;CAAO,CAAC,GAC7F,KAAQ,QAEV,IACI,EAAO,SAAS,KAAK,OAAO;EAAE,MAAM;EAAmB,OAAO,EAAE;EAAO,YAAY,EAAE;CAAY,EAAE,IACnG,CAAC,GAAG,EAAM,SAAS,KAAK,OAAU;EAAE,MAAM;EAAoB;CAAK,EAAE,GAAG,GAAG,EAAM,OAAO,KAAK,OAAW;EAAE,MAAM;EAAkB;CAAM,EAAE,CAAC,GACjJ;EAAC;EAAO,EAAO;EAAU,EAAM;EAAU,EAAM;CAAM,CACvD,GACM,IAAU,KAAS,EAAM,UAAU,EAAK,KAAK,GAC7C,IAAU,IAAU,KAAQ,CAAC,GAC7B,IAAW,EAAQ,IACnB,KAAU,IAAQ,EAAO,UAAU,CAAC,KAAW,EAAM,SACrD,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,EAAQ,EAAI;CACd,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,EAAE;CACd,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,iBAAe,MAAM;IACrB,aAAU;IACV,aAAW,EAAE;IACb,WAAW,EAAG,oCAAoC,IAAS,EAAE,SAAS,WAAW,0DAA0D,4EAA4E,qBAAqB,MAAM,KAAU,kCAAkC;IAC9R,oBAAoB,EAAU,CAAC;IAC/B,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;KACxD,KAAU,kBAAC,GAAD;MAAY,OAAO,EAAE;MAAY,WAAW;KAAQ,CAAA;KAC/D,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,EAAE,SAAS,YAAY,KAAK,EAAE,KAAK,OAAO,EAAE,SAAS,UAAU,KAAK,EAAE,MAAM,OAAO,KAAK,EAAE,OAkD7F,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;IAEf,IADA,EAAE,eAAe,GACb,KAAQ,GAAU,OAAO,EAAG,CAAQ;IACxC,EAAO,EAAK,KAAK,CAAC;GACpB;GAPF,UAAA;IASG,IAAS,kBAAC,UAAD;KAAQ,MAAK;KAAS,WAAU;KAAoJ,cAAY,EAAE,iBAAiB;KAAG,UAAA,kBAAC,GAAD;MAAe,MAAM;MAAc,aAAa;MAAG,WAAU;MAAS,eAAA;KAAa,CAAA;IAAS,CAAA,IACxT,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,MAAW,KAAA;KAC9D,qBAAkB;KAClB,cAAa;KACb,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,EAAE;KACd;KACA,SAAS;KACT,SAAS;KACT,YAAY,MAAM;MAChB,IAAI,EAAE,QAAQ,UAAU;OAGtB,AAFA,EAAE,eAAe,GACjB,EAAQ,EAAK,GACb,EAAU,EAAE;OACZ;MACF;MACA,AAAI,EAAE,QAAQ,eACZ,EAAE,eAAe,GACjB,EAAO,GACP,GAAW,MAAM,IAAO,KAAK,IAAI,EAAQ,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KACtD,EAAE,QAAQ,aAAa,MAChC,EAAE,eAAe,GACjB,GAAW,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC;KAExC;IACD,CAAA;IACA,MAAW,KAAQ,MAClB,kBAAC,UAAD;KACE,MAAK;KACL,WAAU;KACV,cAAmB,EAAP,IAAS,yBAA4B,cAAc;KAC/D,cAAc,MAAU,EAAM,eAAe;KAC7C,eAAe;MAGb,AAFA,EAAM,SAAS,MAAM,GACrB,EAAU,EAAE,GACR,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,EACjB,kBAAC,GAAD;GAAkB,YAAY,EAAE;GAAY,WAAU;GAA0C,OAAO,EAAE,OAAI;GAAG,aAAU;GAA1H,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;IAAgD,aAAU;IAAkB,eAAA;GAAa,CAAA,GACxG,kBAAC,OAAD;IAAK,KAAK;IAAO,WAAU;IAAiE,aAAU;IAAe,WAAW;IAC9H,UAAA,kBAAC,OAAD;KAAK,WAAU;KACb,UAAA,kBAAC,OAAD;MAAK,WAAU;MAAgF,OAAO,EAAE,WAAW,+CAA+C,EAAI,iBAAiB;MACrL,UAAA,kBAAC,OAAD;OAAK,WAAW,IAAQ,cAAc;OAAS,UAAA;MAAa,CAAA;KACzD,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"}
|
|
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 /** The reader's recent searches while the box is empty. Default true. */\n recent?: boolean;\n /** Header-wide dropdown under the nearest header/banner; 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; searchedAt?: string };\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 useHeaderTop(open: boolean, enabled: boolean, root: RefObject<HTMLDivElement | null>, input: RefObject<HTMLInputElement | null>): number {\n const [top, setTop] = useState(0);\n useLayoutEffect(() => {\n if (!open || !enabled || !root.current) return;\n const anchor = root.current.closest('header, [role=\"banner\"]') ?? input.current ?? root.current;\n const view = root.current.ownerDocument.defaultView;\n if (!view) return;\n const measure = () => setTop(Math.max(0, Math.min(view.innerHeight, anchor.getBoundingClientRect().bottom)));\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 top;\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\nfunction SearchDate({ value, formatter }: { value?: string; formatter: Intl.DateTimeFormat }) {\n if (!value || Number.isNaN(Date.parse(value))) return null;\n return <time dateTime={value} className=\"shrink-0 text-xs text-muted-foreground\">{formatter.format(new Date(value))}</time>;\n}\n\n/**\n * A search field suggesting contents and groups as the reader types, and\n * recent searches while it is empty; Enter searches, or opens the suggestion chosen.\n */\nexport function SearchBox(p: SearchBoxProps) {\n const { t, error, language } = 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 const [active, setActive] = useState(-1);\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 top = useHeaderTop(open, header, root, input);\n const dates = useMemo(() => new Intl.DateTimeFormat(language, { dateStyle: \"short\" }), [language]);\n const empty = !text.trim();\n const found = useTypeahead(text, { kind: p.kind, group_kinds: p.groupKinds, client: p.client });\n const recent = useRecentSearches({ enabled: p.recent !== false && open && empty, client: p.client });\n const items = useMemo<Suggestion[]>(\n () =>\n empty\n ? recent.searches.map((r) => ({ type: \"recent\" as const, query: r.query, searchedAt: r.searched_at }))\n : [...found.contents.map((card) => ({ type: \"content\" as const, card })), ...found.groups.map((group) => ({ type: \"group\" as const, group }))],\n [empty, recent.searches, found.contents, found.groups],\n );\n const current = empty || found.query === text.trim();\n const visible = current ? items : [];\n const selected = visible[active];\n const loading = empty ? recent.loading : !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(-1);\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={s.type === \"content\" ? `c:${s.card.id}` : s.type === \"group\" ? `g:${s.group.id}` : `r:${s.query}`}\n id={`${id}-${i}`}\n role=\"option\"\n aria-selected={i === active}\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 === active && \"bg-accent text-accent-foreground\")}\n onMouseEnter={() => setActive(i)}\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 {header && <SearchDate value={s.searchedAt} formatter={dates} />}\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}-${active}` : 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(-1);\n }}\n onFocus={reveal}\n onClick={reveal}\n onKeyDown={(e) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n setOpen(false);\n setActive(-1);\n return;\n }\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n reveal();\n setActive((a) => open ? Math.min(visible.length - 1, a + 1) : 0);\n } else if (e.key === \"ArrowUp\" && open) {\n e.preventDefault();\n setActive((a) => Math.max(-1, a - 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(-1);\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 backdrop-blur-sm\" data-ckui=\"search-backdrop\" aria-hidden />\n <div ref={panel} className=\"relative w-full origin-top animate-in fade-in-0 slide-in-from-top-2 bg-background pt-1.5 text-foreground shadow-lg duration-200 ease-out motion-reduce:animate-none\" data-ckui=\"search-panel\" onKeyDown={panelKeyDown}>\n <div className=\"mx-auto w-full max-w-[71rem] px-4 pt-5 md:px-[10%]\">\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 className={empty ? \"px-4 py-4\" : \"px-4\"}>{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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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export { MediaReadinessNotice, type MediaReadinessNoticeProps } from './componen
|
|
|
21
21
|
export * from './components/social/index.js';
|
|
22
22
|
export * from './components/groups/index.js';
|
|
23
23
|
export * from './components/content/index.js';
|
|
24
|
+
export { ContentAdmin, type ContentAdminProps, type ContentAdminSection } from './components/admin/content-admin.js';
|
|
24
25
|
export { UserCommentStanding, useUserCommentStandingAccess, type UserCommentStandingProps } from './components/admin/user-comment-standing.js';
|
|
25
26
|
export { PollManager, pollState, usePollManagerAccess, type PollManagerProps } from './components/admin/poll-manager.js';
|
|
26
27
|
export { ContentEditor, ContentManager, contentStatus, useContentManagerAccess, type BodyEditorProps, type ContentEditorProps, type ContentManagerProps, type ContentStatus } from './components/admin/content-manager.js';
|