@openrails/contentkit-ui 0.97.1 → 0.98.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +57 -4
  2. package/dist/client/beacons.d.ts +17 -7
  3. package/dist/client/beacons.js +205 -41
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/browser-reader-preferences.d.ts +15 -2
  6. package/dist/client/browser-reader-preferences.js +67 -30
  7. package/dist/client/browser-reader-preferences.js.map +1 -1
  8. package/dist/client/discovery.d.ts +13 -3
  9. package/dist/client/discovery.js +31 -16
  10. package/dist/client/discovery.js.map +1 -1
  11. package/dist/client/generated/routes.d.ts +14 -1
  12. package/dist/client/generated/routes.js +18 -0
  13. package/dist/client/generated/routes.js.map +1 -1
  14. package/dist/client/generated/wire.d.ts +47 -4
  15. package/dist/client/generated/wire.js.map +1 -1
  16. package/dist/client/http.d.ts +7 -3
  17. package/dist/client/http.js +26 -7
  18. package/dist/client/http.js.map +1 -1
  19. package/dist/client/index.d.ts +2 -2
  20. package/dist/client/reader-preferences.d.ts +7 -0
  21. package/dist/client/reader-preferences.js +16 -4
  22. package/dist/client/reader-preferences.js.map +1 -1
  23. package/dist/client.js +17 -17
  24. package/dist/components/content/card.js +50 -40
  25. package/dist/components/content/card.js.map +1 -1
  26. package/dist/components/content/filters.d.ts +5 -0
  27. package/dist/components/content/filters.js +61 -52
  28. package/dist/components/content/filters.js.map +1 -1
  29. package/dist/components/content/popular.d.ts +1 -1
  30. package/dist/components/content/popular.js.map +1 -1
  31. package/dist/components/content/reader-settings.d.ts +4 -0
  32. package/dist/components/content/reader-settings.js +3 -1
  33. package/dist/components/content/reader-settings.js.map +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/react/discovery.d.ts +7 -2
  36. package/dist/react/discovery.js +208 -87
  37. package/dist/react/discovery.js.map +1 -1
  38. package/dist/react/provider.js +4 -1
  39. package/dist/react/provider.js.map +1 -1
  40. package/dist/styles.css +1 -1
  41. package/package.json +1 -1
@@ -102,10 +102,10 @@ export interface UseContentFilters {
102
102
  loading: boolean;
103
103
  error?: ContentKitError;
104
104
  saving: boolean;
105
- /** Replaces every setting; the lists showing re-read. */
105
+ /** Replaces offered settings for the supplied kinds; other kinds remain unchanged. */
106
106
  save: (update: ContentFiltersUpdate) => Promise<ContentFilters>;
107
107
  /** Shows or hides one filter's group, keeping the other settings. */
108
- setVisible: (groupId: string, visible: boolean) => Promise<ContentFilters>;
108
+ setVisible: (kind: string, entityId: string, visible: boolean) => Promise<ContentFilters>;
109
109
  setIncludeDeleted: (include: boolean) => Promise<ContentFilters>;
110
110
  }
111
111
  /** Current-domain toggles: browser choices for guests, server choices for accounts. */
@@ -148,6 +148,7 @@ export interface ExposureOptions {
148
148
  client?: ContentKitClient;
149
149
  }
