@openrails/contentkit-ui 0.85.1 → 0.87.0-rc.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 (211) hide show
  1. package/README.md +28 -7
  2. package/dist/client/beacons.d.ts +13 -4
  3. package/dist/client/beacons.js +14 -7
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/client.d.ts +3 -0
  6. package/dist/client/client.js +29 -27
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/codes.d.ts +6 -1
  9. package/dist/client/codes.js +4 -1
  10. package/dist/client/codes.js.map +1 -1
  11. package/dist/client/content/types.d.ts +4 -2
  12. package/dist/client/content/types.js.map +1 -1
  13. package/dist/client/discovery.js +3 -0
  14. package/dist/client/discovery.js.map +1 -1
  15. package/dist/client/errors.d.ts +6 -0
  16. package/dist/client/errors.js +6 -2
  17. package/dist/client/errors.js.map +1 -1
  18. package/dist/client/gallery.d.ts +3 -5
  19. package/dist/client/gallery.js +16 -20
  20. package/dist/client/gallery.js.map +1 -1
  21. package/dist/client/generated/error-codes.d.ts +1 -1
  22. package/dist/client/generated/error-codes.js +9 -1
  23. package/dist/client/generated/error-codes.js.map +1 -1
  24. package/dist/client/generated/routes.d.ts +29 -3
  25. package/dist/client/generated/routes.js +44 -3
  26. package/dist/client/generated/routes.js.map +1 -1
  27. package/dist/client/generated/wire.d.ts +60 -10
  28. package/dist/client/generated/wire.js.map +1 -1
  29. package/dist/client/index.d.ts +2 -1
  30. package/dist/client/media/queue.d.ts +4 -2
  31. package/dist/client/media/queue.js +18 -12
  32. package/dist/client/media/queue.js.map +1 -1
  33. package/dist/client/media/rules.d.ts +7 -1
  34. package/dist/client/media/rules.js +27 -21
  35. package/dist/client/media/rules.js.map +1 -1
  36. package/dist/client/media/windows.js +1 -1
  37. package/dist/client/media/windows.js.map +1 -1
  38. package/dist/client/stats.d.ts +32 -0
  39. package/dist/client/stats.js +35 -0
  40. package/dist/client/stats.js.map +1 -0
  41. package/dist/client/versions.d.ts +6 -4
  42. package/dist/client/versions.js +24 -26
  43. package/dist/client/versions.js.map +1 -1
  44. package/dist/client.js +30 -29
  45. package/dist/components/admin/content-manager.js +206 -206
  46. package/dist/components/admin/content-manager.js.map +1 -1
  47. package/dist/components/admin/group-manager.js +71 -70
  48. package/dist/components/admin/group-manager.js.map +1 -1
  49. package/dist/components/admin/media-item-panel.d.ts +24 -7
  50. package/dist/components/admin/media-item-panel.js +209 -197
  51. package/dist/components/admin/media-item-panel.js.map +1 -1
  52. package/dist/components/admin/poll-manager.js +25 -25
  53. package/dist/components/admin/stats-dashboard.d.ts +22 -0
  54. package/dist/components/admin/stats-dashboard.js +257 -0
  55. package/dist/components/admin/stats-dashboard.js.map +1 -0
  56. package/dist/components/content/card.d.ts +5 -3
  57. package/dist/components/content/card.js +155 -133
  58. package/dist/components/content/card.js.map +1 -1
  59. package/dist/components/content/download-button.d.ts +26 -0
  60. package/dist/components/content/download-button.js +51 -0
  61. package/dist/components/content/download-button.js.map +1 -0
  62. package/dist/components/content/editors.js +455 -427
  63. package/dist/components/content/editors.js.map +1 -1
  64. package/dist/components/content/grid.d.ts +8 -1
  65. package/dist/components/content/grid.js +153 -104
  66. package/dist/components/content/grid.js.map +1 -1
  67. package/dist/components/content/index.d.ts +4 -2
  68. package/dist/components/content/listing.d.ts +3 -1
  69. package/dist/components/content/listing.js +34 -34
  70. package/dist/components/content/listing.js.map +1 -1
  71. package/dist/components/content/paywall.d.ts +17 -5
  72. package/dist/components/content/paywall.js +115 -100
  73. package/dist/components/content/paywall.js.map +1 -1
  74. package/dist/components/content/reader-stats.d.ts +12 -0
  75. package/dist/components/content/reader-stats.js +47 -0
  76. package/dist/components/content/reader-stats.js.map +1 -0
  77. package/dist/components/groups/banner.js +33 -33
  78. package/dist/components/groups/directory.js +11 -11
  79. package/dist/components/groups/editor.js +293 -293
  80. package/dist/components/groups/editor.js.map +1 -1
  81. package/dist/components/groups/header.js +27 -27
  82. package/dist/components/groups/header.js.map +1 -1
  83. package/dist/components/groups/memberships.js +180 -180
  84. package/dist/components/groups/parts.d.ts +0 -2
  85. package/dist/components/groups/parts.js +9 -27
  86. package/dist/components/groups/parts.js.map +1 -1
  87. package/dist/components/media-folder-editor.js +239 -189
  88. package/dist/components/media-folder-editor.js.map +1 -1
  89. package/dist/components/media-gallery.d.ts +1 -4
  90. package/dist/components/media-gallery.js +19 -34
  91. package/dist/components/media-gallery.js.map +1 -1
  92. package/dist/components/notifications.js +17 -17
  93. package/dist/components/slot-upload.d.ts +2 -2
  94. package/dist/components/slot-upload.js +84 -79
  95. package/dist/components/slot-upload.js.map +1 -1
  96. package/dist/components/social/bans.js +34 -34
  97. package/dist/components/social/comments.js +1 -1
  98. package/dist/components/social/comments.js.map +1 -1
  99. package/dist/components/social/engagement.d.ts +2 -1
  100. package/dist/components/social/engagement.js +5 -3
  101. package/dist/components/social/engagement.js.map +1 -1
  102. package/dist/components/social/moderation.js +52 -48
  103. package/dist/components/social/moderation.js.map +1 -1
  104. package/dist/components/video-player.d.ts +2 -0
  105. package/dist/components/video-player.js +318 -314
  106. package/dist/components/video-player.js.map +1 -1
  107. package/dist/i18n/context.d.ts +2 -0
  108. package/dist/i18n/context.js +23 -5
  109. package/dist/i18n/context.js.map +1 -1
  110. package/dist/index.d.ts +2 -1
  111. package/dist/index.js +13 -10
  112. package/dist/locales/admin/de.js +27 -0
  113. package/dist/locales/admin/de.js.map +1 -1
  114. package/dist/locales/admin/en.d.ts +27 -0
  115. package/dist/locales/admin/en.js +27 -0
  116. package/dist/locales/admin/en.js.map +1 -1
  117. package/dist/locales/admin/es.js +27 -0
  118. package/dist/locales/admin/es.js.map +1 -1
  119. package/dist/locales/admin/ja.js +27 -0
  120. package/dist/locales/admin/ja.js.map +1 -1
  121. package/dist/locales/admin/ko.js +27 -0
  122. package/dist/locales/admin/ko.js.map +1 -1
  123. package/dist/locales/admin/zh.js +27 -0
  124. package/dist/locales/admin/zh.js.map +1 -1
  125. package/dist/locales/content/de.js +6 -0
  126. package/dist/locales/content/de.js.map +1 -1
  127. package/dist/locales/content/en.d.ts +6 -0
  128. package/dist/locales/content/en.js +6 -0
  129. package/dist/locales/content/en.js.map +1 -1
  130. package/dist/locales/content/es.js +6 -0
  131. package/dist/locales/content/es.js.map +1 -1
  132. package/dist/locales/content/ja.js +6 -0
  133. package/dist/locales/content/ja.js.map +1 -1
  134. package/dist/locales/content/ko.js +6 -0
  135. package/dist/locales/content/ko.js.map +1 -1
  136. package/dist/locales/content/zh.js +6 -0
  137. package/dist/locales/content/zh.js.map +1 -1
  138. package/dist/locales/de.js +14 -2
  139. package/dist/locales/de.js.map +1 -1
  140. package/dist/locales/discovery/de.js +3 -0
  141. package/dist/locales/discovery/de.js.map +1 -1
  142. package/dist/locales/discovery/en.d.ts +3 -0
  143. package/dist/locales/discovery/en.js +3 -0
  144. package/dist/locales/discovery/en.js.map +1 -1
  145. package/dist/locales/discovery/es.js +3 -0
  146. package/dist/locales/discovery/es.js.map +1 -1
  147. package/dist/locales/discovery/ja.js +3 -0
  148. package/dist/locales/discovery/ja.js.map +1 -1
  149. package/dist/locales/discovery/ko.js +3 -0
  150. package/dist/locales/discovery/ko.js.map +1 -1
  151. package/dist/locales/discovery/zh.js +3 -0
  152. package/dist/locales/discovery/zh.js.map +1 -1
  153. package/dist/locales/en.d.ts +50 -2
  154. package/dist/locales/en.js +14 -2
  155. package/dist/locales/en.js.map +1 -1
  156. package/dist/locales/es.js +14 -2
  157. package/dist/locales/es.js.map +1 -1
  158. package/dist/locales/ja.js +14 -2
  159. package/dist/locales/ja.js.map +1 -1
  160. package/dist/locales/ko.js +14 -2
  161. package/dist/locales/ko.js.map +1 -1
  162. package/dist/locales/zh.js +14 -2
  163. package/dist/locales/zh.js.map +1 -1
  164. package/dist/react/admin.js +22 -23
  165. package/dist/react/admin.js.map +1 -1
  166. package/dist/react/canonical.d.ts +4 -3
  167. package/dist/react/canonical.js +34 -33
  168. package/dist/react/canonical.js.map +1 -1
  169. package/dist/react/comments.d.ts +1 -1
  170. package/dist/react/comments.js +3 -2
  171. package/dist/react/comments.js.map +1 -1
  172. package/dist/react/contents.js +144 -144
  173. package/dist/react/contents.js.map +1 -1
  174. package/dist/react/context.d.ts +1 -1
  175. package/dist/react/context.js.map +1 -1
  176. package/dist/react/detail.d.ts +12 -6
  177. package/dist/react/detail.js +31 -24
  178. package/dist/react/detail.js.map +1 -1
  179. package/dist/react/discovery.d.ts +11 -0
  180. package/dist/react/discovery.js +49 -30
  181. package/dist/react/discovery.js.map +1 -1
  182. package/dist/react/engagement.d.ts +6 -2
  183. package/dist/react/engagement.js +2 -6
  184. package/dist/react/engagement.js.map +1 -1
  185. package/dist/react/folder.d.ts +7 -2
  186. package/dist/react/folder.js +165 -147
  187. package/dist/react/folder.js.map +1 -1
  188. package/dist/react/groups.d.ts +0 -1
  189. package/dist/react/groups.js +13 -12
  190. package/dist/react/groups.js.map +1 -1
  191. package/dist/react/index.d.ts +6 -4
  192. package/dist/react/location.d.ts +11 -0
  193. package/dist/react/location.js +18 -0
  194. package/dist/react/location.js.map +1 -0
  195. package/dist/react/public.d.ts +8 -0
  196. package/dist/react/public.js +32 -27
  197. package/dist/react/public.js.map +1 -1
  198. package/dist/react/stats.d.ts +24 -0
  199. package/dist/react/stats.js +28 -0
  200. package/dist/react/stats.js.map +1 -0
  201. package/dist/react/use-resource.js +8 -7
  202. package/dist/react/use-resource.js.map +1 -1
  203. package/dist/react/versions.d.ts +25 -5
  204. package/dist/react/versions.js +61 -40
  205. package/dist/react/versions.js.map +1 -1
  206. package/dist/react.js +26 -24
  207. package/dist/styles.css +1 -1
  208. package/dist/urls/index.d.ts +35 -10
  209. package/dist/urls.js +100 -70
  210. package/dist/urls.js.map +1 -1
  211. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"comments.js","names":[],"sources":["../../src/react/comments.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { BanScope, Decision, HeldKind, Reaction, Sort, Target } from \"../client/content/types.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { AdminComment, BanInput, BannedWord, BannedWordInput, Comment, CommentBan, CommentStanding, FeedItem, HeldItem, IPBan, IPBanInput, ReactionCounts } from \"../client/generated/wire.js\";\nimport type { Page, ResourceStore } from \"./resources.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\n/** Counts after the caller's reaction changes from counts.mine to value. */\nexport function withReaction(counts: ReactionCounts, value: Reaction): ReactionCounts {\n const likes = counts.likes - (counts.mine === 1 ? 1 : 0) + (value === 1 ? 1 : 0);\n const dislikes = counts.dislikes - (counts.mine === -1 ? 1 : 0) + (value === -1 ? 1 : 0);\n return { likes, dislikes, mine: value };\n}\n\nconst commentLists = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\nconst tokens = new WeakMap<ResourceStore, Map<string, number>>();\n\n/** Sets the caller's reaction to a comment in every list showing it at once; rolls back on failure unless a newer one started. */\nasync function reactToComment(client: ContentKitClient, store: ResourceStore, scope: string, c: Pick<Comment, \"id\" | \"likes\" | \"dislikes\" | \"mine\">, value: Reaction) {\n let t = tokens.get(store);\n if (!t) tokens.set(store, (t = new Map()));\n const token = (t.get(c.id) ?? 0) + 1;\n t.set(c.id, token);\n const reads = new Set<number>();\n const put = (counts: ReactionCounts, rollback = false) =>\n store.patch<Page<Comment>>(\n (tag) => commentLists.includes(tag.type) && tag.readScope === scope,\n (page, readVersion) => {\n if (rollback && !reads.has(readVersion)) return page;\n reads.add(readVersion);\n return { ...page, items: page.items.map((x) => (x.id === c.id ? { ...x, ...counts } : x)) };\n },\n );\n put(withReaction({ likes: c.likes, dislikes: c.dislikes, mine: c.mine }, value));\n try {\n await client.comments.react(c.id, value);\n } catch (e) {\n if (t.get(c.id) === token) put({ likes: c.likes, dislikes: c.dislikes, mine: c.mine }, true);\n throw e;\n }\n}\n\nexport interface UseCommentsOptions {\n sort?: Sort;\n /** One language's thread: the list shows it and new comments join it. */\n language?: string;\n /** Top-level comments per page. Default 20. */\n pageSize?: number;\n client?: ContentKitClient;\n}\n\nexport interface UseComments extends UseList<Comment> {\n /** Comments on the item, or replies to a top-level comment (replyTo); anonymous callers give anonName. */\n post: (body: string, o?: { replyTo?: string; anonName?: string }) => Promise<Comment>;\n edit: (id: string, body: string) => Promise<Comment>;\n /** Leaves a tombstone. */\n remove: (id: string) => Promise<void>;\n /** Sets the caller's reaction at once, rolled back if the server refuses it. */\n react: (comment: Comment, value: Reaction) => Promise<void>;\n /** Restores a deleted comment (moderators). */\n restore: (id: string) => Promise<void>;\n}\n\n/** A target's top-level comments, a page at a time, with every write; new comments and replies land in place. */\nexport function useComments(target: Target, o: UseCommentsOptions = {}): UseComments {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n const sort = o.sort ?? \"newest\";\n const language = o.language || undefined;\n const { type, id, version } = target;\n const key = keyOf(\"comments\", scope, type, id, version, language, sort, size);\n const list = useList(\n store,\n key,\n { type: \"comments\", target: { type, id, version } },\n offsetPages(size, (q, signal) => client.comments.list({ type, id, version }, { ...q, sort, language }, signal)),\n );\n const post = useCallback<UseComments[\"post\"]>(\n (body, p = {}) => client.comments.create({ type, id, version }, { body, reply_to_id: p.replyTo, anon_name: p.anonName, language: p.replyTo ? undefined : language }),\n [client, type, id, version, language],\n );\n const edit = useCallback((cid: string, body: string) => client.comments.edit(cid, body), [client]);\n const remove = useCallback((cid: string) => client.comments.delete(cid), [client]);\n const restore = useCallback((cid: string) => client.comments.restore(cid), [client]);\n const react = useCallback((c: Comment, v: Reaction) => reactToComment(client, store, scope, c, v), [client, store, scope]);\n return { ...list, post, edit, remove, react, restore };\n}\n\n/** A top-level comment's replies, oldest first, a page at a time; enabled false defers the first read. */\nexport function useCommentReplies(id: string, o: { pageSize?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseList<Comment> {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 10;\n const key = o.enabled === false ? null : keyOf(\"replies\", scope, id, size);\n return useList(store, key, { type: \"replies\", id }, offsetPages(size, (q, signal) => client.comments.replies(id, q, signal)));\n}\n\n/** Whether the caller may comment on the target, the ban that stops it, and what it may do to others' comments. */\nexport function useCanComment(target: Target, o: { client?: ContentKitClient } = {}) {\n const { client, store, scope } = useContentScope(o.client);\n const { type, id, version } = target;\n const r = useResource<CommentStanding>(store, keyOf(\"standing\", scope, type, id, version), { type: \"standing\", target: { type, id, version } }, (s) =>\n client.comments.standing({ type, id, version }, s),\n );\n return { standing: r.data ?? null, loading: r.loading || !r.loaded, error: r.error, reload: r.reload };\n}\n\n/** The newest published comments across the site, with their items (the host shows titles). */\nexport function useLatestComments(o: { pageSize?: number; client?: ContentKitClient } = {}): UseList<FeedItem> {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n return useList(store, keyOf(\"latest\", scope, size), { type: \"latest\" }, offsetPages(size, (q, signal) => client.comments.latest(q, signal), true));\n}\n\nexport interface UseAdminComments extends UseList<AdminComment> {\n remove: (id: string) => Promise<void>;\n restore: (id: string) => Promise<void>;\n react: (comment: Comment, value: Reaction) => Promise<void>;\n}\n\n/** Staff: every comment, newest first, deleted, held and rejected ones with their bodies (root:comments:moderate); or one content kind's, or one author's. */\nexport function useAdminComments(o: { contentKind?: string; author?: string; pageSize?: number; client?: ContentKitClient } = {}): UseAdminComments {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 25;\n const kind = o.contentKind || undefined;\n const author = o.author || undefined;\n const list = useList(\n store,\n keyOf(\"admin-comments\", scope, kind, author, size),\n { type: \"admin-comments\" },\n offsetPages(size, (q, signal) => client.comments.adminList({ ...q, contentKind: kind, author }, signal)),\n );\n const remove = useCallback((id: string) => client.comments.delete(id), [client]);\n const restore = useCallback((id: string) => client.comments.restore(id), [client]);\n const react = useCallback((c: Comment, v: Reaction) => reactToComment(client, store, scope, c, v), [client, store, scope]);\n return { ...list, remove, restore, react };\n}\n\nexport interface UseModerationQueue extends UseList<HeldItem> {\n /** Approves (publishes) or rejects at the revision the queue listed; the item leaves the queue. */\n resolve: (item: HeldItem, decision: Decision, reason?: string) => Promise<void>;\n}\n\n/** Staff: held comments or content awaiting review, oldest first (ModerationReview). */\nexport function useModerationQueue(o: { kind?: HeldKind; pageSize?: number; client?: ContentKitClient } = {}): UseModerationQueue {\n const { client, store, scope } = useContentScope(o.client);\n const kind = o.kind ?? \"comment\";\n const size = o.pageSize ?? 20;\n const list = useList<HeldItem>(store, keyOf(\"held\", scope, kind, size), { type: \"held\" }, async (cursor, signal) => {\n const page = await client.moderation.held({ kind, cursor: typeof cursor === \"string\" ? cursor : undefined, limit: size }, signal);\n return { items: page.items, next: page.next ?? null };\n });\n const resolve = useCallback(async (item: HeldItem, d: Decision, reason?: string) => void (await client.moderation.resolve(item, d, reason)), [client]);\n return { ...list, resolve };\n}\n\nexport interface UseCommentBans extends UseList<CommentBan & { id: string }> {\n ban: (user: string, input?: BanInput) => Promise<CommentBan>;\n lift: (user: string) => Promise<void>;\n}\n\n/** The bans of one scope: the caller's own (\"owner\") or the site's (\"global\", CommentBan), newest first. */\nexport function useCommentBans(o: { scope?: BanScope; pageSize?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseCommentBans {\n const { client, store, scope: readScope } = useContentScope(o.client);\n const scope = o.scope ?? \"global\";\n const size = o.pageSize ?? 25;\n const list = useList(\n store,\n o.enabled === false ? null : keyOf(\"bans\", readScope, scope, size),\n { type: \"bans\", scope },\n offsetPages(size, async (q, signal) => (await client.bans.list(scope, q, signal)).map((b) => ({ ...b, id: b.user_id }))),\n );\n const ban = useCallback((user: string, input?: BanInput) => client.bans.ban(scope, user, input), [client, scope]);\n const lift = useCallback((user: string) => client.bans.lift(scope, user), [client, scope]);\n return { ...list, ban, lift };\n}\n\nexport interface UseBannedWords {\n words: BannedWord[];\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans words, or changes what banned ones do. */\n ban: (words: BannedWordInput[]) => Promise<BannedWord[]>;\n unban: (words: string[]) => Promise<void>;\n}\n\n/** Staff: the banned words every comment and answer meets (root:comments:moderate). */\nexport function useBannedWords(o: { enabled?: boolean; client?: ContentKitClient } = {}): UseBannedWords {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<BannedWord[]>(store, o.enabled === false ? null : keyOf(\"moderation-rules\", scope, \"words\"), { type: \"moderation-rules\", scope: \"words\" }, (signal) =>\n client.moderation.bannedWords(signal),\n );\n const ban = useCallback((words: BannedWordInput[]) => client.moderation.banWords(words), [client]);\n const unban = useCallback((words: string[]) => client.moderation.unbanWords(words), [client]);\n return { words: r.data ?? [], loading: o.enabled !== false && (r.loading || !r.loaded), error: r.error, reload: r.reload, ban, unban };\n}\n\nexport interface UseIPBans {\n bans: IPBan[];\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans addresses or networks, or replaces their bans. */\n ban: (bans: IPBanInput[]) => Promise<IPBan[]>;\n lift: (networks: string[]) => Promise<void>;\n}\n\n/** Staff: the IP bans on commenting and answering, newest first (root:comments:moderate). */\nexport function useIPBans(o: { enabled?: boolean; client?: ContentKitClient } = {}): UseIPBans {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<IPBan[]>(store, o.enabled === false ? null : keyOf(\"moderation-rules\", scope, \"ips\"), { type: \"moderation-rules\", scope: \"ips\" }, (signal) =>\n client.moderation.ipBans(signal),\n );\n const ban = useCallback((bans: IPBanInput[]) => client.moderation.banIPs(bans), [client]);\n const lift = useCallback((networks: string[]) => client.moderation.liftIPBans(networks), [client]);\n return { bans: r.data ?? [], loading: o.enabled !== false && (r.loading || !r.loaded), error: r.error, reload: r.reload, ban, lift };\n}\n\nexport interface UseGlobalCommentBan {\n /** The user's site-wide ban, expired or not; null when none. */\n ban: CommentBan | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans them, or replaces their ban. */\n save: (input?: BanInput) => Promise<CommentBan>;\n lift: () => Promise<void>;\n}\n\n/** Staff: one user's site-wide comment ban (root:comments:ban). */\nexport function useGlobalCommentBan(user: string, o: { client?: ContentKitClient } = {}): UseGlobalCommentBan {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<CommentBan | null>(store, keyOf(\"ban\", scope, user), { type: \"bans\", scope: \"global\", id: user }, (signal) => client.bans.get(\"global\", user, signal));\n const save = useCallback((input?: BanInput) => client.bans.ban(\"global\", user, input), [client, user]);\n const lift = useCallback(() => client.bans.lift(\"global\", user), [client, user]);\n return { ban: r.data ?? null, loading: r.loading || !r.loaded, error: r.error, reload: r.reload, save, lift };\n}\n"],"mappings":";;;AASA,SAAgB,EAAa,GAAwB,GAAiC;CAGpF,OAAO;EAAE,OAFK,EAAO,QAAS,IAAO,SAAS,KAAc,QAAU;EAEtD,UADC,EAAO,WAAY,IAAO,SAAS,MAAe,QAAU;EACnD,MAAM;CAAM;AACxC;AAEA,IAAM,IAAe;CAAC;CAAY;CAAW;CAAU;AAAgB,GACjE,oBAAS,IAAI,QAA4C;AAG/D,eAAe,EAAe,GAA0B,GAAsB,GAAe,GAAwD,GAAiB;CACpK,IAAI,IAAI,EAAO,IAAI,CAAK;CACxB,AAAK,KAAG,EAAO,IAAI,GAAQ,oBAAI,IAAI,IAAI,CAAE;CACzC,IAAM,KAAS,EAAE,IAAI,EAAE,EAAE,KAAK,KAAK;CACnC,EAAE,IAAI,EAAE,IAAI,CAAK;CACjB,IAAM,oBAAQ,IAAI,IAAY,GACxB,KAAO,GAAwB,IAAW,OAC9C,EAAM,OACH,MAAQ,EAAa,SAAS,EAAI,IAAI,KAAK,EAAI,cAAc,IAC7D,GAAM,MACD,KAAY,CAAC,EAAM,IAAI,CAAW,IAAU,KAChD,EAAM,IAAI,CAAW,GACd;EAAE,GAAG;EAAM,OAAO,EAAK,MAAM,KAAK,MAAO,EAAE,OAAO,EAAE,KAAK;GAAE,GAAG;GAAG,GAAG;EAAO,IAAI,CAAE;CAAE,EAE9F;CACF,EAAI,EAAa;EAAE,OAAO,EAAE;EAAO,UAAU,EAAE;EAAU,MAAM,EAAE;CAAK,GAAG,CAAK,CAAC;CAC/E,IAAI;EACF,MAAM,EAAO,SAAS,MAAM,EAAE,IAAI,CAAK;CACzC,SAAS,GAAG;EAEV,MADI,EAAE,IAAI,EAAE,EAAE,MAAM,KAAO,EAAI;GAAE,OAAO,EAAE;GAAO,UAAU,EAAE;GAAU,MAAM,EAAE;EAAK,GAAG,EAAI,GACrF;CACR;AACF;AAwBA,SAAgB,EAAY,GAAgB,IAAwB,CAAC,GAAgB;CACnF,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAO,EAAE,YAAY,IACrB,IAAO,EAAE,QAAQ,UACjB,IAAW,EAAE,YAAY,KAAA,GACzB,EAAE,SAAM,OAAI,eAAY,GACxB,IAAM,EAAM,YAAY,GAAO,GAAM,GAAI,GAAS,GAAU,GAAM,CAAI,GACtE,IAAO,EACX,GACA,GACA;EAAE,MAAM;EAAY,QAAQ;GAAE;GAAM;GAAI;EAAQ;CAAE,GAClD,EAAY,IAAO,GAAG,MAAW,EAAO,SAAS,KAAK;EAAE;EAAM;EAAI;CAAQ,GAAG;EAAE,GAAG;EAAG;EAAM;CAAS,GAAG,CAAM,CAAC,CAChH,GACM,IAAO,GACV,GAAM,IAAI,CAAC,MAAM,EAAO,SAAS,OAAO;EAAE;EAAM;EAAI;CAAQ,GAAG;EAAE;EAAM,aAAa,EAAE;EAAS,WAAW,EAAE;EAAU,UAAU,EAAE,UAAU,KAAA,IAAY;CAAS,CAAC,GACnK;EAAC;EAAQ;EAAM;EAAI;EAAS;CAAQ,CACtC,GACM,IAAO,GAAa,GAAa,MAAiB,EAAO,SAAS,KAAK,GAAK,CAAI,GAAG,CAAC,CAAM,CAAC,GAC3F,IAAS,GAAa,MAAgB,EAAO,SAAS,OAAO,CAAG,GAAG,CAAC,CAAM,CAAC,GAC3E,IAAU,GAAa,MAAgB,EAAO,SAAS,QAAQ,CAAG,GAAG,CAAC,CAAM,CAAC,GAC7E,IAAQ,GAAa,GAAY,MAAgB,EAAe,GAAQ,GAAO,GAAO,GAAG,CAAC,GAAG;EAAC;EAAQ;EAAO;CAAK,CAAC;CACzH,OAAO;EAAE,GAAG;EAAM;EAAM;EAAM;EAAQ;EAAO;CAAQ;AACvD;AAGA,SAAgB,EAAkB,GAAY,IAAyE,CAAC,GAAqB;CAC3I,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAO,EAAE,YAAY,IACrB,IAAM,EAAE,YAAY,KAAQ,OAAO,EAAM,WAAW,GAAO,GAAI,CAAI;CACzE,OAAO,EAAQ,GAAO,GAAK;EAAE,MAAM;EAAW;CAAG,GAAG,EAAY,IAAO,GAAG,MAAW,EAAO,SAAS,QAAQ,GAAI,GAAG,CAAM,CAAC,CAAC;AAC9H;AAGA,SAAgB,EAAc,GAAgB,IAAmC,CAAC,GAAG;CACnF,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,EAAE,SAAM,OAAI,eAAY,GACxB,IAAI,EAA6B,GAAO,EAAM,YAAY,GAAO,GAAM,GAAI,CAAO,GAAG;EAAE,MAAM;EAAY,QAAQ;GAAE;GAAM;GAAI;EAAQ;CAAE,IAAI,MAC/I,EAAO,SAAS,SAAS;EAAE;EAAM;EAAI;CAAQ,GAAG,CAAC,CACnD;CACA,OAAO;EAAE,UAAU,EAAE,QAAQ;EAAM,SAAS,EAAE,WAAW,CAAC,EAAE;EAAQ,OAAO,EAAE;EAAO,QAAQ,EAAE;CAAO;AACvG;AAGA,SAAgB,EAAkB,IAAsD,CAAC,GAAsB;CAC7G,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAO,EAAE,YAAY;CAC3B,OAAO,EAAQ,GAAO,EAAM,UAAU,GAAO,CAAI,GAAG,EAAE,MAAM,SAAS,GAAG,EAAY,IAAO,GAAG,MAAW,EAAO,SAAS,OAAO,GAAG,CAAM,GAAG,EAAI,CAAC;AACnJ;AASA,SAAgB,EAAiB,IAA6F,CAAC,GAAqB;CAClJ,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAO,EAAE,YAAY,IACrB,IAAO,EAAE,eAAe,KAAA,GACxB,IAAS,EAAE,UAAU,KAAA,GACrB,IAAO,EACX,GACA,EAAM,kBAAkB,GAAO,GAAM,GAAQ,CAAI,GACjD,EAAE,MAAM,iBAAiB,GACzB,EAAY,IAAO,GAAG,MAAW,EAAO,SAAS,UAAU;EAAE,GAAG;EAAG,aAAa;EAAM;CAAO,GAAG,CAAM,CAAC,CACzG,GACM,IAAS,GAAa,MAAe,EAAO,SAAS,OAAO,CAAE,GAAG,CAAC,CAAM,CAAC,GACzE,IAAU,GAAa,MAAe,EAAO,SAAS,QAAQ,CAAE,GAAG,CAAC,CAAM,CAAC,GAC3E,IAAQ,GAAa,GAAY,MAAgB,EAAe,GAAQ,GAAO,GAAO,GAAG,CAAC,GAAG;EAAC;EAAQ;EAAO;CAAK,CAAC;CACzH,OAAO;EAAE,GAAG;EAAM;EAAQ;EAAS;CAAM;AAC3C;AAQA,SAAgB,EAAmB,IAAuE,CAAC,GAAuB;CAChI,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAO,EAAE,QAAQ,WACjB,IAAO,EAAE,YAAY,IACrB,IAAO,EAAkB,GAAO,EAAM,QAAQ,GAAO,GAAM,CAAI,GAAG,EAAE,MAAM,OAAO,GAAG,OAAO,GAAQ,MAAW;EAClH,IAAM,IAAO,MAAM,EAAO,WAAW,KAAK;GAAE;GAAM,QAAQ,OAAO,KAAW,WAAW,IAAS,KAAA;GAAW,OAAO;EAAK,GAAG,CAAM;EAChI,OAAO;GAAE,OAAO,EAAK;GAAO,MAAM,EAAK,QAAQ;EAAK;CACtD,CAAC,GACK,IAAU,EAAY,OAAO,GAAgB,GAAa,MAAoB,KAAM,MAAM,EAAO,WAAW,QAAQ,GAAM,GAAG,CAAM,GAAI,CAAC,CAAM,CAAC;CACrJ,OAAO;EAAE,GAAG;EAAM;CAAQ;AAC5B;AAQA,SAAgB,EAAe,IAA2F,CAAC,GAAmB;CAC5I,IAAM,EAAE,WAAQ,UAAO,OAAO,MAAc,EAAgB,EAAE,MAAM,GAC9D,IAAQ,EAAE,SAAS,UACnB,IAAO,EAAE,YAAY,IACrB,IAAO,EACX,GACA,EAAE,YAAY,KAAQ,OAAO,EAAM,QAAQ,GAAW,GAAO,CAAI,GACjE;EAAE,MAAM;EAAQ;CAAM,GACtB,EAAY,GAAM,OAAO,GAAG,OAAY,MAAM,EAAO,KAAK,KAAK,GAAO,GAAG,CAAM,EAAA,CAAG,KAAK,OAAO;EAAE,GAAG;EAAG,IAAI,EAAE;CAAQ,EAAE,CAAC,CACzH,GACM,IAAM,GAAa,GAAc,MAAqB,EAAO,KAAK,IAAI,GAAO,GAAM,CAAK,GAAG,CAAC,GAAQ,CAAK,CAAC,GAC1G,IAAO,GAAa,MAAiB,EAAO,KAAK,KAAK,GAAO,CAAI,GAAG,CAAC,GAAQ,CAAK,CAAC;CACzF,OAAO;EAAE,GAAG;EAAM;EAAK;CAAK;AAC9B;AAaA,SAAgB,EAAe,IAAsD,CAAC,GAAmB;CACvG,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAI,EAA0B,GAAO,EAAE,YAAY,KAAQ,OAAO,EAAM,oBAAoB,GAAO,OAAO,GAAG;EAAE,MAAM;EAAoB,OAAO;CAAQ,IAAI,MAChK,EAAO,WAAW,YAAY,CAAM,CACtC,GACM,IAAM,GAAa,MAA6B,EAAO,WAAW,SAAS,CAAK,GAAG,CAAC,CAAM,CAAC,GAC3F,IAAQ,GAAa,MAAoB,EAAO,WAAW,WAAW,CAAK,GAAG,CAAC,CAAM,CAAC;CAC5F,OAAO;EAAE,OAAO,EAAE,QAAQ,CAAC;EAAG,SAAS,EAAE,YAAY,OAAU,EAAE,WAAW,CAAC,EAAE;EAAS,OAAO,EAAE;EAAO,QAAQ,EAAE;EAAQ;EAAK;CAAM;AACvI;AAaA,SAAgB,EAAU,IAAsD,CAAC,GAAc;CAC7F,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAI,EAAqB,GAAO,EAAE,YAAY,KAAQ,OAAO,EAAM,oBAAoB,GAAO,KAAK,GAAG;EAAE,MAAM;EAAoB,OAAO;CAAM,IAAI,MACvJ,EAAO,WAAW,OAAO,CAAM,CACjC,GACM,IAAM,GAAa,MAAuB,EAAO,WAAW,OAAO,CAAI,GAAG,CAAC,CAAM,CAAC,GAClF,IAAO,GAAa,MAAuB,EAAO,WAAW,WAAW,CAAQ,GAAG,CAAC,CAAM,CAAC;CACjG,OAAO;EAAE,MAAM,EAAE,QAAQ,CAAC;EAAG,SAAS,EAAE,YAAY,OAAU,EAAE,WAAW,CAAC,EAAE;EAAS,OAAO,EAAE;EAAO,QAAQ,EAAE;EAAQ;EAAK;CAAK;AACrI;AAcA,SAAgB,EAAoB,GAAc,IAAmC,CAAC,GAAwB;CAC5G,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAI,EAA+B,GAAO,EAAM,OAAO,GAAO,CAAI,GAAG;EAAE,MAAM;EAAQ,OAAO;EAAU,IAAI;CAAK,IAAI,MAAW,EAAO,KAAK,IAAI,UAAU,GAAM,CAAM,CAAC,GACrK,IAAO,GAAa,MAAqB,EAAO,KAAK,IAAI,UAAU,GAAM,CAAK,GAAG,CAAC,GAAQ,CAAI,CAAC,GAC/F,IAAO,QAAkB,EAAO,KAAK,KAAK,UAAU,CAAI,GAAG,CAAC,GAAQ,CAAI,CAAC;CAC/E,OAAO;EAAE,KAAK,EAAE,QAAQ;EAAM,SAAS,EAAE,WAAW,CAAC,EAAE;EAAQ,OAAO,EAAE;EAAO,QAAQ,EAAE;EAAQ;EAAM;CAAK;AAC9G"}
