@openrails/contentkit-ui 0.97.1 → 0.98.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +57 -4
  2. package/dist/client/beacons.d.ts +17 -7
  3. package/dist/client/beacons.js +205 -41
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/browser-reader-preferences.d.ts +15 -2
  6. package/dist/client/browser-reader-preferences.js +67 -30
  7. package/dist/client/browser-reader-preferences.js.map +1 -1
  8. package/dist/client/discovery.d.ts +13 -3
  9. package/dist/client/discovery.js +31 -16
  10. package/dist/client/discovery.js.map +1 -1
  11. package/dist/client/generated/routes.d.ts +14 -1
  12. package/dist/client/generated/routes.js +18 -0
  13. package/dist/client/generated/routes.js.map +1 -1
  14. package/dist/client/generated/wire.d.ts +47 -4
  15. package/dist/client/generated/wire.js.map +1 -1
  16. package/dist/client/http.d.ts +7 -3
  17. package/dist/client/http.js +26 -7
  18. package/dist/client/http.js.map +1 -1
  19. package/dist/client/index.d.ts +2 -2
  20. package/dist/client/reader-preferences.d.ts +7 -0
  21. package/dist/client/reader-preferences.js +16 -4
  22. package/dist/client/reader-preferences.js.map +1 -1
  23. package/dist/client.js +17 -17
  24. package/dist/components/content/card.js +50 -40
  25. package/dist/components/content/card.js.map +1 -1
  26. package/dist/components/content/filters.d.ts +5 -0
  27. package/dist/components/content/filters.js +61 -52
  28. package/dist/components/content/filters.js.map +1 -1
  29. package/dist/components/content/popular.d.ts +1 -1
  30. package/dist/components/content/popular.js.map +1 -1
  31. package/dist/components/content/reader-settings.d.ts +4 -0
  32. package/dist/components/content/reader-settings.js +3 -1
  33. package/dist/components/content/reader-settings.js.map +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/react/discovery.d.ts +7 -2
  36. package/dist/react/discovery.js +208 -87
  37. package/dist/react/discovery.js.map +1 -1
  38. package/dist/react/provider.js +4 -1
  39. package/dist/react/provider.js.map +1 -1
  40. package/dist/styles.css +1 -1
  41. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"reader-preferences.js","names":[],"sources":["../../src/client/reader-preferences.ts"],"sourcesContent":["import { ContentKitError, toContentKitError } from \"./errors.js\";\nimport type { ContentFilters, ContentFiltersUpdate } from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call } from \"./route.js\";\n\n/** The host's account-owned preferred language, without a route fallback. */\nexport interface LanguagePreference {\n load(accountId: string): Promise<string | null | undefined>;\n save(language: string, accountId: string): Promise<void>;\n}\n\nexport interface ReaderPreferenceState {\n language?: string;\n loading: boolean;\n saving: boolean;\n pending: boolean;\n error?: ContentKitError;\n}\n\n/** Guest storage and explicit, retryable registration inheritance. */\nexport class ReaderPreferencesClient {\n readonly browser;\n private viewer?: string | null;\n private adapter?: LanguagePreference;\n private accountLanguage?: string;\n private loading = false;\n private saving = false;\n private failure?: ContentKitError;\n private generation = 0;\n private syncing?: Promise<void>;\n private syncingGeneration = -1;\n private state: ReaderPreferenceState = Object.freeze({ loading: false, saving: false, pending: false });\n private readonly listeners = new Set<() => void>();\n private stopBrowser?: () => void;\n private guestValue: string;\n\n constructor(private readonly http: Http) {\n this.browser = http.browserPreferences;\n this.guestValue = JSON.stringify(this.browser.snapshot());\n }\n\n readonly snapshot = (): ReaderPreferenceState => this.state;\n readonly subscribe = (listener: () => void): (() => void) => {\n if (!this.listeners.size) {\n this.stopBrowser = this.browser.subscribe(this.browserChanged);\n if (typeof window !== \"undefined\") window.addEventListener(\"online\", this.retryOnline);\n }\n this.listeners.add(listener);\n this.refresh();\n return () => {\n this.listeners.delete(listener);\n if (!this.listeners.size) {\n this.stopBrowser?.();\n if (typeof window !== \"undefined\") window.removeEventListener(\"online\", this.retryOnline);\n }\n };\n };\n\n configure(viewer: string | null | undefined, adapter?: LanguagePreference): void {\n this.http.setViewer(viewer);\n if (viewer === this.viewer && adapter === this.adapter) return;\n this.viewer = viewer;\n this.adapter = adapter;\n const generation = ++this.generation;\n this.accountLanguage = undefined;\n this.failure = undefined;\n this.saving = false;\n this.loading = !!viewer && !!adapter;\n this.refresh();\n if (viewer) void this.activate(generation).catch(() => {});\n }\n\n async setLanguage(language: string): Promise<void> {\n try { language = Intl.getCanonicalLocales(language)[0]!; }\n catch { throw new ContentKitError(\"invalid_request\", \"Invalid preferred language.\"); }\n if (!language) throw new ContentKitError(\"invalid_request\", \"Invalid preferred language.\");\n const account = this.viewer;\n if (account == null) { this.browser.setLanguage(language); this.refresh(); return; }\n if (!this.adapter) throw new Error(\"contentkit: account language preference adapter is required\");\n const generation = this.generation;\n this.saving = true;\n this.failure = undefined;\n this.refresh();\n try {\n await this.adapter.save(language, account);\n if (generation !== this.generation) throw new ContentKitError(\"aborted\", \"The account changed.\");\n this.accountLanguage = language;\n this.browser.complete(account, \"language\");\n } catch (error) {\n if (generation === this.generation) this.failure = toContentKitError(error);\n throw error;\n } finally {\n if (generation === this.generation) { this.saving = false; this.refresh(); }\n }\n }\n\n /** Call only after a confirmed registration event, never on ordinary login. */\n async inheritAfterRegistration(account: string): Promise<void> {\n this.browser.register(account);\n this.refresh();\n return this.viewer === account ? this.retryInheritance() : Promise.resolve();\n }\n\n async retry(): Promise<void> {\n if (!this.viewer) return;\n const generation = this.generation;\n this.failure = undefined;\n this.loading = true;\n this.refresh();\n await this.activate(generation);\n if (generation === this.generation && this.failure) throw this.failure;\n }\n\n retryInheritance(): Promise<void> {\n if (!this.viewer || !this.browser.pending(this.viewer)) return Promise.resolve();\n if (this.syncing && this.syncingGeneration === this.generation) return this.syncing;\n const account = this.viewer, generation = this.generation;\n const task = this.inherit(account, generation).finally(() => {\n if (this.syncing === task) this.syncing = undefined;\n this.refresh();\n });\n this.syncing = task;\n this.syncingGeneration = generation;\n this.refresh();\n return this.syncing;\n }\n\n private async activate(generation: number): Promise<void> {\n try {\n await this.retryInheritance();\n if (generation !== this.generation) return;\n const language = this.viewer ? await this.adapter?.load(this.viewer) : undefined;\n if (generation === this.generation) this.accountLanguage = language ?? undefined;\n } catch (error) {\n if (generation === this.generation) this.failure = toContentKitError(error);\n } finally {\n if (generation === this.generation) { this.loading = false; this.refresh(); }\n }\n }\n\n private async inherit(account: string, generation: number): Promise<void> {\n this.failure = undefined;\n try {\n let pending = this.browser.pending(account)!;\n if (!pending.languageDone) {\n if (!this.adapter) throw new Error(\"contentkit: account language preference adapter is required\");\n const language = (await this.adapter.load(account)) ?? null;\n if (generation !== this.generation) return;\n const live = this.browser.pending(account);\n if (live && !live.languageDone) {\n if (!live.languageObserved) this.browser.observeLanguage(account, language);\n pending = this.browser.pending(account)!;\n // The initial registration default may be non-null. A later different\n // choice wins over a retry of the captured guest preference.\n const changedLater = language !== pending.initialLanguage && language !== pending.language;\n if (!changedLater && language !== pending.language && pending.language) await this.adapter.save(pending.language, account);\n if (generation !== this.generation) return;\n this.accountLanguage = changedLater ? language ?? undefined : pending.language;\n this.browser.complete(account, \"language\");\n }\n }\n pending = this.browser.pending(account)!;\n if (pending && !pending.filtersDone) {\n if (generation !== this.generation) return;\n // Registration initializes account-wide choices. Only the server knows\n // which saved entities are toggleable on another domain or now stale.\n await call<\"POST\", \"/me/content-filters/initialize\", ContentFilters>(this.http, \"POST\", \"/me/content-filters/initialize\", {\n body: { account_id: account, filters: [...pending.filters], include_deleted: false } satisfies ContentFiltersUpdate & { account_id: string },\n });\n if (generation !== this.generation) return;\n this.browser.complete(account, \"filters\");\n }\n if (generation === this.generation) this.http.captureChanges()({ type: \"contentFilters.changed\" });\n } catch (error) {\n if (generation === this.generation) this.failure = toContentKitError(error);\n throw error;\n }\n }\n\n private readonly retryOnline = (): void => {\n if (this.state.pending || this.state.error) void this.retry().catch(() => {});\n };\n private readonly browserChanged = (): void => {\n const value = JSON.stringify(this.browser.snapshot());\n if (this.viewer === null && value !== this.guestValue) this.http.captureChanges()({ type: \"contentFilters.changed\" });\n this.guestValue = value;\n this.refresh();\n };\n\n private refresh(): void {\n const next: ReaderPreferenceState = {\n language: this.viewer == null ? this.browser.snapshot().language : this.accountLanguage,\n loading: this.loading, saving: this.saving,\n pending: !!this.viewer && !!this.browser.pending(this.viewer), error: this.failure,\n };\n if (next.language === this.state.language && next.loading === this.state.loading && next.saving === this.state.saving && next.pending === this.state.pending && next.error === this.state.error) return;\n this.state = Object.freeze(next);\n for (const listener of this.listeners) { try { listener(); } catch { /* Observers do not own persistence. */ } }\n }\n}\n"],"mappings":";;;AAoBA,IAAa,IAAb,MAAqC;CAgBN;CAf7B;CACA;CACA;CACA;CACA,UAAkB;CAClB,SAAiB;CACjB;CACA,aAAqB;CACrB;CACA,oBAA4B;CAC5B,QAAuC,OAAO,OAAO;EAAE,SAAS;EAAO,QAAQ;EAAO,SAAS;CAAM,CAAC;CACtG,4BAA6B,IAAI,IAAgB;CACjD;CACA;CAEA,YAAY,GAA6B;EAEvC,AAF2B,KAAA,OAAA,GAC3B,KAAK,UAAU,EAAK,oBACpB,KAAK,aAAa,KAAK,UAAU,KAAK,QAAQ,SAAS,CAAC;CAC1D;CAEA,iBAAiD,KAAK;CACtD,aAAsB,OACf,KAAK,UAAU,SAClB,KAAK,cAAc,KAAK,QAAQ,UAAU,KAAK,cAAc,GACzD,OAAO,SAAW,OAAa,OAAO,iBAAiB,UAAU,KAAK,WAAW,IAEvF,KAAK,UAAU,IAAI,CAAQ,GAC3B,KAAK,QAAQ,SACA;EAEX,AADA,KAAK,UAAU,OAAO,CAAQ,GACzB,KAAK,UAAU,SAClB,KAAK,cAAc,GACf,OAAO,SAAW,OAAa,OAAO,oBAAoB,UAAU,KAAK,WAAW;CAE5F;CAGF,UAAU,GAAmC,GAAoC;EAE/E,IADA,KAAK,KAAK,UAAU,CAAM,GACtB,MAAW,KAAK,UAAU,MAAY,KAAK,SAAS;EAExD,AADA,KAAK,SAAS,GACd,KAAK,UAAU;EACf,IAAM,IAAa,EAAE,KAAK;EAM1B,AALA,KAAK,kBAAkB,KAAA,GACvB,KAAK,UAAU,KAAA,GACf,KAAK,SAAS,IACd,KAAK,UAAU,CAAC,CAAC,KAAU,CAAC,CAAC,GAC7B,KAAK,QAAQ,GACT,KAAQ,KAAU,SAAS,CAAU,CAAC,CAAC,YAAY,CAAC,CAAC;CAC3D;CAEA,MAAM,YAAY,GAAiC;EACjD,IAAI;GAAE,IAAW,KAAK,oBAAoB,CAAQ,CAAC,CAAC;EAAK,QACnD;GAAE,MAAM,IAAI,EAAgB,mBAAmB,6BAA6B;EAAG;EACrF,IAAI,CAAC,GAAU,MAAM,IAAI,EAAgB,mBAAmB,6BAA6B;EACzF,IAAM,IAAU,KAAK;EACrB,IAAI,KAAW,MAAM;GAAsC,AAApC,KAAK,QAAQ,YAAY,CAAQ,GAAG,KAAK,QAAQ;GAAG;EAAQ;EACnF,IAAI,CAAC,KAAK,SAAS,MAAU,MAAM,6DAA6D;EAChG,IAAM,IAAa,KAAK;EAGxB,AAFA,KAAK,SAAS,IACd,KAAK,UAAU,KAAA,GACf,KAAK,QAAQ;EACb,IAAI;GAEF,IADA,MAAM,KAAK,QAAQ,KAAK,GAAU,CAAO,GACrC,MAAe,KAAK,YAAY,MAAM,IAAI,EAAgB,WAAW,sBAAsB;GAE/F,AADA,KAAK,kBAAkB,GACvB,KAAK,QAAQ,SAAS,GAAS,UAAU;EAC3C,SAAS,GAAO;GAEd,MADI,MAAe,KAAK,eAAY,KAAK,UAAU,EAAkB,CAAK,IACpE;EACR,UAAU;GACR,AAAI,MAAe,KAAK,eAAc,KAAK,SAAS,IAAO,KAAK,QAAQ;EAC1E;CACF;CAGA,MAAM,yBAAyB,GAAgC;EAG7D,OAFA,KAAK,QAAQ,SAAS,CAAO,GAC7B,KAAK,QAAQ,GACN,KAAK,WAAW,IAAU,KAAK,iBAAiB,IAAI,QAAQ,QAAQ;CAC7E;CAEA,MAAM,QAAuB;EAC3B,IAAI,CAAC,KAAK,QAAQ;EAClB,IAAM,IAAa,KAAK;EAKxB,IAJA,KAAK,UAAU,KAAA,GACf,KAAK,UAAU,IACf,KAAK,QAAQ,GACb,MAAM,KAAK,SAAS,CAAU,GAC1B,MAAe,KAAK,cAAc,KAAK,SAAS,MAAM,KAAK;CACjE;CAEA,mBAAkC;EAChC,IAAI,CAAC,KAAK,UAAU,CAAC,KAAK,QAAQ,QAAQ,KAAK,MAAM,GAAG,OAAO,QAAQ,QAAQ;EAC/E,IAAI,KAAK,WAAW,KAAK,sBAAsB,KAAK,YAAY,OAAO,KAAK;EAC5E,IAAM,IAAU,KAAK,QAAQ,IAAa,KAAK,YACzC,IAAO,KAAK,QAAQ,GAAS,CAAU,CAAC,CAAC,cAAc;GAE3D,AADI,KAAK,YAAY,MAAM,KAAK,UAAU,KAAA,IAC1C,KAAK,QAAQ;EACf,CAAC;EAID,OAHA,KAAK,UAAU,GACf,KAAK,oBAAoB,GACzB,KAAK,QAAQ,GACN,KAAK;CACd;CAEA,MAAc,SAAS,GAAmC;EACxD,IAAI;GAEF,IADA,MAAM,KAAK,iBAAiB,GACxB,MAAe,KAAK,YAAY;GACpC,IAAM,IAAW,KAAK,SAAS,MAAM,KAAK,SAAS,KAAK,KAAK,MAAM,IAAI,KAAA;GACvE,AAAI,MAAe,KAAK,eAAY,KAAK,kBAAkB,KAAY,KAAA;EACzE,SAAS,GAAO;GACd,AAAI,MAAe,KAAK,eAAY,KAAK,UAAU,EAAkB,CAAK;EAC5E,UAAU;GACR,AAAI,MAAe,KAAK,eAAc,KAAK,UAAU,IAAO,KAAK,QAAQ;EAC3E;CACF;CAEA,MAAc,QAAQ,GAAiB,GAAmC;EACxE,KAAK,UAAU,KAAA;EACf,IAAI;GACF,IAAI,IAAU,KAAK,QAAQ,QAAQ,CAAO;GAC1C,IAAI,CAAC,EAAQ,cAAc;IACzB,IAAI,CAAC,KAAK,SAAS,MAAU,MAAM,6DAA6D;IAChG,IAAM,IAAY,MAAM,KAAK,QAAQ,KAAK,CAAO,KAAM;IACvD,IAAI,MAAe,KAAK,YAAY;IACpC,IAAM,IAAO,KAAK,QAAQ,QAAQ,CAAO;IACzC,IAAI,KAAQ,CAAC,EAAK,cAAc;KAE9B,AADK,EAAK,oBAAkB,KAAK,QAAQ,gBAAgB,GAAS,CAAQ,GAC1E,IAAU,KAAK,QAAQ,QAAQ,CAAO;KAGtC,IAAM,IAAe,MAAa,EAAQ,mBAAmB,MAAa,EAAQ;KAElF,IADI,CAAC,KAAgB,MAAa,EAAQ,YAAY,EAAQ,YAAU,MAAM,KAAK,QAAQ,KAAK,EAAQ,UAAU,CAAO,GACrH,MAAe,KAAK,YAAY;KAEpC,AADA,KAAK,kBAAkB,IAAe,KAAY,KAAA,IAAY,EAAQ,UACtE,KAAK,QAAQ,SAAS,GAAS,UAAU;IAC3C;GACF;GAEA,IADA,IAAU,KAAK,QAAQ,QAAQ,CAAO,GAClC,KAAW,CAAC,EAAQ,aAAa;IAOnC,IANI,MAAe,KAAK,eAGxB,MAAM,EAA+D,KAAK,MAAM,QAAQ,kCAAkC,EACxH,MAAM;KAAE,YAAY;KAAS,SAAS,CAAC,GAAG,EAAQ,OAAO;KAAG,iBAAiB;IAAM,EACrF,CAAC,GACG,MAAe,KAAK,aAAY;IACpC,KAAK,QAAQ,SAAS,GAAS,SAAS;GAC1C;GACA,AAAI,MAAe,KAAK,cAAY,KAAK,KAAK,eAAe,CAAC,CAAC,EAAE,MAAM,yBAAyB,CAAC;EACnG,SAAS,GAAO;GAEd,MADI,MAAe,KAAK,eAAY,KAAK,UAAU,EAAkB,CAAK,IACpE;EACR;CACF;CAEA,oBAA2C;EACzC,CAAI,KAAK,MAAM,WAAW,KAAK,MAAM,UAAO,KAAU,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;CAC9E;CACA,uBAA8C;EAC5C,IAAM,IAAQ,KAAK,UAAU,KAAK,QAAQ,SAAS,CAAC;EAGpD,AAFI,KAAK,WAAW,QAAQ,MAAU,KAAK,cAAY,KAAK,KAAK,eAAe,CAAC,CAAC,EAAE,MAAM,yBAAyB,CAAC,GACpH,KAAK,aAAa,GAClB,KAAK,QAAQ;CACf;CAEA,UAAwB;EACtB,IAAM,IAA8B;GAClC,UAAU,KAAK,UAAU,OAAO,KAAK,QAAQ,SAAS,CAAC,CAAC,WAAW,KAAK;GACxE,SAAS,KAAK;GAAS,QAAQ,KAAK;GACpC,SAAS,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,KAAK,QAAQ,QAAQ,KAAK,MAAM;GAAG,OAAO,KAAK;EAC7E;EACI,MAAK,aAAa,KAAK,MAAM,YAAY,EAAK,YAAY,KAAK,MAAM,WAAW,EAAK,WAAW,KAAK,MAAM,UAAU,EAAK,YAAY,KAAK,MAAM,WAAW,EAAK,UAAU,KAAK,MAAM,OAC1L;QAAK,QAAQ,OAAO,OAAO,CAAI;GAC/B,KAAK,IAAM,KAAY,KAAK,WAAa,IAAI;IAAE,EAAS;GAAG,QAAQ,CAA0C;EAD9E;CAEjC;AACF"}
