@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.
- package/README.md +23 -17
- package/dist/client/browser-reader-preferences.d.ts +8 -9
- package/dist/client/browser-reader-preferences.js +71 -63
- package/dist/client/browser-reader-preferences.js.map +1 -1
- package/dist/client/discovery.d.ts +1 -1
- package/dist/client/discovery.js +2 -2
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/generated/routes.d.ts +1 -1
- package/dist/client/generated/routes.js +1 -1
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +5 -10
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/http.d.ts +3 -4
- package/dist/client/http.js +7 -15
- package/dist/client/http.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/reader-preferences.d.ts +2 -2
- package/dist/client/reader-preferences.js +2 -6
- package/dist/client/reader-preferences.js.map +1 -1
- package/dist/components/content/daily-releases.js +39 -43
- package/dist/components/content/daily-releases.js.map +1 -1
- package/dist/components/content/filters.d.ts +2 -6
- package/dist/components/content/filters.js +57 -66
- package/dist/components/content/filters.js.map +1 -1
- package/dist/components/content/reader-settings.d.ts +0 -1
- package/dist/components/content/reader-settings.js +1 -2
- package/dist/components/content/reader-settings.js.map +1 -1
- package/dist/components/content/release-date-picker.d.ts +4 -4
- package/dist/components/content/release-date-picker.js +106 -48
- package/dist/components/content/release-date-picker.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/locales/discovery/de.js +1 -2
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +1 -2
- package/dist/locales/discovery/en.js +1 -2
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +1 -2
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +1 -2
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +1 -2
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +1 -2
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +1 -2
- package/dist/react/discovery.d.ts +3 -5
- package/dist/react/discovery.js +42 -71
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/provider.js +1 -4
- package/dist/react/provider.js.map +1 -1
- package/dist/styles.css +1 -1
- 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`
|
|
371
|
-
|
|
372
|
-
|
|
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
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
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
|
|
930
|
+
The HTTP shape is flat and updates only the submitted entities:
|
|
931
931
|
|
|
932
932
|
```json
|
|
933
|
-
{"
|
|
933
|
+
{"filters":[{"entity_id":"tag-id","visible":false}]}
|
|
934
934
|
```
|
|
935
935
|
|
|
936
|
-
`
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
|
11
|
+
export type ReaderFilters = readonly ReaderFilterChoice[];
|
|
13
12
|
export interface GuestReaderPreferences {
|
|
14
13
|
language?: string;
|
|
15
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
48
|
+
function i(e) {
|
|
51
49
|
return {
|
|
52
50
|
language: e.language,
|
|
53
|
-
|
|
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
|
|
54
|
+
function a(i, a = !1) {
|
|
58
55
|
try {
|
|
59
|
-
let
|
|
60
|
-
if (!
|
|
61
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
registrations: o
|
|
71
|
+
...c(s),
|
|
72
|
+
registrations: l
|
|
75
73
|
};
|
|
76
74
|
} catch {
|
|
77
75
|
return e;
|
|
78
76
|
}
|
|
79
77
|
}
|
|
80
|
-
var
|
|
78
|
+
var o = class {
|
|
81
79
|
key;
|
|
82
80
|
browser;
|
|
81
|
+
legacyKey;
|
|
83
82
|
value = e;
|
|
84
|
-
publicValue =
|
|
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()
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
122
|
-
legacy_filters: this.value.legacy_filters,
|
|
124
|
+
filters: this.value.filters,
|
|
123
125
|
languageDone: !this.value.language,
|
|
124
|
-
filtersDone: !
|
|
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.
|
|
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
|
-
|
|
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
|
-
...
|
|
186
|
+
...i(e),
|
|
178
187
|
registrations: Object.freeze(e.registrations.map((e) => Object.freeze({
|
|
179
188
|
...e,
|
|
180
|
-
...
|
|
189
|
+
...i(e)
|
|
181
190
|
})))
|
|
182
191
|
});
|
|
183
192
|
let n = {
|
|
184
193
|
language: this.value.language,
|
|
185
|
-
|
|
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
|
-
},
|
|
194
|
-
function
|
|
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
|
|
201
|
-
let
|
|
202
|
-
|
|
203
|
-
let
|
|
204
|
-
return
|
|
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 {
|
|
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":";AAsBA,IAAM,IAAkB,OAAO,OAAO;CAAE,OAAO,OAAO,OAAO,CAAC,CAAC;CAAG,gBAAgB,OAAO,OAAO,CAAC,CAAC;CAAG,eAAe,OAAO,OAAO,CAAC,CAAC;AAAE,CAAC;AAEvI,SAAS,EAAS,GAAoC;CAChD,aAAO,KAAU,YAAY,EAAM,SAAS,KAChD,IAAI;EAAE,OAAO,KAAK,oBAAoB,CAAK,CAAC,CAAC;CAAI,QAAQ;EAAE;CAAQ;AACrE;AAEA,SAAS,EAAQ,GAAiC;CAChD,IAAI,CAAC,MAAM,QAAQ,CAAK,GAAG,OAAO,CAAC;CACnC,IAAM,oBAAU,IAAI,IAAqB;CACzC,KAAK,IAAM,KAAO,GAChB,AAAI,KAAO,QAAQ,EAAI,aAAa,EAAI,aAAc,aAAa,EAAI,aAAa,EAAI,SAAA,CAAU,SAAS,MAAM,EAAI,aAAa,EAAI,SAAA,CAAU,UAAU,OAAO,OAAO,EAAI,WAAY,aAAW,EAAQ,IAAI,EAAI,aAAa,EAAI,UAAU,EAAI,OAAO;CAE3P,OAAO,CAAC,GAAG,CAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAW,QAAc;EAAE;EAAW;CAAQ,EAAE;AACnH;AAEA,SAAS,EAAc,GAAsC;CAC3D,IAAI,CAAC,MAAM,QAAQ,CAAK,GAAG,OAAO,CAAC;CACnC,IAAM,oBAAU,IAAI,IAAgC;CACpD,KAAK,IAAM,KAAO,GAAO;EACvB,IAAM,IAAK,GAAK;EACZ,OAAO,KAAO,YAAY,CAAC,KAAM,EAAG,SAAS,QAC7C,EAAI,YAAY,MAAQ,EAAI,YAAY,KAAA,IAAW,EAAQ,IAAI,GAAI;GAAE,WAAW;GAAI,SAAS;EAAK,CAAC,IAC9F,EAAI,YAAY,KAAA,KAAa,OAAO,EAAI,WAAY,aAAW,EAAQ,IAAI,GAAI;GAAE,WAAW;GAAI,SAAS,EAAI;EAAQ,CAAC;CACjI;CACA,OAAO,CAAC,GAAG,CAAO,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,OAAS,CAAG;AACjF;AAEA,SAAS,EAAM,GAAmC;CAEhD,OADI,CAAC,KAAS,OAAO,KAAU,YAAY,MAAM,QAAQ,CAAK,IAAU,CAAC,IAClE,OAAO,YAAY,OAAO,QAAQ,CAAK,CAAC,CAC5C,QAAQ,CAAC,OAAU,oBAAoB,KAAK,CAAI,CAAC,CAAC,CAClD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CACtC,KAAK,CAAC,GAAM,OAAW,CAAC,GAAM,EAAc,CAAK,CAAC,CAAC,CAAC;AACzD;AAEA,SAAS,EAAQ,GAAgC;CAC/C,OAAO;EAAE,OAAO,EAAM,EAAM,KAAK;EAAG,gBAAgB,EAAQ,EAAM,kBAAkB,EAAM,OAAO;CAAE;AACrG;AAEA,SAAS,EAAW,GAAwC;CAC1D,OAAO,EAAM,eAAe,SAAS,KAAK,OAAO,OAAO,EAAM,KAAK,CAAC,CAAC,MAAM,MAAS,EAAK,SAAS,CAAC;AACrG;AAEA,SAAS,EAAc,GAAuD;CAC5E,OAAO;EAAE,UAAU,EAAM;EACvB,OAAO,OAAO,OAAO,OAAO,YAAY,OAAO,QAAQ,EAAM,KAAK,CAAC,CAAC,KAAK,CAAC,GAAM,OAAU,CAAC,GAAM,OAAO,OAAO,EAAK,KAAK,MAAM,OAAO,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;EAC3J,gBAAgB,OAAO,OAAO,EAAM,eAAe,KAAK,MAAM,OAAO,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;CAAE;AAC5F;AAEA,SAAS,EAAO,GAA8B;CAC5C,IAAI;EACF,IAAM,IAAgB,KAAK,MAAM,KAAO,IAAI;EAC5C,IAAI,CAAC,KAAQ,OAAO,KAAS,UAAU,OAAO;EAC9C,IAAM,IAAQ,GACR,IAA4C,CAAC;EACnD,IAAI,MAAM,QAAQ,EAAM,aAAa,GACnC,KAAK,IAAM,KAAS,EAAM,cAAc,MAAM,GAAG,EAAE,GAC7C,CAAC,KAAS,OAAO,EAAM,WAAY,YAAY,CAAC,EAAM,WAAW,EAAM,QAAQ,SAAS,OAAO,EAAc,MAAM,MAAM,EAAE,YAAY,EAAM,OAAO,KACxJ,EAAc,KAAK;GAAE,SAAS,EAAM;GAAS,UAAU,EAAS,EAAM,QAAQ;GAAG,GAAG,EAAQ,CAAK;GAAG,cAAc,EAAM,iBAAiB;GAAM,aAAa,EAAM,gBAAgB;GAChL,kBAAkB,EAAM,qBAAqB;GAAM,iBAAiB,EAAM,oBAAoB,OAAO,OAAO,EAAS,EAAM,eAAe;EAAE,CAAC;EAGnJ,OAAO;GAAE,UAAU,EAAS,EAAM,QAAQ;GAAG,GAAG,EAAQ,CAAK;GAAG;EAAc;CAChF,QAAQ;EAAE,OAAO;CAAO;AAC1B;AAGA,IAAa,IAAb,MAAsC;CAOP;CAA8B;CAN3D,QAA0B;CAC1B,cAA8C;CAC9C,UAAkB,KAAK,UAAU,CAAK;CACtC,cAAsB;CACtB,4BAA6B,IAAI,IAAgB;CAEjD,YAAY,GAA8B,GAAmC;EAAE,AAAlD,KAAA,MAAA,GAA8B,KAAA,UAAA,GAAoB,KAAK,KAAK;CAAG;CAE5F,iBAAkD,KAAK;CAEvD,aAAsB,OACpB,KAAK,KAAK,GACL,KAAK,UAAU,QAAM,KAAK,SAAS,iBAAiB,WAAW,KAAK,SAAS,GAClF,KAAK,UAAU,IAAI,CAAQ,SACd;EAEX,AADA,KAAK,UAAU,OAAO,CAAQ,GACzB,KAAK,UAAU,QAAM,KAAK,SAAS,oBAAoB,WAAW,KAAK,SAAS;CACvF;CAGF,YAAY,GAAqB;EAC/B,IAAM,IAAO,EAAS,CAAK;EAC3B,IAAI,CAAC,GAAM,MAAU,MAAM,wCAAwC;EAEnE,AADA,KAAK,KAAK,GACV,KAAK,KAAK;GAAE,GAAG,KAAK;GAAO,UAAU;EAAK,CAAC;CAC7C;CAEA,WAAW,GAAgC;EAEzC,AADA,KAAK,KAAK,GACV,KAAK,KAAK;GAAE,GAAG,KAAK;GAAO,OAAO,EAAM,CAAK;EAAE,CAAC;CAClD;CAEA,QAAQ,GAAuD;EAE7D,OADA,KAAK,KAAK,GACH,KAAK,MAAM,cAAc,MAAM,MAAM,EAAE,YAAY,CAAO;CACnE;CAGA,SAAS,GAAuB;EAE9B,IADA,KAAK,KAAK,GACN,CAAC,KAAW,EAAQ,SAAS,KAAK,MAAU,MAAM,8CAA8C;EAChG,UAAK,QAAQ,CAAO,MACnB,KAAK,MAAM,YAAa,EAAW,KAAK,KAAK,IAClD;OAAI,KAAK,MAAM,cAAc,UAAU,IAAI,MAAU,MAAM,uDAAuD;GAClH,KAAK,KAAK;IAAE,GAAG,KAAK;IAAO,eAAe,CAAC,GAAG,KAAK,MAAM,eAAe;KACtE;KAAS,UAAU,KAAK,MAAM;KAAU,OAAO,KAAK,MAAM;KAAO,gBAAgB,KAAK,MAAM;KAC5F,cAAc,CAAC,KAAK,MAAM;KAAU,aAAa,CAAC,EAAW,KAAK,KAAK;IACzE,CAAC;GAAE,CAAC;EAJ8G;CAKpH;CAEA,SAAS,GAAiB,GAAoC;EAC5D,KAAK,KAAK;EACV,IAAM,IAAgB,KAAK,MAAM,cAAc,KAAK,MAAM,EAAE,YAAY,IAAU;GAAE,GAAG;IAAI,MAAS,aAAa,iBAAiB,gBAAgB;EAAK,IAAI,CAAC,CAAC,CAC1J,QAAQ,MAAM,CAAC,EAAE,gBAAgB,CAAC,EAAE,WAAW;EAClD,KAAK,KAAK;GAAE,GAAG,KAAK;GAAO;EAAc,CAAC;CAC5C;CAEA,gBAAgB,GAAiB,GAAsC;EAErE,AADA,KAAK,KAAK,GACV,KAAK,KAAK;GAAE,GAAG,KAAK;GAAO,eAAe,KAAK,MAAM,cAAc,KAAK,MAAM,EAAE,YAAY,KAAW,CAAC,EAAE,mBAAmB;IAAE,GAAG;IAAG,kBAAkB;IAAM;GAAgB,IAAI,CAAC;EAAE,CAAC;CACvL;CAEA,aAA8B,MAA8B;EAC1D,CAAI,EAAM,QAAQ,QAAQ,EAAM,QAAQ,KAAK,QAAK,KAAK,KAAK;CAC9D;CAEA,OAAqB;EACf,IAAC,KAAK,WAAW,MAAK,aAC1B,IAAI;GAAE,KAAK,QAAQ,EAAO,KAAK,QAAQ,aAAa,QAAQ,KAAK,GAAG,CAAC,CAAC;EAAG,QACnE;GAAE,KAAK,cAAc;EAAM;CACnC;CAEA,KAAa,GAAsB;EACjC,IAAI,KAAK,WAAW,CAAC,KAAK,aACxB,IAAI;GAAE,KAAK,QAAQ,aAAa,QAAQ,KAAK,KAAK,KAAK,UAAU,CAAI,CAAC;EAAG,QACnE;GAAE,KAAK,cAAc;EAAM;EAEnC,KAAK,QAAQ,CAAI;CACnB;CAEA,QAAgB,GAAsB;EACpC,IAAM,IAAU,KAAK,UAAU;GAAE,UAAU,EAAK;GAAU,OAAO,EAAK;GAAO,gBAAgB,EAAK;GAAgB,eAAe,EAAK;EAAc,CAAC;EACrJ,IAAI,MAAY,KAAK,SAAS;EAE9B,AADA,KAAK,UAAU,GACf,KAAK,QAAQ,OAAO,OAAO;GAAE,GAAG,EAAc,CAAI;GAAG,eAAe,OAAO,OAAO,EAAK,cAAc,KAAK,MAAM,OAAO,OAAO;IAAE,GAAG;IAAG,GAAG,EAAc,CAAC;GAAE,CAAC,CAAC,CAAC;EAAE,CAAC;EAChK,IAAM,IAAc;GAAE,UAAU,KAAK,MAAM;GAAU,OAAO,KAAK,MAAM;GAAO,gBAAgB,KAAK,MAAM;EAAe;EACxH,AAAI,KAAK,UAAU,CAAW,MAAM,KAAK,UAAU,KAAK,WAAW,MAAG,KAAK,cAAc,OAAO,OAAO,CAAW;EAClH,KAAK,IAAM,KAAY,KAAK,WAAa,IAAI;GAAE,EAAS;EAAG,QAAQ,CAA0C;CAC/G;AACF,GAEM,oBAAS,IAAI,QAAuD;AAG1E,SAAgB,EAAyB,GAAyC;CAChF,IAAM,IAAU,OAAO,SAAW,MAAc,KAAA,IAAY,QACxD,IAAQ,EAAM,QAAQ,QAAQ,EAAE;CACpC,IAAI,GAAW,IAAI;EAAE,IAAQ,IAAI,IAAI,GAAO,EAAQ,SAAS,IAAI,CAAC,CAAC;CAAM,QAAQ,CAAmC;CACpH,IAAM,IAAM,oCAAoC;CAChD,IAAI,CAAC,GAAS,OAAO,IAAI,EAAyB,CAAG;CACrD,IAAI,IAAS,EAAO,IAAI,CAAO;CAC/B,AAAK,KAAQ,EAAO,IAAI,GAAU,oBAAS,IAAI,IAAI,CAAE;CACrD,IAAI,IAAQ,EAAO,IAAI,CAAG;CAE1B,OADK,KAAO,EAAO,IAAI,GAAM,IAAQ,IAAI,EAAyB,GAAK,CAAO,CAAE,GACzE;AACT"}
|
|
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
|
-
/**
|
|
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>;
|
package/dist/client/discovery.js
CHANGED
|
@@ -98,10 +98,10 @@ var s = class {
|
|
|
98
98
|
signal: n,
|
|
99
99
|
guestPreferences: !1
|
|
100
100
|
});
|
|
101
|
-
return this.http.acceptGuestFilters(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, "
|
|
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":"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"}
|
|
@@ -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: "
|
|
1999
|
+
readonly method: "PATCH";
|
|
2000
2000
|
readonly module: "discovery";
|
|
2001
2001
|
readonly path: "/me/content-filters";
|
|
2002
2002
|
readonly auth: "user";
|