@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.
Files changed (217) hide show
  1. package/README.md +6 -6
  2. package/dist/client/beacons.js +4 -1
  3. package/dist/client/beacons.js.map +1 -1
  4. package/dist/client/contents.d.ts +9 -1
  5. package/dist/client/contents.js +73 -55
  6. package/dist/client/contents.js.map +1 -1
  7. package/dist/client/discovery.d.ts +6 -2
  8. package/dist/client/discovery.js +1 -1
  9. package/dist/client/discovery.js.map +1 -1
  10. package/dist/client/generated/error-codes.d.ts +1 -1
  11. package/dist/client/generated/error-codes.js +4 -0
  12. package/dist/client/generated/error-codes.js.map +1 -1
  13. package/dist/client/generated/routes.d.ts +43 -30
  14. package/dist/client/generated/routes.js +70 -63
  15. package/dist/client/generated/routes.js.map +1 -1
  16. package/dist/client/generated/wire.d.ts +36 -74
  17. package/dist/client/generated/wire.js.map +1 -1
  18. package/dist/client/index.d.ts +1 -1
  19. package/dist/client/media/windows.d.ts +2 -0
  20. package/dist/client/media/windows.js +4 -1
  21. package/dist/client/media/windows.js.map +1 -1
  22. package/dist/client/stats.d.ts +4 -20
  23. package/dist/client/stats.js +0 -11
  24. package/dist/client/stats.js.map +1 -1
  25. package/dist/client/versions.d.ts +8 -6
  26. package/dist/client/versions.js +15 -10
  27. package/dist/client/versions.js.map +1 -1
  28. package/dist/components/admin/bulk-drafts.d.ts +17 -0
  29. package/dist/components/admin/bulk-drafts.js +41 -0
  30. package/dist/components/admin/bulk-drafts.js.map +1 -0
  31. package/dist/components/admin/content-admin.d.ts +1 -1
  32. package/dist/components/admin/content-admin.js.map +1 -1
  33. package/dist/components/admin/content-manager.d.ts +11 -3
  34. package/dist/components/admin/content-manager.js +739 -380
  35. package/dist/components/admin/content-manager.js.map +1 -1
  36. package/dist/components/admin/group-manager.d.ts +8 -3
  37. package/dist/components/admin/group-manager.js +218 -146
  38. package/dist/components/admin/group-manager.js.map +1 -1
  39. package/dist/components/admin/page-notices.d.ts +16 -0
  40. package/dist/components/admin/page-notices.js +113 -0
  41. package/dist/components/admin/page-notices.js.map +1 -0
  42. package/dist/components/admin/page-picker.d.ts +38 -0
  43. package/dist/components/admin/page-picker.js +159 -0
  44. package/dist/components/admin/page-picker.js.map +1 -0
  45. package/dist/components/admin/reader-content-editor.js +537 -500
  46. package/dist/components/admin/reader-content-editor.js.map +1 -1
  47. package/dist/components/admin/reader-manifest.js +181 -179
  48. package/dist/components/admin/reader-manifest.js.map +1 -1
  49. package/dist/components/admin/shell.d.ts +10 -2
  50. package/dist/components/admin/shell.js +114 -42
  51. package/dist/components/admin/shell.js.map +1 -1
  52. package/dist/components/admin/stats-dashboard.d.ts +4 -9
  53. package/dist/components/admin/stats-dashboard.js +300 -202
  54. package/dist/components/admin/stats-dashboard.js.map +1 -1
  55. package/dist/components/admin/upload-queue.d.ts +14 -0
  56. package/dist/components/admin/upload-queue.js +115 -98
  57. package/dist/components/admin/upload-queue.js.map +1 -1
  58. package/dist/components/content/card.d.ts +31 -3
  59. package/dist/components/content/card.js +324 -158
  60. package/dist/components/content/card.js.map +1 -1
  61. package/dist/components/content/daily-releases.js +14 -12
  62. package/dist/components/content/daily-releases.js.map +1 -1
  63. package/dist/components/content/editor-save.js +27 -25
  64. package/dist/components/content/editor-save.js.map +1 -1
  65. package/dist/components/content/editors.js +274 -253
  66. package/dist/components/content/editors.js.map +1 -1
  67. package/dist/components/content/favorites.d.ts +1 -1
  68. package/dist/components/content/favorites.js +36 -22
  69. package/dist/components/content/favorites.js.map +1 -1
  70. package/dist/components/content/grid.d.ts +11 -4
  71. package/dist/components/content/grid.js +52 -43
  72. package/dist/components/content/grid.js.map +1 -1
  73. package/dist/components/content/history.js +83 -63
  74. package/dist/components/content/history.js.map +1 -1
  75. package/dist/components/content/index.d.ts +5 -3
  76. package/dist/components/content/listing.d.ts +14 -3
  77. package/dist/components/content/listing.js +65 -56
  78. package/dist/components/content/listing.js.map +1 -1
  79. package/dist/components/content/offer-fields.js +6 -6
  80. package/dist/components/content/paged-grid.d.ts +10 -0
  81. package/dist/components/content/paged-grid.js +116 -0
  82. package/dist/components/content/paged-grid.js.map +1 -0
  83. package/dist/components/content/pagination.d.ts +19 -0
  84. package/dist/components/content/pagination.js +97 -0
  85. package/dist/components/content/pagination.js.map +1 -0
  86. package/dist/components/content/paywall.d.ts +9 -0
  87. package/dist/components/content/paywall.js +27 -3
  88. package/dist/components/content/paywall.js.map +1 -1
  89. package/dist/components/content/post-list.d.ts +37 -0
  90. package/dist/components/content/post-list.js +203 -0
  91. package/dist/components/content/post-list.js.map +1 -0
  92. package/dist/components/content/reader-settings.d.ts +2 -0
  93. package/dist/components/content/reader-settings.js +1 -1
  94. package/dist/components/content/reader-settings.js.map +1 -1
  95. package/dist/components/groups/banner.d.ts +6 -2
  96. package/dist/components/groups/banner.js +79 -61
  97. package/dist/components/groups/banner.js.map +1 -1
  98. package/dist/components/groups/directory.d.ts +12 -2
  99. package/dist/components/groups/directory.js +168 -105
  100. package/dist/components/groups/directory.js.map +1 -1
  101. package/dist/components/groups/editor.js +10 -10
  102. package/dist/components/groups/editor.js.map +1 -1
  103. package/dist/components/groups/entity-chips.d.ts +61 -0
  104. package/dist/components/groups/entity-chips.js +382 -0
  105. package/dist/components/groups/entity-chips.js.map +1 -0
  106. package/dist/components/groups/group-members.d.ts +29 -0
  107. package/dist/components/groups/group-members.js +474 -0
  108. package/dist/components/groups/group-members.js.map +1 -0
  109. package/dist/components/groups/header.js +53 -45
  110. package/dist/components/groups/header.js.map +1 -1
  111. package/dist/components/groups/index.d.ts +3 -1
  112. package/dist/components/groups/memberships.d.ts +3 -36
  113. package/dist/components/groups/memberships.js +105 -480
  114. package/dist/components/groups/memberships.js.map +1 -1
  115. package/dist/components/groups/scoped.d.ts +23 -0
  116. package/dist/components/groups/scoped.js +92 -0
  117. package/dist/components/groups/scoped.js.map +1 -0
  118. package/dist/components/media-folder-editor.d.ts +5 -0
  119. package/dist/components/media-folder-editor.js +418 -230
  120. package/dist/components/media-folder-editor.js.map +1 -1
  121. package/dist/components/social/poll.js +57 -48
  122. package/dist/components/social/poll.js.map +1 -1
  123. package/dist/components/sortable-list.d.ts +5 -1
  124. package/dist/components/sortable-list.js +97 -81
  125. package/dist/components/sortable-list.js.map +1 -1
  126. package/dist/index.d.ts +1 -1
  127. package/dist/index.js +41 -37
  128. package/dist/locales/admin/de.js +78 -45
  129. package/dist/locales/admin/de.js.map +1 -1
  130. package/dist/locales/admin/en.d.ts +74 -41
  131. package/dist/locales/admin/en.js +78 -45
  132. package/dist/locales/admin/en.js.map +1 -1
  133. package/dist/locales/admin/es.js +78 -45
  134. package/dist/locales/admin/es.js.map +1 -1
  135. package/dist/locales/admin/ja.js +78 -45
  136. package/dist/locales/admin/ja.js.map +1 -1
  137. package/dist/locales/admin/ko.js +78 -45
  138. package/dist/locales/admin/ko.js.map +1 -1
  139. package/dist/locales/admin/zh.js +78 -45
  140. package/dist/locales/admin/zh.js.map +1 -1
  141. package/dist/locales/de.js +2 -0
  142. package/dist/locales/de.js.map +1 -1
  143. package/dist/locales/discovery/de.js +25 -1
  144. package/dist/locales/discovery/de.js.map +1 -1
  145. package/dist/locales/discovery/en.d.ts +24 -0
  146. package/dist/locales/discovery/en.js +25 -1
  147. package/dist/locales/discovery/en.js.map +1 -1
  148. package/dist/locales/discovery/es.js +25 -1
  149. package/dist/locales/discovery/es.js.map +1 -1
  150. package/dist/locales/discovery/ja.js +25 -1
  151. package/dist/locales/discovery/ja.js.map +1 -1
  152. package/dist/locales/discovery/ko.js +25 -1
  153. package/dist/locales/discovery/ko.js.map +1 -1
  154. package/dist/locales/discovery/zh.js +25 -1
  155. package/dist/locales/discovery/zh.js.map +1 -1
  156. package/dist/locales/en.d.ts +120 -43
  157. package/dist/locales/en.js +2 -0
  158. package/dist/locales/en.js.map +1 -1
  159. package/dist/locales/es.js +2 -0
  160. package/dist/locales/es.js.map +1 -1
  161. package/dist/locales/groups/de.js +22 -4
  162. package/dist/locales/groups/de.js.map +1 -1
  163. package/dist/locales/groups/en.d.ts +20 -2
  164. package/dist/locales/groups/en.js +22 -4
  165. package/dist/locales/groups/en.js.map +1 -1
  166. package/dist/locales/groups/es.js +22 -4
  167. package/dist/locales/groups/es.js.map +1 -1
  168. package/dist/locales/groups/ja.js +22 -4
  169. package/dist/locales/groups/ja.js.map +1 -1
  170. package/dist/locales/groups/ko.js +22 -4
  171. package/dist/locales/groups/ko.js.map +1 -1
  172. package/dist/locales/groups/zh.js +22 -4
  173. package/dist/locales/groups/zh.js.map +1 -1
  174. package/dist/locales/ja.js +2 -0
  175. package/dist/locales/ja.js.map +1 -1
  176. package/dist/locales/ko.js +2 -0
  177. package/dist/locales/ko.js.map +1 -1
  178. package/dist/locales/zh.js +2 -0
  179. package/dist/locales/zh.js.map +1 -1
  180. package/dist/react/contents.js +1 -1
  181. package/dist/react/contents.js.map +1 -1
  182. package/dist/react/discovery.d.ts +16 -0
  183. package/dist/react/discovery.js +69 -37
  184. package/dist/react/discovery.js.map +1 -1
  185. package/dist/react/groups.d.ts +19 -3
  186. package/dist/react/groups.js +49 -22
  187. package/dist/react/groups.js.map +1 -1
  188. package/dist/react/index.d.ts +6 -4
  189. package/dist/react/page-notices.d.ts +13 -0
  190. package/dist/react/page-notices.js +38 -0
  191. package/dist/react/page-notices.js.map +1 -0
  192. package/dist/react/post-reads.d.ts +23 -0
  193. package/dist/react/post-reads.js +90 -0
  194. package/dist/react/post-reads.js.map +1 -0
  195. package/dist/react/remembered-sort.d.ts +5 -0
  196. package/dist/react/remembered-sort.js +29 -0
  197. package/dist/react/remembered-sort.js.map +1 -0
  198. package/dist/react/resources.js +2 -2
  199. package/dist/react/resources.js.map +1 -1
  200. package/dist/react/stats.d.ts +3 -7
  201. package/dist/react/stats.js +2 -6
  202. package/dist/react/stats.js.map +1 -1
  203. package/dist/react/use-engagement.d.ts +2 -0
  204. package/dist/react/use-engagement.js +40 -34
  205. package/dist/react/use-engagement.js.map +1 -1
  206. package/dist/react.js +29 -27
  207. package/dist/styles.css +1 -1
  208. package/dist/urls/index.d.ts +1 -1
  209. package/dist/urls.js +2 -2
  210. package/dist/urls.js.map +1 -1
  211. package/package.json +1 -1
  212. package/dist/components/admin/page-review.d.ts +0 -13
  213. package/dist/components/admin/page-review.js +0 -225
  214. package/dist/components/admin/page-review.js.map +0 -1
  215. package/dist/react/page-review.d.ts +0 -14
  216. package/dist/react/page-review.js +0 -37
  217. package/dist/react/page-review.js.map +0 -1
