@openrails/contentkit-ui 0.101.1 → 0.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -13
- 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 +3 -1
- package/dist/client/discovery.js +2 -2
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/generated/routes.d.ts +2 -2
- package/dist/client/generated/routes.js +2 -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/index.d.ts +1 -0
- 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 +16 -0
- package/dist/components/content/release-date-picker.js +65 -0
- package/dist/components/content/release-date-picker.js.map +1 -0
- package/dist/index.js +22 -21
- package/dist/locales/discovery/de.js +3 -0
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +3 -0
- package/dist/locales/discovery/en.js +3 -0
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +3 -0
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +3 -0
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +3 -0
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +3 -0
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +3 -0
- 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 +1 -1
package/README.md
CHANGED
|
@@ -362,6 +362,25 @@ disclosures, with reduced-motion support. The initial card budget is shared
|
|
|
362
362
|
across days, newest first. Opening older days or choosing **Show more** reveals
|
|
363
363
|
already-fetched cards; it does not make another listing request.
|
|
364
364
|
|
|
365
|
+
To browse old releases, set `query.end_date` to an inclusive `YYYY-MM-DD` in
|
|
366
|
+
the listing's time zone. The same bounded window ends on that date; it does
|
|
367
|
+
not load intervening years. Omit it for the latest releases, or combine it
|
|
368
|
+
with `days: 1` for one exact day. Future and invalid dates return HTTP 400.
|
|
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:
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
<ReleaseDatePicker value={date} onValueChange={setDate} />
|
|
376
|
+
<DailyReleases query={{ kind: "gallery", days: 10, end_date: date || undefined }} />
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
`isReleaseDate(date)` validates URL input before rendering the feed; the picker
|
|
380
|
+
shows an error for invalid or future dates. `localReleaseDate()` provides
|
|
381
|
+
today's browser-local date; supply the picker's `max` when the listing uses a
|
|
382
|
+
different time zone.
|
|
383
|
+
|
|
365
384
|
Only revealed cards occupy the grid, and images/previews mount when those cards
|
|
366
385
|
enter the viewport. Collapsing a day removes its media. The standard card
|
|
367
386
|
properties (`href`, `decorate`, `groupKinds`, `tiers`, and so on) remain available.
|
|
@@ -900,24 +919,28 @@ account languages use the provider's `languagePreference` adapter. Each domain
|
|
|
900
919
|
only offers its configured, user-toggleable entities; internal classifications
|
|
901
920
|
and required or locked rules never appear in reader settings. Labels use the
|
|
902
921
|
entities' localized names. Saving this domain's settings preserves choices for
|
|
903
|
-
entities offered by other domains
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
changes. Old unscoped browser choices remain a fallback until replaced
|
|
909
|
-
by a choice for that content kind.
|
|
922
|
+
entities offered by other domains. The configured `applies_to` content kinds
|
|
923
|
+
share one preference per entity, so a tag toggle can affect galleries and blog
|
|
924
|
+
posts together. `kinds` selects a listing context and displays controls when it
|
|
925
|
+
intersects `applies_to`; it never creates a second per-kind preference. Rows use
|
|
926
|
+
entity names and have no duplicate type headings.
|
|
910
927
|
|
|
911
|
-
The HTTP shape
|
|
928
|
+
The HTTP shape is flat and updates only the submitted entities:
|
|
912
929
|
|
|
913
930
|
```json
|
|
914
|
-
{"
|
|
931
|
+
{"filters":[{"entity_id":"tag-id","visible":false}]}
|
|
915
932
|
```
|
|
916
933
|
|
|
917
|
-
`
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
934
|
+
`PATCH /me/content-filters` preserves omitted entities and `include_deleted`.
|
|
935
|
+
`setVisible(entityId, visible)` performs that one-entity update. Reset a choice
|
|
936
|
+
with `{entity_id, default: true}`; the reset follows current domain defaults and
|
|
937
|
+
suppresses old legacy choices. Independent account updates merge atomically.
|
|
938
|
+
|
|
939
|
+
Guest storage migrates v1 choices and pending registration snapshots into v2,
|
|
940
|
+
keeping the original v1 bytes. Conflicting old per-kind choices resolve to
|
|
941
|
+
hidden; explicit scoped resets suppress the older unscoped fallback. New shared
|
|
942
|
+
choices override all old values. Registration initialization uses the flat
|
|
943
|
+
`filters` array and never overwrites an existing account's settings.
|
|
921
944
|
|
|
922
945
|
```tsx
|
|
923
946
|
<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":"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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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"}
|
|
@@ -93,6 +93,8 @@ export type ReleaseDatesQuery = ContentFilterQuery & ReaderQuery & PageQuery & {
|
|
|
93
93
|
/** Complete daily metadata for one calendar window, without pagination. */
|
|
94
94
|
export type DailyReleasesQuery = Omit<ContentFilterQuery, "released_on" | "tz"> & ReaderQuery & CardQuery & {
|
|
95
95
|
days?: number;
|
|
96
|
+
/** Inclusive YYYY-MM-DD in time_zone; today by default. Future dates are rejected. */
|
|
97
|
+
end_date?: string;
|
|
96
98
|
time_zone?: string;
|
|
97
99
|
};
|
|
98
100
|
export type HistoryQuery = ReaderQuery & CardQuery & PageQuery & {
|
|
@@ -142,7 +144,7 @@ export declare class DiscoveryClient {
|
|
|
142
144
|
releases(q?: DailyReleasesQuery, signal?: AbortSignal): Promise<DailyReleases>;
|
|
143
145
|
/** Only the current domain's reader-toggleable entities, named in the requested language. */
|
|
144
146
|
contentFilters(language?: string, signal?: AbortSignal): Promise<ContentFilters>;
|
|
145
|
-
/**
|
|
147
|
+
/** Atomically changes submitted shared choices; omitted settings remain intact. */
|
|
146
148
|
setContentFilters(update: ContentFiltersUpdate, language?: string): Promise<ContentFilters>;
|
|
147
149
|
/** What the reader engaged with, most recent first. */
|
|
148
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 & { days?: number; time_zone?: string };\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":";;;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;AAkCA,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,KAAK,GAClC;CACT;CAEA,MAAM,kBAAkB,GAA8B,GAA4C;EAChG,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAsB,MAAM,EAAK,KAAK,MAAM,OAAO,uBAAuB;GAAE,OAAO,EAAE,YAAS;GAAG,MAAM;EAAO,CAAC;EAErH,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"}
|
|
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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"}
|
|
@@ -1897,7 +1897,7 @@ export declare const CONTENTKIT_ROUTES: readonly [{
|
|
|
1897
1897
|
readonly path: "/releases";
|
|
1898
1898
|
readonly auth: "public";
|
|
1899
1899
|
readonly permission: null;
|
|
1900
|
-
readonly query: readonly ["kind", "groups", "exclude_groups", "owner_group", "creators", "access", "tiers", "traits", "attribute", "published_within", "view_status", "language", "display_language", "mode", "card_entities", "days", "time_zone"];
|
|
1900
|
+
readonly query: readonly ["kind", "groups", "exclude_groups", "owner_group", "creators", "access", "tiers", "traits", "attribute", "published_within", "view_status", "language", "display_language", "mode", "card_entities", "days", "end_date", "time_zone"];
|
|
1901
1901
|
readonly request: null;
|
|
1902
1902
|
readonly responses: {
|
|
1903
1903
|
readonly 200: "DailyReleases";
|
|
@@ -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";
|
|
@@ -2649,6 +2649,7 @@ var e = {
|
|
|
2649
2649
|
"mode",
|
|
2650
2650
|
"card_entities",
|
|
2651
2651
|
"days",
|
|
2652
|
+
"end_date",
|
|
2652
2653
|
"time_zone"
|
|
2653
2654
|
],
|
|
2654
2655
|
request: null,
|
|
@@ -2886,7 +2887,7 @@ var e = {
|
|
|
2886
2887
|
]
|
|
2887
2888
|
},
|
|
2888
2889
|
{
|
|
2889
|
-
method: "
|
|
2890
|
+
method: "PATCH",
|
|
2890
2891
|
module: "discovery",
|
|
2891
2892
|
path: "/me/content-filters",
|
|
2892
2893
|
auth: "user",
|