@openrails/contentkit-ui 0.104.0-rc.1 → 0.104.0-rc.3
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 +6 -6
- package/dist/client/beacons.js +4 -1
- package/dist/client/beacons.js.map +1 -1
- package/dist/client/contents.d.ts +9 -1
- package/dist/client/contents.js +73 -55
- package/dist/client/contents.js.map +1 -1
- package/dist/client/discovery.d.ts +6 -2
- package/dist/client/discovery.js +1 -1
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +4 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +43 -30
- package/dist/client/generated/routes.js +70 -63
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +36 -74
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/media/windows.d.ts +2 -0
- package/dist/client/media/windows.js +4 -1
- package/dist/client/media/windows.js.map +1 -1
- package/dist/client/stats.d.ts +4 -20
- package/dist/client/stats.js +0 -11
- package/dist/client/stats.js.map +1 -1
- package/dist/client/versions.d.ts +8 -6
- package/dist/client/versions.js +15 -10
- package/dist/client/versions.js.map +1 -1
- package/dist/components/admin/bulk-drafts.d.ts +17 -0
- package/dist/components/admin/bulk-drafts.js +41 -0
- package/dist/components/admin/bulk-drafts.js.map +1 -0
- package/dist/components/admin/content-admin.d.ts +1 -1
- package/dist/components/admin/content-admin.js.map +1 -1
- package/dist/components/admin/content-manager.d.ts +11 -3
- package/dist/components/admin/content-manager.js +739 -380
- package/dist/components/admin/content-manager.js.map +1 -1
- package/dist/components/admin/group-manager.d.ts +8 -3
- package/dist/components/admin/group-manager.js +218 -146
- package/dist/components/admin/group-manager.js.map +1 -1
- package/dist/components/admin/page-notices.d.ts +16 -0
- package/dist/components/admin/page-notices.js +113 -0
- package/dist/components/admin/page-notices.js.map +1 -0
- package/dist/components/admin/page-picker.d.ts +38 -0
- package/dist/components/admin/page-picker.js +159 -0
- package/dist/components/admin/page-picker.js.map +1 -0
- package/dist/components/admin/reader-content-editor.js +537 -500
- package/dist/components/admin/reader-content-editor.js.map +1 -1
- package/dist/components/admin/reader-manifest.js +181 -179
- package/dist/components/admin/reader-manifest.js.map +1 -1
- package/dist/components/admin/shell.d.ts +10 -2
- package/dist/components/admin/shell.js +114 -42
- package/dist/components/admin/shell.js.map +1 -1
- package/dist/components/admin/stats-dashboard.d.ts +4 -9
- package/dist/components/admin/stats-dashboard.js +300 -202
- package/dist/components/admin/stats-dashboard.js.map +1 -1
- package/dist/components/admin/upload-queue.d.ts +14 -0
- package/dist/components/admin/upload-queue.js +115 -98
- package/dist/components/admin/upload-queue.js.map +1 -1
- package/dist/components/content/card.d.ts +31 -3
- package/dist/components/content/card.js +324 -158
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/daily-releases.js +14 -12
- package/dist/components/content/daily-releases.js.map +1 -1
- package/dist/components/content/editor-save.js +27 -25
- package/dist/components/content/editor-save.js.map +1 -1
- package/dist/components/content/editors.js +274 -253
- package/dist/components/content/editors.js.map +1 -1
- package/dist/components/content/favorites.d.ts +1 -1
- package/dist/components/content/favorites.js +36 -22
- package/dist/components/content/favorites.js.map +1 -1
- package/dist/components/content/grid.d.ts +11 -4
- package/dist/components/content/grid.js +52 -43
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/history.js +83 -63
- package/dist/components/content/history.js.map +1 -1
- package/dist/components/content/index.d.ts +5 -3
- package/dist/components/content/listing.d.ts +14 -3
- package/dist/components/content/listing.js +65 -56
- package/dist/components/content/listing.js.map +1 -1
- package/dist/components/content/offer-fields.js +6 -6
- package/dist/components/content/paged-grid.d.ts +10 -0
- package/dist/components/content/paged-grid.js +116 -0
- package/dist/components/content/paged-grid.js.map +1 -0
- package/dist/components/content/pagination.d.ts +19 -0
- package/dist/components/content/pagination.js +97 -0
- package/dist/components/content/pagination.js.map +1 -0
- package/dist/components/content/paywall.d.ts +9 -0
- package/dist/components/content/paywall.js +27 -3
- package/dist/components/content/paywall.js.map +1 -1
- package/dist/components/content/post-list.d.ts +37 -0
- package/dist/components/content/post-list.js +203 -0
- package/dist/components/content/post-list.js.map +1 -0
- package/dist/components/content/reader-settings.d.ts +2 -0
- package/dist/components/content/reader-settings.js +1 -1
- package/dist/components/content/reader-settings.js.map +1 -1
- package/dist/components/groups/banner.d.ts +6 -2
- package/dist/components/groups/banner.js +79 -61
- package/dist/components/groups/banner.js.map +1 -1
- package/dist/components/groups/directory.d.ts +12 -2
- package/dist/components/groups/directory.js +168 -105
- package/dist/components/groups/directory.js.map +1 -1
- package/dist/components/groups/editor.js +10 -10
- package/dist/components/groups/editor.js.map +1 -1
- package/dist/components/groups/entity-chips.d.ts +61 -0
- package/dist/components/groups/entity-chips.js +382 -0
- package/dist/components/groups/entity-chips.js.map +1 -0
- package/dist/components/groups/group-members.d.ts +29 -0
- package/dist/components/groups/group-members.js +474 -0
- package/dist/components/groups/group-members.js.map +1 -0
- package/dist/components/groups/header.js +53 -45
- package/dist/components/groups/header.js.map +1 -1
- package/dist/components/groups/index.d.ts +3 -1
- package/dist/components/groups/memberships.d.ts +3 -36
- package/dist/components/groups/memberships.js +105 -480
- package/dist/components/groups/memberships.js.map +1 -1
- package/dist/components/groups/scoped.d.ts +23 -0
- package/dist/components/groups/scoped.js +92 -0
- package/dist/components/groups/scoped.js.map +1 -0
- package/dist/components/media-folder-editor.d.ts +5 -0
- package/dist/components/media-folder-editor.js +418 -230
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/social/poll.js +57 -48
- package/dist/components/social/poll.js.map +1 -1
- package/dist/components/sortable-list.d.ts +5 -1
- package/dist/components/sortable-list.js +97 -81
- package/dist/components/sortable-list.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +41 -37
- package/dist/locales/admin/de.js +78 -45
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +74 -41
- package/dist/locales/admin/en.js +78 -45
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +78 -45
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +78 -45
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +78 -45
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +78 -45
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/de.js +2 -0
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.js +25 -1
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +24 -0
- package/dist/locales/discovery/en.js +25 -1
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +25 -1
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +25 -1
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +25 -1
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +25 -1
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +120 -43
- package/dist/locales/en.js +2 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +2 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/groups/de.js +22 -4
- package/dist/locales/groups/de.js.map +1 -1
- package/dist/locales/groups/en.d.ts +20 -2
- package/dist/locales/groups/en.js +22 -4
- package/dist/locales/groups/en.js.map +1 -1
- package/dist/locales/groups/es.js +22 -4
- package/dist/locales/groups/es.js.map +1 -1
- package/dist/locales/groups/ja.js +22 -4
- package/dist/locales/groups/ja.js.map +1 -1
- package/dist/locales/groups/ko.js +22 -4
- package/dist/locales/groups/ko.js.map +1 -1
- package/dist/locales/groups/zh.js +22 -4
- package/dist/locales/groups/zh.js.map +1 -1
- package/dist/locales/ja.js +2 -0
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +2 -0
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +2 -0
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/contents.js +1 -1
- package/dist/react/contents.js.map +1 -1
- package/dist/react/discovery.d.ts +16 -0
- package/dist/react/discovery.js +69 -37
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/groups.d.ts +19 -3
- package/dist/react/groups.js +49 -22
- package/dist/react/groups.js.map +1 -1
- package/dist/react/index.d.ts +6 -4
- package/dist/react/page-notices.d.ts +13 -0
- package/dist/react/page-notices.js +38 -0
- package/dist/react/page-notices.js.map +1 -0
- package/dist/react/post-reads.d.ts +23 -0
- package/dist/react/post-reads.js +90 -0
- package/dist/react/post-reads.js.map +1 -0
- package/dist/react/remembered-sort.d.ts +5 -0
- package/dist/react/remembered-sort.js +29 -0
- package/dist/react/remembered-sort.js.map +1 -0
- package/dist/react/resources.js +2 -2
- package/dist/react/resources.js.map +1 -1
- package/dist/react/stats.d.ts +3 -7
- package/dist/react/stats.js +2 -6
- package/dist/react/stats.js.map +1 -1
- package/dist/react/use-engagement.d.ts +2 -0
- package/dist/react/use-engagement.js +40 -34
- package/dist/react/use-engagement.js.map +1 -1
- package/dist/react.js +29 -27
- package/dist/styles.css +1 -1
- package/dist/urls/index.d.ts +1 -1
- package/dist/urls.js +2 -2
- package/dist/urls.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/admin/page-review.d.ts +0 -13
- package/dist/components/admin/page-review.js +0 -225
- package/dist/components/admin/page-review.js.map +0 -1
- package/dist/react/page-review.d.ts +0 -14
- package/dist/react/page-review.js +0 -37
- package/dist/react/page-review.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"card.js","names":[],"sources":["../../../src/components/content/card.tsx"],"sourcesContent":["import { FavouriteIcon, LockIcon, ThumbsUpIcon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useContext, useState, type CSSProperties, type MouseEvent, type ReactNode } from \"react\";\nimport type { Card, CardAccess, Chip as EntityChip } from \"../../client/generated/wire.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { ratio } from \"../../client/aspect.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { usePresets } from \"../../react/public.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { HoverPreview } from \"../hover-preview.js\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { formatDate, RelativeTime } from \"../social/parts.js\";\nimport { GroupAvatar, GroupLink, labelOf, type GroupLike, type Labels } from \"../groups/parts.js\";\nimport { priceLabel } from \"./paywall.js\";\n\nexport interface ContentCardProps {\n card: Card;\n /** Overrides the provider's client for this card's public media configuration. */\n client?: ContentKitClient;\n /** The card's link; default its canonical path. */\n href?: (card: Card) => string | undefined;\n /** Before the card's link is followed: a click signal. */\n onOpen?: (card: Card) => void;\n /** Fallback cover shape when the server declares no preset aspect. Default portrait (3:4). */\n shape?: \"portrait\" | \"landscape\";\n /** A CSS aspect ratio overriding shape, such as \"182 / 260\". */\n aspectRatio?: CSSProperties[\"aspectRatio\"];\n /** Place the title below the cover (default), or over its bottom gradient. */\n titlePosition?: \"below\" | \"cover\";\n /** Mount images and previews. False keeps the card's text and reserved cover space. */\n loadMedia?: boolean;\n /** The group kinds whose chips show, in order; default every kind the card names. */\n groupKinds?: readonly string[];\n /** Chips per group kind. Default 3. */\n chips?: number;\n /** Select which entity chips appear, before the per-kind chip limit. */\n filterEntity?: (entity: EntityChip, kind: string) => boolean;\n /** Bordered chips (default) or plain linked text. */\n entityStyle?: \"chips\" | \"text\";\n /** One inline list (default), or a separate row for each group kind. */\n entityLayout?: \"inline\" | \"rows\";\n /** Metadata fields to show; omitted fields keep their defaults. */\n metadata?: CardMetadata;\n /** Show like and favorite totals. Default true. */\n showCounts?: boolean;\n /** Tiers the site sells, so a lock names them; others show as \"for sale\". */\n tiers?: readonly string[];\n /** Display names of kinds, traits and tiers by identifier. */\n labels?: Labels;\n /** Show the owner group (a channel) when the card has one; default true, false on that group's own feed. */\n owner?: boolean;\n /** The owner group's link; default its canonical path. */\n ownerHref?: (g: GroupLike) => string | undefined;\n /** Over the cover: the host's badges for the card, placed in the cover's box; clicks reach the card. */\n decorate?: (card: Card) => ReactNode;\n /** Below the meta line: the host's own. */\n children?: ReactNode;\n className?: string;\n}\n\nexport interface CardMetadata {\n languages?: boolean;\n /** True shows all traits; an array selects specific trait identifiers. */\n traits?: boolean | readonly string[];\n pages?: boolean;\n duration?: boolean;\n}\n\n/** The host's badges over a cover. */\nfunction Decorations({ card, decorate, aboveTitle }: { card: Card; decorate?: (card: Card) => ReactNode; aboveTitle?: boolean }) {\n const badges = decorate?.(card);\n if (badges == null || badges === false) return null;\n return (\n <span className={cn(\"pointer-events-none absolute inset-0\", aboveTitle && \"z-20\")} data-ckui=\"card-decorations\">\n {badges}\n </span>\n );\n}\n\nconst compact = (n: number, language?: string) => new Intl.NumberFormat(language, { notation: \"compact\", maximumFractionDigits: 1 }).format(n);\n\n/** Why a card is locked, in words (its price when it is on sale); null when it opens. */\nexport function lockLabel(m: Translator, a: CardAccess, tiers: readonly string[] = [], labels?: Labels): string | null {\n if (a.allowed) return null;\n if (a.unknown) return m.t(\"contents.unknownAccess\");\n const offer = a.offers?.[0];\n if (offer) return offer.target.kind === \"group\" && Number(offer.terms.amount) === 0 ? m.t(\"contents.joinFree\") : priceLabel(m, offer.terms);\n const tier = a.sell?.find((k) => tiers.includes(k));\n if (tier) return labelOf(labels, tier);\n if (a.sell?.some((k) => k.includes(\":group:\"))) return m.t(\"contents.members\");\n if (a.sell?.length) return m.t(\"contents.forSale\");\n return m.t(\"contents.locked\");\n}\n\n/** A link followed in-app through the provider's navigate. */\nfunction useLink(url: string | undefined, before?: () => void) {\n const navigate = useContext(ContentKitContext)?.navigate;\n return useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n before?.();\n if (!navigate || !url || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n e.preventDefault();\n navigate(url);\n },\n [navigate, url, before],\n );\n}\n\n/** One content in a listing: cover (a video the reader opens previews over it on hover), title, version, groups, counts, the reader's progress and why it is locked; what its kind's locked view hides is absent. */\nexport function ContentCard({ card, client, href, onOpen, shape = \"portrait\", aspectRatio, titlePosition = \"below\", loadMedia = true, groupKinds, chips = 3, filterEntity, entityStyle = \"chips\", entityLayout = \"inline\", metadata, showCounts = true, tiers, labels, owner = true, ownerHref, decorate, children, className }: ContentCardProps) {\n const m = useMessages();\n const { presets } = usePresets({ client });\n const coverAspect = aspectRatio ?? ratio(presets.find((p) => p.kind === card.kind && p.name === \"cover\")?.aspect);\n const { t } = m;\n const languageName = useLanguageName();\n const url = href ? href(card) : card.path;\n const open = useCallback(() => onOpen?.(card), [onOpen, card]);\n const onClick = useLink(url, open);\n const lock = lockLabel(m, card.access, tiers, labels);\n const v = card.version;\n const progress = card.viewer?.completed ? 1 : card.viewer && card.viewer.progress_max > 0 ? Math.min(1, card.viewer.progress / card.viewer.progress_max) : 0;\n const kinds = groupKinds ?? Object.keys(card.entities);\n const entityGroups = kinds.map((kind) => ({ kind, entities: (card.entities[kind] ?? []).filter((entity) => !filterEntity || filterEntity(entity, kind)).slice(0, chips) })).filter(({ entities }) => entities.length > 0);\n const minutes = v.duration_ms ? Math.round(v.duration_ms / 60_000) : 0;\n const selectedTraits = metadata?.traits;\n const traits = selectedTraits === false ? [] : Array.isArray(selectedTraits) ? v.traits.filter((trait) => selectedTraits.includes(trait)) : v.traits;\n const meta = [\n ...(metadata?.languages === false ? [] : v.languages.map(languageName)),\n metadata?.pages !== false && v.pages ? m.plural(\"contents.pages\", v.pages) : null,\n metadata?.duration !== false && minutes ? m.plural(\"contents.minutes\", minutes) : null,\n ].filter(Boolean);\n const title = card.title || card.code;\n const entityClassName = cn(\"flex flex-wrap\", entityStyle === \"text\" ? \"gap-x-2 gap-y-1\" : \"gap-1\");\n const entityItems = (kind: string, entities: EntityChip[]) => entities.map((entity) => <li key={`${kind}:${entity.id}`} data-kind={kind}><Chip url={entity.path} name={entity.name || entity.slug} variant={entityStyle} /></li>);\n const entityList = entityGroups.length > 0 ? entityLayout === \"rows\" ? (\n <div className=\"grid gap-1\" data-ckui=\"card-groups\" data-layout=\"rows\">\n {entityGroups.map(({ kind, entities }) => <ul key={kind} className={entityClassName} aria-label={labelOf(labels, kind)} data-ckui=\"card-group-row\" data-kind={kind}>{entityItems(kind, entities)}</ul>)}\n </div>\n ) : (\n <ul className={entityClassName} data-ckui=\"card-groups\" data-layout=\"inline\">\n {entityGroups.flatMap(({ kind, entities }) => entityItems(kind, entities))}\n </ul>\n ) : null;\n const titleLink = <a href={url} onClick={onClick} className={cn(\"text-lg leading-snug font-medium break-words focus-visible:underline sm:text-xl\", titlePosition === \"cover\" ? \"absolute inset-x-0 bottom-0 z-10 bg-linear-to-t from-black/90 via-black/60 to-transparent px-3 pt-10 pb-3 text-white hover:underline\" : \"hover:underline\")} data-ckui=\"card-title\">\n <span className=\"line-clamp-2\">{title}</span>\n {lock && <span className=\"sr-only\"> ({lock})</span>}\n </a>;\n const [article, setArticle] = useState<HTMLElement | null>(null);\n return (\n <article ref={setArticle} className={cn(\"group/card relative grid content-start gap-2\", className)} data-ckui=\"content-card\" data-id={card.id} data-locked={lock ? \"\" : undefined} lang={card.language || undefined}>\n <div className=\"relative overflow-hidden rounded-lg\" data-ckui=\"card-cover\">\n <a href={url} onClick={onClick} className=\"relative block overflow-hidden rounded-lg bg-muted\" tabIndex={-1} aria-hidden>\n <div data-ckui=\"card-cover-frame\" className={coverAspect === undefined ? shape === \"landscape\" ? \"aspect-video\" : \"aspect-[3/4]\" : undefined} style={coverAspect === undefined ? undefined : { aspectRatio: coverAspect }}>\n {loadMedia ? <RenditionImg\n outputs={card.cover}\n alt=\"\"\n fit=\"cover\"\n skeleton\n loading=\"lazy\"\n className=\"size-full transition-transform group-hover/card:scale-[1.02]\"\n fallback={<span className=\"grid size-full place-items-center text-2xl font-semibold text-muted-foreground\">{title.charAt(0).toUpperCase()}</span>}\n /> : <span className=\"block size-full bg-muted\" aria-hidden />}\n </div>\n {loadMedia && v.hls && <HoverPreview item={{ kind: card.kind, id: card.id, version: v.id }} dir={v.hls} duration={v.duration_ms ? v.duration_ms / 1000 : undefined} target={article} />}\n {lock && (\n <span 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\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n )}\n {progress > 0 && (\n <span className={cn(\"absolute inset-x-0 bottom-0 h-1 bg-background/60\", titlePosition === \"cover\" && \"z-20\")} data-ckui=\"card-progress\">\n <span className=\"block h-full bg-primary\" style={{ width: `${Math.round(progress * 100)}%` }} />\n </span>\n )}\n </a>\n {titlePosition === \"cover\" && titleLink}\n {loadMedia && <Decorations card={card} decorate={decorate} aboveTitle={titlePosition === \"cover\"} />}\n </div>\n <div className=\"grid gap-1\">\n {titlePosition === \"below\" && titleLink}\n {entityStyle === \"text\" && entityList}\n {owner && card.owner && <OwnerChip group={card.owner} href={ownerHref} loadMedia={loadMedia} />}\n {traits.length > 0 && <div className=\"flex flex-wrap gap-1\" data-ckui=\"card-traits\">\n {traits.map((trait) => <Badge key={trait} variant=\"outline\" className=\"h-auto rounded-md text-sm text-muted-foreground\" data-ckui=\"card-trait\" data-trait={trait}>{labelOf(labels, trait)}</Badge>)}\n </div>}\n {meta.length > 0 && <p className=\"text-xs text-muted-foreground\">{meta.join(\" · \")}</p>}\n {!card.access.allowed && card.access.free_after && <p className=\"text-xs text-muted-foreground\">{t(\"contents.freeFrom\", { date: formatDate(m, card.access.free_after, false) })}</p>}\n {entityStyle === \"chips\" && entityList}\n {showCounts && (card.counts.favorites !== undefined || card.counts.likes !== undefined) && (\n <p className=\"flex items-center gap-3 text-xs text-muted-foreground tabular-nums\" data-ckui=\"card-counts\">\n {card.counts.favorites !== undefined && (\n <span className=\"inline-flex items-center gap-1\" title={t(\"contents.favorites\")}>\n <HugeiconsIcon icon={FavouriteIcon} strokeWidth={2} className={cn(\"size-3.5\", card.viewer?.favorited && \"fill-current text-primary\")} aria-hidden />\n <span className=\"sr-only\">{t(\"contents.favorites\")}</span>\n {compact(card.counts.favorites, m.language)}\n </span>\n )}\n {card.counts.likes !== undefined && (\n <span className=\"inline-flex items-center gap-1\" title={t(\"contents.likes\")}>\n <HugeiconsIcon icon={ThumbsUpIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n <span className=\"sr-only\">{t(\"contents.likes\")}</span>\n {compact(card.counts.likes, m.language)}\n </span>\n )}\n </p>\n )}\n {children}\n </div>\n </article>\n );\n}\n\n/** The group that owns a content (a channel): its avatar and name, linking to its page. */\nexport function OwnerChip({ group, href, className, loadMedia = true }: { group: GroupLike; href?: (g: GroupLike) => string | undefined; className?: string; loadMedia?: boolean }) {\n return (\n <span className={cn(\"flex min-w-0 text-xs text-muted-foreground\", className)} data-ckui=\"card-owner\" data-id={group.id}>\n <GroupLink group={group} href={href} className=\"inline-flex min-w-0 items-center gap-1.5 hover:text-foreground\">\n {loadMedia ? <GroupAvatar group={group} className=\"size-5 shrink-0 text-[0.625rem]\" /> : <span className=\"size-5 shrink-0 rounded-full bg-muted\" aria-hidden />}\n <span className=\"truncate\">{group.name || group.slug}</span>\n </GroupLink>\n </span>\n );\n}\n\nfunction Chip({ url, name, variant = \"chips\" }: { url?: string; name: string; variant?: \"chips\" | \"text\" }) {\n const onClick = useLink(url);\n const cls = variant === \"text\" ? \"text-sm text-muted-foreground\" : \"inline-block rounded-md border border-border px-1.5 py-0.5 text-sm text-muted-foreground\";\n return url ? (\n <a href={url} onClick={onClick} className={cn(cls, \"hover:text-foreground\", variant === \"text\" ? \"hover:underline\" : \"hover:bg-muted\")}>\n {name}\n </a>\n ) : (\n <span className={cls}>{name}</span>\n );\n}\n\nexport interface PostCardProps extends Omit<ContentCardProps, \"shape\" | \"aspectRatio\" | \"titlePosition\" | \"chips\" | \"groupKinds\" | \"filterEntity\" | \"entityStyle\" | \"entityLayout\" | \"metadata\"> {\n /** Above the title: the post's channel or author; default its owner group's chip. */\n byline?: ReactNode;\n}\n\n/** A post in a feed: a wide cover, its title, when it was published and what unlocking it costs. */\nexport function PostCard({ card, href, onOpen, tiers, labels, byline, owner = true, ownerHref, loadMedia = true, showCounts = true, decorate, children, className }: PostCardProps) {\n const m = useMessages();\n const url = href ? href(card) : card.path;\n const open = useCallback(() => onOpen?.(card), [onOpen, card]);\n const onClick = useLink(url, open);\n const lock = lockLabel(m, card.access, tiers, labels);\n const title = card.title || card.code;\n return (\n <article className={cn(\"relative grid gap-3 rounded-lg border border-border p-3\", className)} data-ckui=\"post-card\" data-id={card.id} data-locked={lock ? \"\" : undefined} lang={card.language || undefined}>\n {byline ?? (owner && card.owner && <OwnerChip group={card.owner} href={ownerHref} loadMedia={loadMedia} className=\"text-sm\" />)}\n <a href={url} onClick={onClick} className=\"grid gap-2\">\n <span className=\"font-medium break-words hover:underline\" data-ckui=\"card-title\">\n {title}\n </span>\n {card.cover.length > 0 && (\n <span className=\"relative block aspect-video overflow-hidden rounded-md bg-muted\">\n {loadMedia && <RenditionImg outputs={card.cover} alt=\"\" fit=\"cover\" skeleton loading=\"lazy\" className={cn(\"size-full\", lock && \"blur-md\")} />}\n {loadMedia && <Decorations card={card} decorate={decorate} />}\n {lock && (\n <span className=\"absolute inset-0 grid place-items-center\">\n <span className=\"inline-flex items-center gap-1 rounded-md bg-background/85 px-2 py-1 text-xs font-medium shadow-sm\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n </span>\n )}\n </span>\n )}\n </a>\n <p className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {card.published_at && <RelativeTime at={card.published_at} />}\n {!card.cover.length && lock && <span data-ckui=\"card-lock\">{lock}</span>}\n {showCounts && card.counts.favorites !== undefined && (\n <span className=\"inline-flex items-center gap-1 tabular-nums\" data-ckui=\"card-counts\">\n <HugeiconsIcon icon={FavouriteIcon} strokeWidth={2} className={cn(\"size-3.5\", card.viewer?.favorited && \"fill-current text-primary\")} aria-hidden />\n <span className=\"sr-only\">{m.t(\"contents.favorites\")}</span>\n {compact(card.counts.favorites, m.language)}\n </span>\n )}\n </p>\n {children}\n </article>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"card.js","names":[],"sources":["../../../src/components/content/card.tsx"],"sourcesContent":["import { FavouriteIcon, LockIcon, SparklesIcon, ThumbsUpIcon, Tick02Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { Fragment, useCallback, useContext, useLayoutEffect, useState, type CSSProperties, type MouseEvent, type ReactNode } from \"react\";\nimport type { Card, CardAccess, Chip as EntityChip } from \"../../client/generated/wire.js\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport { ratio } from \"../../client/aspect.js\";\nimport { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { usePresets } from \"../../react/public.js\";\nimport { Badge } from \"#ckui/ui/badge\";\nimport { HoverPreview } from \"../hover-preview.js\";\nimport { RenditionImg } from \"../rendition-img.js\";\nimport { formatDate, RelativeTime } from \"../social/parts.js\";\nimport { GroupAvatar, GroupLink, labelOf, type GroupLike, type Labels } from \"../groups/parts.js\";\nimport { priceLabel } from \"./paywall.js\";\n\nexport interface ContentCardProps {\n card: Card;\n /** Overrides the provider's client for this card's public media configuration. */\n client?: ContentKitClient;\n /** The card's link; default its canonical path. */\n href?: (card: Card) => string | undefined;\n /** Before the card's link is followed: a click signal. */\n onOpen?: (card: Card) => void;\n /** Fallback cover shape when the server declares no preset aspect. Default portrait (3:4). */\n shape?: \"portrait\" | \"landscape\";\n /** A CSS aspect ratio overriding shape, such as \"182 / 260\". */\n aspectRatio?: CSSProperties[\"aspectRatio\"];\n /** Place the title below the cover (default), or over its bottom gradient. */\n titlePosition?: \"below\" | \"cover\";\n /** Mount images and previews. False keeps the card's text and reserved cover space. */\n loadMedia?: boolean;\n /** The group kinds whose chips show, in order; default every kind the card names. */\n groupKinds?: readonly string[];\n /** Chips per group kind, or `{ base, lg }` to show a different number from the lg breakpoint. Default 3. */\n chips?: number | { base: number; lg: number };\n /** After a kind's chips, a \"+N\" chip counting the ones left out. Default false. */\n chipOverflow?: boolean;\n /** Inline chips on one line, as many whole chips as fit (at least one); the \"+N\" counts the rest. Default false. */\n chipFit?: boolean;\n /** Select which entity chips appear, before the per-kind chip limit. */\n filterEntity?: (entity: EntityChip, kind: string) => boolean;\n /** Bordered chips (default) or plain linked text. */\n entityStyle?: \"chips\" | \"text\";\n /** One inline list (default), or a separate row for each group kind. */\n entityLayout?: \"inline\" | \"rows\";\n /** Metadata fields to show; omitted fields keep their defaults. */\n metadata?: CardMetadata;\n /** Show like and favorite totals. Default true. */\n showCounts?: boolean;\n /** Tiers the site sells, so a lock names them; others show as \"for sale\". */\n tiers?: readonly string[];\n /** Display names of kinds, traits and tiers by identifier. */\n labels?: Labels;\n /** Show the owner group (a channel) when the card has one; default true, false on that group's own feed. */\n owner?: boolean;\n /** The owner group's link; default its canonical path. */\n ownerHref?: (g: GroupLike) => string | undefined;\n /** Over the cover: the host's badges for the card, placed in the cover's box; clicks reach the card. */\n decorate?: (card: Card) => ReactNode;\n /** Mark updated, completed and (for staff) deleted cards over the cover. Default true; false when the host draws cardStatus itself. */\n status?: boolean;\n /** Dim the cover of what the reader disliked. Default true. */\n dimDisliked?: boolean;\n /** Below the meta line: the host's own. */\n children?: ReactNode;\n className?: string;\n}\n\nexport interface CardMetadata {\n languages?: boolean;\n /** True shows all traits; an array selects specific trait identifiers. */\n traits?: boolean | readonly string[];\n pages?: boolean;\n duration?: boolean;\n /** Group kinds named last in the meta line: the first entity of the first kind the card has, linked. */\n groups?: readonly string[];\n}\n\n/** What a card says about the reader's standing and the work's state. */\nexport interface CardStatus {\n /** A newer version since the reader last read it, else within the last two weeks. */\n updated: boolean;\n completed: boolean;\n /** Only staff and the item's creators are told. */\n deleted: boolean;\n disliked: boolean;\n}\n\nconst RECENT_MS = 14 * 24 * 60 * 60 * 1000;\n\nexport function cardStatus(card: Card, now = Date.now()): CardStatus {\n const updated = card.updated_at ? Date.parse(card.updated_at) : NaN;\n const read = card.viewer?.last_read_at ? Date.parse(card.viewer.last_read_at) : NaN;\n return {\n updated: Number.isFinite(updated) && (Number.isFinite(read) ? updated > read : now - updated < RECENT_MS),\n completed: !!card.viewer?.completed,\n deleted: !!card.deleted,\n disliked: card.viewer?.reaction === -1,\n };\n}\n\n/** Quiet marks for a card's status: updated, completed and deleted. */\nexport function CardStatusBadges({ card, className }: { card: Card; className?: string }) {\n const { t } = useMessages();\n const st = cardStatus(card);\n if (!st.updated && !st.completed && !st.deleted) return null;\n const icon = \"size-4 text-white drop-shadow-[0_0_3px_rgba(0,0,0,0.9)]\";\n return (\n <span className={cn(\"pointer-events-none flex items-center gap-1.5\", className)} data-ckui=\"card-status\">\n {st.deleted && <span className=\"rounded-md bg-background/85 px-1.5 py-0.5 text-xs font-medium text-foreground shadow-sm\">{t(\"contents.deleted\")}</span>}\n {st.updated && <HugeiconsIcon icon={SparklesIcon} strokeWidth={2} className={icon} role=\"img\" aria-label={t(\"contents.updated\")} />}\n {st.completed && <HugeiconsIcon icon={Tick02Icon} strokeWidth={2.5} className={icon} role=\"img\" aria-label={t(\"contents.completed\")} />}\n </span>\n );\n}\n\n/** The host's badges over a cover. */\nfunction Decorations({ card, decorate, aboveTitle }: { card: Card; decorate?: (card: Card) => ReactNode; aboveTitle?: boolean }) {\n const badges = decorate?.(card);\n if (badges == null || badges === false) return null;\n return (\n <span className={cn(\"pointer-events-none absolute inset-0\", aboveTitle && \"z-20\")} data-ckui=\"card-decorations\">\n {badges}\n </span>\n );\n}\n\nconst compact = (n: number, language?: string) => new Intl.NumberFormat(language, { notation: \"compact\", maximumFractionDigits: 1 }).format(n);\n\n/** Why a card is locked, in words (its price when it is on sale); null when it opens. */\nexport function lockLabel(m: Translator, a: CardAccess, tiers: readonly string[] = [], labels?: Labels): string | null {\n if (a.allowed) return null;\n if (a.unknown) return m.t(\"contents.unknownAccess\");\n const offer = a.offers?.[0];\n if (offer) return offer.target.kind === \"group\" && Number(offer.terms.amount) === 0 ? m.t(\"contents.joinFree\") : priceLabel(m, offer.terms);\n const tier = a.sell?.find((k) => tiers.includes(k));\n if (tier) return labelOf(labels, tier);\n if (a.sell?.some((k) => k.includes(\":group:\"))) return m.t(\"contents.members\");\n if (a.sell?.length) return m.t(\"contents.forSale\");\n return m.t(\"contents.locked\");\n}\n\n/** A link followed in-app through the provider's navigate. */\nfunction useLink(url: string | undefined, before?: () => void) {\n const navigate = useContext(ContentKitContext)?.navigate;\n return useCallback(\n (e: MouseEvent<HTMLAnchorElement>) => {\n before?.();\n if (!navigate || !url || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\n e.preventDefault();\n navigate(url);\n },\n [navigate, url, before],\n );\n}\n\n/** Which chips of a one-line list fit whole beside its \"+N\": `through` counts chips in list order (breakpoint-hidden ones included), `fitted` the ones that show, `squeezed` when even the first one does not fit and must shrink; null until measured. `chipsKey` names the chips rendered, so new ones are measured. */\nfunction useChipFit(list: HTMLUListElement | null, total: number, chipsKey: string) {\n const [fit, setFit] = useState<{ through: number; fitted: number; squeezed: boolean } | null>(null);\n useLayoutEffect(() => {\n if (!list) return;\n const view = list.ownerDocument.defaultView;\n const measure = () => {\n const gap = Number.parseFloat(view?.getComputedStyle(list).columnGap ?? \"\") || 0;\n const items = [...list.children] as HTMLElement[];\n const more = items.find((li) => li.dataset.ckui === \"card-chip-more\")?.offsetWidth ?? 0;\n let used = 0;\n let through = 0;\n let fitted = 0;\n let first = -1;\n const chips = items.filter((li) => li.dataset.ckui !== \"card-chip-more\");\n for (const [i, chip] of chips.entries()) {\n if (view?.getComputedStyle(chip).display === \"none\") continue;\n if (first < 0) first = i;\n const next = used + (fitted > 0 ? gap : 0) + chip.offsetWidth;\n if (next + (total > fitted + 1 && more > 0 ? gap + more : 0) > list.clientWidth) break;\n used = next;\n through = i + 1;\n fitted += 1;\n }\n const squeezed = fitted === 0 && first >= 0;\n if (squeezed) [through, fitted] = [first + 1, 1];\n setFit((f) => f?.through === through && f.fitted === fitted && f.squeezed === squeezed ? f : { through, fitted, squeezed });\n };\n measure();\n // Each chip too: a breakpoint hiding one, or a web font loading, changes a chip but not the list.\n const observer = typeof ResizeObserver === \"undefined\" ? undefined : new ResizeObserver(measure);\n observer?.observe(list);\n for (const li of list.children) observer?.observe(li);\n return () => observer?.disconnect();\n }, [list, total, chipsKey]);\n return fit;\n}\n\n/** One content in a listing: cover (a video the reader opens previews over it on hover), title, version, groups, counts, the reader's progress and why it is locked; what its kind's locked view hides is absent. */\nexport function ContentCard({ card, client, href, onOpen, shape = \"portrait\", aspectRatio, titlePosition = \"below\", loadMedia = true, groupKinds, chips = 3, chipOverflow = false, chipFit = false, filterEntity, entityStyle = \"chips\", entityLayout = \"inline\", metadata, showCounts = true, tiers, labels, owner = true, ownerHref, decorate, status = true, dimDisliked = true, children, className }: ContentCardProps) {\n const m = useMessages();\n const { presets } = usePresets({ client });\n const coverAspect = aspectRatio ?? ratio(presets.find((p) => p.kind === card.kind && p.name === \"cover\")?.aspect);\n const { t } = m;\n const languageName = useLanguageName();\n const url = href ? href(card) : card.path;\n const open = useCallback(() => onOpen?.(card), [onOpen, card]);\n const onClick = useLink(url, open);\n const lock = lockLabel(m, card.access, tiers, labels);\n const st = cardStatus(card);\n const dim = dimDisliked && st.disliked;\n const v = card.version;\n const progress = card.viewer?.completed ? 1 : card.viewer && card.viewer.progress_max > 0 ? Math.min(1, card.viewer.progress / card.viewer.progress_max) : 0;\n const kinds = groupKinds ?? Object.keys(card.entities);\n const limit = typeof chips === \"number\" ? { base: chips, lg: chips } : chips;\n const entityGroups = kinds.map((kind) => {\n const all = (card.entities[kind] ?? []).filter((entity) => !filterEntity || filterEntity(entity, kind));\n return { kind, entities: all.slice(0, Math.max(limit.base, limit.lg)), total: all.length };\n }).filter(({ entities }) => entities.length > 0);\n const minutes = v.duration_ms ? Math.round(v.duration_ms / 60_000) : 0;\n const selectedTraits = metadata?.traits;\n const traits = selectedTraits === false ? [] : Array.isArray(selectedTraits) ? v.traits.filter((trait) => selectedTraits.includes(trait)) : v.traits;\n const metaGroup = metadata?.groups?.map((kind) => (card.entities[kind] ?? []).find((entity) => !filterEntity || filterEntity(entity, kind))).find(Boolean);\n const meta: ReactNode[] = [\n ...(metadata?.languages === false ? [] : v.languages.map(languageName)),\n metadata?.pages !== false && v.pages ? m.plural(\"contents.pages\", v.pages) : null,\n metadata?.duration !== false && minutes ? m.plural(\"contents.minutes\", minutes) : null,\n metaGroup ? <MetaLink key=\"group\" url={metaGroup.path} name={metaGroup.name || metaGroup.slug} /> : null,\n ].filter(Boolean);\n const title = card.title || card.code;\n const entityClassName = cn(\"flex flex-wrap\", entityStyle === \"text\" ? \"gap-x-2 gap-y-1\" : \"gap-1\");\n // A chip past one breakpoint's limit shows only at the other.\n const atWidth = (i: number) => i < limit.base && i < limit.lg ? undefined : i < limit.base ? \"lg:hidden\" : \"hidden lg:list-item\";\n const overflowItems = (kind: string, total: number) => {\n if (!chipOverflow) return [];\n const base = total - Math.min(total, limit.base);\n const lg = total - Math.min(total, limit.lg);\n const more = (n: number, className?: string) => <li key={`${kind}:more:${className ?? \"\"}`} data-kind={kind} data-ckui=\"card-chip-more\" className={className}>\n <span aria-hidden><Chip name={`+${n}`} variant={entityStyle} /></span>\n <span className=\"sr-only\">{m.plural(\"contents.moreChips\", n)}</span>\n </li>;\n if (base === lg) return base > 0 ? [more(base)] : [];\n return [base > 0 && more(base, \"lg:hidden\"), lg > 0 && more(lg, \"hidden lg:list-item\")].filter(Boolean);\n };\n const entityItems = (kind: string, entities: EntityChip[], total: number) => [\n ...entities.map((entity, i) => <li key={`${kind}:${entity.id}`} data-kind={kind} className={atWidth(i)}><Chip url={entity.path} name={entity.name || entity.slug} variant={entityStyle} /></li>),\n ...overflowItems(kind, total),\n ];\n const [chipList, setChipList] = useState<HTMLUListElement | null>(null);\n const fitting = chipFit && entityLayout === \"inline\";\n const chipTotal = entityGroups.reduce((n, g) => n + g.total, 0);\n const chipsKey = `${entityStyle}:${limit.base}:${limit.lg}:${entityGroups.map(({ kind, entities }) => `${kind}=${entities.map((e) => e.id).join(\",\")}`).join(\";\")}`;\n const fit = useChipFit(fitting ? chipList : null, chipTotal, chipsKey);\n const fittedItems = () => {\n let index = 0;\n const items = entityGroups.flatMap(({ kind, entities }) => entities.map((entity, i) => {\n const at = index++;\n const out = fit !== null && at >= fit.through;\n const squeezed = fit?.squeezed && at === fit.through - 1;\n return <li key={`${kind}:${entity.id}`} data-kind={kind} className={cn(squeezed ? \"min-w-0 shrink [&>*]:block [&>*]:truncate\" : \"shrink-0\", atWidth(i), out && \"pointer-events-none invisible absolute\")} aria-hidden={out || undefined}><Chip url={entity.path} name={entity.name || entity.slug} variant={entityStyle} /></li>;\n }));\n if (!chipOverflow) return items;\n // Always present, so its width is kept free; shown once measured with chips left out.\n const more = fit === null ? chipTotal : chipTotal - fit.fitted;\n const hidden = fit === null || more <= 0;\n return [...items, <li key=\"more\" data-ckui=\"card-chip-more\" className={cn(\"shrink-0\", hidden && \"pointer-events-none invisible absolute\")} aria-hidden={hidden || undefined}>\n <span aria-hidden><Chip name={`+${more}`} variant={entityStyle} /></span>\n <span className=\"sr-only\">{m.plural(\"contents.moreChips\", more)}</span>\n </li>];\n };\n const entityList = entityGroups.length > 0 ? entityLayout === \"rows\" ? (\n <div className=\"grid gap-1\" data-ckui=\"card-groups\" data-layout=\"rows\">\n {entityGroups.map(({ kind, entities, total }) => <ul key={kind} className={entityClassName} aria-label={labelOf(labels, kind)} data-ckui=\"card-group-row\" data-kind={kind}>{entityItems(kind, entities, total)}</ul>)}\n </div>\n ) : (\n <ul ref={setChipList} className={fitting ? cn(\"relative flex flex-nowrap overflow-hidden\", entityStyle === \"text\" ? \"gap-x-2\" : \"gap-1\") : entityClassName} data-ckui=\"card-groups\" data-layout=\"inline\" data-fit={fitting ? \"\" : undefined}>\n {fitting ? fittedItems() : entityGroups.flatMap(({ kind, entities, total }) => entityItems(kind, entities, total))}\n </ul>\n ) : null;\n const metaLine = meta.length > 0 && <p className=\"line-clamp-2 text-xs break-words text-muted-foreground\" data-ckui=\"card-meta\">\n {meta.map((item, i) => <Fragment key={i}>\n {i > 0 && <><span className=\"sr-only\"> · </span><span className=\"mx-1.5 mb-px inline-block size-[3px] rounded-full bg-current align-middle\" aria-hidden data-ckui=\"card-meta-dot\" /></>}\n {item}\n </Fragment>)}\n </p>;\n const titleLink = <a href={url} onClick={onClick} className={cn(\"text-lg leading-snug font-medium break-words focus-visible:underline sm:text-xl\", titlePosition === \"cover\" ? \"absolute inset-x-0 bottom-0 z-10 bg-linear-to-t from-black/90 via-black/60 to-transparent px-3 pt-10 pb-3 text-white hover:underline\" : \"hover:underline\")} data-ckui=\"card-title\">\n <span className=\"line-clamp-2\">{title}</span>\n {lock && <span className=\"sr-only\"> ({lock})</span>}\n {dim && <span className=\"sr-only\"> ({t(\"contents.disliked\")})</span>}\n </a>;\n const [article, setArticle] = useState<HTMLElement | null>(null);\n return (\n <article ref={setArticle} className={cn(\"group/card relative grid content-start gap-2\", className)} data-ckui=\"content-card\" data-id={card.id} data-locked={lock ? \"\" : undefined} lang={card.language || undefined}>\n <div className=\"relative overflow-hidden rounded-lg\" data-ckui=\"card-cover\">\n <a href={url} onClick={onClick} className=\"relative block overflow-hidden rounded-lg bg-muted\" tabIndex={-1} aria-hidden>\n <div data-ckui=\"card-cover-frame\" data-disliked={dim || undefined} className={cn(coverAspect === undefined && (shape === \"landscape\" ? \"aspect-video\" : \"aspect-[3/4]\"), \"transition-[opacity,filter]\", dim && \"opacity-50 brightness-75\", st.deleted && \"opacity-60 grayscale\")} style={coverAspect === undefined ? undefined : { aspectRatio: coverAspect }}>\n {loadMedia ? <RenditionImg\n outputs={card.cover}\n alt=\"\"\n fit=\"cover\"\n skeleton\n loading=\"lazy\"\n className=\"size-full transition-transform group-hover/card:scale-[1.02]\"\n fallback={<span className=\"grid size-full place-items-center text-2xl font-semibold text-muted-foreground\">{title.charAt(0).toUpperCase()}</span>}\n /> : <span className=\"block size-full bg-muted\" aria-hidden />}\n </div>\n {loadMedia && v.hls && <HoverPreview item={{ kind: card.kind, id: card.id, version: v.id }} dir={v.hls} duration={v.duration_ms ? v.duration_ms / 1000 : undefined} target={article} />}\n {lock && (\n <span 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\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n )}\n {progress > 0 && (\n <span className={cn(\"absolute inset-x-0 bottom-0 h-1 bg-background/60\", titlePosition === \"cover\" && \"z-20\")} data-ckui=\"card-progress\">\n <span className=\"block h-full bg-primary\" style={{ width: `${Math.round(progress * 100)}%` }} />\n </span>\n )}\n </a>\n {titlePosition === \"cover\" && titleLink}\n {status && <CardStatusBadges card={card} className={cn(\"absolute right-2 z-20\", titlePosition === \"cover\" ? \"top-2\" : \"bottom-2.5\")} />}\n {loadMedia && <Decorations card={card} decorate={decorate} aboveTitle={titlePosition === \"cover\"} />}\n </div>\n <div className=\"grid gap-1\">\n {titlePosition === \"below\" && titleLink}\n {entityStyle === \"text\" && entityList}\n {owner && card.owner && <OwnerChip group={card.owner} href={ownerHref} loadMedia={loadMedia} />}\n {traits.length > 0 && <div className=\"flex flex-wrap gap-1\" data-ckui=\"card-traits\">\n {traits.map((trait) => <Badge key={trait} variant=\"outline\" className=\"h-auto rounded-md text-sm text-muted-foreground\" data-ckui=\"card-trait\" data-trait={trait}>{labelOf(labels, trait)}</Badge>)}\n </div>}\n {metaLine}\n {!card.access.allowed && card.access.free_after && <p className=\"text-xs text-muted-foreground\">{t(\"contents.freeFrom\", { date: formatDate(m, card.access.free_after, false) })}</p>}\n {entityStyle === \"chips\" && entityList}\n {showCounts && (card.counts.favorites !== undefined || card.counts.likes !== undefined) && (\n <p className=\"flex items-center gap-3 text-xs text-muted-foreground tabular-nums\" data-ckui=\"card-counts\">\n {card.counts.favorites !== undefined && (\n <span className=\"inline-flex items-center gap-1\" title={t(\"contents.favorites\")}>\n <HugeiconsIcon icon={FavouriteIcon} strokeWidth={2} className={cn(\"size-3.5\", card.viewer?.favorited && \"fill-current text-primary\")} aria-hidden />\n <span className=\"sr-only\">{t(\"contents.favorites\")}</span>\n {compact(card.counts.favorites, m.language)}\n </span>\n )}\n {card.counts.likes !== undefined && (\n <span className=\"inline-flex items-center gap-1\" title={t(\"contents.likes\")}>\n <HugeiconsIcon icon={ThumbsUpIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n <span className=\"sr-only\">{t(\"contents.likes\")}</span>\n {compact(card.counts.likes, m.language)}\n </span>\n )}\n </p>\n )}\n {children}\n </div>\n </article>\n );\n}\n\n/** The group that owns a content (a channel): its avatar and name, linking to its page. */\nexport function OwnerChip({ group, href, className, loadMedia = true }: { group: GroupLike; href?: (g: GroupLike) => string | undefined; className?: string; loadMedia?: boolean }) {\n return (\n <span className={cn(\"flex min-w-0 text-xs text-muted-foreground\", className)} data-ckui=\"card-owner\" data-id={group.id}>\n <GroupLink group={group} href={href} className=\"inline-flex min-w-0 items-center gap-1.5 hover:text-foreground\">\n {loadMedia ? <GroupAvatar group={group} className=\"size-5 shrink-0 text-[0.625rem]\" /> : <span className=\"size-5 shrink-0 rounded-full bg-muted\" aria-hidden />}\n <span className=\"truncate\">{group.name || group.slug}</span>\n </GroupLink>\n </span>\n );\n}\n\nfunction MetaLink({ url, name }: { url?: string; name: string }) {\n const onClick = useLink(url);\n return url ? <a href={url} onClick={onClick} className=\"hover:text-foreground hover:underline\">{name}</a> : <span>{name}</span>;\n}\n\nfunction Chip({ url, name, variant = \"chips\" }: { url?: string; name: string; variant?: \"chips\" | \"text\" }) {\n const onClick = useLink(url);\n const cls = variant === \"text\" ? \"text-sm text-muted-foreground\" : \"inline-block rounded-md border border-border px-1.5 py-0.5 text-sm text-muted-foreground\";\n return url ? (\n <a href={url} onClick={onClick} className={cn(cls, \"hover:text-foreground\", variant === \"text\" ? \"hover:underline\" : \"hover:bg-muted\")}>\n {name}\n </a>\n ) : (\n <span className={cls}>{name}</span>\n );\n}\n\nexport interface PostCardProps extends Omit<ContentCardProps, \"shape\" | \"aspectRatio\" | \"titlePosition\" | \"chips\" | \"groupKinds\" | \"filterEntity\" | \"entityStyle\" | \"entityLayout\" | \"metadata\"> {\n /** Above the title: the post's channel or author; default its owner group's chip. */\n byline?: ReactNode;\n}\n\n/** A post in a feed: a wide cover, its title, when it was published and what unlocking it costs. */\nexport function PostCard({ card, href, onOpen, tiers, labels, byline, owner = true, ownerHref, loadMedia = true, showCounts = true, decorate, children, className }: PostCardProps) {\n const m = useMessages();\n const url = href ? href(card) : card.path;\n const open = useCallback(() => onOpen?.(card), [onOpen, card]);\n const onClick = useLink(url, open);\n const lock = lockLabel(m, card.access, tiers, labels);\n const title = card.title || card.code;\n return (\n <article className={cn(\"relative grid gap-3 rounded-lg border border-border p-3\", className)} data-ckui=\"post-card\" data-id={card.id} data-locked={lock ? \"\" : undefined} lang={card.language || undefined}>\n {byline ?? (owner && card.owner && <OwnerChip group={card.owner} href={ownerHref} loadMedia={loadMedia} className=\"text-sm\" />)}\n <a href={url} onClick={onClick} className=\"grid gap-2\">\n <span className=\"font-medium break-words hover:underline\" data-ckui=\"card-title\">\n {title}\n </span>\n {card.cover.length > 0 && (\n <span className=\"relative block aspect-video overflow-hidden rounded-md bg-muted\">\n {loadMedia && <RenditionImg outputs={card.cover} alt=\"\" fit=\"cover\" skeleton loading=\"lazy\" className={cn(\"size-full\", lock && \"blur-md\")} />}\n {loadMedia && <Decorations card={card} decorate={decorate} />}\n {lock && (\n <span className=\"absolute inset-0 grid place-items-center\">\n <span className=\"inline-flex items-center gap-1 rounded-md bg-background/85 px-2 py-1 text-xs font-medium shadow-sm\" data-ckui=\"card-lock\">\n <HugeiconsIcon icon={LockIcon} strokeWidth={2} className=\"size-3.5\" aria-hidden />\n {lock}\n </span>\n </span>\n )}\n </span>\n )}\n </a>\n <p className=\"flex flex-wrap items-center gap-3 text-xs text-muted-foreground\">\n {card.published_at && <RelativeTime at={card.published_at} />}\n {!card.cover.length && lock && <span data-ckui=\"card-lock\">{lock}</span>}\n {showCounts && card.counts.favorites !== undefined && (\n <span className=\"inline-flex items-center gap-1 tabular-nums\" data-ckui=\"card-counts\">\n <HugeiconsIcon icon={FavouriteIcon} strokeWidth={2} className={cn(\"size-3.5\", card.viewer?.favorited && \"fill-current text-primary\")} aria-hidden />\n <span className=\"sr-only\">{m.t(\"contents.favorites\")}</span>\n {compact(card.counts.favorites, m.language)}\n </span>\n )}\n </p>\n {children}\n </article>\n );\n}\n"],"mappings":"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"}
|
|
@@ -7,10 +7,10 @@ import { useNearViewport as a } from "../../react/viewport.js";
|
|
|
7
7
|
import { Skeleton as o } from "../../ui/skeleton.js";
|
|
8
8
|
import { contentError as s } from "../social/parts.js";
|
|
9
9
|
import { useDailyReleases as c, useExposure as l } from "../../react/discovery.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
10
|
+
import { ContentFilterDialog as u } from "./filter-dialog.js";
|
|
11
|
+
import { useReleaseReveal as d } from "./release-reveal.js";
|
|
12
|
+
import { ContentSummaryTile as f } from "./summary-tile.js";
|
|
13
|
+
import { ContentCard as p } from "./card.js";
|
|
14
14
|
import { cardGridLayout as m } from "./grid.js";
|
|
15
15
|
import { useCallback as h, useContext as g, useId as _, useMemo as v, useRef as y, useState as b } from "react";
|
|
16
16
|
import { jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
@@ -108,7 +108,7 @@ function D({ query: a, initialVisible: l = 30, revealStep: u = 30, collapsePremi
|
|
|
108
108
|
}, M.revision);
|
|
109
109
|
}
|
|
110
110
|
var O = (e) => !e.access.allowed && e.access.level === "tier";
|
|
111
|
-
function k({ day: t, initial: n, step: r, pending: a, cardProps: s, collapsePremium: c, summaryLabels:
|
|
111
|
+
function k({ day: t, initial: n, step: r, pending: a, cardProps: s, collapsePremium: c, summaryLabels: p, language: h, kinds: g }) {
|
|
112
112
|
let E = e(), D = _(), [k, j] = b(n > 0 || !t.contents.length && t.filtered_count > 0), [M, N] = b(n), [P, F] = b(!1), [I, L] = b(!1), R = y(null), z = y(null), B = v(() => t.contents.slice(0, M), [t.contents, M]), V = v(() => t.contents.filter(O).length, [t.contents]), H = g?.[0], U = v(() => {
|
|
113
113
|
let e = !1;
|
|
114
114
|
return B.flatMap((t, n) => !c || P || !O(t) ? [{
|
|
@@ -137,7 +137,7 @@ function k({ day: t, initial: n, step: r, pending: a, cardProps: s, collapsePrem
|
|
|
137
137
|
surface: s.surface ?? "releases",
|
|
138
138
|
render: t.date,
|
|
139
139
|
client: s.client
|
|
140
|
-
}), K = m(s.grid), q =
|
|
140
|
+
}), K = m(s.grid), q = d(`${M}|${U.map((e) => e.key).join("\0")}`), J = new Intl.DateTimeFormat(E.language, {
|
|
141
141
|
dateStyle: "full",
|
|
142
142
|
timeZone: "UTC"
|
|
143
143
|
}).format(/* @__PURE__ */ new Date(`${t.date}T12:00:00Z`)), Y = () => {
|
|
@@ -184,14 +184,14 @@ function k({ day: t, initial: n, step: r, pending: a, cardProps: s, collapsePrem
|
|
|
184
184
|
ref: q.grid,
|
|
185
185
|
className: C(K.className, "pt-2"),
|
|
186
186
|
style: K.style,
|
|
187
|
-
children: [U.map((e) => e.summary ? /* @__PURE__ */ x(
|
|
187
|
+
children: [U.map((e) => e.summary ? /* @__PURE__ */ x(f, {
|
|
188
188
|
tileKey: e.key,
|
|
189
189
|
contentKind: g?.[0] ?? e.card.kind,
|
|
190
190
|
open: k && !a,
|
|
191
191
|
preview: e.card,
|
|
192
192
|
cardProps: s,
|
|
193
193
|
kind: "premium",
|
|
194
|
-
label:
|
|
194
|
+
label: p?.premium?.(V) ?? E.plural("releases.premiumMore", V),
|
|
195
195
|
onClick: X
|
|
196
196
|
}, e.key) : /* @__PURE__ */ x(A, {
|
|
197
197
|
card: e.card,
|
|
@@ -199,13 +199,13 @@ function k({ day: t, initial: n, step: r, pending: a, cardProps: s, collapsePrem
|
|
|
199
199
|
position: e.position,
|
|
200
200
|
exposure: G,
|
|
201
201
|
cardProps: s
|
|
202
|
-
}, e.key)), t.filtered_count > 0 && /* @__PURE__ */ x(
|
|
202
|
+
}, e.key)), t.filtered_count > 0 && /* @__PURE__ */ x(f, {
|
|
203
203
|
tileKey: "filtered-summary",
|
|
204
204
|
contentKind: H,
|
|
205
205
|
open: k && !a,
|
|
206
206
|
cardProps: s,
|
|
207
207
|
kind: "filtered",
|
|
208
|
-
label:
|
|
208
|
+
label: p?.filtered?.(t.filtered_count) ?? E.plural("releases.filteredMore", t.filtered_count),
|
|
209
209
|
onClick: (e) => {
|
|
210
210
|
R.current = e.currentTarget, L(!0);
|
|
211
211
|
}
|
|
@@ -223,7 +223,7 @@ function k({ day: t, initial: n, step: r, pending: a, cardProps: s, collapsePrem
|
|
|
223
223
|
})]
|
|
224
224
|
})
|
|
225
225
|
}),
|
|
226
|
-
/* @__PURE__ */ x(
|
|
226
|
+
/* @__PURE__ */ x(u, {
|
|
227
227
|
returnFocus: () => !a && R.current?.isConnected ? R.current : z.current,
|
|
228
228
|
open: I,
|
|
229
229
|
onOpenChange: L,
|
|
@@ -251,7 +251,7 @@ function A({ card: e, open: t, position: n, exposure: r, cardProps: i }) {
|
|
|
251
251
|
return /* @__PURE__ */ x("li", {
|
|
252
252
|
ref: l,
|
|
253
253
|
"data-release-key": e.id,
|
|
254
|
-
children: /* @__PURE__ */ x(
|
|
254
|
+
children: /* @__PURE__ */ x(p, {
|
|
255
255
|
card: e,
|
|
256
256
|
client: i.client,
|
|
257
257
|
href: i.href,
|
|
@@ -261,6 +261,8 @@ function A({ card: e, open: t, position: n, exposure: r, cardProps: i }) {
|
|
|
261
261
|
loadMedia: t && s,
|
|
262
262
|
groupKinds: i.groupKinds,
|
|
263
263
|
chips: i.chips,
|
|
264
|
+
chipOverflow: i.chipOverflow,
|
|
265
|
+
chipFit: i.chipFit,
|
|
264
266
|
filterEntity: i.filterEntity,
|
|
265
267
|
entityStyle: i.entityStyle,
|
|
266
268
|
entityLayout: i.entityLayout,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daily-releases.js","names":[],"sources":["../../../src/components/content/daily-releases.tsx"],"sourcesContent":["import { ArrowDown01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useContext, useId, useMemo, useState, useRef, type MouseEvent } from \"react\";\nimport type { DailyReleasesQuery } from \"../../client/discovery.js\";\nimport type { Card, ReleaseDay } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useDailyReleases, useExposure } from \"../../react/discovery.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useContentScope } from \"../../react/use-resource.js\";\nimport { useNearViewport } from \"../../react/viewport.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { contentError } from \"../social/parts.js\";\nimport { ContentSummaryTile } from \"./summary-tile.js\";\nimport { ContentFilterDialog } from \"./filter-dialog.js\";\nimport { useReleaseReveal } from \"./release-reveal.js\";\nimport { ContentCard } from \"./card.js\";\nimport { cardGridLayout, type CardListProps } from \"./grid.js\";\n\nexport interface DailyReleasesProps extends Omit<CardListProps, \"infinite\" | \"pageSize\" | \"renderCard\" | \"loadMedia\"> {\n /** One complete calendar window. Defaults to 10 days in the browser's time zone. */\n query?: DailyReleasesQuery;\n /** Cards initially revealed across the whole window, allocated newest first. Default 30. */\n initialVisible?: number;\n /** Collapse locked tier previews per day; expansion never grants access. Default false. */\n collapsePremium?: boolean;\n /** Host-specific nouns; defaults use localized generic item labels. */\n summaryLabels?: { premium?: (count: number) => string; filtered?: (count: number) => string };\n /** Additional cached cards revealed by a day's Show more button. Default 30. */\n revealStep?: number;\n}\n\nconst count = (value: number, fallback: number) => Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;\n\n/**\n * One metadata request for a calendar window, with no requests when a day is\n * opened or expanded. Only revealed, visible cards mount images and previews.\n */\nexport function DailyReleases({ query, initialVisible = 30, revealStep = 30, collapsePremium = false, summaryLabels, ...p }: DailyReleasesProps) {\n const m = useMessages();\n const { client, viewer } = useContentScope(p.client);\n const context = useContext(ContentKitContext);\n const request = useMemo(() => ({ days: 10, time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone, ...query }), [query]);\n const releases = useDailyReleases(request, { client: p.client });\n const layout = cardGridLayout(p.grid);\n const requestKey = JSON.stringify(request);\n // Guest preference changes create a fresh read scope. Keep the open day/dialog\n // while it loads, but never show the old cards or carry data across accounts.\n const identity = useMemo(() => ({ client, viewer, language: context?.language, accessRevision: context?.accessRevision, requestKey }), [client, viewer, context?.language, context?.accessRevision, requestKey]);\n const [previous, setPrevious] = useState({ identity, data: releases.data, revision: 0 });\n if (previous.identity !== identity || (releases.data && previous.data !== releases.data)) setPrevious({ identity, data: releases.data, revision: previous.revision + (previous.identity === identity ? 0 : 1) });\n const data = releases.data ?? (previous.identity === identity ? previous.data : undefined);\n const pending = !releases.data && Boolean(data);\n const days = useMemo(() => data?.days.filter((day) => (day.contents.length || day.filtered_count > 0)).sort((a, b) => b.date.localeCompare(a.date)) ?? [], [data]);\n let remaining = count(initialVisible, 30);\n return (\n <ContentKitUiRoot key={previous.revision} appearance={p.appearance} className={cn(\"grid gap-6 text-sm\", p.className)} data-ckui=\"daily-releases\" aria-busy={releases.loading}>\n {data && releases.error && <div role=\"alert\" className=\"grid justify-items-start gap-2\"><p className=\"text-destructive\">{contentError(m, releases.error)}</p><Button type=\"button\" variant=\"outline\" onClick={releases.reload}>{m.t(\"common.retry\")}</Button></div>}\n {releases.loading && !data ? <div className={layout.className} style={layout.style}>{Array.from({ length: 6 }, (_, i) => <Skeleton key={i} className={p.aspectRatio === undefined ? p.shape === \"landscape\" ? \"aspect-video\" : \"aspect-[3/4]\" : undefined} style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }} />)}</div> : releases.error && !data ? (\n <div role=\"alert\" className=\"grid justify-items-start gap-2\">\n <p className=\"text-destructive\">{contentError(m, releases.error)}</p>\n <Button size=\"sm\" variant=\"outline\" onClick={releases.reload}>{m.t(\"common.retry\")}</Button>\n </div>\n ) : !days.length ? <p role=\"status\" className=\"text-muted-foreground\">{p.empty ?? m.t(\"contents.empty\")}</p> : days.map((day) => {\n const initial = Math.min(remaining, day.contents.length);\n remaining -= initial;\n return <ReleaseDaySection key={JSON.stringify([requestKey, data?.from_date, day.date])} day={day} pending={pending} collapsePremium={collapsePremium} summaryLabels={summaryLabels} language={request.display_language ?? request.language} kinds={request.kind ? [request.kind] : undefined} initial={initial} step={Math.max(1, count(revealStep, 30))} cardProps={p} />;\n })}\n </ContentKitUiRoot>\n );\n}\n\ntype ReleaseCardProps = Omit<DailyReleasesProps, \"query\" | \"initialVisible\" | \"revealStep\" | \"collapsePremium\" | \"summaryLabels\">;\nconst lockedPremium = (card: Card) => !card.access.allowed && card.access.level === \"tier\";\n\nfunction ReleaseDaySection({ day, initial, step, pending, cardProps: p, collapsePremium, summaryLabels, language, kinds }: {\n day: ReleaseDay; initial: number; step: number; pending: boolean; cardProps: ReleaseCardProps;\n collapsePremium: boolean; summaryLabels?: DailyReleasesProps[\"summaryLabels\"]; language?: string; kinds?: string[];\n}) {\n const m = useMessages();\n const id = useId();\n const [open, setOpen] = useState(initial > 0 || (!day.contents.length && day.filtered_count > 0));\n const [visible, setVisible] = useState(initial);\n const [premiumExpanded, setPremiumExpanded] = useState(false);\n const [filtersOpen, setFiltersOpen] = useState(false);\n const filterTrigger = useRef<HTMLElement | null>(null);\n const dayHeading = useRef<HTMLButtonElement>(null);\n const cards = useMemo(() => day.contents.slice(0, visible), [day.contents, visible]);\n const premiumCount = useMemo(() => day.contents.filter(lockedPremium).length, [day.contents]);\n const summaryContentKind = kinds?.[0];\n const entries = useMemo(() => {\n let inserted = false;\n return cards.flatMap((card, i): { key: string; card: Card; summary: boolean; position: number }[] => {\n if (!collapsePremium || premiumExpanded || !lockedPremium(card)) return [{ key: card.id, card, summary: false, position: i + 1 }];\n if (inserted) return [];\n inserted = true;\n return [{ key: \"premium-summary\", card, summary: true, position: i + 1 }];\n });\n }, [cards, collapsePremium, premiumExpanded]);\n const exposed = useMemo(() => open && !pending ? entries.filter((entry) => !entry.summary).map((entry) => ({ ...entry.card, position: entry.position })) : [], [open, pending, entries]);\n const exposure = useExposure(exposed, { surface: p.surface ?? \"releases\", render: day.date, client: p.client });\n const layout = cardGridLayout(p.grid);\n const reveal = useReleaseReveal(`${visible}|${entries.map((entry) => entry.key).join(\"\\0\")}`);\n const label = new Intl.DateTimeFormat(m.language, { dateStyle: \"full\", timeZone: \"UTC\" }).format(new Date(`${day.date}T12:00:00Z`));\n const toggle = () => {\n if (!open && !visible) setVisible(Math.min(step, day.contents.length));\n setOpen(!open);\n };\n const expandPremium = (event: MouseEvent<HTMLButtonElement>) => {\n reveal.prepare(event.detail === 0);\n setPremiumExpanded(true);\n };\n return (\n <section data-ckui=\"release-day\" data-date={day.date}>\n <h2>\n <Button ref={dayHeading} id={`${id}-heading`} type=\"button\" variant=\"ghost\" className=\"h-auto w-full justify-start gap-3 px-0 py-3 text-start text-lg font-semibold\" aria-expanded={open} aria-controls={`${id}-contents`} onClick={toggle}>\n <HugeiconsIcon icon={ArrowDown01Icon} strokeWidth={2} className={cn(\"size-5 shrink-0 transition-transform duration-200 motion-reduce:transition-none\", !open && \"-rotate-90\")} aria-hidden />\n <time dateTime={day.date}>{label}</time>\n </Button>\n </h2>\n {open && pending && <Skeleton className=\"h-24 w-full\" />}\n <div hidden={pending} style={pending ? { display: \"none\" } : undefined} id={`${id}-contents`} role=\"region\" aria-labelledby={`${id}-heading`} aria-hidden={!open || pending} inert={!open || pending} className={cn(\"grid transition-[grid-template-rows,opacity] duration-250 ease-out motion-reduce:transition-none\", open ? \"grid-rows-[1fr] opacity-100\" : \"grid-rows-[0fr] opacity-0\")}>\n <div className=\"min-h-0 overflow-hidden\">\n <ol ref={reveal.grid} className={cn(layout.className, \"pt-2\")} style={layout.style}>\n {entries.map((entry) => entry.summary ?\n <ContentSummaryTile key={entry.key} tileKey={entry.key} contentKind={kinds?.[0] ?? entry.card.kind} open={open && !pending} preview={entry.card} cardProps={p} kind=\"premium\" label={summaryLabels?.premium?.(premiumCount) ?? m.plural(\"releases.premiumMore\", premiumCount)} onClick={expandPremium} /> :\n <ReleaseCard key={entry.key} card={entry.card} open={open && !pending} position={entry.position} exposure={exposure} cardProps={p} />)}\n {day.filtered_count > 0 && <ContentSummaryTile tileKey=\"filtered-summary\" contentKind={summaryContentKind} open={open && !pending} cardProps={p} kind=\"filtered\" label={summaryLabels?.filtered?.(day.filtered_count) ?? m.plural(\"releases.filteredMore\", day.filtered_count)} onClick={(event) => { filterTrigger.current = event.currentTarget; setFiltersOpen(true); }} />}\n </ol>\n {visible < day.contents.length && <div className=\"flex justify-center pt-5 pb-1\"><Button type=\"button\" variant=\"outline\" onClick={(event) => { reveal.prepare(event.detail === 0); setVisible((n) => Math.min(n + step, day.contents.length)); }}>{m.t(\"contents.showMore\")}</Button></div>}\n </div>\n </div>\n <ContentFilterDialog returnFocus={() => !pending && filterTrigger.current?.isConnected ? filterTrigger.current : dayHeading.current} open={filtersOpen} onOpenChange={setFiltersOpen} client={p.client} kinds={kinds} language={language} counts={day.filtered_entities ?? []} description={m.t(\"releases.filterContext\", { date: label })} appearance={p.appearance} />\n </section>\n );\n}\n\n\nfunction ReleaseCard({ card, open, position, exposure, cardProps: p }: { card: Card; open: boolean; position: number; exposure: ReturnType<typeof useExposure>; cardProps: ReleaseCardProps }) {\n const [viewport, visible] = useNearViewport({ margin: \"0px\", leaveMargin: \"1200px 0px\" });\n const tracked = useMemo(() => exposure.track(card.id, position), [exposure, card.id, position]);\n const ref = useCallback((element: HTMLLIElement | null) => { viewport(element); tracked(element); }, [viewport, tracked]);\n return <li ref={ref} data-release-key={card.id}>\n <ContentCard card={card} client={p.client} href={p.href} shape={p.shape} aspectRatio={p.aspectRatio} titlePosition={p.titlePosition} loadMedia={open && visible} groupKinds={p.groupKinds} chips={p.chips} filterEntity={p.filterEntity} entityStyle={p.entityStyle} entityLayout={p.entityLayout} metadata={p.metadata} showCounts={p.showCounts} tiers={p.tiers} labels={p.labels} owner={p.owner} ownerHref={p.ownerHref} decorate={p.decorate} onOpen={() => exposure.click(card.id, position)} />\n </li>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"daily-releases.js","names":[],"sources":["../../../src/components/content/daily-releases.tsx"],"sourcesContent":["import { ArrowDown01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useCallback, useContext, useId, useMemo, useState, useRef, type MouseEvent } from \"react\";\nimport type { DailyReleasesQuery } from \"../../client/discovery.js\";\nimport type { Card, ReleaseDay } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useDailyReleases, useExposure } from \"../../react/discovery.js\";\nimport { ContentKitContext } from \"../../react/context.js\";\nimport { useContentScope } from \"../../react/use-resource.js\";\nimport { useNearViewport } from \"../../react/viewport.js\";\nimport { ContentKitUiRoot } from \"../../scope.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Skeleton } from \"#ckui/ui/skeleton\";\nimport { contentError } from \"../social/parts.js\";\nimport { ContentSummaryTile } from \"./summary-tile.js\";\nimport { ContentFilterDialog } from \"./filter-dialog.js\";\nimport { useReleaseReveal } from \"./release-reveal.js\";\nimport { ContentCard } from \"./card.js\";\nimport { cardGridLayout, type CardListProps } from \"./grid.js\";\n\nexport interface DailyReleasesProps extends Omit<CardListProps, \"infinite\" | \"pageSize\" | \"renderCard\" | \"loadMedia\"> {\n /** One complete calendar window. Defaults to 10 days in the browser's time zone. */\n query?: DailyReleasesQuery;\n /** Cards initially revealed across the whole window, allocated newest first. Default 30. */\n initialVisible?: number;\n /** Collapse locked tier previews per day; expansion never grants access. Default false. */\n collapsePremium?: boolean;\n /** Host-specific nouns; defaults use localized generic item labels. */\n summaryLabels?: { premium?: (count: number) => string; filtered?: (count: number) => string };\n /** Additional cached cards revealed by a day's Show more button. Default 30. */\n revealStep?: number;\n}\n\nconst count = (value: number, fallback: number) => Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;\n\n/**\n * One metadata request for a calendar window, with no requests when a day is\n * opened or expanded. Only revealed, visible cards mount images and previews.\n */\nexport function DailyReleases({ query, initialVisible = 30, revealStep = 30, collapsePremium = false, summaryLabels, ...p }: DailyReleasesProps) {\n const m = useMessages();\n const { client, viewer } = useContentScope(p.client);\n const context = useContext(ContentKitContext);\n const request = useMemo(() => ({ days: 10, time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone, ...query }), [query]);\n const releases = useDailyReleases(request, { client: p.client });\n const layout = cardGridLayout(p.grid);\n const requestKey = JSON.stringify(request);\n // Guest preference changes create a fresh read scope. Keep the open day/dialog\n // while it loads, but never show the old cards or carry data across accounts.\n const identity = useMemo(() => ({ client, viewer, language: context?.language, accessRevision: context?.accessRevision, requestKey }), [client, viewer, context?.language, context?.accessRevision, requestKey]);\n const [previous, setPrevious] = useState({ identity, data: releases.data, revision: 0 });\n if (previous.identity !== identity || (releases.data && previous.data !== releases.data)) setPrevious({ identity, data: releases.data, revision: previous.revision + (previous.identity === identity ? 0 : 1) });\n const data = releases.data ?? (previous.identity === identity ? previous.data : undefined);\n const pending = !releases.data && Boolean(data);\n const days = useMemo(() => data?.days.filter((day) => (day.contents.length || day.filtered_count > 0)).sort((a, b) => b.date.localeCompare(a.date)) ?? [], [data]);\n let remaining = count(initialVisible, 30);\n return (\n <ContentKitUiRoot key={previous.revision} appearance={p.appearance} className={cn(\"grid gap-6 text-sm\", p.className)} data-ckui=\"daily-releases\" aria-busy={releases.loading}>\n {data && releases.error && <div role=\"alert\" className=\"grid justify-items-start gap-2\"><p className=\"text-destructive\">{contentError(m, releases.error)}</p><Button type=\"button\" variant=\"outline\" onClick={releases.reload}>{m.t(\"common.retry\")}</Button></div>}\n {releases.loading && !data ? <div className={layout.className} style={layout.style}>{Array.from({ length: 6 }, (_, i) => <Skeleton key={i} className={p.aspectRatio === undefined ? p.shape === \"landscape\" ? \"aspect-video\" : \"aspect-[3/4]\" : undefined} style={p.aspectRatio === undefined ? undefined : { aspectRatio: p.aspectRatio }} />)}</div> : releases.error && !data ? (\n <div role=\"alert\" className=\"grid justify-items-start gap-2\">\n <p className=\"text-destructive\">{contentError(m, releases.error)}</p>\n <Button size=\"sm\" variant=\"outline\" onClick={releases.reload}>{m.t(\"common.retry\")}</Button>\n </div>\n ) : !days.length ? <p role=\"status\" className=\"text-muted-foreground\">{p.empty ?? m.t(\"contents.empty\")}</p> : days.map((day) => {\n const initial = Math.min(remaining, day.contents.length);\n remaining -= initial;\n return <ReleaseDaySection key={JSON.stringify([requestKey, data?.from_date, day.date])} day={day} pending={pending} collapsePremium={collapsePremium} summaryLabels={summaryLabels} language={request.display_language ?? request.language} kinds={request.kind ? [request.kind] : undefined} initial={initial} step={Math.max(1, count(revealStep, 30))} cardProps={p} />;\n })}\n </ContentKitUiRoot>\n );\n}\n\ntype ReleaseCardProps = Omit<DailyReleasesProps, \"query\" | \"initialVisible\" | \"revealStep\" | \"collapsePremium\" | \"summaryLabels\">;\nconst lockedPremium = (card: Card) => !card.access.allowed && card.access.level === \"tier\";\n\nfunction ReleaseDaySection({ day, initial, step, pending, cardProps: p, collapsePremium, summaryLabels, language, kinds }: {\n day: ReleaseDay; initial: number; step: number; pending: boolean; cardProps: ReleaseCardProps;\n collapsePremium: boolean; summaryLabels?: DailyReleasesProps[\"summaryLabels\"]; language?: string; kinds?: string[];\n}) {\n const m = useMessages();\n const id = useId();\n const [open, setOpen] = useState(initial > 0 || (!day.contents.length && day.filtered_count > 0));\n const [visible, setVisible] = useState(initial);\n const [premiumExpanded, setPremiumExpanded] = useState(false);\n const [filtersOpen, setFiltersOpen] = useState(false);\n const filterTrigger = useRef<HTMLElement | null>(null);\n const dayHeading = useRef<HTMLButtonElement>(null);\n const cards = useMemo(() => day.contents.slice(0, visible), [day.contents, visible]);\n const premiumCount = useMemo(() => day.contents.filter(lockedPremium).length, [day.contents]);\n const summaryContentKind = kinds?.[0];\n const entries = useMemo(() => {\n let inserted = false;\n return cards.flatMap((card, i): { key: string; card: Card; summary: boolean; position: number }[] => {\n if (!collapsePremium || premiumExpanded || !lockedPremium(card)) return [{ key: card.id, card, summary: false, position: i + 1 }];\n if (inserted) return [];\n inserted = true;\n return [{ key: \"premium-summary\", card, summary: true, position: i + 1 }];\n });\n }, [cards, collapsePremium, premiumExpanded]);\n const exposed = useMemo(() => open && !pending ? entries.filter((entry) => !entry.summary).map((entry) => ({ ...entry.card, position: entry.position })) : [], [open, pending, entries]);\n const exposure = useExposure(exposed, { surface: p.surface ?? \"releases\", render: day.date, client: p.client });\n const layout = cardGridLayout(p.grid);\n const reveal = useReleaseReveal(`${visible}|${entries.map((entry) => entry.key).join(\"\\0\")}`);\n const label = new Intl.DateTimeFormat(m.language, { dateStyle: \"full\", timeZone: \"UTC\" }).format(new Date(`${day.date}T12:00:00Z`));\n const toggle = () => {\n if (!open && !visible) setVisible(Math.min(step, day.contents.length));\n setOpen(!open);\n };\n const expandPremium = (event: MouseEvent<HTMLButtonElement>) => {\n reveal.prepare(event.detail === 0);\n setPremiumExpanded(true);\n };\n return (\n <section data-ckui=\"release-day\" data-date={day.date}>\n <h2>\n <Button ref={dayHeading} id={`${id}-heading`} type=\"button\" variant=\"ghost\" className=\"h-auto w-full justify-start gap-3 px-0 py-3 text-start text-lg font-semibold\" aria-expanded={open} aria-controls={`${id}-contents`} onClick={toggle}>\n <HugeiconsIcon icon={ArrowDown01Icon} strokeWidth={2} className={cn(\"size-5 shrink-0 transition-transform duration-200 motion-reduce:transition-none\", !open && \"-rotate-90\")} aria-hidden />\n <time dateTime={day.date}>{label}</time>\n </Button>\n </h2>\n {open && pending && <Skeleton className=\"h-24 w-full\" />}\n <div hidden={pending} style={pending ? { display: \"none\" } : undefined} id={`${id}-contents`} role=\"region\" aria-labelledby={`${id}-heading`} aria-hidden={!open || pending} inert={!open || pending} className={cn(\"grid transition-[grid-template-rows,opacity] duration-250 ease-out motion-reduce:transition-none\", open ? \"grid-rows-[1fr] opacity-100\" : \"grid-rows-[0fr] opacity-0\")}>\n <div className=\"min-h-0 overflow-hidden\">\n <ol ref={reveal.grid} className={cn(layout.className, \"pt-2\")} style={layout.style}>\n {entries.map((entry) => entry.summary ?\n <ContentSummaryTile key={entry.key} tileKey={entry.key} contentKind={kinds?.[0] ?? entry.card.kind} open={open && !pending} preview={entry.card} cardProps={p} kind=\"premium\" label={summaryLabels?.premium?.(premiumCount) ?? m.plural(\"releases.premiumMore\", premiumCount)} onClick={expandPremium} /> :\n <ReleaseCard key={entry.key} card={entry.card} open={open && !pending} position={entry.position} exposure={exposure} cardProps={p} />)}\n {day.filtered_count > 0 && <ContentSummaryTile tileKey=\"filtered-summary\" contentKind={summaryContentKind} open={open && !pending} cardProps={p} kind=\"filtered\" label={summaryLabels?.filtered?.(day.filtered_count) ?? m.plural(\"releases.filteredMore\", day.filtered_count)} onClick={(event) => { filterTrigger.current = event.currentTarget; setFiltersOpen(true); }} />}\n </ol>\n {visible < day.contents.length && <div className=\"flex justify-center pt-5 pb-1\"><Button type=\"button\" variant=\"outline\" onClick={(event) => { reveal.prepare(event.detail === 0); setVisible((n) => Math.min(n + step, day.contents.length)); }}>{m.t(\"contents.showMore\")}</Button></div>}\n </div>\n </div>\n <ContentFilterDialog returnFocus={() => !pending && filterTrigger.current?.isConnected ? filterTrigger.current : dayHeading.current} open={filtersOpen} onOpenChange={setFiltersOpen} client={p.client} kinds={kinds} language={language} counts={day.filtered_entities ?? []} description={m.t(\"releases.filterContext\", { date: label })} appearance={p.appearance} />\n </section>\n );\n}\n\n\nfunction ReleaseCard({ card, open, position, exposure, cardProps: p }: { card: Card; open: boolean; position: number; exposure: ReturnType<typeof useExposure>; cardProps: ReleaseCardProps }) {\n const [viewport, visible] = useNearViewport({ margin: \"0px\", leaveMargin: \"1200px 0px\" });\n const tracked = useMemo(() => exposure.track(card.id, position), [exposure, card.id, position]);\n const ref = useCallback((element: HTMLLIElement | null) => { viewport(element); tracked(element); }, [viewport, tracked]);\n return <li ref={ref} data-release-key={card.id}>\n <ContentCard card={card} client={p.client} href={p.href} shape={p.shape} aspectRatio={p.aspectRatio} titlePosition={p.titlePosition} loadMedia={open && visible} groupKinds={p.groupKinds} chips={p.chips} chipOverflow={p.chipOverflow} chipFit={p.chipFit} filterEntity={p.filterEntity} entityStyle={p.entityStyle} entityLayout={p.entityLayout} metadata={p.metadata} showCounts={p.showCounts} tiers={p.tiers} labels={p.labels} owner={p.owner} ownerHref={p.ownerHref} decorate={p.decorate} onOpen={() => exposure.click(card.id, position)} />\n </li>;\n}\n"],"mappings":"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"}
|
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ruleFor as e } from "../../client/media/rules.js";
|
|
2
|
+
import { isProcessing as t, pageRemoved as n } from "../../client/media/windows.js";
|
|
3
|
+
import { createContext as r, useCallback as i, useContext as a, useLayoutEffect as o, useRef as s, useState as c } from "react";
|
|
3
4
|
//#region src/components/content/editor-save.tsx
|
|
4
|
-
var
|
|
5
|
-
function
|
|
6
|
-
let n =
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
var l = r(null);
|
|
6
|
+
function u(e, t) {
|
|
7
|
+
let n = a(l), r = n?.register, i = n?.changed, c = s(t);
|
|
8
|
+
c.current = t, o(() => r?.(e, c), [r, e]), o(() => i?.(), [
|
|
9
|
+
i,
|
|
9
10
|
t.dirty,
|
|
10
11
|
t.pending,
|
|
11
12
|
t.valid
|
|
12
13
|
]);
|
|
13
14
|
}
|
|
14
|
-
function
|
|
15
|
-
let e =
|
|
16
|
-
e.current.delete(t),
|
|
17
|
-
}), [
|
|
15
|
+
function d() {
|
|
16
|
+
let e = s(/* @__PURE__ */ new Map()), [, t] = c(0), n = i(() => t((e) => e + 1), []), r = i((t, r) => (e.current.set(t, r), n(), () => {
|
|
17
|
+
e.current.delete(t), n();
|
|
18
|
+
}), [n]), a = () => [...e.current.values()].map((e) => e.current).find((e) => e.pending || e.dirty && !e.valid)?.label;
|
|
18
19
|
return {
|
|
19
20
|
context: {
|
|
20
|
-
register:
|
|
21
|
-
changed:
|
|
21
|
+
register: r,
|
|
22
|
+
changed: n
|
|
22
23
|
},
|
|
23
|
-
problem:
|
|
24
|
+
problem: a,
|
|
24
25
|
save: async (t) => {
|
|
25
|
-
let n =
|
|
26
|
+
let n = a();
|
|
26
27
|
if (n) return n;
|
|
27
28
|
let r = [...e.current.values()].sort((e, t) => (e.current.order ?? 0) - (t.current.order ?? 0));
|
|
28
29
|
for (let e of r) if (!t() || e.current.dirty && !await e.current.save()) return e.current.label;
|
|
@@ -31,21 +32,22 @@ function l() {
|
|
|
31
32
|
dirty: [...e.current.values()].some((e) => e.current.dirty)
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
|
-
async function
|
|
35
|
-
if (!
|
|
36
|
-
let
|
|
35
|
+
async function f(r, i) {
|
|
36
|
+
if (!i.version) return !1;
|
|
37
|
+
let a = 0, o, s = 0;
|
|
37
38
|
do {
|
|
38
|
-
let
|
|
39
|
+
let c = await r.media.read(i, {
|
|
39
40
|
editor: !0,
|
|
40
|
-
offset:
|
|
41
|
+
offset: a,
|
|
41
42
|
limit: 200
|
|
42
43
|
});
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if (c.files.some((e) => !!e.failed || t(e))) return !1;
|
|
45
|
+
let l = c.uploads ?? [];
|
|
46
|
+
s += c.files.filter((t) => t.upload && e(l, t.path)?.page && !n(t)).length, o = c.total, a += c.limit || 200;
|
|
47
|
+
} while (a < o);
|
|
48
|
+
return s > 0;
|
|
47
49
|
}
|
|
48
50
|
//#endregion
|
|
49
|
-
export {
|
|
51
|
+
export { l as EditorSaveContext, f as readerMediaReady, d as useEditorSaveCoordinator, u as useEditorSaveTask };
|
|
50
52
|
|
|
51
53
|
//# sourceMappingURL=editor-save.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor-save.js","names":[],"sources":["../../../src/components/content/editor-save.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useLayoutEffect, useRef, useState, type RefObject } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { RefBody } from \"../../client/generated/wire.js\";\nimport { isProcessing } from \"../../client/media/windows.js\";\n\ninterface EditorTask {\n label: string;\n dirty: boolean;\n pending: boolean;\n valid: boolean;\n save: () => Promise<boolean>;\n order?: number;\n}\n\ninterface EditorTasks {\n register: (id: string, task: RefObject<EditorTask>) => () => void;\n changed: () => void;\n}\n\n/** Optional coordination for an editor's existing independently usable tools. */\nexport const EditorSaveContext = createContext<EditorTasks | null>(null);\n\nexport function useEditorSaveTask(id: string, task: EditorTask) {\n const context = useContext(EditorSaveContext);\n const register = context?.register;\n const changed = context?.changed;\n const current = useRef(task);\n current.current = task;\n useLayoutEffect(() => register?.(id, current), [register, id]);\n useLayoutEffect(() => changed?.(), [changed, task.dirty, task.pending, task.valid]);\n}\n\n/** Save only dirty tools, before publication; a refused save never publishes. */\nexport function useEditorSaveCoordinator() {\n const tasks = useRef(new Map<string, RefObject<EditorTask>>());\n const [, refresh] = useState(0);\n const changed = useCallback(() => refresh((value) => value + 1), []);\n const register = useCallback(\n (id: string, task: RefObject<EditorTask>) => {\n tasks.current.set(id, task);\n changed();\n return () => {\n tasks.current.delete(id);\n changed();\n };\n },\n [changed],\n );\n const problem = () => [...tasks.current.values()].map((r) => r.current).find((t) => t.pending || (t.dirty && !t.valid))?.label;\n const save = async (current: () => boolean) => {\n const invalid = problem();\n if (invalid) return invalid;\n const ordered = [...tasks.current.values()].sort((a, b) => (a.current.order ?? 0) - (b.current.order ?? 0));\n for (const ref of ordered) {\n if (!current()) return ref.current.label;\n if (ref.current.dirty && !(await ref.current.save())) return ref.current.label;\n }\n return undefined;\n };\n return {\n context: { register, changed },\n problem,\n save,\n pending: [...tasks.current.values()].some((t) => t.current.pending),\n dirty: [...tasks.current.values()].some((t) => t.current.dirty),\n };\n}\n\n/** Check every metadata window, not only the first page of a long gallery. */\nexport async function readerMediaReady(client: ContentKitClient, ref: RefBody): Promise<boolean> {\n if (!ref.version) return false;\n
|
|
1
|
+
{"version":3,"file":"editor-save.js","names":[],"sources":["../../../src/components/content/editor-save.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useLayoutEffect, useRef, useState, type RefObject } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { RefBody } from \"../../client/generated/wire.js\";\nimport { ruleFor } from \"../../client/media/rules.js\";\nimport { isProcessing, pageRemoved } from \"../../client/media/windows.js\";\n\ninterface EditorTask {\n label: string;\n dirty: boolean;\n pending: boolean;\n valid: boolean;\n save: () => Promise<boolean>;\n order?: number;\n}\n\ninterface EditorTasks {\n register: (id: string, task: RefObject<EditorTask>) => () => void;\n changed: () => void;\n}\n\n/** Optional coordination for an editor's existing independently usable tools. */\nexport const EditorSaveContext = createContext<EditorTasks | null>(null);\n\nexport function useEditorSaveTask(id: string, task: EditorTask) {\n const context = useContext(EditorSaveContext);\n const register = context?.register;\n const changed = context?.changed;\n const current = useRef(task);\n current.current = task;\n useLayoutEffect(() => register?.(id, current), [register, id]);\n useLayoutEffect(() => changed?.(), [changed, task.dirty, task.pending, task.valid]);\n}\n\n/** Save only dirty tools, before publication; a refused save never publishes. */\nexport function useEditorSaveCoordinator() {\n const tasks = useRef(new Map<string, RefObject<EditorTask>>());\n const [, refresh] = useState(0);\n const changed = useCallback(() => refresh((value) => value + 1), []);\n const register = useCallback(\n (id: string, task: RefObject<EditorTask>) => {\n tasks.current.set(id, task);\n changed();\n return () => {\n tasks.current.delete(id);\n changed();\n };\n },\n [changed],\n );\n const problem = () => [...tasks.current.values()].map((r) => r.current).find((t) => t.pending || (t.dirty && !t.valid))?.label;\n const save = async (current: () => boolean) => {\n const invalid = problem();\n if (invalid) return invalid;\n const ordered = [...tasks.current.values()].sort((a, b) => (a.current.order ?? 0) - (b.current.order ?? 0));\n for (const ref of ordered) {\n if (!current()) return ref.current.label;\n if (ref.current.dirty && !(await ref.current.save())) return ref.current.label;\n }\n return undefined;\n };\n return {\n context: { register, changed },\n problem,\n save,\n pending: [...tasks.current.values()].some((t) => t.current.pending),\n dirty: [...tasks.current.values()].some((t) => t.current.dirty),\n };\n}\n\n/** Check every metadata window, not only the first page of a long gallery. */\nexport async function readerMediaReady(client: ContentKitClient, ref: RefBody): Promise<boolean> {\n if (!ref.version) return false;\n let offset = 0;\n let total: number;\n let pages = 0;\n do {\n const read = await client.media.read(ref, { editor: true, offset, limit: 200 });\n if (read.files.some((file) => !!file.failed || isProcessing(file))) return false;\n const rules = read.uploads ?? [];\n pages += read.files.filter((file) => file.upload && ruleFor(rules, file.path)?.page && !pageRemoved(file)).length;\n total = read.total;\n offset += read.limit || 200;\n } while (offset < total);\n return pages > 0;\n}\n"],"mappings":";;;;AAqBA,IAAa,IAAoB,EAAkC,IAAI;AAEvE,SAAgB,EAAkB,GAAY,GAAkB;CAC9D,IAAM,IAAU,EAAW,CAAiB,GACtC,IAAW,GAAS,UACpB,IAAU,GAAS,SACnB,IAAU,EAAO,CAAI;CAG3B,AAFA,EAAQ,UAAU,GAClB,QAAsB,IAAW,GAAI,CAAO,GAAG,CAAC,GAAU,CAAE,CAAC,GAC7D,QAAsB,IAAU,GAAG;EAAC;EAAS,EAAK;EAAO,EAAK;EAAS,EAAK;CAAK,CAAC;AACpF;AAGA,SAAgB,IAA2B;CACzC,IAAM,IAAQ,kBAAO,IAAI,IAAmC,CAAC,GACvD,GAAG,KAAW,EAAS,CAAC,GACxB,IAAU,QAAkB,GAAS,MAAU,IAAQ,CAAC,GAAG,CAAC,CAAC,GAC7D,IAAW,GACd,GAAY,OACX,EAAM,QAAQ,IAAI,GAAI,CAAI,GAC1B,EAAQ,SACK;EAEX,AADA,EAAM,QAAQ,OAAO,CAAE,GACvB,EAAQ;CACV,IAEF,CAAC,CAAO,CACV,GACM,UAAgB,CAAC,GAAG,EAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,MAAM,EAAE,WAAY,EAAE,SAAS,CAAC,EAAE,KAAM,CAAC,EAAE;CAWzH,OAAO;EACL,SAAS;GAAE;GAAU;EAAQ;EAC7B;EACA,aAbkB,MAA2B;GAC7C,IAAM,IAAU,EAAQ;GACxB,IAAI,GAAS,OAAO;GACpB,IAAM,IAAU,CAAC,GAAG,EAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,OAAO,EAAE,QAAQ,SAAS,MAAM,EAAE,QAAQ,SAAS,EAAE;GAC1G,KAAK,IAAM,KAAO,GAEhB,IADI,CAAC,EAAQ,KACT,EAAI,QAAQ,SAAS,CAAE,MAAM,EAAI,QAAQ,KAAK,GAAI,OAAO,EAAI,QAAQ;EAG7E;EAKE,SAAS,CAAC,GAAG,EAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,QAAQ,OAAO;EAClE,OAAO,CAAC,GAAG,EAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,QAAQ,KAAK;CAChE;AACF;AAGA,eAAsB,EAAiB,GAA0B,GAAgC;CAC/F,IAAI,CAAC,EAAI,SAAS,OAAO;CACzB,IAAI,IAAS,GACT,GACA,IAAQ;CACZ,GAAG;EACD,IAAM,IAAO,MAAM,EAAO,MAAM,KAAK,GAAK;GAAE,QAAQ;GAAM;GAAQ,OAAO;EAAI,CAAC;EAC9E,IAAI,EAAK,MAAM,MAAM,MAAS,CAAC,CAAC,EAAK,UAAU,EAAa,CAAI,CAAC,GAAG,OAAO;EAC3E,IAAM,IAAQ,EAAK,WAAW,CAAC;EAG/B,AAFA,KAAS,EAAK,MAAM,QAAQ,MAAS,EAAK,UAAU,EAAQ,GAAO,EAAK,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAY,CAAI,CAAC,CAAC,CAAC,QAC3G,IAAQ,EAAK,OACb,KAAU,EAAK,SAAS;CAC1B,SAAS,IAAS;CAClB,OAAO,IAAQ;AACjB"}
|