@openrails/contentkit-ui 0.93.1 → 0.95.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +77 -5
  2. package/dist/client/browser-reader-preferences.d.ts +38 -0
  3. package/dist/client/browser-reader-preferences.js +172 -0
  4. package/dist/client/browser-reader-preferences.js.map +1 -0
  5. package/dist/client/client.d.ts +3 -0
  6. package/dist/client/client.js +27 -25
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/discovery.d.ts +14 -1
  9. package/dist/client/discovery.js +36 -22
  10. package/dist/client/discovery.js.map +1 -1
  11. package/dist/client/generated/routes.d.ts +33 -7
  12. package/dist/client/generated/routes.js +57 -2
  13. package/dist/client/generated/routes.js.map +1 -1
  14. package/dist/client/generated/wire.d.ts +15 -0
  15. package/dist/client/generated/wire.js.map +1 -1
  16. package/dist/client/http.d.ts +18 -1
  17. package/dist/client/http.js +67 -26
  18. package/dist/client/http.js.map +1 -1
  19. package/dist/client/index.d.ts +3 -1
  20. package/dist/client/reader-preferences.d.ts +46 -0
  21. package/dist/client/reader-preferences.js +147 -0
  22. package/dist/client/reader-preferences.js.map +1 -0
  23. package/dist/client.js +17 -15
  24. package/dist/components/content/card.d.ts +21 -5
  25. package/dist/components/content/card.js +80 -67
  26. package/dist/components/content/card.js.map +1 -1
  27. package/dist/components/content/daily-releases.d.ts +15 -0
  28. package/dist/components/content/daily-releases.js +167 -0
  29. package/dist/components/content/daily-releases.js.map +1 -0
  30. package/dist/components/content/filters.d.ts +1 -1
  31. package/dist/components/content/filters.js +1 -5
  32. package/dist/components/content/filters.js.map +1 -1
  33. package/dist/components/content/grid.d.ts +14 -1
  34. package/dist/components/content/grid.js +58 -32
  35. package/dist/components/content/grid.js.map +1 -1
  36. package/dist/components/content/index.d.ts +4 -2
  37. package/dist/components/content/popular.d.ts +11 -0
  38. package/dist/components/content/popular.js +49 -0
  39. package/dist/components/content/popular.js.map +1 -0
  40. package/dist/components/content/reader-settings.d.ts +1 -1
  41. package/dist/components/content/reader-settings.js +67 -44
  42. package/dist/components/content/reader-settings.js.map +1 -1
  43. package/dist/index.js +22 -20
  44. package/dist/locales/discovery/de.js +6 -0
  45. package/dist/locales/discovery/de.js.map +1 -1
  46. package/dist/locales/discovery/en.d.ts +6 -0
  47. package/dist/locales/discovery/en.js +6 -0
  48. package/dist/locales/discovery/en.js.map +1 -1
  49. package/dist/locales/discovery/es.js +6 -0
  50. package/dist/locales/discovery/es.js.map +1 -1
  51. package/dist/locales/discovery/ja.js +6 -0
  52. package/dist/locales/discovery/ja.js.map +1 -1
  53. package/dist/locales/discovery/ko.js +6 -0
  54. package/dist/locales/discovery/ko.js.map +1 -1
  55. package/dist/locales/discovery/zh.js +6 -0
  56. package/dist/locales/discovery/zh.js.map +1 -1
  57. package/dist/locales/en.d.ts +6 -0
  58. package/dist/react/context.d.ts +1 -1
  59. package/dist/react/context.js.map +1 -1
  60. package/dist/react/discovery.d.ts +14 -4
  61. package/dist/react/discovery.js +196 -149
  62. package/dist/react/discovery.js.map +1 -1
  63. package/dist/react/index.d.ts +2 -1
  64. package/dist/react/provider.d.ts +4 -1
  65. package/dist/react/provider.js +38 -31
  66. package/dist/react/provider.js.map +1 -1
  67. package/dist/react/reader-preferences.d.ts +13 -0
  68. package/dist/react/reader-preferences.js +15 -0
  69. package/dist/react/reader-preferences.js.map +1 -0
  70. package/dist/react/resources.js +11 -11
  71. package/dist/react/resources.js.map +1 -1
  72. package/dist/react.js +10 -9
  73. package/dist/styles.css +1 -1
  74. package/package.json +1 -1
package/README.md CHANGED
@@ -290,18 +290,60 @@ cover at 3:1.
290
290
 
291
291
  ### Card lists: decorations and locked cards
292
292
 
293
- Every card list (`ContentGrid`, `ContentListing`, `ContentRail`,
293
+ Every card list (`ContentGrid`, `ContentListing`, `ContentRail`, `DailyReleases`,
294
294
  `SearchResults`, `ContentHistory`) and `ContentCard` take `decorate(card)`:
295
295
  the host's badges, drawn over the cover (place them yourself; clicks reach
296
296
  the card, and click signals stay intact). Cards carry what badges need:
297
297
  `access.level` and `access.tiers` (the effective rule, the same for every
298
298
  reader), `version.has_audio`, `viewer.favorited` and `entities`.
299
299
 
300
+ Hosts can tailor the details without replacing the card. For example,
301
+ `metadata={{ languages: false, traits: ["colored"], duration: false }}` keeps
302
+ the page count and the Colored trait, and `showCounts={false}` hides reaction
303
+ totals. `groupKinds={["series"]}` selects series chips;
304
+ `filterEntity={(entity, kind) => ...}` filters them before the chip limit.
305
+ Defaults continue to show all normal metadata, entity chips and totals.
306
+
307
+ Set `grid={{ minCardWidth: 320, mobileColumns: 2 }}` for large covers in a fluid
308
+ grid: two columns on small screens, then as many columns as the available
309
+ width supports. This is supported by card grids, daily releases, Popular and
310
+ `ContentRail` previews; an unconfigured rail remains horizontal. There is no
311
+ desktop column ceiling, and other apps keep their existing layout by default.
312
+
300
313
  `ContentGrid` and `ContentListing` take `locked`: `show` (default) marks the
301
314
  cards the reader doesn't open, `hide` leaves them out, and `collapse` lists
302
315
  the open ones and puts the rest behind a counted toggle that loads them on
303
316
  opening (`useLockedCount` counts them).
304
317
 
