@openrails/contentkit-ui 0.98.6 → 0.99.0

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 (99) hide show
  1. package/README.md +26 -0
  2. package/dist/client/content/comments.d.ts +4 -2
  3. package/dist/client/content/comments.js +10 -6
  4. package/dist/client/content/comments.js.map +1 -1
  5. package/dist/client/generated/routes.d.ts +28 -2
  6. package/dist/client/generated/routes.js +39 -2
  7. package/dist/client/generated/routes.js.map +1 -1
  8. package/dist/client/generated/wire.d.ts +85 -0
  9. package/dist/client/generated/wire.js.map +1 -1
  10. package/dist/client/media/client.d.ts +4 -0
  11. package/dist/client/media/client.js +8 -4
  12. package/dist/client/media/client.js.map +1 -1
  13. package/dist/client/media/queue.js +42 -26
  14. package/dist/client/media/queue.js.map +1 -1
  15. package/dist/client/media/rules.d.ts +6 -0
  16. package/dist/client/media/rules.js +64 -10
  17. package/dist/client/media/rules.js.map +1 -1
  18. package/dist/client/versions.d.ts +6 -2
  19. package/dist/client/versions.js +26 -0
  20. package/dist/client/versions.js.map +1 -1
  21. package/dist/components/admin/page-review.d.ts +13 -0
  22. package/dist/components/admin/page-review.js +216 -0
  23. package/dist/components/admin/page-review.js.map +1 -0
  24. package/dist/components/content/card.js +98 -103
  25. package/dist/components/content/card.js.map +1 -1
  26. package/dist/components/content/daily-releases.d.ts +8 -1
  27. package/dist/components/content/daily-releases.js +291 -133
  28. package/dist/components/content/daily-releases.js.map +1 -1
  29. package/dist/components/content/editors.js +216 -210
  30. package/dist/components/content/editors.js.map +1 -1
  31. package/dist/components/content/filter-dialog.d.ts +11 -0
  32. package/dist/components/content/filter-dialog.js +29 -0
  33. package/dist/components/content/filter-dialog.js.map +1 -0
  34. package/dist/components/content/filters.d.ts +10 -0
  35. package/dist/components/content/filters.js +5 -2
  36. package/dist/components/content/filters.js.map +1 -1
  37. package/dist/components/content/grid.js +19 -14
  38. package/dist/components/content/grid.js.map +1 -1
  39. package/dist/components/content/index.d.ts +2 -1
  40. package/dist/components/content/release-reveal.d.ts +5 -0
  41. package/dist/components/content/release-reveal.js +62 -0
  42. package/dist/components/content/release-reveal.js.map +1 -0
  43. package/dist/components/social/comments.d.ts +1 -1
  44. package/dist/components/social/comments.js +15 -15
  45. package/dist/components/social/comments.js.map +1 -1
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.js +24 -22
  48. package/dist/locales/admin/de.js +23 -0
  49. package/dist/locales/admin/de.js.map +1 -1
  50. package/dist/locales/admin/en.d.ts +23 -0
  51. package/dist/locales/admin/en.js +23 -0
  52. package/dist/locales/admin/en.js.map +1 -1
  53. package/dist/locales/admin/es.js +23 -0
  54. package/dist/locales/admin/es.js.map +1 -1
  55. package/dist/locales/admin/ja.js +23 -0
  56. package/dist/locales/admin/ja.js.map +1 -1
  57. package/dist/locales/admin/ko.js +23 -0
  58. package/dist/locales/admin/ko.js.map +1 -1
  59. package/dist/locales/admin/zh.js +23 -0
  60. package/dist/locales/admin/zh.js.map +1 -1
  61. package/dist/locales/discovery/de.js +11 -0
  62. package/dist/locales/discovery/de.js.map +1 -1
  63. package/dist/locales/discovery/en.d.ts +11 -0
  64. package/dist/locales/discovery/en.js +11 -0
  65. package/dist/locales/discovery/en.js.map +1 -1
  66. package/dist/locales/discovery/es.js +11 -0
  67. package/dist/locales/discovery/es.js.map +1 -1
  68. package/dist/locales/discovery/ja.js +11 -0
  69. package/dist/locales/discovery/ja.js.map +1 -1
  70. package/dist/locales/discovery/ko.js +11 -0
  71. package/dist/locales/discovery/ko.js.map +1 -1
  72. package/dist/locales/discovery/zh.js +11 -0
  73. package/dist/locales/discovery/zh.js.map +1 -1
  74. package/dist/locales/en.d.ts +34 -0
  75. package/dist/locales/social/en.js +2 -2
  76. package/dist/locales/social/en.js.map +1 -1
  77. package/dist/react/comments.d.ts +2 -1
  78. package/dist/react/comments.js +10 -8
  79. package/dist/react/comments.js.map +1 -1
  80. package/dist/react/folder.js +39 -39
  81. package/dist/react/folder.js.map +1 -1
  82. package/dist/react/index.d.ts +1 -0
  83. package/dist/react/page-review.d.ts +14 -0
  84. package/dist/react/page-review.js +36 -0
  85. package/dist/react/page-review.js.map +1 -0
  86. package/dist/react/public.d.ts +3 -1
  87. package/dist/react/public.js +76 -72
  88. package/dist/react/public.js.map +1 -1
  89. package/dist/react/resources.d.ts +5 -13
  90. package/dist/react/resources.js +58 -49
  91. package/dist/react/resources.js.map +1 -1
  92. package/dist/react/use-resource.d.ts +1 -1
  93. package/dist/react/use-resource.js +23 -13
  94. package/dist/react/use-resource.js.map +1 -1
  95. package/dist/react/versions.js +70 -42
  96. package/dist/react/versions.js.map +1 -1
  97. package/dist/react.js +8 -7
  98. package/dist/styles.css +1 -1
  99. package/package.json +1 -1
@@ -49,7 +49,8 @@ function u(i, o = {}) {
49
49
  type: m,
50
50
  id: h,
51
51
  version: g
52
- }
52
+ },
53
+ language: p
53
54
  }, t(d, (e, t) => s.comments.list({
54
55
  type: m,
55
56
  id: h,
@@ -95,23 +96,24 @@ function d(i, a = {}) {
95
96
  }, t(l, (e, t) => o.comments.replies(i, e, t)));
96
97
  }
97
98
  function f(t, r = {}) {
98
- let { client: a, store: o, scope: s } = n(r.client), { type: c, id: l, version: u } = t, d = i(o, e("standing", s, c, l, u), {
99
+ let { client: a, store: o, scope: s } = n(r.client), { type: c, id: l, version: u } = t, d = r.language || void 0, f = i(o, e("standing", s, c, l, u, d), {
99
100
  type: "standing",
100
101
  target: {
101
102
  type: c,
102
103
  id: l,
103
104
  version: u
104
- }
105
+ },
106
+ language: d
105
107
  }, (e) => a.comments.standing({
106
108
  type: c,
107
109
  id: l,
108
110
  version: u
109
- }, e));
111
+ }, e, { language: d }));
110
112
  return {
111
- standing: d.data ?? null,
112
- loading: d.loading || !d.loaded,
113
- error: d.error,
114
- reload: d.reload
113
+ standing: f.data ?? null,
114
+ loading: f.loading || !f.loaded,
115
+ error: f.error,
116
+ reload: f.reload
115
117
  };
116
118
  }