150
150
  export interface UseExposure {
151
+ /** The first bounded segment; click() selects the matching segment for later positions. */
151
152
  render_id: string;
152
153
  /** A card's ref: reports it shown once at least half of it is in view. */
153
154
  track: (content: string, position: number) => (el: Element | null) => void;
@@ -157,4 +158,8 @@ export interface UseExposure {
157
158
  /** A rendered result list: each page rendered, each card shown, clicks attributed to the render. */
158
159
  export declare function useExposure(items: readonly {
159
160
  id: string;
161
+ version_id?: string;
162
+ version?: {
163
+ id: string;
164
+ } | null;
160
165
  }[], o: ExposureOptions): UseExposure;
@@ -192,17 +192,21 @@ function I(t = {}) {
192
192
  guest: m,
193
193
  data: T.data
194
194
  };
195
- let E = d((e, t = b.filters) => {
196
- let n = new Map(t.map((e) => [e.group_id, e.visible]));
195
+ let E = d((e, t = b) => {
196
+ let n = new Map(t.legacy_filters.map((e) => [e.entity_id, e.visible]));
197
197
  return {
198
198
  include_deleted: !1,
199
- filters: e.filters.map((e) => ({
200
- ...e,
201
- visible: n.get(e.entity.id) ?? e.default_visible,
202
- overridden: n.has(e.entity.id)
199
+ kinds: Object.fromEntries(Object.entries(e.kinds).map(([e, r]) => {
200
+ let i = new Map(n);
201
+ for (let n of t.kinds[e] ?? []) i.set(n.entity_id, n.default ? void 0 : n.visible);
202
+ return [e, r.map((e) => ({
203
+ ...e,
204
+ visible: i.get(e.entity.id) ?? e.default_visible,
205
+ overridden: i.get(e.entity.id) !== void 0
206
+ }))];
203
207
  }))
204
208
  };
205
- }, [b.filters]), D = h(() => T.data && m ? E(T.data) : T.data, [
209
+ }, [b]), D = h(() => T.data && m ? E(T.data) : T.data, [
206
210
  T.data,
207
211
  m,
208
212
  E
@@ -213,12 +217,28 @@ function I(t = {}) {
213
217
  let N = d(async (t) => {
214
218
  if (!k()) throw new e("aborted", "The account changed.");
215
219
  if (m) {
216
- let n = M.current;
217
- if (!n) throw new e("invalid_request", "Content filters have not loaded.");
218
- let r = new Set(n.filters.map((e) => e.entity.id));
219
- if (t.filters.some((e) => !r.has(e.group_id))) throw new e("invalid_request", "This content filter is not configured.");
220
- let i = [...y.snapshot().filters.filter((e) => !r.has(e.group_id)), ...t.filters];
221
- return y.setFilters(i), E(n, i);
220
+ let r = M.current;
221
+ if (!r) throw new e("invalid_request", "Content filters have not loaded.");
222
+ let i = y.snapshot(), a = { ...i.kinds }, o = new Set(i.legacy_filters.map((e) => e.entity_id));
223
+ for (let [n, s] of Object.entries(t.kinds)) {
224
+ let t = r.kinds[n];
225
+ if (!t) throw new e("invalid_request", "This content kind has no configurable filters.");
226
+ let c = new Set(t.map((e) => e.entity.id));
227
+ if (s.some((e) => !c.has(e.entity_id))) throw new e("invalid_request", "This content filter is not configured.");
228
+ let l = new Set(s.map((e) => e.entity_id)), u = t.filter((e) => o.has(e.entity.id) && !l.has(e.entity.id)).map((e) => ({
229
+ entity_id: e.entity.id,
230
+ default: !0
231
+ }));
232
+ a[n] = [
233
+ ...(i.kinds[n] ?? []).filter((e) => !c.has(e.entity_id)),
234
+ ...s,
235
+ ...u
236
+ ];
237
+ }
238
+ return n.preferences.setGuestFilters(a), E(r, {
239
+ ...i,
240
+ kinds: a
241
+ });
222
242
  }
223
243
  j((e) => e + 1);
224
244
  try {
@@ -240,25 +260,46 @@ function I(t = {}) {
240
260
  m,
241
261
  y,
242
262
  E
243
- ]), P = (e) => e.filters.filter((e) => e.overridden).map((e) => ({
244
- group_id: e.entity.id,
263
+ ]), P = (e) => Object.fromEntries(Object.entries(e.kinds).map(([e, t]) => [e, t.filter((e) => e.overridden).map((e) => ({
264
+ entity_id: e.entity.id,
245
265
  visible: e.visible
246
- })), F = d((e, t) => {
247
- let n = M.current;
248
- return n ? N({
249
- filters: [...P(n).filter((t) => t.group_id !== e), {
250
- group_id: e,
251
- visible: t
252
- }],
253
- include_deleted: n.include_deleted
254
- }) : Promise.reject(/* @__PURE__ */ Error("contentkit-ui: the content filters have not loaded"));
255
- }, [N]), I = d((e) => {
256
- let t = M.current;
257
- return t ? N({
258
- filters: P(t),
259
- include_deleted: e
260
- }) : Promise.reject(/* @__PURE__ */ Error("contentkit-ui: the content filters have not loaded"));
261
- }, [N]);
266
+ }))])), F = d(async (t, r, i) => {
267
+ let a = M.current;
268
+ if (!a) return Promise.reject(/* @__PURE__ */ Error("contentkit-ui: the content filters have not loaded"));
269
+ if (m) {
270
+ if (!k()) return Promise.reject(new e("aborted", "The account changed."));
271
+ if (!a.kinds[t]?.some((e) => e.entity.id === r)) return Promise.reject(new e("invalid_request", "This content filter is not configured."));
272
+ let o = y.snapshot(), s = {
273
+ ...o.kinds,
274
+ [t]: [...(o.kinds[t] ?? []).filter((e) => e.entity_id !== r), {
275
+ entity_id: r,
276
+ visible: i
277
+ }]
278
+ };
279
+ return n.preferences.setGuestFilters(s), Promise.resolve(E(a, {
280
+ ...o,
281
+ kinds: s
282
+ }));
283
+ }
284
+ let o = P(a)[t] ?? [];
285
+ return N({
286
+ kinds: { [t]: [...o.filter((e) => e.entity_id !== r), {
287
+ entity_id: r,
288
+ visible: i
289
+ }] },
290
+ include_deleted: a.include_deleted
291
+ });
292
+ }, [
293
+ N,
294
+ m,
295
+ k,
296
+ y,
297
+ E,
298
+ n
299
+ ]), I = d((e) => M.current ? N({
300
+ kinds: {},
301
+ include_deleted: e
302
+ }) : Promise.reject(/* @__PURE__ */ Error("contentkit-ui: the content filters have not loaded")), [N]);
262
303
  return {
263
304
  filters: D ?? null,
264
305
  loading: T.loading || !T.loaded,
@@ -361,82 +402,162 @@ function V(e = {}) {
361
402
  return B(t);
362
403
  }
363
404
  function H(e, t, n = {}) {
364
- let r = V(n), { client: i, ...a } = n, o = h(() => e ? r.view(e, t, a) : null, [
405
+ let { client: r, scope: i, viewer: o, language: s } = a(n.client), c = B(r), l = x(r, i), u = h(() => ({ session: void 0 }), [
406
+ c,
407
+ e,
408
+ t,
409
+ i
410
+ ]);
411
+ return d((i) => {
412
+ e && l() && (o === void 0 || r.preferences.identity() === o) && (u.session ??= c.view(e, t, {
413
+ render_id: n.render_id,
414
+ surface: n.surface,
415
+ position: n.position,
416
+ language: n.language ?? s
417
+ }), u.session.update(i));
418
+ }, [
419
+ c,
365
420
  r,
366
421
  e,
367
- t
422
+ t,
423
+ o,
424
+ s,
425
+ n.language,
426
+ n.render_id,
427
+ n.surface,
428
+ n.position,
429
+ u,
430
+ l
368
431
  ]);
369
- return d((e) => o?.update(e), [o]);
370
432
  }
371
433
  function U(e, t) {
372
- let n = V(t), r = h(() => u(), [t.render]), i = h(() => ({
373
- render_id: r,
434
+ let { client: n, scope: r, language: i } = a(t.client), o = B(n), s = x(n, r), c = h(() => u(), [t.render, r]), l = h(() => ({
374
435
  surface: t.surface,
375
436
  ranker: t.ranker,
376
437
  query_id: t.query_id,
377
- language: t.language
438
+ language: t.language ?? i
378
439
  }), [
379
- r,
380
440
  t.surface,
381
441
  t.ranker,
382
442
  t.query_id,
383
- t.language
384
- ]), a = g(/* @__PURE__ */ new Set()), o = g(/* @__PURE__ */ new Set());
443
+ t.language,
444
+ i
445
+ ]), f = h(() => ({
446
+ segments: /* @__PURE__ */ new Map(),
447
+ pending: /* @__PURE__ */ new Set(),
448
+ timer: void 0
449
+ }), [c]), m = d((e) => e === 0 ? c : `${c}-${e}`, [c]), g = d((e) => {
450
+ let t = f.segments.get(e);
451
+ return t || (t = {
452
+ rendered: /* @__PURE__ */ new Map(),
453
+ visible: /* @__PURE__ */ new Map(),
454
+ renderedRevision: 0,
455
+ visibleRevision: 0
456
+ }, f.segments.set(e, t)), t;
457
+ }, [f]);
385
458
  p(() => {
386
- a.current = /* @__PURE__ */ new Set(), o.current = /* @__PURE__ */ new Set();
387
- }, [r]), p(() => {
388
- let t = e.flatMap((e, t) => a.current.has(e.id) ? [] : [{
389
- content_id: e.id,
390
- position: t + 1
391
- }]);
392
- for (let e of t) a.current.add(e.content_id);
393
- t.length && n.exposure({
394
- ...i,
395
- stage: "rendered",
396
- occurred_at: (/* @__PURE__ */ new Date()).toISOString(),
397
- shown: t
398
- });
459
+ if (!s()) return;
460
+ let t = /* @__PURE__ */ new Set();
461
+ for (let [n, r] of e.entries()) {
462
+ let e = Math.floor(n / W), i = g(e);
463
+ i.rendered.has(r.id) || (i.rendered.set(r.id, {
464
+ content_id: r.id,
465
+ version_id: r.version_id ?? r.version?.id,
466
+ position: n + 1
467
+ }), t.add(e));
468
+ }
469
+ for (let e of t) {
470
+ let t = g(e);
471
+ o.exposure({
472
+ ...l,
473
+ render_id: m(e),
474
+ stage: "rendered",
475
+ revision: ++t.renderedRevision,
476
+ occurred_at: t.renderedAt ??= (/* @__PURE__ */ new Date()).toISOString(),
477
+ shown: [...t.rendered.values()]
478
+ });
479
+ }
399
480
  }, [
400
- n,
401
- i,
402
- e
481
+ o,
482
+ l,
483
+ e,
484
+ f,
485
+ g,
486
+ m,
487
+ s
403
488
  ]);
404
- let s = g([]), c = g(void 0), l = h(() => typeof IntersectionObserver == "function" ? new IntersectionObserver((e) => {
405
- for (let t of e) {
406
- if (!t.isIntersecting) continue;
407
- let e = t.target, n = e.dataset.exposureContent;
408
- o.current.has(n) || (o.current.add(n), s.current.push({
409
- content_id: n,
410
- position: Number(e.dataset.exposurePosition)
411
- }));
489
+ let _ = h(() => typeof IntersectionObserver == "function" ? new IntersectionObserver((e) => {
490
+ if (s()) {
491
+ for (let t of e) {
492
+ if (!t.isIntersecting) continue;
493
+ let e = t.target, n = e.dataset.exposureContent, r = Number(e.dataset.exposurePosition), i = Math.floor((r - 1) / W), a = g(i);
494
+ a.visible.has(n) || (a.visible.set(n, a.rendered.get(n) ?? {
495
+ content_id: n,
496
+ position: r
497
+ }), f.pending.add(i));
498
+ }
499
+ f.pending.size && (f.timer ??= setTimeout(() => {
500
+ if (f.timer = void 0, s()) {
501
+ for (let e of f.pending) {
502
+ let t = g(e);
503
+ o.exposure({
504
+ ...l,
505
+ render_id: m(e),
506
+ stage: "visible",
507
+ revision: ++t.visibleRevision,
508
+ occurred_at: t.visibleAt ??= (/* @__PURE__ */ new Date()).toISOString(),
509
+ shown: [...t.visible.values()]
510
+ });
511
+ }
512
+ f.pending.clear();
513
+ }
514
+ }, 300));
412
515
  }
413
- c.current ??= setTimeout(() => {
414
- c.current = void 0;
415
- let e = s.current.splice(0);
416
- e.length && n.exposure({
417
- ...i,
418
- stage: "visible",
419
- occurred_at: (/* @__PURE__ */ new Date()).toISOString(),
420
- shown: e
421
- });
422
- }, 300);
423
- }, { threshold: .5 }) : null, [n, i]);
424
- return p(() => () => l?.disconnect(), [l]), {
425
- render_id: r,
516
+ }, { threshold: .5 }) : null, [
517
+ o,
518
+ l,
519
+ f,
520
+ g,
521
+ m,
522
+ s
523
+ ]);
524
+ return p(() => () => {
525
+ _?.disconnect(), clearTimeout(f.timer);
526
+ }, [_, f]), p(() => {
527
+ for (let e of f.segments.keys()) o.beginExposure(m(e));
528
+ return () => {
529
+ for (let e of f.segments.keys()) o.endExposure(m(e));
530
+ };
531
+ }, [
532
+ o,
533
+ f,
534
+ m
535
+ ]), {
536
+ render_id: c,
426
537
  track: d((e, t) => (n) => {
427
- n && l && (n.dataset.exposureContent = e, n.dataset.exposurePosition = String(t), l.observe(n));
428
- }, [l]),
429
- click: d((e, i) => n.click(e, {
430
- render_id: r,
431
- surface: t.surface,
432
- position: i
433
- }), [
434
- n,
435
- r,
436
- t.surface
538
+ n && _ && (n.dataset.exposureContent = e, n.dataset.exposurePosition = String(t), _.observe(n));
539
+ }, [_]),
540
+ click: d((n, r) => {
541
+ if (!s()) return;
542
+ let i = Math.floor((r - 1) / W), a = e.find((e) => e.id === n), c = a?.version_id ?? a?.version?.id;
543
+ o.click(n, {
544
+ render_id: m(i),
545
+ surface: t.surface,
546
+ position: r,
547
+ version_id: c,
548
+ language: l.language
549
+ });
550
+ }, [
551
+ o,
552
+ e,
553
+ m,
554
+ t.surface,
555
+ l.language,
556
+ s
437
557
  ])
438
558
  };
439
559
  }
560
+ var W = 32;
440
561
  //#endregion
441
562
  export { B as beaconsFor, V as useBeacons, I as useContentFilters, C as useContents, A as useDailyReleases, U as useExposure, j as useHistory, w as useLockedCount, P as useMyFavorites, F as useMyReactions, M as useRecentSearches, O as useRecommendations, k as useReleaseDates, T as useSearch, D as useSimilar, R as useTypeahead, H as useViewSignal };
442
563
 
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","names":[],"sources":["../../src/react/discovery.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { Beacons, eventId, type Attribution, type BeaconOptions, type ViewSession } from \"../client/beacons.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { MineQuery } from \"../client/content/favorites.js\";\nimport type { AccessMode, ContentQuery, DailyReleasesQuery, HistoryQuery, RailQuery, ReleaseDatesQuery, SearchQuery, TypeaheadQuery } from \"../client/discovery.js\";\nimport { ContentKitError, toContentKitError } from \"../client/errors.js\";\nimport type { Card, ContentFilters, ContentFiltersUpdate, ContentPage, DailyReleases, DateCount, GroupCard, Interaction, InteractionPage, RecentSearch } from \"../client/generated/wire.js\";\nimport { ContentKitContext, useGuarded } from \"./context.js\";\nimport { keyOf, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\nimport { recentSearchLimit } from \"../client/browser-search-history.js\";\n\nconst page = (p: ContentPage) => ({ items: p.contents, next: p.next_cursor ?? null });\nconst NO_SEARCHES: RecentSearch[] = [];\n\n// A new identity even after A → B → A, so late requests cannot write into the new visit.\nfunction useCurrentDiscoveryScope(client: ContentKitClient, scope: string): () => boolean {\n const owner = useMemo(() => ({ client, scope }), [client, scope]);\n const current = useRef<typeof owner | null>(owner);\n useLayoutEffect(() => {\n current.current = owner;\n return () => { current.current = null; };\n }, [owner]);\n return useCallback(() => current.current === owner, [owner]);\n}\n\n/** Titles, names and paths in the provider's language unless the query says otherwise. */\nfunction useDisplay<T extends { display_language?: string }>(q: T | null): T | null {\n const language = useContext(ContentKitContext)?.language;\n return useMemo(() => (q && !q.display_language && language ? { ...q, display_language: language } : q), [q, language]);\n}\n\n/** Contents as cards, a cursor page at a time; null reads nothing. */\nexport function useContents(q: ContentQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q);\n return useList(store, query ? keyOf(\"cards\", scope, \"list\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.list({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/**\n * How many contents of a list the reader doesn't open: its filters' count\n * with locked cards less without them (sort and paging aside); null reads\n * nothing.\n */\nexport function useLockedCount(q: ContentQuery | null, o: { client?: ContentKitClient } = {}): { count?: number; loading: boolean } {\n const { client, store, scope } = useContentScope(o.client);\n const filter = useMemo(() => {\n if (!q) return null;\n const { sort: _s, cursor: _c, limit: _l, card_entities: _g, mode: _m, ...f } = q;\n return f;\n }, [q]);\n const query = useDisplay(filter);\n const r = useResource<number>(store, query ? keyOf(\"release-dates\", scope, \"locked\", query) : null, { type: \"release-dates\" }, async (signal) => {\n const count = async (mode: AccessMode) =>\n (await client.discovery.releaseDates({ ...query, mode, by: \"month\", limit: 1000 }, signal)).dates.reduce((n, d) => n + d.count, 0);\n const [all, open] = await Promise.all([count(\"lock\"), count(\"hide\")]);\n return Math.max(0, all - open);\n });\n return { count: r.data, loading: r.loading };\n}\n\n/** Keyword search results as cards; nothing while q.q is empty. */\nexport function useSearch(q: SearchQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q?.q.trim() ? q : null);\n const isCurrent = useCurrentDiscoveryScope(client, scope);\n return useList(store, query ? keyOf(\"cards\", scope, \"search\", query) : null, { type: \"cards\" }, async (cursor, signal) => {\n const out = await client.discovery.search({ ...query!, cursor: cursor as string | undefined }, signal);\n if (!cursor && viewer === null && !signal.aborted && isCurrent()) client.discovery.browserSearches.record(query!.q, out.contents.length);\n return page(out);\n });\n}\n\nexport interface UseRail {\n items: Card[];\n loading: boolean;\n error?: ContentKitError;\n /** The site runs without the signal plane (not_configured): leave the rail out. */\n unavailable: boolean;\n reload: () => void;\n}\n\nfunction useRail(key: string | null, fetch: (signal: AbortSignal) => Promise<ContentPage>, o: { client?: ContentKitClient }): UseRail {\n const { store } = useContentScope(o.client);\n const r = useResource<Card[]>(store, key, { type: \"cards\" }, async (signal) => (await fetch(signal)).contents);\n const unavailable = r.error?.code === \"not_configured\";\n return { items: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: unavailable ? undefined : r.error, unavailable, reload: r.reload };\n}\n\n/** Contents like one: co-engagement, or the site's configured source. */\nexport function useSimilar(id: string | null | undefined, q: RailQuery = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useRail(id ? keyOf(\"cards\", scope, \"similar\", id, query) : null, (s) => client.discovery.similar(id!, query, s), o);\n}\n\n/** Contents for the signed-in reader; nothing signed out, or when q is null. */\nexport function useRecommendations(q: RailQuery | null = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n return useRail(viewer === null || !query ? null : keyOf(\"cards\", scope, \"recommended\", query), (s) => client.discovery.recommendations(query!, s), o);\n}\n\n/** A release date bucket, keyed by its date. */\nexport type ReleaseDate = DateCount & { id: string };\n\n/** The release date index: per month or day, newest first. */\nexport function useReleaseDates(q: ReleaseDatesQuery = {}, o: { client?: ContentKitClient } = {}): UseList<ReleaseDate> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, keyOf(\"release-dates\", scope, query), { type: \"release-dates\" }, async (cursor, signal) => {\n const r = await client.discovery.releaseDates({ ...query, cursor: cursor as string | undefined }, signal);\n return { items: r.dates.map((d) => ({ ...d, id: d.date })), next: r.next_cursor ?? null };\n });\n}\n\nexport interface UseDailyReleases {\n data?: DailyReleases;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\n/** One complete calendar window, shared by all readers of the same query. */\nexport function useDailyReleases(q: DailyReleasesQuery | null = {}, o: { client?: ContentKitClient } = {}): UseDailyReleases {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q);\n const r = useResource<DailyReleases>(store, query ? keyOf(\"daily-releases\", scope, query) : null, { type: \"daily-releases\" }, (signal) => client.discovery.releases(query!, signal));\n return { data: r.data, loading: !!query && (r.loading || !r.loaded), error: r.error, reload: r.reload };\n}\n\nexport interface UseHistory extends UseList<Card> {\n /** Erases the reader's history: of one content item, one kind, or (no argument) everything. */\n forget: (o?: { kind?: string; content_id?: string }) => Promise<void>;\n}\n\n/** What the signed-in reader engaged with, most recent first; nothing signed out, or when q is null. */\nexport function useHistory(q: HistoryQuery | null = {}, o: { client?: ContentKitClient } = {}): UseHistory {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n const guarded = useGuarded();\n const list = useList(store, viewer === null || !query ? null : keyOf(\"history\", scope, query), { type: \"history\" }, async (cursor, signal) =>\n page(await client.discovery.history({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n const forget = useCallback((f: { kind?: string; content_id?: string } = {}) => guarded(() => client.discovery.forgetHistory(f)), [client, guarded]);\n return { ...list, forget };\n}\n\nexport interface UseRecentSearches {\n searches: RecentSearch[];\n loading: boolean;\n error?: ContentKitError;\n /** Clears one query, or every recent search. */\n clear: (query?: string) => Promise<void>;\n}\n\n/** Guest history stays in this browser; account history comes only from the server. enabled false reads nothing yet. */\nexport function useRecentSearches(o: { limit?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseRecentSearches {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const guarded = useGuarded();\n const isCurrent = useCurrentDiscoveryScope(client, scope);\n const guest = viewer === null;\n const enabled = o.enabled !== false;\n const browser = client.discovery.browserSearches;\n const subscribe = useCallback((listener: () => void) => guest && enabled ? browser.subscribe(listener) : () => {}, [browser, guest, enabled]);\n const snapshot = useCallback(() => guest && enabled ? browser.snapshot() : NO_SEARCHES, [browser, guest, enabled]);\n const local = useSyncExternalStore(subscribe, snapshot, () => NO_SEARCHES);\n const searches = useMemo(() => local.slice(0, recentSearchLimit(o.limit)), [local, o.limit]);\n const key = !enabled || guest ? null : keyOf(\"searches\", scope, o.limit ?? 10);\n const r = useResource<RecentSearch[]>(store, key, { type: \"searches\" }, async (s) => (await client.discovery.recentSearches({ limit: o.limit }, s)).searches);\n useEffect(() => {\n if (!key) return;\n const reload = () => { if (!store.snapshot(key).loading) store.load(key, true); };\n // Reopening a panel must not leave an already-cached account list stale.\n if (store.snapshot(key).loaded) reload();\n const visible = () => { if (document.visibilityState === \"visible\") reload(); };\n if (typeof window !== \"undefined\") window.addEventListener(\"focus\", reload);\n if (typeof document !== \"undefined\") document.addEventListener(\"visibilitychange\", visible);\n return () => {\n if (typeof window !== \"undefined\") window.removeEventListener(\"focus\", reload);\n if (typeof document !== \"undefined\") document.removeEventListener(\"visibilitychange\", visible);\n };\n }, [key, store]);\n const clear = useCallback(async (query?: string) => {\n if (!isCurrent()) return;\n if (guest) { browser.clear(query); return; }\n await guarded(() => isCurrent() ? client.discovery.clearSearches(query ? { query } : {}) : Promise.resolve());\n }, [browser, client, guest, guarded, isCurrent]);\n return guest\n ? { searches: enabled ? searches : NO_SEARCHES, loading: false, clear }\n : { searches: r.data ?? NO_SEARCHES, loading: !!key && (r.loading || !r.loaded), error: r.error, clear };\n}\n\n/** A favorite or reaction with its content's or group's id. */\nexport type MyInteraction = Interaction & { id: string };\n\nconst interactions = (p: InteractionPage) => ({ items: p.items.map((x) => ({ ...x, id: x.content?.id ?? x.entity?.id ?? \"\" })), next: p.next_cursor ?? null });\n\n/** The signed-in reader's favorites, most recent first: content cards or groups. */\nexport function useMyFavorites(q: MineQuery = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"favorites\", scope, query), { type: \"favorites\" }, async (cursor, signal) =>\n interactions(await client.favorites.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/** What the signed-in reader liked (or, with value, disliked), most recent first. */\nexport function useMyReactions(q: MineQuery & { value?: \"like\" | \"dislike\" } = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"reactions\", scope, query), { type: \"reactions\" }, async (cursor, signal) =>\n interactions(await client.reactions.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseContentFilters {\n /** null until the configured filters have loaded. */\n filters: ContentFilters | null;\n loading: boolean;\n error?: ContentKitError;\n saving: boolean;\n /** Replaces every setting; the lists showing re-read. */\n save: (update: ContentFiltersUpdate) => Promise<ContentFilters>;\n /** Shows or hides one filter's group, keeping the other settings. */\n setVisible: (groupId: string, visible: boolean) => Promise<ContentFilters>;\n setIncludeDeleted: (include: boolean) => Promise<ContentFilters>;\n}\n\n/** Current-domain toggles: browser choices for guests, server choices for accounts. */\nexport function useContentFilters(o: { language?: string; client?: ContentKitClient } = {}): UseContentFilters {\n const { client, store, scope, viewer, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const guest = viewer === null;\n const browser = client.preferences.browser;\n const local = useSyncExternalStore(browser.subscribe, browser.snapshot, browser.snapshot);\n const key = keyOf(\"content-filters\", scope, language);\n const previous = useRef<{ client: ContentKitClient; language?: string; guest: boolean; data?: ContentFilters } | null>(null);\n const initial = guest && previous.current?.guest && previous.current.client === client && previous.current.language === language ? previous.current.data : undefined;\n const r = useResource<ContentFilters>(store, key, { type: \"content-filters\" }, (s) => client.discovery.contentFilters(language, s), { initial });\n previous.current = { client, language, guest, data: r.data };\n const applyGuest = useCallback((source: ContentFilters, settings = local.filters): ContentFilters => {\n const values = new Map(settings.map((f) => [f.group_id, f.visible]));\n return { include_deleted: false, filters: source.filters.map((f) => ({ ...f, visible: values.get(f.entity.id) ?? f.default_visible, overridden: values.has(f.entity.id) })) };\n }, [local.filters]);\n const displayed = useMemo(() => r.data && guest ? applyGuest(r.data) : r.data, [r.data, guest, applyGuest]);\n const guarded = useGuarded();\n const isCurrent = useCurrentDiscoveryScope(client, scope);\n const [inflight, setInflight] = useState(0);\n const current = useRef(displayed);\n current.current = displayed;\n useEffect(() => { setInflight(0); }, [client, scope]);\n const save = useCallback(\n async (update: ContentFiltersUpdate) => {\n if (!isCurrent()) throw new ContentKitError(\"aborted\", \"The account changed.\");\n if (guest) {\n const source = current.current;\n if (!source) throw new ContentKitError(\"invalid_request\", \"Content filters have not loaded.\");\n const allowed = new Set(source.filters.map((f) => f.entity.id));\n if (update.filters.some((f) => !allowed.has(f.group_id))) throw new ContentKitError(\"invalid_request\", \"This content filter is not configured.\");\n const merged = [...browser.snapshot().filters.filter((f) => !allowed.has(f.group_id)), ...update.filters];\n browser.setFilters(merged);\n return applyGuest(source, merged);\n }\n setInflight((n) => n + 1);\n try {\n const out = await guarded(() => {\n if (!isCurrent()) throw new ContentKitError(\"aborted\", \"The account changed.\");\n return client.discovery.setContentFilters(update, language);\n });\n if (isCurrent()) store.set(key, out);\n return out;\n } finally {\n setInflight((n) => Math.max(0, n - 1));\n }\n },\n [client, guarded, language, key, store, isCurrent, guest, browser, applyGuest],\n );\n const settings = (f: ContentFilters) => f.filters.filter((x) => x.overridden).map((x) => ({ group_id: x.entity.id, visible: x.visible }));\n const setVisible = useCallback(\n (groupId: string, visible: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: [...settings(f).filter((x) => x.group_id !== groupId), { group_id: groupId, visible }], include_deleted: f.include_deleted });\n },\n [save],\n );\n const setIncludeDeleted = useCallback(\n (include: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ filters: settings(f), include_deleted: include });\n },\n [save],\n );\n return { filters: displayed ?? null, loading: r.loading || !r.loaded, error: r.error, saving: inflight > 0, save, setVisible, setIncludeDeleted };\n}\n\nexport interface UseTypeahead {\n contents: Card[];\n entities: GroupCard[];\n /** The query the suggestions answer (trimmed). */\n query: string;\n loading: boolean;\n error?: ContentKitError;\n}\n\nconst NONE: Pick<UseTypeahead, \"contents\" | \"entities\"> = { contents: [], entities: [] };\n\n/** Suggestions while the reader types: debounced, the stale ones abandoned. */\nexport function useTypeahead(\n text: string,\n o: Omit<TypeaheadQuery, \"q\"> & { debounce?: number; minLength?: number; client?: ContentKitClient; management?: boolean } = {},\n): UseTypeahead {\n const { client, language, scope: viewerScope } = useContentScope(o.client);\n const { debounce = 150, minLength = 1, client: _own, management = false, ...rest } = o;\n const scope = JSON.stringify([viewerScope, management]);\n const q = text.trim();\n const query = useMemo(() => ({ display_language: language, ...rest, q }), [language, q, JSON.stringify(rest)]); // eslint-disable-line react-hooks/exhaustive-deps\n const [state, setState] = useState<UseTypeahead & { scope: string }>({ ...NONE, query: \"\", loading: false, scope });\n useEffect(() => {\n if (q.length < minLength) {\n setState({ ...NONE, query: \"\", loading: false, scope });\n return;\n }\n setState((s) => ({ ...(s.scope === scope ? s : { ...NONE, query: \"\" }), loading: true, scope }));\n const ctl = new AbortController();\n const timer = setTimeout(() => {\n const request = management\n ? client.entities.manage({ q, kind: query.entity_kinds?.[0], language: query.display_language, limit: query.limit ?? 10 }, ctl.signal)\n .then((page) => ({ contents: [], entities: page.entities }))\n : client.discovery.typeahead(query, ctl.signal);\n request.then(\n (r) => { if (!ctl.signal.aborted) setState({ contents: r.contents, entities: r.entities, query: q, loading: false, scope }); },\n (e) => {\n const error = toContentKitError(e);\n if (!ctl.signal.aborted && error.code !== \"aborted\") setState({ ...NONE, query: q, loading: false, error, scope });\n },\n );\n }, debounce);\n return () => {\n clearTimeout(timer);\n ctl.abort();\n };\n }, [client, query, q, debounce, minLength, management, scope]);\n return state.scope === scope ? state : { ...NONE, query: \"\", loading: q.length >= minLength };\n}\n\nconst beacons = new WeakMap<ContentKitClient, Beacons>();\n\n/** The client's signal and exposure batcher: one per client. */\nexport function beaconsFor(client: ContentKitClient, o?: BeaconOptions): Beacons {\n let b = beacons.get(client);\n if (!b) beacons.set(client, (b = new Beacons(client, o)));\n return b;\n}\n\nexport function useBeacons(o: { client?: ContentKitClient } = {}): Beacons {\n const { client } = useContentScope(o.client);\n return beaconsFor(client);\n}\n\n/** A view of a content (or one version) while it is shown: report its progress as it changes. */\nexport function useViewSignal(content: string | null | undefined, version?: string, o: Attribution & { client?: ContentKitClient } = {}): ViewSession[\"update\"] {\n const b = useBeacons(o);\n const { client: _c, ...attribution } = o;\n const session = useMemo(() => (content ? b.view(content, version, attribution) : null), [b, content, version]); // eslint-disable-line react-hooks/exhaustive-deps\n return useCallback((u) => session?.update(u), [session]);\n}\n\nexport interface ExposureOptions {\n /** Where the list is: \"home\", \"search\", \"similar\". */\n surface: string;\n /** Changes with what the list shows: a new render id. */\n render: string;\n ranker?: string;\n query_id?: string;\n language?: string;\n client?: ContentKitClient;\n}\n\nexport interface UseExposure {\n render_id: string;\n /** A card's ref: reports it shown once at least half of it is in view. */\n track: (content: string, position: number) => (el: Element | null) => void;\n /** Reports a click on the card at position. */\n click: (content: string, position: number) => void;\n}\n\n/** A rendered result list: each page rendered, each card shown, clicks attributed to the render. */\nexport function useExposure(items: readonly { id: string }[], o: ExposureOptions): UseExposure {\n const b = useBeacons(o);\n const renderId = useMemo(() => eventId(), [o.render]); // eslint-disable-line react-hooks/exhaustive-deps\n const base = useMemo(() => ({ render_id: renderId, surface: o.surface, ranker: o.ranker, query_id: o.query_id, language: o.language }), [renderId, o.surface, o.ranker, o.query_id, o.language]);\n const rendered = useRef(new Set<string>());\n const visible = useRef(new Set<string>());\n useEffect(() => {\n rendered.current = new Set();\n visible.current = new Set();\n }, [renderId]);\n useEffect(() => {\n const shown = items.flatMap((it, i) => (rendered.current.has(it.id) ? [] : [{ content_id: it.id, position: i + 1 }]));\n for (const s of shown) rendered.current.add(s.content_id);\n if (shown.length) b.exposure({ ...base, stage: \"rendered\", occurred_at: new Date().toISOString(), shown });\n }, [b, base, items]);\n const pending = useRef<{ content_id: string; position: number }[]>([]);\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n const observer = useMemo(() => {\n if (typeof IntersectionObserver !== \"function\") return null;\n return new IntersectionObserver(\n (entries) => {\n for (const e of entries) {\n if (!e.isIntersecting) continue;\n const el = e.target as HTMLElement;\n const content = el.dataset.exposureContent!;\n if (visible.current.has(content)) continue;\n visible.current.add(content);\n pending.current.push({ content_id: content, position: Number(el.dataset.exposurePosition) });\n }\n timer.current ??= setTimeout(() => {\n timer.current = undefined;\n const shown = pending.current.splice(0);\n if (shown.length) b.exposure({ ...base, stage: \"visible\", occurred_at: new Date().toISOString(), shown });\n }, 300);\n },\n { threshold: 0.5 },\n );\n }, [b, base]);\n useEffect(() => () => observer?.disconnect(), [observer]);\n const track = useCallback(\n (content: string, position: number) => (el: Element | null) => {\n if (!el || !observer) return;\n (el as HTMLElement).dataset.exposureContent = content;\n (el as HTMLElement).dataset.exposurePosition = String(position);\n observer.observe(el);\n },\n [observer],\n );\n const click = useCallback((content: string, position: number) => b.click(content, { render_id: renderId, surface: o.surface, position }), [b, renderId, o.surface]);\n return { render_id: renderId, track, click };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"discovery.js","names":[],"sources":["../../src/react/discovery.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { Beacons, eventId, type Attribution, type BeaconOptions, type ViewSession } from \"../client/beacons.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { MineQuery } from \"../client/content/favorites.js\";\nimport type { AccessMode, ContentQuery, DailyReleasesQuery, HistoryQuery, RailQuery, ReleaseDatesQuery, SearchQuery, TypeaheadQuery } from \"../client/discovery.js\";\nimport { ContentKitError, toContentKitError } from \"../client/errors.js\";\nimport type { Card, ContentFilters, ContentFiltersUpdate, ContentPage, DailyReleases, DateCount, GroupCard, Interaction, InteractionPage, RecentSearch } from \"../client/generated/wire.js\";\nimport { ContentKitContext, useGuarded } from \"./context.js\";\nimport { keyOf, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\nimport { recentSearchLimit } from \"../client/browser-search-history.js\";\n\nconst page = (p: ContentPage) => ({ items: p.contents, next: p.next_cursor ?? null });\nconst NO_SEARCHES: RecentSearch[] = [];\n\n// A new identity even after A → B → A, so late requests cannot write into the new visit.\nfunction useCurrentDiscoveryScope(client: ContentKitClient, scope: string): () => boolean {\n const owner = useMemo(() => ({ client, scope }), [client, scope]);\n const current = useRef<typeof owner | null>(owner);\n useLayoutEffect(() => {\n current.current = owner;\n return () => { current.current = null; };\n }, [owner]);\n return useCallback(() => current.current === owner, [owner]);\n}\n\n/** Titles, names and paths in the provider's language unless the query says otherwise. */\nfunction useDisplay<T extends { display_language?: string }>(q: T | null): T | null {\n const language = useContext(ContentKitContext)?.language;\n return useMemo(() => (q && !q.display_language && language ? { ...q, display_language: language } : q), [q, language]);\n}\n\n/** Contents as cards, a cursor page at a time; null reads nothing. */\nexport function useContents(q: ContentQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q);\n return useList(store, query ? keyOf(\"cards\", scope, \"list\", query) : null, { type: \"cards\" }, async (cursor, signal) =>\n page(await client.discovery.list({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/**\n * How many contents of a list the reader doesn't open: its filters' count\n * with locked cards less without them (sort and paging aside); null reads\n * nothing.\n */\nexport function useLockedCount(q: ContentQuery | null, o: { client?: ContentKitClient } = {}): { count?: number; loading: boolean } {\n const { client, store, scope } = useContentScope(o.client);\n const filter = useMemo(() => {\n if (!q) return null;\n const { sort: _s, cursor: _c, limit: _l, card_entities: _g, mode: _m, ...f } = q;\n return f;\n }, [q]);\n const query = useDisplay(filter);\n const r = useResource<number>(store, query ? keyOf(\"release-dates\", scope, \"locked\", query) : null, { type: \"release-dates\" }, async (signal) => {\n const count = async (mode: AccessMode) =>\n (await client.discovery.releaseDates({ ...query, mode, by: \"month\", limit: 1000 }, signal)).dates.reduce((n, d) => n + d.count, 0);\n const [all, open] = await Promise.all([count(\"lock\"), count(\"hide\")]);\n return Math.max(0, all - open);\n });\n return { count: r.data, loading: r.loading };\n}\n\n/** Keyword search results as cards; nothing while q.q is empty. */\nexport function useSearch(q: SearchQuery | null, o: { client?: ContentKitClient } = {}): UseList<Card> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q?.q.trim() ? q : null);\n const isCurrent = useCurrentDiscoveryScope(client, scope);\n return useList(store, query ? keyOf(\"cards\", scope, \"search\", query) : null, { type: \"cards\" }, async (cursor, signal) => {\n const out = await client.discovery.search({ ...query!, cursor: cursor as string | undefined }, signal);\n if (!cursor && viewer === null && !signal.aborted && isCurrent()) client.discovery.browserSearches.record(query!.q, out.contents.length);\n return page(out);\n });\n}\n\nexport interface UseRail {\n items: Card[];\n loading: boolean;\n error?: ContentKitError;\n /** The site runs without the signal plane (not_configured): leave the rail out. */\n unavailable: boolean;\n reload: () => void;\n}\n\nfunction useRail(key: string | null, fetch: (signal: AbortSignal) => Promise<ContentPage>, o: { client?: ContentKitClient }): UseRail {\n const { store } = useContentScope(o.client);\n const r = useResource<Card[]>(store, key, { type: \"cards\" }, async (signal) => (await fetch(signal)).contents);\n const unavailable = r.error?.code === \"not_configured\";\n return { items: r.data ?? [], loading: !!key && (r.loading || !r.loaded), error: unavailable ? undefined : r.error, unavailable, reload: r.reload };\n}\n\n/** Contents like one: co-engagement, or the site's configured source. */\nexport function useSimilar(id: string | null | undefined, q: RailQuery = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useRail(id ? keyOf(\"cards\", scope, \"similar\", id, query) : null, (s) => client.discovery.similar(id!, query, s), o);\n}\n\n/** Contents for the signed-in reader; nothing signed out, or when q is null. */\nexport function useRecommendations(q: RailQuery | null = {}, o: { client?: ContentKitClient } = {}): UseRail {\n const { client, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n return useRail(viewer === null || !query ? null : keyOf(\"cards\", scope, \"recommended\", query), (s) => client.discovery.recommendations(query!, s), o);\n}\n\n/** A release date bucket, keyed by its date. */\nexport type ReleaseDate = DateCount & { id: string };\n\n/** The release date index: per month or day, newest first. */\nexport function useReleaseDates(q: ReleaseDatesQuery = {}, o: { client?: ContentKitClient } = {}): UseList<ReleaseDate> {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, keyOf(\"release-dates\", scope, query), { type: \"release-dates\" }, async (cursor, signal) => {\n const r = await client.discovery.releaseDates({ ...query, cursor: cursor as string | undefined }, signal);\n return { items: r.dates.map((d) => ({ ...d, id: d.date })), next: r.next_cursor ?? null };\n });\n}\n\nexport interface UseDailyReleases {\n data?: DailyReleases;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\n/** One complete calendar window, shared by all readers of the same query. */\nexport function useDailyReleases(q: DailyReleasesQuery | null = {}, o: { client?: ContentKitClient } = {}): UseDailyReleases {\n const { client, store, scope } = useContentScope(o.client);\n const query = useDisplay(q);\n const r = useResource<DailyReleases>(store, query ? keyOf(\"daily-releases\", scope, query) : null, { type: \"daily-releases\" }, (signal) => client.discovery.releases(query!, signal));\n return { data: r.data, loading: !!query && (r.loading || !r.loaded), error: r.error, reload: r.reload };\n}\n\nexport interface UseHistory extends UseList<Card> {\n /** Erases the reader's history: of one content item, one kind, or (no argument) everything. */\n forget: (o?: { kind?: string; content_id?: string }) => Promise<void>;\n}\n\n/** What the signed-in reader engaged with, most recent first; nothing signed out, or when q is null. */\nexport function useHistory(q: HistoryQuery | null = {}, o: { client?: ContentKitClient } = {}): UseHistory {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q);\n const guarded = useGuarded();\n const list = useList(store, viewer === null || !query ? null : keyOf(\"history\", scope, query), { type: \"history\" }, async (cursor, signal) =>\n page(await client.discovery.history({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n const forget = useCallback((f: { kind?: string; content_id?: string } = {}) => guarded(() => client.discovery.forgetHistory(f)), [client, guarded]);\n return { ...list, forget };\n}\n\nexport interface UseRecentSearches {\n searches: RecentSearch[];\n loading: boolean;\n error?: ContentKitError;\n /** Clears one query, or every recent search. */\n clear: (query?: string) => Promise<void>;\n}\n\n/** Guest history stays in this browser; account history comes only from the server. enabled false reads nothing yet. */\nexport function useRecentSearches(o: { limit?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseRecentSearches {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const guarded = useGuarded();\n const isCurrent = useCurrentDiscoveryScope(client, scope);\n const guest = viewer === null;\n const enabled = o.enabled !== false;\n const browser = client.discovery.browserSearches;\n const subscribe = useCallback((listener: () => void) => guest && enabled ? browser.subscribe(listener) : () => {}, [browser, guest, enabled]);\n const snapshot = useCallback(() => guest && enabled ? browser.snapshot() : NO_SEARCHES, [browser, guest, enabled]);\n const local = useSyncExternalStore(subscribe, snapshot, () => NO_SEARCHES);\n const searches = useMemo(() => local.slice(0, recentSearchLimit(o.limit)), [local, o.limit]);\n const key = !enabled || guest ? null : keyOf(\"searches\", scope, o.limit ?? 10);\n const r = useResource<RecentSearch[]>(store, key, { type: \"searches\" }, async (s) => (await client.discovery.recentSearches({ limit: o.limit }, s)).searches);\n useEffect(() => {\n if (!key) return;\n const reload = () => { if (!store.snapshot(key).loading) store.load(key, true); };\n // Reopening a panel must not leave an already-cached account list stale.\n if (store.snapshot(key).loaded) reload();\n const visible = () => { if (document.visibilityState === \"visible\") reload(); };\n if (typeof window !== \"undefined\") window.addEventListener(\"focus\", reload);\n if (typeof document !== \"undefined\") document.addEventListener(\"visibilitychange\", visible);\n return () => {\n if (typeof window !== \"undefined\") window.removeEventListener(\"focus\", reload);\n if (typeof document !== \"undefined\") document.removeEventListener(\"visibilitychange\", visible);\n };\n }, [key, store]);\n const clear = useCallback(async (query?: string) => {\n if (!isCurrent()) return;\n if (guest) { browser.clear(query); return; }\n await guarded(() => isCurrent() ? client.discovery.clearSearches(query ? { query } : {}) : Promise.resolve());\n }, [browser, client, guest, guarded, isCurrent]);\n return guest\n ? { searches: enabled ? searches : NO_SEARCHES, loading: false, clear }\n : { searches: r.data ?? NO_SEARCHES, loading: !!key && (r.loading || !r.loaded), error: r.error, clear };\n}\n\n/** A favorite or reaction with its content's or group's id. */\nexport type MyInteraction = Interaction & { id: string };\n\nconst interactions = (p: InteractionPage) => ({ items: p.items.map((x) => ({ ...x, id: x.content?.id ?? x.entity?.id ?? \"\" })), next: p.next_cursor ?? null });\n\n/** The signed-in reader's favorites, most recent first: content cards or groups. */\nexport function useMyFavorites(q: MineQuery = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"favorites\", scope, query), { type: \"favorites\" }, async (cursor, signal) =>\n interactions(await client.favorites.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\n/** What the signed-in reader liked (or, with value, disliked), most recent first. */\nexport function useMyReactions(q: MineQuery & { value?: \"like\" | \"dislike\" } = {}, o: { client?: ContentKitClient } = {}): UseList<MyInteraction> {\n const { client, store, scope, viewer } = useContentScope(o.client);\n const query = useDisplay(q)!;\n return useList(store, viewer === null ? null : keyOf(\"reactions\", scope, query), { type: \"reactions\" }, async (cursor, signal) =>\n interactions(await client.reactions.mine({ ...query, cursor: cursor as string | undefined }, signal)),\n );\n}\n\nexport interface UseContentFilters {\n /** null until the configured filters have loaded. */\n filters: ContentFilters | null;\n loading: boolean;\n error?: ContentKitError;\n saving: boolean;\n /** Replaces offered settings for the supplied kinds; other kinds remain unchanged. */\n save: (update: ContentFiltersUpdate) => Promise<ContentFilters>;\n /** Shows or hides one filter's group, keeping the other settings. */\n setVisible: (kind: string, entityId: string, visible: boolean) => Promise<ContentFilters>;\n setIncludeDeleted: (include: boolean) => Promise<ContentFilters>;\n}\n\n/** Current-domain toggles: browser choices for guests, server choices for accounts. */\nexport function useContentFilters(o: { language?: string; client?: ContentKitClient } = {}): UseContentFilters {\n const { client, store, scope, viewer, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const guest = viewer === null;\n const browser = client.preferences.browser;\n const local = useSyncExternalStore(browser.subscribe, browser.snapshot, browser.snapshot);\n const key = keyOf(\"content-filters\", scope, language);\n const previous = useRef<{ client: ContentKitClient; language?: string; guest: boolean; data?: ContentFilters } | null>(null);\n const initial = guest && previous.current?.guest && previous.current.client === client && previous.current.language === language ? previous.current.data : undefined;\n const r = useResource<ContentFilters>(store, key, { type: \"content-filters\" }, (s) => client.discovery.contentFilters(language, s), { initial });\n previous.current = { client, language, guest, data: r.data };\n const applyGuest = useCallback((source: ContentFilters, choices = local): ContentFilters => {\n const legacy = new Map(choices.legacy_filters.map((f) => [f.entity_id, f.visible]));\n return { include_deleted: false, kinds: Object.fromEntries(Object.entries(source.kinds).map(([kind, rows]) => {\n const values = new Map<string, boolean | undefined>(legacy);\n for (const f of choices.kinds[kind] ?? []) values.set(f.entity_id, f.default ? undefined : f.visible);\n return [kind, rows.map((f) => ({ ...f, visible: values.get(f.entity.id) ?? f.default_visible, overridden: values.get(f.entity.id) !== undefined }))];\n })) };\n }, [local]);\n const displayed = useMemo(() => r.data && guest ? applyGuest(r.data) : r.data, [r.data, guest, applyGuest]);\n const guarded = useGuarded();\n const isCurrent = useCurrentDiscoveryScope(client, scope);\n const [inflight, setInflight] = useState(0);\n const current = useRef(displayed);\n current.current = displayed;\n useEffect(() => { setInflight(0); }, [client, scope]);\n const save = useCallback(\n async (update: ContentFiltersUpdate) => {\n if (!isCurrent()) throw new ContentKitError(\"aborted\", \"The account changed.\");\n if (guest) {\n const source = current.current;\n if (!source) throw new ContentKitError(\"invalid_request\", \"Content filters have not loaded.\");\n const snapshot = browser.snapshot();\n const merged = { ...snapshot.kinds };\n const legacy = new Set(snapshot.legacy_filters.map((row) => row.entity_id));\n for (const [kind, updates] of Object.entries(update.kinds)) {\n const offered = source.kinds[kind];\n if (!offered) throw new ContentKitError(\"invalid_request\", \"This content kind has no configurable filters.\");\n const allowed = new Set(offered.map((f) => f.entity.id));\n if (updates.some((f) => !allowed.has(f.entity_id))) throw new ContentKitError(\"invalid_request\", \"This content filter is not configured.\");\n const explicit = new Set(updates.map((f) => f.entity_id));\n // Resets follow whichever domain default is current, while suppressing\n // an old unscoped preference for only this kind/entity pair.\n const defaults = offered.filter((f) => legacy.has(f.entity.id) && !explicit.has(f.entity.id))\n .map((f) => ({ entity_id: f.entity.id, default: true as const }));\n merged[kind] = [...(snapshot.kinds[kind] ?? []).filter((f) => !allowed.has(f.entity_id)), ...updates, ...defaults];\n }\n client.preferences.setGuestFilters(merged);\n return applyGuest(source, { ...snapshot, kinds: merged });\n }\n setInflight((n) => n + 1);\n try {\n const out = await guarded(() => {\n if (!isCurrent()) throw new ContentKitError(\"aborted\", \"The account changed.\");\n return client.discovery.setContentFilters(update, language);\n });\n if (isCurrent()) store.set(key, out);\n return out;\n } finally {\n setInflight((n) => Math.max(0, n - 1));\n }\n },\n [client, guarded, language, key, store, isCurrent, guest, browser, applyGuest],\n );\n const settings = (f: ContentFilters) => Object.fromEntries(Object.entries(f.kinds).map(([kind, rows]) =>\n [kind, rows.filter((x) => x.overridden).map((x) => ({ entity_id: x.entity.id, visible: x.visible }))]));\n const setVisible = useCallback(\n async (kind: string, entityId: string, visible: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n if (guest) {\n if (!isCurrent()) return Promise.reject(new ContentKitError(\"aborted\", \"The account changed.\"));\n if (!f.kinds[kind]?.some((row) => row.entity.id === entityId)) return Promise.reject(new ContentKitError(\"invalid_request\", \"This content filter is not configured.\"));\n const snapshot = browser.snapshot();\n const kinds = { ...snapshot.kinds, [kind]: [...(snapshot.kinds[kind] ?? []).filter((row) => row.entity_id !== entityId), { entity_id: entityId, visible }] };\n client.preferences.setGuestFilters(kinds);\n return Promise.resolve(applyGuest(f, { ...snapshot, kinds }));\n }\n const rows = settings(f)[kind] ?? [];\n return save({ kinds: { [kind]: [...rows.filter((x) => x.entity_id !== entityId), { entity_id: entityId, visible }] }, include_deleted: f.include_deleted });\n },\n [save, guest, isCurrent, browser, applyGuest, client],\n );\n const setIncludeDeleted = useCallback(\n (include: boolean) => {\n const f = current.current;\n if (!f) return Promise.reject(new Error(\"contentkit-ui: the content filters have not loaded\"));\n return save({ kinds: {}, include_deleted: include });\n },\n [save],\n );\n return { filters: displayed ?? null, loading: r.loading || !r.loaded, error: r.error, saving: inflight > 0, save, setVisible, setIncludeDeleted };\n}\n\nexport interface UseTypeahead {\n contents: Card[];\n entities: GroupCard[];\n /** The query the suggestions answer (trimmed). */\n query: string;\n loading: boolean;\n error?: ContentKitError;\n}\n\nconst NONE: Pick<UseTypeahead, \"contents\" | \"entities\"> = { contents: [], entities: [] };\n\n/** Suggestions while the reader types: debounced, the stale ones abandoned. */\nexport function useTypeahead(\n text: string,\n o: Omit<TypeaheadQuery, \"q\"> & { debounce?: number; minLength?: number; client?: ContentKitClient; management?: boolean } = {},\n): UseTypeahead {\n const { client, language, scope: viewerScope } = useContentScope(o.client);\n const { debounce = 150, minLength = 1, client: _own, management = false, ...rest } = o;\n const scope = JSON.stringify([viewerScope, management]);\n const q = text.trim();\n const query = useMemo(() => ({ display_language: language, ...rest, q }), [language, q, JSON.stringify(rest)]); // eslint-disable-line react-hooks/exhaustive-deps\n const [state, setState] = useState<UseTypeahead & { scope: string }>({ ...NONE, query: \"\", loading: false, scope });\n useEffect(() => {\n if (q.length < minLength) {\n setState({ ...NONE, query: \"\", loading: false, scope });\n return;\n }\n setState((s) => ({ ...(s.scope === scope ? s : { ...NONE, query: \"\" }), loading: true, scope }));\n const ctl = new AbortController();\n const timer = setTimeout(() => {\n const request = management\n ? client.entities.manage({ q, kind: query.entity_kinds?.[0], language: query.display_language, limit: query.limit ?? 10 }, ctl.signal)\n .then((page) => ({ contents: [], entities: page.entities }))\n : client.discovery.typeahead(query, ctl.signal);\n request.then(\n (r) => { if (!ctl.signal.aborted) setState({ contents: r.contents, entities: r.entities, query: q, loading: false, scope }); },\n (e) => {\n const error = toContentKitError(e);\n if (!ctl.signal.aborted && error.code !== \"aborted\") setState({ ...NONE, query: q, loading: false, error, scope });\n },\n );\n }, debounce);\n return () => {\n clearTimeout(timer);\n ctl.abort();\n };\n }, [client, query, q, debounce, minLength, management, scope]);\n return state.scope === scope ? state : { ...NONE, query: \"\", loading: q.length >= minLength };\n}\n\nconst beacons = new WeakMap<ContentKitClient, Beacons>();\n\n/** The client's signal and exposure batcher: one per client. */\nexport function beaconsFor(client: ContentKitClient, o?: BeaconOptions): Beacons {\n let b = beacons.get(client);\n if (!b) beacons.set(client, (b = new Beacons(client, o)));\n return b;\n}\n\nexport function useBeacons(o: { client?: ContentKitClient } = {}): Beacons {\n const { client } = useContentScope(o.client);\n return beaconsFor(client);\n}\n\n/** A view of a content (or one version) while it is shown: report its progress as it changes. */\nexport function useViewSignal(content: string | null | undefined, version?: string, o: Attribution & { client?: ContentKitClient } = {}): ViewSession[\"update\"] {\n const { client, scope, viewer, language } = useContentScope(o.client);\n const b = beaconsFor(client);\n const isCurrent = useCurrentDiscoveryScope(client, scope);\n // Retire the mutable session whenever its owner or read scope changes.\n const holder = useMemo(() => ({ session: undefined as ViewSession | undefined }), [b, content, version, scope]); // eslint-disable-line react-hooks/exhaustive-deps\n return useCallback((u) => {\n if (!content || !isCurrent() || (viewer !== undefined && client.preferences.identity() !== viewer)) return;\n // Create after the committed provider has configured authentication, not\n // during render while the transport can still belong to the old account.\n holder.session ??= b.view(content, version, { render_id: o.render_id, surface: o.surface, position: o.position, language: o.language ?? language });\n holder.session.update(u);\n }, [b, client, content, version, viewer, language, o.language, o.render_id, o.surface, o.position, holder, isCurrent]);\n}\n\nexport interface ExposureOptions {\n /** Where the list is: \"home\", \"search\", \"similar\". */\n surface: string;\n /** Changes with what the list shows: a new render id. */\n render: string;\n ranker?: string;\n query_id?: string;\n language?: string;\n client?: ContentKitClient;\n}\n\nexport interface UseExposure {\n /** The first bounded segment; click() selects the matching segment for later positions. */\n render_id: string;\n /** A card's ref: reports it shown once at least half of it is in view. */\n track: (content: string, position: number) => (el: Element | null) => void;\n /** Reports a click on the card at position. */\n click: (content: string, position: number) => void;\n}\n\n/** A rendered result list: each page rendered, each card shown, clicks attributed to the render. */\nexport function useExposure(items: readonly { id: string; version_id?: string; version?: { id: string } | null }[], o: ExposureOptions): UseExposure {\n const { client, scope, language } = useContentScope(o.client);\n const b = beaconsFor(client);\n const isCurrent = useCurrentDiscoveryScope(client, scope);\n const renderId = useMemo(() => eventId(), [o.render, scope]); // eslint-disable-line react-hooks/exhaustive-deps\n const base = useMemo(() => ({ surface: o.surface, ranker: o.ranker, query_id: o.query_id, language: o.language ?? language }), [o.surface, o.ranker, o.query_id, o.language, language]);\n const state = useMemo(() => ({ segments: new Map<number, ExposureSegment>(), pending: new Set<number>(), timer: undefined as ReturnType<typeof setTimeout> | undefined }), [renderId]); // eslint-disable-line react-hooks/exhaustive-deps\n const segmentID = useCallback((index: number) => index === 0 ? renderId : `${renderId}-${index}`, [renderId]);\n const segmentFor = useCallback((index: number) => {\n let segment = state.segments.get(index);\n if (!segment) {\n segment = { rendered: new Map(), visible: new Map(), renderedRevision: 0, visibleRevision: 0 };\n state.segments.set(index, segment);\n }\n return segment;\n }, [state]);\n useEffect(() => {\n if (!isCurrent()) return;\n const changed = new Set<number>();\n for (const [i, item] of items.entries()) {\n const index = Math.floor(i / EXPOSURE_SEGMENT_SIZE), segment = segmentFor(index);\n if (segment.rendered.has(item.id)) continue;\n segment.rendered.set(item.id, { content_id: item.id, version_id: item.version_id ?? item.version?.id, position: i + 1 });\n changed.add(index);\n }\n for (const index of changed) {\n const segment = segmentFor(index);\n b.exposure({ ...base, render_id: segmentID(index), stage: \"rendered\", revision: ++segment.renderedRevision,\n occurred_at: segment.renderedAt ??= new Date().toISOString(), shown: [...segment.rendered.values()] });\n }\n }, [b, base, items, state, segmentFor, segmentID, isCurrent]);\n const observer = useMemo(() => {\n if (typeof IntersectionObserver !== \"function\") return null;\n return new IntersectionObserver((entries) => {\n if (!isCurrent()) return;\n for (const e of entries) {\n if (!e.isIntersecting) continue;\n const el = e.target as HTMLElement, content = el.dataset.exposureContent!;\n const position = Number(el.dataset.exposurePosition), index = Math.floor((position - 1) / EXPOSURE_SEGMENT_SIZE);\n const segment = segmentFor(index);\n if (segment.visible.has(content)) continue;\n segment.visible.set(content, segment.rendered.get(content) ?? { content_id: content, position });\n state.pending.add(index);\n }\n if (!state.pending.size) return;\n state.timer ??= setTimeout(() => {\n state.timer = undefined;\n if (!isCurrent()) return;\n for (const index of state.pending) {\n const segment = segmentFor(index);\n b.exposure({ ...base, render_id: segmentID(index), stage: \"visible\", revision: ++segment.visibleRevision,\n occurred_at: segment.visibleAt ??= new Date().toISOString(), shown: [...segment.visible.values()] });\n }\n state.pending.clear();\n }, 300);\n }, { threshold: 0.5 });\n }, [b, base, state, segmentFor, segmentID, isCurrent]);\n useEffect(() => () => { observer?.disconnect(); clearTimeout(state.timer); }, [observer, state]);\n useEffect(() => {\n for (const index of state.segments.keys()) b.beginExposure(segmentID(index));\n return () => { for (const index of state.segments.keys()) b.endExposure(segmentID(index)); };\n }, [b, state, segmentID]);\n const track = useCallback((content: string, position: number) => (el: Element | null) => {\n if (!el || !observer) return;\n (el as HTMLElement).dataset.exposureContent = content;\n (el as HTMLElement).dataset.exposurePosition = String(position);\n observer.observe(el);\n }, [observer]);\n const click = useCallback((content: string, position: number) => {\n if (!isCurrent()) return;\n const index = Math.floor((position - 1) / EXPOSURE_SEGMENT_SIZE);\n const item = items.find((item) => item.id === content);\n const version = item?.version_id ?? item?.version?.id;\n b.click(content, { render_id: segmentID(index), surface: o.surface, position, version_id: version, language: base.language });\n }, [b, items, segmentID, o.surface, base.language, isCurrent]);\n return { render_id: renderId, track, click };\n}\n\ntype ObservedPlacement = { content_id: string; version_id?: string; position: number };\ntype ExposureSegment = {\n rendered: Map<string, ObservedPlacement>;\n visible: Map<string, ObservedPlacement>;\n renderedRevision: number;\n visibleRevision: number;\n renderedAt?: string;\n visibleAt?: string;\n};\nconst EXPOSURE_SEGMENT_SIZE = 32;\n"],"mappings":"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"}
@@ -8,7 +8,10 @@ function c({ client: c, viewer: l, language: u, languagePreference: d, accessRev
8
8
  viewer: l,
9
9
  language: u,
10
10
  accessRevision: f,
11
- guestFilters: l === null ? b.filters : void 0
11
+ guestFilters: l === null ? {
12
+ kinds: b.kinds,
13
+ legacy_filters: b.legacy_filters
14
+ } : void 0
12
15
  });
13
16
  r(() => {
14
17
  c.preferences.configure(l, d);
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","names":[],"sources":["../../src/react/provider.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport { ContentKitContext, type ContentKitErrorHandler, type Navigate } from \"./context.js\";\nimport { storeFor } from \"./store.js\";\nimport type { LanguagePreference } from \"../client/reader-preferences.js\";\n\nexport interface ContentKitProviderProps {\n client: ContentKitClient;\n /** The signed-in user's id as ContentKit sees it (the actor id), or null when signed out. */\n viewer?: string | null;\n /** Request language; keep it in sync with the client's language callback. */\n language?: string;\n /** Account storage for the preferred language; return null when unset. */\n languagePreference?: LanguagePreference;\n /** Change when the current viewer's permissions or entitlements change. */\n accessRevision?: string | number;\n /** Asks the visitor to sign in: comments, reactions, favorites and votes prompt with it rather than act anonymously. */\n onSignIn?: () => void;\n /** Content URL helpers (createContentURLs from `/urls`). */\n urls?: ContentURLs;\n /** Host router for in-app navigation (a canonical replace). */\n navigate?: Navigate;\n /** Every successful mutation; null scope means refetch instead of applying its data. */\n onChange?: (change: ContentKitChange, scope: string | null) => void;\n /** Receives every failure the components show (a component's own `onError` wins). */\n onError?: ContentKitErrorHandler;\n children?: ReactNode;\n}\n\n/** Client, URL config, callbacks and the shared read store for every hook below. Renders no DOM. */\nexport function ContentKitProvider({ client, viewer, language, languagePreference, accessRevision, onSignIn, urls, navigate, onChange, onError, children }: ContentKitProviderProps) {\n const browser = client.preferences.browser;\n const preferences = useSyncExternalStore(browser.subscribe, browser.snapshot, browser.snapshot);\n const readScope = JSON.stringify({ viewer, language, accessRevision, guestFilters: viewer === null ? preferences.filters : undefined });\n useLayoutEffect(() => { client.preferences.configure(viewer, languagePreference); }, [client, viewer, languagePreference]);\n useLayoutEffect(() => { client.setReadScope(readScope); }, [client, readScope]);\n const changed = useRef(onChange);\n useEffect(() => {\n changed.current = onChange;\n });\n useEffect(() => client.subscribe((c, scope) => changed.current?.(c, scope)), [client]);\n const value = useMemo(\n () => ({ client, viewer, language, accessRevision, readScope, onSignIn, urls, navigate, onError, store: storeFor(client) }),\n [client, viewer, language, accessRevision, readScope, onSignIn, urls, navigate, onError],\n );\n return <ContentKitContext.Provider value={value}>{children}</ContentKitContext.Provider>;\n}\n"],"mappings":";;;;;AAgCA,SAAgB,EAAmB,EAAE,WAAQ,WAAQ,aAAU,uBAAoB,mBAAgB,aAAU,SAAM,aAAU,aAAU,YAAS,eAAqC;CACnL,IAAM,IAAU,EAAO,YAAY,SAC7B,IAAc,EAAqB,EAAQ,WAAW,EAAQ,UAAU,EAAQ,QAAQ,GACxF,IAAY,KAAK,UAAU;EAAE;EAAQ;EAAU;EAAgB,cAAc,MAAW,OAAO,EAAY,UAAU,KAAA;CAAU,CAAC;CAEtI,AADA,QAAsB;EAAE,EAAO,YAAY,UAAU,GAAQ,CAAkB;CAAG,GAAG;EAAC;EAAQ;EAAQ;CAAkB,CAAC,GACzH,QAAsB;EAAE,EAAO,aAAa,CAAS;CAAG,GAAG,CAAC,GAAQ,CAAS,CAAC;CAC9E,IAAM,IAAU,EAAO,CAAQ;CAI/B,AAHA,QAAgB;EACd,EAAQ,UAAU;CACpB,CAAC,GACD,QAAgB,EAAO,WAAW,GAAG,MAAU,EAAQ,UAAU,GAAG,CAAK,CAAC,GAAG,CAAC,CAAM,CAAC;CACrF,IAAM,IAAQ,SACL;EAAE;EAAQ;EAAQ;EAAU;EAAgB;EAAW;EAAU;EAAM;EAAU;EAAS,OAAO,EAAS,CAAM;CAAE,IACzH;EAAC;EAAQ;EAAQ;EAAU;EAAgB;EAAW;EAAU;EAAM;EAAU;CAAO,CACzF;CACA,OAAO,kBAAC,EAAkB,UAAnB;EAAmC;EAAQ;CAAqC,CAAA;AACzF"}
1
+ {"version":3,"file":"provider.js","names":[],"sources":["../../src/react/provider.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useMemo, useRef, useSyncExternalStore, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport { ContentKitContext, type ContentKitErrorHandler, type Navigate } from \"./context.js\";\nimport { storeFor } from \"./store.js\";\nimport type { LanguagePreference } from \"../client/reader-preferences.js\";\n\nexport interface ContentKitProviderProps {\n client: ContentKitClient;\n /** The signed-in user's id as ContentKit sees it (the actor id), or null when signed out. */\n viewer?: string | null;\n /** Request language; keep it in sync with the client's language callback. */\n language?: string;\n /** Account storage for the preferred language; return null when unset. */\n languagePreference?: LanguagePreference;\n /** Change when the current viewer's permissions or entitlements change. */\n accessRevision?: string | number;\n /** Asks the visitor to sign in: comments, reactions, favorites and votes prompt with it rather than act anonymously. */\n onSignIn?: () => void;\n /** Content URL helpers (createContentURLs from `/urls`). */\n urls?: ContentURLs;\n /** Host router for in-app navigation (a canonical replace). */\n navigate?: Navigate;\n /** Every successful mutation; null scope means refetch instead of applying its data. */\n onChange?: (change: ContentKitChange, scope: string | null) => void;\n /** Receives every failure the components show (a component's own `onError` wins). */\n onError?: ContentKitErrorHandler;\n children?: ReactNode;\n}\n\n/** Client, URL config, callbacks and the shared read store for every hook below. Renders no DOM. */\nexport function ContentKitProvider({ client, viewer, language, languagePreference, accessRevision, onSignIn, urls, navigate, onChange, onError, children }: ContentKitProviderProps) {\n const browser = client.preferences.browser;\n const preferences = useSyncExternalStore(browser.subscribe, browser.snapshot, browser.snapshot);\n const readScope = JSON.stringify({ viewer, language, accessRevision, guestFilters: viewer === null ? { kinds: preferences.kinds, legacy_filters: preferences.legacy_filters } : undefined });\n useLayoutEffect(() => { client.preferences.configure(viewer, languagePreference); }, [client, viewer, languagePreference]);\n useLayoutEffect(() => { client.setReadScope(readScope); }, [client, readScope]);\n const changed = useRef(onChange);\n useEffect(() => {\n changed.current = onChange;\n });\n useEffect(() => client.subscribe((c, scope) => changed.current?.(c, scope)), [client]);\n const value = useMemo(\n () => ({ client, viewer, language, accessRevision, readScope, onSignIn, urls, navigate, onError, store: storeFor(client) }),\n [client, viewer, language, accessRevision, readScope, onSignIn, urls, navigate, onError],\n );\n return <ContentKitContext.Provider value={value}>{children}</ContentKitContext.Provider>;\n}\n"],"mappings":";;;;;AAgCA,SAAgB,EAAmB,EAAE,WAAQ,WAAQ,aAAU,uBAAoB,mBAAgB,aAAU,SAAM,aAAU,aAAU,YAAS,eAAqC;CACnL,IAAM,IAAU,EAAO,YAAY,SAC7B,IAAc,EAAqB,EAAQ,WAAW,EAAQ,UAAU,EAAQ,QAAQ,GACxF,IAAY,KAAK,UAAU;EAAE;EAAQ;EAAU;EAAgB,cAAc,MAAW,OAAO;GAAE,OAAO,EAAY;GAAO,gBAAgB,EAAY;EAAe,IAAI,KAAA;CAAU,CAAC;CAE3L,AADA,QAAsB;EAAE,EAAO,YAAY,UAAU,GAAQ,CAAkB;CAAG,GAAG;EAAC;EAAQ;EAAQ;CAAkB,CAAC,GACzH,QAAsB;EAAE,EAAO,aAAa,CAAS;CAAG,GAAG,CAAC,GAAQ,CAAS,CAAC;CAC9E,IAAM,IAAU,EAAO,CAAQ;CAI/B,AAHA,QAAgB;EACd,EAAQ,UAAU;CACpB,CAAC,GACD,QAAgB,EAAO,WAAW,GAAG,MAAU,EAAQ,UAAU,GAAG,CAAK,CAAC,GAAG,CAAC,CAAM,CAAC;CACrF,IAAM,IAAQ,SACL;EAAE;EAAQ;EAAQ;EAAU;EAAgB;EAAW;EAAU;EAAM;EAAU;EAAS,OAAO,EAAS,CAAM;CAAE,IACzH;EAAC;EAAQ;EAAQ;EAAU;EAAgB;EAAW;EAAU;EAAM;EAAU;CAAO,CACzF;CACA,OAAO,kBAAC,EAAkB,UAAnB;EAAmC;EAAQ;CAAqC,CAAA;AACzF"}