318
+ ### Daily releases
319
+
320
+ ```tsx
321
+ <DailyReleases query={{ kind: "gallery", days: 10 }} initialVisible={30} />
322
+ ```
323
+
324
+ `DailyReleases` fetches all card metadata for the calendar window in one
325
+ `GET /releases` request. It uses the browser's IANA time zone unless
326
+ `query.time_zone` is supplied. Days appear newest first as keyboard-accessible
327
+ disclosures, with reduced-motion support. The initial card budget is shared
328
+ across days, newest first. Opening older days or choosing **Show more** reveals
329
+ already-fetched cards; it does not make another listing request.
330
+
331
+ Only revealed cards occupy the grid, and images/previews mount when those cards
332
+ enter the viewport. Collapsing a day removes its media. The standard card
333
+ properties (`href`, `decorate`, `groupKinds`, `tiers`, and so on) remain available.
334
+ For a custom layout, `useDailyReleases(query)` exposes the same shared read and
335
+ `client.discovery.releases(query)` exposes the typed request directly.
336
+
337
+ `published_within` filters a listing by publication age independently of its
338
+ ranking window. For example, `{ sort: "popular:7d", published_within: "7d" }`
339
+ ranks only content published during the past seven days.
340
+
341
+ `<PopularContentGrid query={{ kind: "gallery" }} />` supplies the 7-day,
342
+ 90-day, 365-day and all-time selector, keeps score and age windows matched,
343
+ and loads further pages as the reader scrolls. `period` and `onPeriodChange`
344
+ optionally keep its state in the host's URL. For a homepage preview, pass
345
+ `popularQuery("7d", { kind: "gallery", limit: 6 })` to a `ContentRail`'s `list`.
346
+
305
347
  ### MediaFolderEditor
306
348
 
307
349
  ```tsx
@@ -761,7 +803,37 @@ the host owns search-page navigation.
761
803
  />
762
804
  ```
763
805
 
