@openrails/contentkit-ui 0.104.0-rc.1 → 0.104.0-rc.3
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 +6 -6
- package/dist/client/beacons.js +4 -1
- package/dist/client/beacons.js.map +1 -1
- package/dist/client/contents.d.ts +9 -1
- package/dist/client/contents.js +73 -55
- package/dist/client/contents.js.map +1 -1
- package/dist/client/discovery.d.ts +6 -2
- package/dist/client/discovery.js +1 -1
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +4 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +43 -30
- package/dist/client/generated/routes.js +70 -63
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +36 -74
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- 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/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 +739 -380
- 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/reader-content-editor.js +537 -500
- 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 +300 -202
- 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 +14 -12
- 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 +1 -1
- package/dist/components/content/favorites.js +36 -22
- package/dist/components/content/favorites.js.map +1 -1
- package/dist/components/content/grid.d.ts +11 -4
- package/dist/components/content/grid.js +52 -43
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/history.js +83 -63
- package/dist/components/content/history.js.map +1 -1
- package/dist/components/content/index.d.ts +5 -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/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 +418 -230
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/social/poll.js +57 -48
- package/dist/components/social/poll.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/index.d.ts +1 -1
- package/dist/index.js +41 -37
- package/dist/locales/admin/de.js +78 -45
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +74 -41
- package/dist/locales/admin/en.js +78 -45
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +78 -45
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +78 -45
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +78 -45
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +78 -45
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/de.js +2 -0
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.js +25 -1
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +24 -0
- package/dist/locales/discovery/en.js +25 -1
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +25 -1
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +25 -1
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +25 -1
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +25 -1
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +120 -43
- package/dist/locales/en.js +2 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +2 -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 +2 -0
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +2 -0
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +2 -0
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/contents.js +1 -1
- package/dist/react/contents.js.map +1 -1
- package/dist/react/discovery.d.ts +16 -0
- package/dist/react/discovery.js +69 -37
- 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 +6 -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.js +2 -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 +2 -0
- package/dist/react/use-engagement.js +40 -34
- package/dist/react/use-engagement.js.map +1 -1
- package/dist/react.js +29 -27
- package/dist/styles.css +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
package/dist/react/groups.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContentKitClient } from '../client/client.js';
|
|
2
2
|
import { ContentKitError } from '../client/errors.js';
|
|
3
|
-
import { ContentMemberships, ContentModel, Edge, EdgeInput, Group, GroupCard, GroupDetail, GroupInput, GroupUpdate, MemberRef, Membership, MergeReport, MyGroup, Name, Edit } from '../client/generated/wire.js';
|
|
3
|
+
import { ContentMemberships, ContentModel, Edge, EdgeInput, Group, GroupCard, GroupDetail, GroupInput, GroupUpdate, MemberRef, Membership, MergeReport, MyGroup, Name, NewEntity, Edit } from '../client/generated/wire.js';
|
|
4
4
|
import { EntityImageSlot, MyEntityQuery } from '../client/entities.js';
|
|
5
5
|
import { UseList } from './use-resource.js';
|
|
6
6
|
export interface UseKinds {
|
|
@@ -32,7 +32,23 @@ export interface GroupFilter {
|
|
|
32
32
|
sort?: GroupSort;
|
|
33
33
|
/** Default 50, at most 200. */
|
|
34
34
|
pageSize?: number;
|
|
35
|
+
/** Management: a canonical name, alias or slug containing this. */
|
|
36
|
+
query?: string;
|
|
37
|
+
/** Management: the deleted, restorable groups instead of active ones. */
|
|
38
|
+
deleted?: boolean;
|
|
35
39
|
}
|
|
40
|
+
/** One numbered page of groups and every matching group's total. */
|
|
41
|
+
export interface UseGroupsPage {
|
|
42
|
+
items: GroupCard[];
|
|
43
|
+
total?: number;
|
|
44
|
+
loading: boolean;
|
|
45
|
+
error?: ContentKitError;
|
|
46
|
+
reload: () => void;
|
|
47
|
+
}
|
|
48
|
+
/** Page `page` (from 1) of active groups with the total. */
|
|
49
|
+
export declare function useGroupsPage(f: GroupFilter, page: number, o?: {
|
|
50
|
+
client?: ContentKitClient;
|
|
51
|
+
}): UseGroupsPage;
|
|
36
52
|
/** Active groups with name, path, cover and count, a cursor page at a time. */
|
|
37
53
|
export declare function useGroups(f?: GroupFilter, o?: {
|
|
38
54
|
client?: ContentKitClient;
|
|
@@ -107,8 +123,8 @@ export interface UseContentGroups {
|
|
|
107
123
|
error?: ContentKitError;
|
|
108
124
|
saving: boolean;
|
|
109
125
|
reload: () => void;
|
|
110
|
-
/** Replaces the content's memberships, its versions' included, in these group kinds (every kind its kind joins when omitted). */
|
|
111
|
-
set: (memberships: Membership[], kinds?: string[]) => Promise<ContentMemberships>;
|
|
126
|
+
/** Replaces the content's memberships, its versions' included, in these group kinds (every kind its kind joins when omitted); create links new names, creating entities of creatable kinds. */
|
|
127
|
+
set: (memberships: Membership[], kinds?: string[], create?: NewEntity[]) => Promise<ContentMemberships>;
|
|
112
128
|
}
|
|
113
129
|
export declare function useContentGroups(contentId: string | null | undefined, o?: {
|
|
114
130
|
language?: string;
|
package/dist/react/groups.js
CHANGED
|
@@ -11,9 +11,31 @@ function u(e = {}) {
|
|
|
11
11
|
reload: o.reload
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
var d = [];
|
|
15
|
+
function f(e, r, a = {}) {
|
|
16
|
+
let { client: o, store: s, scope: c, language: l } = n(a.client), u = e.language ?? l, f = e.pageSize ?? 50, p = Math.max(1, Math.floor(r)), m = i(s, t("groups", c, "page", e.kind, e.parent, e.scope, e.slug, e.letter, u, e.contentKind, e.sort, f, p), { type: "groups" }, (t) => o.entities.list({
|
|
17
|
+
kind: e.kind,
|
|
18
|
+
parent: e.parent,
|
|
19
|
+
scope: e.scope,
|
|
20
|
+
slug: e.slug,
|
|
21
|
+
letter: e.letter,
|
|
22
|
+
language: u,
|
|
23
|
+
content_kind: e.contentKind,
|
|
24
|
+
sort: e.sort,
|
|
25
|
+
page: p,
|
|
26
|
+
limit: f
|
|
27
|
+
}, t));
|
|
28
|
+
return {
|
|
29
|
+
items: m.data?.entities ?? d,
|
|
30
|
+
total: m.data?.total,
|
|
31
|
+
loading: m.loading || !m.loaded,
|
|
32
|
+
error: m.error,
|
|
33
|
+
reload: m.reload
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function p(e = {}, i = {}) {
|
|
15
37
|
let { client: a, store: o, scope: s, language: c } = n(i.client), l = e.language ?? c, u = e.pageSize ?? 50;
|
|
16
|
-
return r(o, t("groups", s, i.management, e.kind, e.parent, e.scope, e.slug, e.letter, l, e.contentKind, e.sort, u), { type: "groups" }, async (t, n) => {
|
|
38
|
+
return r(o, t("groups", s, i.management, e.kind, e.parent, e.scope, e.slug, e.letter, l, e.contentKind, e.sort, u, e.query, e.deleted), { type: "groups" }, async (t, n) => {
|
|
17
39
|
let r = {
|
|
18
40
|
kind: e.kind,
|
|
19
41
|
parent: e.parent,
|
|
@@ -25,14 +47,18 @@ function d(e = {}, i = {}) {
|
|
|
25
47
|
sort: e.sort,
|
|
26
48
|
cursor: t,
|
|
27
49
|
limit: u
|
|
28
|
-
}, o = await (i.management ? a.entities.manage(
|
|
50
|
+
}, o = await (i.management ? a.entities.manage({
|
|
51
|
+
...r,
|
|
52
|
+
q: e.query?.trim() || void 0,
|
|
53
|
+
state: e.deleted ? "deleted" : void 0
|
|
54
|
+
}, n) : a.entities.list(r, n));
|
|
29
55
|
return {
|
|
30
56
|
items: o.entities,
|
|
31
57
|
next: o.next_cursor ?? null
|
|
32
58
|
};
|
|
33
59
|
});
|
|
34
60
|
}
|
|
35
|
-
function
|
|
61
|
+
function m(r, s = {}) {
|
|
36
62
|
let { client: u, store: d, scope: f, language: p } = n(s.client), m = s.language ?? p, [h, g] = l(null), [_, v] = l(null), [y, b] = l(null), x = _ ?? r ?? h;
|
|
37
63
|
o(() => {
|
|
38
64
|
v(null), b(null);
|
|
@@ -133,7 +159,7 @@ function f(r, s = {}) {
|
|
|
133
159
|
}
|
|
134
160
|
};
|
|
135
161
|
}
|
|
136
|
-
function
|
|
162
|
+
function h(e = {}, i = {}) {
|
|
137
163
|
let { client: a, store: o, scope: s, viewer: c, language: l } = n(i.client), { pageSize: u, ...d } = e, f = {
|
|
138
164
|
language: l,
|
|
139
165
|
...d
|
|
@@ -150,13 +176,13 @@ function p(e = {}, i = {}) {
|
|
|
150
176
|
};
|
|
151
177
|
});
|
|
152
178
|
}
|
|
153
|
-
var
|
|
179
|
+
var g = (e) => [
|
|
154
180
|
e.group_id ?? "",
|
|
155
181
|
e.content_id ?? "",
|
|
156
182
|
e.version_id ?? "",
|
|
157
183
|
e.role ?? ""
|
|
158
184
|
].join("|");
|
|
159
|
-
function
|
|
185
|
+
function _(i, o = {}) {
|
|
160
186
|
let { client: s, store: c, scope: u } = n(o.client), d = o.pageSize ?? 50, f = r(c, i ? t("group-members", u, i, d) : null, {
|
|
161
187
|
type: "group-members",
|
|
162
188
|
id: i ?? void 0
|
|
@@ -168,41 +194,42 @@ function h(i, o = {}) {
|
|
|
168
194
|
return {
|
|
169
195
|
items: n.members.map((e) => ({
|
|
170
196
|
...e,
|
|
171
|
-
id:
|
|
197
|
+
id: g(e)
|
|
172
198
|
})),
|
|
173
199
|
next: n.next_cursor ?? null
|
|
174
200
|
};
|
|
175
|
-
}), p = e(), [
|
|
201
|
+
}), p = e(), [m, h] = l(0), _ = a(async (e) => {
|
|
176
202
|
if (!i) throw Error("contentkit-ui: no group");
|
|
177
|
-
|
|
203
|
+
h((e) => e + 1);
|
|
178
204
|
try {
|
|
179
205
|
return (await p(() => e(i))).count;
|
|
180
206
|
} finally {
|
|
181
|
-
|
|
207
|
+
h((e) => e - 1);
|
|
182
208
|
}
|
|
183
209
|
}, [i, p]);
|
|
184
210
|
return {
|
|
185
211
|
...f,
|
|
186
|
-
saving:
|
|
212
|
+
saving: m > 0,
|
|
187
213
|
add: a((e) => _((t) => s.entities.addMembers(t, e)), [s, _]),
|
|
188
214
|
remove: a((e) => _((t) => s.entities.removeMembers(t, e)), [s, _]),
|
|
189
215
|
reorder: a((e) => _((t) => s.entities.reorder(t, e)), [s, _])
|
|
190
216
|
};
|
|
191
217
|
}
|
|
192
|
-
var
|
|
193
|
-
function
|
|
218
|
+
var v = [];
|
|
219
|
+
function y(r, o = {}) {
|
|
194
220
|
let { client: c, store: u, scope: d, language: f } = n(o.client), p = o.language ?? f, m = r ? t("group-memberships", d, r, p) : null, h = i(u, m, {
|
|
195
221
|
type: "group-memberships",
|
|
196
222
|
id: r ?? void 0
|
|
197
|
-
}, (e) => c.entities.ofContent(r, p, e)),
|
|
223
|
+
}, (e) => c.entities.ofContent(r, p, e)), g = e(), [_, y] = l(0), b = a(async (e, t, n) => {
|
|
198
224
|
if (!r) throw Error("contentkit-ui: no content");
|
|
199
225
|
y((e) => e + 1);
|
|
200
226
|
try {
|
|
201
|
-
let
|
|
227
|
+
let i = await g(() => c.entities.setOfContent(r, {
|
|
202
228
|
kinds: t,
|
|
203
|
-
memberships: e
|
|
229
|
+
memberships: e,
|
|
230
|
+
...n?.length ? { create: n } : {}
|
|
204
231
|
}, p));
|
|
205
|
-
return m && u.set(m,
|
|
232
|
+
return m && u.set(m, i), i;
|
|
206
233
|
} finally {
|
|
207
234
|
y((e) => e - 1);
|
|
208
235
|
}
|
|
@@ -210,21 +237,21 @@ function _(r, o = {}) {
|
|
|
210
237
|
c,
|
|
211
238
|
r,
|
|
212
239
|
p,
|
|
213
|
-
|
|
240
|
+
g,
|
|
214
241
|
u,
|
|
215
242
|
m
|
|
216
243
|
]), x = s(() => new Map((h.data?.entities ?? []).map((e) => [e.id, e])), [h.data]);
|
|
217
244
|
return {
|
|
218
|
-
memberships: s(() => h.data?.memberships ??
|
|
245
|
+
memberships: s(() => h.data?.memberships ?? v, [h.data]),
|
|
219
246
|
entities: x,
|
|
220
247
|
loading: !!m && (h.loading || !h.loaded),
|
|
221
248
|
error: h.error,
|
|
222
|
-
saving:
|
|
249
|
+
saving: _ > 0,
|
|
223
250
|
reload: h.reload,
|
|
224
251
|
set: b
|
|
225
252
|
};
|
|
226
253
|
}
|
|
227
254
|
//#endregion
|
|
228
|
-
export {
|
|
255
|
+
export { g as memberId, y as useContentGroups, m as useGroup, _ as useGroupMembers, p as useGroups, f as useGroupsPage, u as useKinds, h as useMyGroups };
|
|
229
256
|
|
|
230
257
|
//# sourceMappingURL=groups.js.map
|
package/dist/react/groups.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groups.js","names":[],"sources":["../../src/react/groups.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type {\n ContentMemberships,\n ContentModel,\n Edge,\n EdgeInput,\n Group,\n GroupCard,\n GroupDetail,\n GroupInput,\n GroupUpdate,\n MemberRef,\n Membership,\n MergeReport,\n MyGroup,\n Name,\n Edit,\n} from \"../client/generated/wire.js\";\nimport type { EntityImageSlot, MyEntityQuery } from \"../client/entities.js\";\nimport { useGuarded } from \"./context.js\";\nimport { keyOf, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface UseKinds {\n /** null until it loads (or when it fails). */\n kinds: ContentModel | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\n/** The site's content model (GET /v1/kinds): languages, tiers, kinds and entity kinds; one read per client. */\nexport function useKinds(o: { client?: ContentKitClient } = {}): UseKinds {\n const { client, store } = useContentScope(o.client);\n const r = useResource<ContentModel>(store, keyOf(\"kinds\"), { type: \"kinds\" }, (s) => client.kinds(s));\n return { kinds: r.data ?? null, loading: !r.data && (r.loading || !r.loaded), error: r.error, reload: r.reload };\n}\n\nexport type GroupSort = \"name\" | \"count\" | \"newest\";\n\nexport interface GroupFilter {\n kind?: string;\n /** Children of this group. */\n parent?: string;\n /** Groups scoped by this group (characters of a series). */\n scope?: string;\n slug?: string;\n /** Sort names starting with this letter (a-z), or with anything else (\"#\"). */\n letter?: string;\n /** Names, counts and paths in this language; default the provider's. */\n language?: string;\n /** Count contents of this kind only. */\n contentKind?: string;\n /** Default \"name\". */\n sort?: GroupSort;\n /** Default 50, at most 200. */\n pageSize?: number;\n}\n\n/** Active groups with name, path, cover and count, a cursor page at a time. */\nexport function useGroups(f: GroupFilter = {}, o: { client?: ContentKitClient; management?: boolean } = {}): UseList<GroupCard> {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = f.language ?? provided;\n const size = f.pageSize ?? 50;\n return useList(\n store,\n keyOf(\"groups\", scope, o.management, f.kind, f.parent, f.scope, f.slug, f.letter, language, f.contentKind, f.sort, size),\n { type: \"groups\" },\n async (cursor, signal) => {\n const query = { kind: f.kind, parent: f.parent, scope: f.scope, slug: f.slug, letter: f.letter, language, content_kind: f.contentKind, sort: f.sort, cursor: cursor as string | undefined, limit: size };\n const page = await (o.management ? client.entities.manage(query, signal) : client.entities.list(query, signal));\n return { items: page.entities, next: page.next_cursor ?? null };\n },\n );\n}\n\nexport interface UseGroup {\n /** The group as its page shows it; null before create and while loading. A merged group reads as its survivor. */\n group: GroupDetail | null;\n /** What this hook deleted, until restored: reads no longer answer for it. */\n deleted: Group | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n reload: () => void;\n /** Creates the group; the hook then shows it. */\n create: (input: GroupInput) => Promise<Group>;\n update: (patch: GroupUpdate) => Promise<Group>;\n remove: () => Promise<Group>;\n restore: () => Promise<Group>;\n /** Replaces (\"set\"), adds or removes names and aliases; a new canonical name demotes the old one to an alias. */\n names: (op: \"set\" | \"add\" | \"remove\", names: Name[]) => Promise<Name[]>;\n edges: (op: \"add\" | \"remove\", edges: EdgeInput[]) => Promise<Edge[]>;\n /** Merges the group into another of its kind; the hook then shows the survivor. */\n merge: (into: string) => Promise<MergeReport>;\n /** Uploads an image (cropped by edit) and selects it as the cover or avatar. */\n uploadImage: (slot: EntityImageSlot, file: Blob & { name?: string }, o?: { wait?: boolean; edit?: Edit }) => Promise<Group>;\n /** Shows the images uploaded here until their renditions exist. */\n previews: Partial<Record<EntityImageSlot, string>>;\n}\n\n/** One group: its page for anyone; create, edit, names, edges, cover, merge, delete and restore for staff (root:groups:write). null creates. */\nexport function useGroup(id: string | null | undefined, o: { language?: string; initial?: GroupDetail; client?: ContentKitClient; management?: boolean } = {}): UseGroup {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const [created, setCreated] = useState<string | null>(null);\n const [merged, setMerged] = useState<string | null>(null);\n const [deleted, setDeleted] = useState<Group | null>(null);\n const gid = merged ?? id ?? created;\n useEffect(() => {\n setMerged(null);\n setDeleted(null);\n }, [id]);\n const key = gid && !deleted ? keyOf(\"group\", scope, o.management, gid, language) : null;\n const r = useResource<GroupDetail>(store, key, { type: \"group\", id: gid ?? undefined }, (s) => o.management ? client.entities.getForManagement(gid!, language, s) : client.entities.get(gid!, language, s), { initial: o.initial });\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const run = useCallback(\n async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await guarded(fn);\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [guarded],\n );\n const need = useCallback(() => {\n if (!gid) throw new Error(\"contentkit-ui: create the group first\");\n return gid;\n }, [gid]);\n\n const [previews, setPreviews] = useState<Partial<Record<EntityImageSlot, string>>>({});\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => urls.forEach((u) => URL.revokeObjectURL(u));\n }, []);\n\n const group = r.data ?? null;\n return {\n group: deleted ? null : group,\n deleted,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: deleted ? undefined : r.error,\n saving: inflight > 0,\n reload: r.reload,\n create: useCallback(\n (input: GroupInput) =>\n run(async () => {\n const [g] = await client.entities.create([input]);\n setCreated(g!.id);\n return g!;\n }),\n [client, run],\n ),\n update: useCallback((patch: GroupUpdate) => run(() => client.entities.update(need(), patch)), [client, run, need]),\n remove: useCallback(\n () =>\n run(async () => {\n const g = await client.entities.remove(need());\n setDeleted(g);\n return g;\n }),\n [client, run, need],\n ),\n restore: useCallback(\n () =>\n run(async () => {\n const g = await client.entities.update(need(), { state: \"active\" });\n setDeleted(null);\n return g;\n }),\n [client, run, need],\n ),\n names: useCallback((op: \"set\" | \"add\" | \"remove\", names: Name[]) => run(() => client.entities.names(need(), op, names)), [client, run, need]),\n edges: useCallback((op: \"add\" | \"remove\", edges: EdgeInput[]) => run(() => client.entities.edges(need(), op, edges)), [client, run, need]),\n merge: useCallback(\n (into: string) =>\n run(async () => {\n const report = await client.entities.merge(need(), into);\n setMerged(report.into);\n return report;\n }),\n [client, run, need],\n ),\n uploadImage: useCallback(\n (slot: EntityImageSlot, file: Blob & { name?: string }, u: { wait?: boolean; edit?: Edit } = {}) =>\n run(async () => {\n const kind = group?.kind;\n if (!kind) throw new Error(\"contentkit-ui: load the group first\");\n const g = await client.entities.uploadImage({ kind, id: need() }, slot, file, u);\n // A cropped upload's renditions show the crop; until then the file shows uncropped.\n if (typeof URL.createObjectURL === \"function\" && !u.edit) {\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((p) => ({ ...p, [slot]: url }));\n }\n return g;\n }),\n [client, run, need, group?.kind],\n ),\n previews: { cover: group?.cover?.image ? previews.cover : undefined, avatar: group?.avatar?.image ? previews.avatar : undefined },\n };\n}\n\n/** Groups the signed-in person owns, edits or belongs to, a cursor page at a time; nothing signed out. */\nexport function useMyGroups(q: Omit<MyEntityQuery, \"cursor\" | \"limit\"> & { pageSize?: number } = {}, o: { client?: ContentKitClient } = {}): UseList<MyGroup> {\n const { client, store, scope, viewer, language } = useContentScope(o.client);\n const { pageSize, ...rest } = q;\n const query = { language, ...rest };\n return useList(store, viewer === null ? null : keyOf(\"groups-mine\", scope, query, pageSize), { type: \"groups-mine\" }, async (cursor, signal) => {\n const page = await client.entities.mine({ ...query, cursor: cursor as string | undefined, limit: pageSize }, signal);\n return { items: page.entities, next: page.next_cursor ?? null };\n });\n}\n\n/** A membership with a stable id: (group, content, version, role) is its identity. */\nexport type Member = Membership & { id: string };\n\nexport const memberId = (m: Membership): string => [m.group_id ?? \"\", m.content_id ?? \"\", m.version_id ?? \"\", m.role ?? \"\"].join(\"|\");\n\nexport interface UseGroupMembers extends UseList<Member> {\n saving: boolean;\n /** Adds members, or updates their role's position, window and state. */\n add: (members: Membership[]) => Promise<number>;\n remove: (members: Membership[]) => Promise<number>;\n /** Orders an ordered group: the listed members first, in list order. */\n reorder: (members: MemberRef[]) => Promise<number>;\n}\n\n/** Staff: a group's memberships in member order, with writes. */\nexport function useGroupMembers(id: string | null | undefined, o: { pageSize?: number; client?: ContentKitClient } = {}): UseGroupMembers {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 50;\n const list = useList<Member>(store, id ? keyOf(\"group-members\", scope, id, size) : null, { type: \"group-members\", id: id ?? undefined }, async (cursor, signal) => {\n const page = await client.entities.members(id!, { cursor: cursor as string | undefined, limit: size }, signal);\n return { items: page.members.map((m) => ({ ...m, id: memberId(m) })), next: page.next_cursor ?? null };\n });\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const write = useCallback(\n async (fn: (gid: string) => Promise<{ count: number }>) => {\n if (!id) throw new Error(\"contentkit-ui: no group\");\n setInflight((n) => n + 1);\n try {\n return (await guarded(() => fn(id))).count;\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [id, guarded],\n );\n return {\n ...list,\n saving: inflight > 0,\n add: useCallback((ms: Membership[]) => write((g) => client.entities.addMembers(g, ms)), [client, write]),\n remove: useCallback((ms: Membership[]) => write((g) => client.entities.removeMembers(g, ms)), [client, write]),\n reorder: useCallback((ms: MemberRef[]) => write((g) => client.entities.reorder(g, ms)), [client, write]),\n };\n}\n\nexport interface UseContentGroups {\n memberships: Membership[];\n /** The groups the memberships name. */\n entities: ReadonlyMap<string, GroupCard>;\n loading: boolean;\n error?: ContentKitError;\n saving: boolean;\n reload: () => void;\n /** Replaces the content's memberships, its versions' included, in these group kinds (every kind its kind joins when omitted). */\n set: (memberships: Membership[], kinds?: string[]) => Promise<ContentMemberships>;\n}\n\n/** Staff: one content's memberships, its versions' included, and their replacement. */\nconst NO_MEMBERSHIPS: Membership[] = [];\n\nexport function useContentGroups(contentId: string | null | undefined, o: { language?: string; client?: ContentKitClient } = {}): UseContentGroups {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const key = contentId ? keyOf(\"group-memberships\", scope, contentId, language) : null;\n const r = useResource<ContentMemberships>(store, key, { type: \"group-memberships\", id: contentId ?? undefined }, (s) => client.entities.ofContent(contentId!, language, s));\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const set = useCallback(\n async (memberships: Membership[], kinds?: string[]) => {\n if (!contentId) throw new Error(\"contentkit-ui: no content\");\n setInflight((n) => n + 1);\n try {\n const out = await guarded(() => client.entities.setOfContent(contentId, { kinds, memberships }, language));\n if (key) store.set(key, out);\n return out;\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [client, contentId, language, guarded, store, key],\n );\n const entities = useMemo(() => new Map((r.data?.entities ?? []).map((g) => [g.id, g])), [r.data]);\n // Stable while loading or failed: effects keyed on it must not see a new list every render.\n const memberships = useMemo(() => r.data?.memberships ?? NO_MEMBERSHIPS, [r.data]);\n return {\n memberships,\n entities,\n loading: !!key && (r.loading || !r.loaded),\n error: r.error,\n saving: inflight > 0,\n reload: r.reload,\n set,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"groups.js","names":[],"sources":["../../src/react/groups.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type {\n ContentMemberships,\n ContentModel,\n Edge,\n EdgeInput,\n Group,\n GroupCard,\n GroupDetail,\n GroupInput,\n GroupPage,\n GroupUpdate,\n MemberRef,\n Membership,\n MergeReport,\n MyGroup,\n Name,\n NewEntity,\n Edit,\n} from \"../client/generated/wire.js\";\nimport type { EntityImageSlot, MyEntityQuery } from \"../client/entities.js\";\nimport { useGuarded } from \"./context.js\";\nimport { keyOf, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface UseKinds {\n /** null until it loads (or when it fails). */\n kinds: ContentModel | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\n/** The site's content model (GET /v1/kinds): languages, tiers, kinds and entity kinds; one read per client. */\nexport function useKinds(o: { client?: ContentKitClient } = {}): UseKinds {\n const { client, store } = useContentScope(o.client);\n const r = useResource<ContentModel>(store, keyOf(\"kinds\"), { type: \"kinds\" }, (s) => client.kinds(s));\n return { kinds: r.data ?? null, loading: !r.data && (r.loading || !r.loaded), error: r.error, reload: r.reload };\n}\n\nexport type GroupSort = \"name\" | \"count\" | \"newest\";\n\nexport interface GroupFilter {\n kind?: string;\n /** Children of this group. */\n parent?: string;\n /** Groups scoped by this group (characters of a series). */\n scope?: string;\n slug?: string;\n /** Sort names starting with this letter (a-z), or with anything else (\"#\"). */\n letter?: string;\n /** Names, counts and paths in this language; default the provider's. */\n language?: string;\n /** Count contents of this kind only. */\n contentKind?: string;\n /** Default \"name\". */\n sort?: GroupSort;\n /** Default 50, at most 200. */\n pageSize?: number;\n /** Management: a canonical name, alias or slug containing this. */\n query?: string;\n /** Management: the deleted, restorable groups instead of active ones. */\n deleted?: boolean;\n}\n\n/** One numbered page of groups and every matching group's total. */\nexport interface UseGroupsPage {\n items: GroupCard[];\n total?: number;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\nconst NO_GROUPS: GroupCard[] = [];\n\n/** Page `page` (from 1) of active groups with the total. */\nexport function useGroupsPage(f: GroupFilter, page: number, o: { client?: ContentKitClient } = {}): UseGroupsPage {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = f.language ?? provided;\n const size = f.pageSize ?? 50;\n const n = Math.max(1, Math.floor(page));\n const r = useResource<GroupPage>(store, keyOf(\"groups\", scope, \"page\", f.kind, f.parent, f.scope, f.slug, f.letter, language, f.contentKind, f.sort, size, n), { type: \"groups\" }, (signal) =>\n client.entities.list({ kind: f.kind, parent: f.parent, scope: f.scope, slug: f.slug, letter: f.letter, language, content_kind: f.contentKind, sort: f.sort, page: n, limit: size }, signal),\n );\n return { items: r.data?.entities ?? NO_GROUPS, total: r.data?.total, loading: r.loading || !r.loaded, error: r.error, reload: r.reload };\n}\n\n/** Active groups with name, path, cover and count, a cursor page at a time. */\nexport function useGroups(f: GroupFilter = {}, o: { client?: ContentKitClient; management?: boolean } = {}): UseList<GroupCard> {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = f.language ?? provided;\n const size = f.pageSize ?? 50;\n return useList(\n store,\n keyOf(\"groups\", scope, o.management, f.kind, f.parent, f.scope, f.slug, f.letter, language, f.contentKind, f.sort, size, f.query, f.deleted),\n { type: \"groups\" },\n async (cursor, signal) => {\n const query = { kind: f.kind, parent: f.parent, scope: f.scope, slug: f.slug, letter: f.letter, language, content_kind: f.contentKind, sort: f.sort, cursor: cursor as string | undefined, limit: size };\n const page = await (o.management\n ? client.entities.manage({ ...query, q: f.query?.trim() || undefined, state: f.deleted ? \"deleted\" : undefined }, signal)\n : client.entities.list(query, signal));\n return { items: page.entities, next: page.next_cursor ?? null };\n },\n );\n}\n\nexport interface UseGroup {\n /** The group as its page shows it; null before create and while loading. A merged group reads as its survivor. */\n group: GroupDetail | null;\n /** What this hook deleted, until restored: reads no longer answer for it. */\n deleted: Group | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n reload: () => void;\n /** Creates the group; the hook then shows it. */\n create: (input: GroupInput) => Promise<Group>;\n update: (patch: GroupUpdate) => Promise<Group>;\n remove: () => Promise<Group>;\n restore: () => Promise<Group>;\n /** Replaces (\"set\"), adds or removes names and aliases; a new canonical name demotes the old one to an alias. */\n names: (op: \"set\" | \"add\" | \"remove\", names: Name[]) => Promise<Name[]>;\n edges: (op: \"add\" | \"remove\", edges: EdgeInput[]) => Promise<Edge[]>;\n /** Merges the group into another of its kind; the hook then shows the survivor. */\n merge: (into: string) => Promise<MergeReport>;\n /** Uploads an image (cropped by edit) and selects it as the cover or avatar. */\n uploadImage: (slot: EntityImageSlot, file: Blob & { name?: string }, o?: { wait?: boolean; edit?: Edit }) => Promise<Group>;\n /** Shows the images uploaded here until their renditions exist. */\n previews: Partial<Record<EntityImageSlot, string>>;\n}\n\n/** One group: its page for anyone; create, edit, names, edges, cover, merge, delete and restore for staff (root:groups:write). null creates. */\nexport function useGroup(id: string | null | undefined, o: { language?: string; initial?: GroupDetail; client?: ContentKitClient; management?: boolean } = {}): UseGroup {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const [created, setCreated] = useState<string | null>(null);\n const [merged, setMerged] = useState<string | null>(null);\n const [deleted, setDeleted] = useState<Group | null>(null);\n const gid = merged ?? id ?? created;\n useEffect(() => {\n setMerged(null);\n setDeleted(null);\n }, [id]);\n const key = gid && !deleted ? keyOf(\"group\", scope, o.management, gid, language) : null;\n const r = useResource<GroupDetail>(store, key, { type: \"group\", id: gid ?? undefined }, (s) => o.management ? client.entities.getForManagement(gid!, language, s) : client.entities.get(gid!, language, s), { initial: o.initial });\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const run = useCallback(\n async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await guarded(fn);\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [guarded],\n );\n const need = useCallback(() => {\n if (!gid) throw new Error(\"contentkit-ui: create the group first\");\n return gid;\n }, [gid]);\n\n const [previews, setPreviews] = useState<Partial<Record<EntityImageSlot, string>>>({});\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => urls.forEach((u) => URL.revokeObjectURL(u));\n }, []);\n\n const group = r.data ?? null;\n return {\n group: deleted ? null : group,\n deleted,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: deleted ? undefined : r.error,\n saving: inflight > 0,\n reload: r.reload,\n create: useCallback(\n (input: GroupInput) =>\n run(async () => {\n const [g] = await client.entities.create([input]);\n setCreated(g!.id);\n return g!;\n }),\n [client, run],\n ),\n update: useCallback((patch: GroupUpdate) => run(() => client.entities.update(need(), patch)), [client, run, need]),\n remove: useCallback(\n () =>\n run(async () => {\n const g = await client.entities.remove(need());\n setDeleted(g);\n return g;\n }),\n [client, run, need],\n ),\n restore: useCallback(\n () =>\n run(async () => {\n const g = await client.entities.update(need(), { state: \"active\" });\n setDeleted(null);\n return g;\n }),\n [client, run, need],\n ),\n names: useCallback((op: \"set\" | \"add\" | \"remove\", names: Name[]) => run(() => client.entities.names(need(), op, names)), [client, run, need]),\n edges: useCallback((op: \"add\" | \"remove\", edges: EdgeInput[]) => run(() => client.entities.edges(need(), op, edges)), [client, run, need]),\n merge: useCallback(\n (into: string) =>\n run(async () => {\n const report = await client.entities.merge(need(), into);\n setMerged(report.into);\n return report;\n }),\n [client, run, need],\n ),\n uploadImage: useCallback(\n (slot: EntityImageSlot, file: Blob & { name?: string }, u: { wait?: boolean; edit?: Edit } = {}) =>\n run(async () => {\n const kind = group?.kind;\n if (!kind) throw new Error(\"contentkit-ui: load the group first\");\n const g = await client.entities.uploadImage({ kind, id: need() }, slot, file, u);\n // A cropped upload's renditions show the crop; until then the file shows uncropped.\n if (typeof URL.createObjectURL === \"function\" && !u.edit) {\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((p) => ({ ...p, [slot]: url }));\n }\n return g;\n }),\n [client, run, need, group?.kind],\n ),\n previews: { cover: group?.cover?.image ? previews.cover : undefined, avatar: group?.avatar?.image ? previews.avatar : undefined },\n };\n}\n\n/** Groups the signed-in person owns, edits or belongs to, a cursor page at a time; nothing signed out. */\nexport function useMyGroups(q: Omit<MyEntityQuery, \"cursor\" | \"limit\"> & { pageSize?: number } = {}, o: { client?: ContentKitClient } = {}): UseList<MyGroup> {\n const { client, store, scope, viewer, language } = useContentScope(o.client);\n const { pageSize, ...rest } = q;\n const query = { language, ...rest };\n return useList(store, viewer === null ? null : keyOf(\"groups-mine\", scope, query, pageSize), { type: \"groups-mine\" }, async (cursor, signal) => {\n const page = await client.entities.mine({ ...query, cursor: cursor as string | undefined, limit: pageSize }, signal);\n return { items: page.entities, next: page.next_cursor ?? null };\n });\n}\n\n/** A membership with a stable id: (group, content, version, role) is its identity. */\nexport type Member = Membership & { id: string };\n\nexport const memberId = (m: Membership): string => [m.group_id ?? \"\", m.content_id ?? \"\", m.version_id ?? \"\", m.role ?? \"\"].join(\"|\");\n\nexport interface UseGroupMembers extends UseList<Member> {\n saving: boolean;\n /** Adds members, or updates their role's position, window and state. */\n add: (members: Membership[]) => Promise<number>;\n remove: (members: Membership[]) => Promise<number>;\n /** Orders an ordered group: the listed members first, in list order. */\n reorder: (members: MemberRef[]) => Promise<number>;\n}\n\n/** Staff: a group's memberships in member order, with writes. */\nexport function useGroupMembers(id: string | null | undefined, o: { pageSize?: number; client?: ContentKitClient } = {}): UseGroupMembers {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 50;\n const list = useList<Member>(store, id ? keyOf(\"group-members\", scope, id, size) : null, { type: \"group-members\", id: id ?? undefined }, async (cursor, signal) => {\n const page = await client.entities.members(id!, { cursor: cursor as string | undefined, limit: size }, signal);\n return { items: page.members.map((m) => ({ ...m, id: memberId(m) })), next: page.next_cursor ?? null };\n });\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const write = useCallback(\n async (fn: (gid: string) => Promise<{ count: number }>) => {\n if (!id) throw new Error(\"contentkit-ui: no group\");\n setInflight((n) => n + 1);\n try {\n return (await guarded(() => fn(id))).count;\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [id, guarded],\n );\n return {\n ...list,\n saving: inflight > 0,\n add: useCallback((ms: Membership[]) => write((g) => client.entities.addMembers(g, ms)), [client, write]),\n remove: useCallback((ms: Membership[]) => write((g) => client.entities.removeMembers(g, ms)), [client, write]),\n reorder: useCallback((ms: MemberRef[]) => write((g) => client.entities.reorder(g, ms)), [client, write]),\n };\n}\n\nexport interface UseContentGroups {\n memberships: Membership[];\n /** The groups the memberships name. */\n entities: ReadonlyMap<string, GroupCard>;\n loading: boolean;\n error?: ContentKitError;\n saving: boolean;\n reload: () => void;\n /** Replaces the content's memberships, its versions' included, in these group kinds (every kind its kind joins when omitted); create links new names, creating entities of creatable kinds. */\n set: (memberships: Membership[], kinds?: string[], create?: NewEntity[]) => Promise<ContentMemberships>;\n}\n\n/** Staff: one content's memberships, its versions' included, and their replacement. */\nconst NO_MEMBERSHIPS: Membership[] = [];\n\nexport function useContentGroups(contentId: string | null | undefined, o: { language?: string; client?: ContentKitClient } = {}): UseContentGroups {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const key = contentId ? keyOf(\"group-memberships\", scope, contentId, language) : null;\n const r = useResource<ContentMemberships>(store, key, { type: \"group-memberships\", id: contentId ?? undefined }, (s) => client.entities.ofContent(contentId!, language, s));\n const guarded = useGuarded();\n const [inflight, setInflight] = useState(0);\n const set = useCallback(\n async (memberships: Membership[], kinds?: string[], create?: NewEntity[]) => {\n if (!contentId) throw new Error(\"contentkit-ui: no content\");\n setInflight((n) => n + 1);\n try {\n const out = await guarded(() => client.entities.setOfContent(contentId, { kinds, memberships, ...(create?.length ? { create } : {}) }, language));\n if (key) store.set(key, out);\n return out;\n } finally {\n setInflight((n) => n - 1);\n }\n },\n [client, contentId, language, guarded, store, key],\n );\n const entities = useMemo(() => new Map((r.data?.entities ?? []).map((g) => [g.id, g])), [r.data]);\n // Stable while loading or failed: effects keyed on it must not see a new list every render.\n const memberships = useMemo(() => r.data?.memberships ?? NO_MEMBERSHIPS, [r.data]);\n return {\n memberships,\n entities,\n loading: !!key && (r.loading || !r.loaded),\n error: r.error,\n saving: inflight > 0,\n reload: r.reload,\n set,\n };\n}\n"],"mappings":"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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ContentKitProvider, type ContentKitProviderProps } from './provider.js';
|
|
2
2
|
export { useReaderPreferences } from './reader-preferences.js';
|
|
3
|
-
export {
|
|
3
|
+
export { usePageNotices, type UsePageNotices } from './page-notices.js';
|
|
4
4
|
export { useContentKit, useContentKitClient, useContentURLs, useErrorReporter, useGuarded, GuardContext, useOptionalContentKitClient, type ContentKitContextValue, type ContentKitErrorHandler, type ContentKitErrorInfo, type ContentKitGuard, type ContentKitOperation, type Navigate, type SocialOperation, } from './context.js';
|
|
5
5
|
export { useMediaRead, type MediaReadOptions, type ReadWindow, type UseMediaRead } from './read.js';
|
|
6
6
|
export { nameIn, thumbnailOf, useMediaFolder, type FolderGroup, type MediaFolderOptions, type UseMediaFolder } from './folder.js';
|
|
@@ -14,11 +14,11 @@ export { useContentConfig, type UseContentConfig } from './config.js';
|
|
|
14
14
|
export { useFavorite, useReaction, type UseFavorite, type UseReaction } from './engagement.js';
|
|
15
15
|
export { usePoll, usePollEditor, usePolls, withVote, type PollFilter, type PollImages, type UsePoll, type UsePollEditor } from './polls.js';
|
|
16
16
|
export { useContent, useManagedContents, type ManageFilter, type UseContent } from './contents.js';
|
|
17
|
-
export { useActivity, useMyStats,
|
|
17
|
+
export { useActivity, useMyStats, type UseStats } from './stats.js';
|
|
18
18
|
export { defaultVersion, editionOf, findVersion, liveVersions, useContentDetail, type ContentDetailOptions, type UseContentDetail } from './detail.js';
|
|
19
19
|
export { useVersionParam } from './location.js';
|
|
20
|
-
export { memberId, useContentGroups, useGroup, useGroupMembers, useGroups, useKinds, useMyGroups, type GroupFilter, type GroupSort, type Member, type UseContentGroups, type UseGroup, type UseGroupMembers, type UseKinds, } from './groups.js';
|
|
21
|
-
export { beaconsFor, useBeacons, useContentFilters, useContents, useExposure, useHistory, useLockedCount, useMyFavorites, useMyReactions, useRecentSearches, useRecommendations, useReleaseDates, useDailyReleases, type UseDailyReleases, useSearch, useSimilar, useTypeahead, type ExposureOptions, type MyInteraction, type UseInteractions, type ReleaseDate, type UseContentFilters, type UseExposure, type UseHistory, type UseRail, type UseRecentSearches, type UseTypeahead, } from './discovery.js';
|
|
20
|
+
export { memberId, useContentGroups, useGroup, useGroupMembers, useGroups, useGroupsPage, useKinds, useMyGroups, type GroupFilter, type GroupSort, type Member, type UseContentGroups, type UseGroup, type UseGroupMembers, type UseGroupsPage, type UseKinds, } from './groups.js';
|
|
21
|
+
export { beaconsFor, useBeacons, useContentFilters, useContents, useContentsPage, useExposure, useHistory, useLockedCount, useMyFavorites, useMyReactions, useRecentSearches, useRecommendations, useReleaseDates, useDailyReleases, type UseDailyReleases, useSearch, useSearchPage, useSimilar, useTypeahead, type ExposureOptions, type MyInteraction, type UseInteractions, type ReleaseDate, type UseContentFilters, type UseContentsPage, type UseExposure, type UseHistory, type UseRail, type UseRecentSearches, type UseTypeahead, } from './discovery.js';
|
|
22
22
|
export { downloadMessage, nextRelease, startDownload, useReaderPlan, useSegmentPages, useVersionDownload, type ReaderPage, type UseReaderPlan, type UseSegmentPages, type UseVersionDownload, } from './versions.js';
|
|
23
23
|
export { useContentScope, usePaged, type UseList, type UsePaged } from './use-resource.js';
|
|
24
24
|
export { ResourceStore, resourcesFor, type Page, type Resource } from './resources.js';
|
|
@@ -30,3 +30,5 @@ export type { PlaybackProgress, ProgressReason } from './player.js';
|
|
|
30
30
|
export { HOVER_DELAY, InlinePreviewContext, useInlinePreview, useReducedMotion, type InlinePreviewOptions } from './inline-preview.js';
|
|
31
31
|
export { useNotifications, useNotificationUnread, type UseNotifications, type UseNotificationsOptions } from './notifications.js';
|
|
32
32
|
export { useReadingEngagement, type ReadingEngagement, type ReadingEngagementOptions } from './use-engagement.js';
|
|
33
|
+
export { usePostReading, usePostReads, type PostReadingOptions, type PostReads } from './post-reads.js';
|
|
34
|
+
export { useRememberedSort } from './remembered-sort.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ContentKitClient } from '../client/client.js';
|
|
2
|
+
import { PageNotices } from '../client/generated/wire.js';
|
|
3
|
+
export interface UsePageNotices {
|
|
4
|
+
notices: PageNotices["notices"];
|
|
5
|
+
loading: boolean;
|
|
6
|
+
reload: () => void;
|
|
7
|
+
dismiss: (notice: string) => Promise<void>;
|
|
8
|
+
restore: (notice: string) => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
/** A version's quiet notes about automatic page changes, for its editors. */
|
|
11
|
+
export declare function usePageNotices(content: string | null | undefined, version: string | null | undefined, o?: {
|
|
12
|
+
client?: ContentKitClient;
|
|
13
|
+
}): UsePageNotices;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { keyOf as e, useContentScope as t, useResource as n } from "./use-resource.js";
|
|
2
|
+
import { useCallback as r } from "react";
|
|
3
|
+
//#region src/react/page-notices.ts
|
|
4
|
+
function i(i, a, o = {}) {
|
|
5
|
+
let { client: s, store: c, scope: l } = t(o.client), u = i && a ? e("page-notices", l, i, a) : null, d = n(c, u, {
|
|
6
|
+
type: "page-notices",
|
|
7
|
+
id: a ?? void 0,
|
|
8
|
+
scope: i ?? void 0
|
|
9
|
+
}, (e) => s.versions.pageNotices(i, a, e)), f = d.data, p = r(async (e) => {
|
|
10
|
+
i && a && u && (await s.versions.dismissPageNotice(i, a, e), c.set(u, { notices: (f?.notices ?? []).filter((t) => t.id !== e) }));
|
|
11
|
+
}, [
|
|
12
|
+
s,
|
|
13
|
+
i,
|
|
14
|
+
a,
|
|
15
|
+
u,
|
|
16
|
+
c,
|
|
17
|
+
f
|
|
18
|
+
]), m = r(async (e) => {
|
|
19
|
+
i && a && u && c.set(u, await s.versions.restorePageNotice(i, a, e));
|
|
20
|
+
}, [
|
|
21
|
+
s,
|
|
22
|
+
i,
|
|
23
|
+
a,
|
|
24
|
+
u,
|
|
25
|
+
c
|
|
26
|
+
]);
|
|
27
|
+
return {
|
|
28
|
+
notices: f?.notices ?? [],
|
|
29
|
+
loading: !!u && (d.loading || !d.loaded),
|
|
30
|
+
reload: d.reload,
|
|
31
|
+
dismiss: p,
|
|
32
|
+
restore: m
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { i as usePageNotices };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=page-notices.js.map
|
|
@@ -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"}
|
package/dist/react/resources.js
CHANGED
|
@@ -150,7 +150,7 @@ var n = (e) => [...e.bindings.values()].at(-1), r = {
|
|
|
150
150
|
switch (e.type) {
|
|
151
151
|
case "media.committed":
|
|
152
152
|
case "media.processed":
|
|
153
|
-
this.invalidate((t) => t.type === "page-
|
|
153
|
+
this.invalidate((t) => t.type === "page-notices" && t.id === e.ref.version);
|
|
154
154
|
break;
|
|
155
155
|
case "notifications.updated":
|
|
156
156
|
this.updateNotification(e.item, n);
|
|
@@ -281,7 +281,7 @@ var n = (e) => [...e.bindings.values()].at(-1), r = {
|
|
|
281
281
|
this.invalidate((t) => t.type === "moderation-rules" && t.scope === e.rules);
|
|
282
282
|
break;
|
|
283
283
|
case "reader.changed":
|
|
284
|
-
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);
|
|
285
285
|
break;
|
|
286
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");
|
|
287
287
|
}
|