@openrails/contentkit-ui 0.103.2 → 0.104.0-rc.2
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 +111 -49
- package/dist/client/beacons.d.ts +3 -13
- package/dist/client/beacons.js +8 -46
- package/dist/client/beacons.js.map +1 -1
- package/dist/client/client.d.ts +3 -0
- package/dist/client/client.js +45 -43
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/favorites.d.ts +2 -0
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/types.d.ts +3 -0
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/contents.d.ts +7 -1
- package/dist/client/contents.js +8 -0
- package/dist/client/contents.js.map +1 -1
- package/dist/client/discovery.d.ts +34 -5
- package/dist/client/discovery.js +23 -2
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/engagement.d.ts +70 -0
- package/dist/client/engagement.js +191 -0
- package/dist/client/engagement.js.map +1 -0
- package/dist/client/generated/routes.d.ts +74 -35
- package/dist/client/generated/routes.js +119 -60
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +88 -85
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/guest-reading-history.d.ts +40 -0
- package/dist/client/guest-reading-history.js +144 -0
- package/dist/client/guest-reading-history.js.map +1 -0
- package/dist/client/index.d.ts +5 -3
- package/dist/client/media/windows.d.ts +2 -0
- package/dist/client/media/windows.js +4 -1
- package/dist/client/media/windows.js.map +1 -1
- package/dist/client/stats.d.ts +4 -20
- package/dist/client/stats.js +0 -11
- package/dist/client/stats.js.map +1 -1
- package/dist/client/versions.d.ts +8 -6
- package/dist/client/versions.js +15 -10
- package/dist/client/versions.js.map +1 -1
- package/dist/client.js +24 -22
- package/dist/components/admin/bulk-drafts.d.ts +17 -0
- package/dist/components/admin/bulk-drafts.js +41 -0
- package/dist/components/admin/bulk-drafts.js.map +1 -0
- package/dist/components/admin/content-admin.d.ts +1 -1
- package/dist/components/admin/content-admin.js.map +1 -1
- package/dist/components/admin/content-manager.d.ts +11 -3
- package/dist/components/admin/content-manager.js +751 -394
- package/dist/components/admin/content-manager.js.map +1 -1
- package/dist/components/admin/group-manager.d.ts +8 -3
- package/dist/components/admin/group-manager.js +218 -146
- package/dist/components/admin/group-manager.js.map +1 -1
- package/dist/components/admin/page-notices.d.ts +16 -0
- package/dist/components/admin/page-notices.js +113 -0
- package/dist/components/admin/page-notices.js.map +1 -0
- package/dist/components/admin/page-picker.d.ts +38 -0
- package/dist/components/admin/page-picker.js +159 -0
- package/dist/components/admin/page-picker.js.map +1 -0
- package/dist/components/admin/quick-search.d.ts +14 -0
- package/dist/components/admin/quick-search.js +60 -0
- package/dist/components/admin/quick-search.js.map +1 -0
- package/dist/components/admin/reader-content-editor.js +382 -321
- package/dist/components/admin/reader-content-editor.js.map +1 -1
- package/dist/components/admin/reader-manifest.js +181 -179
- package/dist/components/admin/reader-manifest.js.map +1 -1
- package/dist/components/admin/shell.d.ts +10 -2
- package/dist/components/admin/shell.js +114 -42
- package/dist/components/admin/shell.js.map +1 -1
- package/dist/components/admin/stats-dashboard.d.ts +4 -9
- package/dist/components/admin/stats-dashboard.js +303 -207
- package/dist/components/admin/stats-dashboard.js.map +1 -1
- package/dist/components/admin/upload-queue.d.ts +14 -0
- package/dist/components/admin/upload-queue.js +115 -98
- package/dist/components/admin/upload-queue.js.map +1 -1
- package/dist/components/content/card.d.ts +31 -3
- package/dist/components/content/card.js +324 -158
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/daily-releases.js +144 -190
- package/dist/components/content/daily-releases.js.map +1 -1
- package/dist/components/content/editor-save.js +27 -25
- package/dist/components/content/editor-save.js.map +1 -1
- package/dist/components/content/editors.js +274 -253
- package/dist/components/content/editors.js.map +1 -1
- package/dist/components/content/favorites.d.ts +6 -0
- package/dist/components/content/favorites.js +47 -0
- package/dist/components/content/favorites.js.map +1 -0
- package/dist/components/content/filter-dialog.js +19 -15
- package/dist/components/content/filter-dialog.js.map +1 -1
- package/dist/components/content/filters.d.ts +2 -0
- package/dist/components/content/filters.js +5 -1
- package/dist/components/content/filters.js.map +1 -1
- package/dist/components/content/grid.d.ts +44 -16
- package/dist/components/content/grid.js +308 -270
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/history.d.ts +6 -0
- package/dist/components/content/history.js +111 -0
- package/dist/components/content/history.js.map +1 -0
- package/dist/components/content/index.d.ts +6 -3
- package/dist/components/content/listing.d.ts +14 -3
- package/dist/components/content/listing.js +65 -56
- package/dist/components/content/listing.js.map +1 -1
- package/dist/components/content/offer-fields.js +6 -6
- package/dist/components/content/paged-grid.d.ts +10 -0
- package/dist/components/content/paged-grid.js +116 -0
- package/dist/components/content/paged-grid.js.map +1 -0
- package/dist/components/content/pagination.d.ts +19 -0
- package/dist/components/content/pagination.js +97 -0
- package/dist/components/content/pagination.js.map +1 -0
- package/dist/components/content/paywall.d.ts +9 -0
- package/dist/components/content/paywall.js +27 -3
- package/dist/components/content/paywall.js.map +1 -1
- package/dist/components/content/post-list.d.ts +37 -0
- package/dist/components/content/post-list.js +203 -0
- package/dist/components/content/post-list.js.map +1 -0
- package/dist/components/content/reader-settings.d.ts +2 -0
- package/dist/components/content/reader-settings.js +1 -1
- package/dist/components/content/reader-settings.js.map +1 -1
- package/dist/components/content/search.d.ts +11 -4
- package/dist/components/content/search.js +22 -11
- package/dist/components/content/search.js.map +1 -1
- package/dist/components/content/summary-tile.d.ts +14 -0
- package/dist/components/content/summary-tile.js +63 -0
- package/dist/components/content/summary-tile.js.map +1 -0
- package/dist/components/groups/banner.d.ts +6 -2
- package/dist/components/groups/banner.js +79 -61
- package/dist/components/groups/banner.js.map +1 -1
- package/dist/components/groups/directory.d.ts +12 -2
- package/dist/components/groups/directory.js +168 -105
- package/dist/components/groups/directory.js.map +1 -1
- package/dist/components/groups/editor.js +10 -10
- package/dist/components/groups/editor.js.map +1 -1
- package/dist/components/groups/entity-chips.d.ts +61 -0
- package/dist/components/groups/entity-chips.js +382 -0
- package/dist/components/groups/entity-chips.js.map +1 -0
- package/dist/components/groups/group-members.d.ts +29 -0
- package/dist/components/groups/group-members.js +474 -0
- package/dist/components/groups/group-members.js.map +1 -0
- package/dist/components/groups/header.js +53 -45
- package/dist/components/groups/header.js.map +1 -1
- package/dist/components/groups/index.d.ts +3 -1
- package/dist/components/groups/memberships.d.ts +3 -36
- package/dist/components/groups/memberships.js +105 -480
- package/dist/components/groups/memberships.js.map +1 -1
- package/dist/components/groups/scoped.d.ts +23 -0
- package/dist/components/groups/scoped.js +92 -0
- package/dist/components/groups/scoped.js.map +1 -0
- package/dist/components/media-folder-editor.d.ts +5 -0
- package/dist/components/media-folder-editor.js +419 -231
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/sortable-list.d.ts +5 -1
- package/dist/components/sortable-list.js +97 -81
- package/dist/components/sortable-list.js.map +1 -1
- package/dist/components/video-mini-player.d.ts +4 -1
- package/dist/components/video-mini-player.js +77 -71
- package/dist/components/video-mini-player.js.map +1 -1
- package/dist/components/video-player.d.ts +4 -1
- package/dist/components/video-player.js +280 -276
- package/dist/components/video-player.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +41 -34
- package/dist/locales/admin/de.js +80 -47
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +74 -41
- package/dist/locales/admin/en.js +80 -47
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +80 -47
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +80 -47
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +80 -47
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +80 -47
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/de.js +1 -0
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.js +25 -2
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +24 -1
- package/dist/locales/discovery/en.js +25 -2
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +25 -2
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +25 -2
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +25 -2
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +25 -2
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +119 -44
- package/dist/locales/en.js +1 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/groups/de.js +22 -4
- package/dist/locales/groups/de.js.map +1 -1
- package/dist/locales/groups/en.d.ts +20 -2
- package/dist/locales/groups/en.js +22 -4
- package/dist/locales/groups/en.js.map +1 -1
- package/dist/locales/groups/es.js +22 -4
- package/dist/locales/groups/es.js.map +1 -1
- package/dist/locales/groups/ja.js +22 -4
- package/dist/locales/groups/ja.js.map +1 -1
- package/dist/locales/groups/ko.js +22 -4
- package/dist/locales/groups/ko.js.map +1 -1
- package/dist/locales/groups/zh.js +22 -4
- package/dist/locales/groups/zh.js.map +1 -1
- package/dist/locales/ja.js +1 -0
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +1 -0
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +1 -0
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/discovery.d.ts +36 -17
- package/dist/react/discovery.js +305 -221
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/groups.d.ts +19 -3
- package/dist/react/groups.js +49 -22
- package/dist/react/groups.js.map +1 -1
- package/dist/react/index.d.ts +7 -4
- package/dist/react/page-notices.d.ts +13 -0
- package/dist/react/page-notices.js +38 -0
- package/dist/react/page-notices.js.map +1 -0
- package/dist/react/post-reads.d.ts +23 -0
- package/dist/react/post-reads.js +90 -0
- package/dist/react/post-reads.js.map +1 -0
- package/dist/react/remembered-sort.d.ts +5 -0
- package/dist/react/remembered-sort.js +29 -0
- package/dist/react/remembered-sort.js.map +1 -0
- package/dist/react/resources.d.ts +3 -1
- package/dist/react/resources.js +6 -2
- package/dist/react/resources.js.map +1 -1
- package/dist/react/stats.d.ts +3 -7
- package/dist/react/stats.js +2 -6
- package/dist/react/stats.js.map +1 -1
- package/dist/react/use-engagement.d.ts +28 -0
- package/dist/react/use-engagement.js +226 -0
- package/dist/react/use-engagement.js.map +1 -0
- package/dist/react/use-resource.d.ts +4 -3
- package/dist/react/use-resource.js +2 -1
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +29 -26
- package/dist/styles.css +1 -1
- package/dist/ui/dialog.d.ts +2 -1
- package/dist/ui/dialog.js +23 -20
- package/dist/ui/dialog.js.map +1 -1
- package/dist/urls/index.d.ts +1 -1
- package/dist/urls.js +2 -2
- package/dist/urls.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/admin/page-review.d.ts +0 -13
- package/dist/components/admin/page-review.js +0 -225
- package/dist/components/admin/page-review.js.map +0 -1
- package/dist/react/page-review.d.ts +0 -14
- package/dist/react/page-review.js +0 -37
- package/dist/react/page-review.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-notices.js","names":[],"sources":["../../src/react/page-notices.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { PageNotices } from \"../client/generated/wire.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\nexport interface UsePageNotices {\n notices: PageNotices[\"notices\"];\n loading: boolean;\n reload: () => void;\n dismiss: (notice: string) => Promise<void>;\n restore: (notice: string) => Promise<void>;\n}\n\n/** A version's quiet notes about automatic page changes, for its editors. */\nexport function usePageNotices(content: string | null | undefined, version: string | null | undefined, o: { client?: ContentKitClient } = {}): UsePageNotices {\n const { client, store, scope } = useContentScope(o.client);\n const key = content && version ? keyOf(\"page-notices\", scope, content, version) : null;\n const r = useResource<PageNotices>(store, key, { type: \"page-notices\", id: version ?? undefined, scope: content ?? undefined }, (signal) => client.versions.pageNotices(content!, version!, signal));\n const current = r.data;\n const dismiss = useCallback(async (notice: string) => {\n if (!content || !version || !key) return;\n await client.versions.dismissPageNotice(content, version, notice);\n store.set(key, { notices: (current?.notices ?? []).filter((n) => n.id !== notice) });\n }, [client, content, version, key, store, current]);\n const restore = useCallback(async (notice: string) => {\n if (!content || !version || !key) return;\n store.set(key, await client.versions.restorePageNotice(content, version, notice));\n }, [client, content, version, key, store]);\n return { notices: current?.notices ?? [], loading: !!key && (r.loading || !r.loaded), reload: r.reload, dismiss, restore };\n}\n"],"mappings":";;;AAcA,SAAgB,EAAe,GAAoC,GAAoC,IAAmC,CAAC,GAAmB;CAC5J,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAM,KAAW,IAAU,EAAM,gBAAgB,GAAO,GAAS,CAAO,IAAI,MAC5E,IAAI,EAAyB,GAAO,GAAK;EAAE,MAAM;EAAgB,IAAI,KAAW,KAAA;EAAW,OAAO,KAAW,KAAA;CAAU,IAAI,MAAW,EAAO,SAAS,YAAY,GAAU,GAAU,CAAM,CAAC,GAC7L,IAAU,EAAE,MACZ,IAAU,EAAY,OAAO,MAAmB;EAChD,AAAC,KAAY,KAAY,MAC7B,MAAM,EAAO,SAAS,kBAAkB,GAAS,GAAS,CAAM,GAChE,EAAM,IAAI,GAAK,EAAE,UAAU,GAAS,WAAW,CAAC,EAAA,CAAG,QAAQ,MAAM,EAAE,OAAO,CAAM,EAAE,CAAC;CACrF,GAAG;EAAC;EAAQ;EAAS;EAAS;EAAK;EAAO;CAAO,CAAC,GAC5C,IAAU,EAAY,OAAO,MAAmB;EAChD,AAAC,KAAY,KAAY,KAC7B,EAAM,IAAI,GAAK,MAAM,EAAO,SAAS,kBAAkB,GAAS,GAAS,CAAM,CAAC;CAClF,GAAG;EAAC;EAAQ;EAAS;EAAS;EAAK;CAAK,CAAC;CACzC,OAAO;EAAE,SAAS,GAAS,WAAW,CAAC;EAAG,SAAS,CAAC,CAAC,MAAQ,EAAE,WAAW,CAAC,EAAE;EAAS,QAAQ,EAAE;EAAQ;EAAS;CAAQ;AAC3H"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ContentKitClient } from '../client/client.js';
|
|
2
|
+
export interface PostReads {
|
|
3
|
+
/** Whether a post published at publishedAt is unread. Signed in, seen is the account's measured read. */
|
|
4
|
+
unread: (id: string, publishedAt?: string, seen?: boolean) => boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Unread posts. Signed in: not measured as read and published within the last
|
|
8
|
+
* windowDays. A guest: published since this browser started following the
|
|
9
|
+
* list and not read here.
|
|
10
|
+
*/
|
|
11
|
+
export declare function usePostReads(windowDays?: number): PostReads;
|
|
12
|
+
export interface PostReadingOptions {
|
|
13
|
+
client?: ContentKitClient;
|
|
14
|
+
kind?: string;
|
|
15
|
+
/** Visible, focused attention before a guest's browser counts the post read; default 3 seconds. */
|
|
16
|
+
guestReadAfter?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Attach to a text post's body. Its visible, focused attention is measured
|
|
20
|
+
* (never a raw open), so the account's history marks it read; a guest's
|
|
21
|
+
* browser marks it read after guestReadAfter of attention.
|
|
22
|
+
*/
|
|
23
|
+
export declare function usePostReading(contentId: string | null | undefined, versionId?: string, options?: PostReadingOptions): (element: HTMLElement | null) => void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ContentKitContext as e } from "./context.js";
|
|
2
|
+
import { useContentScope as t } from "./use-resource.js";
|
|
3
|
+
import { useReadingEngagement as n } from "./use-engagement.js";
|
|
4
|
+
import { useCallback as r, useContext as i, useEffect as a, useRef as o, useSyncExternalStore as s } from "react";
|
|
5
|
+
//#region src/react/post-reads.ts
|
|
6
|
+
var c = "ckui:post-reads:", l = 500, u = /* @__PURE__ */ new Set(), d = /* @__PURE__ */ new Map();
|
|
7
|
+
function f() {
|
|
8
|
+
try {
|
|
9
|
+
return typeof window > "u" ? null : window.localStorage;
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function p(e) {
|
|
15
|
+
let t = d.get(e);
|
|
16
|
+
if (t) return t;
|
|
17
|
+
let n = {
|
|
18
|
+
since: Date.now(),
|
|
19
|
+
read: []
|
|
20
|
+
};
|
|
21
|
+
try {
|
|
22
|
+
let t = f()?.getItem(c + e), r = t ? JSON.parse(t) : null;
|
|
23
|
+
r && typeof r.since == "number" && Array.isArray(r.read) && (n = {
|
|
24
|
+
since: r.since,
|
|
25
|
+
read: r.read.filter((e) => typeof e == "string")
|
|
26
|
+
});
|
|
27
|
+
} catch {}
|
|
28
|
+
return d.set(e, n), n;
|
|
29
|
+
}
|
|
30
|
+
function m(e, t) {
|
|
31
|
+
d.set(e, t);
|
|
32
|
+
try {
|
|
33
|
+
f()?.setItem(c + e, JSON.stringify(t));
|
|
34
|
+
} catch {}
|
|
35
|
+
for (let e of u) e();
|
|
36
|
+
}
|
|
37
|
+
function h(e) {
|
|
38
|
+
u.add(e);
|
|
39
|
+
let t = (t) => {
|
|
40
|
+
t.key?.startsWith(c) && (d.delete(t.key.slice(16)), e());
|
|
41
|
+
};
|
|
42
|
+
return window.addEventListener("storage", t), () => {
|
|
43
|
+
u.delete(e), window.removeEventListener("storage", t);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function g(n = 30) {
|
|
47
|
+
let { scope: o } = t(), l = i(e)?.viewer != null, u = s(h, () => p(o), () => p(o));
|
|
48
|
+
return a(() => {
|
|
49
|
+
if (!l) try {
|
|
50
|
+
f()?.getItem(c + o) || m(o, p(o));
|
|
51
|
+
} catch {}
|
|
52
|
+
}, [o, l]), { unread: r((e, t, r) => t ? l ? !r && Date.parse(t) > Date.now() - n * 864e5 : Date.parse(t) > u.since && !u.read.includes(e) : !1, [
|
|
53
|
+
u,
|
|
54
|
+
l,
|
|
55
|
+
n
|
|
56
|
+
]) };
|
|
57
|
+
}
|
|
58
|
+
function _(s, c, u = {}) {
|
|
59
|
+
let { scope: d } = t(), f = i(e)?.viewer === null, h = n(s, c, {
|
|
60
|
+
client: u.client,
|
|
61
|
+
kind: u.kind
|
|
62
|
+
}), g = o(null), _ = u.guestReadAfter ?? 3e3;
|
|
63
|
+
a(() => {
|
|
64
|
+
if (!f || !s || p(d).read.includes(s)) return;
|
|
65
|
+
let e = 0, t = setInterval(() => {
|
|
66
|
+
let n = g.current?.getBoundingClientRect();
|
|
67
|
+
if (!(n && n.height > 0 && n.bottom > 0 && n.top < window.innerHeight) || document.visibilityState === "hidden" || !document.hasFocus() || (e += 500) < _) return;
|
|
68
|
+
clearInterval(t);
|
|
69
|
+
let r = p(d);
|
|
70
|
+
r.read.includes(s) || m(d, {
|
|
71
|
+
since: r.since,
|
|
72
|
+
read: [s, ...r.read].slice(0, l)
|
|
73
|
+
});
|
|
74
|
+
}, 500);
|
|
75
|
+
return () => clearInterval(t);
|
|
76
|
+
}, [
|
|
77
|
+
f,
|
|
78
|
+
s,
|
|
79
|
+
d,
|
|
80
|
+
_
|
|
81
|
+
]);
|
|
82
|
+
let { text: v } = h;
|
|
83
|
+
return r((e) => {
|
|
84
|
+
g.current = e, v(e);
|
|
85
|
+
}, [v]);
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { _ as usePostReading, g as usePostReads };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=post-reads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-reads.js","names":[],"sources":["../../src/react/post-reads.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitContext } from \"./context.js\";\nimport { useReadingEngagement } from \"./use-engagement.js\";\nimport { useContentScope } from \"./use-resource.js\";\n\n// A guest's posts read in this browser, and since when it has followed the list.\n// Signed-in readers use their measured server history instead.\ninterface Reads {\n since: number;\n read: readonly string[];\n}\n\nconst PREFIX = \"ckui:post-reads:\";\nconst MAX = 500;\nconst listeners = new Set<() => void>();\nconst snapshots = new Map<string, Reads>();\n\nfunction storage(): Storage | null {\n try {\n return typeof window === \"undefined\" ? null : window.localStorage;\n } catch {\n return null;\n }\n}\n\nfunction load(scope: string): Reads {\n const cached = snapshots.get(scope);\n if (cached) return cached;\n let reads: Reads = { since: Date.now(), read: [] };\n try {\n const raw = storage()?.getItem(PREFIX + scope);\n const parsed = raw ? (JSON.parse(raw) as Partial<Reads>) : null;\n if (parsed && typeof parsed.since === \"number\" && Array.isArray(parsed.read)) reads = { since: parsed.since, read: parsed.read.filter((id) => typeof id === \"string\") };\n } catch {\n // unreadable storage: everything older than now counts as read\n }\n snapshots.set(scope, reads);\n return reads;\n}\n\nfunction save(scope: string, reads: Reads) {\n snapshots.set(scope, reads);\n try {\n storage()?.setItem(PREFIX + scope, JSON.stringify(reads));\n } catch {\n // private mode or full storage: keep this tab's copy\n }\n for (const l of listeners) l();\n}\n\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n const sync = (e: StorageEvent) => {\n if (!e.key?.startsWith(PREFIX)) return;\n snapshots.delete(e.key.slice(PREFIX.length));\n listener();\n };\n window.addEventListener(\"storage\", sync);\n return () => {\n listeners.delete(listener);\n window.removeEventListener(\"storage\", sync);\n };\n}\n\nexport interface PostReads {\n /** Whether a post published at publishedAt is unread. Signed in, seen is the account's measured read. */\n unread: (id: string, publishedAt?: string, seen?: boolean) => boolean;\n}\n\n/**\n * Unread posts. Signed in: not measured as read and published within the last\n * windowDays. A guest: published since this browser started following the\n * list and not read here.\n */\nexport function usePostReads(windowDays = 30): PostReads {\n const { scope } = useContentScope();\n const member = useContext(ContentKitContext)?.viewer != null;\n const reads = useSyncExternalStore(subscribe, () => load(scope), () => load(scope));\n useEffect(() => {\n if (member) return;\n try {\n if (!storage()?.getItem(PREFIX + scope)) save(scope, load(scope));\n } catch {\n // storage unavailable\n }\n }, [scope, member]);\n const unread = useCallback(\n (id: string, publishedAt?: string, seen?: boolean) => {\n if (!publishedAt) return false;\n if (member) return !seen && Date.parse(publishedAt) > Date.now() - windowDays * 86_400_000;\n return Date.parse(publishedAt) > reads.since && !reads.read.includes(id);\n },\n [reads, member, windowDays],\n );\n return { unread };\n}\n\nexport interface PostReadingOptions {\n client?: ContentKitClient;\n kind?: string;\n /** Visible, focused attention before a guest's browser counts the post read; default 3 seconds. */\n guestReadAfter?: number;\n}\n\n/**\n * Attach to a text post's body. Its visible, focused attention is measured\n * (never a raw open), so the account's history marks it read; a guest's\n * browser marks it read after guestReadAfter of attention.\n */\nexport function usePostReading(contentId: string | null | undefined, versionId?: string, options: PostReadingOptions = {}): (element: HTMLElement | null) => void {\n const { scope } = useContentScope();\n const guest = useContext(ContentKitContext)?.viewer === null;\n const engagement = useReadingEngagement(contentId, versionId, { client: options.client, kind: options.kind });\n const body = useRef<HTMLElement | null>(null);\n const after = options.guestReadAfter ?? 3000;\n useEffect(() => {\n if (!guest || !contentId || load(scope).read.includes(contentId)) return;\n let attended = 0;\n const tick = 500;\n const timer = setInterval(() => {\n const box = body.current?.getBoundingClientRect();\n const shown = !!box && box.height > 0 && box.bottom > 0 && box.top < window.innerHeight;\n if (!shown || document.visibilityState === \"hidden\" || !document.hasFocus()) return;\n if ((attended += tick) < after) return;\n clearInterval(timer);\n const reads = load(scope);\n if (!reads.read.includes(contentId)) save(scope, { since: reads.since, read: [contentId, ...reads.read].slice(0, MAX) });\n }, tick);\n return () => clearInterval(timer);\n }, [guest, contentId, scope, after]);\n const { text } = engagement;\n return useCallback(\n (element: HTMLElement | null) => {\n body.current = element;\n text(element);\n },\n [text],\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A sort remembered per page type in this browser. A sort in the URL wins and
|
|
3
|
+
* is remembered; otherwise the last choice, else the default.
|
|
4
|
+
*/
|
|
5
|
+
export declare function useRememberedSort<S extends string>(key: string, fromURL: S | undefined | null, fallback: S, allowed?: readonly S[]): [S, (sort: S) => void];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useCallback as e, useEffect as t, useState as n } from "react";
|
|
2
|
+
//#region src/react/remembered-sort.ts
|
|
3
|
+
var r = "ckui.sort.";
|
|
4
|
+
function i(e) {
|
|
5
|
+
try {
|
|
6
|
+
return globalThis.localStorage?.getItem(r + e) ?? void 0;
|
|
7
|
+
} catch {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function a(a, o, s, c) {
|
|
12
|
+
let [l, u] = n(() => i(a)), d = (e) => !!e && (!c || c.includes(e)), f = d(o) ? o : d(l) ? l : s, p = e((e) => {
|
|
13
|
+
try {
|
|
14
|
+
globalThis.localStorage?.setItem(r + a, e);
|
|
15
|
+
} catch {}
|
|
16
|
+
u(e);
|
|
17
|
+
}, [a]), m = d(o) ? o : void 0;
|
|
18
|
+
return t(() => {
|
|
19
|
+
m !== void 0 && m !== i(a) && p(m);
|
|
20
|
+
}, [
|
|
21
|
+
a,
|
|
22
|
+
m,
|
|
23
|
+
p
|
|
24
|
+
]), [f, p];
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { a as useRememberedSort };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=remembered-sort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remembered-sort.js","names":[],"sources":["../../src/react/remembered-sort.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\n\nconst PREFIX = \"ckui.sort.\";\n\nfunction stored(key: string): string | undefined {\n try {\n return globalThis.localStorage?.getItem(PREFIX + key) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * A sort remembered per page type in this browser. A sort in the URL wins and\n * is remembered; otherwise the last choice, else the default.\n */\nexport function useRememberedSort<S extends string>(key: string, fromURL: S | undefined | null, fallback: S, allowed?: readonly S[]): [S, (sort: S) => void] {\n const [remembered, setRemembered] = useState(() => stored(key));\n const ok = (s: string | undefined | null): s is S => !!s && (!allowed || allowed.includes(s as S));\n const sort = ok(fromURL) ? fromURL : ok(remembered) ? remembered : fallback;\n const remember = useCallback(\n (next: S) => {\n try {\n globalThis.localStorage?.setItem(PREFIX + key, next);\n } catch {\n // Private mode: the URL still carries it.\n }\n setRemembered(next);\n },\n [key],\n );\n const linked = ok(fromURL) ? fromURL : undefined;\n useEffect(() => {\n if (linked !== undefined && linked !== stored(key)) remember(linked);\n }, [key, linked, remember]);\n return [sort, remember];\n}\n"],"mappings":";;AAEA,IAAM,IAAS;AAEf,SAAS,EAAO,GAAiC;CAC/C,IAAI;EACF,OAAO,WAAW,cAAc,QAAQ,IAAS,CAAG,KAAK,KAAA;CAC3D,QAAQ;EACN;CACF;AACF;AAMA,SAAgB,EAAoC,GAAa,GAA+B,GAAa,GAAgD;CAC3J,IAAM,CAAC,GAAY,KAAiB,QAAe,EAAO,CAAG,CAAC,GACxD,KAAM,MAAyC,CAAC,CAAC,MAAM,CAAC,KAAW,EAAQ,SAAS,CAAM,IAC1F,IAAO,EAAG,CAAO,IAAI,IAAU,EAAG,CAAU,IAAI,IAAa,GAC7D,IAAW,GACd,MAAY;EACX,IAAI;GACF,WAAW,cAAc,QAAQ,IAAS,GAAK,CAAI;EACrD,QAAQ,CAER;EACA,EAAc,CAAI;CACpB,GACA,CAAC,CAAG,CACN,GACM,IAAS,EAAG,CAAO,IAAI,IAAU,KAAA;CAIvC,OAHA,QAAgB;EACd,AAAI,MAAW,KAAA,KAAa,MAAW,EAAO,CAAG,KAAG,EAAS,CAAM;CACrE,GAAG;EAAC;EAAK;EAAQ;CAAQ,CAAC,GACnB,CAAC,GAAM,CAAQ;AACxB"}
|
|
@@ -12,9 +12,11 @@ export interface Resource<T> {
|
|
|
12
12
|
error?: ContentKitError;
|
|
13
13
|
}
|
|
14
14
|
/** A page of a list and how to get the next one (null: no more). */
|
|
15
|
-
export interface Page<T> {
|
|
15
|
+
export interface Page<T, M = unknown> {
|
|
16
16
|
items: T[];
|
|
17
17
|
next: string | number | null;
|
|
18
|
+
/** Metadata belongs to the first page of this selection. */
|
|
19
|
+
metadata?: M;
|
|
18
20
|
}
|
|
19
21
|
export type Fetcher<T> = (signal: AbortSignal) => Promise<T>;
|
|
20
22
|
/** What a slot holds, for matching changes: its resource type and the ids it depends on. */
|
package/dist/react/resources.js
CHANGED
|
@@ -80,6 +80,7 @@ var n = (e) => [...e.bindings.values()].at(-1), r = {
|
|
|
80
80
|
let t = r.entry.data, n = new Set(t.items.map((e) => e.id));
|
|
81
81
|
this.update(r, {
|
|
82
82
|
data: {
|
|
83
|
+
...t,
|
|
83
84
|
items: [...t.items, ...e.items.filter((e) => !n.has(e.id))],
|
|
84
85
|
next: e.next
|
|
85
86
|
},
|
|
@@ -149,7 +150,7 @@ var n = (e) => [...e.bindings.values()].at(-1), r = {
|
|
|
149
150
|
switch (e.type) {
|
|
150
151
|
case "media.committed":
|
|
151
152
|
case "media.processed":
|
|
152
|
-
this.invalidate((t) => t.type === "page-
|
|
153
|
+
this.invalidate((t) => t.type === "page-notices" && t.id === e.ref.version);
|
|
153
154
|
break;
|
|
154
155
|
case "notifications.updated":
|
|
155
156
|
this.updateNotification(e.item, n);
|
|
@@ -267,6 +268,9 @@ var n = (e) => [...e.bindings.values()].at(-1), r = {
|
|
|
267
268
|
case "access.changed":
|
|
268
269
|
this.invalidate((e) => e.type === "cards" || e.type === "history" || e.type === "content-detail" || e.type === "reader-plan" || e.type.startsWith("group") || e.type === "daily-releases" || e.type === "standing");
|
|
269
270
|
break;
|
|
271
|
+
case "engagement.changed":
|
|
272
|
+
this.invalidate((t) => t.type === "history" || t.type === "cards" || t.type === "daily-releases" || t.type === "standing" || t.type.startsWith("stats") || t.type === "content-detail" && !!t.id && e.ids.includes(t.id));
|
|
273
|
+
break;
|
|
270
274
|
case "history.changed":
|
|
271
275
|
this.invalidate((e) => e.type === "history" || e.type === "cards" || e.type === "daily-releases");
|
|
272
276
|
break;
|
|
@@ -277,7 +281,7 @@ var n = (e) => [...e.bindings.values()].at(-1), r = {
|
|
|
277
281
|
this.invalidate((t) => t.type === "moderation-rules" && t.scope === e.rules);
|
|
278
282
|
break;
|
|
279
283
|
case "reader.changed":
|
|
280
|
-
this.invalidate((t) => (t.type === "reader-plan" || t.type === "page-
|
|
284
|
+
this.invalidate((t) => (t.type === "reader-plan" || t.type === "page-notices") && t.id === e.version);
|
|
281
285
|
break;
|
|
282
286
|
case "moderation.resolved": i(["held"], (t) => t.id === e.id ? null : t), e.kind === "comment" ? this.invalidate((e) => a.includes(e.type) || e.type === "standing") : this.invalidate((t) => t.type === "content" && t.id === e.id || t.type === "contents");
|
|
283
287
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport { sameTarget, type Target } from \"../client/content/types.js\";\nimport type { Card, Comment, Content, Item, Poll } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T> {\n items: T[];\n next: string | number | null;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n readScope?: string;\n target?: Target;\n language?: string;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n bindings: Map<symbol, Binding>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n /** Identifies the full read; item patches and appended pages retain it. */\n readVersion: number;\n}\n\ninterface Binding {\n fetcher: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n}\n\nconst activeBinding = (s: Slot) => [...s.bindings.values()].at(-1);\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n // Tokens must remain unique when a scope leaves and its slots are recreated.\n private version = 0;\n\n constructor(client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), bindings: new Map(), entry: IDLE, version: 0, readVersion: ++this.version }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** Registers one subscriber's key-bound fetchers; cleanup releases only it. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Binding[\"more\"]): () => void {\n const s = this.slots.get(key);\n if (!s) return () => {};\n const owner = Symbol();\n s.bindings.set(owner, { fetcher, more });\n return () => { s.bindings.delete(owner); };\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n const binding = s && activeBinding(s);\n if (!s || !binding || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n binding.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n s.readVersion = ++this.version;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const binding = s && activeBinding(s);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s || !binding?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await binding.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n s.readVersion = ++this.version;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? (s.version = ++this.version) : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to matching slots, with the read identity for scoped rollback. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T, readVersion: number) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T, s.readVersion) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange, scope: string | null): void {\n const patch = <T>(match: (tag: Tag) => boolean, fn: (data: T) => T) => {\n this.patch<T>((t) => match(t) && (t.readScope ?? \"{}\") === scope, fn);\n this.invalidate((t) => match(t) && (t.readScope ?? \"{}\") !== scope);\n };\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n // Cards in lists (pages) and rails (arrays) show a content's counts and the viewer's state.\n const cards = (id: string, fn: (card: Card) => Card) =>\n patch<Page<Card> | Card[]>(\n (t) => t.type === \"cards\" || t.type === \"history\",\n (data) => {\n const each = (list: Card[]) => (list.some((x) => x.id === id) ? list.map((x) => (x.id === id ? fn(x) : x)) : list);\n if (Array.isArray(data)) return each(data);\n const items = each(data.items);\n return items === data.items ? data : { ...data, items };\n },\n );\n switch (c.type) {\n case \"media.committed\":\n case \"media.processed\":\n this.invalidate((t) => t.type === \"page-review\" && t.id === c.ref.version);\n break;\n case \"notifications.updated\":\n this.updateNotification(c.item, scope);\n break;\n case \"notifications.changed\":\n this.invalidate((t) => t.type === \"notifications\");\n break;\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const thread = (t: Tag) => sameTarget(t.target, c.target) && (!t.language || t.language === c.comment.language);\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && thread(t));\n patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n if (!parent) this.invalidate((t) => t.type === \"standing\" && thread(t));\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n this.invalidate((t) => t.type === \"standing\");\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n this.invalidate((t) => t.type === \"standing\");\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type) || t.type === \"standing\");\n break;\n case \"reaction.changed\":\n patch((t) => t.type === \"reaction\" && sameTarget(t.target, c.target), () => c.counts);\n if (c.target.type === \"contents\") {\n cards(c.target.id, (x) => ({ ...x, counts: { ...x.counts, likes: c.counts.likes, dislikes: c.counts.dislikes } }));\n this.invalidate((t) => t.type === \"daily-releases\");\n }\n this.invalidate((t) => t.type === \"reactions\");\n break;\n case \"favorite.changed\":\n patch((t) => t.type === \"favorite\" && sameTarget(t.target, c.target), () => ({ favorited: c.favorited, count: c.count }));\n if (c.target.type === \"contents\") {\n cards(c.target.id, (x) => ({ ...x, counts: { ...x.counts, favorites: c.count }, viewer: x.viewer && { ...x.viewer, favorited: c.favorited } }));\n this.invalidate((t) => t.type === \"daily-releases\");\n }\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"content.saved\":\n patch<Content | null>((t) => t.type === \"content\" && t.id === c.content.id, () => c.content);\n // A state, kind or text change can move it between lists: refetch them.\n this.invalidate((t) => t.type === \"contents\" || t.type === \"cards\" || t.type === \"daily-releases\" || (t.type === \"content-detail\" && t.id === c.content.id));\n this.invalidate((t) => t.type === \"standing\" && t.target?.type === \"contents\" && t.target.id === c.content.id);\n break;\n case \"content.changed\":\n this.invalidate((t) => ((t.type === \"content\" || t.type === \"content-detail\") && t.id === c.id) || t.type === \"contents\" || t.type === \"cards\" || t.type === \"daily-releases\");\n this.invalidate((t) => t.type === \"standing\" && t.target?.type === \"contents\" && t.target.id === c.id);\n break;\n case \"content.deleted\":\n items<{ id: string }>([\"contents\", \"cards\", \"history\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => ((t.type === \"content\" || t.type === \"content-detail\") && t.id === c.id) || (t.type === \"contents\" && t.scope === \"deleted\") || t.type === \"daily-releases\");\n this.invalidate((t) => t.type === \"standing\" && t.target?.type === \"contents\" && t.target.id === c.id);\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"entities.changed\":\n this.invalidate((t) => t.type.startsWith(\"group\") || t.type === \"daily-releases\");\n break;\n case \"contentFilters.changed\":\n this.invalidate((t) => t.type === \"cards\" || t.type === \"history\" || t.type === \"release-dates\" || t.type === \"daily-releases\");\n break;\n case \"access.changed\":\n this.invalidate((t) => t.type === \"cards\" || t.type === \"history\" || t.type === \"content-detail\" || t.type === \"reader-plan\" || t.type.startsWith(\"group\") || t.type === \"daily-releases\" || t.type === \"standing\");\n break;\n case \"history.changed\":\n this.invalidate((t) => t.type === \"history\" || t.type === \"cards\" || t.type === \"daily-releases\");\n break;\n case \"searches.changed\":\n this.invalidate((t) => t.type === \"searches\");\n break;\n case \"rules.changed\":\n this.invalidate((t) => t.type === \"moderation-rules\" && t.scope === c.rules);\n break;\n case \"reader.changed\":\n this.invalidate((t) => (t.type === \"reader-plan\" || t.type === \"page-review\") && t.id === c.version);\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type) || t.type === \"standing\");\n else this.invalidate((t) => (t.type === \"content\" && t.id === c.id) || t.type === \"contents\");\n break;\n }\n }\n\n // Preserve loaded pages and canonical receipt revisions. Reads that started\n // before a successful mutation cannot overwrite it when they finish later.\n private updateNotification(item: Item, scope: string | null): void {\n for (const [key, slot] of this.slots) {\n if (slot.tag.type !== \"notifications\" || !slot.listeners.size) continue;\n const page = slot.entry.data as Page<Item> | undefined;\n const cached = page?.items.find((n) => n.id === item.id);\n // Mutations from separate mounted views can complete out of order.\n // In particular, an old active receipt must not evict a newer archive.\n if ((slot.tag.readScope ?? \"{}\") === scope && cached && cached.revision > item.revision) continue;\n const archived = slot.tag.scope?.startsWith(\"archived:\") ?? false;\n const matches = archived === !!item.dismissed_at;\n if ((slot.tag.readScope ?? \"{}\") !== scope || !page || slot.tag.scope?.endsWith(\":unread\") || (matches && !page.items.some((n) => n.id === item.id))) {\n this.load(key, true);\n continue;\n }\n if (!matches && !page.items.some((n) => n.id === item.id)) continue;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n slot.ctl = slot.moreCtl = undefined;\n this.update(slot, { loading: false, loadingMore: false, data: { ...page, items: page.items.flatMap((n) => n.id === item.id ? matches ? [item] : [] : [n]) } });\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport { sameTarget, type Target } from \"../client/content/types.js\";\nimport type { Card, Comment, Content, Item, Poll } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T, M = unknown> {\n items: T[];\n next: string | number | null;\n /** Metadata belongs to the first page of this selection. */\n metadata?: M;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n readScope?: string;\n target?: Target;\n language?: string;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n bindings: Map<symbol, Binding>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n /** Identifies the full read; item patches and appended pages retain it. */\n readVersion: number;\n}\n\ninterface Binding {\n fetcher: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n}\n\nconst activeBinding = (s: Slot) => [...s.bindings.values()].at(-1);\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n // Tokens must remain unique when a scope leaves and its slots are recreated.\n private version = 0;\n\n constructor(client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), bindings: new Map(), entry: IDLE, version: 0, readVersion: ++this.version }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** Registers one subscriber's key-bound fetchers; cleanup releases only it. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Binding[\"more\"]): () => void {\n const s = this.slots.get(key);\n if (!s) return () => {};\n const owner = Symbol();\n s.bindings.set(owner, { fetcher, more });\n return () => { s.bindings.delete(owner); };\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n const binding = s && activeBinding(s);\n if (!s || !binding || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n binding.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n s.readVersion = ++this.version;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const binding = s && activeBinding(s);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s || !binding?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await binding.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { ...cur, items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n s.readVersion = ++this.version;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? (s.version = ++this.version) : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to matching slots, with the read identity for scoped rollback. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T, readVersion: number) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T, s.readVersion) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange, scope: string | null): void {\n const patch = <T>(match: (tag: Tag) => boolean, fn: (data: T) => T) => {\n this.patch<T>((t) => match(t) && (t.readScope ?? \"{}\") === scope, fn);\n this.invalidate((t) => match(t) && (t.readScope ?? \"{}\") !== scope);\n };\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n // Cards in lists (pages) and rails (arrays) show a content's counts and the viewer's state.\n const cards = (id: string, fn: (card: Card) => Card) =>\n patch<Page<Card> | Card[]>(\n (t) => t.type === \"cards\" || t.type === \"history\",\n (data) => {\n const each = (list: Card[]) => (list.some((x) => x.id === id) ? list.map((x) => (x.id === id ? fn(x) : x)) : list);\n if (Array.isArray(data)) return each(data);\n const items = each(data.items);\n return items === data.items ? data : { ...data, items };\n },\n );\n switch (c.type) {\n case \"media.committed\":\n case \"media.processed\":\n this.invalidate((t) => t.type === \"page-notices\" && t.id === c.ref.version);\n break;\n case \"notifications.updated\":\n this.updateNotification(c.item, scope);\n break;\n case \"notifications.changed\":\n this.invalidate((t) => t.type === \"notifications\");\n break;\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const thread = (t: Tag) => sameTarget(t.target, c.target) && (!t.language || t.language === c.comment.language);\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && thread(t));\n patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n if (!parent) this.invalidate((t) => t.type === \"standing\" && thread(t));\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n this.invalidate((t) => t.type === \"standing\");\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n this.invalidate((t) => t.type === \"standing\");\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type) || t.type === \"standing\");\n break;\n case \"reaction.changed\":\n patch((t) => t.type === \"reaction\" && sameTarget(t.target, c.target), () => c.counts);\n if (c.target.type === \"contents\") {\n cards(c.target.id, (x) => ({ ...x, counts: { ...x.counts, likes: c.counts.likes, dislikes: c.counts.dislikes } }));\n this.invalidate((t) => t.type === \"daily-releases\");\n }\n this.invalidate((t) => t.type === \"reactions\");\n break;\n case \"favorite.changed\":\n patch((t) => t.type === \"favorite\" && sameTarget(t.target, c.target), () => ({ favorited: c.favorited, count: c.count }));\n if (c.target.type === \"contents\") {\n cards(c.target.id, (x) => ({ ...x, counts: { ...x.counts, favorites: c.count }, viewer: x.viewer && { ...x.viewer, favorited: c.favorited } }));\n this.invalidate((t) => t.type === \"daily-releases\");\n }\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"content.saved\":\n patch<Content | null>((t) => t.type === \"content\" && t.id === c.content.id, () => c.content);\n // A state, kind or text change can move it between lists: refetch them.\n this.invalidate((t) => t.type === \"contents\" || t.type === \"cards\" || t.type === \"daily-releases\" || (t.type === \"content-detail\" && t.id === c.content.id));\n this.invalidate((t) => t.type === \"standing\" && t.target?.type === \"contents\" && t.target.id === c.content.id);\n break;\n case \"content.changed\":\n this.invalidate((t) => ((t.type === \"content\" || t.type === \"content-detail\") && t.id === c.id) || t.type === \"contents\" || t.type === \"cards\" || t.type === \"daily-releases\");\n this.invalidate((t) => t.type === \"standing\" && t.target?.type === \"contents\" && t.target.id === c.id);\n break;\n case \"content.deleted\":\n items<{ id: string }>([\"contents\", \"cards\", \"history\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => ((t.type === \"content\" || t.type === \"content-detail\") && t.id === c.id) || (t.type === \"contents\" && t.scope === \"deleted\") || t.type === \"daily-releases\");\n this.invalidate((t) => t.type === \"standing\" && t.target?.type === \"contents\" && t.target.id === c.id);\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"entities.changed\":\n this.invalidate((t) => t.type.startsWith(\"group\") || t.type === \"daily-releases\");\n break;\n case \"contentFilters.changed\":\n this.invalidate((t) => t.type === \"cards\" || t.type === \"history\" || t.type === \"release-dates\" || t.type === \"daily-releases\");\n break;\n case \"access.changed\":\n this.invalidate((t) => t.type === \"cards\" || t.type === \"history\" || t.type === \"content-detail\" || t.type === \"reader-plan\" || t.type.startsWith(\"group\") || t.type === \"daily-releases\" || t.type === \"standing\");\n break;\n case \"engagement.changed\":\n this.invalidate((t) => t.type === \"history\" || t.type === \"cards\" || t.type === \"daily-releases\" || t.type === \"standing\" || t.type.startsWith(\"stats\") || (t.type === \"content-detail\" && !!t.id && c.ids.includes(t.id)));\n break;\n case \"history.changed\":\n this.invalidate((t) => t.type === \"history\" || t.type === \"cards\" || t.type === \"daily-releases\");\n break;\n case \"searches.changed\":\n this.invalidate((t) => t.type === \"searches\");\n break;\n case \"rules.changed\":\n this.invalidate((t) => t.type === \"moderation-rules\" && t.scope === c.rules);\n break;\n case \"reader.changed\":\n this.invalidate((t) => (t.type === \"reader-plan\" || t.type === \"page-notices\") && t.id === c.version);\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type) || t.type === \"standing\");\n else this.invalidate((t) => (t.type === \"content\" && t.id === c.id) || t.type === \"contents\");\n break;\n }\n }\n\n // Preserve loaded pages and canonical receipt revisions. Reads that started\n // before a successful mutation cannot overwrite it when they finish later.\n private updateNotification(item: Item, scope: string | null): void {\n for (const [key, slot] of this.slots) {\n if (slot.tag.type !== \"notifications\" || !slot.listeners.size) continue;\n const page = slot.entry.data as Page<Item> | undefined;\n const cached = page?.items.find((n) => n.id === item.id);\n // Mutations from separate mounted views can complete out of order.\n // In particular, an old active receipt must not evict a newer archive.\n if ((slot.tag.readScope ?? \"{}\") === scope && cached && cached.revision > item.revision) continue;\n const archived = slot.tag.scope?.startsWith(\"archived:\") ?? false;\n const matches = archived === !!item.dismissed_at;\n if ((slot.tag.readScope ?? \"{}\") !== scope || !page || slot.tag.scope?.endsWith(\":unread\") || (matches && !page.items.some((n) => n.id === item.id))) {\n this.load(key, true);\n continue;\n }\n if (!matches && !page.items.some((n) => n.id === item.id)) continue;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n slot.ctl = slot.moreCtl = undefined;\n this.update(slot, { loading: false, loadingMore: false, data: { ...page, items: page.items.flatMap((n) => n.id === item.id ? matches ? [item] : [] : [n]) } });\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
package/dist/react/stats.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ContentKitClient } from '../client/client.js';
|
|
2
2
|
import { ContentKitError } from '../client/errors.js';
|
|
3
|
-
import { Activity, MyStats
|
|
4
|
-
import { ActivityQuery
|
|
3
|
+
import { Activity, MyStats } from '../client/generated/wire.js';
|
|
4
|
+
import { ActivityQuery } from '../client/stats.js';
|
|
5
5
|
export interface UseStats<T> {
|
|
6
6
|
data: T | null;
|
|
7
7
|
loading: boolean;
|
|
@@ -10,14 +10,10 @@ export interface UseStats<T> {
|
|
|
10
10
|
unavailable: boolean;
|
|
11
11
|
reload: () => void;
|
|
12
12
|
}
|
|
13
|
-
/** Staff:
|
|
13
|
+
/** Staff: distinct people per UTC day (anonymous, free, premium) who read or played; distinct within each day, not across the period. Null reads nothing. */
|
|
14
14
|
export declare function useActivity(q: ActivityQuery | null, o?: {
|
|
15
15
|
client?: ContentKitClient;
|
|
16
16
|
}): UseStats<Activity>;
|
|
17
|
-
/** Staff: a kind's content ranked by views or comments over a period; null reads nothing. */
|
|
18
|
-
export declare function useTopContents(q: TopQuery | null, o?: {
|
|
19
|
-
client?: ContentKitClient;
|
|
20
|
-
}): UseStats<TopContents>;
|
|
21
17
|
/** The signed-in reader's statistics; nothing signed out. */
|
|
22
18
|
export declare function useMyStats(o?: {
|
|
23
19
|
client?: ContentKitClient;
|
package/dist/react/stats.js
CHANGED
|
@@ -14,15 +14,11 @@ function i(n, i = {}) {
|
|
|
14
14
|
let { scope: a } = t(i.client);
|
|
15
15
|
return r(n ? e("stats", a, "activity", n.kind, n.days) : null, (e, t) => e.stats.activity(n, t), i);
|
|
16
16
|
}
|
|
17
|
-
function a(n
|
|
18
|
-
let { scope: a } = t(i.client);
|
|
19
|
-
return r(n ? e("stats", a, "top", n) : null, (e, t) => e.stats.top(n, t), i);
|
|
20
|
-
}
|
|
21
|
-
function o(n = {}) {
|
|
17
|
+
function a(n = {}) {
|
|
22
18
|
let { scope: i, viewer: a } = t(n.client);
|
|
23
19
|
return r(a === null ? null : e("stats", i, "me"), (e, t) => e.stats.mine(t), n);
|
|
24
20
|
}
|
|
25
21
|
//#endregion
|
|
26
|
-
export { i as useActivity,
|
|
22
|
+
export { i as useActivity, a as useMyStats };
|
|
27
23
|
|
|
28
24
|
//# sourceMappingURL=stats.js.map
|
package/dist/react/stats.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stats.js","names":[],"sources":["../../src/react/stats.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Activity, MyStats
|
|
1
|
+
{"version":3,"file":"stats.js","names":[],"sources":["../../src/react/stats.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Activity, MyStats } from \"../client/generated/wire.js\";\nimport type { ActivityQuery } from \"../client/stats.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\nexport interface UseStats<T> {\n data: T | null;\n loading: boolean;\n error?: ContentKitError;\n /** The site runs without the signal plane (not_configured). */\n unavailable: boolean;\n reload: () => void;\n}\n\nfunction useStats<T>(key: string | null, fetch: (c: ContentKitClient, signal: AbortSignal) => Promise<T>, o: { client?: ContentKitClient }): UseStats<T> {\n const { client, store } = useContentScope(o.client);\n const r = useResource<T>(store, key, { type: \"stats\" }, (signal) => fetch(client, signal));\n const unavailable = r.error?.code === \"not_configured\";\n return { data: r.data ?? null, loading: !!key && (r.loading || !r.loaded), error: unavailable ? undefined : r.error, unavailable, reload: r.reload };\n}\n\n/** Staff: distinct people per UTC day (anonymous, free, premium) who read or played; distinct within each day, not across the period. Null reads nothing. */\nexport function useActivity(q: ActivityQuery | null, o: { client?: ContentKitClient } = {}): UseStats<Activity> {\n const { scope } = useContentScope(o.client);\n return useStats(q ? keyOf(\"stats\", scope, \"activity\", q.kind, q.days) : null, (c, s) => c.stats.activity(q!, s), o);\n}\n\n/** The signed-in reader's statistics; nothing signed out. */\nexport function useMyStats(o: { client?: ContentKitClient } = {}): UseStats<MyStats> {\n const { scope, viewer } = useContentScope(o.client);\n return useStats(viewer === null ? null : keyOf(\"stats\", scope, \"me\"), (c, s) => c.stats.mine(s), o);\n}\n"],"mappings":";;AAeA,SAAS,EAAY,GAAoB,GAAiE,GAA+C;CACvJ,IAAM,EAAE,WAAQ,aAAU,EAAgB,EAAE,MAAM,GAC5C,IAAI,EAAe,GAAO,GAAK,EAAE,MAAM,QAAQ,IAAI,MAAW,EAAM,GAAQ,CAAM,CAAC,GACnF,IAAc,EAAE,OAAO,SAAS;CACtC,OAAO;EAAE,MAAM,EAAE,QAAQ;EAAM,SAAS,CAAC,CAAC,MAAQ,EAAE,WAAW,CAAC,EAAE;EAAS,OAAO,IAAc,KAAA,IAAY,EAAE;EAAO;EAAa,QAAQ,EAAE;CAAO;AACrJ;AAGA,SAAgB,EAAY,GAAyB,IAAmC,CAAC,GAAuB;CAC9G,IAAM,EAAE,aAAU,EAAgB,EAAE,MAAM;CAC1C,OAAO,EAAS,IAAI,EAAM,SAAS,GAAO,YAAY,EAAE,MAAM,EAAE,IAAI,IAAI,OAAO,GAAG,MAAM,EAAE,MAAM,SAAS,GAAI,CAAC,GAAG,CAAC;AACpH;AAGA,SAAgB,EAAW,IAAmC,CAAC,GAAsB;CACnF,IAAM,EAAE,UAAO,cAAW,EAAgB,EAAE,MAAM;CAClD,OAAO,EAAS,MAAW,OAAO,OAAO,EAAM,SAAS,GAAO,IAAI,IAAI,GAAG,MAAM,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC;AACpG"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ContentKitClient } from '../client/client.js';
|
|
2
|
+
export interface ReadingEngagementOptions {
|
|
3
|
+
client?: ContentKitClient;
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
/** Local guest history grouping only; the server infers kind from content. */
|
|
6
|
+
kind?: string;
|
|
7
|
+
/** Genuine input renews attention for this long; default 60 seconds. */
|
|
8
|
+
idleAfter?: number;
|
|
9
|
+
/** Snapshot interval, at most 120 seconds. Page changes never send requests. */
|
|
10
|
+
checkpointInterval?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ReadingEngagement {
|
|
13
|
+
/** Attach to the actual loaded image, not its placeholder or thumbnail. */
|
|
14
|
+
page: (index: number, resume?: string) => (element: HTMLImageElement | null) => void;
|
|
15
|
+
/** Attach to the playing video. Seeks do not count as watched intervals. */
|
|
16
|
+
video: (element: HTMLVideoElement | null) => void;
|
|
17
|
+
/** Attach to a text post's body: one unit covered by its visible share. */
|
|
18
|
+
text: (element: HTMLElement | null) => void;
|
|
19
|
+
/** Existing filename/anchor selector to restore, independent of page index. */
|
|
20
|
+
setResume: (resume: string | undefined) => void;
|
|
21
|
+
flush: () => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Keep this hook in the content page, above horizontal/vertical renderer switches.
|
|
25
|
+
* It records loaded, visible attention, with one visit and bounded access receipts;
|
|
26
|
+
* preloads and route opens alone produce no engagement or network writes.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useReadingEngagement(contentId: string | null | undefined, versionId?: string, options?: ReadingEngagementOptions): ReadingEngagement;
|