764
- Render these under `ContentKitProvider`. Keep its `viewer` current: guests can
765
- change language, while signed-in readers also see the site's configured content
766
- filters. Filter toggles save through ContentKit immediately. The language callback
767
- belongs to the host so it can update its route and stored preference together.
806
+ Render these under `ContentKitProvider` with `viewer={null}` for guests. Guest
807
+ language choices and filter overrides are stored per app in localStorage and
808
+ shared across tabs. Account filter choices come from ContentKit's database;
809
+ account languages use the provider's `languagePreference` adapter:
810
+
811
+ ```tsx
812
+ <ContentKitProvider
813
+ client={contentkit}
814
+ viewer={user?.id ?? null}
815
+ language={routeLanguage}
816
+ languagePreference={{
817
+ load: (accountId) => loadAccountLanguage(accountId),
818
+ save: (language, accountId) => saveAccountLanguage(accountId, language),
819
+ }}
820
+ >
821
+ {children}
822
+ </ContentKitProvider>
823
+ ```
824
+
825
+ The adapter must check the expected account before reading or writing. Return
826
+ null from `load` when no language is saved. A localized URL does not change a
827
+ stored preference: only an explicit language selection does. The language
828
+ callback still belongs to the host's router.
829
+
830
+ On a confirmed **new registration**, call
831
+ `contentkit.preferences.inheritAfterRegistration(userId)`. Never call it for an
832
+ ordinary sign-in. The captured guest choices are retried once that account's
833
+ authenticated transport is ready; failed transfers remain pending in localStorage.
834
+ Existing account filter records are preserved. A confirmed new registration
835
+ inherits the guest language over its initial default; retries preserve a later
836
+ language choice. Ordinary sign-ins never import guest preferences.
837
+ `useReaderPreferences()` exposes the preferred language, pending/error state and
838
+ a retry action. ReaderSettings shows an incomplete transfer and lets the reader
839
+ retry it. Guest preferences are retained separately after registration and logout.
@@ -0,0 +1,38 @@
1
+ import { FilterSetting } from './generated/wire.js';
2
+ export interface GuestReaderPreferences {
3
+ language?: string;
4
+ filters: readonly FilterSetting[];
5
+ }
6
+ export interface PendingReaderPreferences extends GuestReaderPreferences {
7
+ account: string;
8
+ languageDone: boolean;
9
+ filtersDone: boolean;
10
+ languageObserved?: boolean;
11
+ initialLanguage?: string | null;
12
+ }
13
+ /** Anonymous preferences and registration retries, isolated to one app URL. */
14
+ export declare class BrowserReaderPreferences {
15
+ private readonly key;
16
+ private readonly browser?;
17
+ private value;
18
+ private publicValue;
19
+ private encoded;
20
+ private unavailable;
21
+ private readonly listeners;
22
+ constructor(key: string, browser?: Window | undefined);
23
+ readonly snapshot: () => GuestReaderPreferences;
24
+ readonly subscribe: (listener: () => void) => (() => void);
25
+ setLanguage(value: string): void;
26
+ setFilters(value: readonly FilterSetting[]): void;
27
+ pending(account: string): PendingReaderPreferences | undefined;
28
+ /** Capture only on a confirmed registration, never on a login transition. */
29
+ register(account: string): void;
30
+ complete(account: string, step: "language" | "filters"): void;
31
+ observeLanguage(account: string, initialLanguage: string | null): void;
32
+ private readonly onStorage;
33
+ private sync;
34
+ private save;
35
+ private replace;
36
+ }
37
+ /** Same-app clients share a store; server-rendered requests never do. */
38
+ export declare function browserReaderPreferences(mount: string): BrowserReaderPreferences;
@@ -0,0 +1,172 @@
1
+ //#region src/client/browser-reader-preferences.ts
2
+ var e = Object.freeze({
3
+ filters: Object.freeze([]),
4
+ registrations: Object.freeze([])
5
+ });
6
+ function t(e) {
7
+ if (!(typeof e != "string" || e.length > 64)) try {
8
+ return Intl.getCanonicalLocales(e)[0];
9
+ } catch {
10
+ return;
11
+ }
12
+ }
13
+ function n(e) {
14
+ if (!Array.isArray(e)) return [];
15
+ let t = /* @__PURE__ */ new Map();
16
+ for (let n of e.slice(0, 100)) n && typeof n.group_id == "string" && n.group_id.length > 0 && n.group_id.length <= 128 && typeof n.visible == "boolean" && t.set(n.group_id, n.visible);
17
+ return [...t].sort(([e], [t]) => e.localeCompare(t)).map(([e, t]) => ({
18
+ group_id: e,
19
+ visible: t
20
+ }));
21
+ }
22
+ function r(r) {
23
+ try {
24
+ let i = JSON.parse(r ?? "{}");
25
+ if (!i || typeof i != "object") return e;
26
+ let a = i, o = [];
27
+ if (Array.isArray(a.registrations)) for (let e of a.registrations.slice(0, 20)) !e || typeof e.account != "string" || !e.account || e.account.length > 128 || o.some((t) => t.account === e.account) || o.push({
28
+ account: e.account,
29
+ language: t(e.language),
30
+ filters: n(e.filters),
31
+ languageDone: e.languageDone === !0,
32
+ filtersDone: e.filtersDone === !0,
33
+ languageObserved: e.languageObserved === !0,
34
+ initialLanguage: e.initialLanguage === null ? null : t(e.initialLanguage)
35
+ });
36
+ return {
37
+ language: t(a.language),
38
+ filters: n(a.filters),
39
+ registrations: o
40
+ };
41
+ } catch {
42
+ return e;
43
+ }
44
+ }
45
+ var i = class {
46
+ key;
47
+ browser;
48
+ value = e;
49
+ publicValue = e;
50
+ encoded = JSON.stringify(e);
51
+ unavailable = !1;
52
+ listeners = /* @__PURE__ */ new Set();
53
+ constructor(e, t) {
54
+ this.key = e, this.browser = t, this.sync();
55
+ }
56
+ snapshot = () => this.publicValue;
57
+ subscribe = (e) => (this.sync(), this.listeners.size || this.browser?.addEventListener("storage", this.onStorage), this.listeners.add(e), () => {
58
+ this.listeners.delete(e), this.listeners.size || this.browser?.removeEventListener("storage", this.onStorage);
59
+ });
60
+ setLanguage(e) {
61
+ let n = t(e);
62
+ if (!n) throw Error("contentkit: invalid preferred language");
63
+ this.sync(), this.save({
64
+ ...this.value,
65
+ language: n
66
+ });
67
+ }
68
+ setFilters(e) {
69
+ this.sync(), this.save({
70
+ ...this.value,
71
+ filters: n(e)
72
+ });
73
+ }
74
+ pending(e) {
75
+ return this.sync(), this.value.registrations.find((t) => t.account === e);
76
+ }
77
+ register(e) {
78
+ if (this.sync(), !e || e.length > 128) throw Error("contentkit: registration account is required");
79
+ if (!this.pending(e) && (this.value.language || this.value.filters.length)) {
80
+ if (this.value.registrations.length >= 20) throw Error("contentkit: too many pending preference registrations");
81
+ this.save({
82
+ ...this.value,
83
+ registrations: [...this.value.registrations, {
84
+ account: e,
85
+ language: this.value.language,
86
+ filters: this.value.filters,
87
+ languageDone: !this.value.language,
88
+ filtersDone: !this.value.filters.length
89
+ }]
90
+ });
91
+ }
92
+ }
93
+ complete(e, t) {
94
+ this.sync();
95
+ let n = this.value.registrations.map((n) => n.account === e ? {
96
+ ...n,
97
+ [t === "language" ? "languageDone" : "filtersDone"]: !0
98
+ } : n).filter((e) => !e.languageDone || !e.filtersDone);
99
+ this.save({
100
+ ...this.value,
101
+ registrations: n
102
+ });
103
+ }
104
+ observeLanguage(e, t) {
105
+ this.sync(), this.save({
106
+ ...this.value,
107
+ registrations: this.value.registrations.map((n) => n.account === e && !n.languageObserved ? {
108
+ ...n,
109
+ languageObserved: !0,
110
+ initialLanguage: t
111
+ } : n)
112
+ });
113
+ }
114
+ onStorage = (e) => {
115
+ (e.key === null || e.key === this.key) && this.sync();
116
+ };
117
+ sync() {
118
+ if (this.browser && !this.unavailable) try {
119
+ this.replace(r(this.browser.localStorage.getItem(this.key)));
120
+ } catch {
121
+ this.unavailable = !0;
122
+ }
123
+ }
124
+ save(e) {
125
+ if (this.browser && !this.unavailable) try {
126
+ this.browser.localStorage.setItem(this.key, JSON.stringify(e));
127
+ } catch {
128
+ this.unavailable = !0;
129
+ }
130
+ this.replace(e);
131
+ }
132
+ replace(e) {
133
+ let t = JSON.stringify({
134
+ language: e.language,
135
+ filters: e.filters,
136
+ registrations: e.registrations
137
+ });
138
+ if (t === this.encoded) return;
139
+ this.encoded = t, this.value = Object.freeze({
140
+ ...e,
141
+ filters: Object.freeze(e.filters.map((e) => Object.freeze({ ...e }))),
142
+ registrations: Object.freeze(e.registrations.map((e) => Object.freeze({
143
+ ...e,
144
+ filters: Object.freeze(e.filters.map((e) => Object.freeze({ ...e })))
145
+ })))
146
+ });
147
+ let n = {
148
+ language: this.value.language,
149
+ filters: this.value.filters
150
+ };
151
+ JSON.stringify(n) !== JSON.stringify(this.publicValue) && (this.publicValue = Object.freeze(n));
152
+ for (let e of this.listeners) try {
153
+ e();
154
+ } catch {}
155
+ }
156
+ }, a = /* @__PURE__ */ new WeakMap();
157
+ function o(e) {
158
+ let t = typeof window > "u" ? void 0 : window, n = e.replace(/\/+$/, "");
159
+ if (t) try {
160
+ n = new URL(n, t.location.href).href;
161
+ } catch {}
162
+ let r = `contentkit:reader-preferences:v1:${n}`;
163
+ if (!t) return new i(r);
164
+ let o = a.get(t);
165
+ o || a.set(t, o = /* @__PURE__ */ new Map());
166
+ let s = o.get(r);
167
+ return s || o.set(r, s = new i(r, t)), s;
168
+ }
169
+ //#endregion
170
+ export { i as BrowserReaderPreferences, o as browserReaderPreferences };
171
+
172
+ //# sourceMappingURL=browser-reader-preferences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-reader-preferences.js","names":[],"sources":["../../src/client/browser-reader-preferences.ts"],"sourcesContent":["import type { FilterSetting } from \"./generated/wire.js\";\n\nexport interface GuestReaderPreferences {\n language?: string;\n filters: readonly FilterSetting[];\n}\n\nexport interface PendingReaderPreferences extends GuestReaderPreferences {\n account: string;\n languageDone: boolean;\n filtersDone: boolean;\n languageObserved?: boolean;\n initialLanguage?: string | null;\n}\n\ntype Document = GuestReaderPreferences & { registrations: readonly PendingReaderPreferences[] };\nconst EMPTY: Document = Object.freeze({ filters: Object.freeze([]), registrations: Object.freeze([]) });\n\nfunction language(value: unknown): string | undefined {\n if (typeof value !== \"string\" || value.length > 64) return;\n try { return Intl.getCanonicalLocales(value)[0]; } catch { return; }\n}\n\nfunction filters(value: unknown): FilterSetting[] {\n if (!Array.isArray(value)) return [];\n const entries = new Map<string, boolean>();\n for (const row of value.slice(0, 100)) {\n if (row && typeof row.group_id === \"string\" && row.group_id.length > 0 && row.group_id.length <= 128 && typeof row.visible === \"boolean\") entries.set(row.group_id, row.visible);\n }\n return [...entries].sort(([a], [b]) => a.localeCompare(b)).map(([group_id, visible]) => ({ group_id, visible }));\n}\n\nfunction decode(raw: string | null): Document {\n try {\n const data: unknown = JSON.parse(raw ?? \"{}\");\n if (!data || typeof data !== \"object\") return EMPTY;\n const value = data as Record<string, unknown>;\n const registrations: PendingReaderPreferences[] = [];\n if (Array.isArray(value.registrations)) {\n for (const entry of value.registrations.slice(0, 20)) {\n if (!entry || typeof entry.account !== \"string\" || !entry.account || entry.account.length > 128 || registrations.some((r) => r.account === entry.account)) continue;\n registrations.push({ account: entry.account, language: language(entry.language), filters: filters(entry.filters), languageDone: entry.languageDone === true, filtersDone: entry.filtersDone === true,\n languageObserved: entry.languageObserved === true, initialLanguage: entry.initialLanguage === null ? null : language(entry.initialLanguage) });\n }\n }\n return { language: language(value.language), filters: filters(value.filters), registrations };\n } catch { return EMPTY; }\n}\n\n/** Anonymous preferences and registration retries, isolated to one app URL. */\nexport class BrowserReaderPreferences {\n private value: Document = EMPTY;\n private publicValue: GuestReaderPreferences = EMPTY;\n private encoded = JSON.stringify(EMPTY);\n private unavailable = false;\n private readonly listeners = new Set<() => void>();\n\n constructor(private readonly key: string, private readonly browser?: Window) { this.sync(); }\n\n readonly snapshot = (): GuestReaderPreferences => this.publicValue;\n\n readonly subscribe = (listener: () => void): (() => void) => {\n this.sync();\n if (!this.listeners.size) this.browser?.addEventListener(\"storage\", this.onStorage);\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n if (!this.listeners.size) this.browser?.removeEventListener(\"storage\", this.onStorage);\n };\n };\n\n setLanguage(value: string): void {\n const next = language(value);\n if (!next) throw new Error(\"contentkit: invalid preferred language\");\n this.sync();\n this.save({ ...this.value, language: next });\n }\n\n setFilters(value: readonly FilterSetting[]): void {\n this.sync();\n this.save({ ...this.value, filters: filters(value) });\n }\n\n pending(account: string): PendingReaderPreferences | undefined {\n this.sync();\n return this.value.registrations.find((r) => r.account === account);\n }\n\n /** Capture only on a confirmed registration, never on a login transition. */\n register(account: string): void {\n this.sync();\n if (!account || account.length > 128) throw new Error(\"contentkit: registration account is required\");\n if (this.pending(account)) return;\n if (!this.value.language && !this.value.filters.length) return;\n if (this.value.registrations.length >= 20) throw new Error(\"contentkit: too many pending preference registrations\");\n this.save({ ...this.value, registrations: [...this.value.registrations, {\n account, language: this.value.language, filters: this.value.filters,\n languageDone: !this.value.language, filtersDone: !this.value.filters.length,\n }] });\n }\n\n complete(account: string, step: \"language\" | \"filters\"): void {\n this.sync();\n const registrations = this.value.registrations.map((r) => r.account === account ? { ...r, [step === \"language\" ? \"languageDone\" : \"filtersDone\"]: true } : r)\n .filter((r) => !r.languageDone || !r.filtersDone);\n this.save({ ...this.value, registrations });\n }\n\n observeLanguage(account: string, initialLanguage: string | null): void {\n this.sync();\n this.save({ ...this.value, registrations: this.value.registrations.map((r) => r.account === account && !r.languageObserved ? { ...r, languageObserved: true, initialLanguage } : r) });\n }\n\n private readonly onStorage = (event: StorageEvent): void => {\n if (event.key === null || event.key === this.key) this.sync();\n };\n\n private sync(): void {\n if (!this.browser || this.unavailable) return;\n try { this.replace(decode(this.browser.localStorage.getItem(this.key))); }\n catch { this.unavailable = true; }\n }\n\n private save(next: Document): void {\n if (this.browser && !this.unavailable) {\n try { this.browser.localStorage.setItem(this.key, JSON.stringify(next)); }\n catch { this.unavailable = true; }\n }\n this.replace(next);\n }\n\n private replace(next: Document): void {\n const encoded = JSON.stringify({ language: next.language, filters: next.filters, registrations: next.registrations });\n if (encoded === this.encoded) return;\n this.encoded = encoded;\n this.value = Object.freeze({ ...next, filters: Object.freeze(next.filters.map((f) => Object.freeze({ ...f }))), registrations: Object.freeze(next.registrations.map((r) => Object.freeze({ ...r, filters: Object.freeze(r.filters.map((f) => Object.freeze({ ...f }))) }))) });\n const publicValue = { language: this.value.language, filters: this.value.filters };\n if (JSON.stringify(publicValue) !== JSON.stringify(this.publicValue)) this.publicValue = Object.freeze(publicValue);\n for (const listener of this.listeners) { try { listener(); } catch { /* Observers do not own persistence. */ } }\n }\n}\n\nconst stores = new WeakMap<Window, Map<string, BrowserReaderPreferences>>();\n\n/** Same-app clients share a store; server-rendered requests never do. */\nexport function browserReaderPreferences(mount: string): BrowserReaderPreferences {\n const browser = typeof window === \"undefined\" ? undefined : window;\n let scope = mount.replace(/\\/+$/, \"\");\n if (browser) { try { scope = new URL(scope, browser.location.href).href; } catch { /* Keep the explicit app key. */ } }\n const key = `contentkit:reader-preferences:v1:${scope}`;\n if (!browser) return new BrowserReaderPreferences(key);\n let scoped = stores.get(browser);\n if (!scoped) stores.set(browser, (scoped = new Map()));\n let store = scoped.get(key);\n if (!store) scoped.set(key, (store = new BrowserReaderPreferences(key, browser)));\n return store;\n}\n"],"mappings":"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"}
@@ -16,6 +16,7 @@ import { VersionsClient } from './versions.js';
16
16
  import { ContentKitChange, ContentKitModule, HttpOptions } from './http.js';