@@ -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;
@@ -11,9 +11,31 @@ function u(e = {}) {
11
11
  reload: o.reload
12
12
  };
13
13
  }
14
- function d(e = {}, i = {}) {
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(r, n) : a.entities.list(r, n));
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 f(r, s = {}) {
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 p(e = {}, i = {}) {
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 m = (e) => [
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 h(i, o = {}) {
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: m(e)
197
+ id: g(e)
172
198
  })),
173
199
  next: n.next_cursor ?? null
174
200
  };
175
- }), p = e(), [h, g] = l(0), _ = a(async (e) => {
201
+ }), p = e(), [m, h] = l(0), _ = a(async (e) => {
176
202
  if (!i) throw Error("contentkit-ui: no group");
177
- g((e) => e + 1);
203
+ h((e) => e + 1);
178
204
  try {
179
205
  return (await p(() => e(i))).count;
180
206
  } finally {
181
- g((e) => e - 1);
207
+ h((e) => e - 1);
182
208
  }
183
209
  }, [i, p]);
184
210
  return {
185
211
  ...f,
186
- saving: h > 0,
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 g = [];
193
- function _(r, o = {}) {
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)), _ = e(), [v, y] = l(0), b = a(async (e, t) => {
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 n = await _(() => c.entities.setOfContent(r, {
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, n), n;
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 ?? g, [h.data]),
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: v > 0,
249
+ saving: _ > 0,
223
250
  reload: h.reload,
224
251
  set: b
225
252
  };
226
253
  }
227
254
  //#endregion
228
- export { m as memberId, _ as useContentGroups, f as useGroup, h as useGroupMembers, d as useGroups, u as useKinds, p as useMyGroups };
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
@@ -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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"}
@@ -1,6 +1,6 @@
1
1
  export { ContentKitProvider, type ContentKitProviderProps } from './provider.js';
2
2
  export { useReaderPreferences } from './reader-preferences.js';
3
- export { usePageReview, type UsePageReview } from './page-review.js';
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, useTopContents, type UseStats } from './stats.js';
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"}
@@ -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-review" && t.id === e.ref.version);
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-review") && t.id === e.version);
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
  }