1
+ {"version":3,"file":"comments.js","names":[],"sources":["../../src/react/comments.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { BanScope, Decision, HeldKind, Reaction, Sort, Target } from \"../client/content/types.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { AdminComment, BanInput, BannedWord, BannedWordInput, Comment, CommentBan, CommentStanding, FeedItem, HeldItem, IPBan, IPBanInput, ReactionCounts } from \"../client/generated/wire.js\";\nimport type { Page, ResourceStore } from \"./resources.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\n/** Counts after the caller's reaction changes from counts.mine to value; a hidden tally stays hidden. */\nexport function withReaction(counts: ReactionCounts, value: Reaction): ReactionCounts {\n const move = (n: number | undefined, from: boolean, to: boolean) => (n === undefined ? undefined : n - (from ? 1 : 0) + (to ? 1 : 0));\n return { likes: move(counts.likes, counts.mine === 1, value === 1), dislikes: move(counts.dislikes, counts.mine === -1, value === -1), mine: value };\n}\n\nconst commentLists = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\nconst tokens = new WeakMap<ResourceStore, Map<string, number>>();\n\n/** Sets the caller's reaction to a comment in every list showing it at once; rolls back on failure unless a newer one started. */\nasync function reactToComment(client: ContentKitClient, store: ResourceStore, scope: string, c: Pick<Comment, \"id\" | \"likes\" | \"dislikes\" | \"mine\">, value: Reaction) {\n let t = tokens.get(store);\n if (!t) tokens.set(store, (t = new Map()));\n const token = (t.get(c.id) ?? 0) + 1;\n t.set(c.id, token);\n const reads = new Set<number>();\n const put = (counts: ReactionCounts, rollback = false) =>\n store.patch<Page<Comment>>(\n (tag) => commentLists.includes(tag.type) && tag.readScope === scope,\n (page, readVersion) => {\n if (rollback && !reads.has(readVersion)) return page;\n reads.add(readVersion);\n return { ...page, items: page.items.map((x) => (x.id === c.id ? { ...x, ...counts } : x)) };\n },\n );\n put(withReaction({ likes: c.likes, dislikes: c.dislikes, mine: c.mine }, value));\n try {\n await client.comments.react(c.id, value);\n } catch (e) {\n if (t.get(c.id) === token) put({ likes: c.likes, dislikes: c.dislikes, mine: c.mine }, true);\n throw e;\n }\n}\n\nexport interface UseCommentsOptions {\n sort?: Sort;\n /** One language's thread: the list shows it and new comments join it. */\n language?: string;\n /** Top-level comments per page. Default 20. */\n pageSize?: number;\n client?: ContentKitClient;\n}\n\nexport interface UseComments extends UseList<Comment> {\n /** Comments on the item, or replies to a top-level comment (replyTo); anonymous callers give anonName. */\n post: (body: string, o?: { replyTo?: string; anonName?: string }) => Promise<Comment>;\n edit: (id: string, body: string) => Promise<Comment>;\n /** Leaves a tombstone. */\n remove: (id: string) => Promise<void>;\n /** Sets the caller's reaction at once, rolled back if the server refuses it. */\n react: (comment: Comment, value: Reaction) => Promise<void>;\n /** Restores a deleted comment (moderators). */\n restore: (id: string) => Promise<void>;\n}\n\n/** A target's top-level comments, a page at a time, with every write; new comments and replies land in place. */\nexport function useComments(target: Target, o: UseCommentsOptions = {}): UseComments {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n const sort = o.sort ?? \"newest\";\n const language = o.language || undefined;\n const { type, id, version } = target;\n const key = keyOf(\"comments\", scope, type, id, version, language, sort, size);\n const list = useList(\n store,\n key,\n { type: \"comments\", target: { type, id, version } },\n offsetPages(size, (q, signal) => client.comments.list({ type, id, version }, { ...q, sort, language }, signal)),\n );\n const post = useCallback<UseComments[\"post\"]>(\n (body, p = {}) => client.comments.create({ type, id, version }, { body, reply_to_id: p.replyTo, anon_name: p.anonName, language: p.replyTo ? undefined : language }),\n [client, type, id, version, language],\n );\n const edit = useCallback((cid: string, body: string) => client.comments.edit(cid, body), [client]);\n const remove = useCallback((cid: string) => client.comments.delete(cid), [client]);\n const restore = useCallback((cid: string) => client.comments.restore(cid), [client]);\n const react = useCallback((c: Comment, v: Reaction) => reactToComment(client, store, scope, c, v), [client, store, scope]);\n return { ...list, post, edit, remove, react, restore };\n}\n\n/** A top-level comment's replies, oldest first, a page at a time; enabled false defers the first read. */\nexport function useCommentReplies(id: string, o: { pageSize?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseList<Comment> {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 10;\n const key = o.enabled === false ? null : keyOf(\"replies\", scope, id, size);\n return useList(store, key, { type: \"replies\", id }, offsetPages(size, (q, signal) => client.comments.replies(id, q, signal)));\n}\n\n/** Whether the caller may comment on the target, the ban that stops it, and what it may do to others' comments. */\nexport function useCanComment(target: Target, o: { client?: ContentKitClient } = {}) {\n const { client, store, scope } = useContentScope(o.client);\n const { type, id, version } = target;\n const r = useResource<CommentStanding>(store, keyOf(\"standing\", scope, type, id, version), { type: \"standing\", target: { type, id, version } }, (s) =>\n client.comments.standing({ type, id, version }, s),\n );\n return { standing: r.data ?? null, loading: r.loading || !r.loaded, error: r.error, reload: r.reload };\n}\n\n/** The newest published comments across the site, with their items (the host shows titles). */\nexport function useLatestComments(o: { pageSize?: number; client?: ContentKitClient } = {}): UseList<FeedItem> {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n return useList(store, keyOf(\"latest\", scope, size), { type: \"latest\" }, offsetPages(size, (q, signal) => client.comments.latest(q, signal), true));\n}\n\nexport interface UseAdminComments extends UseList<AdminComment> {\n remove: (id: string) => Promise<void>;\n restore: (id: string) => Promise<void>;\n react: (comment: Comment, value: Reaction) => Promise<void>;\n}\n\n/** Staff: every comment, newest first, deleted, held and rejected ones with their bodies (root:comments:moderate); or one content kind's, or one author's. */\nexport function useAdminComments(o: { contentKind?: string; author?: string; pageSize?: number; client?: ContentKitClient } = {}): UseAdminComments {\n const { client, store, scope } = useContentScope(o.client);\n const size = o.pageSize ?? 25;\n const kind = o.contentKind || undefined;\n const author = o.author || undefined;\n const list = useList(\n store,\n keyOf(\"admin-comments\", scope, kind, author, size),\n { type: \"admin-comments\" },\n offsetPages(size, (q, signal) => client.comments.adminList({ ...q, contentKind: kind, author }, signal)),\n );\n const remove = useCallback((id: string) => client.comments.delete(id), [client]);\n const restore = useCallback((id: string) => client.comments.restore(id), [client]);\n const react = useCallback((c: Comment, v: Reaction) => reactToComment(client, store, scope, c, v), [client, store, scope]);\n return { ...list, remove, restore, react };\n}\n\nexport interface UseModerationQueue extends UseList<HeldItem> {\n /** Approves (publishes) or rejects at the revision the queue listed; the item leaves the queue. */\n resolve: (item: HeldItem, decision: Decision, reason?: string) => Promise<void>;\n}\n\n/** Staff: held comments or content awaiting review, oldest first (ModerationReview). */\nexport function useModerationQueue(o: { kind?: HeldKind; pageSize?: number; client?: ContentKitClient } = {}): UseModerationQueue {\n const { client, store, scope } = useContentScope(o.client);\n const kind = o.kind ?? \"comment\";\n const size = o.pageSize ?? 20;\n const list = useList<HeldItem>(store, keyOf(\"held\", scope, kind, size), { type: \"held\" }, async (cursor, signal) => {\n const page = await client.moderation.held({ kind, cursor: typeof cursor === \"string\" ? cursor : undefined, limit: size }, signal);\n return { items: page.items, next: page.next ?? null };\n });\n const resolve = useCallback(async (item: HeldItem, d: Decision, reason?: string) => void (await client.moderation.resolve(item, d, reason)), [client]);\n return { ...list, resolve };\n}\n\nexport interface UseCommentBans extends UseList<CommentBan & { id: string }> {\n ban: (user: string, input?: BanInput) => Promise<CommentBan>;\n lift: (user: string) => Promise<void>;\n}\n\n/** The bans of one scope: the caller's own (\"owner\") or the site's (\"global\", CommentBan), newest first. */\nexport function useCommentBans(o: { scope?: BanScope; pageSize?: number; enabled?: boolean; client?: ContentKitClient } = {}): UseCommentBans {\n const { client, store, scope: readScope } = useContentScope(o.client);\n const scope = o.scope ?? \"global\";\n const size = o.pageSize ?? 25;\n const list = useList(\n store,\n o.enabled === false ? null : keyOf(\"bans\", readScope, scope, size),\n { type: \"bans\", scope },\n offsetPages(size, async (q, signal) => (await client.bans.list(scope, q, signal)).map((b) => ({ ...b, id: b.user_id }))),\n );\n const ban = useCallback((user: string, input?: BanInput) => client.bans.ban(scope, user, input), [client, scope]);\n const lift = useCallback((user: string) => client.bans.lift(scope, user), [client, scope]);\n return { ...list, ban, lift };\n}\n\nexport interface UseBannedWords {\n words: BannedWord[];\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans words, or changes what banned ones do. */\n ban: (words: BannedWordInput[]) => Promise<BannedWord[]>;\n unban: (words: string[]) => Promise<void>;\n}\n\n/** Staff: the banned words every comment and answer meets (root:comments:moderate). */\nexport function useBannedWords(o: { enabled?: boolean; client?: ContentKitClient } = {}): UseBannedWords {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<BannedWord[]>(store, o.enabled === false ? null : keyOf(\"moderation-rules\", scope, \"words\"), { type: \"moderation-rules\", scope: \"words\" }, (signal) =>\n client.moderation.bannedWords(signal),\n );\n const ban = useCallback((words: BannedWordInput[]) => client.moderation.banWords(words), [client]);\n const unban = useCallback((words: string[]) => client.moderation.unbanWords(words), [client]);\n return { words: r.data ?? [], loading: o.enabled !== false && (r.loading || !r.loaded), error: r.error, reload: r.reload, ban, unban };\n}\n\nexport interface UseIPBans {\n bans: IPBan[];\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans addresses or networks, or replaces their bans. */\n ban: (bans: IPBanInput[]) => Promise<IPBan[]>;\n lift: (networks: string[]) => Promise<void>;\n}\n\n/** Staff: the IP bans on commenting and answering, newest first (root:comments:moderate). */\nexport function useIPBans(o: { enabled?: boolean; client?: ContentKitClient } = {}): UseIPBans {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<IPBan[]>(store, o.enabled === false ? null : keyOf(\"moderation-rules\", scope, \"ips\"), { type: \"moderation-rules\", scope: \"ips\" }, (signal) =>\n client.moderation.ipBans(signal),\n );\n const ban = useCallback((bans: IPBanInput[]) => client.moderation.banIPs(bans), [client]);\n const lift = useCallback((networks: string[]) => client.moderation.liftIPBans(networks), [client]);\n return { bans: r.data ?? [], loading: o.enabled !== false && (r.loading || !r.loaded), error: r.error, reload: r.reload, ban, lift };\n}\n\nexport interface UseGlobalCommentBan {\n /** The user's site-wide ban, expired or not; null when none. */\n ban: CommentBan | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Bans them, or replaces their ban. */\n save: (input?: BanInput) => Promise<CommentBan>;\n lift: () => Promise<void>;\n}\n\n/** Staff: one user's site-wide comment ban (root:comments:ban). */\nexport function useGlobalCommentBan(user: string, o: { client?: ContentKitClient } = {}): UseGlobalCommentBan {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<CommentBan | null>(store, keyOf(\"ban\", scope, user), { type: \"bans\", scope: \"global\", id: user }, (signal) => client.bans.get(\"global\", user, signal));\n const save = useCallback((input?: BanInput) => client.bans.ban(\"global\", user, input), [client, user]);\n const lift = useCallback(() => client.bans.lift(\"global\", user), [client, user]);\n return { ban: r.data ?? null, loading: r.loading || !r.loaded, error: r.error, reload: r.reload, save, lift };\n}\n"],"mappings":"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"}
@@ -1,214 +1,214 @@
1
1
  import { useGuarded as e } from "./context.js";