17
17
  import { MediaClient, ContentFolders, MediaOptions } from './media/client.js';
18
18
  import { NotificationsClient } from './notifications.js';
19
+ import { ReaderPreferencesClient } from './reader-preferences.js';
19
20
  export interface ContentKitClientOptions extends HttpOptions {
20
21
  /** Upload tuning (transport, part concurrency, retries). */
21
22
  media?: MediaOptions;
@@ -59,6 +60,8 @@ export interface ContentKitClient {
59
60
  readonly stats: StatsClient;
60
61
  /** The signed-in person's native and imported notifications. */
61
62
  readonly notifications: NotificationsClient;
63
+ /** Browser guest preferences and explicit registration inheritance. */
64
+ readonly preferences: ReaderPreferencesClient;
62
65
  /** What the content module allows: which interactions signed-out visitors may make. */
63
66
  config(signal?: AbortSignal): Promise<Config>;
64
67
  /** The site's content model: languages, tiers, kinds, group kinds and content filters. */
@@ -16,35 +16,37 @@ import { StatsClient as m } from "./stats.js";
16
16
  import { VersionsClient as h } from "./versions.js";
17
17
  import { Http as g } from "./http.js";
18
18
  import { NotificationsClient as _ } from "./notifications.js";
19
+ import { ReaderPreferencesClient as v } from "./reader-preferences.js";
19
20
  //#region src/client/client.ts
20
- function v(v) {
21
- let { media: y, folders: b, ...x } = v, S = new g(x), C = new t(S, y, b);
21
+ function y(y) {
22
+ let { media: b, folders: x, ...S } = y, C = new g(S), w = new t(C, b, x);
22
23
  return {
23
- media: C,
24
- contents: new i(S),
25
- catalog: new n(S),
26
- comments: new o(S),
27
- reactions: new d(S),
28
- favorites: new c(S),
29
- polls: new u(S, C),
30
- bans: new a(S),
31
- moderation: new l(S),
32
- entities: new f(S, C),
33
- discovery: new s(S),
34
- versions: new h(S),
35
- offers: new p(S),
36
- codes: new r(S),
37
- stats: new m(S),
38
- notifications: new _(S),
39
- config: (t) => e(S, "GET", "/config", { signal: t }),
40
- kinds: (t) => e(S, "GET", "/kinds", { signal: t }),
41
- import: (t) => e(S, "POST", "/import", { body: t }),
42
- url: (e, t) => S.mount(e, t),
43
- setReadScope: (e) => S.setReadScope(e),
44
- subscribe: (e) => S.subscribe(e)
24
+ media: w,
25
+ contents: new i(C),
26
+ catalog: new n(C),
27
+ comments: new o(C),
28
+ reactions: new d(C),
29
+ favorites: new c(C),
30
+ polls: new u(C, w),
31
+ bans: new a(C),
32
+ moderation: new l(C),
33
+ entities: new f(C, w),
34
+ discovery: new s(C),
35
+ versions: new h(C),
36
+ offers: new p(C),
37
+ codes: new r(C),
38
+ stats: new m(C),
39
+ notifications: new _(C),
40
+ preferences: new v(C),
41
+ config: (t) => e(C, "GET", "/config", { signal: t }),
42
+ kinds: (t) => e(C, "GET", "/kinds", { signal: t }),
43
+ import: (t) => e(C, "POST", "/import", { body: t }),
44
+ url: (e, t) => C.mount(e, t),
45
+ setReadScope: (e) => C.setReadScope(e),
46
+ subscribe: (e) => C.subscribe(e)
45
47
  };
46
48
  }
47
49
  //#endregion
48
- export { v as createContentKitClient };
50
+ export { y as createContentKitClient };
49
51
 
50
52
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../src/client/client.ts"],"sourcesContent":["import { CatalogClient } from \"./catalog.js\";\nimport { CodesClient } from \"./codes.js\";\nimport { ContentsClient } from \"./contents.js\";\nimport { BansClient } from \"./content/bans.js\";\nimport { CommentsClient } from \"./content/comments.js\";\nimport { FavoritesClient } from \"./content/favorites.js\";\nimport { ModerationClient } from \"./content/moderation.js\";\nimport { PollsClient } from \"./content/polls.js\";\nimport { ReactionsClient } from \"./content/reactions.js\";\nimport type { Batch, Config, ContentModel, RefBody, Result } from \"./generated/wire.js\";\nimport { DiscoveryClient } from \"./discovery.js\";\nimport { EntitiesClient } from \"./entities.js\";\nimport { OffersClient } from \"./offers.js\";\nimport { StatsClient } from \"./stats.js\";\nimport { VersionsClient } from \"./versions.js\";\nimport { Http, type ContentKitChange, type ContentKitModule, type HttpOptions } from \"./http.js\";\nimport { MediaClient, type ContentFolders, type MediaOptions } from \"./media/client.js\";\nimport { call } from \"./route.js\";\nimport { NotificationsClient } from \"./notifications.js\";\n\nexport interface ContentKitClientOptions extends HttpOptions {\n /** Upload tuning (transport, part concurrency, retries). */\n media?: MediaOptions;\n /** The media kind of poll folders, when the host renamed it (content.Media). */\n folders?: ContentFolders;\n}\n\n/**\n * One ContentKit deployment as the browser sees it. Each module is a property\n * over one shared transport (mounts, auth, language, errors, change events).\n */\nexport interface ContentKitClient {\n /** Uploads, multipart, commits, reads, frames, HLS bases, the HLS loader and inline images. */\n readonly media: MediaClient;\n /** Content items: reads, and writes for their creators, owner-group editors and staff. */\n readonly contents: ContentsClient;\n /** The catalog's totals by kind, language and audience. */\n readonly catalog: CatalogClient;\n /** Threads, replies, edits, tombstones, reactions, standing, the latest feed, moderation. */\n readonly comments: CommentsClient;\n /** Likes and dislikes of an item. */\n readonly reactions: ReactionsClient;\n /** The signed-in caller's favorites. */\n readonly favorites: FavoritesClient;\n /** Polls: votes, free-text answers and the staff editor. */\n readonly polls: PollsClient;\n /** Comment bans, owner and global. */\n readonly bans: BansClient;\n /** The review queue of held comments and content. */\n readonly moderation: ModerationClient;\n /** Groups: listings, pages, staff writes and memberships. */\n readonly entities: EntitiesClient;\n /** Lists, search, typeahead, rails, release dates, content filters, history, signals and exposures. */\n readonly discovery: DiscoveryClient;\n /** Content versions: reader plans, reader metadata, forks and downloads. */\n readonly versions: VersionsClient;\n /** Offers on contents, versions and groups; group joins; the reader's library. */\n readonly offers: OffersClient;\n /** Content-code lookup. */\n readonly codes: CodesClient;\n /** Staff activity and rankings; the signed-in reader's own statistics. */\n readonly stats: StatsClient;\n /** The signed-in person's native and imported notifications. */\n readonly notifications: NotificationsClient;\n\n /** What the content module allows: which interactions signed-out visitors may make. */\n config(signal?: AbortSignal): Promise<Config>;\n /** The site's content model: languages, tiers, kinds, group kinds and content filters. */\n kinds(signal?: AbortSignal): Promise<ContentModel>;\n /** Staff bulk import (root:import:write): groups, contents, memberships, offers and aliases, idempotent per source. */\n import(batch: Batch): Promise<Result>;\n /** A module's base URL as configured (no trailing slash). */\n url(module: ContentKitModule, ref?: RefBody): string;\n /** Cache context only, not authentication. The React provider keeps this in sync. */\n setReadScope(scope: string): void;\n /** Every successful mutation; a null scope means its data predates a context change. */\n subscribe(listener: (change: ContentKitChange, scope: string | null) => void): () => void;\n}\n\nexport function createContentKitClient(options: ContentKitClientOptions): ContentKitClient {\n const { media: mediaOptions, folders, ...http } = options;\n const h = new Http(http);\n const media = new MediaClient(h, mediaOptions, folders);\n return {\n media,\n contents: new ContentsClient(h),\n catalog: new CatalogClient(h),\n comments: new CommentsClient(h),\n reactions: new ReactionsClient(h),\n favorites: new FavoritesClient(h),\n polls: new PollsClient(h, media),\n bans: new BansClient(h),\n moderation: new ModerationClient(h),\n entities: new EntitiesClient(h, media),\n discovery: new DiscoveryClient(h),\n versions: new VersionsClient(h),\n offers: new OffersClient(h),\n codes: new CodesClient(h),\n stats: new StatsClient(h),\n notifications: new NotificationsClient(h),\n config: (signal) => call(h, \"GET\", \"/config\", { signal }),\n kinds: (signal) => call(h, \"GET\", \"/kinds\", { signal }),\n import: (batch) => call(h, \"POST\", \"/import\", { body: batch }),\n url: (module, ref) => h.mount(module, ref),\n setReadScope: (scope) => h.setReadScope(scope),\n subscribe: (listener) => h.subscribe(listener),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+EA,SAAgB,EAAuB,GAAoD;CACzF,IAAM,EAAE,OAAO,GAAc,YAAS,GAAG,MAAS,GAC5C,IAAI,IAAI,EAAK,CAAI,GACjB,IAAQ,IAAI,EAAY,GAAG,GAAc,CAAO;CACtD,OAAO;EACL;EACA,UAAU,IAAI,EAAe,CAAC;EAC9B,SAAS,IAAI,EAAc,CAAC;EAC5B,UAAU,IAAI,EAAe,CAAC;EAC9B,WAAW,IAAI,EAAgB,CAAC;EAChC,WAAW,IAAI,EAAgB,CAAC;EAChC,OAAO,IAAI,EAAY,GAAG,CAAK;EAC/B,MAAM,IAAI,EAAW,CAAC;EACtB,YAAY,IAAI,EAAiB,CAAC;EAClC,UAAU,IAAI,EAAe,GAAG,CAAK;EACrC,WAAW,IAAI,EAAgB,CAAC;EAChC,UAAU,IAAI,EAAe,CAAC;EAC9B,QAAQ,IAAI,EAAa,CAAC;EAC1B,OAAO,IAAI,EAAY,CAAC;EACxB,OAAO,IAAI,EAAY,CAAC;EACxB,eAAe,IAAI,EAAoB,CAAC;EACxC,SAAS,MAAW,EAAK,GAAG,OAAO,WAAW,EAAE,UAAO,CAAC;EACxD,QAAQ,MAAW,EAAK,GAAG,OAAO,UAAU,EAAE,UAAO,CAAC;EACtD,SAAS,MAAU,EAAK,GAAG,QAAQ,WAAW,EAAE,MAAM,EAAM,CAAC;EAC7D,MAAM,GAAQ,MAAQ,EAAE,MAAM,GAAQ,CAAG;EACzC,eAAe,MAAU,EAAE,aAAa,CAAK;EAC7C,YAAY,MAAa,EAAE,UAAU,CAAQ;CAC/C;AACF"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../src/client/client.ts"],"sourcesContent":["import { CatalogClient } from \"./catalog.js\";\nimport { CodesClient } from \"./codes.js\";\nimport { ContentsClient } from \"./contents.js\";\nimport { BansClient } from \"./content/bans.js\";\nimport { CommentsClient } from \"./content/comments.js\";\nimport { FavoritesClient } from \"./content/favorites.js\";\nimport { ModerationClient } from \"./content/moderation.js\";\nimport { PollsClient } from \"./content/polls.js\";\nimport { ReactionsClient } from \"./content/reactions.js\";\nimport type { Batch, Config, ContentModel, RefBody, Result } from \"./generated/wire.js\";\nimport { DiscoveryClient } from \"./discovery.js\";\nimport { EntitiesClient } from \"./entities.js\";\nimport { OffersClient } from \"./offers.js\";\nimport { StatsClient } from \"./stats.js\";\nimport { VersionsClient } from \"./versions.js\";\nimport { Http, type ContentKitChange, type ContentKitModule, type HttpOptions } from \"./http.js\";\nimport { MediaClient, type ContentFolders, type MediaOptions } from \"./media/client.js\";\nimport { call } from \"./route.js\";\nimport { NotificationsClient } from \"./notifications.js\";\nimport { ReaderPreferencesClient } from \"./reader-preferences.js\";\n\nexport interface ContentKitClientOptions extends HttpOptions {\n /** Upload tuning (transport, part concurrency, retries). */\n media?: MediaOptions;\n /** The media kind of poll folders, when the host renamed it (content.Media). */\n folders?: ContentFolders;\n}\n\n/**\n * One ContentKit deployment as the browser sees it. Each module is a property\n * over one shared transport (mounts, auth, language, errors, change events).\n */\nexport interface ContentKitClient {\n /** Uploads, multipart, commits, reads, frames, HLS bases, the HLS loader and inline images. */\n readonly media: MediaClient;\n /** Content items: reads, and writes for their creators, owner-group editors and staff. */\n readonly contents: ContentsClient;\n /** The catalog's totals by kind, language and audience. */\n readonly catalog: CatalogClient;\n /** Threads, replies, edits, tombstones, reactions, standing, the latest feed, moderation. */\n readonly comments: CommentsClient;\n /** Likes and dislikes of an item. */\n readonly reactions: ReactionsClient;\n /** The signed-in caller's favorites. */\n readonly favorites: FavoritesClient;\n /** Polls: votes, free-text answers and the staff editor. */\n readonly polls: PollsClient;\n /** Comment bans, owner and global. */\n readonly bans: BansClient;\n /** The review queue of held comments and content. */\n readonly moderation: ModerationClient;\n /** Groups: listings, pages, staff writes and memberships. */\n readonly entities: EntitiesClient;\n /** Lists, search, typeahead, rails, release dates, content filters, history, signals and exposures. */\n readonly discovery: DiscoveryClient;\n /** Content versions: reader plans, reader metadata, forks and downloads. */\n readonly versions: VersionsClient;\n /** Offers on contents, versions and groups; group joins; the reader's library. */\n readonly offers: OffersClient;\n /** Content-code lookup. */\n readonly codes: CodesClient;\n /** Staff activity and rankings; the signed-in reader's own statistics. */\n readonly stats: StatsClient;\n /** The signed-in person's native and imported notifications. */\n readonly notifications: NotificationsClient;\n /** Browser guest preferences and explicit registration inheritance. */\n readonly preferences: ReaderPreferencesClient;\n\n /** What the content module allows: which interactions signed-out visitors may make. */\n config(signal?: AbortSignal): Promise<Config>;\n /** The site's content model: languages, tiers, kinds, group kinds and content filters. */\n kinds(signal?: AbortSignal): Promise<ContentModel>;\n /** Staff bulk import (root:import:write): groups, contents, memberships, offers and aliases, idempotent per source. */\n import(batch: Batch): Promise<Result>;\n /** A module's base URL as configured (no trailing slash). */\n url(module: ContentKitModule, ref?: RefBody): string;\n /** Cache context only, not authentication. The React provider keeps this in sync. */\n setReadScope(scope: string): void;\n /** Every successful mutation; a null scope means its data predates a context change. */\n subscribe(listener: (change: ContentKitChange, scope: string | null) => void): () => void;\n}\n\nexport function createContentKitClient(options: ContentKitClientOptions): ContentKitClient {\n const { media: mediaOptions, folders, ...http } = options;\n const h = new Http(http);\n const media = new MediaClient(h, mediaOptions, folders);\n return {\n media,\n contents: new ContentsClient(h),\n catalog: new CatalogClient(h),\n comments: new CommentsClient(h),\n reactions: new ReactionsClient(h),\n favorites: new FavoritesClient(h),\n polls: new PollsClient(h, media),\n bans: new BansClient(h),\n moderation: new ModerationClient(h),\n entities: new EntitiesClient(h, media),\n discovery: new DiscoveryClient(h),\n versions: new VersionsClient(h),\n offers: new OffersClient(h),\n codes: new CodesClient(h),\n stats: new StatsClient(h),\n notifications: new NotificationsClient(h),\n preferences: new ReaderPreferencesClient(h),\n config: (signal) => call(h, \"GET\", \"/config\", { signal }),\n kinds: (signal) => call(h, \"GET\", \"/kinds\", { signal }),\n import: (batch) => call(h, \"POST\", \"/import\", { body: batch }),\n url: (module, ref) => h.mount(module, ref),\n setReadScope: (scope) => h.setReadScope(scope),\n subscribe: (listener) => h.subscribe(listener),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkFA,SAAgB,EAAuB,GAAoD;CACzF,IAAM,EAAE,OAAO,GAAc,YAAS,GAAG,MAAS,GAC5C,IAAI,IAAI,EAAK,CAAI,GACjB,IAAQ,IAAI,EAAY,GAAG,GAAc,CAAO;CACtD,OAAO;EACL;EACA,UAAU,IAAI,EAAe,CAAC;EAC9B,SAAS,IAAI,EAAc,CAAC;EAC5B,UAAU,IAAI,EAAe,CAAC;EAC9B,WAAW,IAAI,EAAgB,CAAC;EAChC,WAAW,IAAI,EAAgB,CAAC;EAChC,OAAO,IAAI,EAAY,GAAG,CAAK;EAC/B,MAAM,IAAI,EAAW,CAAC;EACtB,YAAY,IAAI,EAAiB,CAAC;EAClC,UAAU,IAAI,EAAe,GAAG,CAAK;EACrC,WAAW,IAAI,EAAgB,CAAC;EAChC,UAAU,IAAI,EAAe,CAAC;EAC9B,QAAQ,IAAI,EAAa,CAAC;EAC1B,OAAO,IAAI,EAAY,CAAC;EACxB,OAAO,IAAI,EAAY,CAAC;EACxB,eAAe,IAAI,EAAoB,CAAC;EACxC,aAAa,IAAI,EAAwB,CAAC;EAC1C,SAAS,MAAW,EAAK,GAAG,OAAO,WAAW,EAAE,UAAO,CAAC;EACxD,QAAQ,MAAW,EAAK,GAAG,OAAO,UAAU,EAAE,UAAO,CAAC;EACtD,SAAS,MAAU,EAAK,GAAG,QAAQ,WAAW,EAAE,MAAM,EAAM,CAAC;EAC7D,MAAM,GAAQ,MAAQ,EAAE,MAAM,GAAQ,CAAG;EACzC,eAAe,MAAU,EAAE,aAAa,CAAK;EAC7C,YAAY,MAAa,EAAE,UAAU,CAAQ;CAC/C;AACF"}
@@ -1,4 +1,4 @@
1
- import { ContentFilters, ContentFiltersUpdate, ContentPage, ExposureInput, RecentSearches, ReleaseDates, SignalInput, Typeahead } from './generated/wire.js';
1
+ import { ContentFilters, ContentFiltersUpdate, ContentPage, DailyReleases, ExposureInput, RecentSearches, ReleaseDates, SignalInput, Typeahead } from './generated/wire.js';
2
2
  import { Http } from './http.js';
3
3
  import { BrowserSearchHistory } from './browser-search-history.js';
4
4
  /** A popularity period. */
@@ -28,6 +28,8 @@ export interface ContentFilterQuery {
28
28
  attribute?: readonly string[];
29
29
  /** Published on this day (YYYY-MM-DD) or month (YYYY-MM). */
30
30
  released_on?: string;
31
+ /** Only contents published within this age window; independent of the popularity score window. */
32
+ published_within?: Period;
31
33
  /** IANA time zone of released_on and release dates; UTC by default. */
32
34
  tz?: string;
33
35
  /** The signed-in reader's unseen, in-progress or completed contents. */
@@ -52,6 +54,10 @@ export interface PageQuery {
52
54
  export type ContentQuery = ContentFilterQuery & ReaderQuery & CardQuery & PageQuery & {
53
55
  sort?: ListSort;
54
56
  };
57
+ export type PopularPeriod = "7d" | "90d" | "365d" | "all";
58
+ export type PopularQuery = Omit<ContentQuery, "sort" | "published_within">;
59
+ /** Match the popularity score window and publication age window. */
60
+ export declare function popularQuery(period?: PopularPeriod, filters?: PopularQuery): ContentQuery;
55
61
  export type SearchQuery = ContentFilterQuery & ReaderQuery & CardQuery & PageQuery & {
56
62
  q: string;
57
63
  sort?: "relevance" | ListSort;
@@ -71,6 +77,11 @@ export type RailQuery = ContentFilterQuery & ReaderQuery & CardQuery & {
71
77
  export type ReleaseDatesQuery = ContentFilterQuery & ReaderQuery & PageQuery & {
72
78
  by?: "month" | "day";
73
79
  };
80
+ /** Complete daily metadata for one calendar window, without pagination. */
81
+ export type DailyReleasesQuery = Omit<ContentFilterQuery, "released_on" | "tz"> & ReaderQuery & CardQuery & {
82
+ days?: number;
83
+ time_zone?: string;
84
+ };
74
85
  export type HistoryQuery = ReaderQuery & CardQuery & PageQuery & {
75
86
  kind?: string;
76
87
  status?: "seen" | "in_progress" | "completed";
@@ -114,6 +125,8 @@ export declare class DiscoveryClient {
114
125
  recommendations(q?: RailQuery, signal?: AbortSignal): Promise<ContentPage>;
115
126
  /** Per month or day, newest first: how many contents the reader's listing shows released then, and how many are free. */
116
127
  releaseDates(q?: ReleaseDatesQuery, signal?: AbortSignal): Promise<ReleaseDates>;
128
+ /** Every visible card in each day; opening or expanding a day needs no further request. */
129
+ releases(q?: DailyReleasesQuery, signal?: AbortSignal): Promise<DailyReleases>;
117
130
  contentFilters(language?: string, signal?: AbortSignal): Promise<ContentFilters>;
118
131
  /** Replaces the reader's filter settings; every read applies them. */
119
132
  setContentFilters(update: ContentFiltersUpdate, language?: string): Promise<ContentFilters>;
@@ -1,7 +1,14 @@
1
- import { call as e } from "./route.js";
2
- import { browserSearchHistory as t } from "./browser-search-history.js";
1
+ import { call as e, routeURL as t } from "./route.js";
2
+ import { browserSearchHistory as n } from "./browser-search-history.js";
3
3
  //#region src/client/discovery.ts
4
- var n = /* @__PURE__ */ new Set([
4
+ function r(e = "7d", t = {}) {
5
+ return {
6
+ ...t,
7
+ sort: `popular:${e}`,
8
+ published_within: e
9
+ };
10
+ }
11
+ var i = /* @__PURE__ */ new Set([
5
12
  "groups",
6
13
  "exclude_groups",
7
14
  "creators",
@@ -11,29 +18,29 @@ var n = /* @__PURE__ */ new Set([
11
18
  "card_entities",
12
19
  "entity_kinds"
13
20
  ]);
14
- function r(e) {
21
+ function a(e) {
15
22
  let t = {};
16
- for (let [r, i] of Object.entries(e)) t[r] = n.has(r) && Array.isArray(i) ? i.length ? i.join(",") : void 0 : i;
23
+ for (let [n, r] of Object.entries(e)) t[n] = i.has(n) && Array.isArray(r) ? r.length ? r.join(",") : void 0 : r;
17
24
  return t;
18
25
  }
19
- var i = class {
26
+ var o = class {
20
27
  http;
21
28
  browserSearches;
22
29
  constructor(e) {
23
- this.http = e, this.browserSearches = t(e.mount("discovery"));
30
+ this.http = e, this.browserSearches = n(e.mount("discovery"));
24
31
  }
25
32
  list(t = {}, n) {
26
33
  return e(this.http, "GET", "/contents", {
27
- query: r(t),
34
+ query: a(t),
28
35
  signal: n
29
36
  });
30
37
  }
31
38
  async search(t, n) {
32
- let i = t.cursor ? null : this.http.captureChanges(), a = await e(this.http, "GET", "/search", {
33
- query: r(t),
39
+ let r = t.cursor ? null : this.http.captureChanges(), i = await e(this.http, "GET", "/search", {
40
+ query: a(t),
34
41
  signal: n
35
42
  });
36
- return i?.({ type: "searches.changed" }), a;
43
+ return r?.({ type: "searches.changed" }), i;
37
44
  }
38
45
  recentSearches(t = {}, n) {
39
46
  return e(this.http, "GET", "/me/searches", {
@@ -47,35 +54,42 @@ var i = class {
47
54
  }
48
55
  typeahead(t, n) {
49
56
  return e(this.http, "GET", "/typeahead", {
50
- query: r(t),
57
+ query: a(t),
51
58
  signal: n
52
59
  });
53
60
  }
54
- similar(t, n = {}, i) {
61
+ similar(t, n = {}, r) {
55
62
  return e(this.http, "GET", "/contents/{id}/similar", {
56
63
  params: { id: t },
57
- query: r(n),
58
- signal: i
64
+ query: a(n),
65
+ signal: r
59
66
  });
60
67
  }
61
68
  recommendations(t = {}, n) {
62
69
  return e(this.http, "GET", "/recommendations", {
63
- query: r(t),
70
+ query: a(t),
64
71
  signal: n
65
72
  });
66
73
  }
67
74
  releaseDates(t = {}, n) {
68
75
  return e(this.http, "GET", "/release-dates", {
69
- query: r(t),
76
+ query: a(t),
70
77
  signal: n
71
78
  });
72
79
  }
73
- contentFilters(t, n) {
74
- return e(this.http, "GET", "/me/content-filters", {
75
- query: { language: t },
80
+ releases(t = {}, n) {
81
+ return e(this.http, "GET", "/releases", {
82
+ query: a(t),
76
83
  signal: n
77
84
  });
78
85
  }
86
+ async contentFilters(e, n) {
87
+ let r = await this.http.request(t(this.http, "GET", "/content-filters", { query: { language: e } }), {
88
+ signal: n,
89
+ guestPreferences: !1
90
+ });
91
+ return this.http.acceptGuestFilters(r.filters), r;
92
+ }
79
93
  async setContentFilters(t, n) {
80
94
  let r = this.http.captureChanges(), i = await e(this.http, "PUT", "/me/content-filters", {
81
95
  query: { language: n },
@@ -85,7 +99,7 @@ var i = class {
85
99
  }
86
100
  history(t = {}, n) {
87
101
  return e(this.http, "GET", "/me/history", {
88
- query: r(t),
102
+ query: a(t),
89
103
  signal: n
90
104
  });
91
105
  }
@@ -115,6 +129,6 @@ var i = class {
115
129
  }
116
130
  };
117
131
  //#endregion
118
- export { i as DiscoveryClient, r as query };
132
+ export { o as DiscoveryClient, r as popularQuery, a as query };
119
133
 
120
134
  //# sourceMappingURL=discovery.js.map