1
+ {"version":3,"file":"reader-preferences.js","names":[],"sources":["../../src/client/reader-preferences.ts"],"sourcesContent":["import type { ReaderFilterKinds } from \"./browser-reader-preferences.js\";\nimport { ContentKitError, toContentKitError } from \"./errors.js\";\nimport type { ContentFilters } from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call } from \"./route.js\";\n\n/** The host's account-owned preferred language, without a route fallback. */\nexport interface LanguagePreference {\n load(accountId: string): Promise<string | null | undefined>;\n save(language: string, accountId: string): Promise<void>;\n}\n\nexport interface ReaderPreferenceState {\n language?: string;\n loading: boolean;\n saving: boolean;\n pending: boolean;\n error?: ContentKitError;\n}\n\n/** Guest storage and explicit, retryable registration inheritance. */\nexport class ReaderPreferencesClient {\n readonly browser;\n private viewer?: string | null;\n private adapter?: LanguagePreference;\n private accountLanguage?: string;\n private loading = false;\n private saving = false;\n private failure?: ContentKitError;\n private generation = 0;\n private syncing?: Promise<void>;\n private syncingGeneration = -1;\n private state: ReaderPreferenceState = Object.freeze({ loading: false, saving: false, pending: false });\n private readonly listeners = new Set<() => void>();\n private readonly identityListeners = new Set<() => void>();\n private stopBrowser?: () => void;\n private guestValue: string;\n\n constructor(private readonly http: Http) {\n this.browser = http.browserPreferences;\n this.guestValue = JSON.stringify(this.browser.snapshot());\n }\n\n readonly snapshot = (): ReaderPreferenceState => this.state;\n /** Current authenticated subject, null for a guest, undefined while unresolved. */\n readonly identity = (): string | null | undefined => this.viewer;\n readonly subscribeIdentity = (listener: () => void): (() => void) => {\n this.identityListeners.add(listener);\n return () => void this.identityListeners.delete(listener);\n };\n readonly subscribe = (listener: () => void): (() => void) => {\n if (!this.listeners.size) {\n this.stopBrowser = this.browser.subscribe(this.browserChanged);\n if (typeof window !== \"undefined\") window.addEventListener(\"online\", this.retryOnline);\n }\n this.listeners.add(listener);\n this.refresh();\n return () => {\n this.listeners.delete(listener);\n if (!this.listeners.size) {\n this.stopBrowser?.();\n if (typeof window !== \"undefined\") window.removeEventListener(\"online\", this.retryOnline);\n }\n };\n };\n\n configure(viewer: string | null | undefined, adapter?: LanguagePreference): void {\n this.http.setViewer(viewer);\n if (viewer === this.viewer && adapter === this.adapter) return;\n const identityChanged = viewer !== this.viewer;\n this.viewer = viewer;\n this.adapter = adapter;\n if (identityChanged) for (const listener of this.identityListeners) listener();\n const generation = ++this.generation;\n this.accountLanguage = undefined;\n this.failure = undefined;\n this.saving = false;\n this.loading = !!viewer && !!adapter;\n this.refresh();\n if (viewer) void this.activate(generation).catch(() => {});\n }\n\n /** Save an anonymous edit atomically after checking the current domain's wire limits. */\n setGuestFilters(kinds: ReaderFilterKinds): void {\n if (this.viewer !== null) throw new ContentKitError(\"invalid_request\", \"Guest preferences are only available while signed out.\");\n this.http.validateGuestPreferences({ ...this.browser.snapshot(), kinds });\n this.browser.setFilters(kinds);\n }\n\n async setLanguage(language: string): Promise<void> {\n try { language = Intl.getCanonicalLocales(language)[0]!; }\n catch { throw new ContentKitError(\"invalid_request\", \"Invalid preferred language.\"); }\n if (!language) throw new ContentKitError(\"invalid_request\", \"Invalid preferred language.\");\n const account = this.viewer;\n if (account == null) { this.browser.setLanguage(language); this.refresh(); return; }\n if (!this.adapter) throw new Error(\"contentkit: account language preference adapter is required\");\n const generation = this.generation;\n this.saving = true;\n this.failure = undefined;\n this.refresh();\n try {\n await this.adapter.save(language, account);\n if (generation !== this.generation) throw new ContentKitError(\"aborted\", \"The account changed.\");\n this.accountLanguage = language;\n this.browser.complete(account, \"language\");\n } catch (error) {\n if (generation === this.generation) this.failure = toContentKitError(error);\n throw error;\n } finally {\n if (generation === this.generation) { this.saving = false; this.refresh(); }\n }\n }\n\n /** Call only after a confirmed registration event, never on ordinary login. */\n async inheritAfterRegistration(account: string): Promise<void> {\n this.browser.register(account);\n this.refresh();\n return this.viewer === account ? this.retryInheritance() : Promise.resolve();\n }\n\n async retry(): Promise<void> {\n if (!this.viewer) return;\n const generation = this.generation;\n this.failure = undefined;\n this.loading = true;\n this.refresh();\n await this.activate(generation);\n if (generation === this.generation && this.failure) throw this.failure;\n }\n\n retryInheritance(): Promise<void> {\n if (!this.viewer || !this.browser.pending(this.viewer)) return Promise.resolve();\n if (this.syncing && this.syncingGeneration === this.generation) return this.syncing;\n const account = this.viewer, generation = this.generation;\n const task = this.inherit(account, generation).finally(() => {\n if (this.syncing === task) this.syncing = undefined;\n this.refresh();\n });\n this.syncing = task;\n this.syncingGeneration = generation;\n this.refresh();\n return this.syncing;\n }\n\n private async activate(generation: number): Promise<void> {\n try {\n await this.retryInheritance();\n if (generation !== this.generation) return;\n const language = this.viewer ? await this.adapter?.load(this.viewer) : undefined;\n if (generation === this.generation) this.accountLanguage = language ?? undefined;\n } catch (error) {\n if (generation === this.generation) this.failure = toContentKitError(error);\n } finally {\n if (generation === this.generation) { this.loading = false; this.refresh(); }\n }\n }\n\n private async inherit(account: string, generation: number): Promise<void> {\n this.failure = undefined;\n try {\n let pending = this.browser.pending(account)!;\n if (!pending.languageDone) {\n if (!this.adapter) throw new Error(\"contentkit: account language preference adapter is required\");\n const language = (await this.adapter.load(account)) ?? null;\n if (generation !== this.generation) return;\n const live = this.browser.pending(account);\n if (live && !live.languageDone) {\n if (!live.languageObserved) this.browser.observeLanguage(account, language);\n pending = this.browser.pending(account)!;\n // The initial registration default may be non-null. A later different\n // choice wins over a retry of the captured guest preference.\n const changedLater = language !== pending.initialLanguage && language !== pending.language;\n if (!changedLater && language !== pending.language && pending.language) await this.adapter.save(pending.language, account);\n if (generation !== this.generation) return;\n this.accountLanguage = changedLater ? language ?? undefined : pending.language;\n this.browser.complete(account, \"language\");\n }\n }\n pending = this.browser.pending(account)!;\n if (pending && !pending.filtersDone) {\n if (generation !== this.generation) return;\n // Registration initializes account-wide choices. Only the server knows\n // which saved entities are toggleable on another domain or now stale.\n await call<\"POST\", \"/me/content-filters/initialize\", ContentFilters>(this.http, \"POST\", \"/me/content-filters/initialize\", {\n body: { account_id: account, kinds: pending.kinds, legacy_filters: [...pending.legacy_filters], include_deleted: false },\n });\n if (generation !== this.generation) return;\n this.browser.complete(account, \"filters\");\n }\n if (generation === this.generation) this.http.captureChanges()({ type: \"contentFilters.changed\" });\n } catch (error) {\n if (generation === this.generation) this.failure = toContentKitError(error);\n throw error;\n }\n }\n\n private readonly retryOnline = (): void => {\n if (this.state.pending || this.state.error) void this.retry().catch(() => {});\n };\n private readonly browserChanged = (): void => {\n const value = JSON.stringify(this.browser.snapshot());\n if (this.viewer === null && value !== this.guestValue) this.http.captureChanges()({ type: \"contentFilters.changed\" });\n this.guestValue = value;\n this.refresh();\n };\n\n private refresh(): void {\n const next: ReaderPreferenceState = {\n language: this.viewer == null ? this.browser.snapshot().language : this.accountLanguage,\n loading: this.loading, saving: this.saving,\n pending: !!this.viewer && !!this.browser.pending(this.viewer), error: this.failure,\n };\n if (next.language === this.state.language && next.loading === this.state.loading && next.saving === this.state.saving && next.pending === this.state.pending && next.error === this.state.error) return;\n this.state = Object.freeze(next);\n for (const listener of this.listeners) { try { listener(); } catch { /* Observers do not own persistence. */ } }\n }\n}\n"],"mappings":"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"}
package/dist/client.js CHANGED
@@ -25,20 +25,20 @@ import { CodesClient as He } from "./client/codes.js";
25
25
  import { ContentsClient as Ue, IMAGE_SCHEME as We, imageRef as Ge, imageRefs as Ke } from "./client/contents.js";
