@openrails/contentkit-ui 0.71.0 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -10
- package/dist/client/content/bans.d.ts +2 -0
- package/dist/client/content/bans.js +24 -12
- package/dist/client/content/bans.js.map +1 -1
- package/dist/client/content/comments.d.ts +2 -0
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/polls.d.ts +9 -2
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/generated/routes.d.ts +28 -2
- package/dist/client/generated/routes.js +40 -0
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +4 -0
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +3 -3
- package/dist/client/media/queue.d.ts +1 -1
- package/dist/client/media/queue.js +1 -1
- package/dist/client/media/queue.js.map +1 -1
- package/dist/client/taxonomy.d.ts +5 -1
- package/dist/client/taxonomy.js +7 -0
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/client.js +2 -2
- package/dist/components/admin/media-item-panel.d.ts +27 -0
- package/dist/components/admin/media-item-panel.js +397 -0
- package/dist/components/admin/media-item-panel.js.map +1 -0
- package/dist/components/admin/poll-manager.d.ts +28 -0
- package/dist/components/admin/poll-manager.js +297 -0
- package/dist/components/admin/poll-manager.js.map +1 -0
- package/dist/components/admin/post-manager.d.ts +45 -0
- package/dist/components/admin/post-manager.js +602 -0
- package/dist/components/admin/post-manager.js.map +1 -0
- package/dist/components/admin/shell.d.ts +73 -0
- package/dist/components/admin/shell.js +148 -0
- package/dist/components/admin/shell.js.map +1 -0
- package/dist/components/admin/taxonomy-manager.d.ts +54 -0
- package/dist/components/admin/taxonomy-manager.js +976 -0
- package/dist/components/admin/taxonomy-manager.js.map +1 -0
- package/dist/components/admin/upload-queue.d.ts +23 -0
- package/dist/components/admin/upload-queue.js +365 -0
- package/dist/components/admin/upload-queue.js.map +1 -0
- package/dist/components/admin/user-comment-standing.d.ts +25 -0
- package/dist/components/admin/user-comment-standing.js +229 -0
- package/dist/components/admin/user-comment-standing.js.map +1 -0
- package/dist/components/social/bans.d.ts +5 -2
- package/dist/components/social/bans.js +132 -134
- package/dist/components/social/bans.js.map +1 -1
- package/dist/components/social/index.d.ts +1 -1
- package/dist/components/social/moderation.d.ts +11 -13
- package/dist/components/social/moderation.js +299 -218
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/components/social/parts.d.ts +3 -2
- package/dist/components/social/parts.js +74 -48
- package/dist/components/social/parts.js.map +1 -1
- package/dist/components/social/poll-editor.js +1 -0
- package/dist/components/social/poll-editor.js.map +1 -1
- package/dist/editor/index.d.ts +1 -0
- package/dist/editor/rich-text-editor.d.ts +13 -0
- package/dist/editor/rich-text-editor.js +228 -0
- package/dist/editor/rich-text-editor.js.map +1 -0
- package/dist/editor.js +2 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +15 -8
- package/dist/locales/admin/de.d.ts +2 -0
- package/dist/locales/admin/de.js +227 -0
- package/dist/locales/admin/de.js.map +1 -0
- package/dist/locales/admin/en.d.ts +222 -0
- package/dist/locales/admin/en.js +227 -0
- package/dist/locales/admin/en.js.map +1 -0
- package/dist/locales/admin/es.d.ts +2 -0
- package/dist/locales/admin/es.js +227 -0
- package/dist/locales/admin/es.js.map +1 -0
- package/dist/locales/admin/ja.d.ts +2 -0
- package/dist/locales/admin/ja.js +227 -0
- package/dist/locales/admin/ja.js.map +1 -0
- package/dist/locales/admin/ko.d.ts +2 -0
- package/dist/locales/admin/ko.js +227 -0
- package/dist/locales/admin/ko.js.map +1 -0
- package/dist/locales/admin/zh.d.ts +2 -0
- package/dist/locales/admin/zh.js +227 -0
- package/dist/locales/admin/zh.js.map +1 -0
- package/dist/locales/de.js +5 -3
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts +227 -0
- package/dist/locales/en.js +5 -3
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +5 -3
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +5 -3
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +5 -3
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/social/de.js +8 -1
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +7 -0
- package/dist/locales/social/en.js +8 -1
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +8 -1
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +8 -1
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +8 -1
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +8 -1
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/locales/zh.js +5 -3
- package/dist/locales/zh.js.map +1 -1
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +23 -19
- package/dist/provider.js.map +1 -1
- package/dist/react/admin.d.ts +55 -0
- package/dist/react/admin.js +97 -0
- package/dist/react/admin.js.map +1 -0
- package/dist/react/comments.d.ts +18 -2
- package/dist/react/comments.js +24 -8
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +7 -1
- package/dist/react/context.js +6 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/folder.js.map +1 -1
- package/dist/react/index.d.ts +4 -3
- package/dist/react/polls.js +88 -87
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.js +123 -122
- package/dist/react/posts.js.map +1 -1
- package/dist/react/resources.js +3 -0
- package/dist/react/resources.js.map +1 -1
- package/dist/react/upload.d.ts +10 -10
- package/dist/react/upload.js +1 -1
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +16 -0
- package/dist/react/use-resource.js +20 -1
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +21 -20
- package/dist/styles.css +1 -1
- package/package.json +37 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"moderation.js","names":[],"sources":["../../../src/components/social/moderation.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useMemo, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Decision, HeldKind } from \"../../client/content/types.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { AdminComment, HeldItem, PublicUser } from \"../../client/generated/wire.js\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useAdminComments, useModerationQueue } from \"../../react/comments.js\";\nimport { useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { CommentBanDialog, CommentBans } from \"./bans.js\";\nimport { contentError, ErrorLine, RelativeTime, UserAvatar, UserName } from \"./parts.js\";\n\nexport type ModerationSection = \"comments\" | \"queue\" | \"bans\";\n\nexport interface CommentModerationProps {\n /** Content kinds offered as a filter of the comment list: codes, or codes with labels. */\n contentKinds?: readonly (string | { value: string; label: string })[];\n /** A link to a commented item. */\n itemHref?: (item: { kind: string; id: string }) => string | undefined;\n userHref?: (user: PublicUser) => string | undefined;\n /** The sections, in order. Default all: every comment, the review queue, the site's bans. */\n sections?: readonly ModerationSection[];\n pageSize?: number;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/**\n * Staff: every comment (deleted, held and rejected ones with their text) to\n * delete, restore or ban its author; the review queue of held comments and\n * posts to approve or reject; and the site's comment bans. Each section\n * shows what the caller's permissions allow and says so when they don't.\n */\nexport function CommentModeration({ sections = [\"comments\", \"queue\", \"bans\"], className, appearance, ...p }: CommentModerationProps) {\n const { t } = useMessages();\n const [tab, setTab] = useState<ModerationSection>(sections[0] ?? \"comments\");\n const label: Record<ModerationSection, string> = { comments: t(\"moderation.all\"), queue: t(\"moderation.queue\"), bans: t(\"moderation.bans\") };\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-4 text-sm\", className)} data-ckui=\"comment-moderation\">\n <h2 className=\"text-base font-semibold\">{t(\"moderation.title\")}</h2>\n <Tabs value={tab} onValueChange={(v) => setTab(v as ModerationSection)}>\n {sections.length > 1 && (\n <TabsList>\n {sections.map((s) => (\n <TabsTrigger key={s} value={s}>\n {label[s]}\n </TabsTrigger>\n ))}\n </TabsList>\n )}\n {sections.includes(\"comments\") && (\n <TabsContent value=\"comments\" className=\"pt-3\">\n <AllComments {...p} />\n </TabsContent>\n )}\n {sections.includes(\"queue\") && (\n <TabsContent value=\"queue\" className=\"pt-3\">\n <Queue {...p} />\n </TabsContent>\n )}\n {sections.includes(\"bans\") && (\n <TabsContent value=\"bans\" className=\"pt-3\">\n <CommentBans scope=\"global\" pageSize={p.pageSize} userHref={p.userHref} client={p.client} onError={p.onError} />\n </TabsContent>\n )}\n </Tabs>\n </ContentKitUiRoot>\n );\n}\n\ntype Rest = Omit<CommentModerationProps, \"sections\" | \"className\" | \"appearance\">;\n\nfunction status(c: AdminComment): \"deleted\" | \"held\" | \"rejected\" | \"published\" {\n if (c.deleted) return \"deleted\";\n return c.moderation === \"held\" ? \"held\" : c.moderation === \"rejected\" ? \"rejected\" : \"published\";\n}\n\nfunction AllComments(p: Rest) {\n const m = useMessages();\n const { t } = m;\n const kinds = useMemo(() => (p.contentKinds ?? []).map((k) => (typeof k === \"string\" ? { value: k, label: k } : k)), [p.contentKinds]);\n const [kind, setKind] = useState(\"\");\n const list = useAdminComments({ contentKind: kind, pageSize: p.pageSize, client: p.client });\n const report = useErrorReporter(p.onError);\n const [error, setError] = useState<ContentKitError | null>(null);\n const [banning, setBanning] = useState<{ id: string; name: string } | null>(null);\n const act = (op: \"comment.delete\" | \"comment.restore\", fn: () => Promise<unknown>) => {\n setError(null);\n fn().catch((e) => {\n const err = toContentKitError(e);\n setError(err);\n report(err, op);\n });\n };\n return (\n <div className=\"grid gap-3\">\n {kinds.length > 0 && (\n <Select value={kind} onValueChange={(v) => setKind(String(v ?? \"\"))}>\n <SelectTrigger size=\"sm\" aria-label={t(\"moderation.kind\")}>\n <SelectValue>{(v: string) => (v ? (kinds.find((k) => k.value === v)?.label ?? v) : t(\"moderation.allKinds\"))}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">{t(\"moderation.allKinds\")}</SelectItem>\n {kinds.map((k) => (\n <SelectItem key={k.value} value={k.value}>\n {k.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <ErrorLine error={error} />\n <Listing loading={list.loading} error={list.error} empty={t(\"moderation.empty\")} count={list.items.length} reload={list.reload}>\n {list.items.map((c) => {\n const name = c.author?.username ?? c.anon_name ?? c.user_id ?? t(\"comments.anonymous\");\n const s = status(c);\n const href = p.itemHref?.({ kind: c.content_kind, id: c.content_id });\n return (\n <li key={c.id} className=\"grid gap-2 p-3\" data-ckui=\"admin-comment\" data-status={s}>\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n <UserAvatar user={c.author} name={name} size=\"sm\" />\n <UserName user={c.author} name={name} href={p.userHref} />\n <RelativeTime at={c.created_at} className=\"text-xs text-muted-foreground\" />\n <Badge variant={s === \"published\" ? \"outline\" : s === \"held\" ? \"secondary\" : \"destructive\"}>{t(`moderation.${s}`)}</Badge>\n {c.reply_to_id && <Badge variant=\"outline\">{t(\"moderation.reply\")}</Badge>}\n <span className=\"text-xs text-muted-foreground\">\n {t(\"moderation.on\")}{\" \"}\n {href ? (\n <a className=\"hover:underline\" href={href}>\n {c.content_kind}/{c.content_id}\n </a>\n ) : (\n `${c.content_kind}/${c.content_id}`\n )}\n </span>\n </div>\n <p className={cn(\"line-clamp-4 break-words whitespace-pre-wrap\", c.deleted && \"text-muted-foreground line-through\")}>{c.body}</p>\n {c.moderation_reason && <p className=\"text-xs text-muted-foreground\">{t(\"moderation.heldBecause\", { reason: c.moderation_reason })}</p>}\n <div className=\"flex flex-wrap gap-2\">\n {c.deleted ? (\n <Button size=\"sm\" variant=\"outline\" onClick={() => act(\"comment.restore\", () => list.restore(c.id))}>\n {t(\"moderation.restore\")}\n </Button>\n ) : (\n <Button size=\"sm\" variant=\"destructive\" onClick={() => act(\"comment.delete\", () => list.remove(c.id))}>\n {t(\"moderation.delete\")}\n </Button>\n )}\n {c.user_id && (\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setBanning({ id: c.user_id!, name })}>\n {t(\"comments.ban\")}\n </Button>\n )}\n </div>\n </li>\n );\n })}\n </Listing>\n {list.hasMore && (\n <Button variant=\"outline\" className=\"justify-self-center\" disabled={list.loadingMore} onClick={() => void list.loadMore()}>\n {t(\"moderation.loadMore\")}\n </Button>\n )}\n {banning && <CommentBanDialog open onOpenChange={(o) => !o && setBanning(null)} user={banning} scopes={[\"global\"]} client={p.client} onError={p.onError} />}\n </div>\n );\n}\n\nfunction Queue(p: Rest) {\n const m = useMessages();\n const { t } = m;\n const [kind, setKind] = useState<HeldKind>(\"comment\");\n const queue = useModerationQueue({ kind, pageSize: p.pageSize, client: p.client });\n return (\n <div className=\"grid gap-3\">\n <div className=\"flex gap-2\" role=\"group\" aria-label={t(\"moderation.queue\")}>\n {([\"comment\", \"post\"] as const).map((k) => (\n <Button key={k} size=\"sm\" variant={kind === k ? \"default\" : \"outline\"} aria-pressed={kind === k} onClick={() => setKind(k)}>\n {t(k === \"post\" ? \"moderation.queuePosts\" : \"moderation.queueComments\")}\n </Button>\n ))}\n </div>\n <Listing loading={queue.loading} error={queue.error} empty={t(\"moderation.queueEmpty\")} count={queue.items.length} reload={queue.reload}>\n {queue.items.map((it) => (\n <HeldRow key={it.id} item={it} itemHref={p.itemHref} userHref={p.userHref} onResolve={queue.resolve} reload={queue.reload} onError={p.onError} />\n ))}\n </Listing>\n {queue.hasMore && (\n <Button variant=\"outline\" className=\"justify-self-center\" disabled={queue.loadingMore} onClick={() => void queue.loadMore()}>\n {t(\"moderation.loadMore\")}\n </Button>\n )}\n </div>\n );\n}\n\nfunction HeldRow(p: {\n item: HeldItem;\n itemHref?: Rest[\"itemHref\"];\n userHref?: Rest[\"userHref\"];\n onResolve: (item: HeldItem, d: Decision, reason?: string) => Promise<void>;\n reload: () => void;\n onError?: ContentKitErrorHandler;\n}) {\n const m = useMessages();\n const { t } = m;\n const report = useErrorReporter(p.onError);\n const { item: it } = p;\n const [rejecting, setRejecting] = useState(false);\n const [reason, setReason] = useState(\"\");\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const name = it.author?.username ?? it.anon_name ?? it.author_id ?? t(\"comments.anonymous\");\n const href = p.itemHref?.({ kind: it.ref.content_kind, id: it.ref.content_id });\n const resolve = async (d: Decision) => {\n setPending(true);\n setError(null);\n try {\n await p.onResolve(it, d, d === \"reject\" ? reason.trim() || undefined : undefined);\n } catch (e) {\n const err = toContentKitError(e);\n report(err, \"moderation.resolve\");\n // A stale revision: the text changed since it was listed.\n if (err.code === \"not_found\" || err.code === \"conflict\") {\n setError(t(\"moderation.stale\"));\n p.reload();\n } else setError(contentError(m, err));\n } finally {\n setPending(false);\n }\n };\n return (\n <li className=\"grid gap-2 p-3\" data-ckui=\"held-item\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n <UserAvatar user={it.author} name={name} size=\"sm\" />\n <UserName user={it.author} name={name} href={p.userHref} />\n <RelativeTime at={it.held_at} className=\"text-xs text-muted-foreground\" />\n {href && (\n <a className=\"text-xs text-muted-foreground hover:underline\" href={href}>\n {t(\"moderation.on\")} {it.ref.content_kind}/{it.ref.content_id}\n </a>\n )}\n </div>\n {it.title && <p className=\"font-medium\">{it.title}</p>}\n <p className=\"line-clamp-6 break-words whitespace-pre-wrap\">{it.body}</p>\n {(it.reason || it.error) && <p className=\"text-xs text-muted-foreground\">{t(\"moderation.heldBecause\", { reason: it.reason || it.error || \"\" })}</p>}\n {rejecting && (\n <Input value={reason} maxLength={500} placeholder={t(\"moderation.rejectReason\")} aria-label={t(\"moderation.rejectReason\")} onChange={(e) => setReason(e.target.value)} autoFocus />\n )}\n <div className=\"flex flex-wrap gap-2\">\n <Button size=\"sm\" disabled={pending} onClick={() => void resolve(\"approve\")}>\n {t(\"moderation.approve\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" disabled={pending} onClick={() => (rejecting ? void resolve(\"reject\") : setRejecting(true))}>\n {t(\"moderation.reject\")}\n </Button>\n {rejecting && (\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setRejecting(false)}>\n {t(\"common.cancel\")}\n </Button>\n )}\n </div>\n {error && <ErrorLine>{error}</ErrorLine>}\n </li>\n );\n}\n\nfunction Listing(p: { loading: boolean; error?: ContentKitError; empty: string; count: number; reload: () => void; children: React.ReactNode }) {\n const m = useMessages();\n const { t } = m;\n if (p.loading && !p.count) {\n return (\n <div className=\"grid gap-3\" aria-busy>\n <Skeleton className=\"h-16 w-full\" />\n <Skeleton className=\"h-16 w-full\" />\n </div>\n );\n }\n if (p.error && !p.count) {\n return (\n <div className=\"grid justify-items-start gap-2\">\n <ErrorLine>{p.error.code === \"forbidden\" || p.error.code === \"unauthorized\" ? contentError(m, p.error) : t(\"moderation.loadFailed\")}</ErrorLine>\n <Button size=\"sm\" variant=\"outline\" onClick={p.reload}>\n {t(\"common.retry\")}\n </Button>\n </div>\n );\n }\n if (!p.count) return <p className=\"text-muted-foreground\">{p.empty}</p>;\n return <ul className=\"grid divide-y divide-border rounded-lg border border-border\">{p.children}</ul>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"moderation.js","names":[],"sources":["../../../src/components/social/moderation.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useEffect, useId, useMemo, useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { Decision, HeldKind } from \"../../client/content/types.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { AdminComment, HeldItem, PublicUser } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type FlowClientOptions, type StaffAccess } from \"../../react/admin.js\";\nimport { useModerationQueue } from \"../../react/comments.js\";\nimport { useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { keyOf, useContentScope, usePaged } from \"../../react/use-resource.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"#ckui/ui/tabs\";\nimport { AdminRoot, NoAccess, Pager, Rows, pageOf, useGuarded, type AdminFlowProps, type AdminRouteProps } from \"../admin/shell.js\";\nimport { CommentBanDialog, CommentBans } from \"./bans.js\";\nimport { contentError, ErrorLine, RelativeTime, UserAvatar, UserName } from \"./parts.js\";\n\nexport type ModerationSection = \"comments\" | \"queue\" | \"bans\";\n\nexport interface CommentModerationProps extends AdminFlowProps, AdminRouteProps {\n /** Content kinds offered as a filter of the comment list: codes, or codes with labels. */\n contentKinds?: readonly (string | { value: string; label: string })[];\n /** A link to a commented item. */\n itemHref?: (item: { kind: string; id: string }) => string | undefined;\n userHref?: (user: PublicUser) => string | undefined;\n /** The sections, in order; the first is at basePath, the others at basePath/{section}. Default all. */\n sections?: readonly ModerationSection[];\n pageSize?: number;\n}\n\nconst ALL_SECTIONS: readonly ModerationSection[] = [\"comments\", \"queue\", \"bans\"];\n\n/** Which moderation sections the caller may use: every comment, the review queue, the site's bans. */\nexport function useCommentModerationAccess(o: FlowClientOptions = {}): StaffAccess<ModerationSection> {\n return useStaffAccess(ALL_SECTIONS, o);\n}\n\n/**\n * Staff: every comment (deleted, held and rejected ones with their text) to\n * delete, restore or ban its author, by content kind or author; the review\n * queue of held comments and posts to approve or reject; and the site's\n * comment bans. Sections, filters and pages live in the URL under basePath;\n * the caller sees only the sections its permissions allow.\n */\nexport function CommentModeration({ client: own, baseUrl, auth, guard, appearance, className, language, messages, sections = ALL_SECTIONS, ...p }: CommentModerationProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"comment-moderation\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Moderation {...p} client={client} sections={sections} />\n </AdminRoot>\n );\n}\n\ntype Rest = Omit<CommentModerationProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\" | \"sections\" | \"basePath\" | \"navigation\"> & {\n client: ContentKitClient;\n};\n\nfunction Moderation({ sections, basePath, navigation, ...p }: Rest & { sections: readonly ModerationSection[]; basePath?: string; navigation?: CommentModerationProps[\"navigation\"] }) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useCommentModerationAccess({ client: p.client });\n const allowed = sections.filter((s) => access.allowed[s]);\n const label: Record<ModerationSection, string> = { comments: t(\"moderation.all\"), queue: t(\"moderation.queue\"), bans: t(\"moderation.bans\") };\n const title = <h2 className=\"text-base font-semibold\">{t(\"moderation.title\")}</h2>;\n if (access.loading)\n return (\n <>\n {title}\n <Rows />\n </>\n );\n if (!allowed.length)\n return (\n <>\n {title}\n <NoAccess>{t(\"moderation.noAccess\")}</NoAccess>\n </>\n );\n const viewOf = (s: ModerationSection) => (s === sections[0] ? \"\" : s);\n const asked = (loc.view || sections[0]) as ModerationSection;\n const section = allowed.includes(asked) ? asked : allowed[0]!;\n const view = viewOf(section);\n return (\n <>\n {title}\n <Tabs value={section} onValueChange={(v) => loc.go(viewOf(v as ModerationSection))}>\n {allowed.length > 1 && (\n <TabsList>\n {allowed.map((s) => (\n <TabsTrigger key={s} value={s}>\n {label[s]}\n </TabsTrigger>\n ))}\n </TabsList>\n )}\n <TabsContent value={section} className=\"pt-3\">\n {section === \"comments\" && <AllComments {...p} loc={loc} view={view} />}\n {section === \"queue\" && <Queue {...p} loc={loc} view={view} />}\n {section === \"bans\" && (\n <CommentBans\n scope=\"global\"\n pageSize={p.pageSize}\n page={pageOf(loc.query)}\n onPageChange={(n) => loc.go(view, { page: n > 1 ? n : undefined })}\n userHref={p.userHref}\n client={p.client}\n onError={p.onError}\n />\n )}\n </TabsContent>\n </Tabs>\n </>\n );\n}\n\ntype SectionProps = Rest & { loc: AdminLocation; view: string };\n\nfunction status(c: AdminComment): \"deleted\" | \"held\" | \"rejected\" | \"published\" {\n if (c.deleted) return \"deleted\";\n return c.moderation === \"held\" ? \"held\" : c.moderation === \"rejected\" ? \"rejected\" : \"published\";\n}\n\nfunction AllComments({ loc, view, client, ...p }: SectionProps) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const kinds = useMemo(() => (p.contentKinds ?? []).map((k) => (typeof k === \"string\" ? { value: k, label: k } : k)), [p.contentKinds]);\n const kind = loc.query.get(\"kind\") ?? \"\";\n const author = loc.query.get(\"author\") ?? \"\";\n const page = pageOf(loc.query);\n const size = p.pageSize ?? 25;\n const filter = (next: { kind?: string; author?: string }) => loc.go(view, { kind, author, ...next });\n const [authorInput, setAuthorInput] = useState(author);\n useEffect(() => setAuthorInput(author), [author]);\n const { store, scope } = useContentScope(client);\n const list = usePaged(store, keyOf(\"admin-comments-page\", scope, kind, author, size), { type: \"admin-comments\" }, page, size, (q, signal) =>\n client.comments.adminList({ ...q, contentKind: kind || undefined, author: author || undefined }, signal),\n );\n const report = useErrorReporter(p.onError);\n const guarded = useGuarded();\n const [error, setError] = useState<ContentKitError | null>(null);\n const [banning, setBanning] = useState<{ id: string; name: string } | null>(null);\n const act = (op: \"comment.delete\" | \"comment.restore\", fn: () => Promise<unknown>) => {\n setError(null);\n guarded(fn).catch((e) => {\n const err = toContentKitError(e);\n setError(err);\n report(err, op);\n });\n };\n const byName = author ? (list.items.find((c) => c.user_id === author)?.author?.username ?? author) : \"\";\n return (\n <div className=\"grid gap-3\">\n <div className=\"flex flex-wrap items-end gap-2\">\n {kinds.length > 0 && (\n <Select value={kind} onValueChange={(v) => filter({ kind: String(v ?? \"\") })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"moderation.kind\")}>\n <SelectValue>{(v: string) => (v ? (kinds.find((k) => k.value === v)?.label ?? v) : t(\"moderation.allKinds\"))}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">{t(\"moderation.allKinds\")}</SelectItem>\n {kinds.map((k) => (\n <SelectItem key={k.value} value={k.value}>\n {k.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <form\n className=\"flex min-w-56 flex-1 items-end gap-2\"\n onSubmit={(e) => {\n e.preventDefault();\n filter({ author: authorInput.trim() });\n }}\n >\n <div className=\"grid flex-1 gap-1\">\n <Label htmlFor={`${ids}-author`} className=\"sr-only\">\n {t(\"moderation.author\")}\n </Label>\n <Input id={`${ids}-author`} className=\"h-8\" value={authorInput} placeholder={t(\"moderation.authorPlaceholder\")} onChange={(e) => setAuthorInput(e.target.value)} />\n </div>\n <Button type=\"submit\" size=\"sm\" variant=\"outline\">\n {t(\"moderation.filter\")}\n </Button>\n </form>\n </div>\n {author && (\n <div className=\"flex flex-wrap items-center gap-2\" data-ckui=\"author-filter\">\n <span>{t(\"moderation.byAuthor\", { name: byName })}</span>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => filter({ author: \"\" })}>\n {t(\"moderation.clearAuthor\")}\n </Button>\n </div>\n )}\n <ErrorLine error={error} />\n <Listing loading={list.loading} error={list.error} empty={t(\"moderation.empty\")} count={list.items.length} reload={list.reload}>\n {list.items.map((c) => {\n const name = c.author?.username ?? c.anon_name ?? c.user_id ?? t(\"comments.anonymous\");\n const s = status(c);\n const href = p.itemHref?.({ kind: c.content_kind, id: c.content_id });\n return (\n <li key={c.id} className=\"grid gap-2 p-3\" data-ckui=\"admin-comment\" data-status={s}>\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n <UserAvatar user={c.author} name={name} size=\"sm\" />\n <UserName user={c.author} name={name} href={p.userHref} />\n <RelativeTime at={c.created_at} className=\"text-xs text-muted-foreground\" />\n <Badge variant={s === \"published\" ? \"outline\" : s === \"held\" ? \"secondary\" : \"destructive\"}>{t(`moderation.${s}`)}</Badge>\n {c.reply_to_id && <Badge variant=\"outline\">{t(\"moderation.reply\")}</Badge>}\n <span className=\"text-xs text-muted-foreground\">\n {t(\"moderation.on\")}{\" \"}\n {href ? (\n <a className=\"hover:underline\" href={href}>\n {c.content_kind}/{c.content_id}\n </a>\n ) : (\n `${c.content_kind}/${c.content_id}`\n )}\n </span>\n </div>\n <p className={cn(\"line-clamp-4 break-words whitespace-pre-wrap\", c.deleted && \"text-muted-foreground line-through\")}>{c.body}</p>\n {c.moderation_reason && <p className=\"text-xs text-muted-foreground\">{t(\"moderation.heldBecause\", { reason: c.moderation_reason })}</p>}\n <div className=\"flex flex-wrap gap-2\">\n {c.deleted ? (\n <Button size=\"sm\" variant=\"outline\" onClick={() => act(\"comment.restore\", () => client.comments.restore(c.id))}>\n {t(\"moderation.restore\")}\n </Button>\n ) : (\n <Button size=\"sm\" variant=\"destructive\" onClick={() => act(\"comment.delete\", () => client.comments.delete(c.id))}>\n {t(\"moderation.delete\")}\n </Button>\n )}\n {c.user_id && c.user_id !== author && (\n <Button size=\"sm\" variant=\"ghost\" onClick={() => filter({ author: c.user_id! })}>\n {t(\"moderation.theirComments\")}\n </Button>\n )}\n {c.user_id && (\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setBanning({ id: c.user_id!, name })}>\n {t(\"comments.ban\")}\n </Button>\n )}\n </div>\n </li>\n );\n })}\n </Listing>\n <Pager page={page} hasNext={list.hasNext} disabled={list.loading} onPage={(n) => loc.go(view, { kind, author, page: n > 1 ? n : undefined })} />\n {banning && <CommentBanDialog open onOpenChange={(o) => !o && setBanning(null)} user={banning} scopes={[\"global\"]} client={client} onError={p.onError} />}\n </div>\n );\n}\n\nfunction Queue({ loc, view, ...p }: SectionProps) {\n const m = useMessages();\n const { t } = m;\n const kind: HeldKind = loc.query.get(\"kind\") === \"post\" ? \"post\" : \"comment\";\n const queue = useModerationQueue({ kind, pageSize: p.pageSize, client: p.client });\n return (\n <div className=\"grid gap-3\">\n <div className=\"flex gap-2\" role=\"group\" aria-label={t(\"moderation.queue\")}>\n {([\"comment\", \"post\"] as const).map((k) => (\n <Button key={k} size=\"sm\" variant={kind === k ? \"default\" : \"outline\"} aria-pressed={kind === k} onClick={() => loc.go(view, { kind: k === \"comment\" ? undefined : k })}>\n {t(k === \"post\" ? \"moderation.queuePosts\" : \"moderation.queueComments\")}\n </Button>\n ))}\n </div>\n <Listing loading={queue.loading} error={queue.error} empty={t(\"moderation.queueEmpty\")} count={queue.items.length} reload={queue.reload}>\n {queue.items.map((it) => (\n <HeldRow key={it.id} item={it} itemHref={p.itemHref} userHref={p.userHref} onResolve={queue.resolve} reload={queue.reload} onError={p.onError} />\n ))}\n </Listing>\n {queue.hasMore && (\n <Button variant=\"outline\" className=\"justify-self-center\" disabled={queue.loadingMore} onClick={() => void queue.loadMore()}>\n {t(\"moderation.loadMore\")}\n </Button>\n )}\n </div>\n );\n}\n\nfunction HeldRow(p: {\n item: HeldItem;\n itemHref?: Rest[\"itemHref\"];\n userHref?: Rest[\"userHref\"];\n onResolve: (item: HeldItem, d: Decision, reason?: string) => Promise<void>;\n reload: () => void;\n onError?: ContentKitErrorHandler;\n}) {\n const m = useMessages();\n const { t } = m;\n const report = useErrorReporter(p.onError);\n const guarded = useGuarded();\n const { item: it } = p;\n const [rejecting, setRejecting] = useState(false);\n const [reason, setReason] = useState(\"\");\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const name = it.author?.username ?? it.anon_name ?? it.author_id ?? t(\"comments.anonymous\");\n const href = p.itemHref?.({ kind: it.ref.content_kind, id: it.ref.content_id });\n const resolve = async (d: Decision) => {\n setPending(true);\n setError(null);\n try {\n await guarded(() => p.onResolve(it, d, d === \"reject\" ? reason.trim() || undefined : undefined));\n } catch (e) {\n const err = toContentKitError(e);\n report(err, \"moderation.resolve\");\n // A stale revision: the text changed since it was listed.\n if (err.code === \"not_found\" || err.code === \"conflict\") {\n setError(t(\"moderation.stale\"));\n p.reload();\n } else setError(contentError(m, err));\n } finally {\n setPending(false);\n }\n };\n return (\n <li className=\"grid gap-2 p-3\" data-ckui=\"held-item\">\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1\">\n <UserAvatar user={it.author} name={name} size=\"sm\" />\n <UserName user={it.author} name={name} href={p.userHref} />\n <RelativeTime at={it.held_at} className=\"text-xs text-muted-foreground\" />\n {href && (\n <a className=\"text-xs text-muted-foreground hover:underline\" href={href}>\n {t(\"moderation.on\")} {it.ref.content_kind}/{it.ref.content_id}\n </a>\n )}\n </div>\n {it.title && <p className=\"font-medium\">{it.title}</p>}\n <p className=\"line-clamp-6 break-words whitespace-pre-wrap\">{it.body}</p>\n {(it.reason || it.error) && <p className=\"text-xs text-muted-foreground\">{t(\"moderation.heldBecause\", { reason: it.reason || it.error || \"\" })}</p>}\n {rejecting && (\n <Input value={reason} maxLength={500} placeholder={t(\"moderation.rejectReason\")} aria-label={t(\"moderation.rejectReason\")} onChange={(e) => setReason(e.target.value)} autoFocus />\n )}\n <div className=\"flex flex-wrap gap-2\">\n <Button size=\"sm\" disabled={pending} onClick={() => void resolve(\"approve\")}>\n {t(\"moderation.approve\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" disabled={pending} onClick={() => (rejecting ? void resolve(\"reject\") : setRejecting(true))}>\n {t(\"moderation.reject\")}\n </Button>\n {rejecting && (\n <Button size=\"sm\" variant=\"ghost\" onClick={() => setRejecting(false)}>\n {t(\"common.cancel\")}\n </Button>\n )}\n </div>\n {error && <ErrorLine>{error}</ErrorLine>}\n </li>\n );\n}\n\nfunction Listing(p: { loading: boolean; error?: ContentKitError; empty: string; count: number; reload: () => void; children: React.ReactNode }) {\n const m = useMessages();\n const { t } = m;\n if (p.loading && !p.count) {\n return (\n <div className=\"grid gap-3\" aria-busy>\n <Skeleton className=\"h-16 w-full\" />\n <Skeleton className=\"h-16 w-full\" />\n </div>\n );\n }\n if (p.error && !p.count) {\n return (\n <div className=\"grid justify-items-start gap-2\">\n <ErrorLine>{p.error.code === \"forbidden\" || p.error.code === \"unauthorized\" ? contentError(m, p.error) : t(\"moderation.loadFailed\")}</ErrorLine>\n <Button size=\"sm\" variant=\"outline\" onClick={p.reload}>\n {t(\"common.retry\")}\n </Button>\n </div>\n );\n }\n if (!p.count) return <p className=\"text-muted-foreground\">{p.empty}</p>;\n return <ul className=\"grid divide-y divide-border rounded-lg border border-border\">{p.children}</ul>;\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import { Translator } from '../../i18n/messages.js';
|
|
|
5
5
|
import { ContentKitErrorHandler, SocialOperation } from '../../react/context.js';
|
|
6
6
|
/** A content refusal or fault in words: the content wording for shared codes, else the code's. */
|
|
7
7
|
export declare function contentError(m: Translator, e: unknown): string;
|
|
8
|
-
/** Runs one async action at a time: its pending state and last error, reported once. */
|
|
8
|
+
/** Runs one async action at a time (through the flow's guard): its pending state and last error, reported once. */
|
|
9
9
|
export declare function useAction(operation: SocialOperation, onError?: ContentKitErrorHandler): {
|
|
10
10
|
run: <T>(fn: () => Promise<T>) => Promise<T | undefined>;
|
|
11
11
|
pending: boolean;
|
|
@@ -33,13 +33,14 @@ export declare function UserName({ user, name, href, className }: {
|
|
|
33
33
|
href?: (u: PublicUser) => string | undefined;
|
|
34
34
|
className?: string;
|
|
35
35
|
}): import('react').JSX.Element;
|
|
36
|
-
/** A confirmation for a destructive action. */
|
|
36
|
+
/** A confirmation for a destructive action; with typed, the word must be typed first (acts that cannot be undone). */
|
|
37
37
|
export declare function ConfirmDialog(p: {
|
|
38
38
|
open: boolean;
|
|
39
39
|
onOpenChange: (open: boolean) => void;
|
|
40
40
|
title: ReactNode;
|
|
41
41
|
description?: ReactNode;
|
|
42
42
|
confirm: ReactNode;
|
|
43
|
+
typed?: string;
|
|
43
44
|
onConfirm: () => void;
|
|
44
45
|
}): import('react').JSX.Element;
|
|
45
46
|
/** A failure line, announced politely. */
|
|
@@ -1,106 +1,132 @@
|
|
|
1
1
|
import { toContentKitError as e } from "../../client/errors.js";
|
|
2
2
|
import { useMessages as t } from "../../i18n/context.js";
|
|
3
|
-
import { useErrorReporter as n } from "../../react/context.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
3
|
+
import { useErrorReporter as n, useGuarded as r } from "../../react/context.js";
|
|
4
|
+
import { Input as i } from "../../ui/input.js";
|
|
5
|
+
import { Label as a } from "../../ui/label.js";
|
|
6
|
+
import { AlertDialog as o, AlertDialogAction as s, AlertDialogCancel as c, AlertDialogContent as l, AlertDialogDescription as u, AlertDialogFooter as d, AlertDialogHeader as f, AlertDialogTitle as p } from "../../ui/alert-dialog.js";
|
|
7
|
+
import { Avatar as m, AvatarFallback as h, AvatarImage as g } from "../../ui/avatar.js";
|
|
8
|
+
import { useCallback as _, useEffect as v, useId as y, useState as b } from "react";
|
|
9
|
+
import { jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
10
|
+
import { cn as C } from "cn";
|
|
9
11
|
//#region src/components/social/parts.tsx
|
|
10
|
-
function
|
|
12
|
+
function w(t, n) {
|
|
11
13
|
let r = e(n);
|
|
12
14
|
return r.code === "rate_limited" ? t.t("contentErrors.rate_limited", { seconds: r.retryAfter ?? 60 }) : r.code === "forbidden" || r.code === "unauthorized" || r.code === "not_found" || r.code === "conflict" ? t.t(`contentErrors.${r.code}`) : r.code === "moderation_rejected" ? r.message ? t.t("comments.rejected", { reason: r.message }) : t.t("comments.rejectedNoReason") : t.error(r);
|
|
13
15
|
}
|
|
14
|
-
function
|
|
15
|
-
let
|
|
16
|
+
function T(t, i) {
|
|
17
|
+
let a = n(i), o = r(), [s, c] = b(!1), [l, u] = b(null);
|
|
16
18
|
return {
|
|
17
|
-
run:
|
|
18
|
-
|
|
19
|
+
run: _(async (n) => {
|
|
20
|
+
c(!0), u(null);
|
|
19
21
|
try {
|
|
20
|
-
return await n
|
|
22
|
+
return await o(n);
|
|
21
23
|
} catch (n) {
|
|
22
24
|
let r = e(n);
|
|
23
|
-
r.code !== "aborted" && (
|
|
25
|
+
r.code !== "aborted" && (u(r), a(r, t));
|
|
24
26
|
return;
|
|
25
27
|
} finally {
|
|
26
|
-
|
|
28
|
+
c(!1);
|
|
27
29
|
}
|
|
28
|
-
}, [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
}, [
|
|
31
|
+
t,
|
|
32
|
+
a,
|
|
33
|
+
o
|
|
34
|
+
]),
|
|
35
|
+
pending: s,
|
|
36
|
+
error: l,
|
|
37
|
+
setError: u
|
|
32
38
|
};
|
|
33
39
|
}
|
|
34
|
-
function
|
|
40
|
+
function E(e, t, n = Date.now()) {
|
|
35
41
|
let r = (Date.parse(t) - n) / 1e3, i = Math.abs(r);
|
|
36
42
|
if (i < 45) return e.t("time.justNow");
|
|
37
43
|
let a = new Intl.RelativeTimeFormat(e.language, { numeric: "auto" });
|
|
38
44
|
return i < 2700 ? a.format(Math.round(r / 60), "minute") : i < 79200 ? a.format(Math.round(r / 3600), "hour") : i < 2246400 ? a.format(Math.round(r / 86400), "day") : i < 28512e3 ? a.format(Math.round(r / 2592e3), "month") : a.format(Math.round(r / 31536e3), "year");
|
|
39
45
|
}
|
|
40
|
-
function
|
|
46
|
+
function D(e, t, n = !0) {
|
|
41
47
|
return new Intl.DateTimeFormat(e.language, n ? {
|
|
42
48
|
dateStyle: "medium",
|
|
43
49
|
timeStyle: "short"
|
|
44
50
|
} : { dateStyle: "medium" }).format(new Date(t));
|
|
45
51
|
}
|
|
46
|
-
function
|
|
52
|
+
function O({ at: e, className: n }) {
|
|
47
53
|
let r = t();
|
|
48
|
-
return /* @__PURE__ */
|
|
54
|
+
return /* @__PURE__ */ x("time", {
|
|
49
55
|
dateTime: e,
|
|
50
|
-
title:
|
|
56
|
+
title: D(r, e),
|
|
51
57
|
className: n,
|
|
52
|
-
children:
|
|
58
|
+
children: E(r, e)
|
|
53
59
|
});
|
|
54
60
|
}
|
|
55
|
-
function
|
|
56
|
-
return /* @__PURE__ */
|
|
61
|
+
function k({ user: e, name: t, size: n = "default" }) {
|
|
62
|
+
return /* @__PURE__ */ S(m, {
|
|
57
63
|
size: n,
|
|
58
|
-
children: [e?.avatar && /* @__PURE__ */ g
|
|
64
|
+
children: [e?.avatar && /* @__PURE__ */ x(g, {
|
|
59
65
|
src: e.avatar,
|
|
60
66
|
srcSet: e.avatar_srcset,
|
|
61
67
|
sizes: n === "lg" ? "40px" : n === "sm" ? "24px" : "32px",
|
|
62
68
|
alt: "",
|
|
63
69
|
loading: "lazy"
|
|
64
|
-
}), /* @__PURE__ */
|
|
70
|
+
}), /* @__PURE__ */ x(h, {
|
|
65
71
|
"aria-hidden": !0,
|
|
66
72
|
children: (t.trim()[0] ?? "?").toUpperCase()
|
|
67
73
|
})]
|
|
68
74
|
});
|
|
69
75
|
}
|
|
70
|
-
function
|
|
71
|
-
let i = e && n?.(e), a =
|
|
72
|
-
return i ? /* @__PURE__ */
|
|
76
|
+
function A({ user: e, name: t, href: n, className: r }) {
|
|
77
|
+
let i = e && n?.(e), a = C("font-medium text-foreground", r);
|
|
78
|
+
return i ? /* @__PURE__ */ x("a", {
|
|
73
79
|
href: i,
|
|
74
|
-
className:
|
|
80
|
+
className: C(a, "hover:underline"),
|
|
75
81
|
children: t
|
|
76
|
-
}) : /* @__PURE__ */
|
|
82
|
+
}) : /* @__PURE__ */ x("span", {
|
|
77
83
|
className: a,
|
|
78
84
|
children: t
|
|
79
85
|
});
|
|
80
86
|
}
|
|
81
|
-
function
|
|
82
|
-
let { t: n } = t();
|
|
83
|
-
|
|
87
|
+
function j(e) {
|
|
88
|
+
let { t: n } = t(), r = y(), [m, h] = b("");
|
|
89
|
+
v(() => {
|
|
90
|
+
e.open && h("");
|
|
91
|
+
}, [e.open]);
|
|
92
|
+
let g = !e.typed || m.trim().toLowerCase() === e.typed.toLowerCase();
|
|
93
|
+
return /* @__PURE__ */ x(o, {
|
|
84
94
|
open: e.open,
|
|
85
95
|
onOpenChange: e.onOpenChange,
|
|
86
|
-
children: /* @__PURE__ */
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
children: /* @__PURE__ */ S(l, { children: [
|
|
97
|
+
/* @__PURE__ */ S(f, { children: [/* @__PURE__ */ x(p, { children: e.title }), e.description && /* @__PURE__ */ x(u, { children: e.description })] }),
|
|
98
|
+
e.typed && /* @__PURE__ */ S("div", {
|
|
99
|
+
className: "grid gap-1.5",
|
|
100
|
+
children: [/* @__PURE__ */ x(a, {
|
|
101
|
+
htmlFor: `${r}-typed`,
|
|
102
|
+
children: n("admin.typeToConfirm", { word: e.typed })
|
|
103
|
+
}), /* @__PURE__ */ x(i, {
|
|
104
|
+
id: `${r}-typed`,
|
|
105
|
+
value: m,
|
|
106
|
+
autoComplete: "off",
|
|
107
|
+
onChange: (e) => h(e.target.value)
|
|
108
|
+
})]
|
|
109
|
+
}),
|
|
110
|
+
/* @__PURE__ */ S(d, { children: [/* @__PURE__ */ x(c, { children: n("common.cancel") }), /* @__PURE__ */ x(s, {
|
|
111
|
+
variant: "destructive",
|
|
112
|
+
disabled: !g,
|
|
113
|
+
onClick: () => {
|
|
114
|
+
g && (e.onConfirm(), e.onOpenChange(!1));
|
|
115
|
+
},
|
|
116
|
+
children: e.confirm
|
|
117
|
+
})] })
|
|
118
|
+
] })
|
|
93
119
|
});
|
|
94
120
|
}
|
|
95
|
-
function
|
|
121
|
+
function M({ error: e, children: n, className: r }) {
|
|
96
122
|
let i = t();
|
|
97
|
-
return !e && !n ? null : /* @__PURE__ */
|
|
123
|
+
return !e && !n ? null : /* @__PURE__ */ x("p", {
|
|
98
124
|
role: "alert",
|
|
99
|
-
className:
|
|
100
|
-
children: n ??
|
|
125
|
+
className: C("text-sm text-destructive", r),
|
|
126
|
+
children: n ?? w(i, e)
|
|
101
127
|
});
|
|
102
128
|
}
|
|
103
129
|
//#endregion
|
|
104
|
-
export {
|
|
130
|
+
export { j as ConfirmDialog, M as ErrorLine, O as RelativeTime, k as UserAvatar, A as UserName, w as contentError, D as formatDate, E as relativeTime, T as useAction };
|
|
105
131
|
|
|
106
132
|
//# sourceMappingURL=parts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parts.js","names":[],"sources":["../../../src/components/social/parts.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useCallback, useState, type ReactNode } from \"react\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { PublicUser } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { useErrorReporter, type ContentKitErrorHandler, type SocialOperation } from \"../../react/context.js\";\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from \"#ckui/ui/alert-dialog\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"#ckui/ui/avatar\";\n\n/** A content refusal or fault in words: the content wording for shared codes, else the code's. */\nexport function contentError(m: Translator, e: unknown): string {\n const err = toContentKitError(e);\n if (err.code === \"rate_limited\") return m.t(\"contentErrors.rate_limited\", { seconds: err.retryAfter ?? 60 });\n if (err.code === \"forbidden\" || err.code === \"unauthorized\" || err.code === \"not_found\" || err.code === \"conflict\") return m.t(`contentErrors.${err.code}`);\n if (err.code === \"moderation_rejected\") return err.message ? m.t(\"comments.rejected\", { reason: err.message }) : m.t(\"comments.rejectedNoReason\");\n return m.error(err);\n}\n\n/** Runs one async action at a time: its pending state and last error, reported once. */\nexport function useAction(operation: SocialOperation, onError?: ContentKitErrorHandler) {\n const report = useErrorReporter(onError);\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<ContentKitError | null>(null);\n const run = useCallback(\n async <T,>(fn: () => Promise<T>): Promise<T | undefined> => {\n setPending(true);\n setError(null);\n try {\n return await fn();\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") {\n setError(err);\n report(err, operation);\n }\n return undefined;\n } finally {\n setPending(false);\n }\n },\n [operation, report],\n );\n return { run, pending, error, setError };\n}\n\n/** \"3 minutes ago\", in the UI's language. */\nexport function relativeTime(m: Translator, iso: string, now = Date.now()): string {\n const s = (Date.parse(iso) - now) / 1000;\n const a = Math.abs(s);\n if (a < 45) return m.t(\"time.justNow\");\n const f = new Intl.RelativeTimeFormat(m.language, { numeric: \"auto\" });\n if (a < 2700) return f.format(Math.round(s / 60), \"minute\");\n if (a < 79200) return f.format(Math.round(s / 3600), \"hour\");\n if (a < 2246400) return f.format(Math.round(s / 86400), \"day\");\n if (a < 28512000) return f.format(Math.round(s / 2592000), \"month\");\n return f.format(Math.round(s / 31536000), \"year\");\n}\n\nexport function formatDate(m: Translator, iso: string, time = true): string {\n return new Intl.DateTimeFormat(m.language, time ? { dateStyle: \"medium\", timeStyle: \"short\" } : { dateStyle: \"medium\" }).format(new Date(iso));\n}\n\n/** A relative time with the full date as its title. */\nexport function RelativeTime({ at, className }: { at: string; className?: string }) {\n const m = useMessages();\n return (\n <time dateTime={at} title={formatDate(m, at)} className={className}>\n {relativeTime(m, at)}\n </time>\n );\n}\n\n/** A user's avatar, or their initial. */\nexport function UserAvatar({ user, name, size = \"default\" }: { user?: PublicUser; name: string; size?: \"default\" | \"sm\" | \"lg\" }) {\n return (\n <Avatar size={size}>\n {user?.avatar && <AvatarImage src={user.avatar} srcSet={user.avatar_srcset} sizes={size === \"lg\" ? \"40px\" : size === \"sm\" ? \"24px\" : \"32px\"} alt=\"\" loading=\"lazy\" />}\n <AvatarFallback aria-hidden>{(name.trim()[0] ?? \"?\").toUpperCase()}</AvatarFallback>\n </Avatar>\n );\n}\n\n/** A user's name, linked when the host gives a profile URL. */\nexport function UserName({ user, name, href, className }: { user?: PublicUser; name: string; href?: (u: PublicUser) => string | undefined; className?: string }) {\n const url = user && href?.(user);\n const cls = cn(\"font-medium text-foreground\", className);\n return url ? (\n <a href={url} className={cn(cls, \"hover:underline\")}>\n {name}\n </a>\n ) : (\n <span className={cls}>{name}</span>\n );\n}\n\n/** A confirmation for a destructive action. */\nexport function ConfirmDialog(p: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n title: ReactNode;\n description?: ReactNode;\n confirm: ReactNode;\n onConfirm: () => void;\n}) {\n const { t } = useMessages();\n return (\n <AlertDialog open={p.open} onOpenChange={p.onOpenChange}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>{p.title}</AlertDialogTitle>\n {p.description && <AlertDialogDescription>{p.description}</AlertDialogDescription>}\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>{t(\"common.cancel\")}</AlertDialogCancel>\n <AlertDialogAction\n variant=\"destructive\"\n onClick={() => {\n p.onOpenChange(false);\n p.onConfirm();\n }}\n >\n {p.confirm}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n\n/** A failure line, announced politely. */\nexport function ErrorLine({ error, children, className }: { error?: ContentKitError | null; children?: ReactNode; className?: string }) {\n const m = useMessages();\n if (!error && !children) return null;\n return (\n <p role=\"alert\" className={cn(\"text-sm text-destructive\", className)}>\n {children ?? contentError(m, error)}\n </p>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parts.js","names":[],"sources":["../../../src/components/social/parts.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useCallback, useEffect, useId, useState, type ReactNode } from \"react\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { PublicUser } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { useErrorReporter, type ContentKitErrorHandler, type SocialOperation } from \"../../react/context.js\";\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from \"#ckui/ui/alert-dialog\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"#ckui/ui/avatar\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { useGuarded } from \"../../react/context.js\";\n\n/** A content refusal or fault in words: the content wording for shared codes, else the code's. */\nexport function contentError(m: Translator, e: unknown): string {\n const err = toContentKitError(e);\n if (err.code === \"rate_limited\") return m.t(\"contentErrors.rate_limited\", { seconds: err.retryAfter ?? 60 });\n if (err.code === \"forbidden\" || err.code === \"unauthorized\" || err.code === \"not_found\" || err.code === \"conflict\") return m.t(`contentErrors.${err.code}`);\n if (err.code === \"moderation_rejected\") return err.message ? m.t(\"comments.rejected\", { reason: err.message }) : m.t(\"comments.rejectedNoReason\");\n return m.error(err);\n}\n\n/** Runs one async action at a time (through the flow's guard): its pending state and last error, reported once. */\nexport function useAction(operation: SocialOperation, onError?: ContentKitErrorHandler) {\n const report = useErrorReporter(onError);\n const guarded = useGuarded();\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<ContentKitError | null>(null);\n const run = useCallback(\n async <T,>(fn: () => Promise<T>): Promise<T | undefined> => {\n setPending(true);\n setError(null);\n try {\n return await guarded(fn);\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") {\n setError(err);\n report(err, operation);\n }\n return undefined;\n } finally {\n setPending(false);\n }\n },\n [operation, report, guarded],\n );\n return { run, pending, error, setError };\n}\n\n/** \"3 minutes ago\", in the UI's language. */\nexport function relativeTime(m: Translator, iso: string, now = Date.now()): string {\n const s = (Date.parse(iso) - now) / 1000;\n const a = Math.abs(s);\n if (a < 45) return m.t(\"time.justNow\");\n const f = new Intl.RelativeTimeFormat(m.language, { numeric: \"auto\" });\n if (a < 2700) return f.format(Math.round(s / 60), \"minute\");\n if (a < 79200) return f.format(Math.round(s / 3600), \"hour\");\n if (a < 2246400) return f.format(Math.round(s / 86400), \"day\");\n if (a < 28512000) return f.format(Math.round(s / 2592000), \"month\");\n return f.format(Math.round(s / 31536000), \"year\");\n}\n\nexport function formatDate(m: Translator, iso: string, time = true): string {\n return new Intl.DateTimeFormat(m.language, time ? { dateStyle: \"medium\", timeStyle: \"short\" } : { dateStyle: \"medium\" }).format(new Date(iso));\n}\n\n/** A relative time with the full date as its title. */\nexport function RelativeTime({ at, className }: { at: string; className?: string }) {\n const m = useMessages();\n return (\n <time dateTime={at} title={formatDate(m, at)} className={className}>\n {relativeTime(m, at)}\n </time>\n );\n}\n\n/** A user's avatar, or their initial. */\nexport function UserAvatar({ user, name, size = \"default\" }: { user?: PublicUser; name: string; size?: \"default\" | \"sm\" | \"lg\" }) {\n return (\n <Avatar size={size}>\n {user?.avatar && <AvatarImage src={user.avatar} srcSet={user.avatar_srcset} sizes={size === \"lg\" ? \"40px\" : size === \"sm\" ? \"24px\" : \"32px\"} alt=\"\" loading=\"lazy\" />}\n <AvatarFallback aria-hidden>{(name.trim()[0] ?? \"?\").toUpperCase()}</AvatarFallback>\n </Avatar>\n );\n}\n\n/** A user's name, linked when the host gives a profile URL. */\nexport function UserName({ user, name, href, className }: { user?: PublicUser; name: string; href?: (u: PublicUser) => string | undefined; className?: string }) {\n const url = user && href?.(user);\n const cls = cn(\"font-medium text-foreground\", className);\n return url ? (\n <a href={url} className={cn(cls, \"hover:underline\")}>\n {name}\n </a>\n ) : (\n <span className={cls}>{name}</span>\n );\n}\n\n/** A confirmation for a destructive action; with typed, the word must be typed first (acts that cannot be undone). */\nexport function ConfirmDialog(p: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n title: ReactNode;\n description?: ReactNode;\n confirm: ReactNode;\n typed?: string;\n onConfirm: () => void;\n}) {\n const { t } = useMessages();\n const id = useId();\n const [word, setWord] = useState(\"\");\n useEffect(() => {\n if (p.open) setWord(\"\");\n }, [p.open]);\n const ready = !p.typed || word.trim().toLowerCase() === p.typed.toLowerCase();\n return (\n <AlertDialog open={p.open} onOpenChange={p.onOpenChange}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>{p.title}</AlertDialogTitle>\n {p.description && <AlertDialogDescription>{p.description}</AlertDialogDescription>}\n </AlertDialogHeader>\n {p.typed && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${id}-typed`}>{t(\"admin.typeToConfirm\", { word: p.typed })}</Label>\n <Input id={`${id}-typed`} value={word} autoComplete=\"off\" onChange={(e) => setWord(e.target.value)} />\n </div>\n )}\n <AlertDialogFooter>\n <AlertDialogCancel>{t(\"common.cancel\")}</AlertDialogCancel>\n <AlertDialogAction\n variant=\"destructive\"\n disabled={!ready}\n onClick={() => {\n if (!ready) return;\n p.onConfirm();\n p.onOpenChange(false);\n }}\n >\n {p.confirm}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n\n/** A failure line, announced politely. */\nexport function ErrorLine({ error, children, className }: { error?: ContentKitError | null; children?: ReactNode; className?: string }) {\n const m = useMessages();\n if (!error && !children) return null;\n return (\n <p role=\"alert\" className={cn(\"text-sm text-destructive\", className)}>\n {children ?? contentError(m, error)}\n </p>\n );\n}\n"],"mappings":";;;;;;;;;;;AAuBA,SAAgB,EAAa,GAAe,GAAoB;CAC9D,IAAM,IAAM,EAAkB,CAAC;CAI/B,OAHI,EAAI,SAAS,iBAAuB,EAAE,EAAE,8BAA8B,EAAE,SAAS,EAAI,cAAc,GAAG,CAAC,IACvG,EAAI,SAAS,eAAe,EAAI,SAAS,kBAAkB,EAAI,SAAS,eAAe,EAAI,SAAS,aAAmB,EAAE,EAAE,iBAAiB,EAAI,MAAM,IACtJ,EAAI,SAAS,wBAA8B,EAAI,UAAU,EAAE,EAAE,qBAAqB,EAAE,QAAQ,EAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,2BAA2B,IACzI,EAAE,MAAM,CAAG;AACpB;AAGA,SAAgB,EAAU,GAA4B,GAAkC;CACtF,IAAM,IAAS,EAAiB,CAAO,GACjC,IAAU,EAAW,GACrB,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAO,KAAY,EAAiC,IAAI;CAoB/D,OAAO;EAAE,KAnBG,EACV,OAAW,MAAiD;GAE1D,AADA,EAAW,EAAI,GACf,EAAS,IAAI;GACb,IAAI;IACF,OAAO,MAAM,EAAQ,CAAE;GACzB,SAAS,GAAG;IACV,IAAM,IAAM,EAAkB,CAAC;IAC/B,AAAI,EAAI,SAAS,cACf,EAAS,CAAG,GACZ,EAAO,GAAK,CAAS;IAEvB;GACF,UAAU;IACR,EAAW,EAAK;GAClB;EACF,GACA;GAAC;GAAW;GAAQ;EAAO,CAEpB;EAAK;EAAS;EAAO;CAAS;AACzC;AAGA,SAAgB,EAAa,GAAe,GAAa,IAAM,KAAK,IAAI,GAAW;CACjF,IAAM,KAAK,KAAK,MAAM,CAAG,IAAI,KAAO,KAC9B,IAAI,KAAK,IAAI,CAAC;CACpB,IAAI,IAAI,IAAI,OAAO,EAAE,EAAE,cAAc;CACrC,IAAM,IAAI,IAAI,KAAK,mBAAmB,EAAE,UAAU,EAAE,SAAS,OAAO,CAAC;CAKrE,OAJI,IAAI,OAAa,EAAE,OAAO,KAAK,MAAM,IAAI,EAAE,GAAG,QAAQ,IACtD,IAAI,QAAc,EAAE,OAAO,KAAK,MAAM,IAAI,IAAI,GAAG,MAAM,IACvD,IAAI,UAAgB,EAAE,OAAO,KAAK,MAAM,IAAI,KAAK,GAAG,KAAK,IACzD,IAAI,UAAiB,EAAE,OAAO,KAAK,MAAM,IAAI,MAAO,GAAG,OAAO,IAC3D,EAAE,OAAO,KAAK,MAAM,IAAI,OAAQ,GAAG,MAAM;AAClD;AAEA,SAAgB,EAAW,GAAe,GAAa,IAAO,IAAc;CAC1E,OAAO,IAAI,KAAK,eAAe,EAAE,UAAU,IAAO;EAAE,WAAW;EAAU,WAAW;CAAQ,IAAI,EAAE,WAAW,SAAS,CAAC,CAAC,CAAC,OAAO,IAAI,KAAK,CAAG,CAAC;AAC/I;AAGA,SAAgB,EAAa,EAAE,OAAI,gBAAiD;CAClF,IAAM,IAAI,EAAY;CACtB,OACE,kBAAC,QAAD;EAAM,UAAU;EAAI,OAAO,EAAW,GAAG,CAAE;EAAc;EACtD,UAAA,EAAa,GAAG,CAAE;CACf,CAAA;AAEV;AAGA,SAAgB,EAAW,EAAE,SAAM,SAAM,UAAO,aAAkF;CAChI,OACE,kBAAC,GAAD;EAAc;EAAd,UAAA,CACG,GAAM,UAAU,kBAAC,GAAD;GAAa,KAAK,EAAK;GAAQ,QAAQ,EAAK;GAAe,OAAO,MAAS,OAAO,SAAS,MAAS,OAAO,SAAS;GAAQ,KAAI;GAAG,SAAQ;EAAQ,CAAA,GACpK,kBAAC,GAAD;GAAgB,eAAA;GAAc,WAAA,EAAK,KAAK,CAAC,CAAC,MAAM,IAAA,CAAK,YAAY;EAAkB,CAAA,CAC7E;;AAEZ;AAGA,SAAgB,EAAS,EAAE,SAAM,SAAM,SAAM,gBAAoH;CAC/J,IAAM,IAAM,KAAQ,IAAO,CAAI,GACzB,IAAM,EAAG,+BAA+B,CAAS;CACvD,OAAO,IACL,kBAAC,KAAD;EAAG,MAAM;EAAK,WAAW,EAAG,GAAK,iBAAiB;EAC/C,UAAA;CACA,CAAA,IAEH,kBAAC,QAAD;EAAM,WAAW;EAAM,UAAA;CAAW,CAAA;AAEtC;AAGA,SAAgB,EAAc,GAQ3B;CACD,IAAM,EAAE,SAAM,EAAY,GACpB,IAAK,EAAM,GACX,CAAC,GAAM,KAAW,EAAS,EAAE;CACnC,QAAgB;EACd,AAAI,EAAE,QAAM,EAAQ,EAAE;CACxB,GAAG,CAAC,EAAE,IAAI,CAAC;CACX,IAAM,IAAQ,CAAC,EAAE,SAAS,EAAK,KAAK,CAAC,CAAC,YAAY,MAAM,EAAE,MAAM,YAAY;CAC5E,OACE,kBAAC,GAAD;EAAa,MAAM,EAAE;EAAM,cAAc,EAAE;EACzC,UAAA,kBAAC,GAAD,EAAA,UAAA;GACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAmB,EAAE,MAAwB,CAAA,GAC5C,EAAE,eAAe,kBAAC,GAAD,EAAA,UAAyB,EAAE,YAAoC,CAAA,CAChE,EAAA,CAAA;GAClB,EAAE,SACD,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,GAAD;KAAO,SAAS,GAAG,EAAG;KAAU,UAAA,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,CAAC;IAAS,CAAA,GACnF,kBAAC,GAAD;KAAO,IAAI,GAAG,EAAG;KAAS,OAAO;KAAM,cAAa;KAAM,WAAW,MAAM,EAAQ,EAAE,OAAO,KAAK;IAAI,CAAA,CAClG;;GAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAoB,EAAE,eAAe,EAAqB,CAAA,GAC1D,kBAAC,GAAD;IACE,SAAQ;IACR,UAAU,CAAC;IACX,eAAe;KACR,MACL,EAAE,UAAU,GACZ,EAAE,aAAa,EAAK;IACtB;IAEC,UAAA,EAAE;GACc,CAAA,CACF,EAAA,CAAA;EACD,EAAA,CAAA;CACT,CAAA;AAEjB;AAGA,SAAgB,EAAU,EAAE,UAAO,aAAU,gBAA2F;CACtI,IAAM,IAAI,EAAY;CAEtB,OADI,CAAC,KAAS,CAAC,IAAiB,OAE9B,kBAAC,KAAD;EAAG,MAAK;EAAQ,WAAW,EAAG,4BAA4B,CAAS;EAChE,UAAA,KAAY,EAAa,GAAG,CAAK;CACjC,CAAA;AAEP"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"poll-editor.js","names":[],"sources":["../../../src/components/social/poll-editor.tsx"],"sourcesContent":["import { Delete02Icon, ImageAdd01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useEffect, useId, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { Poll, PollKind, PollOption, PollUpdate } from \"../../client/generated/wire.js\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { usePollEditor } from \"../../react/polls.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Switch } from \"#ckui/ui/switch\";\nimport { Textarea } from \"#ckui/ui/textarea\";\nimport { localInput } from \"./bans.js\";\nimport { SortableList } from \"../sortable-list.js\";\nimport { ConfirmDialog, contentError, ErrorLine } from \"./parts.js\";\n\nexport interface PollEditorProps {\n /** The poll to edit; none creates one. */\n poll?: string | null;\n /** Languages offered for a new poll: codes, or codes with labels. Without them the language is typed. */\n languages?: readonly (string | { value: string; label: string })[];\n /** A new poll's language. Default the first offered, else \"en\". */\n defaultLanguage?: string;\n /** Offer free-text polls (the host registered an answer classifier). Default false. */\n freeText?: boolean;\n /** Most options. Default 10. */\n maxOptions?: number;\n onCreated?: (poll: Poll) => void;\n onSaved?: (poll: Poll) => void;\n onDeleted?: () => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ninterface Draft {\n key: string;\n label: string;\n image: File | null;\n}\n\nconst toISO = (local: string) => (local ? new Date(local).toISOString() : undefined);\nlet seq = 0;\nconst draft = (): Draft => ({ key: `o${++seq}`, label: \"\", image: null });\n\n/**\n * Staff: creates or edits a poll (root:polls:write): question, language, type, when\n * it goes live and closes, visibility, a question image, and options with\n * images, reordered with the move buttons. A new poll is written at once with\n * its images; an existing one saves its fields with Save and each option\n * change as it is made.\n */\nexport function PollEditor(p: PollEditorProps) {\n const m = useMessages();\n const { t } = m;\n const ed = usePollEditor(p.poll ?? null, { client: p.client });\n const report = useErrorReporter(p.onError);\n const [error, setError] = useState<ContentKitError | string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const [deleting, setDeleting] = useState(false);\n const fail = (e: unknown) => {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"poll.save\");\n };\n const root = (children: ReactNode) => (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-5 text-sm\", p.className)} data-ckui=\"poll-editor\">\n {children}\n </ContentKitUiRoot>\n );\n if (ed.loading && !ed.poll) {\n return root(\n <div className=\"grid gap-3\" aria-busy>\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-24 w-full\" />\n </div>,\n );\n }\n if (ed.error && !ed.poll) return root(<ErrorLine error={ed.error} />);\n return root(\n <>\n <h2 className=\"text-base font-semibold\">{t(ed.poll ? \"pollEditor.editTitle\" : \"pollEditor.newTitle\")}</h2>\n {ed.poll ? (\n <EditFields\n key={ed.poll.id}\n poll={ed.poll}\n saving={ed.saving}\n maxOptions={p.maxOptions ?? 10}\n editor={ed}\n onError={fail}\n onInvalid={(msg) => setError(msg)}\n onSaved={(poll) => {\n setError(null);\n setNotice(t(\"pollEditor.saved\"));\n p.onSaved?.(poll);\n }}\n />\n ) : (\n <CreateFields\n saving={ed.saving}\n languages={p.languages}\n defaultLanguage={p.defaultLanguage}\n freeText={!!p.freeText}\n maxOptions={p.maxOptions ?? 10}\n onInvalid={(msg) => setError(msg)}\n onCreate={async (input, images) => {\n setError(null);\n try {\n const { poll, imageErrors } = await ed.create(input, images);\n setNotice(imageErrors.length ? t(\"pollEditor.imageFailed\") : t(\"pollEditor.created\"));\n for (const e of imageErrors) report(e, \"poll.save\");\n p.onCreated?.(poll);\n } catch (e) {\n fail(e);\n }\n }}\n />\n )}\n {error && <ErrorLine>{typeof error === \"string\" ? error : contentError(m, error)}</ErrorLine>}\n {notice && !error && (\n <p role=\"status\" className=\"text-sm text-muted-foreground\">\n {notice}\n </p>\n )}\n {ed.poll && (\n <div className=\"border-t border-border pt-4\">\n <Button variant=\"destructive\" onClick={() => setDeleting(true)} disabled={ed.saving}>\n {t(\"pollEditor.delete\")}\n </Button>\n </div>\n )}\n <ConfirmDialog\n open={deleting}\n onOpenChange={setDeleting}\n title={t(\"pollEditor.deleteTitle\")}\n description={t(\"pollEditor.deleteBody\")}\n confirm={t(\"pollEditor.delete\")}\n onConfirm={() =>\n void ed.remove().then(\n () => p.onDeleted?.(),\n (e) => fail(e),\n )\n }\n />\n </>,\n );\n}\n\nfunction CreateFields(p: {\n saving: boolean;\n languages?: PollEditorProps[\"languages\"];\n defaultLanguage?: string;\n freeText: boolean;\n maxOptions: number;\n onInvalid: (message: string) => void;\n onCreate: (input: Parameters<ReturnType<typeof usePollEditor>[\"create\"]>[0], images: { question: File | null; options: (File | null)[] }) => Promise<void>;\n}) {\n const { t } = useMessages();\n const ids = useId();\n const langs = useMemo(() => (p.languages ?? []).map((l) => (typeof l === \"string\" ? { value: l, label: l } : l)), [p.languages]);\n const [question, setQuestion] = useState(\"\");\n const [language, setLanguage] = useState(p.defaultLanguage ?? langs[0]?.value ?? \"en\");\n const [kind, setKind] = useState<PollKind>(\"multiple_choice\");\n const [liveAt, setLiveAt] = useState(() => localInput(new Date()));\n const [closesAt, setClosesAt] = useState(\"\");\n const [image, setImage] = useState<File | null>(null);\n const [options, setOptions] = useState<Draft[]>(() => [draft(), draft()]);\n const set = (key: string, patch: Partial<Draft>) => setOptions((os) => os.map((o) => (o.key === key ? { ...o, ...patch } : o)));\n const move = (from: number, to: number) =>\n setOptions((os) => {\n const next = [...os];\n next.splice(to, 0, ...next.splice(from, 1));\n return next;\n });\n const submit = () => {\n if (!question.trim()) return p.onInvalid(t(\"pollEditor.questionRequired\"));\n const kept = options.filter((o) => o.label.trim() || o.image);\n if (kind === \"multiple_choice\") {\n if (kept.some((o) => !o.label.trim())) return p.onInvalid(t(\"pollEditor.optionRequired\"));\n if (kept.length < 2) return p.onInvalid(t(\"pollEditor.minOptions\"));\n }\n const choice = kind === \"multiple_choice\";\n void p.onCreate(\n {\n kind,\n question: question.trim(),\n language,\n live_at: toISO(liveAt),\n closes_at: toISO(closesAt),\n options: choice ? kept.map((o, position) => ({ label: o.label.trim(), position })) : undefined,\n },\n { question: image, options: choice ? kept.map((o) => o.image) : [] },\n );\n };\n return (\n <form\n className=\"grid gap-4\"\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n >\n <Field id={`${ids}-q`} label={t(\"pollEditor.question\")}>\n <Textarea id={`${ids}-q`} rows={2} value={question} placeholder={t(\"pollEditor.questionPlaceholder\")} onChange={(e) => setQuestion(e.target.value)} />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field id={`${ids}-lang`} label={t(\"pollEditor.language\")}>\n {langs.length ? (\n <Select value={language} onValueChange={(v) => setLanguage(String(v))}>\n <SelectTrigger id={`${ids}-lang`} className=\"w-full\">\n <SelectValue>{(v: string) => langs.find((l) => l.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {langs.map((l) => (\n <SelectItem key={l.value} value={l.value}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : (\n <Input id={`${ids}-lang`} value={language} onChange={(e) => setLanguage(e.target.value)} />\n )}\n </Field>\n {p.freeText && (\n <fieldset className=\"grid gap-1.5\">\n <legend className=\"mb-1.5 text-sm font-medium\">{t(\"pollEditor.kind\")}</legend>\n <div className=\"flex flex-wrap gap-2\">\n {([\"multiple_choice\", \"free_text\"] as const).map((k) => (\n <Button key={k} type=\"button\" size=\"sm\" variant={kind === k ? \"default\" : \"outline\"} aria-pressed={kind === k} onClick={() => setKind(k)}>\n {t(k === \"free_text\" ? \"pollEditor.freeText\" : \"pollEditor.multipleChoice\")}\n </Button>\n ))}\n </div>\n </fieldset>\n )}\n <Field id={`${ids}-live`} label={t(\"pollEditor.liveAt\")}>\n <Input id={`${ids}-live`} type=\"datetime-local\" value={liveAt} onChange={(e) => setLiveAt(e.target.value)} />\n </Field>\n <Field id={`${ids}-closes`} label={t(\"pollEditor.closesAt\")}>\n <Input id={`${ids}-closes`} type=\"datetime-local\" value={closesAt} min={liveAt} onChange={(e) => setClosesAt(e.target.value)} />\n </Field>\n </div>\n <ImageField label={t(\"pollEditor.image\")} url={useObjectURL(image)} onPick={setImage} onRemove={() => setImage(null)} />\n {kind === \"free_text\" ? (\n <p className=\"text-muted-foreground\">{t(\"pollEditor.freeTextHelp\")}</p>\n ) : (\n <fieldset className=\"grid gap-2\">\n <legend className=\"mb-1.5 text-sm font-medium\">{t(\"pollEditor.options\")}</legend>\n <SortableList\n items={options}\n id={(o) => o.key}\n name={(o) => o.label || t(\"pollEditor.option\", { index: options.indexOf(o) + 1 })}\n onMove={move}\n label={t(\"pollEditor.options\")}\n className=\"gap-2\"\n row={() => ({ className: OPTION_ROW, \"data-ckui\": \"poll-option\" })}\n >\n {(o, handle, i) => (\n <OptionRow\n handle={handle}\n index={i}\n label={o.label}\n image={o.image}\n onLabel={(label) => set(o.key, { label })}\n onImage={(image) => set(o.key, { image })}\n onRemove={options.length > 2 ? () => setOptions((os) => os.filter((x) => x.key !== o.key)) : undefined}\n />\n )}\n </SortableList>\n {options.length < p.maxOptions ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"justify-self-start\" onClick={() => setOptions((os) => [...os, draft()])}>\n {t(\"pollEditor.addOption\")}\n </Button>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t(\"pollEditor.maxOptions\", { max: p.maxOptions })}</p>\n )}\n </fieldset>\n )}\n <Button type=\"submit\" className=\"justify-self-start\" disabled={p.saving}>\n {p.saving ? t(\"pollEditor.creating\") : t(\"pollEditor.create\")}\n </Button>\n </form>\n );\n}\n\nfunction EditFields(p: {\n poll: Poll;\n saving: boolean;\n maxOptions: number;\n editor: ReturnType<typeof usePollEditor>;\n onError: (e: unknown) => void;\n onInvalid: (message: string) => void;\n onSaved: (poll: Poll) => void;\n}) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const { poll, editor } = p;\n const [question, setQuestion] = useState(poll.question);\n const [liveAt, setLiveAt] = useState(localInput(new Date(poll.live_at)));\n const [closesAt, setClosesAt] = useState(poll.closes_at ? localInput(new Date(poll.closes_at)) : \"\");\n const [active, setActive] = useState(poll.is_active);\n const [added, setAdded] = useState(\"\");\n const [removing, setRemoving] = useState<PollOption | null>(null);\n const options = [...poll.options].sort((a, b) => a.position - b.position);\n const patch: PollUpdate = {};\n if (question.trim() !== poll.question) patch.question = question.trim();\n if (toISO(liveAt) !== new Date(localInput(new Date(poll.live_at))).toISOString()) patch.live_at = toISO(liveAt);\n if (closesAt && toISO(closesAt) !== (poll.closes_at && new Date(localInput(new Date(poll.closes_at))).toISOString())) patch.closes_at = toISO(closesAt);\n else if (!closesAt && poll.closes_at) patch.closes_at = null;\n if (active !== poll.is_active) patch.is_active = active;\n const dirty = Object.keys(patch).length > 0;\n const act = (fn: () => Promise<unknown>) => void fn().catch(p.onError);\n return (\n <div className=\"grid gap-4\">\n <form\n className=\"grid gap-4\"\n onSubmit={(e) => {\n e.preventDefault();\n if (!question.trim()) return p.onInvalid(t(\"pollEditor.questionRequired\"));\n if (dirty) act(async () => p.onSaved(await editor.update(patch)));\n }}\n >\n <Field id={`${ids}-q`} label={t(\"pollEditor.question\")}>\n <Textarea id={`${ids}-q`} rows={2} value={question} onChange={(e) => setQuestion(e.target.value)} />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field id={`${ids}-live`} label={t(\"pollEditor.liveAt\")}>\n <Input id={`${ids}-live`} type=\"datetime-local\" value={liveAt} onChange={(e) => setLiveAt(e.target.value)} />\n </Field>\n <Field id={`${ids}-closes`} label={t(\"pollEditor.closesAt\")}>\n <Input id={`${ids}-closes`} type=\"datetime-local\" value={closesAt} min={liveAt} onChange={(e) => setClosesAt(e.target.value)} />\n </Field>\n </div>\n <div className=\"flex items-center gap-3\">\n <Switch id={`${ids}-active`} checked={active} onCheckedChange={setActive} />\n <Label htmlFor={`${ids}-active`}>{t(active ? \"pollEditor.active\" : \"pollEditor.inactive\")}</Label>\n </div>\n <Button type=\"submit\" className=\"justify-self-start\" disabled={!dirty || p.saving}>\n {p.saving ? t(\"common.saving\") : t(\"common.save\")}\n </Button>\n </form>\n <ImageField label={t(\"pollEditor.image\")} url={poll.image_url} disabled={p.saving} onPick={(f) => act(() => editor.setImage(f))} onRemove={() => act(() => editor.setImage(null))} />\n {poll.kind === \"free_text\" ? (\n <p className=\"text-muted-foreground\">\n {t(\"pollEditor.freeTextHelp\")} {m.plural(\"poll.answers\", poll.answer_count ?? 0)}\n </p>\n ) : (\n <fieldset className=\"grid gap-2\">\n <legend className=\"mb-1.5 text-sm font-medium\">{t(\"pollEditor.options\")}</legend>\n <SortableList\n items={options}\n id={(o) => o.id}\n name={(o) => o.label}\n disabled={p.saving}\n onMove={(from, to) => act(() => editor.moveOption(options[from]!.id, to))}\n label={t(\"pollEditor.options\")}\n className=\"gap-2\"\n row={() => ({ className: OPTION_ROW, \"data-ckui\": \"poll-option\" })}\n >\n {(o, handle, i) => (\n <OptionRow\n handle={handle}\n index={i}\n label={o.label}\n url={o.image_url}\n votes={o.vote_count}\n disabled={p.saving}\n onCommit={(label) => label.trim() && label.trim() !== o.label && act(() => editor.renameOption(o.id, label.trim()))}\n onImage={(f) => act(() => editor.setOptionImage(o.id, f))}\n onRemove={options.length > 2 ? () => setRemoving(o) : undefined}\n />\n )}\n </SortableList>\n {options.length < p.maxOptions ? (\n <form\n className=\"flex gap-2\"\n onSubmit={(e) => {\n e.preventDefault();\n if (!added.trim()) return;\n act(async () => {\n await editor.addOption(added.trim());\n setAdded(\"\");\n });\n }}\n >\n <Input value={added} placeholder={t(\"pollEditor.newOption\")} aria-label={t(\"pollEditor.newOption\")} onChange={(e) => setAdded(e.target.value)} />\n <Button type=\"submit\" variant=\"outline\" disabled={!added.trim() || p.saving}>\n {t(\"pollEditor.addOption\")}\n </Button>\n </form>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t(\"pollEditor.maxOptions\", { max: p.maxOptions })}</p>\n )}\n </fieldset>\n )}\n <ConfirmDialog\n open={!!removing}\n onOpenChange={(o) => !o && setRemoving(null)}\n title={t(\"pollEditor.removeOptionTitle\")}\n description={t(\"pollEditor.removeOptionBody\")}\n confirm={t(\"pollEditor.removeOption\")}\n onConfirm={() => removing && act(() => editor.removeOption(removing.id))}\n />\n </div>\n );\n}\n\nconst OPTION_ROW = \"flex flex-wrap items-center gap-2 rounded-lg border border-border p-2\";\n\n/** One option's content: its drag handle, label, image, votes and remove. */\nfunction OptionRow(p: {\n handle: ReactNode;\n index: number;\n label: string;\n /** A new poll's staged image. */\n image?: File | null;\n /** An existing option's image. */\n url?: string;\n votes?: number;\n disabled?: boolean;\n /** A new poll's label as typed. */\n onLabel?: (label: string) => void;\n /** An existing option's label, on blur or Enter. */\n onCommit?: (label: string) => void;\n onImage: (file: File | null) => void;\n onRemove?: () => void;\n}) {\n const m = useMessages();\n const { t } = m;\n const [label, setLabel] = useState(p.label);\n useEffect(() => setLabel(p.label), [p.label]);\n const staged = useObjectURL(p.image ?? null);\n const name = t(\"pollEditor.option\", { index: p.index + 1 });\n return (\n <>\n {p.handle}\n <Input\n className=\"min-w-40 flex-1\"\n value={label}\n aria-label={name}\n placeholder={name}\n disabled={p.disabled && !p.onLabel}\n onChange={(e) => {\n setLabel(e.target.value);\n p.onLabel?.(e.target.value);\n }}\n onBlur={() => p.onCommit?.(label)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && p.onCommit) {\n e.preventDefault();\n p.onCommit(label);\n }\n }}\n />\n <ImageField compact label={t(\"pollEditor.optionImage\", { index: p.index + 1 })} url={p.url ?? staged} disabled={p.disabled} onPick={p.onImage} onRemove={() => p.onImage(null)} />\n {p.votes !== undefined && <span className=\"text-xs tabular-nums text-muted-foreground\">{m.plural(\"poll.votes\", p.votes)}</span>}\n {p.onRemove && (\n <Button type=\"button\" variant=\"ghost\" size=\"icon-sm\" aria-label={`${t(\"pollEditor.removeOption\")}: ${name}`} disabled={p.disabled} onClick={p.onRemove}>\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} />\n </Button>\n )}\n </>\n );\n}\n\nfunction ImageField(p: { label: string; url?: string | null; compact?: boolean; disabled?: boolean; onPick: (file: File) => void; onRemove: () => void }) {\n const { t } = useMessages();\n const input = useRef<HTMLInputElement>(null);\n const pick = (\n <input\n ref={input}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={p.label}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) p.onPick(f);\n }}\n />\n );\n if (p.compact) {\n return (\n <div className=\"flex items-center gap-1\" data-ckui=\"poll-image\">\n {pick}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className=\"overflow-hidden p-0\"\n aria-label={`${p.url ? t(\"pollEditor.replaceImage\") : t(\"pollEditor.uploadImage\")}: ${p.label}`}\n disabled={p.disabled}\n onClick={() => input.current?.click()}\n >\n {p.url ? <img src={p.url} alt=\"\" className=\"size-full object-cover\" /> : <HugeiconsIcon icon={ImageAdd01Icon} strokeWidth={2} />}\n </Button>\n {p.url && (\n <Button type=\"button\" variant=\"ghost\" size=\"xs\" disabled={p.disabled} onClick={p.onRemove}>\n {t(\"pollEditor.removeImage\")}\n </Button>\n )}\n </div>\n );\n }\n return (\n <div className=\"grid gap-1.5\" data-ckui=\"poll-image\">\n <span className=\"text-sm font-medium\">{p.label}</span>\n {pick}\n {p.url && <img src={p.url} alt=\"\" className=\"aspect-video w-full max-w-sm rounded-lg border border-border object-cover\" />}\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={p.disabled} onClick={() => input.current?.click()}>\n <HugeiconsIcon icon={ImageAdd01Icon} strokeWidth={2} data-icon=\"inline-start\" />\n {p.url ? t(\"pollEditor.replaceImage\") : t(\"pollEditor.uploadImage\")}\n </Button>\n {p.url && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" disabled={p.disabled} onClick={p.onRemove}>\n {t(\"pollEditor.removeImage\")}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\nfunction Field({ id, label, children }: { id: string; label: string; children: ReactNode }) {\n return (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={id}>{label}</Label>\n {children}\n </div>\n );\n}\n\n/** An object URL for a staged file, revoked when it changes. */\nfunction useObjectURL(file: File | null): string | undefined {\n const [url, setUrl] = useState<string>();\n useEffect(() => {\n if (!file) return setUrl(undefined);\n const u = URL.createObjectURL(file);\n setUrl(u);\n return () => URL.revokeObjectURL(u);\n }, [file]);\n return url;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"poll-editor.js","names":[],"sources":["../../../src/components/social/poll-editor.tsx"],"sourcesContent":["import { Delete02Icon, ImageAdd01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useEffect, useId, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../../client/errors.js\";\nimport type { Poll, PollKind, PollOption, PollUpdate } from \"../../client/generated/wire.js\";\nimport type { ContentKitUiAppearance } from \"../../appearance.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { usePollEditor } from \"../../react/polls.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Input } from \"#ckui/ui/input\";\nimport { Label } from \"#ckui/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { Switch } from \"#ckui/ui/switch\";\nimport { Textarea } from \"#ckui/ui/textarea\";\nimport { localInput } from \"./bans.js\";\nimport { SortableList } from \"../sortable-list.js\";\nimport { ConfirmDialog, contentError, ErrorLine } from \"./parts.js\";\n\nexport interface PollEditorProps {\n /** The poll to edit; none creates one. */\n poll?: string | null;\n /** Languages offered for a new poll: codes, or codes with labels. Without them the language is typed. */\n languages?: readonly (string | { value: string; label: string })[];\n /** A new poll's language. Default the first offered, else \"en\". */\n defaultLanguage?: string;\n /** Offer free-text polls (the host registered an answer classifier). Default false. */\n freeText?: boolean;\n /** Most options. Default 10. */\n maxOptions?: number;\n onCreated?: (poll: Poll) => void;\n onSaved?: (poll: Poll) => void;\n onDeleted?: () => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\ninterface Draft {\n key: string;\n label: string;\n image: File | null;\n}\n\nconst toISO = (local: string) => (local ? new Date(local).toISOString() : undefined);\nlet seq = 0;\nconst draft = (): Draft => ({ key: `o${++seq}`, label: \"\", image: null });\n\n/**\n * Staff: creates or edits a poll (root:polls:write): question, language, type, when\n * it goes live and closes, visibility, a question image, and options with\n * images, reordered with the move buttons. A new poll is written at once with\n * its images; an existing one saves its fields with Save and each option\n * change as it is made.\n */\nexport function PollEditor(p: PollEditorProps) {\n const m = useMessages();\n const { t } = m;\n const ed = usePollEditor(p.poll ?? null, { client: p.client });\n const report = useErrorReporter(p.onError);\n const [error, setError] = useState<ContentKitError | string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const [deleting, setDeleting] = useState(false);\n const fail = (e: unknown) => {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"poll.save\");\n };\n const root = (children: ReactNode) => (\n <ContentKitUiRoot appearance={p.appearance} className={cn(\"grid gap-5 text-sm\", p.className)} data-ckui=\"poll-editor\">\n {children}\n </ContentKitUiRoot>\n );\n if (ed.loading && !ed.poll) {\n return root(\n <div className=\"grid gap-3\" aria-busy>\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-24 w-full\" />\n </div>,\n );\n }\n if (ed.error && !ed.poll) return root(<ErrorLine error={ed.error} />);\n return root(\n <>\n <h2 className=\"text-base font-semibold\">{t(ed.poll ? \"pollEditor.editTitle\" : \"pollEditor.newTitle\")}</h2>\n {ed.poll ? (\n <EditFields\n key={ed.poll.id}\n poll={ed.poll}\n saving={ed.saving}\n maxOptions={p.maxOptions ?? 10}\n editor={ed}\n onError={fail}\n onInvalid={(msg) => setError(msg)}\n onSaved={(poll) => {\n setError(null);\n setNotice(t(\"pollEditor.saved\"));\n p.onSaved?.(poll);\n }}\n />\n ) : (\n <CreateFields\n saving={ed.saving}\n languages={p.languages}\n defaultLanguage={p.defaultLanguage}\n freeText={!!p.freeText}\n maxOptions={p.maxOptions ?? 10}\n onInvalid={(msg) => setError(msg)}\n onCreate={async (input, images) => {\n setError(null);\n try {\n const { poll, imageErrors } = await ed.create(input, images);\n setNotice(imageErrors.length ? t(\"pollEditor.imageFailed\") : t(\"pollEditor.created\"));\n for (const e of imageErrors) report(e, \"poll.save\");\n p.onCreated?.(poll);\n } catch (e) {\n fail(e);\n }\n }}\n />\n )}\n {error && <ErrorLine>{typeof error === \"string\" ? error : contentError(m, error)}</ErrorLine>}\n {notice && !error && (\n <p role=\"status\" className=\"text-sm text-muted-foreground\">\n {notice}\n </p>\n )}\n {ed.poll && (\n <div className=\"border-t border-border pt-4\">\n <Button variant=\"destructive\" onClick={() => setDeleting(true)} disabled={ed.saving}>\n {t(\"pollEditor.delete\")}\n </Button>\n </div>\n )}\n <ConfirmDialog\n open={deleting}\n onOpenChange={setDeleting}\n title={t(\"pollEditor.deleteTitle\")}\n description={t(\"pollEditor.deleteBody\")}\n confirm={t(\"pollEditor.delete\")}\n typed={t(\"admin.deleteWord\")}\n onConfirm={() =>\n void ed.remove().then(\n () => p.onDeleted?.(),\n (e) => fail(e),\n )\n }\n />\n </>,\n );\n}\n\nfunction CreateFields(p: {\n saving: boolean;\n languages?: PollEditorProps[\"languages\"];\n defaultLanguage?: string;\n freeText: boolean;\n maxOptions: number;\n onInvalid: (message: string) => void;\n onCreate: (input: Parameters<ReturnType<typeof usePollEditor>[\"create\"]>[0], images: { question: File | null; options: (File | null)[] }) => Promise<void>;\n}) {\n const { t } = useMessages();\n const ids = useId();\n const langs = useMemo(() => (p.languages ?? []).map((l) => (typeof l === \"string\" ? { value: l, label: l } : l)), [p.languages]);\n const [question, setQuestion] = useState(\"\");\n const [language, setLanguage] = useState(p.defaultLanguage ?? langs[0]?.value ?? \"en\");\n const [kind, setKind] = useState<PollKind>(\"multiple_choice\");\n const [liveAt, setLiveAt] = useState(() => localInput(new Date()));\n const [closesAt, setClosesAt] = useState(\"\");\n const [image, setImage] = useState<File | null>(null);\n const [options, setOptions] = useState<Draft[]>(() => [draft(), draft()]);\n const set = (key: string, patch: Partial<Draft>) => setOptions((os) => os.map((o) => (o.key === key ? { ...o, ...patch } : o)));\n const move = (from: number, to: number) =>\n setOptions((os) => {\n const next = [...os];\n next.splice(to, 0, ...next.splice(from, 1));\n return next;\n });\n const submit = () => {\n if (!question.trim()) return p.onInvalid(t(\"pollEditor.questionRequired\"));\n const kept = options.filter((o) => o.label.trim() || o.image);\n if (kind === \"multiple_choice\") {\n if (kept.some((o) => !o.label.trim())) return p.onInvalid(t(\"pollEditor.optionRequired\"));\n if (kept.length < 2) return p.onInvalid(t(\"pollEditor.minOptions\"));\n }\n const choice = kind === \"multiple_choice\";\n void p.onCreate(\n {\n kind,\n question: question.trim(),\n language,\n live_at: toISO(liveAt),\n closes_at: toISO(closesAt),\n options: choice ? kept.map((o, position) => ({ label: o.label.trim(), position })) : undefined,\n },\n { question: image, options: choice ? kept.map((o) => o.image) : [] },\n );\n };\n return (\n <form\n className=\"grid gap-4\"\n onSubmit={(e) => {\n e.preventDefault();\n submit();\n }}\n >\n <Field id={`${ids}-q`} label={t(\"pollEditor.question\")}>\n <Textarea id={`${ids}-q`} rows={2} value={question} placeholder={t(\"pollEditor.questionPlaceholder\")} onChange={(e) => setQuestion(e.target.value)} />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field id={`${ids}-lang`} label={t(\"pollEditor.language\")}>\n {langs.length ? (\n <Select value={language} onValueChange={(v) => setLanguage(String(v))}>\n <SelectTrigger id={`${ids}-lang`} className=\"w-full\">\n <SelectValue>{(v: string) => langs.find((l) => l.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {langs.map((l) => (\n <SelectItem key={l.value} value={l.value}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ) : (\n <Input id={`${ids}-lang`} value={language} onChange={(e) => setLanguage(e.target.value)} />\n )}\n </Field>\n {p.freeText && (\n <fieldset className=\"grid gap-1.5\">\n <legend className=\"mb-1.5 text-sm font-medium\">{t(\"pollEditor.kind\")}</legend>\n <div className=\"flex flex-wrap gap-2\">\n {([\"multiple_choice\", \"free_text\"] as const).map((k) => (\n <Button key={k} type=\"button\" size=\"sm\" variant={kind === k ? \"default\" : \"outline\"} aria-pressed={kind === k} onClick={() => setKind(k)}>\n {t(k === \"free_text\" ? \"pollEditor.freeText\" : \"pollEditor.multipleChoice\")}\n </Button>\n ))}\n </div>\n </fieldset>\n )}\n <Field id={`${ids}-live`} label={t(\"pollEditor.liveAt\")}>\n <Input id={`${ids}-live`} type=\"datetime-local\" value={liveAt} onChange={(e) => setLiveAt(e.target.value)} />\n </Field>\n <Field id={`${ids}-closes`} label={t(\"pollEditor.closesAt\")}>\n <Input id={`${ids}-closes`} type=\"datetime-local\" value={closesAt} min={liveAt} onChange={(e) => setClosesAt(e.target.value)} />\n </Field>\n </div>\n <ImageField label={t(\"pollEditor.image\")} url={useObjectURL(image)} onPick={setImage} onRemove={() => setImage(null)} />\n {kind === \"free_text\" ? (\n <p className=\"text-muted-foreground\">{t(\"pollEditor.freeTextHelp\")}</p>\n ) : (\n <fieldset className=\"grid gap-2\">\n <legend className=\"mb-1.5 text-sm font-medium\">{t(\"pollEditor.options\")}</legend>\n <SortableList\n items={options}\n id={(o) => o.key}\n name={(o) => o.label || t(\"pollEditor.option\", { index: options.indexOf(o) + 1 })}\n onMove={move}\n label={t(\"pollEditor.options\")}\n className=\"gap-2\"\n row={() => ({ className: OPTION_ROW, \"data-ckui\": \"poll-option\" })}\n >\n {(o, handle, i) => (\n <OptionRow\n handle={handle}\n index={i}\n label={o.label}\n image={o.image}\n onLabel={(label) => set(o.key, { label })}\n onImage={(image) => set(o.key, { image })}\n onRemove={options.length > 2 ? () => setOptions((os) => os.filter((x) => x.key !== o.key)) : undefined}\n />\n )}\n </SortableList>\n {options.length < p.maxOptions ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"justify-self-start\" onClick={() => setOptions((os) => [...os, draft()])}>\n {t(\"pollEditor.addOption\")}\n </Button>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t(\"pollEditor.maxOptions\", { max: p.maxOptions })}</p>\n )}\n </fieldset>\n )}\n <Button type=\"submit\" className=\"justify-self-start\" disabled={p.saving}>\n {p.saving ? t(\"pollEditor.creating\") : t(\"pollEditor.create\")}\n </Button>\n </form>\n );\n}\n\nfunction EditFields(p: {\n poll: Poll;\n saving: boolean;\n maxOptions: number;\n editor: ReturnType<typeof usePollEditor>;\n onError: (e: unknown) => void;\n onInvalid: (message: string) => void;\n onSaved: (poll: Poll) => void;\n}) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const { poll, editor } = p;\n const [question, setQuestion] = useState(poll.question);\n const [liveAt, setLiveAt] = useState(localInput(new Date(poll.live_at)));\n const [closesAt, setClosesAt] = useState(poll.closes_at ? localInput(new Date(poll.closes_at)) : \"\");\n const [active, setActive] = useState(poll.is_active);\n const [added, setAdded] = useState(\"\");\n const [removing, setRemoving] = useState<PollOption | null>(null);\n const options = [...poll.options].sort((a, b) => a.position - b.position);\n const patch: PollUpdate = {};\n if (question.trim() !== poll.question) patch.question = question.trim();\n if (toISO(liveAt) !== new Date(localInput(new Date(poll.live_at))).toISOString()) patch.live_at = toISO(liveAt);\n if (closesAt && toISO(closesAt) !== (poll.closes_at && new Date(localInput(new Date(poll.closes_at))).toISOString())) patch.closes_at = toISO(closesAt);\n else if (!closesAt && poll.closes_at) patch.closes_at = null;\n if (active !== poll.is_active) patch.is_active = active;\n const dirty = Object.keys(patch).length > 0;\n const act = (fn: () => Promise<unknown>) => void fn().catch(p.onError);\n return (\n <div className=\"grid gap-4\">\n <form\n className=\"grid gap-4\"\n onSubmit={(e) => {\n e.preventDefault();\n if (!question.trim()) return p.onInvalid(t(\"pollEditor.questionRequired\"));\n if (dirty) act(async () => p.onSaved(await editor.update(patch)));\n }}\n >\n <Field id={`${ids}-q`} label={t(\"pollEditor.question\")}>\n <Textarea id={`${ids}-q`} rows={2} value={question} onChange={(e) => setQuestion(e.target.value)} />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field id={`${ids}-live`} label={t(\"pollEditor.liveAt\")}>\n <Input id={`${ids}-live`} type=\"datetime-local\" value={liveAt} onChange={(e) => setLiveAt(e.target.value)} />\n </Field>\n <Field id={`${ids}-closes`} label={t(\"pollEditor.closesAt\")}>\n <Input id={`${ids}-closes`} type=\"datetime-local\" value={closesAt} min={liveAt} onChange={(e) => setClosesAt(e.target.value)} />\n </Field>\n </div>\n <div className=\"flex items-center gap-3\">\n <Switch id={`${ids}-active`} checked={active} onCheckedChange={setActive} />\n <Label htmlFor={`${ids}-active`}>{t(active ? \"pollEditor.active\" : \"pollEditor.inactive\")}</Label>\n </div>\n <Button type=\"submit\" className=\"justify-self-start\" disabled={!dirty || p.saving}>\n {p.saving ? t(\"common.saving\") : t(\"common.save\")}\n </Button>\n </form>\n <ImageField label={t(\"pollEditor.image\")} url={poll.image_url} disabled={p.saving} onPick={(f) => act(() => editor.setImage(f))} onRemove={() => act(() => editor.setImage(null))} />\n {poll.kind === \"free_text\" ? (\n <p className=\"text-muted-foreground\">\n {t(\"pollEditor.freeTextHelp\")} {m.plural(\"poll.answers\", poll.answer_count ?? 0)}\n </p>\n ) : (\n <fieldset className=\"grid gap-2\">\n <legend className=\"mb-1.5 text-sm font-medium\">{t(\"pollEditor.options\")}</legend>\n <SortableList\n items={options}\n id={(o) => o.id}\n name={(o) => o.label}\n disabled={p.saving}\n onMove={(from, to) => act(() => editor.moveOption(options[from]!.id, to))}\n label={t(\"pollEditor.options\")}\n className=\"gap-2\"\n row={() => ({ className: OPTION_ROW, \"data-ckui\": \"poll-option\" })}\n >\n {(o, handle, i) => (\n <OptionRow\n handle={handle}\n index={i}\n label={o.label}\n url={o.image_url}\n votes={o.vote_count}\n disabled={p.saving}\n onCommit={(label) => label.trim() && label.trim() !== o.label && act(() => editor.renameOption(o.id, label.trim()))}\n onImage={(f) => act(() => editor.setOptionImage(o.id, f))}\n onRemove={options.length > 2 ? () => setRemoving(o) : undefined}\n />\n )}\n </SortableList>\n {options.length < p.maxOptions ? (\n <form\n className=\"flex gap-2\"\n onSubmit={(e) => {\n e.preventDefault();\n if (!added.trim()) return;\n act(async () => {\n await editor.addOption(added.trim());\n setAdded(\"\");\n });\n }}\n >\n <Input value={added} placeholder={t(\"pollEditor.newOption\")} aria-label={t(\"pollEditor.newOption\")} onChange={(e) => setAdded(e.target.value)} />\n <Button type=\"submit\" variant=\"outline\" disabled={!added.trim() || p.saving}>\n {t(\"pollEditor.addOption\")}\n </Button>\n </form>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t(\"pollEditor.maxOptions\", { max: p.maxOptions })}</p>\n )}\n </fieldset>\n )}\n <ConfirmDialog\n open={!!removing}\n onOpenChange={(o) => !o && setRemoving(null)}\n title={t(\"pollEditor.removeOptionTitle\")}\n description={t(\"pollEditor.removeOptionBody\")}\n confirm={t(\"pollEditor.removeOption\")}\n onConfirm={() => removing && act(() => editor.removeOption(removing.id))}\n />\n </div>\n );\n}\n\nconst OPTION_ROW = \"flex flex-wrap items-center gap-2 rounded-lg border border-border p-2\";\n\n/** One option's content: its drag handle, label, image, votes and remove. */\nfunction OptionRow(p: {\n handle: ReactNode;\n index: number;\n label: string;\n /** A new poll's staged image. */\n image?: File | null;\n /** An existing option's image. */\n url?: string;\n votes?: number;\n disabled?: boolean;\n /** A new poll's label as typed. */\n onLabel?: (label: string) => void;\n /** An existing option's label, on blur or Enter. */\n onCommit?: (label: string) => void;\n onImage: (file: File | null) => void;\n onRemove?: () => void;\n}) {\n const m = useMessages();\n const { t } = m;\n const [label, setLabel] = useState(p.label);\n useEffect(() => setLabel(p.label), [p.label]);\n const staged = useObjectURL(p.image ?? null);\n const name = t(\"pollEditor.option\", { index: p.index + 1 });\n return (\n <>\n {p.handle}\n <Input\n className=\"min-w-40 flex-1\"\n value={label}\n aria-label={name}\n placeholder={name}\n disabled={p.disabled && !p.onLabel}\n onChange={(e) => {\n setLabel(e.target.value);\n p.onLabel?.(e.target.value);\n }}\n onBlur={() => p.onCommit?.(label)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && p.onCommit) {\n e.preventDefault();\n p.onCommit(label);\n }\n }}\n />\n <ImageField compact label={t(\"pollEditor.optionImage\", { index: p.index + 1 })} url={p.url ?? staged} disabled={p.disabled} onPick={p.onImage} onRemove={() => p.onImage(null)} />\n {p.votes !== undefined && <span className=\"text-xs tabular-nums text-muted-foreground\">{m.plural(\"poll.votes\", p.votes)}</span>}\n {p.onRemove && (\n <Button type=\"button\" variant=\"ghost\" size=\"icon-sm\" aria-label={`${t(\"pollEditor.removeOption\")}: ${name}`} disabled={p.disabled} onClick={p.onRemove}>\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} />\n </Button>\n )}\n </>\n );\n}\n\nfunction ImageField(p: { label: string; url?: string | null; compact?: boolean; disabled?: boolean; onPick: (file: File) => void; onRemove: () => void }) {\n const { t } = useMessages();\n const input = useRef<HTMLInputElement>(null);\n const pick = (\n <input\n ref={input}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={p.label}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) p.onPick(f);\n }}\n />\n );\n if (p.compact) {\n return (\n <div className=\"flex items-center gap-1\" data-ckui=\"poll-image\">\n {pick}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className=\"overflow-hidden p-0\"\n aria-label={`${p.url ? t(\"pollEditor.replaceImage\") : t(\"pollEditor.uploadImage\")}: ${p.label}`}\n disabled={p.disabled}\n onClick={() => input.current?.click()}\n >\n {p.url ? <img src={p.url} alt=\"\" className=\"size-full object-cover\" /> : <HugeiconsIcon icon={ImageAdd01Icon} strokeWidth={2} />}\n </Button>\n {p.url && (\n <Button type=\"button\" variant=\"ghost\" size=\"xs\" disabled={p.disabled} onClick={p.onRemove}>\n {t(\"pollEditor.removeImage\")}\n </Button>\n )}\n </div>\n );\n }\n return (\n <div className=\"grid gap-1.5\" data-ckui=\"poll-image\">\n <span className=\"text-sm font-medium\">{p.label}</span>\n {pick}\n {p.url && <img src={p.url} alt=\"\" className=\"aspect-video w-full max-w-sm rounded-lg border border-border object-cover\" />}\n <div className=\"flex gap-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={p.disabled} onClick={() => input.current?.click()}>\n <HugeiconsIcon icon={ImageAdd01Icon} strokeWidth={2} data-icon=\"inline-start\" />\n {p.url ? t(\"pollEditor.replaceImage\") : t(\"pollEditor.uploadImage\")}\n </Button>\n {p.url && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" disabled={p.disabled} onClick={p.onRemove}>\n {t(\"pollEditor.removeImage\")}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\nfunction Field({ id, label, children }: { id: string; label: string; children: ReactNode }) {\n return (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={id}>{label}</Label>\n {children}\n </div>\n );\n}\n\n/** An object URL for a staged file, revoked when it changes. */\nfunction useObjectURL(file: File | null): string | undefined {\n const [url, setUrl] = useState<string>();\n useEffect(() => {\n if (!file) return setUrl(undefined);\n const u = URL.createObjectURL(file);\n setUrl(u);\n return () => URL.revokeObjectURL(u);\n }, [file]);\n return url;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RichTextEditor, richTextEditor, type RichTextEditorProps } from './rich-text-editor.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { PostEditorProps } from '../components/admin/post-manager.js';
|
|
3
|
+
export type RichTextEditorProps = PostEditorProps & {
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* A rich-text field over Tiptap: headings, emphasis, lists, quotes, links
|
|
8
|
+
* and images (picked, pasted or dropped, through uploadImage). Its value is
|
|
9
|
+
* HTML.
|
|
10
|
+
*/
|
|
11
|
+
export declare function RichTextEditor({ value, onChange, uploadImage, disabled, placeholder, className, ...aria }: RichTextEditorProps): import('react').JSX.Element;
|
|
12
|
+
/** A render prop for PostManager: `renderEditor={richTextEditor}`. */
|
|
13
|
+
export declare function richTextEditor(props: PostEditorProps): ReactNode;
|