@openrails/contentkit-ui 0.102.0 → 0.103.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 (52) hide show
  1. package/README.md +23 -17
  2. package/dist/client/browser-reader-preferences.d.ts +8 -9
  3. package/dist/client/browser-reader-preferences.js +71 -63
  4. package/dist/client/browser-reader-preferences.js.map +1 -1
  5. package/dist/client/discovery.d.ts +1 -1
  6. package/dist/client/discovery.js +2 -2
  7. package/dist/client/discovery.js.map +1 -1
  8. package/dist/client/generated/routes.d.ts +1 -1
  9. package/dist/client/generated/routes.js +1 -1
  10. package/dist/client/generated/routes.js.map +1 -1
  11. package/dist/client/generated/wire.d.ts +5 -10
  12. package/dist/client/generated/wire.js.map +1 -1
  13. package/dist/client/http.d.ts +3 -4
  14. package/dist/client/http.js +7 -15
  15. package/dist/client/http.js.map +1 -1
  16. package/dist/client/index.d.ts +1 -1
  17. package/dist/client/reader-preferences.d.ts +2 -2
  18. package/dist/client/reader-preferences.js +2 -6
  19. package/dist/client/reader-preferences.js.map +1 -1
  20. package/dist/components/content/daily-releases.js +39 -43
  21. package/dist/components/content/daily-releases.js.map +1 -1
  22. package/dist/components/content/filters.d.ts +2 -6
  23. package/dist/components/content/filters.js +57 -66
  24. package/dist/components/content/filters.js.map +1 -1
  25. package/dist/components/content/reader-settings.d.ts +0 -1
  26. package/dist/components/content/reader-settings.js +1 -2
  27. package/dist/components/content/reader-settings.js.map +1 -1
  28. package/dist/components/content/release-date-picker.d.ts +4 -4
  29. package/dist/components/content/release-date-picker.js +106 -48
  30. package/dist/components/content/release-date-picker.js.map +1 -1
  31. package/dist/index.js +1 -1
  32. package/dist/locales/discovery/de.js +1 -2
  33. package/dist/locales/discovery/de.js.map +1 -1
  34. package/dist/locales/discovery/en.d.ts +1 -2
  35. package/dist/locales/discovery/en.js +1 -2
  36. package/dist/locales/discovery/en.js.map +1 -1
  37. package/dist/locales/discovery/es.js +1 -2
  38. package/dist/locales/discovery/es.js.map +1 -1
  39. package/dist/locales/discovery/ja.js +1 -2
  40. package/dist/locales/discovery/ja.js.map +1 -1
  41. package/dist/locales/discovery/ko.js +1 -2
  42. package/dist/locales/discovery/ko.js.map +1 -1
  43. package/dist/locales/discovery/zh.js +1 -2
  44. package/dist/locales/discovery/zh.js.map +1 -1
  45. package/dist/locales/en.d.ts +1 -2
  46. package/dist/react/discovery.d.ts +3 -5
  47. package/dist/react/discovery.js +42 -71
  48. package/dist/react/discovery.js.map +1 -1
  49. package/dist/react/provider.js +1 -4
  50. package/dist/react/provider.js.map +1 -1
  51. package/dist/styles.css +1 -1
  52. package/package.json +2 -1
package/README.md CHANGED
@@ -367,13 +367,15 @@ the listing's time zone. The same bounded window ends on that date; it does
367
367
  not load intervening years. Omit it for the latest releases, or combine it
368
368
  with `days: 1` for one exact day. Future and invalid dates return HTTP 400.
369
369
  Current visibility and access rules still apply to historical windows.
370
- `ReleaseDatePicker` supplies the native calendar, editable year and **Latest**
371
- reset. Keep its controlled value in your router to support shared URLs and
372
- back/forward navigation:
370
+ `ReleaseDatePicker` opens a themed calendar popover with month/year dropdowns,
371
+ so older dates need no month-by-month paging. It selects one day, disables
372
+ future dates, and closes after selection; there is no typed input or reset
373
+ button. Keep its controlled value in your router for shared URLs and
374
+ back/forward navigation. The app chooses placement and the listing window:
373
375
 
374
376
  ```tsx
375
377
  <ReleaseDatePicker value={date} onValueChange={setDate} />
376
- <DailyReleases query={{ kind: "gallery", days: 10, end_date: date || undefined }} />
378
+ <DailyReleases query={{ kind: "gallery", days: date ? 1 : 10, end_date: date || undefined }} />
377
379
  ```
378
380
 
379
381
  `isReleaseDate(date)` validates URL input before rendering the feed; the picker
@@ -919,24 +921,28 @@ account languages use the provider's `languagePreference` adapter. Each domain
919
921
  only offers its configured, user-toggleable entities; internal classifications
920
922
  and required or locked rules never appear in reader settings. Labels use the
921
923
  entities' localized names. Saving this domain's settings preserves choices for
922
- entities offered by other domains and other content kinds. `kinds` limits the
923
- controls shown by either `ReaderSettings` or `ContentFilterSettings`; omit it to
924
- show all configured kinds. Multiple kinds get separate headings; `labels` can
925
- supply their display names. The same entity can have independent gallery and
926
- blog preferences. Resets follow the current domain default, including after it
927
- changes. Old unscoped browser choices remain a fallback until replaced
928
- by a choice for that content kind.
924
+ entities offered by other domains. The configured `applies_to` content kinds
925
+ share one preference per entity, so a tag toggle can affect galleries and blog
926
+ posts together. `kinds` selects a listing context and displays controls when it
927
+ intersects `applies_to`; it never creates a second per-kind preference. Rows use
928
+ entity names and have no duplicate type headings.
929
929
 
930
- The HTTP shape groups preferences by kind:
930
+ The HTTP shape is flat and updates only the submitted entities:
931
931
 
932
932
  ```json
933
- {"kinds":{"gallery":[{"entity_id":"tag-id","visible":false}]},"include_deleted":false}
933
+ {"filters":[{"entity_id":"tag-id","visible":false}]}
934
934
  ```
935
935
 
936
- `PUT /me/content-filters` replaces only the submitted kinds. An empty gallery
937
- list resets that kind to its domain defaults without changing blog preferences.
938
- The registration initializer also accepts `legacy_filters` so old browser
939
- choices can be transferred without dropping preferences for another domain.
936
+ `PATCH /me/content-filters` preserves omitted entities and `include_deleted`.
937
+ `setVisible(entityId, visible)` performs that one-entity update. Reset a choice
938
+ with `{entity_id, default: true}`; the reset follows current domain defaults and
939
+ suppresses old legacy choices. Independent account updates merge atomically.
940
+
941
+ Guest storage migrates v1 choices and pending registration snapshots into v2,
942
+ keeping the original v1 bytes. Conflicting old per-kind choices resolve to
943
+ hidden; explicit scoped resets suppress the older unscoped fallback. New shared
944
+ choices override all old values. Registration initialization uses the flat
945
+ `filters` array and never overwrites an existing account's settings.
940
946
 