26
26
  import { BansClient as qe } from "./client/content/bans.js";
27
27
  import { CommentsClient as Je } from "./client/content/comments.js";
28
- import { DiscoveryClient as Ye, popularQuery as Xe } from "./client/discovery.js";
29
- import { FavoritesClient as Ze } from "./client/content/favorites.js";
30
- import { ModerationClient as Qe } from "./client/content/moderation.js";
31
- import { PollsClient as $e } from "./client/content/polls.js";
32
- import { ReactionsClient as et } from "./client/content/reactions.js";
33
- import { EntitiesClient as tt } from "./client/entities.js";
34
- import { OffersClient as nt } from "./client/offers.js";
35
- import { StatsClient as rt } from "./client/stats.js";
36
- import { VersionsClient as it, isPending as at } from "./client/versions.js";
37
- import { BrowserReaderPreferences as ot } from "./client/browser-reader-preferences.js";
38
- import { NotificationsClient as st } from "./client/notifications.js";
39
- import { ReaderPreferencesClient as ct } from "./client/reader-preferences.js";
40
- import { createContentKitClient as lt } from "./client/client.js";
41
- import { LETTERS as ut, attributeInput as dt, fold as ft, fromLocalInput as pt, humanize as mt, letterOf as ht, parseAttribute as gt, parseAttributes as _t, slugify as vt, toLocalInput as yt } from "./client/group-fields.js";
42
- import { Beacons as bt, eventId as xt, readerSession as St } from "./client/beacons.js";
43
- import { emptyReaderMeta as Ct, matchAudio as wt, readerMetaProblems as Tt, readerNames as Et } from "./client/reader-meta.js";
44
- export { he as ABR_DEFAULTS, qe as BansClient, bt as Beacons, ot as BrowserReaderPreferences, i as CLIENT_ERROR_CODES, Ve as CatalogClient, He as CodesClient, Je as CommentsClient, a as ContentKitError, Ue as ContentsClient, e as DEFAULT_DENSITY, Ye as DiscoveryClient, o as ERROR_CODES, tt as EntitiesClient, Ze as FavoritesClient, We as IMAGE_SCHEME, ae as ItemUploadQueue, ut as LETTERS, T as MAX_PART_SIZE, E as MAX_SINGLE_PUT, D as MIN_PART_SIZE, w as MediaApi, N as MediaClient, Qe as ModerationClient, ge as NETWORK_FAILURES_BEFORE_ERROR, st as NotificationsClient, nt as OffersClient, Fe as PLAYBACK_SPEEDS, Ie as PLAYER_TRACKS_KEY, Le as PLAYER_VOLUME_KEY, k as Pacer, $e as PollsClient, ue as READ_CHUNK, _e as REFRESH_BEFORE_EXPIRY, et as ReactionsClient, ct as ReaderPreferencesClient, rt as StatsClient, it as VersionsClient, ve as abrHlsConfig, L as acceptOf, g as aspectOf, dt as attributeInput, P as backoff, v as call, ye as capRung, Y as centeredCrop, b as checkRef, de as chunkOffsets, be as classifyHlsError, xe as classifyMediaError, X as constrainCrop, lt as createContentKitClient, oe as decodeImage, R as defaultImage, A as defaultTransport, t as densityFor, Z as editOf, Q as editedSize, Ct as emptyReaderMeta, ie as encodeRemaining, xt as eventId, Se as expiryDelay, s as failureError, j as fetchTransport, z as filesFor, f as fill, ft as fold, Oe as formatDuration, pt as fromLocalInput, $ as fromRotated, ke as galleryItems, Ce as hlsConfig, mt as humanize, Ge as imageRef, Ke as imageRefs, we as initialEstimate, se as isAnimatedImage, Ae as isAudioType, x as isContentId, c as isContentKitError, je as isImageType, B as isPattern, at as isPending, fe as isProcessing, Me as isSubtitleType, Ne as isVideoType, S as itemKey, ht as letterOf, wt as matchAudio, pe as mergeReads, gt as parseAttribute, _t as parseAttributes, Re as parseSpriteVtt, n as pickRendition, ze as playerKeyAction, Xe as popularQuery, me as processing, p as publicRenditions, m as publicURL, _ as ratio, V as ratioLabel, ce as reactionVerb, l as readContentKitError, Tt as readerMetaProblems, Et as readerNames, St as readerSession, Te as refreshable, Be as resumeAt, ee as rotation, y as routeURL, H as ruleDir, U as ruleFor, te as sameEdit, C as sameItem, F as samePath, le as sameTarget, W as screenFiles, O as sha256Hex, vt as slugify, r as sortRenditions, h as srcSet, G as stableImage, Pe as stageAspect, Ee as startRung, De as statusKind, I as stem, u as toContentKitError, yt as toLocalInput, ne as toOriginal, re as toRotated, d as transportFailure, K as uniqueName, q as uploadRules, J as withMediaType, M as xhrTransport };
28
+ import { DiscoveryClient as Ye, popularBetween as Xe, popularQuery as Ze } from "./client/discovery.js";
29
+ import { FavoritesClient as Qe } from "./client/content/favorites.js";
30
+ import { ModerationClient as $e } from "./client/content/moderation.js";
31
+ import { PollsClient as et } from "./client/content/polls.js";
32
+ import { ReactionsClient as tt } from "./client/content/reactions.js";
33
+ import { EntitiesClient as nt } from "./client/entities.js";
34
+ import { OffersClient as rt } from "./client/offers.js";
35
+ import { StatsClient as it } from "./client/stats.js";
36
+ import { VersionsClient as at, isPending as ot } from "./client/versions.js";
37
+ import { BrowserReaderPreferences as st } from "./client/browser-reader-preferences.js";
38
+ import { NotificationsClient as ct } from "./client/notifications.js";
39
+ import { ReaderPreferencesClient as lt } from "./client/reader-preferences.js";
40
+ import { createContentKitClient as ut } from "./client/client.js";
41
+ import { LETTERS as dt, attributeInput as ft, fold as pt, fromLocalInput as mt, humanize as ht, letterOf as gt, parseAttribute as _t, parseAttributes as vt, slugify as yt, toLocalInput as bt } from "./client/group-fields.js";
42
+ import { Beacons as xt, eventId as St, readerSession as Ct } from "./client/beacons.js";
43
+ import { emptyReaderMeta as wt, matchAudio as Tt, readerMetaProblems as Et, readerNames as Dt } from "./client/reader-meta.js";
44
+ export { he as ABR_DEFAULTS, qe as BansClient, xt as Beacons, st as BrowserReaderPreferences, i as CLIENT_ERROR_CODES, Ve as CatalogClient, He as CodesClient, Je as CommentsClient, a as ContentKitError, Ue as ContentsClient, e as DEFAULT_DENSITY, Ye as DiscoveryClient, o as ERROR_CODES, nt as EntitiesClient, Qe as FavoritesClient, We as IMAGE_SCHEME, ae as ItemUploadQueue, dt as LETTERS, T as MAX_PART_SIZE, E as MAX_SINGLE_PUT, D as MIN_PART_SIZE, w as MediaApi, N as MediaClient, $e as ModerationClient, ge as NETWORK_FAILURES_BEFORE_ERROR, ct as NotificationsClient, rt as OffersClient, Fe as PLAYBACK_SPEEDS, Ie as PLAYER_TRACKS_KEY, Le as PLAYER_VOLUME_KEY, k as Pacer, et as PollsClient, ue as READ_CHUNK, _e as REFRESH_BEFORE_EXPIRY, tt as ReactionsClient, lt as ReaderPreferencesClient, it as StatsClient, at as VersionsClient, ve as abrHlsConfig, L as acceptOf, g as aspectOf, ft as attributeInput, P as backoff, v as call, ye as capRung, Y as centeredCrop, b as checkRef, de as chunkOffsets, be as classifyHlsError, xe as classifyMediaError, X as constrainCrop, ut as createContentKitClient, oe as decodeImage, R as defaultImage, A as defaultTransport, t as densityFor, Z as editOf, Q as editedSize, wt as emptyReaderMeta, ie as encodeRemaining, St as eventId, Se as expiryDelay, s as failureError, j as fetchTransport, z as filesFor, f as fill, pt as fold, Oe as formatDuration, mt as fromLocalInput, $ as fromRotated, ke as galleryItems, Ce as hlsConfig, ht as humanize, Ge as imageRef, Ke as imageRefs, we as initialEstimate, se as isAnimatedImage, Ae as isAudioType, x as isContentId, c as isContentKitError, je as isImageType, B as isPattern, ot as isPending, fe as isProcessing, Me as isSubtitleType, Ne as isVideoType, S as itemKey, gt as letterOf, Tt as matchAudio, pe as mergeReads, _t as parseAttribute, vt as parseAttributes, Re as parseSpriteVtt, n as pickRendition, ze as playerKeyAction, Xe as popularBetween, Ze as popularQuery, me as processing, p as publicRenditions, m as publicURL, _ as ratio, V as ratioLabel, ce as reactionVerb, l as readContentKitError, Et as readerMetaProblems, Dt as readerNames, Ct as readerSession, Te as refreshable, Be as resumeAt, ee as rotation, y as routeURL, H as ruleDir, U as ruleFor, te as sameEdit, C as sameItem, F as samePath, le as sameTarget, W as screenFiles, O as sha256Hex, yt as slugify, r as sortRenditions, h as srcSet, G as stableImage, Pe as stageAspect, Ee as startRung, De as statusKind, I as stem, u as toContentKitError, bt as toLocalInput, ne as toOriginal, re as toRotated, d as transportFailure, K as uniqueName, q as uploadRules, J as withMediaType, M as xhrTransport };
@@ -42,19 +42,18 @@ function T(e, t) {
42
42
  t
43
43
  ]);
