@openrails/contentkit-ui 0.86.0 → 0.87.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 +39 -14
- package/dist/client/beacons.js +5 -1
- package/dist/client/beacons.js.map +1 -1
- package/dist/client/content/types.d.ts +4 -2
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/contents.d.ts +8 -6
- package/dist/client/contents.js +10 -7
- package/dist/client/contents.js.map +1 -1
- package/dist/client/discovery.js +3 -0
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/errors.d.ts +21 -3
- package/dist/client/errors.js +44 -14
- package/dist/client/errors.js.map +1 -1
- package/dist/client/gallery.d.ts +3 -5
- package/dist/client/gallery.js +16 -20
- package/dist/client/gallery.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +8 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +16 -3
- package/dist/client/generated/routes.js +28 -3
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +43 -14
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/http.js +27 -27
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/media/client.d.ts +10 -4
- package/dist/client/media/client.js +33 -7
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/rules.d.ts +1 -1
- package/dist/client/media/rules.js +6 -6
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/media/transport.d.ts +1 -1
- package/dist/client/media/transport.js +16 -16
- package/dist/client/media/transport.js.map +1 -1
- package/dist/client/media/windows.js +1 -1
- package/dist/client/media/windows.js.map +1 -1
- package/dist/client/public.d.ts +2 -2
- package/dist/client/public.js +2 -2
- package/dist/client/public.js.map +1 -1
- package/dist/client/versions.d.ts +6 -4
- package/dist/client/versions.js +24 -26
- package/dist/client/versions.js.map +1 -1
- package/dist/client.js +25 -25
- package/dist/components/admin/content-manager.js +233 -232
- package/dist/components/admin/content-manager.js.map +1 -1
- package/dist/components/content/card.d.ts +5 -3
- package/dist/components/content/card.js +131 -109
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/download-button.d.ts +26 -0
- package/dist/components/content/download-button.js +51 -0
- package/dist/components/content/download-button.js.map +1 -0
- package/dist/components/content/editors.d.ts +8 -0
- package/dist/components/content/editors.js +299 -229
- package/dist/components/content/editors.js.map +1 -1
- package/dist/components/content/grid.d.ts +8 -1
- package/dist/components/content/grid.js +125 -76
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/index.d.ts +3 -2
- package/dist/components/content/listing.d.ts +3 -1
- package/dist/components/content/listing.js.map +1 -1
- package/dist/components/content/paywall.js +61 -61
- package/dist/components/content/paywall.js.map +1 -1
- package/dist/components/media-gallery.d.ts +1 -4
- package/dist/components/media-gallery.js +19 -34
- package/dist/components/media-gallery.js.map +1 -1
- package/dist/components/social/comments.js +1 -1
- package/dist/components/social/comments.js.map +1 -1
- package/dist/components/social/engagement.d.ts +2 -1
- package/dist/components/social/engagement.js +5 -3
- package/dist/components/social/engagement.js.map +1 -1
- package/dist/i18n/messages.js +49 -46
- package/dist/i18n/messages.js.map +1 -1
- package/dist/index.js +12 -11
- package/dist/locales/de.js +15 -3
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.js +3 -0
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +3 -0
- package/dist/locales/discovery/en.js +3 -0
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +3 -0
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +3 -0
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +3 -0
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +3 -0
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +17 -2
- package/dist/locales/en.js +15 -3
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +15 -3
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +15 -3
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +15 -3
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +15 -3
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/comments.d.ts +1 -1
- package/dist/react/comments.js +3 -2
- package/dist/react/comments.js.map +1 -1
- package/dist/react/contents.d.ts +13 -5
- package/dist/react/contents.js +163 -149
- package/dist/react/contents.js.map +1 -1
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/detail.d.ts +7 -1
- package/dist/react/detail.js +9 -6
- package/dist/react/detail.js.map +1 -1
- package/dist/react/discovery.d.ts +11 -0
- package/dist/react/discovery.js +49 -30
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/engagement.d.ts +6 -2
- package/dist/react/engagement.js +2 -6
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/index.d.ts +3 -3
- package/dist/react/public.js +17 -16
- package/dist/react/public.js.map +1 -1
- package/dist/react/versions.d.ts +25 -5
- package/dist/react/versions.js +58 -37
- package/dist/react/versions.js.map +1 -1
- package/dist/react.js +9 -9
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-manager.js","names":[],"sources":["../../../src/components/admin/content-manager.tsx"],"sourcesContent":["import { 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 { imageRefs } from \"../../client/contents.js\";\nimport type { Content } 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 { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContent } from \"../../react/contents.js\";\nimport { keyOf, useContentScope, usePaged } from \"../../react/use-resource.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button, buttonVariants } 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 { Textarea } from \"#ckui/ui/textarea\";\nimport { localInput } from \"../social/bans.js\";\nimport { contentError, ErrorLine, formatDate, RelativeTime, useAction } from \"../social/parts.js\";\nimport { ContentSections } from \"../content/editors.js\";\nimport type { Labels } from \"../groups/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Pager, Rows, pageOf, useConfirm, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\n\n/** What ContentManager hands its body editor. */\nexport interface BodyEditorProps {\n /** The body as HTML, its images at URLs that show them now. */\n value: string;\n onChange: (html: string) => void;\n /** Uploads an image to the item and resolves with a URL that shows it; absent until the item exists. */\n uploadImage?: (file: File) => Promise<string>;\n disabled?: boolean;\n placeholder?: string;\n \"aria-label\"?: string;\n}\n\nexport interface ContentManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The content kind managed. */\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /**\n * The body editor: `richTextEditor` from `@openrails/contentkit-ui/editor`,\n * or the host's own. Without it, the body is a plain HTML field.\n */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n /** Display names of kinds, traits, tiers, attributes and values by identifier. */\n labels?: Labels;\n pageSize?: number;\n}\n\n/** Whether the caller manages content: root:content:write holders, creators and owner-group editors. */\nexport function useContentManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"contents\"> {\n return useStaffAccess([\"contents\"] as const, o);\n}\n\nexport type ContentStatus = \"draft\" | \"scheduled\" | \"published\" | \"held\" | \"rejected\";\n\n/** An item's status as the manager names it. */\nexport function contentStatus(c: Content): ContentStatus {\n if (c.moderation === \"rejected\") return \"rejected\";\n if (c.moderation === \"held\") return \"held\";\n if (c.state === \"draft\") return \"draft\";\n if (!c.live) return \"scheduled\";\n return \"published\";\n}\n\nconst choices = (list?: readonly Choice[]) => (list ?? []).map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst badge: Record<ContentStatus, \"default\" | \"secondary\" | \"outline\" | \"destructive\"> = { published: \"default\", scheduled: \"secondary\", draft: \"outline\", held: \"secondary\", rejected: \"destructive\" };\nconst states = [\"draft\", \"scheduled\", \"published\"] as const;\n\n/**\n * Content items of one kind for their creators and staff: every one the\n * caller manages, by state and text, deleted ones on their own to restore;\n * create and edit title, slug, description, schedule, cover and body with\n * inline images per language, publish or keep a draft, and delete; then its\n * listing and attributes, access rule and price, versions (order, languages,\n * traits, schedule, access override, price, segments, media) and groups. The\n * list is at basePath, a new item at basePath/new and each item at\n * basePath/{id}, filters in the query.\n */\nexport function ContentManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: ContentManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"content-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Contents {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<ContentManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Contents({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useContentManagerAccess({ client: p.client });\n if (access.loading) return <Rows />;\n if (!access.allowed.contents) return <NoAccess>{t(\"contentManager.noAccess\")}</NoAccess>;\n if (!loc.view) return <List {...p} loc={loc} />;\n return (\n <>\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(\"\")}>\n {t(\"contentManager.back\")}\n </Button>\n <ContentEditor\n key={loc.view}\n {...p}\n id={loc.view === \"new\" ? null : loc.view}\n onCreated={(c) => loc.go(c.id, undefined, { replace: true })}\n onDeleted={() => loc.go(\"\", undefined, { replace: true })}\n />\n </>\n );\n}\n\nfunction List({ loc, client, kind, languages: offered, contentHref, pageSize = 20, onError }: Inner & { loc: AdminLocation }) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const language = choices(offered)[0]?.value;\n const query = loc.query.get(\"q\") ?? \"\";\n const status = states.find((s) => s === loc.query.get(\"status\")) ?? \"\";\n const deleted = loc.query.get(\"deleted\") === \"1\";\n const page = pageOf(loc.query);\n const filters = { q: query, status, deleted: deleted ? \"1\" : undefined };\n const filter = (next: Partial<typeof filters>) => loc.go(\"\", { ...filters, ...next });\n const [text, setText] = useState(query);\n useEffect(() => setText(query), [query]);\n const { store, scope } = useContentScope(client);\n const state = deleted ? \"deleted\" : status || undefined;\n const list = usePaged(store, keyOf(\"contents-page\", scope, kind, query, state, pageSize), { type: \"contents\", scope: deleted ? \"deleted\" : undefined }, page, pageSize, (q, signal) =>\n client.contents.manage({ ...q, kind, q: query || undefined, state, language }, signal),\n );\n const del = useAction(\"content.save\", onError);\n const { confirm, dialog } = useConfirm();\n const remove = async (c: Content) => {\n if (await confirm({ title: t(\"contentManager.deleteTitle\", { title: c.title }), description: t(\"contentManager.deleteBody\"), confirm: t(\"contentManager.delete\") })) {\n await del.run(() => client.contents.remove(c.id));\n }\n };\n return (\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"contentManager.title\")}</h2>\n <Button size=\"sm\" onClick={() => loc.go(\"new\")}>\n {t(\"contentManager.new\")}\n </Button>\n </div>\n <div className=\"flex gap-2\" role=\"group\" aria-label={t(\"contentManager.title\")}>\n <Button size=\"sm\" variant={deleted ? \"outline\" : \"default\"} aria-pressed={!deleted} onClick={() => loc.go(\"\", { q: query })}>\n {t(\"contentManager.items\")}\n </Button>\n <Button size=\"sm\" variant={deleted ? \"default\" : \"outline\"} aria-pressed={deleted} onClick={() => loc.go(\"\", { q: query, deleted: \"1\" })}>\n {t(\"contentManager.deleted\")}\n </Button>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-[minmax(0,1fr)_auto]\">\n <form\n className=\"flex gap-2\"\n role=\"search\"\n onSubmit={(e) => {\n e.preventDefault();\n filter({ q: text.trim() });\n }}\n >\n <Label htmlFor={`${ids}-q`} className=\"sr-only\">\n {t(\"contentManager.search\")}\n </Label>\n <Input id={`${ids}-q`} className=\"h-8\" type=\"search\" value={text} placeholder={t(\"contentManager.search\")} onChange={(e) => setText(e.target.value)} />\n <Button type=\"submit\" size=\"sm\" variant=\"outline\">\n {t(\"moderation.filter\")}\n </Button>\n </form>\n {!deleted && (\n <Select value={status} onValueChange={(v) => filter({ status: String(v ?? \"\") as typeof status })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"contentManager.status\")}>\n <SelectValue>{(v: string) => t(v ? `contentManager.${v as (typeof states)[number]}` : \"contentManager.allStatuses\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">{t(\"contentManager.allStatuses\")}</SelectItem>\n {states.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`contentManager.${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <ErrorLine error={del.error} />\n {list.loading && !list.items.length ? (\n <Rows />\n ) : list.error && !list.items.length ? (\n <LoadFailed error={list.error} message={t(\"contentManager.loadFailed\")} retry={list.reload} />\n ) : !list.items.length ? (\n <p className=\"text-muted-foreground\">{t(deleted ? \"contentManager.noDeleted\" : \"contentManager.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={list.loading}>\n {list.items.map((c) => {\n const s = contentStatus(c);\n const href = s === \"published\" ? contentHref?.(c) : undefined;\n return (\n <li key={c.id} className=\"flex flex-wrap items-start gap-3 p-3\" data-ckui=\"content-row\" data-status={deleted ? \"deleted\" : s}>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n {deleted ? (\n <span className=\"font-medium break-words\">{c.title}</span>\n ) : (\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(c.id)}>\n {c.title}\n </button>\n )}\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {!deleted && <Badge variant={badge[s]}>{t(`contentManager.${s}`)}</Badge>}\n <span>{c.language}</span>\n <RelativeTime at={deleted && c.deleted_at ? c.deleted_at : c.updated_at} />\n {s === \"scheduled\" && c.publish_at && <span>{t(\"pollManager.goesLive\", { date: formatDate(m, c.publish_at) })}</span>}\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {deleted ? (\n <Button size=\"sm\" variant=\"outline\" disabled={del.pending} onClick={() => void del.run(() => client.contents.restore(c.id))}>\n {t(\"moderation.restore\")}\n </Button>\n ) : (\n <>\n {href && (\n <a className={buttonVariants({ size: \"sm\", variant: \"ghost\" })} href={href}>\n {t(\"contentManager.view\")}\n </a>\n )}\n <Button size=\"sm\" variant=\"outline\" onClick={() => loc.go(c.id)}>\n {t(\"comments.edit\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" disabled={del.pending} onClick={() => void remove(c)}>\n {t(\"comments.delete\")}\n </Button>\n </>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n <Pager page={page} hasNext={list.hasNext} disabled={list.loading} onPage={(n) => loc.go(\"\", { ...filters, page: n > 1 ? n : undefined })} />\n {dialog}\n </div>\n );\n}\n\ninterface Form {\n title: string;\n slug: string;\n description: string;\n publishAt: string;\n body: string;\n}\n\nconst empty: Form = { title: \"\", slug: \"\", description: \"\", publishAt: \"\", body: \"\" };\n\nexport interface ContentEditorProps {\n /** The item edited; null creates one of kind. */\n id: string | null;\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /** The body editor; without it, a plain HTML field. */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n labels?: Labels;\n /** A new item's owner group: a channel its editors write for. */\n ownerGroup?: string;\n /** After create: show the new item (the editor goes on editing it). */\n onCreated?: (content: Content) => void;\n onDeleted?: (content: Content) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n}\n\n/**\n * One content item for its creators and staff: title, slug, description,\n * schedule, cover and body per language, publish, unpublish and delete;\n * then listing and attributes, access rule and price, versions and groups.\n */\nexport function ContentEditor({ id, client: own, kind, languages: offered, renderEditor, contentHref, labels, ownerGroup, onCreated, onDeleted, onError }: ContentEditorProps) {\n const client = useContentKitClient(own);\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const languages = choices(offered);\n const [language, setLanguage] = useState(languages[0]?.value ?? \"en\");\n const ed = useContent(id, kind, language, { client });\n const report = useErrorReporter(onError);\n // The body is kept as stored (image references); the editor shows it with URLs.\n const [form, setForm] = useState<Form | null>(id ? null : empty);\n const [error, setError] = useState<ContentKitError | string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const { confirm, dialog } = useConfirm();\n const item = ed.content;\n useEffect(() => {\n if (!item) return;\n const stored = item.text?.[language];\n setForm((f) => f ?? { title: stored?.title ?? \"\", slug: stored?.slug ?? \"\", description: stored?.description ?? \"\", body: stored?.body ?? \"\",\n publishAt: item.publish_at && !item.live ? localInput(new Date(item.publish_at)) : \"\" });\n }, [item, language]);\n const { imageSrc } = ed;\n const shown = useMemo(\n () => (form?.body ?? \"\").replace(imageRefs(), (ref, name: string) => {\n const src = imageSrc(name);\n return src && src !== name ? src.replaceAll(\"&\", \"&\") : ref;\n }),\n [form?.body, imageSrc],\n );\n if (id && !form) {\n if (ed.error) return <LoadFailed error={ed.error} message={t(\"contentManager.loadFailed\")} retry={ed.reload} />;\n return (\n <div className=\"grid gap-3\" aria-busy>\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-48 w-full\" />\n </div>\n );\n }\n const f = form!;\n const set = (patch: Partial<Form>) => setForm((cur) => ({ ...cur!, ...patch }));\n const fail = (e: unknown) => {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"content.save\");\n };\n const text = () => ({ title: f.title.trim(), slug: f.slug.trim() || undefined, description: f.description.trim(), body: f.body });\n const save = async () => {\n setError(null);\n setNotice(null);\n if (!f.title.trim()) return setError(t(\"contentManager.titleRequired\"));\n try {\n if (!item) {\n const created = await ed.create({ kind, text: { [language]: text() }, owner_group_id: ownerGroup });\n onCreated?.(created);\n return;\n }\n const saved = await ed.putText(text());\n setNotice(t(\"pollEditor.saved\"));\n setForm((cur) => ({ ...cur!, slug: saved.text?.[language]?.slug ?? cur!.slug }));\n } catch (e) {\n fail(e);\n }\n };\n const publish = async (live: boolean) => {\n setError(null);\n setNotice(null);\n try {\n if (live) await ed.publish(f.publishAt ? new Date(f.publishAt).toISOString() : undefined);\n else await ed.unpublish();\n setNotice(t(live ? \"contentManager.publishedNotice\" : \"contentManager.unpublishedNotice\"));\n } catch (e) {\n fail(e);\n }\n };\n const remove = async () => {\n if (!item || !(await confirm({ title: t(\"contentManager.deleteTitle\", { title: item.title }), description: t(\"contentManager.deleteBody\"), confirm: t(\"contentManager.delete\") }))) return;\n try {\n await ed.remove();\n onDeleted?.(item);\n } catch (e) {\n fail(e);\n }\n };\n const s = item ? contentStatus(item) : \"draft\";\n const href = item && s === \"published\" ? contentHref?.(item) : undefined;\n const editor: BodyEditorProps = {\n value: shown,\n onChange: (html) => set({ body: ed.storedBody(html) }),\n uploadImage: item ? (file) => ed.uploadImage(file) : undefined,\n disabled: ed.saving,\n placeholder: t(\"contentManager.bodyPlaceholder\"),\n \"aria-label\": t(\"contentManager.body\"),\n };\n return (\n <>\n <form\n className=\"grid gap-4\"\n data-ckui=\"content-form\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold\">{t(item ? \"contentManager.editTitle\" : \"contentManager.newTitle\")}</h2>\n {item && <Badge variant={badge[s]}>{t(`contentManager.${s}`)}</Badge>}\n {href && (\n <a className=\"text-xs text-muted-foreground hover:underline\" href={href}>\n {t(\"contentManager.view\")}\n </a>\n )}\n </div>\n {item?.moderation && (\n <Alert variant={item.moderation === \"rejected\" ? \"destructive\" : \"default\"}>\n <AlertDescription>\n {item.moderation_reason\n ? t(`contentManager.${item.moderation === \"rejected\" ? \"rejectedBecause\" : \"heldBecause\"}`, { reason: item.moderation_reason })\n : t(`contentManager.${item.moderation === \"rejected\" ? \"rejectedNote\" : \"heldNote\"}`)}\n </AlertDescription>\n </Alert>\n )}\n {languages.length > 1 && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-lang`}>{t(\"contentManager.language\")}</Label>\n <Select\n value={language}\n onValueChange={(v) => {\n setLanguage(String(v ?? \"\"));\n setForm(item ? null : empty);\n }}\n >\n <SelectTrigger id={`${ids}-lang`} className=\"w-full\">\n <SelectValue>{(v: string) => languages.find((l) => l.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l.value} value={l.value}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-title`}>{t(\"contentManager.itemTitle\")}</Label>\n <Input id={`${ids}-title`} value={f.title} required maxLength={300} onChange={(e) => set({ title: e.target.value })} />\n </div>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-slug`}>{t(\"contentManager.slug\")}</Label>\n <Input id={`${ids}-slug`} value={f.slug} placeholder={t(\"contentManager.slugAuto\")} onChange={(e) => set({ slug: e.target.value })} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-publish`}>{t(\"contentManager.publishAt\")}</Label>\n <Input id={`${ids}-publish`} type=\"datetime-local\" value={f.publishAt} onChange={(e) => set({ publishAt: e.target.value })} />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-description`}>{t(\"contentManager.description\")}</Label>\n <Textarea id={`${ids}-description`} rows={2} value={f.description} onChange={(e) => set({ description: e.target.value })} />\n </div>\n <Cover item={item} src={ed.coverSrc} saving={ed.saving} onFile={(file) => void ed.setCover(file).catch(fail)} />\n <div className=\"grid gap-1.5\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.body\")}</span>\n {renderEditor ? renderEditor(editor) : <Textarea aria-label={editor[\"aria-label\"]} rows={12} value={f.body} placeholder={editor.placeholder} onChange={(e) => set({ body: e.target.value })} />}\n {!item && <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>}\n </div>\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 <div className=\"flex flex-wrap gap-2 border-t border-border pt-4\">\n <Button type=\"submit\" disabled={ed.saving}>\n {ed.saving ? t(\"common.saving\") : t(item ? \"common.save\" : \"contentManager.createDraft\")}\n </Button>\n {item && !item.live && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(true)}>\n {t(\"contentManager.publish\")}\n </Button>\n )}\n {item?.state === \"published\" && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(false)}>\n {t(\"contentManager.unpublish\")}\n </Button>\n )}\n {item && (\n <Button type=\"button\" variant=\"destructive\" className=\"ms-auto\" disabled={ed.saving} onClick={() => void remove()}>\n {t(\"contentManager.delete\")}\n </Button>\n )}\n </div>\n {dialog}\n </form>\n {item && <ContentSections content={item} client={client} onError={onError} labels={labels} />}\n </>\n );\n}\n\nfunction Cover({ item, src, saving, onFile }: { item: Content | null; src?: string; saving: boolean; onFile: (file: File | null) => void }) {\n const { t } = useMessages();\n const file = useRef<HTMLInputElement>(null);\n return (\n <div className=\"grid gap-1.5\" data-ckui=\"content-cover\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.cover\")}</span>\n {item ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {src ? <img src={src} alt=\"\" className=\"h-20 w-36 rounded-md border border-border object-cover\" /> : <span className=\"text-muted-foreground\">{t(\"contentManager.noCover\")}</span>}\n <Button type=\"button\" size=\"sm\" variant=\"outline\" disabled={saving} onClick={() => file.current?.click()}>\n {t(src ? \"pollEditor.replaceImage\" : \"contentManager.addCover\")}\n </Button>\n {src && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={saving} onClick={() => onFile(null)}>\n {t(\"pollEditor.removeImage\")}\n </Button>\n )}\n <input\n ref={file}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={t(\"contentManager.cover\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) onFile(f);\n }}\n />\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"content-manager.js","names":[],"sources":["../../../src/components/admin/content-manager.tsx"],"sourcesContent":["import { 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 { imageRefs } from \"../../client/contents.js\";\nimport type { Content } 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 { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"../../react/context.js\";\nimport { useContent } from \"../../react/contents.js\";\nimport { useKinds } from \"../../react/groups.js\";\nimport { keyOf, useContentScope, usePaged } from \"../../react/use-resource.js\";\nimport { Alert, AlertDescription } from \"#ckui/ui/alert\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { Button, buttonVariants } 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 { Textarea } from \"#ckui/ui/textarea\";\nimport { localInput } from \"../social/bans.js\";\nimport { contentError, ErrorLine, formatDate, RelativeTime, useAction } from \"../social/parts.js\";\nimport { ContentSections } from \"../content/editors.js\";\nimport type { Labels } from \"../groups/parts.js\";\nimport { AdminRoot, LoadFailed, NoAccess, Pager, Rows, pageOf, useConfirm, type AdminFlowProps, type AdminRouteProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\n\n/** What ContentManager hands its body editor. */\nexport interface BodyEditorProps {\n /** The body as HTML, its images at URLs that show them now. */\n value: string;\n onChange: (html: string) => void;\n /** Uploads an image to the item and resolves with a URL that shows it; absent until the item exists. */\n uploadImage?: (file: File) => Promise<string>;\n disabled?: boolean;\n placeholder?: string;\n \"aria-label\"?: string;\n}\n\nexport interface ContentManagerProps extends AdminFlowProps, AdminRouteProps {\n /** The content kind managed. */\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /**\n * The body editor: `richTextEditor` from `@openrails/contentkit-ui/editor`,\n * or the host's own. Without it, the body is a plain HTML field.\n */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n /** Display names of kinds, traits, tiers, attributes and values by identifier. */\n labels?: Labels;\n pageSize?: number;\n}\n\n/** Whether the caller manages content: root:content:write holders, creators and owner-group editors. */\nexport function useContentManagerAccess(o: FlowClientOptions = {}): StaffAccess<\"contents\"> {\n return useStaffAccess([\"contents\"] as const, o);\n}\n\nexport type ContentStatus = \"draft\" | \"scheduled\" | \"published\" | \"held\" | \"rejected\";\n\n/** An item's status as the manager names it. */\nexport function contentStatus(c: Content): ContentStatus {\n if (c.moderation === \"rejected\") return \"rejected\";\n if (c.moderation === \"held\") return \"held\";\n if (c.state === \"draft\") return \"draft\";\n if (!c.live) return \"scheduled\";\n return \"published\";\n}\n\nconst choices = (list?: readonly Choice[]) => (list ?? []).map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst badge: Record<ContentStatus, \"default\" | \"secondary\" | \"outline\" | \"destructive\"> = { published: \"default\", scheduled: \"secondary\", draft: \"outline\", held: \"secondary\", rejected: \"destructive\" };\nconst states = [\"draft\", \"scheduled\", \"published\"] as const;\n\n/**\n * Content items of one kind for their creators and staff: every one the\n * caller manages, by state and text, deleted ones on their own to restore;\n * create and edit title, slug, description, schedule, cover and body with\n * inline images per language, publish or keep a draft, and delete; then its\n * listing and attributes, access rule and price, versions (order, languages,\n * traits, schedule, access override, price, segments, media) and groups. The\n * list is at basePath, a new item at basePath/new and each item at\n * basePath/{id}, filters in the query.\n */\nexport function ContentManager({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: ContentManagerProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"content-manager\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Contents {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<ContentManagerProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Contents({ basePath, navigation, ...p }: Inner) {\n const { t } = useMessages();\n const loc = useAdminLocation(basePath, navigation);\n const access = useContentManagerAccess({ client: p.client });\n if (access.loading) return <Rows />;\n if (!access.allowed.contents) return <NoAccess>{t(\"contentManager.noAccess\")}</NoAccess>;\n if (!loc.view) return <List {...p} loc={loc} />;\n return (\n <>\n <Button variant=\"ghost\" size=\"sm\" className=\"justify-self-start\" onClick={() => loc.go(\"\")}>\n {t(\"contentManager.back\")}\n </Button>\n <ContentEditor\n key={loc.view}\n {...p}\n id={loc.view === \"new\" ? null : loc.view}\n onCreated={(c) => loc.go(c.id, undefined, { replace: true })}\n onDeleted={() => loc.go(\"\", undefined, { replace: true })}\n />\n </>\n );\n}\n\nfunction List({ loc, client, kind, languages: offered, contentHref, pageSize = 20, onError }: Inner & { loc: AdminLocation }) {\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const language = choices(offered)[0]?.value;\n const query = loc.query.get(\"q\") ?? \"\";\n const status = states.find((s) => s === loc.query.get(\"status\")) ?? \"\";\n const deleted = loc.query.get(\"deleted\") === \"1\";\n const page = pageOf(loc.query);\n const filters = { q: query, status, deleted: deleted ? \"1\" : undefined };\n const filter = (next: Partial<typeof filters>) => loc.go(\"\", { ...filters, ...next });\n const [text, setText] = useState(query);\n useEffect(() => setText(query), [query]);\n const { store, scope } = useContentScope(client);\n const state = deleted ? \"deleted\" : status || undefined;\n const list = usePaged(store, keyOf(\"contents-page\", scope, kind, query, state, pageSize), { type: \"contents\", scope: deleted ? \"deleted\" : undefined }, page, pageSize, (q, signal) =>\n client.contents.manage({ ...q, kind, q: query || undefined, state, language }, signal),\n );\n const del = useAction(\"content.save\", onError);\n const { confirm, dialog } = useConfirm();\n const remove = async (c: Content) => {\n if (await confirm({ title: t(\"contentManager.deleteTitle\", { title: c.title }), description: t(\"contentManager.deleteBody\"), confirm: t(\"contentManager.delete\") })) {\n await del.run(() => client.contents.remove(c.id));\n }\n };\n return (\n <div className=\"grid gap-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"contentManager.title\")}</h2>\n <Button size=\"sm\" onClick={() => loc.go(\"new\")}>\n {t(\"contentManager.new\")}\n </Button>\n </div>\n <div className=\"flex gap-2\" role=\"group\" aria-label={t(\"contentManager.title\")}>\n <Button size=\"sm\" variant={deleted ? \"outline\" : \"default\"} aria-pressed={!deleted} onClick={() => loc.go(\"\", { q: query })}>\n {t(\"contentManager.items\")}\n </Button>\n <Button size=\"sm\" variant={deleted ? \"default\" : \"outline\"} aria-pressed={deleted} onClick={() => loc.go(\"\", { q: query, deleted: \"1\" })}>\n {t(\"contentManager.deleted\")}\n </Button>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-[minmax(0,1fr)_auto]\">\n <form\n className=\"flex gap-2\"\n role=\"search\"\n onSubmit={(e) => {\n e.preventDefault();\n filter({ q: text.trim() });\n }}\n >\n <Label htmlFor={`${ids}-q`} className=\"sr-only\">\n {t(\"contentManager.search\")}\n </Label>\n <Input id={`${ids}-q`} className=\"h-8\" type=\"search\" value={text} placeholder={t(\"contentManager.search\")} onChange={(e) => setText(e.target.value)} />\n <Button type=\"submit\" size=\"sm\" variant=\"outline\">\n {t(\"moderation.filter\")}\n </Button>\n </form>\n {!deleted && (\n <Select value={status} onValueChange={(v) => filter({ status: String(v ?? \"\") as typeof status })}>\n <SelectTrigger size=\"sm\" aria-label={t(\"contentManager.status\")}>\n <SelectValue>{(v: string) => t(v ? `contentManager.${v as (typeof states)[number]}` : \"contentManager.allStatuses\")}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"\">{t(\"contentManager.allStatuses\")}</SelectItem>\n {states.map((s) => (\n <SelectItem key={s} value={s}>\n {t(`contentManager.${s}`)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </div>\n <ErrorLine error={del.error} />\n {list.loading && !list.items.length ? (\n <Rows />\n ) : list.error && !list.items.length ? (\n <LoadFailed error={list.error} message={t(\"contentManager.loadFailed\")} retry={list.reload} />\n ) : !list.items.length ? (\n <p className=\"text-muted-foreground\">{t(deleted ? \"contentManager.noDeleted\" : \"contentManager.empty\")}</p>\n ) : (\n <ul className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={list.loading}>\n {list.items.map((c) => {\n const s = contentStatus(c);\n const href = s === \"published\" ? contentHref?.(c) : undefined;\n return (\n <li key={c.id} className=\"flex flex-wrap items-start gap-3 p-3\" data-ckui=\"content-row\" data-status={deleted ? \"deleted\" : s}>\n <div className=\"grid min-w-0 flex-1 gap-1\">\n {deleted ? (\n <span className=\"font-medium break-words\">{c.title}</span>\n ) : (\n <button type=\"button\" className=\"text-left font-medium break-words hover:underline\" onClick={() => loc.go(c.id)}>\n {c.title}\n </button>\n )}\n <div className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground\">\n {!deleted && <Badge variant={badge[s]}>{t(`contentManager.${s}`)}</Badge>}\n <span>{c.language}</span>\n <RelativeTime at={deleted && c.deleted_at ? c.deleted_at : c.updated_at} />\n {s === \"scheduled\" && c.publish_at && <span>{t(\"pollManager.goesLive\", { date: formatDate(m, c.publish_at) })}</span>}\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {deleted ? (\n <Button size=\"sm\" variant=\"outline\" disabled={del.pending} onClick={() => void del.run(() => client.contents.restore(c.id))}>\n {t(\"moderation.restore\")}\n </Button>\n ) : (\n <>\n {href && (\n <a className={buttonVariants({ size: \"sm\", variant: \"ghost\" })} href={href}>\n {t(\"contentManager.view\")}\n </a>\n )}\n <Button size=\"sm\" variant=\"outline\" onClick={() => loc.go(c.id)}>\n {t(\"comments.edit\")}\n </Button>\n <Button size=\"sm\" variant=\"destructive\" disabled={del.pending} onClick={() => void remove(c)}>\n {t(\"comments.delete\")}\n </Button>\n </>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n <Pager page={page} hasNext={list.hasNext} disabled={list.loading} onPage={(n) => loc.go(\"\", { ...filters, page: n > 1 ? n : undefined })} />\n {dialog}\n </div>\n );\n}\n\ninterface Form {\n title: string;\n slug: string;\n description: string;\n publishAt: string;\n body: string;\n}\n\nconst empty: Form = { title: \"\", slug: \"\", description: \"\", publishAt: \"\", body: \"\" };\n\nexport interface ContentEditorProps {\n /** The item edited; null creates one of kind. */\n id: string | null;\n kind: string;\n /** Languages to write in: codes, or codes with labels; the first is a new item's. */\n languages?: readonly Choice[];\n /** The body editor; without it, a plain HTML field. */\n renderEditor?: (props: BodyEditorProps) => ReactNode;\n /** A link to a live item's page. */\n contentHref?: (content: Content) => string | undefined;\n labels?: Labels;\n /** A new item's owner group: a channel its editors write for. */\n ownerGroup?: string;\n /** After create: show the new item (the editor goes on editing it). */\n onCreated?: (content: Content) => void;\n onDeleted?: (content: Content) => void;\n client?: ContentKitClient;\n onError?: ContentKitErrorHandler;\n}\n\n/**\n * One content item for its creators and staff: title, slug, description,\n * schedule, cover and body per language, publish, unpublish and delete;\n * then listing and attributes, access rule and price, versions and groups.\n */\nexport function ContentEditor({ id, client: own, kind, languages: offered, renderEditor, contentHref, labels, ownerGroup, onCreated, onDeleted, onError }: ContentEditorProps) {\n const client = useContentKitClient(own);\n const m = useMessages();\n const { t } = m;\n const ids = useId();\n const languages = choices(offered);\n const [language, setLanguage] = useState(languages[0]?.value ?? \"en\");\n const ed = useContent(id, kind, language, { client });\n // A hidden version's cover is edited here; a visible version's in its own form.\n const hidden = !!useKinds({ client }).kinds?.kinds[kind]?.versions.hidden;\n const report = useErrorReporter(onError);\n // The body is kept as stored (image references); the editor shows it with URLs.\n const [form, setForm] = useState<Form | null>(id ? null : empty);\n const [error, setError] = useState<ContentKitError | string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const { confirm, dialog } = useConfirm();\n const item = ed.content;\n useEffect(() => {\n if (!item) return;\n const stored = item.text?.[language];\n setForm((f) => f ?? { title: stored?.title ?? \"\", slug: stored?.slug ?? \"\", description: stored?.description ?? \"\", body: stored?.body ?? \"\",\n publishAt: item.publish_at && !item.live ? localInput(new Date(item.publish_at)) : \"\" });\n }, [item, language]);\n const { imageSrc } = ed;\n const shown = useMemo(\n () => (form?.body ?? \"\").replace(imageRefs(), (ref, name: string) => {\n const src = imageSrc(name);\n return src && src !== name ? src.replaceAll(\"&\", \"&\") : ref;\n }),\n [form?.body, imageSrc],\n );\n if (id && !form) {\n if (ed.error) return <LoadFailed error={ed.error} message={t(\"contentManager.loadFailed\")} retry={ed.reload} />;\n return (\n <div className=\"grid gap-3\" aria-busy>\n <Skeleton className=\"h-9 w-full\" />\n <Skeleton className=\"h-48 w-full\" />\n </div>\n );\n }\n const f = form!;\n const set = (patch: Partial<Form>) => setForm((cur) => ({ ...cur!, ...patch }));\n const fail = (e: unknown) => {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"content.save\");\n };\n const text = () => ({ title: f.title.trim(), slug: f.slug.trim() || undefined, description: f.description.trim(), body: f.body });\n const save = async () => {\n setError(null);\n setNotice(null);\n if (!f.title.trim()) return setError(t(\"contentManager.titleRequired\"));\n try {\n if (!item) {\n const created = await ed.create({ kind, text: { [language]: text() }, owner_group_id: ownerGroup });\n onCreated?.(created);\n return;\n }\n const saved = await ed.putText(text());\n setNotice(t(\"pollEditor.saved\"));\n setForm((cur) => ({ ...cur!, slug: saved.text?.[language]?.slug ?? cur!.slug }));\n } catch (e) {\n fail(e);\n }\n };\n const publish = async (live: boolean) => {\n setError(null);\n setNotice(null);\n try {\n if (live) await ed.publish(f.publishAt ? new Date(f.publishAt).toISOString() : undefined);\n else await ed.unpublish();\n setNotice(t(live ? \"contentManager.publishedNotice\" : \"contentManager.unpublishedNotice\"));\n } catch (e) {\n fail(e);\n }\n };\n const remove = async () => {\n if (!item || !(await confirm({ title: t(\"contentManager.deleteTitle\", { title: item.title }), description: t(\"contentManager.deleteBody\"), confirm: t(\"contentManager.delete\") }))) return;\n try {\n await ed.remove();\n onDeleted?.(item);\n } catch (e) {\n fail(e);\n }\n };\n const s = item ? contentStatus(item) : \"draft\";\n const href = item && s === \"published\" ? contentHref?.(item) : undefined;\n const editor: BodyEditorProps = {\n value: shown,\n onChange: (html) => set({ body: ed.storedBody(html) }),\n uploadImage: item ? (file) => ed.uploadImage(file) : undefined,\n disabled: ed.saving,\n placeholder: t(\"contentManager.bodyPlaceholder\"),\n \"aria-label\": t(\"contentManager.body\"),\n };\n return (\n <>\n <form\n className=\"grid gap-4\"\n data-ckui=\"content-form\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-base font-semibold\">{t(item ? \"contentManager.editTitle\" : \"contentManager.newTitle\")}</h2>\n {item && <Badge variant={badge[s]}>{t(`contentManager.${s}`)}</Badge>}\n {href && (\n <a className=\"text-xs text-muted-foreground hover:underline\" href={href}>\n {t(\"contentManager.view\")}\n </a>\n )}\n </div>\n {item?.moderation && (\n <Alert variant={item.moderation === \"rejected\" ? \"destructive\" : \"default\"}>\n <AlertDescription>\n {item.moderation_reason\n ? t(`contentManager.${item.moderation === \"rejected\" ? \"rejectedBecause\" : \"heldBecause\"}`, { reason: item.moderation_reason })\n : t(`contentManager.${item.moderation === \"rejected\" ? \"rejectedNote\" : \"heldNote\"}`)}\n </AlertDescription>\n </Alert>\n )}\n {languages.length > 1 && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-lang`}>{t(\"contentManager.language\")}</Label>\n <Select\n value={language}\n onValueChange={(v) => {\n setLanguage(String(v ?? \"\"));\n setForm(item ? null : empty);\n }}\n >\n <SelectTrigger id={`${ids}-lang`} className=\"w-full\">\n <SelectValue>{(v: string) => languages.find((l) => l.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {languages.map((l) => (\n <SelectItem key={l.value} value={l.value}>\n {l.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n )}\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-title`}>{t(\"contentManager.itemTitle\")}</Label>\n <Input id={`${ids}-title`} value={f.title} required maxLength={300} onChange={(e) => set({ title: e.target.value })} />\n </div>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-slug`}>{t(\"contentManager.slug\")}</Label>\n <Input id={`${ids}-slug`} value={f.slug} placeholder={t(\"contentManager.slugAuto\")} onChange={(e) => set({ slug: e.target.value })} />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-publish`}>{t(\"contentManager.publishAt\")}</Label>\n <Input id={`${ids}-publish`} type=\"datetime-local\" value={f.publishAt} onChange={(e) => set({ publishAt: e.target.value })} />\n </div>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor={`${ids}-description`}>{t(\"contentManager.description\")}</Label>\n <Textarea id={`${ids}-description`} rows={2} value={f.description} onChange={(e) => set({ description: e.target.value })} />\n </div>\n {(hidden || !item) && <Cover item={item} src={ed.coverSrc} saving={ed.saving} onFile={(file) => void ed.setCover(file).catch(fail)} />}\n <div className=\"grid gap-1.5\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.body\")}</span>\n {renderEditor ? renderEditor(editor) : <Textarea aria-label={editor[\"aria-label\"]} rows={12} value={f.body} placeholder={editor.placeholder} onChange={(e) => set({ body: e.target.value })} />}\n {!item && <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>}\n </div>\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 <div className=\"flex flex-wrap gap-2 border-t border-border pt-4\">\n <Button type=\"submit\" disabled={ed.saving}>\n {ed.saving ? t(\"common.saving\") : t(item ? \"common.save\" : \"contentManager.createDraft\")}\n </Button>\n {item && !item.live && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(true)}>\n {t(\"contentManager.publish\")}\n </Button>\n )}\n {item?.state === \"published\" && (\n <Button type=\"button\" variant=\"outline\" disabled={ed.saving} onClick={() => void publish(false)}>\n {t(\"contentManager.unpublish\")}\n </Button>\n )}\n {item && (\n <Button type=\"button\" variant=\"destructive\" className=\"ms-auto\" disabled={ed.saving} onClick={() => void remove()}>\n {t(\"contentManager.delete\")}\n </Button>\n )}\n </div>\n {dialog}\n </form>\n {item && <ContentSections content={item} client={client} onError={onError} labels={labels} />}\n </>\n );\n}\n\nfunction Cover({ item, src, saving, onFile }: { item: Content | null; src?: string; saving: boolean; onFile: (file: File | null) => void }) {\n const { t } = useMessages();\n const file = useRef<HTMLInputElement>(null);\n return (\n <div className=\"grid gap-1.5\" data-ckui=\"content-cover\">\n <span className=\"text-sm font-medium\">{t(\"contentManager.cover\")}</span>\n {item ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {src ? <img src={src} alt=\"\" className=\"h-20 w-36 rounded-md border border-border object-cover\" /> : <span className=\"text-muted-foreground\">{t(\"contentManager.noCover\")}</span>}\n <Button type=\"button\" size=\"sm\" variant=\"outline\" disabled={saving} onClick={() => file.current?.click()}>\n {t(src ? \"pollEditor.replaceImage\" : \"contentManager.addCover\")}\n </Button>\n {src && (\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={saving} onClick={() => onFile(null)}>\n {t(\"pollEditor.removeImage\")}\n </Button>\n )}\n <input\n ref={file}\n type=\"file\"\n accept=\"image/*\"\n hidden\n aria-label={t(\"contentManager.cover\")}\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f) onFile(f);\n }}\n />\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">{t(\"contentManager.imagesAfterCreate\")}</p>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -22,14 +22,16 @@ export interface ContentCardProps {
|
|
|
22
22
|
owner?: boolean;
|
|
23
23
|
/** The owner group's link; default its canonical path. */
|
|
24
24
|
ownerHref?: (g: GroupLike) => string | undefined;
|
|
25
|
+
/** Over the cover: the host's badges for the card, placed in the cover's box; clicks reach the card. */
|
|
26
|
+
decorate?: (card: Card) => ReactNode;
|
|
25
27
|
/** Below the meta line: the host's own. */
|
|
26
28
|
children?: ReactNode;
|
|
27
29
|
className?: string;
|
|
28
30
|
}
|
|
29
31
|
/** Why a card is locked, in words (its price when it is on sale); null when it opens. */
|
|
30
32
|
export declare function lockLabel(m: Translator, a: CardAccess, tiers?: readonly string[], labels?: Labels): string | null;
|
|
31
|
-
/** One content in a listing: cover, title, version, groups, counts, the reader's progress and why it is locked. */
|
|
32
|
-
export declare function ContentCard({ card, href, onOpen, shape, groupKinds, chips, tiers, labels, owner, ownerHref, children, className }: ContentCardProps): import('react').JSX.Element;
|
|
33
|
+
/** One content in a listing: cover, title, version, groups, counts, the reader's progress and why it is locked; what its kind's locked view hides is absent. */
|
|
34
|
+
export declare function ContentCard({ card, href, onOpen, shape, groupKinds, chips, tiers, labels, owner, ownerHref, decorate, children, className }: ContentCardProps): import('react').JSX.Element;
|
|
33
35
|
/** The group that owns a content (a channel): its avatar and name, linking to its page. */
|
|
34
36
|
export declare function OwnerChip({ group, href, className }: {
|
|
35
37
|
group: GroupLike;
|
|
@@ -41,4 +43,4 @@ export interface PostCardProps extends Omit<ContentCardProps, "shape" | "chips"
|
|
|
41
43
|
byline?: ReactNode;
|
|
42
44
|
}
|
|
43
45
|
/** A post in a feed: a wide cover, its title, when it was published and what unlocking it costs. */
|
|
44
|
-
export declare function PostCard({ card, href, onOpen, tiers, labels, byline, owner, ownerHref, children, className }: PostCardProps): import('react').JSX.Element;
|
|
46
|
+
export declare function PostCard({ card, href, onOpen, tiers, labels, byline, owner, ownerHref, decorate, children, className }: PostCardProps): import('react').JSX.Element;
|
|
@@ -11,11 +11,19 @@ import { cn as h } from "cn";
|
|
|
11
11
|
import { FavouriteIcon as g, LockIcon as _, ThumbsUpIcon as v } from "@hugeicons/core-free-icons";
|
|
12
12
|
import { HugeiconsIcon as y } from "@hugeicons/react";
|
|
13
13
|
//#region src/components/content/card.tsx
|
|
14
|
-
|
|
14
|
+
function b({ card: e, decorate: t }) {
|
|
15
|
+
let n = t?.(e);
|
|
16
|
+
return n == null || n === !1 ? null : /* @__PURE__ */ p("span", {
|
|
17
|
+
className: "pointer-events-none absolute inset-0",
|
|
18
|
+
"data-ckui": "card-decorations",
|
|
19
|
+
children: n
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
var x = (e, t) => new Intl.NumberFormat(t, {
|
|
15
23
|
notation: "compact",
|
|
16
24
|
maximumFractionDigits: 1
|
|
17
25
|
}).format(e);
|
|
18
|
-
function
|
|
26
|
+
function S(e, t, n = [], r) {
|
|
19
27
|
if (t.allowed) return null;
|
|
20
28
|
if (t.unknown) return e.t("contents.unknownAccess");
|
|
21
29
|
let i = t.offers?.[0];
|
|
@@ -23,7 +31,7 @@ function x(e, t, n = [], r) {
|
|
|
23
31
|
let a = t.sell?.find((e) => n.includes(e));
|
|
24
32
|
return a ? l(r, a) : t.sell?.some((e) => e.includes(":group:")) ? e.t("contents.members") : t.sell?.length ? e.t("contents.forSale") : e.t("contents.locked");
|
|
25
33
|
}
|
|
26
|
-
function
|
|
34
|
+
function C(e, t) {
|
|
27
35
|
let n = f(r)?.navigate;
|
|
28
36
|
return d((r) => {
|
|
29
37
|
t?.(), !(!n || !e || r.defaultPrevented || r.button !== 0 || r.metaKey || r.ctrlKey || r.shiftKey || r.altKey) && (r.preventDefault(), n(e));
|
|
@@ -33,110 +41,116 @@ function S(e, t) {
|
|
|
33
41
|
t
|
|
34
42
|
]);
|
|
35
43
|
}
|
|
36
|
-
function
|
|
37
|
-
let
|
|
38
|
-
...
|
|
39
|
-
...
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
].filter(Boolean),
|
|
44
|
+
function w({ card: r, href: a, onOpen: s, shape: c = "portrait", groupKinds: u, chips: f = 3, tiers: w, labels: D, owner: O = !0, ownerHref: k, decorate: A, children: j, className: M }) {
|
|
45
|
+
let N = n(), { t: P } = N, F = t(), I = a ? a(r) : r.path, L = C(I, d(() => s?.(r), [s, r])), R = S(N, r.access, w, D), z = r.version, B = r.viewer && r.viewer.progress_max > 0 ? Math.min(1, r.viewer.progress / r.viewer.progress_max) : 0, V = u ?? Object.keys(r.groups), H = z.duration_ms ? Math.round(z.duration_ms / 6e4) : 0, U = [
|
|
46
|
+
...z.languages.map(F),
|
|
47
|
+
...z.traits.map((e) => l(D, e)),
|
|
48
|
+
z.pages ? N.plural("contents.pages", z.pages) : null,
|
|
49
|
+
H ? N.plural("contents.minutes", H) : null
|
|
50
|
+
].filter(Boolean), W = r.title || r.code;
|
|
43
51
|
return /* @__PURE__ */ m("article", {
|
|
44
|
-
className: h("group/card grid content-start gap-2",
|
|
52
|
+
className: h("group/card grid content-start gap-2", M),
|
|
45
53
|
"data-ckui": "content-card",
|
|
46
54
|
"data-id": r.id,
|
|
47
|
-
"data-locked":
|
|
55
|
+
"data-locked": R ? "" : void 0,
|
|
48
56
|
lang: r.language || void 0,
|
|
49
|
-
children: [/* @__PURE__ */ m("
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
className: "
|
|
66
|
-
|
|
57
|
+
children: [/* @__PURE__ */ m("div", {
|
|
58
|
+
className: "relative overflow-hidden rounded-lg",
|
|
59
|
+
children: [/* @__PURE__ */ m("a", {
|
|
60
|
+
href: I,
|
|
61
|
+
onClick: L,
|
|
62
|
+
className: "relative block overflow-hidden rounded-lg bg-muted",
|
|
63
|
+
tabIndex: -1,
|
|
64
|
+
"aria-hidden": !0,
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ p("div", {
|
|
67
|
+
className: c === "landscape" ? "aspect-video" : "aspect-[3/4]",
|
|
68
|
+
children: /* @__PURE__ */ p(e, {
|
|
69
|
+
outputs: r.cover,
|
|
70
|
+
alt: "",
|
|
71
|
+
fit: "cover",
|
|
72
|
+
skeleton: !0,
|
|
73
|
+
className: "size-full transition-transform group-hover/card:scale-[1.02]",
|
|
74
|
+
fallback: /* @__PURE__ */ p("span", {
|
|
75
|
+
className: "grid size-full place-items-center text-2xl font-semibold text-muted-foreground",
|
|
76
|
+
children: W.charAt(0).toUpperCase()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
}),
|
|
80
|
+
R && /* @__PURE__ */ m("span", {
|
|
81
|
+
className: "absolute top-2 left-2 inline-flex items-center gap-1 rounded-md bg-background/85 px-1.5 py-0.5 text-xs font-medium text-foreground shadow-sm",
|
|
82
|
+
"data-ckui": "card-lock",
|
|
83
|
+
children: [/* @__PURE__ */ p(y, {
|
|
84
|
+
icon: _,
|
|
85
|
+
strokeWidth: 2,
|
|
86
|
+
className: "size-3.5",
|
|
87
|
+
"aria-hidden": !0
|
|
88
|
+
}), R]
|
|
89
|
+
}),
|
|
90
|
+
B > 0 && /* @__PURE__ */ p("span", {
|
|
91
|
+
className: "absolute inset-x-0 bottom-0 h-1 bg-background/60",
|
|
92
|
+
"data-ckui": "card-progress",
|
|
93
|
+
children: /* @__PURE__ */ p("span", {
|
|
94
|
+
className: "block h-full bg-primary",
|
|
95
|
+
style: { width: `${Math.round(B * 100)}%` }
|
|
67
96
|
})
|
|
68
97
|
})
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
icon: _,
|
|
75
|
-
strokeWidth: 2,
|
|
76
|
-
className: "size-3.5",
|
|
77
|
-
"aria-hidden": !0
|
|
78
|
-
}), I]
|
|
79
|
-
}),
|
|
80
|
-
R > 0 && /* @__PURE__ */ p("span", {
|
|
81
|
-
className: "absolute inset-x-0 bottom-0 h-1 bg-background/60",
|
|
82
|
-
"data-ckui": "card-progress",
|
|
83
|
-
children: /* @__PURE__ */ p("span", {
|
|
84
|
-
className: "block h-full bg-primary",
|
|
85
|
-
style: { width: `${Math.round(R * 100)}%` }
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
]
|
|
98
|
+
]
|
|
99
|
+
}), /* @__PURE__ */ p(b, {
|
|
100
|
+
card: r,
|
|
101
|
+
decorate: A
|
|
102
|
+
})]
|
|
89
103
|
}), /* @__PURE__ */ m("div", {
|
|
90
104
|
className: "grid gap-1",
|
|
91
105
|
children: [
|
|
92
106
|
/* @__PURE__ */ m("a", {
|
|
93
|
-
href:
|
|
94
|
-
onClick:
|
|
107
|
+
href: I,
|
|
108
|
+
onClick: L,
|
|
95
109
|
className: "line-clamp-2 font-medium break-words hover:underline",
|
|
96
110
|
"data-ckui": "card-title",
|
|
97
|
-
children: [
|
|
111
|
+
children: [W, R && /* @__PURE__ */ m("span", {
|
|
98
112
|
className: "sr-only",
|
|
99
113
|
children: [
|
|
100
114
|
" (",
|
|
101
|
-
|
|
115
|
+
R,
|
|
102
116
|
")"
|
|
103
117
|
]
|
|
104
118
|
})]
|
|
105
119
|
}),
|
|
106
|
-
|
|
120
|
+
O && r.owner && /* @__PURE__ */ p(T, {
|
|
107
121
|
group: r.owner,
|
|
108
|
-
href:
|
|
122
|
+
href: k
|
|
109
123
|
}),
|
|
110
|
-
|
|
124
|
+
U.length > 0 && /* @__PURE__ */ p("p", {
|
|
111
125
|
className: "text-xs text-muted-foreground",
|
|
112
|
-
children:
|
|
126
|
+
children: U.join(" · ")
|
|
113
127
|
}),
|
|
114
128
|
r.viewer?.completed && /* @__PURE__ */ p(i, {
|
|
115
129
|
variant: "secondary",
|
|
116
130
|
className: "justify-self-start",
|
|
117
|
-
children:
|
|
131
|
+
children: P("contents.completed")
|
|
118
132
|
}),
|
|
119
133
|
!r.access.allowed && r.access.free_after && /* @__PURE__ */ p("p", {
|
|
120
134
|
className: "text-xs text-muted-foreground",
|
|
121
|
-
children:
|
|
135
|
+
children: P("contents.freeFrom", { date: o(N, r.access.free_after, !1) })
|
|
122
136
|
}),
|
|
123
|
-
|
|
137
|
+
V.some((e) => r.groups[e]?.length) && /* @__PURE__ */ p("ul", {
|
|
124
138
|
className: "flex flex-wrap gap-1",
|
|
125
139
|
"data-ckui": "card-groups",
|
|
126
|
-
children:
|
|
140
|
+
children: V.flatMap((e) => (r.groups[e] ?? []).slice(0, f).map((t) => /* @__PURE__ */ p("li", {
|
|
127
141
|
"data-kind": e,
|
|
128
|
-
children: /* @__PURE__ */ p(
|
|
142
|
+
children: /* @__PURE__ */ p(E, {
|
|
129
143
|
url: t.path,
|
|
130
144
|
name: t.name || t.slug
|
|
131
145
|
})
|
|
132
146
|
}, `${e}:${t.id}`)))
|
|
133
147
|
}),
|
|
134
|
-
/* @__PURE__ */ m("p", {
|
|
148
|
+
(r.counts.favorites !== void 0 || r.counts.likes !== void 0) && /* @__PURE__ */ m("p", {
|
|
135
149
|
className: "flex items-center gap-3 text-xs text-muted-foreground tabular-nums",
|
|
136
150
|
"data-ckui": "card-counts",
|
|
137
|
-
children: [/* @__PURE__ */ m("span", {
|
|
151
|
+
children: [r.counts.favorites !== void 0 && /* @__PURE__ */ m("span", {
|
|
138
152
|
className: "inline-flex items-center gap-1",
|
|
139
|
-
title:
|
|
153
|
+
title: P("contents.favorites"),
|
|
140
154
|
children: [
|
|
141
155
|
/* @__PURE__ */ p(y, {
|
|
142
156
|
icon: g,
|
|
@@ -146,13 +160,13 @@ function C({ card: r, href: a, onOpen: s, shape: c = "portrait", groupKinds: u,
|
|
|
146
160
|
}),
|
|
147
161
|
/* @__PURE__ */ p("span", {
|
|
148
162
|
className: "sr-only",
|
|
149
|
-
children:
|
|
163
|
+
children: P("contents.favorites")
|
|
150
164
|
}),
|
|
151
|
-
|
|
165
|
+
x(r.counts.favorites, N.language)
|
|
152
166
|
]
|
|
153
|
-
}), /* @__PURE__ */ m("span", {
|
|
167
|
+
}), r.counts.likes !== void 0 && /* @__PURE__ */ m("span", {
|
|
154
168
|
className: "inline-flex items-center gap-1",
|
|
155
|
-
title:
|
|
169
|
+
title: P("contents.likes"),
|
|
156
170
|
children: [
|
|
157
171
|
/* @__PURE__ */ p(y, {
|
|
158
172
|
icon: v,
|
|
@@ -162,18 +176,18 @@ function C({ card: r, href: a, onOpen: s, shape: c = "portrait", groupKinds: u,
|
|
|
162
176
|
}),
|
|
163
177
|
/* @__PURE__ */ p("span", {
|
|
164
178
|
className: "sr-only",
|
|
165
|
-
children:
|
|
179
|
+
children: P("contents.likes")
|
|
166
180
|
}),
|
|
167
|
-
|
|
181
|
+
x(r.counts.likes, N.language)
|
|
168
182
|
]
|
|
169
183
|
})]
|
|
170
184
|
}),
|
|
171
|
-
|
|
185
|
+
j
|
|
172
186
|
]
|
|
173
187
|
})]
|
|
174
188
|
});
|
|
175
189
|
}
|
|
176
|
-
function
|
|
190
|
+
function T({ group: e, href: t, className: n }) {
|
|
177
191
|
return /* @__PURE__ */ p("span", {
|
|
178
192
|
className: h("flex min-w-0 text-xs text-muted-foreground", n),
|
|
179
193
|
"data-ckui": "card-owner",
|
|
@@ -192,8 +206,8 @@ function w({ group: e, href: t, className: n }) {
|
|
|
192
206
|
})
|
|
193
207
|
});
|
|
194
208
|
}
|
|
195
|
-
function
|
|
196
|
-
let n =
|
|
209
|
+
function E({ url: e, name: t }) {
|
|
210
|
+
let n = C(e), r = "inline-block rounded-md border border-border px-1.5 py-0.5 text-xs text-muted-foreground";
|
|
197
211
|
return e ? /* @__PURE__ */ p("a", {
|
|
198
212
|
href: e,
|
|
199
213
|
onClick: n,
|
|
@@ -204,61 +218,69 @@ function T({ url: e, name: t }) {
|
|
|
204
218
|
children: t
|
|
205
219
|
});
|
|
206
220
|
}
|
|
207
|
-
function
|
|
208
|
-
let
|
|
221
|
+
function D({ card: t, href: r, onOpen: i, tiers: o, labels: s, byline: c, owner: l = !0, ownerHref: u, decorate: f, children: v, className: w }) {
|
|
222
|
+
let E = n(), D = r ? r(t) : t.path, O = C(D, d(() => i?.(t), [i, t])), k = S(E, t.access, o, s), A = t.title || t.code;
|
|
209
223
|
return /* @__PURE__ */ m("article", {
|
|
210
|
-
className: h("grid gap-3 rounded-lg border border-border p-3",
|
|
224
|
+
className: h("grid gap-3 rounded-lg border border-border p-3", w),
|
|
211
225
|
"data-ckui": "post-card",
|
|
212
226
|
"data-id": t.id,
|
|
213
|
-
"data-locked":
|
|
227
|
+
"data-locked": k ? "" : void 0,
|
|
214
228
|
lang: t.language || void 0,
|
|
215
229
|
children: [
|
|
216
|
-
c ?? (l && t.owner && /* @__PURE__ */ p(
|
|
230
|
+
c ?? (l && t.owner && /* @__PURE__ */ p(T, {
|
|
217
231
|
group: t.owner,
|
|
218
232
|
href: u,
|
|
219
233
|
className: "text-sm"
|
|
220
234
|
})),
|
|
221
235
|
/* @__PURE__ */ m("a", {
|
|
222
|
-
href:
|
|
223
|
-
onClick:
|
|
236
|
+
href: D,
|
|
237
|
+
onClick: O,
|
|
224
238
|
className: "grid gap-2",
|
|
225
239
|
children: [/* @__PURE__ */ p("span", {
|
|
226
240
|
className: "font-medium break-words hover:underline",
|
|
227
241
|
"data-ckui": "card-title",
|
|
228
|
-
children:
|
|
242
|
+
children: A
|
|
229
243
|
}), t.cover.length > 0 && /* @__PURE__ */ m("span", {
|
|
230
244
|
className: "relative block aspect-video overflow-hidden rounded-md bg-muted",
|
|
231
|
-
children: [
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
"
|
|
247
|
-
|
|
245
|
+
children: [
|
|
246
|
+
/* @__PURE__ */ p(e, {
|
|
247
|
+
outputs: t.cover,
|
|
248
|
+
alt: "",
|
|
249
|
+
fit: "cover",
|
|
250
|
+
skeleton: !0,
|
|
251
|
+
className: h("size-full", k && "blur-md")
|
|
252
|
+
}),
|
|
253
|
+
/* @__PURE__ */ p(b, {
|
|
254
|
+
card: t,
|
|
255
|
+
decorate: f
|
|
256
|
+
}),
|
|
257
|
+
k && /* @__PURE__ */ p("span", {
|
|
258
|
+
className: "absolute inset-0 grid place-items-center",
|
|
259
|
+
children: /* @__PURE__ */ m("span", {
|
|
260
|
+
className: "inline-flex items-center gap-1 rounded-md bg-background/85 px-2 py-1 text-xs font-medium shadow-sm",
|
|
261
|
+
"data-ckui": "card-lock",
|
|
262
|
+
children: [/* @__PURE__ */ p(y, {
|
|
263
|
+
icon: _,
|
|
264
|
+
strokeWidth: 2,
|
|
265
|
+
className: "size-3.5",
|
|
266
|
+
"aria-hidden": !0
|
|
267
|
+
}), k]
|
|
268
|
+
})
|
|
248
269
|
})
|
|
249
|
-
|
|
270
|
+
]
|
|
250
271
|
})]
|
|
251
272
|
}),
|
|
252
273
|
/* @__PURE__ */ m("p", {
|
|
253
274
|
className: "flex flex-wrap items-center gap-3 text-xs text-muted-foreground",
|
|
254
275
|
children: [
|
|
255
276
|
t.published_at && /* @__PURE__ */ p(a, { at: t.published_at }),
|
|
256
|
-
!t.cover.length &&
|
|
277
|
+
!t.cover.length && k && /* @__PURE__ */ p("span", {
|
|
257
278
|
"data-ckui": "card-lock",
|
|
258
|
-
children:
|
|
279
|
+
children: k
|
|
259
280
|
}),
|
|
260
|
-
/* @__PURE__ */ m("span", {
|
|
281
|
+
t.counts.favorites !== void 0 && /* @__PURE__ */ m("span", {
|
|
261
282
|
className: "inline-flex items-center gap-1 tabular-nums",
|
|
283
|
+
"data-ckui": "card-counts",
|
|
262
284
|
children: [
|
|
263
285
|
/* @__PURE__ */ p(y, {
|
|
264
286
|
icon: g,
|
|
@@ -268,18 +290,18 @@ function E({ card: t, href: r, onOpen: i, tiers: o, labels: s, byline: c, owner:
|
|
|
268
290
|
}),
|
|
269
291
|
/* @__PURE__ */ p("span", {
|
|
270
292
|
className: "sr-only",
|
|
271
|
-
children:
|
|
293
|
+
children: E.t("contents.favorites")
|
|
272
294
|
}),
|
|
273
|
-
|
|
295
|
+
x(t.counts.favorites, E.language)
|
|
274
296
|
]
|
|
275
297
|
})
|
|
276
298
|
]
|
|
277
299
|
}),
|
|
278
|
-
|
|
300
|
+
v
|
|
279
301
|
]
|
|
280
302
|
});
|
|
281
303
|
}
|
|
282
304
|
//#endregion
|
|
283
|
-
export {
|
|
305
|
+
export { w as ContentCard, T as OwnerChip, D as PostCard, S as lockLabel };
|
|
284
306
|
|
|
285
307
|
//# sourceMappingURL=card.js.map
|