2
- import { useMediaRead as t } from "./read.js";
3
- import { keyOf as n, offsetPages as r, useContentScope as i, useList as a, useResource as o } from "./use-resource.js";
2
+ import { keyOf as t, offsetPages as n, useContentScope as r, useList as i, useResource as a } from "./use-resource.js";
3
+ import { useMediaRead as o } from "./read.js";
4
4
  import { BODY_SUFFIX as s, IMAGE_SCHEME as c, imageRef as l, imageRefs as u } from "../client/contents.js";
5
5
  import { useCallback as d, useEffect as f, useMemo as p, useRef as m, useState as h } from "react";
6
6
  //#region src/react/contents.ts
7
- function g(e = {}, t = {}) {
8
- let { client: o, store: s, scope: c } = i(t.client), { pageSize: l, ...u } = e, d = l ?? 20;
9
- return a(s, n("contents", c, u.kind, u.state, u.owner_group, u.q, u.language, d), {
7
+ function g(e = {}, a = {}) {
8
+ let { client: o, store: s, scope: c } = r(a.client), { pageSize: l, ...u } = e, d = l ?? 20;
9
+ return i(s, t("contents", c, u.kind, u.state, u.owner_group, u.q, u.language, d), {
10
10
  type: "contents",
11
11
  scope: u.state === "deleted" ? "deleted" : void 0
12
- }, r(d, (e, t) => o.contents.manage({
12
+ }, n(d, (e, t) => o.contents.manage({
13
13
  ...u,
14
14
  ...e
15
15
  }, t)));
16
16
  }
17
- var _ = (e) => [...new Set([...(e ?? "").matchAll(u())].map((e) => e[1]))], v = (e) => {
17
+ var ee = (e) => [...new Set([...(e ?? "").matchAll(u())].map((e) => e[1]))], _ = (e) => {
18
18
  let t = e.slice(e.lastIndexOf("/") + 1), n = t.lastIndexOf(".");
19
19
  return n > 0 ? t.slice(0, n) : t;
20
20
  };
21
- function y(r, a, g, y = {}) {
22
- let { client: b, store: ee, scope: x } = i(y.client), [te, ne] = h(null), S = r ?? te, C = S ? n("content", x, S) : null, w = o(ee, C, {
21
+ function v(n, i, g, v = {}) {
22
+ let { client: y, store: te, scope: ne } = r(v.client), [b, re] = h(null), x = n ?? b, S = x ? t("content", ne, x) : null, C = a(te, S, {
23
23
  type: "content",
24
- id: S ?? void 0
25
- }, (e) => b.contents.get(S, g, e), { initial: y.initial }), T = w.data ?? null, [re, E] = h(0), D = e(), O = d(async (e) => {
26
- E((e) => e + 1);
24
+ id: x ?? void 0
25
+ }, (e) => y.contents.get(x, g, e), { initial: v.initial }), w = C.data ?? null, [ie, T] = h(0), E = e(), D = d(async (e) => {
26
+ T((e) => e + 1);
27
27
  try {
28
- return await D(e);
28
+ return await E(e);
29
29
  } finally {
30
- E((e) => e - 1);
30
+ T((e) => e - 1);
31
31
  }
32
- }, [D]), k = d(() => {
33
- if (!S) throw Error("contentkit-ui: create the item first");
34
- return S;
35
- }, [S]), A = T?.text?.[g], j = A?.body, M = T?.cover, [N, ie] = h(/* @__PURE__ */ new Map()), P = m([]);
32
+ }, [E]), O = d(() => {
33
+ if (!x) throw Error("contentkit-ui: create the item first");
34
+ return x;
35
+ }, [x]), k = w?.text?.[g], A = k?.body, j = w?.cover, [M, ae] = h(/* @__PURE__ */ new Map()), N = m([]);
36
36
  f(() => {
37
- let e = P.current;
37
+ let e = N.current;
38
38
  return () => {
39
39
  for (let t of e) URL.revokeObjectURL(t);
40
40
  };
41
41
  }, []);
42
- let F = p(() => new Map(Object.entries(T?.images ?? {})), [T?.images]), I = !!M && !M.url && !N.has(M.image) && !F.has(M.image), L = p(() => _(j).filter((e) => !N.has(e) && !F.has(e)), [
43
- j,
44
- N,
45
- F
46
- ]), R = p(() => I ? [...L, M.image] : L, [
42
+ let P = p(() => new Map(Object.entries(w?.images ?? {})), [w?.images]), F = !!j && !j.url && !M.has(j.image) && !P.has(j.image), I = p(() => ee(A).filter((e) => !M.has(e) && !P.has(e)), [
43
+ A,
44
+ M,
45
+ P
46
+ ]), L = p(() => F ? [...I, j.image] : I, [
47
+ F,
47
48
  I,
48
- L,
49
- M
50
- ]), z = p(() => ({
51
- kind: a,
52
- id: S ?? "none"
53
- }), [a, S]), B = p(() => ({
54
- kind: a + s,
55
- id: S ?? "none"
56
- }), [a, S]), V = t(z, {
49
+ j
50
+ ]), R = p(() => ({
51
+ kind: i,
52
+ id: x ?? "none"
53
+ }), [i, x]), oe = p(() => ({
54
+ kind: i + s,
55
+ id: x ?? "none"
56
+ }), [i, x]), z = o(R, {
57
57
  editor: !0,
58
- read: I ? void 0 : null,
59
- client: b
60
- }), H = t(B, {
58
+ read: F ? void 0 : null,
59
+ client: y
60
+ }), B = o(oe, {
61
61
  editor: !0,
62
- read: L.length ? void 0 : null,
63
- client: b
64
- }), U = p(() => [...V.read?.files ?? [], ...H.read?.files ?? []], [V.read, H.read]), W = p(() => {
62
+ read: I.length ? void 0 : null,
63
+ client: y
64
+ }), V = p(() => [...z.read?.files ?? [], ...B.read?.files ?? []], [z.read, B.read]), H = p(() => {
65
65
  let e = /* @__PURE__ */ new Map();
66
- for (let t of U) t.upload && t.editor_url && e.set(v(t.path), t.editor_url);
66
+ for (let t of V) t.upload && t.editor_url && e.set(_(t.path), t.editor_url);
67
67
  return e;
68
- }, [U]), ae = (e) => U.some((t) => t.failed && v(t.path) === e), G = (!!V.read || !!H.read) && R.some((e) => !W.has(e) && !ae(e)), K = m(0), q = V.reload, J = H.reload, Y = d(() => {
69
- q(), J();
70
- }, [q, J]);
68
+ }, [V]), U = (e) => V.some((t) => t.failed && _(t.path) === e), W = (!!z.read || !!B.read) && L.some((e) => !H.has(e) && !U(e)), G = m(0), K = z.reload, q = B.reload, J = d(() => {
69
+ K(), q();
70
+ }, [K, q]);
71
71
  f(() => {
72
- if (!G) {
73
- K.current = 0;
72
+ if (!W) {
73
+ G.current = 0;
74
74
  return;
75
75
  }
76
- if (K.current >= 6) return;
76
+ if (G.current >= 6) return;
77
77
  let e = setTimeout(() => {
78
- K.current++, Y();
79
- }, 1e3 * 2 ** K.current);
78
+ G.current++, J();
79
+ }, 1e3 * 2 ** G.current);
80
80
  return () => clearTimeout(e);
81
81
  }, [
82
- G,
83
- U,
84
- Y
82
+ W,
83
+ V,
84
+ J
85
85
  ]);
86
- let X = m(/* @__PURE__ */ new Map());
86
+ let Y = m(/* @__PURE__ */ new Map());
87
87
  f(() => {
88
88
  for (let e of [
89
- N,
90
- F,
91
- W
92
- ]) for (let [t, n] of e) X.current.set(n, t);
89
+ M,
90
+ P,
91
+ H
92
+ ]) for (let [t, n] of e) Y.current.set(n, t);
93
93
  }, [
94
- N,
95
- F,
96
- W
94
+ M,
95
+ P,
96
+ H
97
97
  ]);
98
- let Z = d((e) => {
98
+ let X = d((e) => {
99
99
  if (!e) return;
100
100
  let t = e.startsWith("contentkit:") ? e.slice(c.length) : e;
101
- return N.get(t) ?? F.get(t) ?? W.get(t) ?? e;
101
+ return M.get(t) ?? P.get(t) ?? H.get(t) ?? e;
102
102
  }, [
103
- N,
104
- F,
105
- W
106
- ]), oe = p(() => A ? {
107
- ...A,
108
- body: (A.body ?? "").replace(u(), (e, t) => N.get(t) ?? F.get(t) ?? W.get(t) ?? e)
103
+ M,
104
+ P,
105
+ H
106
+ ]), se = p(() => k ? {
107
+ ...k,
108
+ body: (k.body ?? "").replace(u(), (e, t) => M.get(t) ?? P.get(t) ?? H.get(t) ?? e)
109
109
  } : void 0, [
110
- A,
111
- N,
112
- F,
113
- W
114
- ]), Q = d((e) => {
115
- let t = new Map(X.current);
110
+ k,
111
+ M,
112
+ P,
113
+ H
114
+ ]), Z = d((e) => {
115
+ let t = new Map(Y.current);
116
116
  for (let e of [
117
- N,
118
- F,
119
- W
117
+ M,
118
+ P,
119
+ H
120
120
  ]) for (let [n, r] of e) t.set(r, n);
121
121
  let n = e;
122
122
  for (let [e, r] of t) n = n.split(e).join(l(r)).split(e.replaceAll("&", "&amp;")).join(l(r));
123
123
  return n;
124
124
  }, [
125
- N,
126
- F,
127
- W
128
- ]), $ = d((e, t) => {
125
+ M,
126
+ P,
127
+ H
128
+ ]), Q = d((e, t) => {
129
129
  if (typeof URL.createObjectURL != "function") return;
130
130
  let n = URL.createObjectURL(t);
131
- return P.current.push(n), ie((t) => new Map(t).set(e, n)), n;
132
- }, []), se = d((e) => O(async () => {
133
- let t = await b.contents.create(e);
134
- return ne(t.id), t;
135
- }), [b, O]), ce = d((e) => O(() => b.contents.update(k(), e)), [
136
- b,
137
- O,
138
- k
139
- ]), le = d((e) => O(() => b.contents.putText(k(), g, e.body === void 0 ? e : {
131
+ return N.current.push(n), ae((t) => new Map(t).set(e, n)), n;
132
+ }, []), ce = d((e) => D(async () => {
133
+ let t = await y.contents.create(e);
134
+ return re(t.id), t;
135
+ }), [y, D]), le = d((e) => D(() => y.contents.update(O(), e)), [
136
+ y,
137
+ D,
138
+ O
139
+ ]), ue = d((e) => D(() => y.contents.putText(O(), g, e.body === void 0 ? e : {
140
140
  ...e,
141
- body: Q(e.body)
141
+ body: Z(e.body)
142
142
  })), [
143
- b,
143
+ y,
144
+ D,
144
145
  O,
145
- k,
146
146
  g,
147
- Q
148
- ]), ue = d((e) => O(() => b.contents.publish(k(), e)), [
149
- b,
150
- O,
151
- k
152
- ]), de = d(() => O(() => b.contents.unpublish(k())), [
153
- b,
154
- O,
155
- k
156
- ]), fe = d(() => O(() => b.contents.remove(k())), [
157
- b,
158
- O,
159
- k
160
- ]), pe = d(() => O(() => b.contents.restore(k())), [
161
- b,
162
- O,
163
- k
164
- ]), me = d((e) => O(async () => {
165
- if (!e) return await b.contents.setCover(k(), null), null;
166
- let t = await b.media.uploadNamed({
167
- kind: a,
168
- id: k()
169
- }, e), n = await b.contents.setCover(k(), t.name), r = $(t.name, e) ?? n.cover?.url ?? null;
170
- return r && X.current.set(r, t.name), r;
147
+ Z
148
+ ]), $ = d((e) => D(() => y.contents.publish(O(), e)), [
149
+ y,
150
+ D,
151
+ O
152
+ ]), de = d(() => D(() => y.contents.unpublish(O())), [
153
+ y,
154
+ D,
155
+ O
156
+ ]), fe = d(() => D(() => y.contents.remove(O())), [
157
+ y,
158
+ D,
159
+ O
160
+ ]), pe = d(() => D(() => y.contents.restore(O())), [
161
+ y,
162
+ D,
163
+ O
164
+ ]), me = d((e) => D(async () => {
165
+ if (!e) return await y.contents.setCover(O(), null), null;
166
+ let t = await y.media.uploadNamed({
167
+ kind: i,
168
+ id: O()
169
+ }, e), n = await y.contents.setCover(O(), t.name), r = Q(t.name, e) ?? n.cover?.url ?? null;
170
+ return r && Y.current.set(r, t.name), r;
171
171
  }), [
172
- b,
172
+ y,
173
+ D,
173
174
  O,
174
- k,
175
- a,
176
- $
177
- ]), he = d((e) => O(async () => {
178
- let t = await b.media.uploadInline({
179
- kind: a + s,
180
- id: k()
181
- }, e), n = $(t.name, e) ?? t.url ?? t.ref;
182
- return X.current.set(n, t.name), n;
175
+ i,
176
+ Q
177
+ ]), he = d((e) => D(async () => {
178
+ let t = await y.media.uploadInline({
179
+ kind: i + s,
180
+ id: O()
181
+ }, e), n = Q(t.name, e) ?? t.url ?? t.ref;
182
+ return Y.current.set(n, t.name), n;
183
183
  }), [
184
- b,
184
+ y,
185
+ D,
185
186
  O,
186
- k,
187
- a,
188
- $
187
+ i,
188
+ Q
189
189
  ]);
190
190
  return {
191
- content: T,
192
- reload: w.reload,
193
- loading: !!C && (w.loading || !w.loaded && y.initial === void 0),
194
- error: w.error,
195
- saving: re > 0,
196
- create: se,
197
- update: ce,
198
- putText: le,
199
- publish: ue,
191
+ content: w,
192
+ reload: C.reload,
193
+ loading: !!S && (C.loading || !C.loaded && v.initial === void 0),
194
+ error: C.error,
195
+ saving: ie > 0,
196
+ create: ce,
197
+ update: le,
198
+ putText: ue,
199
+ publish: $,
200
200
  unpublish: de,
201
201
  remove: fe,
202
202
  restore: pe,
203
203
  setCover: me,
204
204
  uploadImage: he,
205
- imageSrc: Z,
206
- coverSrc: Z(M?.image),
207
- text: oe,
208
- storedBody: Q
205
+ imageSrc: X,
206
+ coverSrc: X(j?.image),
207
+ text: se,
208
+ storedBody: Z
209
209
  };
210
210
  }
211
211
  //#endregion
212
- export { y as useContent, g as useManagedContents };
212
+ export { v as useContent, g as useManagedContents };
213
213
 
214
214
  //# sourceMappingURL=contents.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contents.js","names":[],"sources":["../../src/react/contents.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 { CatalogText, Content, ContentInput, ContentPatch } from \"../client/generated/wire.js\";\nimport { BODY_SUFFIX, IMAGE_SCHEME, imageRef, imageRefs, type ManageQuery } from \"../client/contents.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { useGuarded } from \"./context.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface ManageFilter extends Omit<ManageQuery, \"limit\" | \"offset\"> {\n pageSize?: number;\n}\n\n/** The content items the caller manages, newest first, a page at a time. */\nexport function useManagedContents(f: ManageFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Content> {\n const { client, store, scope } = useContentScope(o.client);\n const { pageSize, ...q } = f;\n const size = pageSize ?? 20;\n return useList(\n store,\n keyOf(\"contents\", scope, q.kind, q.state, q.owner_group, q.q, q.language, size),\n { type: \"contents\", scope: q.state === \"deleted\" ? \"deleted\" : undefined },\n offsetPages(size, (page, signal) => client.contents.manage({ ...q, ...page }, signal)),\n );\n}\n\nexport interface UseContent {\n /** null before create (or while loading). */\n content: Content | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Reads the item again. */\n reload: () => void;\n /** Creates the item as a draft; the hook then edits it. */\n create: (input: ContentInput) => Promise<Content>;\n update: (patch: ContentPatch) => Promise<Content>;\n /** Sets the text in the hook's language; a body from the editor is stored with image references (storedBody). */\n putText: (text: CatalogText) => Promise<Content>;\n /** Publishes now, or schedules for at. */\n publish: (at?: string) => Promise<Content>;\n unpublish: () => Promise<Content>;\n remove: () => Promise<void>;\n restore: () => Promise<Content>;\n /** Uploads the cover (null clears it); resolves with a URL that shows it now. */\n setCover: (file: Blob | null) => Promise<string | null>;\n /** Uploads an image for the body; resolves with a URL that shows it now, to place in the editor. */\n uploadImage: (file: Blob) => Promise<string>;\n /**\n * A URL that shows one of the item's images now, by name (\"i-{uuid}\") or\n * reference: a fresh upload from its file, else its public file\n * (`content.images`), else, while it has none (an unpublished item's), its\n * signed editor view from an editor read. Anything else is returned as is.\n */\n imageSrc: (image: string | null | undefined) => string | undefined;\n /** The cover through imageSrc. */\n coverSrc: string | undefined;\n /** The language's stored text, its body's images through imageSrc: what an editor shows. */\n text: CatalogText | undefined;\n /** HTML from the editor with every image URL this hook showed turned back into the image's reference. */\n storedBody: (html: string) => string;\n}\n\n/** The names of the images a body references, once each. */\nconst refNames = (body: string | undefined) => [...new Set([...(body ?? \"\").matchAll(imageRefs())].map((m) => m[1]!))];\nconst uploadName = (path: string) => {\n const base = path.slice(path.lastIndexOf(\"/\") + 1);\n const dot = base.lastIndexOf(\".\");\n return dot > 0 ? base.slice(0, dot) : base;\n};\n\n/**\n * One content item of kind for its editors: read, create, text in language,\n * publish, delete, restore, cover and body images. null creates a new item.\n */\nexport function useContent(id: string | null | undefined, kind: string, language: string, o: { initial?: Content; client?: ContentKitClient } = {}): UseContent {\n const { client, store, scope } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const cid = id ?? created;\n const key = cid ? keyOf(\"content\", scope, cid) : null;\n const r = useResource<Content | null>(store, key, { type: \"content\", id: cid ?? undefined }, (s) => client.contents.get(cid!, language, s), { initial: o.initial });\n const content = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const guarded = useGuarded();\n const run = useCallback(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 }, [guarded]);\n const need = useCallback(() => {\n if (!cid) throw new Error(\"contentkit-ui: create the item first\");\n return cid;\n }, [cid]);\n const stored = content?.text?.[language];\n const body = stored?.body;\n const cover = content?.cover;\n\n // Images: fresh uploads show from their file, published ones from\n // content.images, the rest (an unpublished item's) from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => {\n for (const u of urls) URL.revokeObjectURL(u);\n };\n }, []);\n const published = useMemo(() => new Map(Object.entries(content?.images ?? {})), [content?.images]);\n // The cover is the item's folder's; body images are its body folder's ({kind}.body).\n const coverPending = !!cover && !cover.url && !previews.has(cover.image) && !published.has(cover.image);\n const bodyPending = useMemo(() => refNames(body).filter((n) => !previews.has(n) && !published.has(n)), [body, previews, published]);\n const pending = useMemo(() => (coverPending ? [...bodyPending, cover!.image] : bodyPending), [coverPending, bodyPending, cover]);\n const folder = useMemo(() => ({ kind, id: cid ?? \"none\" }), [kind, cid]);\n const bodyFolder = useMemo(() => ({ kind: kind + BODY_SUFFIX, id: cid ?? \"none\" }), [kind, cid]);\n const coverRead = useMediaRead(folder, { editor: true, read: coverPending ? undefined : null, client });\n const bodyRead = useMediaRead(bodyFolder, { editor: true, read: bodyPending.length ? undefined : null, client });\n const files = useMemo(() => [...(coverRead.read?.files ?? []), ...(bodyRead.read?.files ?? [])], [coverRead.read, bodyRead.read]);\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of files) if (f.upload && f.editor_url) out.set(uploadName(f.path), f.editor_url);\n return out;\n }, [files]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const failed = (n: string) => files.some((f) => f.failed && uploadName(f.path) === n);\n const missing = (!!coverRead.read || !!bodyRead.read) && pending.some((n) => !views.has(n) && !failed(n));\n const tries = useRef(0);\n const reloadCover = coverRead.reload;\n const reloadBody = bodyRead.reload;\n const reload = useCallback(() => {\n reloadCover();\n reloadBody();\n }, [reloadCover, reloadBody]);\n useEffect(() => {\n if (!missing) {\n tries.current = 0;\n return;\n }\n if (tries.current >= 6) return;\n const t = setTimeout(() => {\n tries.current++;\n reload();\n }, 1000 * 2 ** tries.current);\n return () => clearTimeout(t);\n }, [missing, files, reload]);\n\n // Every URL shown for an image, of this and earlier reads, back to its name.\n const shown = useRef(new Map<string, string>());\n useEffect(() => {\n for (const m of [previews, published, views]) for (const [n, u] of m) shown.current.set(u, n);\n }, [previews, published, views]);\n\n const imageSrc = useCallback(\n (image: string | null | undefined) => {\n if (!image) return undefined;\n const n = image.startsWith(IMAGE_SCHEME) ? image.slice(IMAGE_SCHEME.length) : image;\n return previews.get(n) ?? published.get(n) ?? views.get(n) ?? image;\n },\n [previews, published, views],\n );\n const text = useMemo(\n () => (stored ? { ...stored, body: (stored.body ?? \"\").replace(imageRefs(), (ref, n: string) => previews.get(n) ?? published.get(n) ?? views.get(n) ?? ref) } : undefined),\n [stored, previews, published, views],\n );\n const storedBody = useCallback(\n (html: string) => {\n const back = new Map(shown.current);\n for (const m of [previews, published, views]) for (const [n, u] of m) back.set(u, n);\n let out = html;\n for (const [u, n] of back) out = out.split(u).join(imageRef(n)).split(u.replaceAll(\"&\", \"&amp;\")).join(imageRef(n));\n return out;\n },\n [previews, published, views],\n );\n const preview = useCallback((name: string, file: Blob): string | undefined => {\n if (typeof URL.createObjectURL !== \"function\") return undefined;\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((m) => new Map(m).set(name, url));\n return url;\n }, []);\n\n const create = useCallback(\n (input: ContentInput) =>\n run(async () => {\n const c = await client.contents.create(input);\n setCreated(c.id);\n return c;\n }),\n [client, run],\n );\n const update = useCallback((patch: ContentPatch) => run(() => client.contents.update(need(), patch)), [client, run, need]);\n const putText = useCallback(\n (t: CatalogText) => run(() => client.contents.putText(need(), language, t.body === undefined ? t : { ...t, body: storedBody(t.body) })),\n [client, run, need, language, storedBody],\n );\n const publish = useCallback((at?: string) => run(() => client.contents.publish(need(), at)), [client, run, need]);\n const unpublish = useCallback(() => run(() => client.contents.unpublish(need())), [client, run, need]);\n const remove = useCallback(() => run(() => client.contents.remove(need())), [client, run, need]);\n const restore = useCallback(() => run(() => client.contents.restore(need())), [client, run, need]);\n const setCover = useCallback(\n (file: Blob | null) =>\n run(async () => {\n if (!file) {\n await client.contents.setCover(need(), null);\n return null;\n }\n const up = await client.media.uploadNamed({ kind, id: need() }, file);\n const saved = await client.contents.setCover(need(), up.name);\n const url = preview(up.name, file) ?? saved.cover?.url ?? null;\n if (url) shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, kind, preview],\n );\n const uploadImage = useCallback(\n (file: Blob) =>\n run(async () => {\n const up = await client.media.uploadInline({ kind: kind + BODY_SUFFIX, id: need() }, file);\n const url = preview(up.name, file) ?? up.url ?? up.ref;\n shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, kind, preview],\n );\n return {\n content,\n reload: r.reload,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n putText,\n publish,\n unpublish,\n remove,\n restore,\n setCover,\n uploadImage,\n imageSrc,\n coverSrc: imageSrc(cover?.image),\n text,\n storedBody,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"contents.js","names":[],"sources":["../../src/react/contents.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 { CatalogText, Content, ContentInput, ContentPatch } from \"../client/generated/wire.js\";\nimport { BODY_SUFFIX, IMAGE_SCHEME, imageRef, imageRefs, type ManageQuery } from \"../client/contents.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { useGuarded } from \"./context.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface ManageFilter extends Omit<ManageQuery, \"limit\" | \"offset\"> {\n pageSize?: number;\n}\n\n/** The content items the caller manages, newest first, a page at a time. */\nexport function useManagedContents(f: ManageFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Content> {\n const { client, store, scope } = useContentScope(o.client);\n const { pageSize, ...q } = f;\n const size = pageSize ?? 20;\n return useList(\n store,\n keyOf(\"contents\", scope, q.kind, q.state, q.owner_group, q.q, q.language, size),\n { type: \"contents\", scope: q.state === \"deleted\" ? \"deleted\" : undefined },\n offsetPages(size, (page, signal) => client.contents.manage({ ...q, ...page }, signal)),\n );\n}\n\nexport interface UseContent {\n /** null before create (or while loading). */\n content: Content | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Reads the item again. */\n reload: () => void;\n /** Creates the item as a draft; the hook then edits it. */\n create: (input: ContentInput) => Promise<Content>;\n update: (patch: ContentPatch) => Promise<Content>;\n /** Sets the text in the hook's language; a body from the editor is stored with image references (storedBody). */\n putText: (text: CatalogText) => Promise<Content>;\n /** Publishes now, or schedules for at. */\n publish: (at?: string) => Promise<Content>;\n unpublish: () => Promise<Content>;\n remove: () => Promise<void>;\n restore: () => Promise<Content>;\n /** Uploads the cover (null clears it); resolves with a URL that shows it now. */\n setCover: (file: Blob | null) => Promise<string | null>;\n /** Uploads an image for the body; resolves with a URL that shows it now, to place in the editor. */\n uploadImage: (file: Blob) => Promise<string>;\n /**\n * A URL that shows one of the item's images now, by name (\"i-{uuid}\") or\n * reference: a fresh upload from its file, else its public file\n * (`content.images`), else, while it has none (an unpublished item's), its\n * signed editor view from an editor read. Anything else is returned as is.\n */\n imageSrc: (image: string | null | undefined) => string | undefined;\n /** The cover through imageSrc. */\n coverSrc: string | undefined;\n /** The language's stored text, its body's images through imageSrc: what an editor shows. */\n text: CatalogText | undefined;\n /** HTML from the editor with every image URL this hook showed turned back into the image's reference. */\n storedBody: (html: string) => string;\n}\n\n/** The names of the images a body references, once each. */\nconst refNames = (body: string | undefined) => [...new Set([...(body ?? \"\").matchAll(imageRefs())].map((m) => m[1]!))];\nconst uploadName = (path: string) => {\n const base = path.slice(path.lastIndexOf(\"/\") + 1);\n const dot = base.lastIndexOf(\".\");\n return dot > 0 ? base.slice(0, dot) : base;\n};\n\n/**\n * One content item of kind for its editors: read, create, text in language,\n * publish, delete, restore, cover and body images. null creates a new item.\n */\nexport function useContent(id: string | null | undefined, kind: string, language: string, o: { initial?: Content; client?: ContentKitClient } = {}): UseContent {\n const { client, store, scope } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const cid = id ?? created;\n const key = cid ? keyOf(\"content\", scope, cid) : null;\n const r = useResource<Content | null>(store, key, { type: \"content\", id: cid ?? undefined }, (s) => client.contents.get(cid!, language, s), { initial: o.initial });\n const content = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const guarded = useGuarded();\n const run = useCallback(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 }, [guarded]);\n const need = useCallback(() => {\n if (!cid) throw new Error(\"contentkit-ui: create the item first\");\n return cid;\n }, [cid]);\n const stored = content?.text?.[language];\n const body = stored?.body;\n const cover = content?.cover;\n\n // Images: fresh uploads show from their file, published ones from\n // content.images, the rest (an unpublished item's) from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => {\n for (const u of urls) URL.revokeObjectURL(u);\n };\n }, []);\n const published = useMemo(() => new Map(Object.entries(content?.images ?? {})), [content?.images]);\n // The cover is the item's folder's; body images are its body folder's ({kind}.body).\n const coverPending = !!cover && !cover.url && !previews.has(cover.image) && !published.has(cover.image);\n const bodyPending = useMemo(() => refNames(body).filter((n) => !previews.has(n) && !published.has(n)), [body, previews, published]);\n const pending = useMemo(() => (coverPending ? [...bodyPending, cover!.image] : bodyPending), [coverPending, bodyPending, cover]);\n const folder = useMemo(() => ({ kind, id: cid ?? \"none\" }), [kind, cid]);\n const bodyFolder = useMemo(() => ({ kind: kind + BODY_SUFFIX, id: cid ?? \"none\" }), [kind, cid]);\n const coverRead = useMediaRead(folder, { editor: true, read: coverPending ? undefined : null, client });\n const bodyRead = useMediaRead(bodyFolder, { editor: true, read: bodyPending.length ? undefined : null, client });\n const files = useMemo(() => [...(coverRead.read?.files ?? []), ...(bodyRead.read?.files ?? [])], [coverRead.read, bodyRead.read]);\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of files) if (f.upload && f.editor_url) out.set(uploadName(f.path), f.editor_url);\n return out;\n }, [files]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const failed = (n: string) => files.some((f) => f.failed && uploadName(f.path) === n);\n const missing = (!!coverRead.read || !!bodyRead.read) && pending.some((n) => !views.has(n) && !failed(n));\n const tries = useRef(0);\n const reloadCover = coverRead.reload;\n const reloadBody = bodyRead.reload;\n const reload = useCallback(() => {\n reloadCover();\n reloadBody();\n }, [reloadCover, reloadBody]);\n useEffect(() => {\n if (!missing) {\n tries.current = 0;\n return;\n }\n if (tries.current >= 6) return;\n const t = setTimeout(() => {\n tries.current++;\n reload();\n }, 1000 * 2 ** tries.current);\n return () => clearTimeout(t);\n }, [missing, files, reload]);\n\n // Every URL shown for an image, of this and earlier reads, back to its name.\n const shown = useRef(new Map<string, string>());\n useEffect(() => {\n for (const m of [previews, published, views]) for (const [n, u] of m) shown.current.set(u, n);\n }, [previews, published, views]);\n\n const imageSrc = useCallback(\n (image: string | null | undefined) => {\n if (!image) return undefined;\n const n = image.startsWith(IMAGE_SCHEME) ? image.slice(IMAGE_SCHEME.length) : image;\n return previews.get(n) ?? published.get(n) ?? views.get(n) ?? image;\n },\n [previews, published, views],\n );\n const text = useMemo(\n () => (stored ? { ...stored, body: (stored.body ?? \"\").replace(imageRefs(), (ref, n: string) => previews.get(n) ?? published.get(n) ?? views.get(n) ?? ref) } : undefined),\n [stored, previews, published, views],\n );\n const storedBody = useCallback(\n (html: string) => {\n const back = new Map(shown.current);\n for (const m of [previews, published, views]) for (const [n, u] of m) back.set(u, n);\n let out = html;\n for (const [u, n] of back) out = out.split(u).join(imageRef(n)).split(u.replaceAll(\"&\", \"&amp;\")).join(imageRef(n));\n return out;\n },\n [previews, published, views],\n );\n const preview = useCallback((name: string, file: Blob): string | undefined => {\n if (typeof URL.createObjectURL !== \"function\") return undefined;\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((m) => new Map(m).set(name, url));\n return url;\n }, []);\n\n const create = useCallback(\n (input: ContentInput) =>\n run(async () => {\n const c = await client.contents.create(input);\n setCreated(c.id);\n return c;\n }),\n [client, run],\n );\n const update = useCallback((patch: ContentPatch) => run(() => client.contents.update(need(), patch)), [client, run, need]);\n const putText = useCallback(\n (t: CatalogText) => run(() => client.contents.putText(need(), language, t.body === undefined ? t : { ...t, body: storedBody(t.body) })),\n [client, run, need, language, storedBody],\n );\n const publish = useCallback((at?: string) => run(() => client.contents.publish(need(), at)), [client, run, need]);\n const unpublish = useCallback(() => run(() => client.contents.unpublish(need())), [client, run, need]);\n const remove = useCallback(() => run(() => client.contents.remove(need())), [client, run, need]);\n const restore = useCallback(() => run(() => client.contents.restore(need())), [client, run, need]);\n const setCover = useCallback(\n (file: Blob | null) =>\n run(async () => {\n if (!file) {\n await client.contents.setCover(need(), null);\n return null;\n }\n const up = await client.media.uploadNamed({ kind, id: need() }, file);\n const saved = await client.contents.setCover(need(), up.name);\n const url = preview(up.name, file) ?? saved.cover?.url ?? null;\n if (url) shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, kind, preview],\n );\n const uploadImage = useCallback(\n (file: Blob) =>\n run(async () => {\n const up = await client.media.uploadInline({ kind: kind + BODY_SUFFIX, id: need() }, file);\n const url = preview(up.name, file) ?? up.url ?? up.ref;\n shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, kind, preview],\n );\n return {\n content,\n reload: r.reload,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n putText,\n publish,\n unpublish,\n remove,\n restore,\n setCover,\n uploadImage,\n imageSrc,\n coverSrc: imageSrc(cover?.image),\n text,\n storedBody,\n };\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { ContentKitError } from '../client/errors.js';
3
3
  import { ContentURLs } from '../urls/index.js';
4
4
  import { ReadStore } from './store.js';
5
5
  /** What failed: a component's or hook's save, load or render step. */
6
- export type ContentKitOperation = "poster.load" | "poster.frame" | "poster.save" | "slot.load" | "slot.decode" | "slot.save" | "slot.remove" | "upload" | "folder.load" | "folder.commit" | "folder.update" | "folder.process" | "crop.load" | "crop.save" | SocialOperation;
6
+ export type ContentKitOperation = "poster.load" | "poster.frame" | "poster.save" | "slot.load" | "slot.decode" | "slot.save" | "slot.remove" | "upload" | "folder.load" | "folder.commit" | "folder.update" | "folder.process" | "crop.load" | "crop.save" | "version.download" | SocialOperation;
7
7
  /** What failed in a content component: a load or a write. */
8
8
  export type SocialOperation = "comment.load" | "comment.post" | "comment.edit" | "comment.delete" | "comment.react" | "comment.restore" | "ban.save" | "ban.lift" | "reaction.save" | "favorite.save" | "poll.load" | "poll.vote" | "poll.answer" | "poll.save" | "post.load" | "content.save" | "moderation.load" | "moderation.resolve" | "rules.save" | "reader.save" | "media.save" | "notifications.load" | "notifications.read" | "notifications.dismiss" | "group.load" | "group.save" | "membership.save" | "history.forget" | "filters.save" | "offer.save" | "membership.join";
9
9
  /** The failure's context: the operation and, for one file, its name. */
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../../src/react/context.ts"],"sourcesContent":["import { createContext, useCallback, useContext, useLayoutEffect, useRef } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport type { ReadStore } from \"./store.js\";\n\n/** What failed: a component's or hook's save, load or render step. */\nexport type ContentKitOperation =\n | \"poster.load\"\n | \"poster.frame\"\n | \"poster.save\"\n | \"slot.load\"\n | \"slot.decode\"\n | \"slot.save\"\n | \"slot.remove\"\n | \"upload\"\n | \"folder.load\"\n | \"folder.commit\"\n | \"folder.update\"\n | \"folder.process\"\n | \"crop.load\"\n | \"crop.save\"\n | SocialOperation;\n\n/** What failed in a content component: a load or a write. */\nexport type SocialOperation =\n | \"comment.load\"\n | \"comment.post\"\n | \"comment.edit\"\n | \"comment.delete\"\n | \"comment.react\"\n | \"comment.restore\"\n | \"ban.save\"\n | \"ban.lift\"\n | \"reaction.save\"\n | \"favorite.save\"\n | \"poll.load\"\n | \"poll.vote\"\n | \"poll.answer\"\n | \"poll.save\"\n | \"post.load\"\n | \"content.save\"\n | \"moderation.load\"\n | \"moderation.resolve\"\n | \"rules.save\"\n | \"reader.save\"\n | \"media.save\"\n | \"notifications.load\"\n | \"notifications.read\"\n | \"notifications.dismiss\"\n | \"group.load\"\n | \"group.save\"\n | \"membership.save\"\n | \"history.forget\"\n | \"filters.save\"\n | \"offer.save\"\n | \"membership.join\";\n\n/** The failure's context: the operation and, for one file, its name. */\nexport interface ContentKitErrorInfo {\n operation: ContentKitOperation;\n file?: string;\n}\n\n/**\n * Every failure a component shows is also reported here (aborts excepted), so\n * the host can toast or log it. Components still show it in place.\n */\nexport type ContentKitErrorHandler = (error: ContentKitError, info: ContentKitErrorInfo) => void;\n\n/** The host router: in-app navigation, e.g. a canonical replace. */\nexport type Navigate = (to: string, options?: { replace?: boolean }) => void;\n\nexport interface ContentKitContextValue {\n client: ContentKitClient;\n /**\n * The signed-in user's id as ContentKit sees it, null when signed out,\n * undefined when the host does not say. Content reads are kept per viewer.\n */\n viewer?: string | null;\n /** Request language; keep it in sync with the client's language callback. */\n language?: string;\n /** Change when the current viewer's permissions or entitlements change. */\n accessRevision?: string | number;\n readScope: string;\n /** Asks the visitor to sign in; with it, signed-out visitors are prompted instead of acting anonymously. */\n onSignIn?: () => void;\n /** The host's content URL config (createContentURLs). */\n urls?: ContentURLs;\n navigate?: Navigate;\n onError?: ContentKitErrorHandler;\n /** The reads hooks share. */\n store: ReadStore;\n}\n\nexport const ContentKitContext = createContext<ContentKitContextValue | null>(null);\n\n/** Values that can change a read without changing its content reference. */\nexport function useReadScope(): string {\n return useContext(ContentKitContext)?.readScope ?? \"{}\";\n}\n\n/** The ContentKitProvider's value; throws outside one. */\nexport function useContentKit(): ContentKitContextValue {\n const ctx = useContext(ContentKitContext);\n if (!ctx) throw new Error(\"contentkit-ui: wrap the tree in <ContentKitProvider client>\");\n return ctx;\n}\n\n/** The client a hook or component uses: its own, else the provider's; throws without either. */\nexport function useContentKitClient(own?: ContentKitClient | null): ContentKitClient {\n const c = useOptionalContentKitClient(own);\n if (!c) throw new Error(\"contentkit-ui: pass `client` or render inside <ContentKitProvider client>\");\n return c;\n}\n\nexport function useOptionalContentKitClient(own?: ContentKitClient | null): ContentKitClient | null {\n const ctx = useContext(ContentKitContext);\n const client = own ?? ctx?.client ?? null;\n const scope = useReadScope();\n useLayoutEffect(() => { client?.setReadScope(scope); }, [client, scope]);\n return client;\n}\n\n/** The provider's content URL helpers; throws when none were given. */\nexport function useContentURLs(): ContentURLs {\n const urls = useContext(ContentKitContext)?.urls;\n if (!urls) throw new Error(\"contentkit-ui: pass `urls` to <ContentKitProvider>\");\n return urls;\n}\n\n/** A stable reporter: the component's own `onError`, else the provider's. */\nexport function useErrorReporter(own?: ContentKitErrorHandler): (e: unknown, operation: ContentKitOperation, file?: string) => void {\n const ctx = useContext(ContentKitContext)?.onError;\n const handler = useRef(own ?? ctx);\n handler.current = own ?? ctx;\n return useCallback((e: unknown, operation: ContentKitOperation, file?: string) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") handler.current?.(error, file === undefined ? { operation } : { operation, file });\n }, []);\n}\n\n/** Runs a write, e.g. auth-ui's useStepUpGuard(): re-authenticates when the write asks for a recent sign-in, then retries. */\nexport type ContentKitGuard = <T>(write: () => Promise<T>) => Promise<T>;\n\n/** The admin flow's guard; hooks run their writes through it. */\nexport const GuardContext = createContext<ContentKitGuard | undefined>(undefined);\n\n/** Runs fn through the flow's guard, when the host gave one. */\nexport function useGuarded(): <T>(fn: () => Promise<T>) => Promise<T> {\n const guard = useContext(GuardContext);\n return useCallback(<T,>(fn: () => Promise<T>) => (guard ? guard(fn) : fn()), [guard]);\n}\n"],"mappings":";;;AA+FA,IAAa,IAAoB,EAA6C,IAAI;AAGlF,SAAgB,IAAuB;CACrC,OAAO,EAAW,CAAiB,CAAC,EAAE,aAAa;AACrD;AAGA,SAAgB,IAAwC;CACtD,IAAM,IAAM,EAAW,CAAiB;CACxC,IAAI,CAAC,GAAK,MAAU,MAAM,6DAA6D;CACvF,OAAO;AACT;AAGA,SAAgB,EAAoB,GAAiD;CACnF,IAAM,IAAI,EAA4B,CAAG;CACzC,IAAI,CAAC,GAAG,MAAU,MAAM,2EAA2E;CACnG,OAAO;AACT;AAEA,SAAgB,EAA4B,GAAwD;CAClG,IAAM,IAAM,EAAW,CAAiB,GAClC,IAAS,KAAO,GAAK,UAAU,MAC/B,IAAQ,EAAa;CAE3B,OADA,QAAsB;EAAE,GAAQ,aAAa,CAAK;CAAG,GAAG,CAAC,GAAQ,CAAK,CAAC,GAChE;AACT;AAGA,SAAgB,IAA8B;CAC5C,IAAM,IAAO,EAAW,CAAiB,CAAC,EAAE;CAC5C,IAAI,CAAC,GAAM,MAAU,MAAM,oDAAoD;CAC/E,OAAO;AACT;AAGA,SAAgB,EAAiB,GAAmG;CAClI,IAAM,IAAM,EAAW,CAAiB,CAAC,EAAE,SACrC,IAAU,EAAO,KAAO,CAAG;CAEjC,OADA,EAAQ,UAAU,KAAO,GAClB,GAAa,GAAY,GAAgC,MAAkB;EAChF,IAAM,IAAQ,EAAkB,CAAC;EACjC,AAAI,EAAM,SAAS,aAAW,EAAQ,UAAU,GAAO,MAAS,KAAA,IAAY,EAAE,aAAU,IAAI;GAAE;GAAW;EAAK,CAAC;CACjH,GAAG,CAAC,CAAC;AACP;AAMA,IAAa,IAAe,EAA2C,KAAA,CAAS;AAGhF,SAAgB,IAAsD;CACpE,IAAM,IAAQ,EAAW,CAAY;CACrC,OAAO,GAAiB,MAA0B,IAAQ,EAAM,CAAE,IAAI,EAAG,GAAI,CAAC,CAAK,CAAC;AACtF"}
1
+ {"version":3,"file":"context.js","names":[],"sources":["../../src/react/context.ts"],"sourcesContent":["import { createContext, useCallback, useContext, useLayoutEffect, useRef } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport type { ReadStore } from \"./store.js\";\n\n/** What failed: a component's or hook's save, load or render step. */\nexport type ContentKitOperation =\n | \"poster.load\"\n | \"poster.frame\"\n | \"poster.save\"\n | \"slot.load\"\n | \"slot.decode\"\n | \"slot.save\"\n | \"slot.remove\"\n | \"upload\"\n | \"folder.load\"\n | \"folder.commit\"\n | \"folder.update\"\n | \"folder.process\"\n | \"crop.load\"\n | \"crop.save\"\n | \"version.download\"\n | SocialOperation;\n\n/** What failed in a content component: a load or a write. */\nexport type SocialOperation =\n | \"comment.load\"\n | \"comment.post\"\n | \"comment.edit\"\n | \"comment.delete\"\n | \"comment.react\"\n | \"comment.restore\"\n | \"ban.save\"\n | \"ban.lift\"\n | \"reaction.save\"\n | \"favorite.save\"\n | \"poll.load\"\n | \"poll.vote\"\n | \"poll.answer\"\n | \"poll.save\"\n | \"post.load\"\n | \"content.save\"\n | \"moderation.load\"\n | \"moderation.resolve\"\n | \"rules.save\"\n | \"reader.save\"\n | \"media.save\"\n | \"notifications.load\"\n | \"notifications.read\"\n | \"notifications.dismiss\"\n | \"group.load\"\n | \"group.save\"\n | \"membership.save\"\n | \"history.forget\"\n | \"filters.save\"\n | \"offer.save\"\n | \"membership.join\";\n\n/** The failure's context: the operation and, for one file, its name. */\nexport interface ContentKitErrorInfo {\n operation: ContentKitOperation;\n file?: string;\n}\n\n/**\n * Every failure a component shows is also reported here (aborts excepted), so\n * the host can toast or log it. Components still show it in place.\n */\nexport type ContentKitErrorHandler = (error: ContentKitError, info: ContentKitErrorInfo) => void;\n\n/** The host router: in-app navigation, e.g. a canonical replace. */\nexport type Navigate = (to: string, options?: { replace?: boolean }) => void;\n\nexport interface ContentKitContextValue {\n client: ContentKitClient;\n /**\n * The signed-in user's id as ContentKit sees it, null when signed out,\n * undefined when the host does not say. Content reads are kept per viewer.\n */\n viewer?: string | null;\n /** Request language; keep it in sync with the client's language callback. */\n language?: string;\n /** Change when the current viewer's permissions or entitlements change. */\n accessRevision?: string | number;\n readScope: string;\n /** Asks the visitor to sign in; with it, signed-out visitors are prompted instead of acting anonymously. */\n onSignIn?: () => void;\n /** The host's content URL config (createContentURLs). */\n urls?: ContentURLs;\n navigate?: Navigate;\n onError?: ContentKitErrorHandler;\n /** The reads hooks share. */\n store: ReadStore;\n}\n\nexport const ContentKitContext = createContext<ContentKitContextValue | null>(null);\n\n/** Values that can change a read without changing its content reference. */\nexport function useReadScope(): string {\n return useContext(ContentKitContext)?.readScope ?? \"{}\";\n}\n\n/** The ContentKitProvider's value; throws outside one. */\nexport function useContentKit(): ContentKitContextValue {\n const ctx = useContext(ContentKitContext);\n if (!ctx) throw new Error(\"contentkit-ui: wrap the tree in <ContentKitProvider client>\");\n return ctx;\n}\n\n/** The client a hook or component uses: its own, else the provider's; throws without either. */\nexport function useContentKitClient(own?: ContentKitClient | null): ContentKitClient {\n const c = useOptionalContentKitClient(own);\n if (!c) throw new Error(\"contentkit-ui: pass `client` or render inside <ContentKitProvider client>\");\n return c;\n}\n\nexport function useOptionalContentKitClient(own?: ContentKitClient | null): ContentKitClient | null {\n const ctx = useContext(ContentKitContext);\n const client = own ?? ctx?.client ?? null;\n const scope = useReadScope();\n useLayoutEffect(() => { client?.setReadScope(scope); }, [client, scope]);\n return client;\n}\n\n/** The provider's content URL helpers; throws when none were given. */\nexport function useContentURLs(): ContentURLs {\n const urls = useContext(ContentKitContext)?.urls;\n if (!urls) throw new Error(\"contentkit-ui: pass `urls` to <ContentKitProvider>\");\n return urls;\n}\n\n/** A stable reporter: the component's own `onError`, else the provider's. */\nexport function useErrorReporter(own?: ContentKitErrorHandler): (e: unknown, operation: ContentKitOperation, file?: string) => void {\n const ctx = useContext(ContentKitContext)?.onError;\n const handler = useRef(own ?? ctx);\n handler.current = own ?? ctx;\n return useCallback((e: unknown, operation: ContentKitOperation, file?: string) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") handler.current?.(error, file === undefined ? { operation } : { operation, file });\n }, []);\n}\n\n/** Runs a write, e.g. auth-ui's useStepUpGuard(): re-authenticates when the write asks for a recent sign-in, then retries. */\nexport type ContentKitGuard = <T>(write: () => Promise<T>) => Promise<T>;\n\n/** The admin flow's guard; hooks run their writes through it. */\nexport const GuardContext = createContext<ContentKitGuard | undefined>(undefined);\n\n/** Runs fn through the flow's guard, when the host gave one. */\nexport function useGuarded(): <T>(fn: () => Promise<T>) => Promise<T> {\n const guard = useContext(GuardContext);\n return useCallback(<T,>(fn: () => Promise<T>) => (guard ? guard(fn) : fn()), [guard]);\n}\n"],"mappings":";;;AAgGA,IAAa,IAAoB,EAA6C,IAAI;AAGlF,SAAgB,IAAuB;CACrC,OAAO,EAAW,CAAiB,CAAC,EAAE,aAAa;AACrD;AAGA,SAAgB,IAAwC;CACtD,IAAM,IAAM,EAAW,CAAiB;CACxC,IAAI,CAAC,GAAK,MAAU,MAAM,6DAA6D;CACvF,OAAO;AACT;AAGA,SAAgB,EAAoB,GAAiD;CACnF,IAAM,IAAI,EAA4B,CAAG;CACzC,IAAI,CAAC,GAAG,MAAU,MAAM,2EAA2E;CACnG,OAAO;AACT;AAEA,SAAgB,EAA4B,GAAwD;CAClG,IAAM,IAAM,EAAW,CAAiB,GAClC,IAAS,KAAO,GAAK,UAAU,MAC/B,IAAQ,EAAa;CAE3B,OADA,QAAsB;EAAE,GAAQ,aAAa,CAAK;CAAG,GAAG,CAAC,GAAQ,CAAK,CAAC,GAChE;AACT;AAGA,SAAgB,IAA8B;CAC5C,IAAM,IAAO,EAAW,CAAiB,CAAC,EAAE;CAC5C,IAAI,CAAC,GAAM,MAAU,MAAM,oDAAoD;CAC/E,OAAO;AACT;AAGA,SAAgB,EAAiB,GAAmG;CAClI,IAAM,IAAM,EAAW,CAAiB,CAAC,EAAE,SACrC,IAAU,EAAO,KAAO,CAAG;CAEjC,OADA,EAAQ,UAAU,KAAO,GAClB,GAAa,GAAY,GAAgC,MAAkB;EAChF,IAAM,IAAQ,EAAkB,CAAC;EACjC,AAAI,EAAM,SAAS,aAAW,EAAQ,UAAU,GAAO,MAAS,KAAA,IAAY,EAAE,aAAU,IAAI;GAAE;GAAW;EAAK,CAAC;CACjH,GAAG,CAAC,CAAC;AACP;AAMA,IAAa,IAAe,EAA2C,KAAA,CAAS;AAGhF,SAAgB,IAAsD;CACpE,IAAM,IAAQ,EAAW,CAAY;CACrC,OAAO,GAAiB,MAA0B,IAAQ,EAAM,CAAE,IAAI,EAAG,GAAI,CAAC,CAAK,CAAC;AACtF"}