44
44
  }
45
- function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d, titlePosition: p = "below", loadMedia: E = !0, groupKinds: k, chips: A = 3, filterEntity: j, entityStyle: M = "chips", metadata: N, showCounts: ee = !0, tiers: P, labels: F, owner: I = !0, ownerHref: L, decorate: R, children: z, className: te }) {
46
- let B = n(), { t: V } = B, ne = t(), H = o ? o(r) : r.path, U = T(H, f(() => c?.(r), [c, r])), W = w(B, r.access, P, F), G = r.version, K = r.viewer && r.viewer.progress_max > 0 ? Math.min(1, r.viewer.progress / r.viewer.progress_max) : 0, q = (k ?? Object.keys(r.entities)).flatMap((e) => (r.entities[e] ?? []).filter((t) => !j || j(t, e)).slice(0, A).map((t) => ({
45
+ function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d, titlePosition: p = "below", loadMedia: E = !0, groupKinds: k, chips: A = 3, filterEntity: j, entityStyle: M = "chips", metadata: N, showCounts: P = !0, tiers: F, labels: I, owner: L = !0, ownerHref: R, decorate: ee, children: te, className: ne }) {
46
+ let z = n(), { t: B } = z, re = t(), V = o ? o(r) : r.path, H = T(V, f(() => c?.(r), [c, r])), U = w(z, r.access, F, I), W = r.version, G = r.viewer && r.viewer.progress_max > 0 ? Math.min(1, r.viewer.progress / r.viewer.progress_max) : 0, K = (k ?? Object.keys(r.entities)).flatMap((e) => (r.entities[e] ?? []).filter((t) => !j || j(t, e)).slice(0, A).map((t) => ({
47
47
  kind: e,
48
48
  entity: t
49
- }))), J = G.duration_ms ? Math.round(G.duration_ms / 6e4) : 0, Y = N?.traits, re = Y === !1 ? [] : Array.isArray(Y) ? G.traits.filter((e) => Y.includes(e)) : G.traits, X = [
50
- ...N?.languages === !1 ? [] : G.languages.map(ne),
51
- ...re.map((e) => u(F, e)),
52
- N?.pages !== !1 && G.pages ? B.plural("contents.pages", G.pages) : null,
53
- N?.duration !== !1 && J ? B.plural("contents.minutes", J) : null
54
- ].filter(Boolean), Z = r.title || r.code, Q = q.length > 0 ? /* @__PURE__ */ h("ul", {
49
+ }))), q = W.duration_ms ? Math.round(W.duration_ms / 6e4) : 0, J = N?.traits, Y = J === !1 ? [] : Array.isArray(J) ? W.traits.filter((e) => J.includes(e)) : W.traits, X = [
50
+ ...N?.languages === !1 ? [] : W.languages.map(re),
51
+ N?.pages !== !1 && W.pages ? z.plural("contents.pages", W.pages) : null,
52
+ N?.duration !== !1 && q ? z.plural("contents.minutes", q) : null
53
+ ].filter(Boolean), Z = r.title || r.code, Q = K.length > 0 ? /* @__PURE__ */ h("ul", {
55
54
  className: _("flex flex-wrap", M === "text" ? "gap-x-2 gap-y-1" : "gap-1"),
56
55
  "data-ckui": "card-groups",
57
- children: q.map(({ kind: e, entity: t }) => /* @__PURE__ */ h("li", {
56
+ children: K.map(({ kind: e, entity: t }) => /* @__PURE__ */ h("li", {
58
57
  "data-kind": e,
59
58
  children: /* @__PURE__ */ h(O, {
60
59
  url: t.path,
@@ -63,36 +62,36 @@ function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d,
63
62
  })
64
63
  }, `${e}:${t.id}`))
65
64
  }) : null, $ = /* @__PURE__ */ g("a", {
66
- href: H,
67
- onClick: U,
68
- className: _("font-medium break-words focus-visible:underline", p === "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"),
65
+ href: V,
66
+ onClick: H,
67
+ className: _("text-lg leading-snug font-medium break-words focus-visible:underline sm:text-xl", p === "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"),
69
68
  "data-ckui": "card-title",
70
69
  children: [/* @__PURE__ */ h("span", {
71
70
  className: "line-clamp-2",
72
71
  children: Z
73
- }), W && /* @__PURE__ */ g("span", {
72
+ }), U && /* @__PURE__ */ g("span", {
74
73
  className: "sr-only",
75
74
  children: [
76
75
  " (",
77
- W,
76
+ U,
78
77
  ")"
79
78
  ]
80
79
  })]
81
80
  }), [ie, ae] = m(null);
82
81
  return /* @__PURE__ */ g("article", {
83
82
  ref: ae,
84
- className: _("group/card grid content-start gap-2", te),
83
+ className: _("group/card grid content-start gap-2", ne),
85
84
  "data-ckui": "content-card",
86
85
  "data-id": r.id,
87
- "data-locked": W ? "" : void 0,
86
+ "data-locked": U ? "" : void 0,
88
87
  lang: r.language || void 0,
89
88
  children: [/* @__PURE__ */ g("div", {
90
89
  className: "relative overflow-hidden rounded-lg",
91
90
  "data-ckui": "card-cover",
92
91
  children: [
93
92
  /* @__PURE__ */ g("a", {
94
- href: H,
95
- onClick: U,
93
+ href: V,
94
+ onClick: H,
96
95
  className: "relative block overflow-hidden rounded-lg bg-muted",
97
96
  tabIndex: -1,
98
97
  "aria-hidden": !0,
@@ -117,17 +116,17 @@ function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d,
117
116
  "aria-hidden": !0
118
117
  })
119
118
  }),
120
- E && G.hls && /* @__PURE__ */ h(i, {
119
+ E && W.hls && /* @__PURE__ */ h(i, {
121
120
  item: {
122
121
  kind: r.kind,
123
122
  id: r.id,
124
- version: G.id
123
+ version: W.id
125
124
  },
126
- dir: G.hls,
127
- duration: G.duration_ms ? G.duration_ms / 1e3 : void 0,
125
+ dir: W.hls,
126
+ duration: W.duration_ms ? W.duration_ms / 1e3 : void 0,
128
127
  target: ie
129
128
  }),
130
- W && /* @__PURE__ */ g("span", {
129
+ U && /* @__PURE__ */ g("span", {
131
130
  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",
132
131
  "data-ckui": "card-lock",
133
132
  children: [/* @__PURE__ */ h(x, {
@@ -135,14 +134,14 @@ function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d,
135
134
  strokeWidth: 2,
136
135
  className: "size-3.5",
137
136
  "aria-hidden": !0
138
- }), W]
137
+ }), U]
139
138
  }),
140
- K > 0 && /* @__PURE__ */ h("span", {
139
+ G > 0 && /* @__PURE__ */ h("span", {
141
140
  className: _("absolute inset-x-0 bottom-0 h-1 bg-background/60", p === "cover" && "z-20"),
142
141
  "data-ckui": "card-progress",
143
142
  children: /* @__PURE__ */ h("span", {
144
143
  className: "block h-full bg-primary",
145
- style: { width: `${Math.round(K * 100)}%` }
144
+ style: { width: `${Math.round(G * 100)}%` }
146
145
  })
147
146
  })
148
147
  ]
@@ -150,7 +149,7 @@ function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d,
150
149
  p === "cover" && $,
151
150
  E && /* @__PURE__ */ h(S, {
152
151
  card: r,
153
- decorate: R,
152
+ decorate: ee,
154
153
  aboveTitle: p === "cover"
155
154
  })
156
155
  ]
@@ -159,11 +158,22 @@ function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d,
159
158
  children: [
160
159
  p === "below" && $,
161
160
  M === "text" && Q,
162
- I && r.owner && /* @__PURE__ */ h(D, {
161
+ L && r.owner && /* @__PURE__ */ h(D, {
163
162
  group: r.owner,
164
- href: L,
163
+ href: R,
165
164
  loadMedia: E
166
165
  }),
166
+ Y.length > 0 && /* @__PURE__ */ h("div", {
167
+ className: "flex flex-wrap gap-1",
168
+ "data-ckui": "card-traits",
169
+ children: Y.map((e) => /* @__PURE__ */ h(a, {
170
+ variant: "outline",
171
+ className: "h-auto rounded-md text-sm text-muted-foreground",
172
+ "data-ckui": "card-trait",
173
+ "data-trait": e,
174
+ children: u(I, e)
175
+ }, e))
176
+ }),
167
177
  X.length > 0 && /* @__PURE__ */ h("p", {
168
178
  className: "text-xs text-muted-foreground",
169
179
  children: X.join(" · ")
@@ -171,19 +181,19 @@ function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d,
171
181
  r.viewer?.completed && /* @__PURE__ */ h(a, {
172
182
  variant: "secondary",
173
183
  className: "justify-self-start",
174
- children: V("contents.completed")
184
+ children: B("contents.completed")
175
185
  }),
176
186
  !r.access.allowed && r.access.free_after && /* @__PURE__ */ h("p", {
177
187
  className: "text-xs text-muted-foreground",
178
- children: V("contents.freeFrom", { date: s(B, r.access.free_after, !1) })
188
+ children: B("contents.freeFrom", { date: s(z, r.access.free_after, !1) })
179
189
  }),
180
190
  M === "chips" && Q,
181
- ee && (r.counts.favorites !== void 0 || r.counts.likes !== void 0) && /* @__PURE__ */ g("p", {
191
+ P && (r.counts.favorites !== void 0 || r.counts.likes !== void 0) && /* @__PURE__ */ g("p", {
182
192
  className: "flex items-center gap-3 text-xs text-muted-foreground tabular-nums",
183
193
  "data-ckui": "card-counts",
184
194
  children: [r.counts.favorites !== void 0 && /* @__PURE__ */ g("span", {
185
195
  className: "inline-flex items-center gap-1",
186
- title: V("contents.favorites"),
196
+ title: B("contents.favorites"),
187
197
  children: [
188
198
  /* @__PURE__ */ h(x, {
189
199
  icon: v,
@@ -193,13 +203,13 @@ function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d,
193
203
  }),
194
204
  /* @__PURE__ */ h("span", {
195
205
  className: "sr-only",
196
- children: V("contents.favorites")
206
+ children: B("contents.favorites")
197
207
  }),
198
- C(r.counts.favorites, B.language)
208
+ C(r.counts.favorites, z.language)
199
209
  ]
200
210
  }), r.counts.likes !== void 0 && /* @__PURE__ */ g("span", {
201
211
  className: "inline-flex items-center gap-1",
202
- title: V("contents.likes"),
212
+ title: B("contents.likes"),
203
213
  children: [
204
214
  /* @__PURE__ */ h(x, {
205
215
  icon: b,
@@ -209,13 +219,13 @@ function E({ card: r, href: o, onOpen: c, shape: l = "portrait", aspectRatio: d,
209
219
  }),
210
220
  /* @__PURE__ */ h("span", {
211
221
  className: "sr-only",
212
- children: V("contents.likes")
222
+ children: B("contents.likes")
213
223
  }),
214
- C(r.counts.likes, B.language)
224
+ C(r.counts.likes, z.language)
215
225
  ]
216
226
  })]
217
227
  }),
218
- z
228
+ te
219
229
  ]
220
230
  })]
221
231
  });
@@ -243,7 +253,7 @@ function D({ group: e, href: t, className: n, loadMedia: r = !0 }) {
243
253
  });
244
254
  }
245
255
  function O({ url: e, name: t, variant: n = "chips" }) {
246
- let r = T(e), i = n === "text" ? "text-sm text-muted-foreground" : "inline-block rounded-md border border-border px-1.5 py-0.5 text-xs text-muted-foreground";
256
+ let r = T(e), i = n === "text" ? "text-sm text-muted-foreground" : "inline-block rounded-md border border-border px-1.5 py-0.5 text-sm text-muted-foreground";
247
257
  return e ? /* @__PURE__ */ h("a", {
248
258
  href: e,
249
259
  onClick: r,
@@ -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 { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { ContentKitContext } from \"../../react/context.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 /** 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 /** Cover shape: \"portrait\" (3:4, readers) or \"landscape\" (16:9, players). Default portrait. */\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 /** 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, href, onOpen, shape = \"portrait\", aspectRatio, titlePosition = \"below\", loadMedia = true, groupKinds, chips = 3, filterEntity, entityStyle = \"chips\", metadata, showCounts = true, tiers, labels, owner = true, ownerHref, decorate, children, className }: ContentCardProps) {\n const m = useMessages();\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 && 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 entities = kinds.flatMap((kind) => (card.entities[kind] ?? []).filter((entity) => !filterEntity || filterEntity(entity, kind)).slice(0, chips).map((entity) => ({ kind, entity })));\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 ...traits.map((x) => labelOf(labels, x)),\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 entityList = entities.length > 0 ? <ul className={cn(\"flex flex-wrap\", entityStyle === \"text\" ? \"gap-x-2 gap-y-1\" : \"gap-1\")} data-ckui=\"card-groups\">\n {entities.map(({ kind, entity }) => <li key={`${kind}:${entity.id}`} data-kind={kind}><Chip url={entity.path} name={entity.name || entity.slug} variant={entityStyle} /></li>)}\n </ul> : null;\n const titleLink = <a href={url} onClick={onClick} className={cn(\"font-medium break-words focus-visible:underline\", 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 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={aspectRatio === undefined ? shape === \"landscape\" ? \"aspect-video\" : \"aspect-[3/4]\" : undefined} style={aspectRatio === undefined ? undefined : { aspectRatio }}>\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 {meta.length > 0 && <p className=\"text-xs text-muted-foreground\">{meta.join(\" · \")}</p>}\n {card.viewer?.completed && (\n <Badge variant=\"secondary\" className=\"justify-self-start\">\n {t(\"contents.completed\")}\n </Badge>\n )}\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-xs 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\" | \"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(\"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, 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 { useLanguageName, useMessages } from \"../../i18n/context.js\";\nimport type { Translator } from \"../../i18n/messages.js\";\nimport { ContentKitContext } from \"../../react/context.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 /** 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 /** Cover shape: \"portrait\" (3:4, readers) or \"landscape\" (16:9, players). Default portrait. */\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 /** 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, href, onOpen, shape = \"portrait\", aspectRatio, titlePosition = \"below\", loadMedia = true, groupKinds, chips = 3, filterEntity, entityStyle = \"chips\", metadata, showCounts = true, tiers, labels, owner = true, ownerHref, decorate, children, className }: ContentCardProps) {\n const m = useMessages();\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 && 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 entities = kinds.flatMap((kind) => (card.entities[kind] ?? []).filter((entity) => !filterEntity || filterEntity(entity, kind)).slice(0, chips).map((entity) => ({ kind, entity })));\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 entityList = entities.length > 0 ? <ul className={cn(\"flex flex-wrap\", entityStyle === \"text\" ? \"gap-x-2 gap-y-1\" : \"gap-1\")} data-ckui=\"card-groups\">\n {entities.map(({ kind, entity }) => <li key={`${kind}:${entity.id}`} data-kind={kind}><Chip url={entity.path} name={entity.name || entity.slug} variant={entityStyle} /></li>)}\n </ul> : 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 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={aspectRatio === undefined ? shape === \"landscape\" ? \"aspect-video\" : \"aspect-[3/4]\" : undefined} style={aspectRatio === undefined ? undefined : { aspectRatio }}>\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.viewer?.completed && (\n <Badge variant=\"secondary\" className=\"justify-self-start\">\n {t(\"contents.completed\")}\n </Badge>\n )}\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\" | \"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(\"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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"}
@@ -2,7 +2,12 @@ import { ContentKitUiAppearance } from '../../appearance.js';
2
2
  import { ContentKitClient } from '../../client/client.js';
3
3
  import { ReleaseDatesQuery } from '../../client/discovery.js';
4
4
  import { ContentKitErrorHandler } from '../../react/context.js';
5
+ import { Labels } from '../groups/parts.js';
5
6
  export interface ContentFilterSettingsProps {
7
+ /** Content kinds to show. Omitted displays every configured kind. */
8
+ kinds?: readonly string[];
9
+ /** Display labels for content-kind sections, when more than one is shown. */
10
+ labels?: Labels;
6
11
  /** Entity names in this language; default the provider's. */
7
12
  language?: string;
8
13
  /** Offer listing deleted content the reader holds. Default true. */