941
947
  ```tsx
942
948
  <ContentKitProvider
@@ -1,4 +1,3 @@
1
- import { FilterSetting } from './generated/wire.js';
2
1
  /** Explicit visibility or a reset that follows the current domain default. */
3
2
  export type ReaderFilterChoice = {
4
3
  entity_id: string;
@@ -9,12 +8,10 @@ export type ReaderFilterChoice = {
9
8
  default: true;
10
9
  visible?: never;
11
10
  };
12
- export type ReaderFilterKinds = Record<string, readonly ReaderFilterChoice[]>;
11
+ export type ReaderFilters = readonly ReaderFilterChoice[];
13
12
  export interface GuestReaderPreferences {
14
13
  language?: string;
15
- kinds: ReaderFilterKinds;
16
- /** Choices saved before content kinds were introduced. Explicit kind choices win. */
17
- legacy_filters: readonly FilterSetting[];
14
+ filters: ReaderFilters;
18
15
  }
19
16
  export interface PendingReaderPreferences extends GuestReaderPreferences {
20
17
  account: string;
@@ -23,22 +20,24 @@ export interface PendingReaderPreferences extends GuestReaderPreferences {
23
20
  languageObserved?: boolean;
24
21
  initialLanguage?: string | null;
25
22
  }
26
- /** Anonymous preferences and registration retries, isolated to one app URL. */
23
+ /** Anonymous choices and registration retries, isolated to one catalog URL.
24
+ * v1 is read once when v2 is absent, and never modified, so old tabs cannot
25
+ * clobber a new shared choice and a rollback still has the original data. */
27
26
  export declare class BrowserReaderPreferences {
28
27
  private readonly key;
29
28
  private readonly browser?;
29
+ private readonly legacyKey?;
30
30
  private value;
31
31
  private publicValue;
32
32
  private encoded;
33
33
  private unavailable;
34
34
  private readonly listeners;
35
- constructor(key: string, browser?: Window | undefined);
35
+ constructor(key: string, browser?: Window | undefined, legacyKey?: string | undefined);
36
36
  readonly snapshot: () => GuestReaderPreferences;
37
37
  readonly subscribe: (listener: () => void) => (() => void);
38
38
  setLanguage(value: string): void;
39
- setFilters(value: ReaderFilterKinds): void;
39
+ setFilters(value: ReaderFilters, validate?: (next: GuestReaderPreferences) => void): void;
40
40
  pending(account: string): PendingReaderPreferences | undefined;
41
- /** Capture only on a confirmed registration, never on a login transition. */
42
41
  register(account: string): void;
43
42
  complete(account: string, step: "language" | "filters"): void;
44
43
  observeLanguage(account: string, initialLanguage: string | null): void;
@@ -1,7 +1,6 @@
1
1
  //#region src/client/browser-reader-preferences.ts
2
2
  var e = Object.freeze({
3
- kinds: Object.freeze({}),
4
- legacy_filters: Object.freeze([]),
3
+ filters: Object.freeze([]),
5
4
  registrations: Object.freeze([])
6
5
  });
7
6
  function t(e) {
@@ -12,15 +11,6 @@ function t(e) {
12
11
  }
13
12
  }
14
13
  function n(e) {
15
- if (!Array.isArray(e)) return [];
16
- let t = /* @__PURE__ */ new Map();
17
- for (let n of e) n && typeof (n.entity_id ?? n.group_id) == "string" && (n.entity_id ?? n.group_id).length > 0 && (n.entity_id ?? n.group_id).length <= 128 && typeof n.visible == "boolean" && t.set(n.entity_id ?? n.group_id, n.visible);
18
- return [...t].sort(([e], [t]) => e.localeCompare(t)).map(([e, t]) => ({
19
- entity_id: e,
20
- visible: t
21
- }));
22
- }
23
- function r(e) {
24
14
  if (!Array.isArray(e)) return [];
25
15
  let t = /* @__PURE__ */ new Map();
26
16
  for (let n of e) {
@@ -35,58 +25,67 @@ function r(e) {
35
25
  }
36
26
  return [...t].sort(([e], [t]) => e.localeCompare(t)).map(([, e]) => e);
37
27
  }
38
- function i(e) {
39
- return !e || typeof e != "object" || Array.isArray(e) ? {} : Object.fromEntries(Object.entries(e).filter(([e]) => /^[a-z][a-z0-9_]*$/.test(e)).sort(([e], [t]) => e.localeCompare(t)).map(([e, t]) => [e, r(t)]));
40
- }
41
- function a(e) {
42
- return {
43
- kinds: i(e.kinds),
44
- legacy_filters: n(e.legacy_filters ?? e.filters)
45
- };
46
- }
47
- function o(e) {
48
- return e.legacy_filters.length > 0 || Object.values(e.kinds).some((e) => e.length > 0);
28
+ function r(e) {
29
+ let t = /* @__PURE__ */ new Map(), r = e.legacy_filters ?? e.filters;
30
+ if (Array.isArray(r)) for (let e of r) {
31
+ let n = e?.entity_id ?? e?.group_id;
32
+ typeof n == "string" && n && n.length <= 128 && typeof e.visible == "boolean" && t.set(n, {
33
+ entity_id: n,
34
+ visible: e.visible
35
+ });
36
+ }
37
+ let i = /* @__PURE__ */ new Map();
38
+ if (e.kinds && typeof e.kinds == "object" && !Array.isArray(e.kinds)) for (let t of Object.values(e.kinds)) for (let e of n(t)) {
39
+ let t = e.default ? 1 : e.visible ? 2 : 3;
40
+ (i.get(e.entity_id)?.rank ?? 0) < t && i.set(e.entity_id, {
41
+ rank: t,
42
+ choice: e
43
+ });
44
+ }
45
+ for (let [e, { choice: n }] of i) t.set(e, n);
46
+ return [...t].sort(([e], [t]) => e.localeCompare(t)).map(([, e]) => e);
49
47
  }
50
- function s(e) {
48
+ function i(e) {
51
49
  return {
52
50
  language: e.language,
53
- kinds: Object.freeze(Object.fromEntries(Object.entries(e.kinds).map(([e, t]) => [e, Object.freeze(t.map((e) => Object.freeze({ ...e })))]))),
54
- legacy_filters: Object.freeze(e.legacy_filters.map((e) => Object.freeze({ ...e })))
51
+ filters: Object.freeze(e.filters.map((e) => Object.freeze({ ...e })))
55
52
  };
56
53
  }
57
- function c(n) {
54
+ function a(i, a = !1) {
58
55
  try {
59
- let r = JSON.parse(n ?? "{}");
60
- if (!r || typeof r != "object") return e;
61
- let i = r, o = [];
62
- if (Array.isArray(i.registrations)) for (let e of i.registrations.slice(0, 20)) !e || typeof e.account != "string" || !e.account || e.account.length > 128 || o.some((t) => t.account === e.account) || o.push({
63
- account: e.account,
56
+ let o = JSON.parse(i ?? "{}");
57
+ if (!o || typeof o != "object") return e;
58
+ let s = o, c = (e) => ({
64
59
  language: t(e.language),
65
- ...a(e),
60
+ filters: a ? r(e) : n(e.filters)
61
+ }), l = [];
62
+ if (Array.isArray(s.registrations)) for (let e of s.registrations.slice(0, 20)) !e || typeof e.account != "string" || !e.account || e.account.length > 128 || l.some((t) => t.account === e.account) || l.push({
63
+ account: e.account,
64
+ ...c(e),
66
65
  languageDone: e.languageDone === !0,
67
66
  filtersDone: e.filtersDone === !0,
68
67
  languageObserved: e.languageObserved === !0,
69
68
  initialLanguage: e.initialLanguage === null ? null : t(e.initialLanguage)
70
69
  });
71
70
  return {
72
- language: t(i.language),
73
- ...a(i),
74
- registrations: o
71
+ ...c(s),
72
+ registrations: l
75
73
  };
76
74
  } catch {
77
75
  return e;
78
76
  }
79
77
  }
80
- var l = class {
78
+ var o = class {
81
79
  key;
82
80
  browser;
81
+ legacyKey;
83
82
  value = e;
84
- publicValue = e;
83
+ publicValue = Object.freeze({ filters: Object.freeze([]) });
85
84
  encoded = JSON.stringify(e);
86
85
  unavailable = !1;
87
86
  listeners = /* @__PURE__ */ new Set();
88
- constructor(e, t) {
89
- this.key = e, this.browser = t, this.sync();
87
+ constructor(e, t, n) {
88
+ this.key = e, this.browser = t, this.legacyKey = n, this.sync();
90
89
  }
91
90
  snapshot = () => this.publicValue;
92
91
  subscribe = (e) => (this.sync(), this.listeners.size || this.browser?.addEventListener("storage", this.onStorage), this.listeners.add(e), () => {
@@ -100,28 +99,31 @@ var l = class {
100
99
  language: n
101
100
  });
102
101
  }
103
- setFilters(e) {
104
- this.sync(), this.save({
102
+ setFilters(e, t) {
103
+ this.sync();
104
+ let r = new Map(this.value.filters.map((e) => [e.entity_id, e]));
105
+ for (let t of n(e)) r.set(t.entity_id, t);
106
+ let i = {
105
107
  ...this.value,
106
- kinds: i(e)
107
- });
108
+ filters: [...r.values()].sort((e, t) => e.entity_id.localeCompare(t.entity_id))
109
+ };
110
+ t?.(i), this.save(i);
108
111
  }
109
112
  pending(e) {
110
113
  return this.sync(), this.value.registrations.find((t) => t.account === e);
111
114
  }
112
115
  register(e) {
113
116
  if (this.sync(), !e || e.length > 128) throw Error("contentkit: registration account is required");
114
- if (!this.pending(e) && (this.value.language || o(this.value))) {
117
+ if (!(this.pending(e) || !this.value.language && !this.value.filters.length)) {
115
118
  if (this.value.registrations.length >= 20) throw Error("contentkit: too many pending preference registrations");
116
119
  this.save({
117
120
  ...this.value,
118
121
  registrations: [...this.value.registrations, {
119
122
  account: e,
120
123
  language: this.value.language,
121
- kinds: this.value.kinds,
122
- legacy_filters: this.value.legacy_filters,
124
+ filters: this.value.filters,
123
125
  languageDone: !this.value.language,
124
- filtersDone: !o(this.value)
126
+ filtersDone: !this.value.filters.length
125
127
  }]
126
128
  });
127
129
  }
@@ -152,7 +154,15 @@ var l = class {
152
154
  };
153
155
  sync() {
154
156
  if (this.browser && !this.unavailable) try {
155
- this.replace(c(this.browser.localStorage.getItem(this.key)));
157
+ let e = this.browser.localStorage.getItem(this.key);
158
+ if (e === null && this.legacyKey) {
159
+ let e = this.browser.localStorage.getItem(this.legacyKey);
160
+ if (e !== null) {
161
+ this.save(a(e, !0));
162
+ return;
163
+ }
164
+ }
165
+ this.replace(a(e));
156
166
  } catch {
157
167
  this.unavailable = !0;
158
168
  }
@@ -168,42 +178,40 @@ var l = class {
168
178
  replace(e) {
169
179
  let t = JSON.stringify({
170
180
  language: e.language,
171
- kinds: e.kinds,
172
- legacy_filters: e.legacy_filters,
181
+ filters: e.filters,
173
182
  registrations: e.registrations
174
183
  });
175
184
  if (t === this.encoded) return;
176
185
  this.encoded = t, this.value = Object.freeze({
177
- ...s(e),
186
+ ...i(e),
178
187
  registrations: Object.freeze(e.registrations.map((e) => Object.freeze({
179
188
  ...e,
180
- ...s(e)
189
+ ...i(e)
181
190
  })))
182
191
  });
183
192
  let n = {
184
193
  language: this.value.language,
185
- kinds: this.value.kinds,
186
- legacy_filters: this.value.legacy_filters
194
+ filters: this.value.filters
187
195
  };
188
196
  JSON.stringify(n) !== JSON.stringify(this.publicValue) && (this.publicValue = Object.freeze(n));
189
197
  for (let e of this.listeners) try {
190
198
  e();
191
199
  } catch {}
192
200
  }
193
- }, u = /* @__PURE__ */ new WeakMap();
194
- function d(e) {
201
+ }, s = /* @__PURE__ */ new WeakMap();
202
+ function c(e) {
195
203
  let t = typeof window > "u" ? void 0 : window, n = e.replace(/\/+$/, "");
196
204
  if (t) try {
197
205
  n = new URL(n, t.location.href).href;
198
206
  } catch {}
199
- let r = `contentkit:reader-preferences:v1:${n}`;
200
- if (!t) return new l(r);
201
- let i = u.get(t);
202
- i || u.set(t, i = /* @__PURE__ */ new Map());
203
- let a = i.get(r);
204
- return a || i.set(r, a = new l(r, t)), a;
207
+ let r = `contentkit:reader-preferences:v2:${n}`, i = `contentkit:reader-preferences:v1:${n}`;
208
+ if (!t) return new o(r);
209
+ let a = s.get(t);
210
+ a || s.set(t, a = /* @__PURE__ */ new Map());
211
+ let c = a.get(r);
212
+ return c || a.set(r, c = new o(r, t, i)), c;
205
213
  }
206
214
  //#endregion
207
- export { l as BrowserReaderPreferences, d as browserReaderPreferences };
215
+ export { o as BrowserReaderPreferences, c as browserReaderPreferences };
208
216
 
209
217
  //# sourceMappingURL=browser-reader-preferences.js.map
@@ -1 +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\n/** Explicit visibility or a reset that follows the current domain default. */\nexport type ReaderFilterChoice = { entity_id: string; visible: boolean; default?: never } | { entity_id: string; default: true; visible?: never };\nexport type ReaderFilterKinds = Record<string, readonly ReaderFilterChoice[]>;\n\nexport interface GuestReaderPreferences {\n language?: string;\n kinds: ReaderFilterKinds;\n /** Choices saved before content kinds were introduced. Explicit kind choices win. */\n legacy_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({ kinds: Object.freeze({}), legacy_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) {\n if (row && typeof (row.entity_id ?? row.group_id) === \"string\" && (row.entity_id ?? row.group_id).length > 0 && (row.entity_id ?? row.group_id).length <= 128 && typeof row.visible === \"boolean\") entries.set(row.entity_id ?? row.group_id, row.visible);\n }\n return [...entries].sort(([a], [b]) => a.localeCompare(b)).map(([entity_id, visible]) => ({ entity_id, visible }));\n}\n\nfunction scopedFilters(value: unknown): ReaderFilterChoice[] {\n if (!Array.isArray(value)) return [];\n const entries = new Map<string, ReaderFilterChoice>();\n for (const row of value) {\n const id = row?.entity_id;\n if (typeof id !== \"string\" || !id || id.length > 128) continue;\n if (row.default === true && row.visible === undefined) entries.set(id, { entity_id: id, default: true });\n else if (row.default === undefined && typeof row.visible === \"boolean\") entries.set(id, { entity_id: id, visible: row.visible });\n }\n return [...entries].sort(([a], [b]) => a.localeCompare(b)).map(([, row]) => row);\n}\n\nfunction kinds(value: unknown): ReaderFilterKinds {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return {};\n return Object.fromEntries(Object.entries(value)\n .filter(([kind]) => /^[a-z][a-z0-9_]*$/.test(kind))\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([kind, value]) => [kind, scopedFilters(value)]));\n}\n\nfunction choices(value: Record<string, unknown>) {\n return { kinds: kinds(value.kinds), legacy_filters: filters(value.legacy_filters ?? value.filters) };\n}\n\nfunction hasChoices(value: GuestReaderPreferences): boolean {\n return value.legacy_filters.length > 0 || Object.values(value.kinds).some((rows) => rows.length > 0);\n}\n\nfunction freezeChoices(value: GuestReaderPreferences): GuestReaderPreferences {\n return { language: value.language,\n kinds: Object.freeze(Object.fromEntries(Object.entries(value.kinds).map(([kind, rows]) => [kind, Object.freeze(rows.map((f) => Object.freeze({ ...f })))]))),\n legacy_filters: Object.freeze(value.legacy_filters.map((f) => Object.freeze({ ...f }))) };\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), ...choices(entry), 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), ...choices(value), 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: ReaderFilterKinds): void {\n this.sync();\n this.save({ ...this.value, kinds: kinds(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 && !hasChoices(this.value)) 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, kinds: this.value.kinds, legacy_filters: this.value.legacy_filters,\n languageDone: !this.value.language, filtersDone: !hasChoices(this.value),\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, kinds: next.kinds, legacy_filters: next.legacy_filters, registrations: next.registrations });\n if (encoded === this.encoded) return;\n this.encoded = encoded;\n this.value = Object.freeze({ ...freezeChoices(next), registrations: Object.freeze(next.registrations.map((r) => Object.freeze({ ...r, ...freezeChoices(r) }))) });\n const publicValue = { language: this.value.language, kinds: this.value.kinds, legacy_filters: this.value.legacy_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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"}
1
+ {"version":3,"file":"browser-reader-preferences.js","names":[],"sources":["../../src/client/browser-reader-preferences.ts"],"sourcesContent":["/** Explicit visibility or a reset that follows the current domain default. */\nexport type ReaderFilterChoice =\n | { entity_id: string; visible: boolean; default?: never }\n | { entity_id: string; default: true; visible?: never };\nexport type ReaderFilters = readonly ReaderFilterChoice[];\n\nexport interface GuestReaderPreferences {\n language?: string;\n filters: ReaderFilters;\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 {\n return Intl.getCanonicalLocales(value)[0];\n } catch {\n return;\n }\n}\nfunction filters(value: unknown): ReaderFilterChoice[] {\n if (!Array.isArray(value)) return [];\n const entries = new Map<string, ReaderFilterChoice>();\n for (const row of value) {\n const id = row?.entity_id;\n if (typeof id !== \"string\" || !id || id.length > 128) continue;\n if (row.default === true && row.visible === undefined) entries.set(id, { entity_id: id, default: true });\n else if (row.default === undefined && typeof row.visible === \"boolean\")\n entries.set(id, { entity_id: id, visible: row.visible });\n }\n return [...entries].sort(([a], [b]) => a.localeCompare(b)).map(([, row]) => row);\n}\n\n// Mirrors readerprefs.Stored: scoped hide wins show; a scoped reset suppresses\n// the older unscoped fallback. New v2 documents never consult legacy values.\nfunction legacyFilters(value: Record<string, unknown>): ReaderFilterChoice[] {\n const fallback = new Map<string, ReaderFilterChoice>();\n const old = value.legacy_filters ?? value.filters;\n if (Array.isArray(old))\n for (const row of old) {\n const id = row?.entity_id ?? row?.group_id;\n if (typeof id === \"string\" && id && id.length <= 128 && typeof row.visible === \"boolean\")\n fallback.set(id, { entity_id: id, visible: row.visible });\n }\n const scoped = new Map<string, { rank: number; choice: ReaderFilterChoice }>();\n if (value.kinds && typeof value.kinds === \"object\" && !Array.isArray(value.kinds))\n for (const rows of Object.values(value.kinds))\n for (const choice of filters(rows)) {\n const rank = choice.default ? 1 : choice.visible ? 2 : 3;\n if ((scoped.get(choice.entity_id)?.rank ?? 0) < rank) scoped.set(choice.entity_id, { rank, choice });\n }\n for (const [id, { choice }] of scoped) fallback.set(id, choice);\n return [...fallback].sort(([a], [b]) => a.localeCompare(b)).map(([, choice]) => choice);\n}\nfunction freezeChoices(value: GuestReaderPreferences): GuestReaderPreferences {\n return { language: value.language, filters: Object.freeze(value.filters.map((row) => Object.freeze({ ...row }))) };\n}\nfunction decode(raw: string | null, legacy = false): Document {\n try {\n const parsed: unknown = JSON.parse(raw ?? \"{}\");\n if (!parsed || typeof parsed !== \"object\") return EMPTY;\n const value = parsed as Record<string, unknown>;\n const choices = (entry: Record<string, unknown>) => ({\n language: language(entry.language),\n filters: legacy ? legacyFilters(entry) : filters(entry.filters),\n });\n const registrations: PendingReaderPreferences[] = [];\n if (Array.isArray(value.registrations))\n for (const entry of value.registrations.slice(0, 20)) {\n if (\n !entry ||\n typeof entry.account !== \"string\" ||\n !entry.account ||\n entry.account.length > 128 ||\n registrations.some((r) => r.account === entry.account)\n )\n continue;\n registrations.push({\n account: entry.account,\n ...choices(entry),\n languageDone: entry.languageDone === true,\n filtersDone: entry.filtersDone === true,\n languageObserved: entry.languageObserved === true,\n initialLanguage: entry.initialLanguage === null ? null : language(entry.initialLanguage),\n });\n }\n return { ...choices(value), registrations };\n } catch {\n return EMPTY;\n }\n}\n\n/** Anonymous choices and registration retries, isolated to one catalog URL.\n * v1 is read once when v2 is absent, and never modified, so old tabs cannot\n * clobber a new shared choice and a rollback still has the original data. */\nexport class BrowserReaderPreferences {\n private value: Document = EMPTY;\n private publicValue: GuestReaderPreferences = Object.freeze({ filters: Object.freeze([]) });\n private encoded = JSON.stringify(EMPTY);\n private unavailable = false;\n private readonly listeners = new Set<() => void>();\n constructor(\n private readonly key: string,\n private readonly browser?: Window,\n private readonly legacyKey?: string,\n ) {\n this.sync();\n }\n readonly snapshot = (): GuestReaderPreferences => this.publicValue;\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 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 setFilters(value: ReaderFilters, validate?: (next: GuestReaderPreferences) => void): void {\n this.sync();\n const merged = new Map(this.value.filters.map((row) => [row.entity_id, row]));\n for (const row of filters(value)) merged.set(row.entity_id, row);\n const next = {\n ...this.value,\n filters: [...merged.values()].sort((a, b) => a.entity_id.localeCompare(b.entity_id)),\n };\n validate?.(next);\n this.save(next);\n }\n pending(account: string): PendingReaderPreferences | undefined {\n this.sync();\n return this.value.registrations.find((r) => r.account === account);\n }\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) || (!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({\n ...this.value,\n registrations: [\n ...this.value.registrations,\n {\n account,\n language: this.value.language,\n filters: this.value.filters,\n languageDone: !this.value.language,\n filtersDone: !this.value.filters.length,\n },\n ],\n });\n }\n complete(account: string, step: \"language\" | \"filters\"): void {\n this.sync();\n const registrations = this.value.registrations\n .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 observeLanguage(account: string, initialLanguage: string | null): void {\n this.sync();\n this.save({\n ...this.value,\n registrations: this.value.registrations.map((r) =>\n r.account === account && !r.languageObserved ? { ...r, languageObserved: true, initialLanguage } : r,\n ),\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 {\n const raw = this.browser.localStorage.getItem(this.key);\n if (raw === null && this.legacyKey) {\n const old = this.browser.localStorage.getItem(this.legacyKey);\n if (old !== null) {\n this.save(decode(old, true));\n return;\n }\n }\n this.replace(decode(raw));\n } catch {\n this.unavailable = true;\n }\n }\n\n private save(next: Document): void {\n if (this.browser && !this.unavailable) {\n try {\n this.browser.localStorage.setItem(this.key, JSON.stringify(next));\n } catch {\n this.unavailable = true;\n }\n }\n this.replace(next);\n }\n\n private replace(next: Document): void {\n const encoded = JSON.stringify({\n language: next.language,\n filters: next.filters,\n registrations: next.registrations,\n });\n if (encoded === this.encoded) return;\n this.encoded = encoded;\n this.value = Object.freeze({\n ...freezeChoices(next),\n registrations: Object.freeze(next.registrations.map((r) => Object.freeze({ ...r, ...freezeChoices(r) }))),\n });\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) {\n try {\n listener();\n } catch {\n /* Observers do not own persistence. */\n }\n }\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) {\n try {\n scope = new URL(scope, browser.location.href).href;\n } catch {\n /* Keep the explicit app key. */\n }\n }\n const key = `contentkit:reader-preferences:v2:${scope}`;\n const legacyKey = `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, legacyKey)));\n return store;\n}\n"],"mappings":"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"}
@@ -144,7 +144,7 @@ export declare class DiscoveryClient {
144
144
  releases(q?: DailyReleasesQuery, signal?: AbortSignal): Promise<DailyReleases>;
145
145
  /** Only the current domain's reader-toggleable entities, named in the requested language. */
146
146
  contentFilters(language?: string, signal?: AbortSignal): Promise<ContentFilters>;
147
- /** Replaces this domain's reader settings; other domains' choices remain intact. */
147
+ /** Atomically changes submitted shared choices; omitted settings remain intact. */
148
148
  setContentFilters(update: ContentFiltersUpdate, language?: string): Promise<ContentFilters>;
149
149
  /** What the reader engaged with, most recent first. */
150
150
  history(q?: HistoryQuery, signal?: AbortSignal): Promise<ContentPage>;
@@ -98,10 +98,10 @@ var s = class {
98
98
  signal: n,
99
99
  guestPreferences: !1
100
100
  });
