@openrails/contentkit-ui 0.68.0 → 0.68.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +55 -10
  2. package/dist/client/client.d.ts +4 -2
  3. package/dist/client/client.js +1 -0
  4. package/dist/client/client.js.map +1 -1
  5. package/dist/client/content/bans.js +7 -7
  6. package/dist/client/content/bans.js.map +1 -1
  7. package/dist/client/content/comments.js +16 -14
  8. package/dist/client/content/comments.js.map +1 -1
  9. package/dist/client/content/favorites.js +5 -5
  10. package/dist/client/content/favorites.js.map +1 -1
  11. package/dist/client/content/moderation.js +5 -5
  12. package/dist/client/content/moderation.js.map +1 -1
  13. package/dist/client/content/polls.js +34 -31
  14. package/dist/client/content/polls.js.map +1 -1
  15. package/dist/client/content/posts.js +21 -20
  16. package/dist/client/content/posts.js.map +1 -1
  17. package/dist/client/content/reactions.js +4 -4
  18. package/dist/client/content/reactions.js.map +1 -1
  19. package/dist/client/http.d.ts +6 -2
  20. package/dist/client/http.js +11 -3
  21. package/dist/client/http.js.map +1 -1
  22. package/dist/client/media/client.js +24 -24
  23. package/dist/client/media/client.js.map +1 -1
  24. package/dist/client/media/rules.d.ts +2 -0
  25. package/dist/client/media/rules.js +6 -2
  26. package/dist/client/media/rules.js.map +1 -1
  27. package/dist/client/taxonomy.js +2 -2
  28. package/dist/client/taxonomy.js.map +1 -1
  29. package/dist/components/media-folder-editor.d.ts +17 -3
  30. package/dist/components/media-folder-editor.js +146 -99
  31. package/dist/components/media-folder-editor.js.map +1 -1
  32. package/dist/components/slot-upload.js +102 -100
  33. package/dist/components/slot-upload.js.map +1 -1
  34. package/dist/react/canonical.d.ts +2 -2
  35. package/dist/react/canonical.js +2 -12
  36. package/dist/react/canonical.js.map +1 -1
  37. package/dist/react/comments.js +35 -27
  38. package/dist/react/comments.js.map +1 -1
  39. package/dist/react/context.d.ts +7 -0
  40. package/dist/react/context.js +20 -15
  41. package/dist/react/context.js.map +1 -1
  42. package/dist/react/engagement.js +2 -2
  43. package/dist/react/engagement.js.map +1 -1
  44. package/dist/react/folder.d.ts +29 -9
  45. package/dist/react/folder.js +170 -113
  46. package/dist/react/folder.js.map +1 -1
  47. package/dist/react/polls.js +3 -3
  48. package/dist/react/polls.js.map +1 -1
  49. package/dist/react/posts.js +2 -2
  50. package/dist/react/posts.js.map +1 -1
  51. package/dist/react/provider.d.ts +7 -3
  52. package/dist/react/provider.js +34 -20
  53. package/dist/react/provider.js.map +1 -1
  54. package/dist/react/public.js +11 -11
  55. package/dist/react/public.js.map +1 -1
  56. package/dist/react/read.js +93 -92
  57. package/dist/react/read.js.map +1 -1
  58. package/dist/react/resources.d.ts +6 -3
  59. package/dist/react/resources.js +40 -40
  60. package/dist/react/resources.js.map +1 -1
  61. package/dist/react/slot.d.ts +4 -2
  62. package/dist/react/slot.js +96 -92
  63. package/dist/react/slot.js.map +1 -1
  64. package/dist/react/store.d.ts +1 -1
  65. package/dist/react/store.js +21 -20
  66. package/dist/react/store.js.map +1 -1
  67. package/dist/react/upload.d.ts +3 -2
  68. package/dist/react/upload.js +20 -15
  69. package/dist/react/upload.js.map +1 -1
  70. package/dist/react/use-resource.d.ts +1 -0
  71. package/dist/react/use-resource.js +38 -30
  72. package/dist/react/use-resource.js.map +1 -1
  73. package/dist/react.js +15 -15
  74. package/package.json +7 -5