117
119
  function p(i = {}) {
@@ -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; 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
+ {"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 }, language },\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/** Commenting permissions and the entire visible thread's count in language, independent of loaded pages. Hidden/locked threads withhold the count. */\nexport function useCanComment(target: Target, o: { language?: string; client?: ContentKitClient } = {}) {\n const { client, store, scope } = useContentScope(o.client);\n const { type, id, version } = target;\n const language = o.language || undefined;\n const r = useResource<CommentStanding>(store, keyOf(\"standing\", scope, type, id, version, language), { type: \"standing\", target: { type, id, version }, language }, (s) =>\n client.comments.standing({ type, id, version }, s, { language }),\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,13 +1,13 @@
1
1
  import { ContentKitError as e, failureError as t, toContentKitError as n } from "../client/errors.js";
2
2
  import { itemKey as r } from "../client/media/ref.js";
3
3
  import { stem as i } from "../client/media/client.js";
4
- import { filesFor as a, isPattern as o, ruleDir as s, ruleFor as c, screenFiles as ee, uniqueName as te, uploadRules as l } from "../client/media/rules.js";
4
+ import { filesFor as a, isPattern as o, ruleDir as s, ruleFor as c, screenFiles as ee, sortPageFiles as te, uniqueName as ne, uniquePageName as re, uploadRules as l } from "../client/media/rules.js";
5
5
  import { useContentKitClient as u, useErrorReporter as d } from "./context.js";
6
- import { useUploadQueue as ne } from "./upload.js";
6
+ import { useUploadQueue as ie } from "./upload.js";
7
7
  import { useMediaRead as f } from "./read.js";
8
8
  import { useCallback as p, useEffect as m, useMemo as h, useRef as g, useState as _ } from "react";
9
9
  //#region src/react/folder.ts
10
- var re = {
10
+ var ae = {
11
11
  start: 0,
12
12
  end: 2 ** 53 - 1,
13
13
  chunk: 200
@@ -17,21 +17,21 @@ function y(e, t) {
17
17
  for (let r of e?.files ?? []) !r.upload && r.from === t.path && r.url && r.type.startsWith("image/") && (!n || (r.w ?? Infinity) < (n.w ?? Infinity)) && (n = r);
18
18
  return n;
19
19
  }
20
- var ie = {
20
+ var oe = {
21
21
  kind: "",
22
22
  id: ""
23
- }, ae = (e) => e ? r(e) : "", b = /* @__PURE__ */ new WeakMap();
24
- function oe(t) {
23
+ }, se = (e) => e ? r(e) : "", b = /* @__PURE__ */ new WeakMap();
24
+ function ce(t) {
25
25
  let n = b.get(t);
26
26
  return n || b.set(t, n = new e("forbidden", "the editor read states no upload rules", { status: 403 })), n;
27
27
  }
28
- function se(e, t) {
28
+ function le(e, t) {
29
29
  return e.filter((e) => e.status !== "failed" && !(e.status === "committed" && t.has(e.path)));
30
30
  }
31
31
  function x(r, y = {}) {
32
- let b = u(y.client), x = d(y.onError), S = r ?? null, C = ae(S), w = S ?? ie, T = f(w, {
32
+ let b = u(y.client), x = d(y.onError), S = r ?? null, C = se(S), w = S ?? oe, T = f(w, {
33
33
  editor: !0,
34
- window: re,
34
+ window: ae,
35
35
  poll: y.poll,
36
36
  client: b,
37
37
  read: S ? void 0 : null
@@ -53,20 +53,20 @@ function x(r, y = {}) {
53
53
  T.read,
54
54
  E,
55
55
  O
56
- ]), N = ne({
56
+ ]), N = ie({
57
57
  client: b,
58
58
  ref: w,
59
59
  path: `${s(O[0] ?? "{name}")}{name}`,
60
60
  concurrency: y.concurrency
61
- }), P = N.queue, F = h(() => N.items.filter((e) => !(e.status === "committed" && j.has(e.path))), [N.items, j]), ce = {
61
+ }), P = N.queue, F = h(() => N.items.filter((e) => !(e.status === "committed" && j.has(e.path))), [N.items, j]), ue = {
62
62
  ...N,
63
63
  items: F
64
64
  }, I = h(() => {
65
65
  let e = new Set(N.items.map((e) => i(e.path)));
66
66
  return M.filter((t) => !e.has(i(t.path)));
67
- }, [M, N.items]), [le, L] = _([]), [R, z] = _([]), B = g([]), [ue, de] = _(0), [V, fe] = _(!1), [pe, H] = _(), [me, U] = _(), W = me?.key === C ? me.error : void 0, G = g(y);
67
+ }, [M, N.items]), [de, L] = _([]), [R, z] = _([]), B = g([]), [fe, pe] = _(0), [V, me] = _(!1), [he, H] = _(), [ge, U] = _(), W = ge?.key === C ? ge.error : void 0, G = g(y);
68
68
  G.current = y;
69
- let K = !!S && !!T.read?.uploads, q = S ? T.read ? T.read.uploads ? void 0 : oe(T.read) : T.error : void 0, J = g({
69
+ let K = !!S && !!T.read?.uploads, q = S ? T.read ? T.read.uploads ? void 0 : ce(T.read) : T.error : void 0, J = g({
70
70
  groups: k,
71
71
  ref: w,
72
72
  listed: j,
@@ -84,12 +84,12 @@ function x(r, y = {}) {
84
84
  ready: K,
85
85
  loadError: q
86
86
  };
87
- let he = p((e) => {
87
+ let _e = p((e) => {
88
88
  let { groups: t, listed: n, unattached: r, leftover: i } = J.current, a = t.map((e) => e.rule ?? {
89
89
  path: e.path,
90
90
  types: [],
91
91
  max_bytes: 0
92
- }), o = se(P.getSnapshot().items, n), l = (e, t) => c(a, e)?.path === t.path, u = ee(e, a, (e) => t.find((t) => t.path === e.path).files.length + i.filter((t) => l(t.path, e)).length + o.filter((t) => l(t.path, e)).length), d = /* @__PURE__ */ new Map(), ne = (e) => {
92
+ }), o = le(P.getSnapshot().items, n), l = (e, t) => c(a, e)?.path === t.path, u = ee(e, a, (e) => t.find((t) => t.path === e.path).files.length + i.filter((t) => l(t.path, e)).length + o.filter((t) => l(t.path, e)).length), d = /* @__PURE__ */ new Map(), ie = (e) => {
93
93
  let n = d.get(e.path);
94
94
  if (!n) {
95
95
  let i = t.find((t) => t.path === e.path);
@@ -97,19 +97,19 @@ function x(r, y = {}) {
97
97
  }
98
98
  return n;
99
99
  }, f = /* @__PURE__ */ new Map();
100
- for (let { file: e, rule: t } of u.accepted) {
101
- let n = ne(t), r = G.current.name?.(e) ?? te(e.name, n);
102
- n.push(r), f.set(e, s(t.path) + r);
100
+ for (let { file: e, rule: t } of te(u.accepted)) {
101
+ let n = ie(t), r = G.current.name?.(e), i = t.page ? re(r ?? e.name, n) : r ?? ne(e.name, n);
102
+ n.push(i), f.set(e, s(t.path) + i);
103
103
  }
104
104
  f.size && (P.add(f.keys(), { path: (e) => f.get(e) }), P.start(), U(void 0)), L(u.refused);
105
105
  for (let e of u.refused) x(e.error, "upload", e.file.name);
106
106
  }, [P, x]), Y = p(() => {
107
107
  let e = B.current, { ready: t, loadError: n } = J.current;
108
- return !e.length || !t && !n ? !1 : (B.current = [], z([]), t ? he(e) : L(e.map((e) => ({
108
+ return !e.length || !t && !n ? !1 : (B.current = [], z([]), t ? _e(e) : L(e.map((e) => ({
109
109
  file: e,
110
110
  error: n
111
111
  }))), !0);
112
- }, [he]), ge = p((e) => {
112
+ }, [_e]), ve = p((e) => {
113
113
  let t = [...e];
114
114
  t.length && (B.current = [...B.current, ...t], Y() || z(B.current));
115
115
  }, [Y]);
@@ -119,17 +119,17 @@ function x(r, y = {}) {
119
119
  q
120
120
  ]);
121
121
  let X = p(async (e, t) => {
122
- de((e) => e + 1);
122
+ pe((e) => e + 1);
123
123
  try {
124
124
  t ? await t() : await b.media.commit(J.current.ref, e), H(void 0);
125
125
  } catch (e) {
126
126
  let t = n(e);
127
127
  throw t.code !== "aborted" && H(t), x(t, "folder.update"), t;
128
128
  } finally {
129
- de((e) => e - 1);
129
+ pe((e) => e - 1);
130
130
  }
131
131
  }, [b, x]), Z = p(async () => {
132
- fe(!0);
132
+ me(!0);
133
133
  try {
134
134
  let e = await P.commit(void 0, {
135
135
  head: G.current.commit === "auto",
@@ -140,7 +140,7 @@ function x(r, y = {}) {
140
140
  let t = n(e);
141
141
  throw H(t), x(t, "folder.commit"), t;
142
142
  } finally {
143
- fe(!1);
143
+ me(!1);
144
144
  }
145
145
  }, [P, x]);
146
146
  m(() => {
@@ -151,21 +151,21 @@ function x(r, y = {}) {
151
151
  T.loading,
152
152
  P
153
153
  ]);
154
- let _e = y.commit === "auto", ve = F.find((e) => e.status !== "committed")?.status === "uploaded" || I.length > 0 && !T.loading;
154
+ let ye = y.commit === "auto", be = F.find((e) => e.status !== "committed")?.status === "uploaded" || I.length > 0 && !T.loading;
155
155
  m(() => {
156
- _e && ve && !V && !W && Z().catch((e) => U({
156
+ ye && be && !V && !W && Z().catch((e) => U({
157
157
  key: C,
158
158
  error: n(e)
159
159
  }));
160
160
  }, [
161
- _e,
162
- ve,
161
+ ye,
162
+ be,
163
163
  V,
164
164
  W,
165
165
  Z,
166
166
  C
167
167
  ]);
168
- let ye = p(async () => (U(void 0), Z()), [Z]), Q = g(/* @__PURE__ */ new Set());
168
+ let xe = p(async () => (U(void 0), Z()), [Z]), Q = g(/* @__PURE__ */ new Set());
169
169
  m(() => {
170
170
  for (let e of N.items) e.status === "failed" && e.error && !Q.current.has(e.error) && (Q.current.add(e.error), x(e.error, "upload", e.file.name));
171
171
  N.blocked && !Q.current.has(N.blocked) && (Q.current.add(N.blocked), x(N.blocked, "upload"));
@@ -195,18 +195,18 @@ function x(r, y = {}) {
195
195
  x,
196
196
  C
197
197
  ]), m(() => void (T.error && x(T.error, "folder.load")), [T.error, x]);
198
- let be = R.length > 0 || V || ue > 0 || _e && ve && !W || F.some((e) => e.status === "uploading" || e.status === "queued" && !N.blocked), xe = A.length + R.length + se(F, j).length + I.length, Se = (e) => J.current.groups.find((t) => c([{ path: t.path }], e));
198
+ let Se = R.length > 0 || V || fe > 0 || ye && be && !W || F.some((e) => e.status === "uploading" || e.status === "queued" && !N.blocked), Ce = A.length + R.length + le(F, j).length + I.length, we = (e) => J.current.groups.find((t) => c([{ path: t.path }], e));
199
199
  return {
200
200
  ref: S,
201
201
  options: y,
202
202
  read: T,
203
203
  groups: k,
204
204
  uploads: A,
205
- queue: ce,
205
+ queue: ue,
206
206
  pending: I,
207
207
  waiting: R,
208
- add: ge,
209
- refused: le,
208
+ add: ve,
209
+ refused: de,
210
210
  dismiss: p(() => L([]), []),
211
211
  move: p((e, t) => X([{
212
212
  op: "move",
@@ -218,7 +218,7 @@ function x(r, y = {}) {
218
218
  path: e
219
219
  }))), [X]),
220
220
  rename: p((t, n) => {
221
- let r = Se(t), a = (r ? s(r.path) : t.slice(0, t.lastIndexOf("/") + 1)) + n.trim();
221
+ let r = we(t), a = (r ? s(r.path) : t.slice(0, t.lastIndexOf("/") + 1)) + n.trim();
222
222
  if (i(a) === i(t)) return Promise.resolve();
223
223
  if (r?.files.some((e) => i(e.path) === i(a))) {
224
224
  let t = new e("conflict", `${n} exists`, { status: 409 });
@@ -240,17 +240,17 @@ function x(r, y = {}) {
240
240
  path: i(e),
241
241
  wait: !1
242
242
  })), [X, b]),
243
- commit: ye,
243
+ commit: xe,
244
244
  discard: p(() => {
245
245
  B.current = [], z([]);
246
246
  for (let e of P.getSnapshot().items) P.remove(e.id);
247
247
  P.pause();
248
248
  }, [P]),
249
- updating: ue > 0,
249
+ updating: fe > 0,
250
250
  committing: V,
251
- busy: be,
252
- fileCount: xe,
253
- error: pe ?? W
251
+ busy: Se,
252
+ fileCount: Ce,
253
+ error: he ?? W
254
254
  };
255
255
  }
256
256
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"folder.js","names":[],"sources":["../../src/react/folder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { itemKey } from \"../client/media/ref.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitError, failureError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, Op, ReadResult, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport type { QueueItem } from \"../client/media/queue.js\";\nimport { filesFor, isPattern, ruleDir, ruleFor, screenFiles, uniqueName, uploadRules, type Screened } from \"../client/media/rules.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"./context.js\";\nimport { useMediaRead, type UseMediaRead } from \"./read.js\";\nimport { useUploadQueue, type UseUploadQueue } from \"./upload.js\";\n\nexport interface MediaFolderOptions {\n /**\n * The upload paths shown and added to, e.g. [\"images/{name}\",\n * \"videos/{name}\"]: one group each. Default every pattern path of the kind\n * the server does not name. A file goes to the first path whose rule takes\n * its type.\n */\n paths?: readonly string[];\n /**\n * \"manual\" (default): commit() adds the uploaded files. \"auto\": each\n * upload commits once those before it have, in the order added (a draft).\n */\n commit?: \"manual\" | \"auto\";\n /** A file's name under its path; default its own, numbered around names taken. */\n name?: (file: File) => string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Poll interval (ms) while uploads process. Default 2500. */\n poll?: number | false;\n /** Every failure (uploads, commits, updates, processing); default the provider's. */\n onError?: ContentKitErrorHandler;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface FolderGroup {\n /** The upload path, e.g. \"images/{name}\". */\n path: string;\n /** Its rules, once an editor read states them. */\n rule?: UploadRule;\n /** Its uploads in manifest order. */\n files: FileInfo[];\n}\n\nexport interface UseMediaFolder {\n /** The item; null until the host has one (files added meanwhile wait for it). */\n ref: RefBody | null;\n /** The options the folder was opened with. */\n options: MediaFolderOptions;\n /** The editor read of the item (every window, polled while processing). */\n read: UseMediaRead;\n groups: FolderGroup[];\n /** Every group's uploads. */\n uploads: FileInfo[];\n /** The upload queue; a file leaves it once the editor read lists it. */\n queue: UseUploadQueue;\n /**\n * Uploads on the server not yet added and not in the queue: an earlier\n * visit's, or another editor's. commit() adds them; remove() discards them.\n */\n pending: FileInfo[];\n /**\n * Files added before the item and its editor read (the upload rules) were\n * known. They are screened and queued when the rules arrive, or refused\n * when the read fails or states no rules (the actor may not edit the item).\n */\n waiting: readonly File[];\n /** Screens files against the rules and queues the rest (once the rules are known); refusals land in refused. */\n add: (files: Iterable<File>) => void;\n /** The last screening's refusals. */\n refused: Screened[\"refused\"];\n dismiss: () => void;\n /** Moves an upload to index among its group's uploads. */\n move: (path: string, index: number) => Promise<void>;\n remove: (paths: readonly string[]) => Promise<void>;\n /** Renames an upload within its path; the extension stays unless name has one. */\n rename: (path: string, name: string) => Promise<void>;\n edit: (path: string, edit: Edit | null) => Promise<void>;\n /** Uploads file in place of the upload at path (same position and meta, no edit). */\n replace: (path: string, file: File) => Promise<void>;\n /** Commits the uploaded files in queue order, then the pending ones. */\n commit: () => Promise<FileInfo[]>;\n /** Stops every upload, aborts its multipart upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** An update (move, remove, rename, edit, replace) is running. */\n updating: boolean;\n committing: boolean;\n /**\n * Work is in flight: files wait for the rules, uploads are queued or\n * running, an automatic commit is due, or a commit or update runs.\n * Leaving the page now interrupts it.\n */\n busy: boolean;\n /** The item's uploads plus the files on their way in (waiting, queued, uploading, uploaded, pending, being added); failed uploads are not counted. */\n fileCount: number;\n /** The last update's or commit's failure; cleared by the next success. */\n error?: ContentKitError;\n}\n\nconst ALL = { start: 0, end: Number.MAX_SAFE_INTEGER, chunk: 200 };\n\n/** The upload's name under its group (\"images/a.png\" in \"images/{name}\" is \"a.png\"). */\nexport const nameIn = (group: { path: string }, path: string) => path.slice(ruleDir(group.path).length);\n\n/** Its smallest derived image with a URL: a thumbnail for an editor. */\nexport function thumbnailOf(read: ReadResult | null | undefined, upload: FileInfo): FileInfo | undefined {\n let best: FileInfo | undefined;\n for (const f of read?.files ?? []) {\n if (f.upload || f.from !== upload.path || !f.url || !f.type.startsWith(\"image/\")) continue;\n if (!best || (f.w ?? Infinity) < (best.w ?? Infinity)) best = f;\n }\n return best;\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\nconst keyOf = (ref: RefBody | null) => (ref ? itemKey(ref) : \"\");\n\nconst noRules = new WeakMap<ReadResult, ContentKitError>();\n/** The refusal for files added to an item whose read carries no upload rules (one per read). */\nfunction notEditor(read: ReadResult): ContentKitError {\n let e = noRules.get(read);\n if (!e) noRules.set(read, (e = new ContentKitError(\"forbidden\", \"the editor read states no upload rules\", { status: 403 })));\n return e;\n}\n\n/** Queue items still on their way in: not failed, and not yet listed by the read. */\nfunction inbound(items: readonly QueueItem[], listed: ReadonlySet<string>) {\n return items.filter((i) => i.status !== \"failed\" && !(i.status === \"committed\" && listed.has(i.path)));\n}\n\n/**\n * An item's folder for its editors: the editor read, an upload queue, the\n * kind's upload rules screening files before they upload, and the updates\n * (move, remove, rename, edit, replace). Files added before the editor read\n * arrives wait for its rules; with a null ref they wait for the item too (a\n * draft created on the first drop). Uploads the worker fails after the read\n * are reported once (\"folder.process\"). Another ref gets a fresh queue.\n */\nexport function useMediaFolder(ref: RefBody | null | undefined, o: MediaFolderOptions = {}): UseMediaFolder {\n const client = useContentKitClient(o.client);\n const report = useErrorReporter(o.onError);\n const item = ref ?? null;\n const key = keyOf(item);\n const target = item ?? NO_ITEM;\n // Without an item there is nothing to read: a supplied null read never fetches.\n const read = useMediaRead(target, { editor: true, window: ALL, poll: o.poll, client, read: item ? undefined : null });\n const rules = uploadRules(read.read);\n const pathsKey = o.paths?.join(\"\\n\");\n const groupPaths = useMemo(\n () => (pathsKey !== undefined ? pathsKey.split(\"\\n\").filter(Boolean) : rules.filter((r) => isPattern(r.path) && !r.named).map((r) => r.path)),\n [pathsKey, rules],\n );\n const groups = useMemo((): FolderGroup[] => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return groupPaths.map((path) => ({ path, rule: rules.find((r) => r.path === path), files: filesFor(read.read, { path }, known) }));\n }, [groupPaths, rules, read.read]);\n const uploads = useMemo(() => groups.flatMap((g) => g.files), [groups]);\n const listed = useMemo(() => new Set(uploads.map((f) => f.path)), [uploads]);\n // Unattached uploads under the folder's paths: this session's while it adds them, or left by another.\n const unattached = useMemo(() => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return (read.read?.files ?? []).filter((f) => f.upload && f.unattached && groupPaths.includes(ruleFor(known, f.path)?.path ?? \"\"));\n }, [read.read, rules, groupPaths]);\n\n const raw = useUploadQueue({ client, ref: target, path: `${ruleDir(groupPaths[0] ?? \"{name}\")}{name}`, concurrency: o.concurrency });\n // The ItemUploadQueue itself is stable per item; the hook's wrapper is new every render.\n const q = raw.queue;\n // A committed file stays in the queue until the read lists it, so it never drops out of view.\n const items = useMemo(() => raw.items.filter((i) => !(i.status === \"committed\" && listed.has(i.path))), [raw.items, listed]);\n const queue = { ...raw, items };\n const leftover = useMemo(() => {\n const held = new Set(raw.items.map((i) => stem(i.path)));\n return unattached.filter((f) => !held.has(stem(f.path)));\n }, [unattached, raw.items]);\n const [refused, setRefused] = useState<Screened[\"refused\"]>([]);\n const [waiting, setWaiting] = useState<readonly File[]>([]);\n const pending = useRef<File[]>([]);\n const [updating, setUpdating] = useState(0);\n const [committing, setCommitting] = useState(false);\n const [error, setError] = useState<ContentKitError>();\n // An automatic commit that failed: no more until commit() or new files.\n const [stall, setStall] = useState<{ key: string; error: ContentKitError }>();\n const stalled = stall?.key === key ? stall.error : undefined;\n const opts = useRef(o);\n opts.current = o;\n // The rules are known once the item's editor read states them. A failed read\n // refuses what waits, and so does one without rules: the server answered a\n // viewer's read, so nothing uploads here.\n const ready = !!item && !!read.read?.uploads;\n const loadError = !item ? undefined : !read.read ? read.error : !read.read.uploads ? notEditor(read.read) : undefined;\n const live = useRef({ groups, ref: target, listed, unattached, leftover, ready, loadError });\n live.current = { groups, ref: target, listed, unattached, leftover, ready, loadError };\n\n const place = useCallback(\n (files: File[]) => {\n const { groups, listed, unattached, leftover } = live.current;\n const gs = groups.map((g) => g.rule ?? { path: g.path, types: [], max_bytes: 0 });\n const queued = inbound(q.getSnapshot().items, listed);\n const inGroup = (path: string, rule: UploadRule) => ruleFor(gs, path)?.path === rule.path;\n const s = screenFiles(files, gs, (rule) => groups.find((g) => g.path === rule.path)!.files.length + leftover.filter((f) => inGroup(f.path, rule)).length + queued.filter((i) => inGroup(i.path, rule)).length);\n const taken = new Map<string, string[]>();\n const takenIn = (rule: UploadRule) => {\n let t = taken.get(rule.path);\n if (!t) {\n const g = groups.find((x) => x.path === rule.path)!;\n // Unattached uploads hold their names too: a put over one is refused.\n t = [g.files, unattached].flat().filter((f) => inGroup(f.path, rule)).map((f) => nameIn(g, f.path));\n t.push(...queued.filter((i) => inGroup(i.path, rule)).map((i) => nameIn(g, i.path)));\n taken.set(rule.path, t);\n }\n return t;\n };\n const paths = new Map<File, string>();\n for (const { file, rule } of s.accepted) {\n const t = takenIn(rule);\n const name = opts.current.name?.(file) ?? uniqueName(file.name, t);\n t.push(name);\n paths.set(file, ruleDir(rule.path) + name);\n }\n if (paths.size) {\n q.add(paths.keys(), { path: (f) => paths.get(f)! });\n // New files lift a block and restart a queue discard() paused.\n q.start();\n setStall(undefined);\n }\n setRefused(s.refused);\n for (const r of s.refused) report(r.error, \"upload\", r.file.name);\n },\n [q, report],\n );\n\n // Screens what waits once the rules are known; a failed read refuses it (the read failure is reported once, as folder.load).\n const flush = useCallback(() => {\n const files = pending.current;\n const { ready, loadError } = live.current;\n if (!files.length || (!ready && !loadError)) return false;\n pending.current = [];\n setWaiting([]);\n if (ready) place(files);\n else setRefused(files.map((file) => ({ file, error: loadError! })));\n return true;\n }, [place]);\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const list = [...files];\n if (!list.length) return;\n pending.current = [...pending.current, ...list];\n if (!flush()) setWaiting(pending.current);\n },\n [flush],\n );\n useEffect(() => void flush(), [flush, ready, loadError]);\n\n const update = useCallback(\n async (ops: Op[], run?: () => Promise<unknown>) => {\n setUpdating((n) => n + 1);\n try {\n if (run) await run();\n else await client.media.commit(live.current.ref, ops);\n setError(undefined);\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") setError(err);\n report(err, \"folder.update\");\n throw err;\n } finally {\n setUpdating((n) => n - 1);\n }\n },\n [client, report],\n );\n\n const commit = useCallback(async () => {\n setCommitting(true);\n try {\n const files = await q.commit(undefined, { head: opts.current.commit === \"auto\", attach: live.current.leftover.map((f) => f.path) });\n setError(undefined);\n return files;\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"folder.commit\");\n throw err;\n } finally {\n setCommitting(false);\n }\n }, [q, report]);\n\n // Committed files leave the queue once the read lists them, or once a read lands without them.\n useEffect(() => {\n for (const i of raw.items) if (i.status === \"committed\" && (listed.has(i.path) || !read.loading)) q.remove(i.id);\n }, [raw.items, listed, read.loading, q]);\n\n // A draft commits the uploaded head of the queue as it finishes, and what\n // an earlier visit left pending once a read states it; a failed commit waits for commit().\n const auto = o.commit === \"auto\";\n const headReady = items.find((i) => i.status !== \"committed\")?.status === \"uploaded\" || (leftover.length > 0 && !read.loading);\n useEffect(() => {\n if (!auto || !headReady || committing || stalled) return;\n commit().catch((e: unknown) => setStall({ key, error: toContentKitError(e) }));\n }, [auto, headReady, committing, stalled, commit, key]);\n const manualCommit = useCallback(async () => {\n setStall(undefined);\n return commit();\n }, [commit]);\n\n // Each failed upload and queue-wide refusal is reported once; rows keep showing it.\n const seen = useRef(new Set<unknown>());\n useEffect(() => {\n for (const i of raw.items) {\n if (i.status !== \"failed\" || !i.error || seen.current.has(i.error)) continue;\n seen.current.add(i.error);\n report(i.error, \"upload\", i.file.name);\n }\n if (raw.blocked && !seen.current.has(raw.blocked)) {\n seen.current.add(raw.blocked);\n report(raw.blocked, \"upload\");\n }\n }, [raw.items, raw.blocked, report]);\n\n // Processing failures that appear after the item's first read.\n const failures = useRef<{ key: string; seen: Set<string> } | null>(null);\n useEffect(() => {\n if (!read.read) return;\n const failed = uploads.filter((f) => f.failed);\n if (failures.current?.key !== key) {\n failures.current = { key, seen: new Set(failed.map((f) => `${f.path}:${f.failed!.of}`)) };\n return;\n }\n for (const f of failed) {\n const id = `${f.path}:${f.failed!.of}`;\n if (failures.current.seen.has(id)) continue;\n failures.current.seen.add(id);\n report(failureError(f.failed!), \"folder.process\", f.path.split(\"/\").pop());\n }\n }, [read.read, uploads, report, key]);\n\n useEffect(() => void (read.error && report(read.error, \"folder.load\")), [read.error, report]);\n\n const busy =\n waiting.length > 0 ||\n committing ||\n updating > 0 ||\n (auto && headReady && !stalled) ||\n items.some((i) => i.status === \"uploading\" || (i.status === \"queued\" && !raw.blocked));\n const fileCount = uploads.length + waiting.length + inbound(items, listed).length + leftover.length;\n\n // Reads the latest groups, so callbacks stay stable.\n const groupOf = (path: string) => live.current.groups.find((g) => ruleFor([{ path: g.path }], path));\n return {\n ref: item,\n options: o,\n read,\n groups,\n uploads,\n queue,\n pending: leftover,\n waiting,\n add,\n refused,\n dismiss: useCallback(() => setRefused([]), []),\n move: useCallback((path, index) => update([{ op: \"move\", path, index }]), [update]),\n remove: useCallback((paths) => update(paths.map((path): Op => ({ op: \"remove\", path }))), [update]),\n rename: useCallback(\n (path, name) => {\n const g = groupOf(path);\n const to = (g ? ruleDir(g.path) : path.slice(0, path.lastIndexOf(\"/\") + 1)) + name.trim();\n if (stem(to) === stem(path)) return Promise.resolve();\n if (g?.files.some((f) => stem(f.path) === stem(to))) {\n const err = new ContentKitError(\"conflict\", `${name} exists`, { status: 409 });\n setError(err);\n return Promise.reject(err);\n }\n return update([{ op: \"rename\", path, to }]);\n },\n [update],\n ),\n edit: useCallback((path, edit) => update([{ op: \"edit\", path, ...(edit ? { edit } : {}) }]), [update]),\n replace: useCallback(\n (path, file) => update([], () => client.media.put(file, { ref: live.current.ref, path: stem(path), wait: false })),\n [update, client],\n ),\n commit: manualCommit,\n discard: useCallback(() => {\n pending.current = [];\n setWaiting([]);\n for (const i of q.getSnapshot().items) q.remove(i.id);\n q.pause();\n }, [q]),\n updating: updating > 0,\n committing,\n busy,\n fileCount,\n error: error ?? stalled,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"folder.js","names":[],"sources":["../../src/react/folder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { itemKey } from \"../client/media/ref.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitError, failureError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, Op, ReadResult, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport type { QueueItem } from \"../client/media/queue.js\";\nimport { filesFor, isPattern, ruleDir, ruleFor, screenFiles, sortPageFiles, uniqueName, uniquePageName, uploadRules, type Screened } from \"../client/media/rules.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"./context.js\";\nimport { useMediaRead, type UseMediaRead } from \"./read.js\";\nimport { useUploadQueue, type UseUploadQueue } from \"./upload.js\";\n\nexport interface MediaFolderOptions {\n /**\n * The upload paths shown and added to, e.g. [\"images/{name}\",\n * \"videos/{name}\"]: one group each. Default every pattern path of the kind\n * the server does not name. A file goes to the first path whose rule takes\n * its type.\n */\n paths?: readonly string[];\n /**\n * \"manual\" (default): commit() adds the uploaded files. \"auto\": each\n * upload commits once those before it have, in the order added (a draft).\n */\n commit?: \"manual\" | \"auto\";\n /** A file's name under its path; default its own, numbered around names taken. */\n name?: (file: File) => string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Poll interval (ms) while uploads process. Default 2500. */\n poll?: number | false;\n /** Every failure (uploads, commits, updates, processing); default the provider's. */\n onError?: ContentKitErrorHandler;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface FolderGroup {\n /** The upload path, e.g. \"images/{name}\". */\n path: string;\n /** Its rules, once an editor read states them. */\n rule?: UploadRule;\n /** Its uploads in manifest order. */\n files: FileInfo[];\n}\n\nexport interface UseMediaFolder {\n /** The item; null until the host has one (files added meanwhile wait for it). */\n ref: RefBody | null;\n /** The options the folder was opened with. */\n options: MediaFolderOptions;\n /** The editor read of the item (every window, polled while processing). */\n read: UseMediaRead;\n groups: FolderGroup[];\n /** Every group's uploads. */\n uploads: FileInfo[];\n /** The upload queue; a file leaves it once the editor read lists it. */\n queue: UseUploadQueue;\n /**\n * Uploads on the server not yet added and not in the queue: an earlier\n * visit's, or another editor's. commit() adds them; remove() discards them.\n */\n pending: FileInfo[];\n /**\n * Files added before the item and its editor read (the upload rules) were\n * known. They are screened and queued when the rules arrive, or refused\n * when the read fails or states no rules (the actor may not edit the item).\n */\n waiting: readonly File[];\n /** Screens files against the rules and queues the rest (once the rules are known); refusals land in refused. */\n add: (files: Iterable<File>) => void;\n /** The last screening's refusals. */\n refused: Screened[\"refused\"];\n dismiss: () => void;\n /** Moves an upload to index among its group's uploads. */\n move: (path: string, index: number) => Promise<void>;\n remove: (paths: readonly string[]) => Promise<void>;\n /** Renames an upload within its path; the extension stays unless name has one. */\n rename: (path: string, name: string) => Promise<void>;\n edit: (path: string, edit: Edit | null) => Promise<void>;\n /** Uploads file in place of the upload at path (same position and meta, no edit). */\n replace: (path: string, file: File) => Promise<void>;\n /** Commits the uploaded files in queue order, then the pending ones. */\n commit: () => Promise<FileInfo[]>;\n /** Stops every upload, aborts its multipart upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** An update (move, remove, rename, edit, replace) is running. */\n updating: boolean;\n committing: boolean;\n /**\n * Work is in flight: files wait for the rules, uploads are queued or\n * running, an automatic commit is due, or a commit or update runs.\n * Leaving the page now interrupts it.\n */\n busy: boolean;\n /** The item's uploads plus the files on their way in (waiting, queued, uploading, uploaded, pending, being added); failed uploads are not counted. */\n fileCount: number;\n /** The last update's or commit's failure; cleared by the next success. */\n error?: ContentKitError;\n}\n\nconst ALL = { start: 0, end: Number.MAX_SAFE_INTEGER, chunk: 200 };\n\n/** The upload's name under its group (\"images/a.png\" in \"images/{name}\" is \"a.png\"). */\nexport const nameIn = (group: { path: string }, path: string) => path.slice(ruleDir(group.path).length);\n\n/** Its smallest derived image with a URL: a thumbnail for an editor. */\nexport function thumbnailOf(read: ReadResult | null | undefined, upload: FileInfo): FileInfo | undefined {\n let best: FileInfo | undefined;\n for (const f of read?.files ?? []) {\n if (f.upload || f.from !== upload.path || !f.url || !f.type.startsWith(\"image/\")) continue;\n if (!best || (f.w ?? Infinity) < (best.w ?? Infinity)) best = f;\n }\n return best;\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\nconst keyOf = (ref: RefBody | null) => (ref ? itemKey(ref) : \"\");\n\nconst noRules = new WeakMap<ReadResult, ContentKitError>();\n/** The refusal for files added to an item whose read carries no upload rules (one per read). */\nfunction notEditor(read: ReadResult): ContentKitError {\n let e = noRules.get(read);\n if (!e) noRules.set(read, (e = new ContentKitError(\"forbidden\", \"the editor read states no upload rules\", { status: 403 })));\n return e;\n}\n\n/** Queue items still on their way in: not failed, and not yet listed by the read. */\nfunction inbound(items: readonly QueueItem[], listed: ReadonlySet<string>) {\n return items.filter((i) => i.status !== \"failed\" && !(i.status === \"committed\" && listed.has(i.path)));\n}\n\n/**\n * An item's folder for its editors: the editor read, an upload queue, the\n * kind's upload rules screening files before they upload, and the updates\n * (move, remove, rename, edit, replace). Files added before the editor read\n * arrives wait for its rules; with a null ref they wait for the item too (a\n * draft created on the first drop). Uploads the worker fails after the read\n * are reported once (\"folder.process\"). Another ref gets a fresh queue.\n */\nexport function useMediaFolder(ref: RefBody | null | undefined, o: MediaFolderOptions = {}): UseMediaFolder {\n const client = useContentKitClient(o.client);\n const report = useErrorReporter(o.onError);\n const item = ref ?? null;\n const key = keyOf(item);\n const target = item ?? NO_ITEM;\n // Without an item there is nothing to read: a supplied null read never fetches.\n const read = useMediaRead(target, { editor: true, window: ALL, poll: o.poll, client, read: item ? undefined : null });\n const rules = uploadRules(read.read);\n const pathsKey = o.paths?.join(\"\\n\");\n const groupPaths = useMemo(\n () => (pathsKey !== undefined ? pathsKey.split(\"\\n\").filter(Boolean) : rules.filter((r) => isPattern(r.path) && !r.named).map((r) => r.path)),\n [pathsKey, rules],\n );\n const groups = useMemo((): FolderGroup[] => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return groupPaths.map((path) => ({ path, rule: rules.find((r) => r.path === path), files: filesFor(read.read, { path }, known) }));\n }, [groupPaths, rules, read.read]);\n const uploads = useMemo(() => groups.flatMap((g) => g.files), [groups]);\n const listed = useMemo(() => new Set(uploads.map((f) => f.path)), [uploads]);\n // Unattached uploads under the folder's paths: this session's while it adds them, or left by another.\n const unattached = useMemo(() => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return (read.read?.files ?? []).filter((f) => f.upload && f.unattached && groupPaths.includes(ruleFor(known, f.path)?.path ?? \"\"));\n }, [read.read, rules, groupPaths]);\n\n const raw = useUploadQueue({ client, ref: target, path: `${ruleDir(groupPaths[0] ?? \"{name}\")}{name}`, concurrency: o.concurrency });\n // The ItemUploadQueue itself is stable per item; the hook's wrapper is new every render.\n const q = raw.queue;\n // A committed file stays in the queue until the read lists it, so it never drops out of view.\n const items = useMemo(() => raw.items.filter((i) => !(i.status === \"committed\" && listed.has(i.path))), [raw.items, listed]);\n const queue = { ...raw, items };\n const leftover = useMemo(() => {\n const held = new Set(raw.items.map((i) => stem(i.path)));\n return unattached.filter((f) => !held.has(stem(f.path)));\n }, [unattached, raw.items]);\n const [refused, setRefused] = useState<Screened[\"refused\"]>([]);\n const [waiting, setWaiting] = useState<readonly File[]>([]);\n const pending = useRef<File[]>([]);\n const [updating, setUpdating] = useState(0);\n const [committing, setCommitting] = useState(false);\n const [error, setError] = useState<ContentKitError>();\n // An automatic commit that failed: no more until commit() or new files.\n const [stall, setStall] = useState<{ key: string; error: ContentKitError }>();\n const stalled = stall?.key === key ? stall.error : undefined;\n const opts = useRef(o);\n opts.current = o;\n // The rules are known once the item's editor read states them. A failed read\n // refuses what waits, and so does one without rules: the server answered a\n // viewer's read, so nothing uploads here.\n const ready = !!item && !!read.read?.uploads;\n const loadError = !item ? undefined : !read.read ? read.error : !read.read.uploads ? notEditor(read.read) : undefined;\n const live = useRef({ groups, ref: target, listed, unattached, leftover, ready, loadError });\n live.current = { groups, ref: target, listed, unattached, leftover, ready, loadError };\n\n const place = useCallback(\n (files: File[]) => {\n const { groups, listed, unattached, leftover } = live.current;\n const gs = groups.map((g) => g.rule ?? { path: g.path, types: [], max_bytes: 0 });\n const queued = inbound(q.getSnapshot().items, listed);\n const inGroup = (path: string, rule: UploadRule) => ruleFor(gs, path)?.path === rule.path;\n const s = screenFiles(files, gs, (rule) => groups.find((g) => g.path === rule.path)!.files.length + leftover.filter((f) => inGroup(f.path, rule)).length + queued.filter((i) => inGroup(i.path, rule)).length);\n const taken = new Map<string, string[]>();\n const takenIn = (rule: UploadRule) => {\n let t = taken.get(rule.path);\n if (!t) {\n const g = groups.find((x) => x.path === rule.path)!;\n // Unattached uploads hold their names too: a put over one is refused.\n t = [g.files, unattached].flat().filter((f) => inGroup(f.path, rule)).map((f) => nameIn(g, f.path));\n t.push(...queued.filter((i) => inGroup(i.path, rule)).map((i) => nameIn(g, i.path)));\n taken.set(rule.path, t);\n }\n return t;\n };\n const paths = new Map<File, string>();\n for (const { file, rule } of sortPageFiles(s.accepted)) {\n const t = takenIn(rule);\n const requested = opts.current.name?.(file);\n const name = rule.page ? uniquePageName(requested ?? file.name, t) : requested ?? uniqueName(file.name, t);\n t.push(name);\n paths.set(file, ruleDir(rule.path) + name);\n }\n if (paths.size) {\n q.add(paths.keys(), { path: (f) => paths.get(f)! });\n // New files lift a block and restart a queue discard() paused.\n q.start();\n setStall(undefined);\n }\n setRefused(s.refused);\n for (const r of s.refused) report(r.error, \"upload\", r.file.name);\n },\n [q, report],\n );\n\n // Screens what waits once the rules are known; a failed read refuses it (the read failure is reported once, as folder.load).\n const flush = useCallback(() => {\n const files = pending.current;\n const { ready, loadError } = live.current;\n if (!files.length || (!ready && !loadError)) return false;\n pending.current = [];\n setWaiting([]);\n if (ready) place(files);\n else setRefused(files.map((file) => ({ file, error: loadError! })));\n return true;\n }, [place]);\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const list = [...files];\n if (!list.length) return;\n pending.current = [...pending.current, ...list];\n if (!flush()) setWaiting(pending.current);\n },\n [flush],\n );\n useEffect(() => void flush(), [flush, ready, loadError]);\n\n const update = useCallback(\n async (ops: Op[], run?: () => Promise<unknown>) => {\n setUpdating((n) => n + 1);\n try {\n if (run) await run();\n else await client.media.commit(live.current.ref, ops);\n setError(undefined);\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") setError(err);\n report(err, \"folder.update\");\n throw err;\n } finally {\n setUpdating((n) => n - 1);\n }\n },\n [client, report],\n );\n\n const commit = useCallback(async () => {\n setCommitting(true);\n try {\n const files = await q.commit(undefined, { head: opts.current.commit === \"auto\", attach: live.current.leftover.map((f) => f.path) });\n setError(undefined);\n return files;\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"folder.commit\");\n throw err;\n } finally {\n setCommitting(false);\n }\n }, [q, report]);\n\n // Committed files leave the queue once the read lists them, or once a read lands without them.\n useEffect(() => {\n for (const i of raw.items) if (i.status === \"committed\" && (listed.has(i.path) || !read.loading)) q.remove(i.id);\n }, [raw.items, listed, read.loading, q]);\n\n // A draft commits the uploaded head of the queue as it finishes, and what\n // an earlier visit left pending once a read states it; a failed commit waits for commit().\n const auto = o.commit === \"auto\";\n const headReady = items.find((i) => i.status !== \"committed\")?.status === \"uploaded\" || (leftover.length > 0 && !read.loading);\n useEffect(() => {\n if (!auto || !headReady || committing || stalled) return;\n commit().catch((e: unknown) => setStall({ key, error: toContentKitError(e) }));\n }, [auto, headReady, committing, stalled, commit, key]);\n const manualCommit = useCallback(async () => {\n setStall(undefined);\n return commit();\n }, [commit]);\n\n // Each failed upload and queue-wide refusal is reported once; rows keep showing it.\n const seen = useRef(new Set<unknown>());\n useEffect(() => {\n for (const i of raw.items) {\n if (i.status !== \"failed\" || !i.error || seen.current.has(i.error)) continue;\n seen.current.add(i.error);\n report(i.error, \"upload\", i.file.name);\n }\n if (raw.blocked && !seen.current.has(raw.blocked)) {\n seen.current.add(raw.blocked);\n report(raw.blocked, \"upload\");\n }\n }, [raw.items, raw.blocked, report]);\n\n // Processing failures that appear after the item's first read.\n const failures = useRef<{ key: string; seen: Set<string> } | null>(null);\n useEffect(() => {\n if (!read.read) return;\n const failed = uploads.filter((f) => f.failed);\n if (failures.current?.key !== key) {\n failures.current = { key, seen: new Set(failed.map((f) => `${f.path}:${f.failed!.of}`)) };\n return;\n }\n for (const f of failed) {\n const id = `${f.path}:${f.failed!.of}`;\n if (failures.current.seen.has(id)) continue;\n failures.current.seen.add(id);\n report(failureError(f.failed!), \"folder.process\", f.path.split(\"/\").pop());\n }\n }, [read.read, uploads, report, key]);\n\n useEffect(() => void (read.error && report(read.error, \"folder.load\")), [read.error, report]);\n\n const busy =\n waiting.length > 0 ||\n committing ||\n updating > 0 ||\n (auto && headReady && !stalled) ||\n items.some((i) => i.status === \"uploading\" || (i.status === \"queued\" && !raw.blocked));\n const fileCount = uploads.length + waiting.length + inbound(items, listed).length + leftover.length;\n\n // Reads the latest groups, so callbacks stay stable.\n const groupOf = (path: string) => live.current.groups.find((g) => ruleFor([{ path: g.path }], path));\n return {\n ref: item,\n options: o,\n read,\n groups,\n uploads,\n queue,\n pending: leftover,\n waiting,\n add,\n refused,\n dismiss: useCallback(() => setRefused([]), []),\n move: useCallback((path, index) => update([{ op: \"move\", path, index }]), [update]),\n remove: useCallback((paths) => update(paths.map((path): Op => ({ op: \"remove\", path }))), [update]),\n rename: useCallback(\n (path, name) => {\n const g = groupOf(path);\n const to = (g ? ruleDir(g.path) : path.slice(0, path.lastIndexOf(\"/\") + 1)) + name.trim();\n if (stem(to) === stem(path)) return Promise.resolve();\n if (g?.files.some((f) => stem(f.path) === stem(to))) {\n const err = new ContentKitError(\"conflict\", `${name} exists`, { status: 409 });\n setError(err);\n return Promise.reject(err);\n }\n return update([{ op: \"rename\", path, to }]);\n },\n [update],\n ),\n edit: useCallback((path, edit) => update([{ op: \"edit\", path, ...(edit ? { edit } : {}) }]), [update]),\n replace: useCallback(\n (path, file) => update([], () => client.media.put(file, { ref: live.current.ref, path: stem(path), wait: false })),\n [update, client],\n ),\n commit: manualCommit,\n discard: useCallback(() => {\n pending.current = [];\n setWaiting([]);\n for (const i of q.getSnapshot().items) q.remove(i.id);\n q.pause();\n }, [q]),\n updating: updating > 0,\n committing,\n busy,\n fileCount,\n error: error ?? stalled,\n };\n}\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  export { ContentKitProvider, type ContentKitProviderProps } from './provider.js';
2
2
  export { useReaderPreferences } from './reader-preferences.js';
3
+ export { usePageReview, type UsePageReview } from './page-review.js';
3
4
  export { useContentKit, useContentKitClient, useContentURLs, useErrorReporter, useGuarded, GuardContext, useOptionalContentKitClient, type ContentKitContextValue, type ContentKitErrorHandler, type ContentKitErrorInfo, type ContentKitGuard, type ContentKitOperation, type Navigate, type SocialOperation, } from './context.js';
4
5
  export { useMediaRead, type MediaReadOptions, type ReadWindow, type UseMediaRead } from './read.js';
5
6
  export { nameIn, thumbnailOf, useMediaFolder, type FolderGroup, type MediaFolderOptions, type UseMediaFolder } from './folder.js';
@@ -0,0 +1,14 @@
1
+ import { ContentKitClient } from '../client/client.js';
2
+ import { ContentKitError } from '../client/errors.js';
3
+ import { PageReview, PageReviewPatch } from '../client/generated/wire.js';
4
+ export interface UsePageReview {
5
+ review: PageReview | null;
6
+ loading: boolean;
7
+ error?: ContentKitError;
8
+ reload: () => void;
9
+ update: (patch: PageReviewPatch) => Promise<PageReview>;
10
+ }
11
+ /** A version's retained page sources and reversible review choices. */
12
+ export declare function usePageReview(content: string, version: string, o?: {
13
+ client?: ContentKitClient;
14
+ }): UsePageReview;
@@ -0,0 +1,36 @@
1
+ import { toContentKitError as e } from "../client/errors.js";
2
+ import { keyOf as t, useContentScope as n, useResource as r } from "./use-resource.js";
3
+ import { useCallback as i } from "react";
4
+ //#region src/react/page-review.ts
5
+ function a(a, o, s = {}) {
6
+ let { client: c, store: l, scope: u } = n(s.client), d = t("page-review", u, a, o), f = r(l, d, {
7
+ type: "page-review",
8
+ id: o,
9
+ scope: a
10
+ }, (e) => c.versions.pages(a, o, e)), { reload: p } = f, m = i(async (t) => {
11
+ try {
12
+ let e = await c.versions.updatePages(a, o, t);
13
+ return l.set(d, e), e;
14
+ } catch (t) {
15
+ throw e(t).status === 409 && p(), t;
16
+ }
17
+ }, [
18
+ c,
19
+ a,
20
+ o,
21
+ l,
22
+ d,
23
+ p
24
+ ]);
25
+ return {
26
+ review: f.data ?? null,
27
+ loading: f.loading || !f.loaded,
28
+ error: f.error,
29
+ reload: f.reload,
30
+ update: m
31
+ };
32
+ }
33
+ //#endregion
34
+ export { a as usePageReview };
35
+
36
+ //# sourceMappingURL=page-review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-review.js","names":[],"sources":["../../src/react/page-review.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { PageReview, PageReviewPatch } from \"../client/generated/wire.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\nexport interface UsePageReview {\n review: PageReview | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n update: (patch: PageReviewPatch) => Promise<PageReview>;\n}\n\n/** A version's retained page sources and reversible review choices. */\nexport function usePageReview(content: string, version: string, o: { client?: ContentKitClient } = {}): UsePageReview {\n const { client, store, scope } = useContentScope(o.client);\n const key = keyOf(\"page-review\", scope, content, version);\n const r = useResource<PageReview>(store, key, { type: \"page-review\", id: version, scope: content }, (signal) => client.versions.pages(content, version, signal));\n const { reload } = r;\n const update = useCallback(async (patch: PageReviewPatch) => {\n try {\n const next = await client.versions.updatePages(content, version, patch);\n store.set(key, next);\n return next;\n } catch (error) {\n if (toContentKitError(error).status === 409) reload();\n throw error;\n }\n }, [client, content, version, store, key, reload]);\n return { review: r.data ?? null, loading: r.loading || !r.loaded, error: r.error, reload: r.reload, update };\n}\n"],"mappings":";;;;AAeA,SAAgB,EAAc,GAAiB,GAAiB,IAAmC,CAAC,GAAkB;CACpH,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAM,EAAM,eAAe,GAAO,GAAS,CAAO,GAClD,IAAI,EAAwB,GAAO,GAAK;EAAE,MAAM;EAAe,IAAI;EAAS,OAAO;CAAQ,IAAI,MAAW,EAAO,SAAS,MAAM,GAAS,GAAS,CAAM,CAAC,GACzJ,EAAE,cAAW,GACb,IAAS,EAAY,OAAO,MAA2B;EAC3D,IAAI;GACF,IAAM,IAAO,MAAM,EAAO,SAAS,YAAY,GAAS,GAAS,CAAK;GAEtE,OADA,EAAM,IAAI,GAAK,CAAI,GACZ;EACT,SAAS,GAAO;GAEd,MADI,EAAkB,CAAK,CAAC,CAAC,WAAW,OAAK,EAAO,GAC9C;EACR;CACF,GAAG;EAAC;EAAQ;EAAS;EAAS;EAAO;EAAK;CAAM,CAAC;CACjD,OAAO;EAAE,QAAQ,EAAE,QAAQ;EAAM,SAAS,EAAE,WAAW,CAAC,EAAE;EAAQ,OAAO,EAAE;EAAO,QAAQ,EAAE;EAAQ;CAAO;AAC7G"}
@@ -7,8 +7,10 @@ export interface UsePresets {
7
7
  presets: PresetRule[];
8
8
  loaded: boolean;
9
9
  error?: ContentKitError;
10
+ /** Retry a failed read; successful public presets remain cached. */
11
+ reload: () => void;
10
12
  }
11
- /** The public presets (`GET /media/presets`), fetched once per client. */
13
+ /** Public presets, shared across consumers; failed reads can be retried. */
12
14
  export declare function usePresets(o?: {
13
15
  client?: ContentKitClient | null;
14
16
  }): UsePresets;