101
- return this.http.acceptGuestFilters(r.kinds), r;
101
+ return this.http.acceptGuestFilters(r.filters), r;
102
102
  }
103
103
  async setContentFilters(t, n) {
104
- let r = this.http.captureChanges(), i = await e(this.http, "PUT", "/me/content-filters", {
104
+ let r = this.http.captureChanges(), i = await e(this.http, "PATCH", "/me/content-filters", {
105
105
  query: { language: n },
106
106
  body: t
107
107
  });
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","names":[],"sources":["../../src/client/discovery.ts"],"sourcesContent":["import type {\n ActivityContexts,\n ActivityContextsInput,\n ActivityTarget,\n ContentFilters,\n ContentFiltersUpdate,\n ContentPage,\n DailyReleases,\n ExposureInput,\n Exposures,\n RecentSearches,\n ReleaseDates,\n SignalInput,\n Signals,\n Typeahead,\n} from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call, routeURL } from \"./route.js\";\nimport { browserSearchHistory, type BrowserSearchHistory } from \"./browser-search-history.js\";\n\n/** A popularity period. */\nexport type Period = \"7d\" | \"30d\" | \"90d\" | \"365d\" | \"all\";\n/** A list's order; position orders by the first of `groups` (an ordered group). */\nexport type ListSort = \"newest\" | \"oldest\" | \"title\" | \"popular\" | `popular:${Period}` | \"top_rated\" | `top_rated:${Period}` | \"most_favorited\" | \"most_viewed\" | \"position\" | \"random\";\n/** How locked contents list: marked (lock, the default) or left out (hide). */\nexport type AccessMode = \"lock\" | \"hide\";\n\n/** Which contents a list holds. */\nexport interface ContentFilterQuery {\n kind?: string;\n /** Group ids every listed version is in. */\n groups?: readonly string[];\n /** Group ids no listed version is in. */\n exclude_groups?: readonly string[];\n /** Only contents this group owns: a channel's feed. */\n owner_group?: string;\n /** User ids: only contents one of them is credited on. */\n creators?: readonly string[];\n /** public (anyone opens it now) or the level it stays locked at. */\n access?: readonly (\"public\" | \"tier\" | \"group\" | \"ppv\" | \"group_ppv\")[];\n /** Only contents locked behind one of these tiers. */\n tiers?: readonly string[];\n /** Version traits every listed version has. */\n traits?: readonly string[];\n /** \"name:value\" of the kind's filterable attributes. */\n attribute?: readonly string[];\n /** Published on this day (YYYY-MM-DD) or month (YYYY-MM). */\n released_on?: string;\n /** Only contents published within this age window; independent of the popularity score window. */\n published_within?: Period;\n /** IANA time zone of released_on and release dates; UTC by default. */\n tz?: string;\n /** The signed-in reader's unseen, in-progress or completed contents. */\n view_status?: \"unseen\" | \"in_progress\" | \"completed\";\n}\n\n/** How a list reads to its reader. */\nexport interface ReaderQuery {\n /** Only content with a live version in this language. */\n language?: string;\n /** Titles, names and paths in this language, and each card's preferred version's; language by default. */\n display_language?: string;\n mode?: AccessMode;\n}\n\n/** The group kinds cards name; every kind with a page by default. */\nexport interface CardQuery {\n card_entities?: readonly string[];\n}\n\nexport interface PageQuery {\n cursor?: string;\n limit?: number;\n}\n\nexport type ContentQuery = ContentFilterQuery & ReaderQuery & CardQuery & PageQuery & {\n sort?: ListSort;\n /** Inclusive UTC date (YYYY-MM-DD); requires activity_until, kind and sort=popular. */\n activity_from?: string;\n /** Exclusive UTC date (YYYY-MM-DD); requires activity_from, kind and sort=popular. */\n activity_until?: string;\n};\nexport type PopularPeriod = \"7d\" | \"90d\" | \"365d\" | \"all\";\n/** Whole UTC dates: from inclusive, until exclusive. */\nexport interface PopularDateRange {\n from: string;\n until: string;\n}\nexport type PopularQuery = Omit<ContentQuery, \"sort\" | \"activity_from\" | \"activity_until\">;\n\n/** Rank activity in the selected window; publication age is an optional separate filter. */\nexport function popularQuery(period: PopularPeriod | PopularDateRange = \"7d\", filters: PopularQuery = {}): ContentQuery {\n return typeof period === \"string\"\n ? { ...filters, sort: `popular:${period}` }\n : { ...filters, sort: \"popular\", activity_from: period.from, activity_until: period.until };\n}\n\n/** Rank activity between two UTC dates: from inclusive, until exclusive. */\nexport function popularBetween(from: string, until: string, filters: PopularQuery & { kind: string }): ContentQuery {\n return popularQuery({ from, until }, filters);\n}\n\nexport type SearchQuery = ContentFilterQuery &\n ReaderQuery &\n CardQuery &\n PageQuery & {\n q: string;\n sort?: \"relevance\" | ListSort;\n personalize?: boolean;\n /** Explicit legacy server-side anonymous history. Omit for public search without server history. */\n session?: string;\n };\nexport interface RecentSearchesQuery {\n /** Explicit legacy server-side anonymous history; no anonymous session is created by default. */\n session?: string;\n /** 10 by default, at most 100. */\n limit?: number;\n}\nexport type RailQuery = ContentFilterQuery & ReaderQuery & CardQuery & { limit?: number };\nexport type ReleaseDatesQuery = ContentFilterQuery & ReaderQuery & PageQuery & { by?: \"month\" | \"day\" };\n/** Complete daily metadata for one calendar window, without pagination. */\nexport type DailyReleasesQuery = Omit<ContentFilterQuery, \"released_on\" | \"tz\"> & ReaderQuery & CardQuery & {\n days?: number;\n /** Inclusive YYYY-MM-DD in time_zone; today by default. Future dates are rejected. */\n end_date?: string;\n time_zone?: string;\n};\nexport type HistoryQuery = ReaderQuery & CardQuery & PageQuery & { kind?: string; status?: \"seen\" | \"in_progress\" | \"completed\" };\nexport interface TypeaheadQuery extends ReaderQuery {\n q: string;\n /** Only contents of this kind. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n entity_kinds?: readonly string[];\n /** Per section: 8 by default, at most 20. */\n limit?: number;\n}\n\n// Comma-joined list parameters; `attribute` repeats instead.\nconst LISTS = new Set([\"groups\", \"exclude_groups\", \"creators\", \"access\", \"tiers\", \"traits\", \"card_entities\", \"entity_kinds\"]);\n\n/** A query with its list parameters comma-joined, as discovery routes read them. */\nexport function query(q: object): Record<string, string | number | boolean | readonly string[] | undefined> {\n const out: Record<string, string | number | boolean | readonly string[] | undefined> = {};\n for (const [k, v] of Object.entries(q)) out[k] = LISTS.has(k) && Array.isArray(v) ? (v.length ? v.join(\",\") : undefined) : v;\n return out;\n}\n\n/**\n * Discovery: lists, search, typeahead, similar and recommended contents,\n * release dates, the reader's content filters and history, signals and exposures.\n */\nexport class DiscoveryClient {\n /** Local anonymous history, isolated from authenticated server history. */\n readonly browserSearches: BrowserSearchHistory;\n\n constructor(private readonly http: Http) {\n this.browserSearches = browserSearchHistory(http.mount(\"discovery\"));\n }\n\n /** Contents as cards, one per content item with the version that fits the reader. */\n list(q: ContentQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/contents\", { query: query(q) as never, signal });\n }\n /** A first page is kept among the reader's recent searches. */\n async search(q: SearchQuery, signal?: AbortSignal): Promise<ContentPage> {\n const emit = q.cursor ? null : this.http.captureChanges();\n const out: ContentPage = await call(this.http, \"GET\", \"/search\", { query: query(q) as never, signal });\n emit?.({ type: \"searches.changed\" });\n return out;\n }\n /** The reader's recent searches, newest first, since they last cleared them. */\n recentSearches(q: RecentSearchesQuery = {}, signal?: AbortSignal): Promise<RecentSearches> {\n return call(this.http, \"GET\", \"/me/searches\", { query: q, signal });\n }\n /** Clears one recent search, or all of them. */\n async clearSearches(o: { query?: string; session?: string } = {}): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/me/searches\", { query: o });\n emit({ type: \"searches.changed\" });\n }\n /** Contents and groups while the reader types. */\n typeahead(q: TypeaheadQuery, signal?: AbortSignal): Promise<Typeahead> {\n return call(this.http, \"GET\", \"/typeahead\", { query: query(q) as never, signal });\n }\n /** Contents like one, of its kind unless q.kind names another. */\n similar(id: string, q: RailQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/contents/{id}/similar\", { params: { id }, query: query(q) as never, signal });\n }\n /** For the signed-in reader. */\n recommendations(q: RailQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/recommendations\", { query: query(q) as never, signal });\n }\n /** Per month or day, newest first: how many contents the reader's listing shows released then, and how many are free. */\n releaseDates(q: ReleaseDatesQuery = {}, signal?: AbortSignal): Promise<ReleaseDates> {\n return call(this.http, \"GET\", \"/release-dates\", { query: query(q) as never, signal });\n }\n /** Every visible card in each day; opening or expanding a day needs no further request. */\n releases(q: DailyReleasesQuery = {}, signal?: AbortSignal): Promise<DailyReleases> {\n return call(this.http, \"GET\", \"/releases\", { query: query(q) as never, signal });\n }\n /** Only the current domain's reader-toggleable entities, named in the requested language. */\n async contentFilters(language?: string, signal?: AbortSignal): Promise<ContentFilters> {\n const result = await this.http.request<ContentFilters>(routeURL(this.http, \"GET\", \"/content-filters\", { query: { language } }), { signal, guestPreferences: false });\n this.http.acceptGuestFilters(result.kinds);\n return result;\n }\n /** Replaces this domain's reader settings; other domains' choices remain intact. */\n async setContentFilters(update: ContentFiltersUpdate, language?: string): Promise<ContentFilters> {\n const emit = this.http.captureChanges();\n const out: ContentFilters = await call(this.http, \"PUT\", \"/me/content-filters\", { query: { language }, body: update });\n emit({ type: \"contentFilters.changed\" });\n return out;\n }\n /** What the reader engaged with, most recent first. */\n history(q: HistoryQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/me/history\", { query: query(q) as never, signal });\n }\n /** Erases the reader's history: of one content item, one kind or everything. */\n async forgetHistory(o: { kind?: string; content_id?: string } = {}): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/me/history\", { query: o });\n emit({ type: \"history.changed\" });\n }\n /** Capture server-owned access and eligibility at an observation's start. */\n activityContexts(targets: readonly ActivityTarget[], session?: string, language = this.http.language()): Promise<ActivityContexts> {\n const body: ActivityContextsInput = { targets: [...targets], session, language };\n return call(this.http, \"POST\", \"/activity-contexts\", { body });\n }\n /** Views and clicks; signed out, of the session named. */\n signals(signals: SignalInput[], session?: string, o: { keepalive?: boolean } = {}): Promise<void> {\n const body: Signals = { session, signals };\n return call(this.http, \"POST\", \"/signals\", { body, keepalive: o.keepalive });\n }\n /** Result lists served, rendered or shown, for click attribution. */\n exposures(exposures: ExposureInput[], session?: string, o: { keepalive?: boolean } = {}): Promise<void> {\n const body: Exposures = { session, exposures };\n return call(this.http, \"POST\", \"/exposures\", { body, keepalive: o.keepalive });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"discovery.js","names":[],"sources":["../../src/client/discovery.ts"],"sourcesContent":["import type {\n ActivityContexts,\n ActivityContextsInput,\n ActivityTarget,\n ContentFilters,\n ContentFiltersUpdate,\n ContentPage,\n DailyReleases,\n ExposureInput,\n Exposures,\n RecentSearches,\n ReleaseDates,\n SignalInput,\n Signals,\n Typeahead,\n} from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call, routeURL } from \"./route.js\";\nimport { browserSearchHistory, type BrowserSearchHistory } from \"./browser-search-history.js\";\n\n/** A popularity period. */\nexport type Period = \"7d\" | \"30d\" | \"90d\" | \"365d\" | \"all\";\n/** A list's order; position orders by the first of `groups` (an ordered group). */\nexport type ListSort = \"newest\" | \"oldest\" | \"title\" | \"popular\" | `popular:${Period}` | \"top_rated\" | `top_rated:${Period}` | \"most_favorited\" | \"most_viewed\" | \"position\" | \"random\";\n/** How locked contents list: marked (lock, the default) or left out (hide). */\nexport type AccessMode = \"lock\" | \"hide\";\n\n/** Which contents a list holds. */\nexport interface ContentFilterQuery {\n kind?: string;\n /** Group ids every listed version is in. */\n groups?: readonly string[];\n /** Group ids no listed version is in. */\n exclude_groups?: readonly string[];\n /** Only contents this group owns: a channel's feed. */\n owner_group?: string;\n /** User ids: only contents one of them is credited on. */\n creators?: readonly string[];\n /** public (anyone opens it now) or the level it stays locked at. */\n access?: readonly (\"public\" | \"tier\" | \"group\" | \"ppv\" | \"group_ppv\")[];\n /** Only contents locked behind one of these tiers. */\n tiers?: readonly string[];\n /** Version traits every listed version has. */\n traits?: readonly string[];\n /** \"name:value\" of the kind's filterable attributes. */\n attribute?: readonly string[];\n /** Published on this day (YYYY-MM-DD) or month (YYYY-MM). */\n released_on?: string;\n /** Only contents published within this age window; independent of the popularity score window. */\n published_within?: Period;\n /** IANA time zone of released_on and release dates; UTC by default. */\n tz?: string;\n /** The signed-in reader's unseen, in-progress or completed contents. */\n view_status?: \"unseen\" | \"in_progress\" | \"completed\";\n}\n\n/** How a list reads to its reader. */\nexport interface ReaderQuery {\n /** Only content with a live version in this language. */\n language?: string;\n /** Titles, names and paths in this language, and each card's preferred version's; language by default. */\n display_language?: string;\n mode?: AccessMode;\n}\n\n/** The group kinds cards name; every kind with a page by default. */\nexport interface CardQuery {\n card_entities?: readonly string[];\n}\n\nexport interface PageQuery {\n cursor?: string;\n limit?: number;\n}\n\nexport type ContentQuery = ContentFilterQuery & ReaderQuery & CardQuery & PageQuery & {\n sort?: ListSort;\n /** Inclusive UTC date (YYYY-MM-DD); requires activity_until, kind and sort=popular. */\n activity_from?: string;\n /** Exclusive UTC date (YYYY-MM-DD); requires activity_from, kind and sort=popular. */\n activity_until?: string;\n};\nexport type PopularPeriod = \"7d\" | \"90d\" | \"365d\" | \"all\";\n/** Whole UTC dates: from inclusive, until exclusive. */\nexport interface PopularDateRange {\n from: string;\n until: string;\n}\nexport type PopularQuery = Omit<ContentQuery, \"sort\" | \"activity_from\" | \"activity_until\">;\n\n/** Rank activity in the selected window; publication age is an optional separate filter. */\nexport function popularQuery(period: PopularPeriod | PopularDateRange = \"7d\", filters: PopularQuery = {}): ContentQuery {\n return typeof period === \"string\"\n ? { ...filters, sort: `popular:${period}` }\n : { ...filters, sort: \"popular\", activity_from: period.from, activity_until: period.until };\n}\n\n/** Rank activity between two UTC dates: from inclusive, until exclusive. */\nexport function popularBetween(from: string, until: string, filters: PopularQuery & { kind: string }): ContentQuery {\n return popularQuery({ from, until }, filters);\n}\n\nexport type SearchQuery = ContentFilterQuery &\n ReaderQuery &\n CardQuery &\n PageQuery & {\n q: string;\n sort?: \"relevance\" | ListSort;\n personalize?: boolean;\n /** Explicit legacy server-side anonymous history. Omit for public search without server history. */\n session?: string;\n };\nexport interface RecentSearchesQuery {\n /** Explicit legacy server-side anonymous history; no anonymous session is created by default. */\n session?: string;\n /** 10 by default, at most 100. */\n limit?: number;\n}\nexport type RailQuery = ContentFilterQuery & ReaderQuery & CardQuery & { limit?: number };\nexport type ReleaseDatesQuery = ContentFilterQuery & ReaderQuery & PageQuery & { by?: \"month\" | \"day\" };\n/** Complete daily metadata for one calendar window, without pagination. */\nexport type DailyReleasesQuery = Omit<ContentFilterQuery, \"released_on\" | \"tz\"> & ReaderQuery & CardQuery & {\n days?: number;\n /** Inclusive YYYY-MM-DD in time_zone; today by default. Future dates are rejected. */\n end_date?: string;\n time_zone?: string;\n};\nexport type HistoryQuery = ReaderQuery & CardQuery & PageQuery & { kind?: string; status?: \"seen\" | \"in_progress\" | \"completed\" };\nexport interface TypeaheadQuery extends ReaderQuery {\n q: string;\n /** Only contents of this kind. */\n kind?: string;\n /** The group kinds suggested; every kind with a page by default. */\n entity_kinds?: readonly string[];\n /** Per section: 8 by default, at most 20. */\n limit?: number;\n}\n\n// Comma-joined list parameters; `attribute` repeats instead.\nconst LISTS = new Set([\"groups\", \"exclude_groups\", \"creators\", \"access\", \"tiers\", \"traits\", \"card_entities\", \"entity_kinds\"]);\n\n/** A query with its list parameters comma-joined, as discovery routes read them. */\nexport function query(q: object): Record<string, string | number | boolean | readonly string[] | undefined> {\n const out: Record<string, string | number | boolean | readonly string[] | undefined> = {};\n for (const [k, v] of Object.entries(q)) out[k] = LISTS.has(k) && Array.isArray(v) ? (v.length ? v.join(\",\") : undefined) : v;\n return out;\n}\n\n/**\n * Discovery: lists, search, typeahead, similar and recommended contents,\n * release dates, the reader's content filters and history, signals and exposures.\n */\nexport class DiscoveryClient {\n /** Local anonymous history, isolated from authenticated server history. */\n readonly browserSearches: BrowserSearchHistory;\n\n constructor(private readonly http: Http) {\n this.browserSearches = browserSearchHistory(http.mount(\"discovery\"));\n }\n\n /** Contents as cards, one per content item with the version that fits the reader. */\n list(q: ContentQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/contents\", { query: query(q) as never, signal });\n }\n /** A first page is kept among the reader's recent searches. */\n async search(q: SearchQuery, signal?: AbortSignal): Promise<ContentPage> {\n const emit = q.cursor ? null : this.http.captureChanges();\n const out: ContentPage = await call(this.http, \"GET\", \"/search\", { query: query(q) as never, signal });\n emit?.({ type: \"searches.changed\" });\n return out;\n }\n /** The reader's recent searches, newest first, since they last cleared them. */\n recentSearches(q: RecentSearchesQuery = {}, signal?: AbortSignal): Promise<RecentSearches> {\n return call(this.http, \"GET\", \"/me/searches\", { query: q, signal });\n }\n /** Clears one recent search, or all of them. */\n async clearSearches(o: { query?: string; session?: string } = {}): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/me/searches\", { query: o });\n emit({ type: \"searches.changed\" });\n }\n /** Contents and groups while the reader types. */\n typeahead(q: TypeaheadQuery, signal?: AbortSignal): Promise<Typeahead> {\n return call(this.http, \"GET\", \"/typeahead\", { query: query(q) as never, signal });\n }\n /** Contents like one, of its kind unless q.kind names another. */\n similar(id: string, q: RailQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/contents/{id}/similar\", { params: { id }, query: query(q) as never, signal });\n }\n /** For the signed-in reader. */\n recommendations(q: RailQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/recommendations\", { query: query(q) as never, signal });\n }\n /** Per month or day, newest first: how many contents the reader's listing shows released then, and how many are free. */\n releaseDates(q: ReleaseDatesQuery = {}, signal?: AbortSignal): Promise<ReleaseDates> {\n return call(this.http, \"GET\", \"/release-dates\", { query: query(q) as never, signal });\n }\n /** Every visible card in each day; opening or expanding a day needs no further request. */\n releases(q: DailyReleasesQuery = {}, signal?: AbortSignal): Promise<DailyReleases> {\n return call(this.http, \"GET\", \"/releases\", { query: query(q) as never, signal });\n }\n /** Only the current domain's reader-toggleable entities, named in the requested language. */\n async contentFilters(language?: string, signal?: AbortSignal): Promise<ContentFilters> {\n const result = await this.http.request<ContentFilters>(routeURL(this.http, \"GET\", \"/content-filters\", { query: { language } }), { signal, guestPreferences: false });\n this.http.acceptGuestFilters(result.filters);\n return result;\n }\n /** Atomically changes submitted shared choices; omitted settings remain intact. */\n async setContentFilters(update: ContentFiltersUpdate, language?: string): Promise<ContentFilters> {\n const emit = this.http.captureChanges();\n const out: ContentFilters = await call(this.http, \"PATCH\", \"/me/content-filters\", { query: { language }, body: update });\n emit({ type: \"contentFilters.changed\" });\n return out;\n }\n /** What the reader engaged with, most recent first. */\n history(q: HistoryQuery = {}, signal?: AbortSignal): Promise<ContentPage> {\n return call(this.http, \"GET\", \"/me/history\", { query: query(q) as never, signal });\n }\n /** Erases the reader's history: of one content item, one kind or everything. */\n async forgetHistory(o: { kind?: string; content_id?: string } = {}): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/me/history\", { query: o });\n emit({ type: \"history.changed\" });\n }\n /** Capture server-owned access and eligibility at an observation's start. */\n activityContexts(targets: readonly ActivityTarget[], session?: string, language = this.http.language()): Promise<ActivityContexts> {\n const body: ActivityContextsInput = { targets: [...targets], session, language };\n return call(this.http, \"POST\", \"/activity-contexts\", { body });\n }\n /** Views and clicks; signed out, of the session named. */\n signals(signals: SignalInput[], session?: string, o: { keepalive?: boolean } = {}): Promise<void> {\n const body: Signals = { session, signals };\n return call(this.http, \"POST\", \"/signals\", { body, keepalive: o.keepalive });\n }\n /** Result lists served, rendered or shown, for click attribution. */\n exposures(exposures: ExposureInput[], session?: string, o: { keepalive?: boolean } = {}): Promise<void> {\n const body: Exposures = { session, exposures };\n return call(this.http, \"POST\", \"/exposures\", { body, keepalive: o.keepalive });\n }\n}\n"],"mappings":";;;AA2FA,SAAgB,EAAa,IAA2C,MAAM,IAAwB,CAAC,GAAiB;CACtH,OAAO,OAAO,KAAW,WACrB;EAAE,GAAG;EAAS,MAAM,WAAW;CAAS,IACxC;EAAE,GAAG;EAAS,MAAM;EAAW,eAAe,EAAO;EAAM,gBAAgB,EAAO;CAAM;AAC9F;AAGA,SAAgB,EAAe,GAAc,GAAe,GAAwD;CAClH,OAAO,EAAa;EAAE;EAAM;CAAM,GAAG,CAAO;AAC9C;AAuCA,IAAM,oBAAQ,IAAI,IAAI;CAAC;CAAU;CAAkB;CAAY;CAAU;CAAS;CAAU;CAAiB;AAAc,CAAC;AAG5H,SAAgB,EAAM,GAAsF;CAC1G,IAAM,IAAiF,CAAC;CACxF,KAAK,IAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,CAAC,GAAG,EAAI,KAAK,EAAM,IAAI,CAAC,KAAK,MAAM,QAAQ,CAAC,IAAK,EAAE,SAAS,EAAE,KAAK,GAAG,IAAI,KAAA,IAAa;CAC3H,OAAO;AACT;AAMA,IAAa,IAAb,MAA6B;CAIE;CAF7B;CAEA,YAAY,GAA6B;EACvC,AAD2B,KAAA,OAAA,GAC3B,KAAK,kBAAkB,EAAqB,EAAK,MAAM,WAAW,CAAC;CACrE;CAGA,KAAK,IAAkB,CAAC,GAAG,GAA4C;EACrE,OAAO,EAAK,KAAK,MAAM,OAAO,aAAa;GAAE,OAAO,EAAM,CAAC;GAAY;EAAO,CAAC;CACjF;CAEA,MAAM,OAAO,GAAgB,GAA4C;EACvE,IAAM,IAAO,EAAE,SAAS,OAAO,KAAK,KAAK,eAAe,GAClD,IAAmB,MAAM,EAAK,KAAK,MAAM,OAAO,WAAW;GAAE,OAAO,EAAM,CAAC;GAAY;EAAO,CAAC;EAErG,OADA,IAAO,EAAE,MAAM,mBAAmB,CAAC,GAC5B;CACT;CAEA,eAAe,IAAyB,CAAC,GAAG,GAA+C;EACzF,OAAO,EAAK,KAAK,MAAM,OAAO,gBAAgB;GAAE,OAAO;GAAG;EAAO,CAAC;CACpE;CAEA,MAAM,cAAc,IAA0C,CAAC,GAAkB;EAC/E,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,UAAU,gBAAgB,EAAE,OAAO,EAAE,CAAC,GAC5D,EAAK,EAAE,MAAM,mBAAmB,CAAC;CACnC;CAEA,UAAU,GAAmB,GAA0C;EACrE,OAAO,EAAK,KAAK,MAAM,OAAO,cAAc;GAAE,OAAO,EAAM,CAAC;GAAY;EAAO,CAAC;CAClF;CAEA,QAAQ,GAAY,IAAe,CAAC,GAAG,GAA4C;EACjF,OAAO,EAAK,KAAK,MAAM,OAAO,0BAA0B;GAAE,QAAQ,EAAE,MAAG;GAAG,OAAO,EAAM,CAAC;GAAY;EAAO,CAAC;CAC9G;CAEA,gBAAgB,IAAe,CAAC,GAAG,GAA4C;EAC7E,OAAO,EAAK,KAAK,MAAM,OAAO,oBAAoB;GAAE,OAAO,EAAM,CAAC;GAAY;EAAO,CAAC;CACxF;CAEA,aAAa,IAAuB,CAAC,GAAG,GAA6C;EACnF,OAAO,EAAK,KAAK,MAAM,OAAO,kBAAkB;GAAE,OAAO,EAAM,CAAC;GAAY;EAAO,CAAC;CACtF;CAEA,SAAS,IAAwB,CAAC,GAAG,GAA8C;EACjF,OAAO,EAAK,KAAK,MAAM,OAAO,aAAa;GAAE,OAAO,EAAM,CAAC;GAAY;EAAO,CAAC;CACjF;CAEA,MAAM,eAAe,GAAmB,GAA+C;EACrF,IAAM,IAAS,MAAM,KAAK,KAAK,QAAwB,EAAS,KAAK,MAAM,OAAO,oBAAoB,EAAE,OAAO,EAAE,YAAS,EAAE,CAAC,GAAG;GAAE;GAAQ,kBAAkB;EAAM,CAAC;EAEnK,OADA,KAAK,KAAK,mBAAmB,EAAO,OAAO,GACpC;CACT;CAEA,MAAM,kBAAkB,GAA8B,GAA4C;EAChG,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAsB,MAAM,EAAK,KAAK,MAAM,SAAS,uBAAuB;GAAE,OAAO,EAAE,YAAS;GAAG,MAAM;EAAO,CAAC;EAEvH,OADA,EAAK,EAAE,MAAM,yBAAyB,CAAC,GAChC;CACT;CAEA,QAAQ,IAAkB,CAAC,GAAG,GAA4C;EACxE,OAAO,EAAK,KAAK,MAAM,OAAO,eAAe;GAAE,OAAO,EAAM,CAAC;GAAY;EAAO,CAAC;CACnF;CAEA,MAAM,cAAc,IAA4C,CAAC,GAAkB;EACjF,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,UAAU,eAAe,EAAE,OAAO,EAAE,CAAC,GAC3D,EAAK,EAAE,MAAM,kBAAkB,CAAC;CAClC;CAEA,iBAAiB,GAAoC,GAAkB,IAAW,KAAK,KAAK,SAAS,GAA8B;EACjI,IAAM,IAA8B;GAAE,SAAS,CAAC,GAAG,CAAO;GAAG;GAAS;EAAS;EAC/E,OAAO,EAAK,KAAK,MAAM,QAAQ,sBAAsB,EAAE,QAAK,CAAC;CAC/D;CAEA,QAAQ,GAAwB,GAAkB,IAA6B,CAAC,GAAkB;EAChG,IAAM,IAAgB;GAAE;GAAS;EAAQ;EACzC,OAAO,EAAK,KAAK,MAAM,QAAQ,YAAY;GAAE;GAAM,WAAW,EAAE;EAAU,CAAC;CAC7E;CAEA,UAAU,GAA4B,GAAkB,IAA6B,CAAC,GAAkB;EACtG,IAAM,IAAkB;GAAE;GAAS;EAAU;EAC7C,OAAO,EAAK,KAAK,MAAM,QAAQ,cAAc;GAAE;GAAM,WAAW,EAAE;EAAU,CAAC;CAC/E;AACF"}
@@ -1996,7 +1996,7 @@ export declare const CONTENTKIT_ROUTES: readonly [{
1996
1996
  readonly errors: readonly [];
1997
1997
  readonly errorSets: readonly ["module:discovery", "auth", "request"];
1998
1998
  }, {
1999
- readonly method: "PUT";
1999
+ readonly method: "PATCH";
2000
2000
  readonly module: "discovery";
2001
2001
  readonly path: "/me/content-filters";
2002
2002
  readonly auth: "user";
@@ -2887,7 +2887,7 @@ var e = {
2887
2887
  ]
2888
2888
  },
2889
2889
  {
2890
- method: "PUT",
2890
+ method: "PATCH",
2891
2891
  module: "discovery",
2892
2892
  path: "/me/content-filters",
2893
2893
  auth: "user",