@@ -6,8 +6,9 @@ var t = {
6
6
  loaded: !1
7
7
  }, n = (e, t) => !!e && !!t && e.kind === t.kind && e.id === t.id, r = class {
8
8
  slots = /* @__PURE__ */ new Map();
9
+ version = 0;
9
10
  constructor(e) {
10
- e.subscribe((e) => this.apply(e));
11
+ e.subscribe((e, t) => this.apply(e, t));
11
12
  }
12
13
  subscribe(e, n, r) {
13
14
  let i = this.slots.get(e);
@@ -15,7 +16,8 @@ var t = {
15
16
  tag: n,
16
17
  listeners: /* @__PURE__ */ new Set(),
17
18
  entry: t,
18
- version: 0
19
+ version: 0,
20
+ readVersion: ++this.version
19
21
  });
20
22
  let a = i;
21
23
  return a.listeners.add(r), () => {
@@ -44,7 +46,7 @@ var t = {
44
46
  loadingMore: !1,
45
47
  error: void 0
46
48
  }), r.fetcher(i.signal).then((e) => {
47
- r.ctl === i && (r.ctl = void 0, this.update(r, {
49
+ r.ctl === i && (r.ctl = void 0, r.readVersion = ++this.version, this.update(r, {
48
50
  data: e,
49
51
  loading: !1,
50
52
  loaded: !0,
@@ -88,7 +90,7 @@ var t = {
88
90
  let n = this.slots.get(e);
89
91
  if (!n) return;
90
92
  let r = typeof t == "function" ? t(n.entry.data) : t;
91
- this.update(n, {
93
+ n.readVersion = ++this.version, this.update(n, {
92
94
  data: r,
93
95
  loaded: !0,
94
96
  error: void 0
@@ -96,7 +98,7 @@ var t = {
96
98
  }
97
99
  mark(e) {
98
100
  let t = this.slots.get(e);
99
- return t ? ++t.version : 0;
101
+ return t ? t.version = ++this.version : 0;
100
102
  }
101
103
  rollback(e, t, n) {
102
104
  let r = this.slots.get(e);
@@ -106,13 +108,15 @@ var t = {
106
108
  });
107
109
  }
108
110
  patch(e, t) {
109
- for (let n of this.slots.values()) n.entry.data !== void 0 && e(n.tag) && this.update(n, { data: t(n.entry.data) });
111
+ for (let n of this.slots.values()) n.entry.data !== void 0 && e(n.tag) && this.update(n, { data: t(n.entry.data, n.readVersion) });
110
112
  }
111
113
  invalidate(e) {
112
114
  for (let [t, n] of this.slots) e(n.tag) && n.listeners.size && this.load(t, !0);
113
115
  }
114
- apply(e) {
115
- let t = (e, t) => this.patch((t) => e.includes(t.type), (e) => {
116
+ apply(e, t) {
117
+ let r = (e, n) => {
118
+ this.patch((n) => e(n) && (n.readScope ?? "{}") === t, n), this.invalidate((n) => e(n) && (n.readScope ?? "{}") !== t);
119
+ }, i = (e, t) => r((t) => e.includes(t.type), (e) => {
116
120
  let n = !1, r = [];
117
121
  for (let i of e.items) {
118
122
  let e = t(i);
@@ -122,7 +126,7 @@ var t = {
122
126
  ...e,
123
127
  items: r
124
128
  } : e;
125
- }), r = [
129
+ }), a = [
126
130
  "comments",
127
131
  "replies",
128
132
  "latest",
@@ -130,42 +134,42 @@ var t = {
130
134
  ];
131
135
  switch (e.type) {
132
136
  case "comment.created": {
133
- let r = e.comment.reply_to_id, i = !e.comment.moderation;
134
- this.patch((t) => r ? t.type === "replies" && t.id === r : t.type === "comments" && n(t.ref, e.ref), (t) => t.items.some((t) => t.id === e.comment.id) ? t : {
135
- ...t,
136
- items: r ? [...t.items, e.comment] : [e.comment, ...t.items]
137
- }), r && i && t(["comments"], (e) => e.id === r ? {
137
+ let t = e.comment.reply_to_id, a = !e.comment.moderation;
138
+ r((r) => t ? r.type === "replies" && r.id === t : r.type === "comments" && n(r.ref, e.ref), (n) => n.items.some((t) => t.id === e.comment.id) ? n : {
139
+ ...n,
140
+ items: t ? [...n.items, e.comment] : [e.comment, ...n.items]
141
+ }), t && a && i(["comments"], (e) => e.id === t ? {
138
142
  ...e,
139
143
  reply_count: e.reply_count + 1
140
144
  } : e), this.invalidate((e) => e.type === "latest" || e.type === "admin-comments");
141
145
  break;
142
146
  }
143
147
  case "comment.updated":
144
- t(r, (t) => t.id === e.comment.id ? {
148
+ i(a, (t) => t.id === e.comment.id ? {
145
149
  ...t,
146
150
  ...e.comment,
147
151
  author: e.comment.author ?? t.author
148
152
  } : t);
149
153
  break;
150
154
  case "comment.reacted":
151
- t(r, (t) => t.id === e.id ? {
155
+ i(a, (t) => t.id === e.id ? {
152
156
  ...t,
153
157
  ...e.counts
154
158
  } : t);
155
159
  break;
156
160
  case "comment.deleted": {
157
- let n;
158
- t(["comments", "replies"], (t) => t.id !== e.id || t.deleted ? t : (t.moderation || (n = t.reply_to_id), {
159
- ...t,
161
+ let t;
162
+ i(["comments", "replies"], (n) => n.id !== e.id || n.deleted ? n : (n.moderation || (t = n.reply_to_id), {
163
+ ...n,
160
164
  deleted: !0,
161
165
  body: "[deleted]",
162
166
  author: void 0,
163
167
  user_id: void 0,
164
168
  anon_name: void 0
165
- })), n && t(["comments"], (e) => e.id === n ? {
169
+ })), t && i(["comments"], (e) => e.id === t ? {
166
170
  ...e,
167
171
  reply_count: Math.max(0, e.reply_count - 1)
168
- } : e), t(["latest"], (t) => t.id === e.id ? null : t), t(["admin-comments"], (t) => t.id === e.id ? {
172
+ } : e), i(["latest"], (t) => t.id === e.id ? null : t), i(["admin-comments"], (t) => t.id === e.id ? {
169
173
  ...t,
170
174
  deleted: !0,
171
175
  deleted_at: (/* @__PURE__ */ new Date()).toISOString()
@@ -173,13 +177,20 @@ var t = {
173
177
  break;
174
178
  }
175
179
  case "comment.restored":
176
- this.invalidate((e) => r.includes(e.type));
180
+ this.invalidate((e) => a.includes(e.type));
177
181
  break;
178
182
  case "reaction.changed":
179
- this.patch((t) => t.type === "reaction" && n(t.ref, e.ref), () => e.counts), e.ref.kind === "post" && this.postTotals(e.ref.id, e.counts.likes, e.counts.dislikes);
183
+ if (r((t) => t.type === "reaction" && n(t.ref, e.ref), () => e.counts), e.ref.kind === "post") {
184
+ let t = (t) => t.id === e.ref.id ? {
185
+ ...t,
186
+ total_likes: e.counts.likes,
187
+ total_dislikes: e.counts.dislikes
188
+ } : t;
189
+ r((t) => t.type === "post" && t.id === e.ref.id, t), i(["posts"], t);
190
+ }
180
191
  break;
181
192
  case "favorite.changed":
182
- this.patch((t) => t.type === "favorite" && n(t.ref, e.ref), () => ({
193
+ r((t) => t.type === "favorite" && n(t.ref, e.ref), () => ({
183
194
  favorited: e.favorited,
184
195
  count: e.count
185
196
  })), this.invalidate((e) => e.type === "favorites");
@@ -188,13 +199,13 @@ var t = {
188
199
  this.invalidate((e) => e.type === "posts");
189
200
  break;
190
201
  case "post.updated":
191
- this.patch((e) => e.type === "post", (t) => t?.id === e.post.id ? e.post : t), t(["posts"], (t) => t.id === e.post.id ? e.post : t);
202
+ r((e) => e.type === "post", (t) => t?.id === e.post.id ? e.post : t), i(["posts"], (t) => t.id === e.post.id ? e.post : t);
192
203
  break;
193
204
  case "post.changed":
194
205
  this.invalidate((t) => t.type === "post" && t.id === e.id || t.type === "posts");
195
206
  break;
196
207
  case "post.deleted":
197
- t(["posts"], (t) => t.id === e.id ? null : t), this.invalidate((e) => e.type === "posts" && e.scope === "deleted");
208
+ i(["posts"], (t) => t.id === e.id ? null : t), this.invalidate((e) => e.type === "posts" && e.scope === "deleted");
198
209
  break;
199
210
  case "post.restored":
200
211
  this.invalidate((t) => t.type === "post" && t.id === e.post.id || t.type === "posts");
@@ -203,32 +214,21 @@ var t = {
203
214
  this.invalidate((e) => e.type === "polls" || e.type === "poll" && !e.id);
204
215
  break;
205
216
  case "poll.updated":
206
- this.patch((e) => e.type === "poll", (t) => t?.id === e.poll.id ? e.poll : t), t(["polls"], (t) => t.id === e.poll.id ? e.poll : t);
217
+ r((e) => e.type === "poll", (t) => t?.id === e.poll.id ? e.poll : t), i(["polls"], (t) => t.id === e.poll.id ? e.poll : t);
207
218
  break;
208
219
  case "poll.changed":
209
220
  this.invalidate((t) => t.type === "poll" && (t.id === e.id || !t.id) || t.type === "polls");
210
221
  break;
211
222
  case "poll.deleted":
212
- t(["polls"], (t) => t.id === e.id ? null : t), this.invalidate((e) => e.type === "poll" && !e.id);
223
+ i(["polls"], (t) => t.id === e.id ? null : t), this.invalidate((e) => e.type === "poll" && !e.id);
213
224
  break;
214
225
  case "ban.saved":
215
226
  case "ban.lifted":
216
227
  this.invalidate((t) => t.type === "bans" && t.scope === e.scope || t.type === "standing");
217
228
  break;
218
- case "moderation.resolved": t(["held"], (t) => t.id === e.id ? null : t), e.kind === "comment" ? this.invalidate((e) => r.includes(e.type)) : this.invalidate((t) => t.type === "post" && t.id === e.id || t.type === "posts");
229
+ case "moderation.resolved": i(["held"], (t) => t.id === e.id ? null : t), e.kind === "comment" ? this.invalidate((e) => a.includes(e.type)) : this.invalidate((t) => t.type === "post" && t.id === e.id || t.type === "posts");
219
230
  }
220
231
  }
221
- postTotals(e, t, n) {
222
- let r = (r) => r.id === e ? {
223
- ...r,
224
- total_likes: t,
225
- total_dislikes: n
226
- } : r;
227
- this.patch((t) => t.type === "post" && t.id === e, r), this.patch((e) => e.type === "posts", (e) => ({
228
- ...e,
229
- items: e.items.map(r)
230
- }));
231
- }
232
232
  update(e, t) {
233
233
  e.entry = {
234
234
  ...e.entry,
@@ -1 +1 @@
1
- {"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Comment, Poll, Post, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T> {\n items: T[];\n next: string | number | null;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n ref?: RefBody;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n fetcher?: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\nconst sameRef = (a?: RefBody, b?: RefBody) => !!a && !!b && a.kind === b.kind && a.id === b.id;\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n\n constructor(client: ContentKitClient) {\n client.subscribe((c) => this.apply(c));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), entry: IDLE, version: 0 }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** The latest fetchers of a key: its first read, and a list's next page. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Slot[\"more\"]): void {\n const s = this.slots.get(key);\n if (s) Object.assign(s, { fetcher, more });\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n if (!s?.fetcher || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n s.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await s.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? ++s.version : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to the data of every slot whose tag matches. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange): void {\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n this.patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n switch (c.type) {\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && sameRef(t.ref, c.ref));\n this.patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type));\n break;\n case \"reaction.changed\":\n this.patch((t) => t.type === \"reaction\" && sameRef(t.ref, c.ref), () => c.counts);\n if (c.ref.kind === \"post\") this.postTotals(c.ref.id, c.counts.likes, c.counts.dislikes);\n break;\n case \"favorite.changed\":\n this.patch((t) => t.type === \"favorite\" && sameRef(t.ref, c.ref), () => ({ favorited: c.favorited, count: c.count }));\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"post.created\":\n this.invalidate((t) => t.type === \"posts\");\n break;\n case \"post.updated\":\n this.patch<Post | null>((t) => t.type === \"post\", (p) => (p?.id === c.post.id ? c.post : p));\n items<Post>([\"posts\"], (x) => (x.id === c.post.id ? c.post : x));\n break;\n case \"post.changed\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n case \"post.deleted\":\n items<Post>([\"posts\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"posts\" && t.scope === \"deleted\");\n break;\n case \"post.restored\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.post.id) || t.type === \"posts\");\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n this.patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type));\n else this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n }\n }\n\n private postTotals(id: string, likes: number, dislikes: number): void {\n const fn = (p: Post): Post => (p.id === id ? { ...p, total_likes: likes, total_dislikes: dislikes } : p);\n this.patch<Post>((t) => t.type === \"post\" && t.id === id, fn);\n this.patch<Page<Post>>((t) => t.type === \"posts\", (page) => ({ ...page, items: page.items.map(fn) }));\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Comment, Poll, Post, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T> {\n items: T[];\n next: string | number | null;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n readScope?: string;\n ref?: RefBody;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n fetcher?: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n /** Identifies the full read; item patches and appended pages retain it. */\n readVersion: number;\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\nconst sameRef = (a?: RefBody, b?: RefBody) => !!a && !!b && a.kind === b.kind && a.id === b.id;\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n // Tokens must remain unique when a scope leaves and its slots are recreated.\n private version = 0;\n\n constructor(client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), entry: IDLE, version: 0, readVersion: ++this.version }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** The latest fetchers of a key: its first read, and a list's next page. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Slot[\"more\"]): void {\n const s = this.slots.get(key);\n if (s) Object.assign(s, { fetcher, more });\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n if (!s?.fetcher || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n s.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n s.readVersion = ++this.version;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await s.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n s.readVersion = ++this.version;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? (s.version = ++this.version) : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to matching slots, with the read identity for scoped rollback. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T, readVersion: number) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T, s.readVersion) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange, scope: string | null): void {\n const patch = <T>(match: (tag: Tag) => boolean, fn: (data: T) => T) => {\n this.patch<T>((t) => match(t) && (t.readScope ?? \"{}\") === scope, fn);\n this.invalidate((t) => match(t) && (t.readScope ?? \"{}\") !== scope);\n };\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n switch (c.type) {\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && sameRef(t.ref, c.ref));\n patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type));\n break;\n case \"reaction.changed\":\n patch((t) => t.type === \"reaction\" && sameRef(t.ref, c.ref), () => c.counts);\n if (c.ref.kind === \"post\") {\n const totals = (p: Post): Post => (p.id === c.ref.id ? { ...p, total_likes: c.counts.likes, total_dislikes: c.counts.dislikes } : p);\n patch<Post>((t) => t.type === \"post\" && t.id === c.ref.id, totals);\n items<Post>([\"posts\"], totals);\n }\n break;\n case \"favorite.changed\":\n patch((t) => t.type === \"favorite\" && sameRef(t.ref, c.ref), () => ({ favorited: c.favorited, count: c.count }));\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"post.created\":\n this.invalidate((t) => t.type === \"posts\");\n break;\n case \"post.updated\":\n patch<Post | null>((t) => t.type === \"post\", (p) => (p?.id === c.post.id ? c.post : p));\n items<Post>([\"posts\"], (x) => (x.id === c.post.id ? c.post : x));\n break;\n case \"post.changed\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n case \"post.deleted\":\n items<Post>([\"posts\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"posts\" && t.scope === \"deleted\");\n break;\n case \"post.restored\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.post.id) || t.type === \"posts\");\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type));\n else this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":";;AA+CA,IAAM,IAAwB;CAAE,MAAM,KAAA;CAAW,SAAS;CAAO,QAAQ;AAAM,GAEzE,KAAW,GAAa,MAAgB,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,IAO/E,IAAb,MAA2B;CACzB,wBAAyB,IAAI,IAAkB;CAE/C,UAAkB;CAElB,YAAY,GAA0B;EACpC,EAAO,WAAW,GAAG,MAAU,KAAK,MAAM,GAAG,CAAK,CAAC;CACrD;CAEA,UAAU,GAAa,GAAU,GAAkC;EACjE,IAAI,IAAI,KAAK,MAAM,IAAI,CAAG;EAC1B,AAAK,KAAG,KAAK,MAAM,IAAI,GAAM,IAAI;GAAE;GAAK,2BAAW,IAAI,IAAI;GAAG,OAAO;GAAM,SAAS;GAAG,aAAa,EAAE,KAAK;EAAQ,CAAE;EACrH,IAAM,IAAO;EAEb,OADA,EAAK,UAAU,IAAI,CAAQ,SACd;GAGX,AAFA,EAAK,UAAU,OAAO,CAAQ,GAE9B,qBAAqB;IACf,EAAK,UAAU,QAAQ,KAAK,MAAM,IAAI,CAAG,MAAM,MACnD,EAAK,KAAK,MAAM,GAChB,EAAK,SAAS,MAAM,GACpB,KAAK,MAAM,OAAO,CAAG;GACvB,CAAC;EACH;CACF;CAEA,SAAY,GAA0B;EACpC,OAAQ,KAAK,MAAM,IAAI,CAAG,CAAC,EAAE,SAAS;CACxC;CAGA,KAAK,GAAa,GAA2B,GAA2B;EACtE,IAAM,IAAI,KAAK,MAAM,IAAI,CAAG;EAC5B,AAAI,KAAG,OAAO,OAAO,GAAG;GAAE;GAAS;EAAK,CAAC;CAC3C;CAGA,KAAK,GAAa,IAAQ,IAAa;EACrC,IAAM,IAAI,KAAK,MAAM,IAAI,CAAG;EAC5B,IAAI,CAAC,GAAG,WAAY,CAAC,MAAU,EAAE,MAAM,UAAU,EAAE,MAAO;EAG1D,AAFA,EAAE,KAAK,MAAM,GACb,EAAE,SAAS,MAAM,GACjB,EAAE,UAAU,KAAA;EACZ,IAAM,IAAO,EAAE,MAAM,IAAI,gBAAgB;EAEzC,AADA,KAAK,OAAO,GAAG;GAAE,SAAS;GAAM,aAAa;GAAO,OAAO,KAAA;EAAU,CAAC,GACtE,EAAE,QAAQ,EAAI,MAAM,CAAC,CAAC,MACnB,MAAS;GACJ,EAAE,QAAQ,MACd,EAAE,MAAM,KAAA,GACR,EAAE,cAAc,EAAE,KAAK,SACvB,KAAK,OAAO,GAAG;IAAE;IAAM,SAAS;IAAO,QAAQ;IAAM,OAAO,KAAA;GAAU,CAAC;EACzE,IACC,MAAM;GACL,IAAI,EAAE,QAAQ,GAAK;GACnB,EAAE,MAAM,KAAA;GACR,IAAM,IAAQ,EAAkB,CAAC;GACjC,AAAI,EAAM,SAAS,aAAW,KAAK,OAAO,GAAG;IAAE,SAAS;IAAO,QAAQ;IAAM;GAAM,CAAC;EACtF,CACF;CACF;CAGA,MAAM,SAAS,GAA4B;EACzC,IAAM,IAAI,KAAK,MAAM,IAAI,CAAG,GACtB,IAAO,GAAG,MAAM;EACtB,IAAI,CAAC,GAAG,QAAQ,CAAC,KAAQ,EAAK,SAAS,QAAQ,EAAE,WAAW,EAAE,KAAK;EACnE,IAAM,IAAO,EAAE,UAAU,IAAI,gBAAgB;EAC7C,KAAK,OAAO,GAAG,EAAE,aAAa,GAAK,CAAC;EACpC,IAAI;GACF,IAAM,IAAQ,MAAM,EAAE,KAAK,EAAK,MAAM,EAAI,MAAM;GAChD,IAAI,EAAE,YAAY,GAAK;GACvB,IAAM,IAAM,EAAE,MAAM,MACd,IAAO,IAAI,IAAI,EAAI,MAAM,KAAK,MAAM,EAAE,EAAE,CAAC;GAC/C,KAAK,OAAO,GAAG;IAAE,MAAM;KAAE,OAAO,CAAC,GAAG,EAAI,OAAO,GAAG,EAAK,MAAM,QAAQ,MAAM,CAAC,EAAK,IAAI,EAAE,EAAE,CAAC,CAAC;KAAG,MAAM,EAAK;IAAK;IAAG,OAAO,KAAA;GAAU,CAAC;EACrI,SAAS,GAAG;GACV,IAAM,IAAQ,EAAkB,CAAC;GACjC,AAAI,EAAE,YAAY,KAAO,EAAM,SAAS,aAAW,KAAK,OAAO,GAAG,EAAE,SAAM,CAAC;EAC7E,UAAU;GACR,AAAI,EAAE,YAAY,MAChB,EAAE,UAAU,KAAA,GACZ,KAAK,OAAO,GAAG,EAAE,aAAa,GAAM,CAAC;EAEzC;CACF;CAGA,IAAO,GAAa,GAA8C;EAChE,IAAM,IAAI,KAAK,MAAM,IAAI,CAAG;EAC5B,IAAI,CAAC,GAAG;EACR,IAAM,IAAO,OAAO,KAAS,aAAc,EAAiC,EAAE,MAAM,IAAqB,IAAI;EAE7G,AADA,EAAE,cAAc,EAAE,KAAK,SACvB,KAAK,OAAO,GAAG;GAAE,MAAM;GAAM,QAAQ;GAAM,OAAO,KAAA;EAAU,CAAC;CAC/D;CAGA,KAAK,GAAqB;EACxB,IAAM,IAAI,KAAK,MAAM,IAAI,CAAG;EAC5B,OAAO,IAAK,EAAE,UAAU,EAAE,KAAK,UAAW;CAC5C;CAGA,SAAY,GAAa,GAAe,GAAe;EACrD,IAAM,IAAI,KAAK,MAAM,IAAI,CAAG;EAC5B,AAAI,KAAK,EAAE,YAAY,KAAO,KAAK,OAAO,GAAG;GAAE;GAAM,QAAQ;EAAK,CAAC;CACrE;CAGA,MAAS,GAA8B,GAA+C;EACpF,KAAK,IAAM,KAAK,KAAK,MAAM,OAAO,GAChC,AAAI,EAAE,MAAM,SAAS,KAAA,KAAa,EAAM,EAAE,GAAG,KAAG,KAAK,OAAO,GAAG,EAAE,MAAM,EAAG,EAAE,MAAM,MAAW,EAAE,WAAW,EAAE,CAAC;CAEjH;CAGA,WAAW,GAAoC;EAC7C,KAAK,IAAM,CAAC,GAAK,MAAM,KAAK,OAAO,AAAI,EAAM,EAAE,GAAG,KAAK,EAAE,UAAU,QAAM,KAAK,KAAK,GAAK,EAAI;CAC9F;CAEA,MAAc,GAAqB,GAA4B;EAC7D,IAAM,KAAY,GAA8B,MAAuB;GAErE,AADA,KAAK,OAAU,MAAM,EAAM,CAAC,MAAM,EAAE,aAAa,UAAU,GAAO,CAAE,GACpE,KAAK,YAAY,MAAM,EAAM,CAAC,MAAM,EAAE,aAAa,UAAU,CAAK;EACpE,GACM,KAAmC,GAAiB,MACxD,GACG,MAAM,EAAM,SAAS,EAAE,IAAI,IAC3B,MAAS;GACR,IAAI,IAAU,IACR,IAAW,CAAC;GAClB,KAAK,IAAM,KAAM,EAAK,OAAO;IAC3B,IAAM,IAAO,EAAG,CAAE;IAElB,AADI,MAAS,MAAI,IAAU,KACvB,KAAM,EAAI,KAAK,CAAI;GACzB;GACA,OAAO,IAAU;IAAE,GAAG;IAAM,OAAO;GAAI,IAAI;EAC7C,CACF,GACI,IAAW;GAAC;GAAY;GAAW;GAAU;EAAgB;EACnE,QAAQ,EAAE,MAAV;GACE,KAAK,mBAAmB;IACtB,IAAM,IAAS,EAAE,QAAQ,aACnB,IAAY,CAAC,EAAE,QAAQ;IAI7B,AAFA,GADa,MAAY,IAAS,EAAE,SAAS,aAAa,EAAE,OAAO,IAAS,EAAE,SAAS,cAAc,EAAQ,EAAE,KAAK,EAAE,GAAG,IAC9F,MAAO,EAAE,MAAM,MAAM,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,IAAI;KAAE,GAAG;KAAG,OAAO,IAAS,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,IAAI,CAAC,EAAE,SAAS,GAAG,EAAE,KAAK;IAAE,CAAE,GAC3J,KAAU,KAAW,EAAe,CAAC,UAAU,IAAI,MAAO,EAAE,OAAO,IAAS;KAAE,GAAG;KAAG,aAAa,EAAE,cAAc;IAAE,IAAI,CAAE,GAC7H,KAAK,YAAY,MAAM,EAAE,SAAS,YAAY,EAAE,SAAS,gBAAgB;IACzE;GACF;GACA,KAAK;IACH,EAAe,IAAW,MAAO,EAAE,OAAO,EAAE,QAAQ,KAAK;KAAE,GAAG;KAAG,GAAG,EAAE;KAAS,QAAQ,EAAE,QAAQ,UAAU,EAAE;IAAO,IAAI,CAAE;IAC1H;GACF,KAAK;IACH,EAAe,IAAW,MAAO,EAAE,OAAO,EAAE,KAAK;KAAE,GAAG;KAAG,GAAG,EAAE;IAAO,IAAI,CAAE;IAC3E;GACF,KAAK,mBAAmB;IACtB,IAAI;IAQJ,AAPA,EAAe,CAAC,YAAY,SAAS,IAAI,MACnC,EAAE,OAAO,EAAE,MAAM,EAAE,UAAgB,KAClC,EAAE,eAAY,IAAS,EAAE,cACvB;KAAE,GAAG;KAAG,SAAS;KAAM,MAAM;KAAa,QAAQ,KAAA;KAAW,SAAS,KAAA;KAAW,WAAW,KAAA;IAAU,EAC9G,GACG,KAAQ,EAAe,CAAC,UAAU,IAAI,MAAO,EAAE,OAAO,IAAS;KAAE,GAAG;KAAG,aAAa,KAAK,IAAI,GAAG,EAAE,cAAc,CAAC;IAAE,IAAI,CAAE,GAC7H,EAAe,CAAC,QAAQ,IAAI,MAAO,EAAE,OAAO,EAAE,KAAK,OAAO,CAAE,GAC5D,EAAyC,CAAC,gBAAgB,IAAI,MAAO,EAAE,OAAO,EAAE,KAAK;KAAE,GAAG;KAAG,SAAS;KAAM,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;IAAE,IAAI,CAAE;IACvJ;GACF;GACA,KAAK;IACH,KAAK,YAAY,MAAM,EAAS,SAAS,EAAE,IAAI,CAAC;IAChD;GACF,KAAK;IAEH,IADA,GAAO,MAAM,EAAE,SAAS,cAAc,EAAQ,EAAE,KAAK,EAAE,GAAG,SAAS,EAAE,MAAM,GACvE,EAAE,IAAI,SAAS,QAAQ;KACzB,IAAM,KAAU,MAAmB,EAAE,OAAO,EAAE,IAAI,KAAK;MAAE,GAAG;MAAG,aAAa,EAAE,OAAO;MAAO,gBAAgB,EAAE,OAAO;KAAS,IAAI;KAElI,AADA,GAAa,MAAM,EAAE,SAAS,UAAU,EAAE,OAAO,EAAE,IAAI,IAAI,CAAM,GACjE,EAAY,CAAC,OAAO,GAAG,CAAM;IAC/B;IACA;GACF,KAAK;IAEH,AADA,GAAO,MAAM,EAAE,SAAS,cAAc,EAAQ,EAAE,KAAK,EAAE,GAAG,UAAU;KAAE,WAAW,EAAE;KAAW,OAAO,EAAE;IAAM,EAAE,GAC/G,KAAK,YAAY,MAAM,EAAE,SAAS,WAAW;IAC7C;GACF,KAAK;IACH,KAAK,YAAY,MAAM,EAAE,SAAS,OAAO;IACzC;GACF,KAAK;IAEH,AADA,GAAoB,MAAM,EAAE,SAAS,SAAS,MAAO,GAAG,OAAO,EAAE,KAAK,KAAK,EAAE,OAAO,CAAE,GACtF,EAAY,CAAC,OAAO,IAAI,MAAO,EAAE,OAAO,EAAE,KAAK,KAAK,EAAE,OAAO,CAAE;IAC/D;GACF,KAAK;IACH,KAAK,YAAY,MAAO,EAAE,SAAS,UAAU,EAAE,OAAO,EAAE,MAAO,EAAE,SAAS,OAAO;IACjF;GACF,KAAK;IAEH,AADA,EAAY,CAAC,OAAO,IAAI,MAAO,EAAE,OAAO,EAAE,KAAK,OAAO,CAAE,GACxD,KAAK,YAAY,MAAM,EAAE,SAAS,WAAW,EAAE,UAAU,SAAS;IAClE;GACF,KAAK;IACH,KAAK,YAAY,MAAO,EAAE,SAAS,UAAU,EAAE,OAAO,EAAE,KAAK,MAAO,EAAE,SAAS,OAAO;IACtF;GACF,KAAK;IACH,KAAK,YAAY,MAAM,EAAE,SAAS,WAAY,EAAE,SAAS,UAAU,CAAC,EAAE,EAAG;IACzE;GACF,KAAK;IAGH,AADA,GAAoB,MAAM,EAAE,SAAS,SAAS,MAAO,GAAG,OAAO,EAAE,KAAK,KAAK,EAAE,OAAO,CAAE,GACtF,EAAY,CAAC,OAAO,IAAI,MAAO,EAAE,OAAO,EAAE,KAAK,KAAK,EAAE,OAAO,CAAE;IAC/D;GACF,KAAK;IACH,KAAK,YAAY,MAAO,EAAE,SAAS,WAAW,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,OAAQ,EAAE,SAAS,OAAO;IAC5F;GACF,KAAK;IAEH,AADA,EAAY,CAAC,OAAO,IAAI,MAAO,EAAE,OAAO,EAAE,KAAK,OAAO,CAAE,GACxD,KAAK,YAAY,MAAM,EAAE,SAAS,UAAU,CAAC,EAAE,EAAE;IACjD;GACF,KAAK;GACL,KAAK;IACH,KAAK,YAAY,MAAO,EAAE,SAAS,UAAU,EAAE,UAAU,EAAE,SAAU,EAAE,SAAS,UAAU;IAC1F;GACF,KAAK,uBAEH,AADA,EAAsB,CAAC,MAAM,IAAI,MAAO,EAAE,OAAO,EAAE,KAAK,OAAO,CAAE,GAC7D,EAAE,SAAS,YAAW,KAAK,YAAY,MAAM,EAAS,SAAS,EAAE,IAAI,CAAC,IACrE,KAAK,YAAY,MAAO,EAAE,SAAS,UAAU,EAAE,OAAO,EAAE,MAAO,EAAE,SAAS,OAAO;EAE1F;CACF;CAGA,OAAe,GAAS,GAAyC;EAC/D,EAAE,QAAQ;GAAE,GAAG,EAAE;GAAO,GAAG;EAAM;EACjC,KAAK,IAAM,KAAK,EAAE,WAAW,EAAE;CACjC;AACF,GAEM,oBAAS,IAAI,QAAyC;AAG5D,SAAgB,EAAa,GAAyC;CACpE,IAAI,IAAI,EAAO,IAAI,CAAM;CAEzB,OADK,KAAG,EAAO,IAAI,GAAS,IAAI,IAAI,EAAc,CAAM,CAAE,GACnD;AACT"}
@@ -2,7 +2,7 @@ import { AspectRatio } from '../client/aspect.js';
2
2
  import { ContentKitClient } from '../client/client.js';
3
3
  import { Size } from '../client/crop.js';
4
4
  import { ContentKitError } from '../client/errors.js';
5
- import { Edit, FileInfo, ReadResult, RefBody } from '../client/generated/wire.js';
5
+ import { Edit, FileInfo, PresetRule, ReadResult, RefBody } from '../client/generated/wire.js';
6
6
  import { CropSource } from '../client/image.js';
7
7
  import { Progress } from '../client/media/client.js';
8
8
  import { PublicPreset } from '../client/public.js';
@@ -23,8 +23,10 @@ export interface UseSlotImage {
23
23
  file: FileInfo | null;
24
24
  /** The current published files; empty until the worker publishes them. */
25
25
  renditions: Rendition[];
26
- /** "W:H": the preset's, else "1:1". */
26
+ /** "W:H": the image's, else its public preset's (GET /media/presets), else "1:1". */
27
27
  aspect: AspectRatio;
28
+ /** The path's public preset (aspect, widths, min_width) once the presets load. */
29
+ rule?: PresetRule;
28
30
  loading: boolean;
29
31
  error?: ContentKitError;
30
32
  reload: () => void;
@@ -2,109 +2,106 @@ import { ContentKitError as e, toContentKitError as t } from "../client/errors.j
2
2
  import { publicRenditions as n } from "../client/public.js";
3
3
  import { ratio as r } from "../client/aspect.js";
4
4
  import { samePath as i, stem as a } from "../client/media/client.js";
5
- import { centeredCrop as o, constrainCrop as s, editedSize as c, rotation as l, sameEdit as u } from "../client/crop.js";
6
- import { useContentKitClient as d } from "./context.js";
7
- import { decodeImage as f, isAnimatedImage as p } from "../client/image.js";
8
- import { withUpload as m } from "./store.js";
9
- import { useMediaRead as h } from "./read.js";
10
- import { useCallback as g, useEffect as _, useMemo as v, useRef as y, useState as b } from "react";
5
+ import { presetFor as o } from "../client/media/rules.js";
6
+ import { centeredCrop as s, constrainCrop as c, editedSize as l, rotation as u, sameEdit as d } from "../client/crop.js";
7
+ import { useContentKitClient as f, useReadScope as p } from "./context.js";
8
+ import { decodeImage as m, isAnimatedImage as h } from "../client/image.js";
9
+ import { withUpload as g } from "./store.js";
10
+ import { useMediaRead as _ } from "./read.js";
11
+ import { usePresets as v } from "./public.js";
12
+ import { useCallback as y, useLayoutEffect as b, useMemo as x, useRef as S, useState as C } from "react";
11
13
  //#region src/react/slot.ts
12
- function x(e) {
13
- let t = h(e.ref, {
14
+ function w(e) {
15
+ let t = _(e.ref, {
14
16
  editor: !0,
15
17
  prefix: a(e.path),
16
18
  read: e.read,
17
19
  client: e.client
18
- }), r = t.read, o = r?.files.find((t) => t.upload && i(t.path, e.path)) ?? null, { path: s } = e, c = t.set, l = g((e) => c(m(r, s, e)), [
19
- r,
20
- s,
21
- c
20
+ }), { presets: r } = v({ client: e.client }), s = o(r, e.ref.kind, e.path, e.image?.preset), c = t.read, l = c?.files.find((t) => t.upload && i(t.path, e.path)) ?? null, { path: u } = e, d = t.set, f = y((e) => d(g(c, u, e)), [
21
+ c,
22
+ u,
23
+ d
22
24
  ]);
23
25
  return {
24
- file: o,
25
- renditions: v(() => {
26
- if (!r) return n(e.image);
27
- let t = r.public?.find((t) => i(t.from, s) && (!e.image || t.preset === e.image.preset));
26
+ file: l,
27
+ renditions: x(() => {
28
+ if (!c) return n(e.image);
29
+ let t = c.public?.find((t) => i(t.from, u) && (!e.image || t.preset === e.image.preset));
28
30
  return n(t);
29
31
  }, [
30
- r,
32
+ c,
31
33
  e.image,
32
- s
34
+ u
33
35
  ]),
34
- aspect: e.image?.aspect || "1:1",
36
+ aspect: e.image?.aspect || s?.aspect || "1:1",
37
+ rule: s,
35
38
  loading: t.loading,
36
39
  error: t.error,
37
40
  reload: t.reload,
38
- set: l
41
+ set: f
39
42
  };
40
43
  }
41
- function S(e, t, n) {
42
- let i = r(n), a = l(t?.rotate ?? 0), u = t?.crop ? s(t.crop, e, i, a) : o(e, i, a);
43
- return c({
44
- width: u.w,
45
- height: u.h
44
+ function T(e, t, n) {
45
+ let i = r(n), a = u(t?.rotate ?? 0), o = t?.crop ? c(t.crop, e, i, a) : s(e, i, a);
46
+ return l({
47
+ width: o.w,
48
+ height: o.h
46
49
  }, a);
47
50
  }
48
- function C(n) {
49
- let { media: r } = d(n.client), [i, a] = b({ status: "idle" }), o = y(i);
50
- o.current = i;
51
- let s = y(null), c = y(0), l = y(n);
52
- l.current = n;
53
- let m = n.aspect ?? "1:1", h = g((e) => {
54
- let t = o.current, n = "source" in t ? t.source : void 0, r = "source" in e ? e.source : void 0;
55
- n && n !== r && n.revoke?.(), o.current = e, a(e);
56
- }, []);
57
- _(() => () => {
58
- s.current?.abort();
59
- let e = o.current;
60
- "source" in e && e.source?.revoke?.();
61
- }, []);
62
- let x = g(async (t, n, r) => {
63
- s.current?.abort(), s.current = null;
64
- let i = ++c.current;
65
- h({ status: "decoding" });
51
+ function E(n) {
52
+ let { media: r } = f(n.client), i = p(), [a, o] = C({ status: "idle" }), s = S(a);
53
+ s.current = a;
54
+ let c = S(null), l = S(0), u = S(n);
55
+ u.current = n;
56
+ let g = n.aspect ?? "1:1", _ = y((e) => {
57
+ let t = s.current, n = "source" in t ? t.source : void 0, r = "source" in e ? e.source : void 0;
58
+ n && n !== r && n.revoke?.(), s.current = e, o(e);
59
+ }, []), v = y(async (t, n, r) => {
60
+ c.current?.abort(), c.current = null;
61
+ let i = ++l.current;
62
+ _({ status: "decoding" });
66
63
  try {
67
64
  let e = await t();
68
- if (i !== c.current) return e.revoke?.();
69
- h({
65
+ if (i !== l.current) return e.revoke?.();
66
+ _({
70
67
  status: "cropping",
71
68
  source: e,
72
69
  edit: r,
73
70
  mode: n
74
71
  });
75
72
  } catch (t) {
76
- if (i !== c.current) return;
73
+ if (i !== l.current) return;
77
74
  let n = t instanceof e ? t : new e("decode", t instanceof Error ? t.message : String(t), { cause: t });
78
- h({
75
+ _({
79
76
  status: "error",
80
77
  error: n
81
- }), l.current.onError?.(n, "slot.decode");
78
+ }), u.current.onError?.(n, "slot.decode");
82
79
  }
83
- }, [h]), C = g((t) => x(async () => {
84
- if (l.current.animation === "reject" && await p(t)) throw new e("animation_not_allowed", "animated images are not allowed here", { status: 422 });
85
- return (l.current.decode ?? f)(t);
86
- }, "new", null), [x]), w = !!n.file && !n.file.staged && (n.file.size ?? 0) > 0 && n.file.type.startsWith("image/"), T = g(async () => {
87
- let e = l.current.file;
88
- e && await x(() => r.editorView(l.current.ref, e.path), "recrop", e.edit ?? null);
89
- }, [r, x]), E = g((e) => {
90
- let t = o.current;
91
- t.status === "cropping" ? u(t.edit, e) || h({
80
+ }, [_]), w = y((t) => v(async () => {
81
+ if (u.current.animation === "reject" && await h(t)) throw new e("animation_not_allowed", "animated images are not allowed here", { status: 422 });
82
+ return (u.current.decode ?? m)(t);
83
+ }, "new", null), [v]), E = !!n.file && !n.file.staged && (n.file.size ?? 0) > 0 && n.file.type.startsWith("image/"), D = y(async () => {
84
+ let e = u.current.file;
85
+ e && await v(() => r.editorView(u.current.ref, e.path), "recrop", e.edit ?? null);
86
+ }, [r, v]), O = y((e) => {
87
+ let t = s.current;
88
+ t.status === "cropping" ? d(t.edit, e) || _({
92
89
  ...t,
93
90
  edit: e
94
- }) : t.status === "error" && t.source && t.mode && h({
91
+ }) : t.status === "error" && t.source && t.mode && _({
95
92
  status: "cropping",
96
93
  source: t.source,
97
94
  edit: e,
98
95
  mode: t.mode
99
96
  });
100
- }, [h]), D = g(async (e) => {
101
- e !== void 0 && E(e);
102
- let n = o.current;
97
+ }, [_]), k = y(async (e) => {
98
+ e !== void 0 && O(e);
99
+ let n = s.current;
103
100
  if (!(n.status === "cropping" || n.status === "error" && n.source && n.mode)) return;
104
- let { source: i, mode: a } = n, c = n.edit ?? null, { ref: u, path: d, renderTimeout: f } = l.current, p = s.current = new AbortController(), m = (e) => p === s.current && h({
101
+ let { source: i, mode: a } = n, o = n.edit ?? null, { ref: l, path: d, renderTimeout: f } = u.current, p = c.current = new AbortController(), m = (e) => p === c.current && _({
105
102
  status: "saving",
106
103
  source: i,
107
- edit: c,
104
+ edit: o,
108
105
  mode: a,
109
106
  progress: e,
110
107
  rendering: e?.phase === "processing"
@@ -113,69 +110,76 @@ function C(n) {
113
110
  try {
114
111
  let e;
115
112
  if (a === "new") e = await r.put(i.file, {
116
- ref: u,
113
+ ref: l,
117
114
  path: d,
118
- edit: c,
115
+ edit: o,
119
116
  signal: p.signal,
120
117
  timeout: f,
121
118
  onProgress: m
122
119
  });
123
120
  else {
124
- let t = l.current.file?.path ?? d;
125
- await r.commit(u, [{
121
+ let t = u.current.file?.path ?? d;
122
+ if (await r.commit(l, [{
126
123
  op: "edit",
127
124
  path: t,
128
- ...c ? { edit: c } : {}
129
- }], { signal: p.signal }), m({
125
+ ...o ? { edit: o } : {}
126
+ }], { signal: p.signal }), p !== c.current) return;
127
+ m({
130
128
  phase: "processing",
131
129
  loaded: 0,
132
130
  total: 0
133
- }), e = await r.waitFor(u, t, {
131
+ }), e = await r.waitFor(l, t, {
134
132
  signal: p.signal,
135
133
  timeout: f
136
134
  });
137
135
  }
138
- return p === s.current ? (h({
136
+ return p === c.current ? (_({
139
137
  status: "done",
140
138
  file: e
141
- }), l.current.onSaved?.(e), e) : void 0;
139
+ }), u.current.onSaved?.(e), e) : void 0;
142
140
  } catch (e) {
143
- if (p !== s.current) return;
141
+ if (p !== c.current) return;
144
142
  let n = t(e);
145
- h(n.code === "aborted" ? {
143
+ _(n.code === "aborted" ? {
146
144
  status: "cropping",
147
145
  source: i,
148
- edit: c,
146
+ edit: o,
149
147
  mode: a
150
148
  } : {
151
149
  status: "error",
152
150
  error: n,
153
151
  source: i,
154
- edit: c,
152
+ edit: o,
155
153
  mode: a
156
- }), n.code !== "aborted" && l.current.onError?.(n, "slot.save");
154
+ }), n.code !== "aborted" && u.current.onError?.(n, "slot.save");
157
155
  return;
158
156
  }
159
157
  }, [
160
158
  r,
161
- h,
162
- E
163
- ]), O = g(() => {
164
- c.current++, s.current?.abort(), s.current = null, h({ status: "idle" });
165
- }, [h]), k = v(() => "source" in i && i.source ? S(i.source, i.edit, m) : void 0, [i, m]);
159
+ _,
160
+ O
161
+ ]), A = y(() => {
162
+ l.current++, c.current?.abort(), c.current = null, _({ status: "idle" });
163
+ }, [_]);
164
+ b(() => A, [
165
+ i,
166
+ r,
167
+ A
168
+ ]);
169
+ let j = x(() => "source" in a && a.source ? T(a.source, a.edit, g) : void 0, [a, g]);
166
170
  return {
167
- ...i,
168
- aspect: m,
169
- cropped: k,
170
- pick: C,
171
- canRecrop: w,
172
- recrop: T,
173
- setEdit: E,
174
- save: D,
175
- cancel: O
171
+ ...a,
172
+ aspect: g,
173
+ cropped: j,
174
+ pick: w,
175
+ canRecrop: E,
176
+ recrop: D,
177
+ setEdit: O,
178
+ save: k,
179
+ cancel: A
176
180
  };
177
181
  }
178
182
  //#endregion
179
- export { S as editOutput, C as useSlotCrop, x as useSlotImage };
183
+ export { T as editOutput, E as useSlotCrop, w as useSlotImage };
180
184
 
181
185
  //# sourceMappingURL=slot.js.map