@openrails/contentkit-ui 0.70.0 → 0.72.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.
- package/README.md +98 -10
- package/dist/client/content/bans.d.ts +2 -0
- package/dist/client/content/bans.js +24 -12
- package/dist/client/content/bans.js.map +1 -1
- package/dist/client/content/comments.d.ts +4 -2
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/polls.d.ts +10 -3
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/content/posts.d.ts +4 -4
- package/dist/client/content/posts.js.map +1 -1
- package/dist/client/generated/error-codes.js +2 -2
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.d.ts +146 -120
- package/dist/client/generated/routes.js +245 -117
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +5 -0
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/taxonomy.d.ts +6 -2
- package/dist/client/taxonomy.js +7 -0
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/components/admin/poll-manager.d.ts +28 -0
- package/dist/components/admin/poll-manager.js +297 -0
- package/dist/components/admin/poll-manager.js.map +1 -0
- package/dist/components/admin/post-manager.d.ts +45 -0
- package/dist/components/admin/post-manager.js +602 -0
- package/dist/components/admin/post-manager.js.map +1 -0
- package/dist/components/admin/shell.d.ts +73 -0
- package/dist/components/admin/shell.js +148 -0
- package/dist/components/admin/shell.js.map +1 -0
- package/dist/components/admin/taxonomy-manager.d.ts +54 -0
- package/dist/components/admin/taxonomy-manager.js +976 -0
- package/dist/components/admin/taxonomy-manager.js.map +1 -0
- package/dist/components/admin/user-comment-standing.d.ts +25 -0
- package/dist/components/admin/user-comment-standing.js +229 -0
- package/dist/components/admin/user-comment-standing.js.map +1 -0
- package/dist/components/social/bans.d.ts +5 -2
- package/dist/components/social/bans.js +132 -134
- package/dist/components/social/bans.js.map +1 -1
- package/dist/components/social/index.d.ts +1 -1
- package/dist/components/social/moderation.d.ts +11 -13
- package/dist/components/social/moderation.js +299 -218
- package/dist/components/social/moderation.js.map +1 -1
- package/dist/components/social/parts.d.ts +3 -2
- package/dist/components/social/parts.js +74 -48
- package/dist/components/social/parts.js.map +1 -1
- package/dist/components/social/poll-editor.d.ts +1 -1
- package/dist/components/social/poll-editor.js +1 -0
- package/dist/components/social/poll-editor.js.map +1 -1
- package/dist/editor/index.d.ts +1 -0
- package/dist/editor/rich-text-editor.d.ts +13 -0
- package/dist/editor/rich-text-editor.js +228 -0
- package/dist/editor/rich-text-editor.js.map +1 -0
- package/dist/editor.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +13 -8
- package/dist/locales/admin/de.d.ts +2 -0
- package/dist/locales/admin/de.js +176 -0
- package/dist/locales/admin/de.js.map +1 -0
- package/dist/locales/admin/en.d.ts +171 -0
- package/dist/locales/admin/en.js +176 -0
- package/dist/locales/admin/en.js.map +1 -0
- package/dist/locales/admin/es.d.ts +2 -0
- package/dist/locales/admin/es.js +176 -0
- package/dist/locales/admin/es.js.map +1 -0
- package/dist/locales/admin/ja.d.ts +2 -0
- package/dist/locales/admin/ja.js +176 -0
- package/dist/locales/admin/ja.js.map +1 -0
- package/dist/locales/admin/ko.d.ts +2 -0
- package/dist/locales/admin/ko.js +176 -0
- package/dist/locales/admin/ko.js.map +1 -0
- package/dist/locales/admin/zh.d.ts +2 -0
- package/dist/locales/admin/zh.js +176 -0
- package/dist/locales/admin/zh.js.map +1 -0
- package/dist/locales/de.js +5 -3
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts +176 -0
- package/dist/locales/en.js +5 -3
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +5 -3
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +5 -3
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +5 -3
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/social/de.js +8 -1
- package/dist/locales/social/de.js.map +1 -1
- package/dist/locales/social/en.d.ts +7 -0
- package/dist/locales/social/en.js +8 -1
- package/dist/locales/social/en.js.map +1 -1
- package/dist/locales/social/es.js +8 -1
- package/dist/locales/social/es.js.map +1 -1
- package/dist/locales/social/ja.js +8 -1
- package/dist/locales/social/ja.js.map +1 -1
- package/dist/locales/social/ko.js +8 -1
- package/dist/locales/social/ko.js.map +1 -1
- package/dist/locales/social/zh.js +8 -1
- package/dist/locales/social/zh.js.map +1 -1
- package/dist/locales/zh.js +5 -3
- package/dist/locales/zh.js.map +1 -1
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +23 -19
- package/dist/provider.js.map +1 -1
- package/dist/react/admin.d.ts +55 -0
- package/dist/react/admin.js +97 -0
- package/dist/react/admin.js.map +1 -0
- package/dist/react/comments.d.ts +18 -2
- package/dist/react/comments.js +24 -8
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +7 -1
- package/dist/react/context.js +6 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/index.d.ts +4 -3
- package/dist/react/polls.d.ts +2 -2
- package/dist/react/polls.js +88 -87
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.d.ts +3 -3
- package/dist/react/posts.js +123 -122
- package/dist/react/posts.js.map +1 -1
- package/dist/react/resources.js +3 -0
- package/dist/react/resources.js.map +1 -1
- package/dist/react/use-resource.d.ts +16 -0
- package/dist/react/use-resource.js +20 -1
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +21 -20
- package/dist/styles.css +1 -1
- package/package.json +37 -2
package/dist/react/comments.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ContentKitClient } from '../client/client.js';
|
|
2
2
|
import { BanScope, Decision, HeldKind, Reaction, Sort } from '../client/content/types.js';
|
|
3
|
+
import { ContentKitError } from '../client/errors.js';
|
|
3
4
|
import { AdminComment, BanInput, Comment, CommentBan, CommentStanding, FeedItem, HeldItem, ReactionCounts, RefBody } from '../client/generated/wire.js';
|
|
4
5
|
import { UseList } from './use-resource.js';
|
|
5
6
|
/** Counts after the caller's reaction changes from counts.mine to value. */
|
|
@@ -38,7 +39,7 @@ export declare function useCanComment(ref: RefBody, o?: {
|
|
|
38
39
|
}): {
|
|
39
40
|
standing: CommentStanding | null;
|
|
40
41
|
loading: boolean;
|
|
41
|
-
error:
|
|
42
|
+
error: ContentKitError | undefined;
|
|
42
43
|
reload: () => void;
|
|
43
44
|
};
|
|
44
45
|
/** The newest published comments across the site, with their items (the host shows titles). */
|
|
@@ -51,9 +52,10 @@ export interface UseAdminComments extends UseList<AdminComment> {
|
|
|
51
52
|
restore: (id: string) => Promise<void>;
|
|
52
53
|
react: (comment: Comment, value: Reaction) => Promise<void>;
|
|
53
54
|
}
|
|
54
|
-
/** Staff: every comment, newest first, deleted, held and rejected ones with their bodies (
|
|
55
|
+
/** 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. */
|
|
55
56
|
export declare function useAdminComments(o?: {
|
|
56
57
|
contentKind?: string;
|
|
58
|
+
author?: string;
|
|
57
59
|
pageSize?: number;
|
|
58
60
|
client?: ContentKitClient;
|
|
59
61
|
}): UseAdminComments;
|
|
@@ -80,3 +82,17 @@ export declare function useCommentBans(o?: {
|
|
|
80
82
|
enabled?: boolean;
|
|
81
83
|
client?: ContentKitClient;
|
|
82
84
|
}): UseCommentBans;
|
|
85
|
+
export interface UseGlobalCommentBan {
|
|
86
|
+
/** The user's site-wide ban, expired or not; null when none. */
|
|
87
|
+
ban: CommentBan | null;
|
|
88
|
+
loading: boolean;
|
|
89
|
+
error?: ContentKitError;
|
|
90
|
+
reload: () => void;
|
|
91
|
+
/** Bans them, or replaces their ban. */
|
|
92
|
+
save: (input?: BanInput) => Promise<CommentBan>;
|
|
93
|
+
lift: () => Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
/** Staff: one user's site-wide comment ban (root:comments:ban). */
|
|
96
|
+
export declare function useGlobalCommentBan(user: string, o?: {
|
|
97
|
+
client?: ContentKitClient;
|
|
98
|
+
}): UseGlobalCommentBan;
|
package/dist/react/comments.js
CHANGED
|
@@ -103,19 +103,20 @@ function p(i = {}) {
|
|
|
103
103
|
return r(o, e("latest", s, c), { type: "latest" }, t(c, (e, t) => a.comments.latest(e, t), !0));
|
|
104
104
|
}
|
|
105
105
|
function m(i = {}) {
|
|
106
|
-
let { client: o, store: s, scope: c } = n(i.client), u = i.pageSize ?? 25, d = i.contentKind || void 0, f = r(s, e("admin-comments", c, d, u), { type: "admin-comments" }, t(u, (e, t) => o.comments.adminList({
|
|
106
|
+
let { client: o, store: s, scope: c } = n(i.client), u = i.pageSize ?? 25, d = i.contentKind || void 0, f = i.author || void 0, p = r(s, e("admin-comments", c, d, f, u), { type: "admin-comments" }, t(u, (e, t) => o.comments.adminList({
|
|
107
107
|
...e,
|
|
108
|
-
contentKind: d
|
|
109
|
-
|
|
108
|
+
contentKind: d,
|
|
109
|
+
author: f
|
|
110
|
+
}, t))), m = a((e) => o.comments.delete(e), [o]), h = a((e) => o.comments.restore(e), [o]), g = a((e, t) => l(o, s, c, e, t), [
|
|
110
111
|
o,
|
|
111
112
|
s,
|
|
112
113
|
c
|
|
113
114
|
]);
|
|
114
115
|
return {
|
|
115
|
-
...
|
|
116
|
-
remove:
|
|
117
|
-
restore:
|
|
118
|
-
react:
|
|
116
|
+
...p,
|
|
117
|
+
remove: m,
|
|
118
|
+
restore: h,
|
|
119
|
+
react: g
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
function h(t = {}) {
|
|
@@ -149,7 +150,22 @@ function g(i = {}) {
|
|
|
149
150
|
lift: p
|
|
150
151
|
};
|
|
151
152
|
}
|
|
153
|
+
function _(t, r = {}) {
|
|
154
|
+
let { client: o, store: s, scope: c } = n(r.client), l = i(s, e("ban", c, t), {
|
|
155
|
+
type: "bans",
|
|
156
|
+
scope: "global",
|
|
157
|
+
id: t
|
|
158
|
+
}, (e) => o.bans.get("global", t, e)), u = a((e) => o.bans.ban("global", t, e), [o, t]), d = a(() => o.bans.lift("global", t), [o, t]);
|
|
159
|
+
return {
|
|
160
|
+
ban: l.data ?? null,
|
|
161
|
+
loading: l.loading || !l.loaded,
|
|
162
|
+
error: l.error,
|
|
163
|
+
reload: l.reload,
|
|
164
|
+
save: u,
|
|
165
|
+
lift: d
|
|
166
|
+
};
|
|
167
|
+
}
|
|
152
168
|
//#endregion
|
|
153
|
-
export { m as useAdminComments, f as useCanComment, g as useCommentBans, d as useCommentReplies, u as useComments, p as useLatestComments, h as useModerationQueue, o as withReaction };
|
|
169
|
+
export { m as useAdminComments, f as useCanComment, g as useCommentBans, d as useCommentReplies, u as useComments, _ as useGlobalCommentBan, p as useLatestComments, h as useModerationQueue, o as withReaction };
|
|
154
170
|
|
|
155
171
|
//# sourceMappingURL=comments.js.map
|
|
@@ -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 } from \"../client/content/types.js\";\nimport type { AdminComment, BanInput, Comment, CommentBan, CommentStanding, FeedItem, HeldItem, ReactionCounts, RefBody } 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 /** 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/** An item's top-level comments, a page at a time, with every write; new comments and replies land in place. */\nexport function useComments(ref: RefBody, 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 key = keyOf(\"comments\", scope, ref.kind, ref.id, sort, size);\n const list = useList(store, key, { type: \"comments\", ref: { kind: ref.kind, id: ref.id } }, offsetPages(size, (q, signal) => client.comments.list(ref, { ...q, sort }, signal)));\n const { kind, id } = ref;\n const post = useCallback<UseComments[\"post\"]>(\n (body, p = {}) => client.comments.create({ kind, id }, { body, reply_to_id: p.replyTo, anon_name: p.anonName }),\n [client, kind, id],\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 item, the ban that stops it, and what it may do to others' comments. */\nexport function useCanComment(ref: RefBody, o: { client?: ContentKitClient } = {}) {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<CommentStanding>(store, keyOf(\"standing\", scope, ref.kind, ref.id), { type: \"standing\", ref: { kind: ref.kind, id: ref.id } }, (s) =>\n client.comments.standing(ref, 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 (CommentModerate). */\nexport function useAdminComments(o: { contentKind?: 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 list = useList(\n store,\n keyOf(\"admin-comments\", scope, kind, size),\n { type: \"admin-comments\" },\n offsetPages(size, (q, signal) => client.comments.adminList({ ...q, contentKind: kind }, 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 posts 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"],"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 } from \"../client/content/types.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { AdminComment, BanInput, Comment, CommentBan, CommentStanding, FeedItem, HeldItem, ReactionCounts, RefBody } 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 /** 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/** An item's top-level comments, a page at a time, with every write; new comments and replies land in place. */\nexport function useComments(ref: RefBody, 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 key = keyOf(\"comments\", scope, ref.kind, ref.id, sort, size);\n const list = useList(store, key, { type: \"comments\", ref: { kind: ref.kind, id: ref.id } }, offsetPages(size, (q, signal) => client.comments.list(ref, { ...q, sort }, signal)));\n const { kind, id } = ref;\n const post = useCallback<UseComments[\"post\"]>(\n (body, p = {}) => client.comments.create({ kind, id }, { body, reply_to_id: p.replyTo, anon_name: p.anonName }),\n [client, kind, id],\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 item, the ban that stops it, and what it may do to others' comments. */\nexport function useCanComment(ref: RefBody, o: { client?: ContentKitClient } = {}) {\n const { client, store, scope } = useContentScope(o.client);\n const r = useResource<CommentStanding>(store, keyOf(\"standing\", scope, ref.kind, ref.id), { type: \"standing\", ref: { kind: ref.kind, id: ref.id } }, (s) =>\n client.comments.standing(ref, 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 posts 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 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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"}
|
package/dist/react/context.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { ReadStore } from './store.js';
|
|
|
5
5
|
/** What failed: a component's or hook's save, load or render step. */
|
|
6
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;
|
|
7
7
|
/** What failed in a content component: a load or a write. */
|
|
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" | "post.save" | "moderation.load" | "moderation.resolve";
|
|
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" | "post.save" | "moderation.load" | "moderation.resolve" | "taxonomy.load" | "taxonomy.save";
|
|
9
9
|
/** The failure's context: the operation and, for one file, its name. */
|
|
10
10
|
export interface ContentKitErrorInfo {
|
|
11
11
|
operation: ContentKitOperation;
|
|
@@ -53,3 +53,9 @@ export declare function useOptionalContentKitClient(own?: ContentKitClient | nul
|
|
|
53
53
|
export declare function useContentURLs(): ContentURLs;
|
|
54
54
|
/** A stable reporter: the component's own `onError`, else the provider's. */
|
|
55
55
|
export declare function useErrorReporter(own?: ContentKitErrorHandler): (e: unknown, operation: ContentKitOperation, file?: string) => void;
|
|
56
|
+
/** Runs a write, e.g. auth-ui's useStepUpGuard(): re-authenticates when the write asks for a recent sign-in, then retries. */
|
|
57
|
+
export type ContentKitGuard = <T>(write: () => Promise<T>) => Promise<T>;
|
|
58
|
+
/** The admin flow's guard; hooks run their writes through it. */
|
|
59
|
+
export declare const GuardContext: import('react').Context<ContentKitGuard | undefined>;
|
|
60
|
+
/** Runs fn through the flow's guard, when the host gave one. */
|
|
61
|
+
export declare function useGuarded(): <T>(fn: () => Promise<T>) => Promise<T>;
|
package/dist/react/context.js
CHANGED
|
@@ -36,7 +36,12 @@ function f(t) {
|
|
|
36
36
|
});
|
|
37
37
|
}, []);
|
|
38
38
|
}
|
|
39
|
+
var p = t(void 0);
|
|
40
|
+
function m() {
|
|
41
|
+
let e = r(p);
|
|
42
|
+
return n((t) => e ? e(t) : t(), [e]);
|
|
43
|
+
}
|
|
39
44
|
//#endregion
|
|
40
|
-
export { o as ContentKitContext, c as useContentKit, l as useContentKitClient, d as useContentURLs, f as useErrorReporter, u as useOptionalContentKitClient, s as useReadScope };
|
|
45
|
+
export { o as ContentKitContext, p as GuardContext, c as useContentKit, l as useContentKitClient, d as useContentURLs, f as useErrorReporter, m as useGuarded, u as useOptionalContentKitClient, s as useReadScope };
|
|
41
46
|
|
|
42
47
|
//# sourceMappingURL=context.js.map
|
|
@@ -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 | \"post.save\"\n | \"moderation.load\"\n | \"moderation.resolve\";\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"],"mappings":";;;
|
|
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 | \"post.save\"\n | \"moderation.load\"\n | \"moderation.resolve\"\n | \"taxonomy.load\"\n | \"taxonomy.save\";\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":";;;AAoFA,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"}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
export { ContentKitProvider, type ContentKitProviderProps } from './provider.js';
|
|
2
|
-
export { useContentKit, useContentKitClient, useContentURLs, useErrorReporter, useOptionalContentKitClient, type ContentKitContextValue, type ContentKitErrorHandler, type ContentKitErrorInfo, type ContentKitOperation, type Navigate, type SocialOperation, } from './context.js';
|
|
2
|
+
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';
|
|
3
3
|
export { useMediaRead, type MediaReadOptions, type ReadWindow, type UseMediaRead } from './read.js';
|
|
4
4
|
export { nameIn, thumbnailOf, useMediaFolder, type FolderGroup, type MediaFolderOptions, type UseMediaFolder } from './folder.js';
|
|
5
5
|
export { useEditorCrop, type EditorCropOptions, type EditorCropState, type UseEditorCrop } from './crop.js';
|
|
6
6
|
export { usePresets, usePublicImage, type PublicImageOptions, type UsePresets, type UsePublicImage } from './public.js';
|
|
7
7
|
export { useCanonicalContent, type CanonicalContent, type CanonicalContentOptions } from './canonical.js';
|
|
8
8
|
export { useNearViewport, type NearViewportOptions } from './viewport.js';
|
|
9
|
-
export {
|
|
9
|
+
export { useAdminLocation, useFlowClient, useStaffAccess, type AdminLocation, type AdminNavigation, type AdminQuery, type ContentKitAuth, type FlowClientOptions, type StaffAccess, type StaffArea, } from './admin.js';
|
|
10
|
+
export { useAdminComments, useCanComment, useCommentBans, useGlobalCommentBan, useCommentReplies, useComments, useLatestComments, useModerationQueue, withReaction, type UseAdminComments, type UseCommentBans, type UseGlobalCommentBan, type UseComments, type UseCommentsOptions, type UseModerationQueue, } from './comments.js';
|
|
10
11
|
export { useContentConfig, type UseContentConfig } from './config.js';
|
|
11
12
|
export { useFavorite, useReaction, type UseFavorite, type UseReaction } from './engagement.js';
|
|
12
13
|
export { usePoll, usePollEditor, usePolls, withVote, type PollFilter, type PollImages, type UsePoll, type UsePollEditor } from './polls.js';
|
|
13
14
|
export { isPublished, usePost, usePosts, type PostFilter, type UsePost } from './posts.js';
|
|
14
|
-
export { useContentScope, type UseList } from './use-resource.js';
|
|
15
|
+
export { useContentScope, usePaged, type UseList, type UsePaged } from './use-resource.js';
|
|
15
16
|
export { ResourceStore, resourcesFor, type Page, type Resource } from './resources.js';
|
|
16
17
|
export { useCrop, useEncodeProgress, useUpload, useUploadQueue, type UploadQueueOptions, type UploadStatus, type UseCrop, type UseCropOptions, type UseEncodeProgress, type UseUpload, type UseUploadQueue, } from './upload.js';
|
|
17
18
|
export { editOutput, useSlotCrop, useSlotImage, type SlotCropMode, type SlotCropOptions, type SlotCropState, type SlotImageOptions, type UseSlotCrop, type UseSlotImage, } from './slot.js';
|
package/dist/react/polls.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface PollFilter {
|
|
|
32
32
|
month?: string;
|
|
33
33
|
/** YYYY-MM-DD */
|
|
34
34
|
date?: string;
|
|
35
|
-
/** Staff: every poll, scheduled and inactive ones included (
|
|
35
|
+
/** Staff: every poll, scheduled and inactive ones included (root:polls:write). */
|
|
36
36
|
admin?: boolean;
|
|
37
37
|
pageSize?: number;
|
|
38
38
|
}
|
|
@@ -68,7 +68,7 @@ export interface UsePollEditor {
|
|
|
68
68
|
setImage: (file: Blob | null) => Promise<void>;
|
|
69
69
|
setOptionImage: (option: string, file: Blob | null) => Promise<void>;
|
|
70
70
|
}
|
|
71
|
-
/** Staff: one poll's editor (
|
|
71
|
+
/** Staff: one poll's editor (root:polls:write); null creates a new poll. */
|
|
72
72
|
export declare function usePollEditor(id: string | null | undefined, o?: {
|
|
73
73
|
client?: ContentKitClient;
|
|
74
74
|
}): UsePollEditor;
|
package/dist/react/polls.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { toContentKitError as e } from "../client/errors.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useGuarded as t } from "./context.js";
|
|
3
|
+
import { keyOf as n, offsetPages as r, useContentScope as i, useList as a, useResource as o } from "./use-resource.js";
|
|
4
|
+
import { useCallback as s, useState as c } from "react";
|
|
4
5
|
//#region src/react/polls.ts
|
|
5
|
-
function
|
|
6
|
+
function l(e, t) {
|
|
6
7
|
return e.voted ? e : {
|
|
7
8
|
...e,
|
|
8
9
|
voted: !0,
|
|
@@ -14,43 +15,43 @@ function c(e, t) {
|
|
|
14
15
|
} : e)
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
|
-
function
|
|
18
|
-
let { client:
|
|
18
|
+
function u(t, r = {}) {
|
|
19
|
+
let { client: a, store: u, scope: d } = i(r.client), f = t ? n("poll", d, t) : n("poll", d, "latest", r.language ?? ""), p = o(u, f, {
|
|
19
20
|
type: "poll",
|
|
20
|
-
id:
|
|
21
|
-
}, async (e) =>
|
|
22
|
-
language:
|
|
21
|
+
id: t ?? void 0
|
|
22
|
+
}, async (e) => t ? a.polls.get(t, e) : (await a.polls.list({
|
|
23
|
+
language: r.language,
|
|
23
24
|
limit: 1
|
|
24
|
-
}, e))[0] ?? null, { initial:
|
|
25
|
+
}, e))[0] ?? null, { initial: r.initial }), [m, h] = c(!1), g = p.data ?? null, _ = s(async (t) => {
|
|
25
26
|
let n = u.snapshot(f).data ?? g;
|
|
26
27
|
if (!n) return;
|
|
27
28
|
let r = u.mark(f);
|
|
28
|
-
u.set(f,
|
|
29
|
+
u.set(f, l(n, t)), h(!0);
|
|
29
30
|
try {
|
|
30
|
-
await
|
|
31
|
+
await a.polls.vote(n.id, t);
|
|
31
32
|
} catch (t) {
|
|
32
33
|
throw u.rollback(f, r, n), e(t);
|
|
33
34
|
} finally {
|
|
34
35
|
h(!1);
|
|
35
36
|
}
|
|
36
37
|
}, [
|
|
37
|
-
|
|
38
|
+
a,
|
|
38
39
|
u,
|
|
39
40
|
f,
|
|
40
41
|
g
|
|
41
|
-
]), v =
|
|
42
|
+
]), v = s(async (e) => {
|
|
42
43
|
if (g) {
|
|
43
44
|
h(!0);
|
|
44
45
|
try {
|
|
45
|
-
await
|
|
46
|
+
await a.polls.answer(g.id, e);
|
|
46
47
|
} finally {
|
|
47
48
|
h(!1);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
|
-
}, [
|
|
51
|
+
}, [a, g]);
|
|
51
52
|
return {
|
|
52
53
|
poll: g,
|
|
53
|
-
loading: p.loading || !p.loaded &&
|
|
54
|
+
loading: p.loading || !p.loaded && r.initial === void 0,
|
|
54
55
|
error: p.error,
|
|
55
56
|
reload: p.reload,
|
|
56
57
|
pending: m,
|
|
@@ -58,13 +59,13 @@ function l(n, i = {}) {
|
|
|
58
59
|
answer: v
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
|
-
function
|
|
62
|
-
let { client: o, store: s, scope: c } =
|
|
62
|
+
function d(e = {}, t = {}) {
|
|
63
|
+
let { client: o, store: s, scope: c } = i(t.client), l = e.pageSize ?? 20, u = {
|
|
63
64
|
language: e.language,
|
|
64
65
|
month: e.month,
|
|
65
66
|
date: e.date
|
|
66
67
|
};
|
|
67
|
-
return
|
|
68
|
+
return a(s, n("polls", c, !!e.admin, u.language, u.month, u.date, l), { type: "polls" }, r(l, (t, n) => e.admin ? o.polls.adminList({
|
|
68
69
|
...u,
|
|
69
70
|
...t
|
|
70
71
|
}, n) : o.polls.list({
|
|
@@ -72,62 +73,62 @@ function u(e = {}, a = {}) {
|
|
|
72
73
|
...t
|
|
73
74
|
}, n)));
|
|
74
75
|
}
|
|
75
|
-
function
|
|
76
|
-
let { client:
|
|
76
|
+
function f(r, a = {}) {
|
|
77
|
+
let { client: l, store: u, scope: d } = i(a.client), [f, p] = c(null), m = r ?? f, h = m ? n("poll", d, m) : null, g = o(u, h, {
|
|
77
78
|
type: "poll",
|
|
78
|
-
id:
|
|
79
|
-
}, (e) =>
|
|
80
|
-
|
|
79
|
+
id: m ?? void 0
|
|
80
|
+
}, (e) => l.polls.get(m, e)), _ = g.data ?? null, [v, y] = c(0), b = t(), x = s(async (e) => {
|
|
81
|
+
y((e) => e + 1);
|
|
81
82
|
try {
|
|
82
|
-
return await e
|
|
83
|
+
return await b(e);
|
|
83
84
|
} finally {
|
|
84
|
-
|
|
85
|
+
y((e) => e - 1);
|
|
85
86
|
}
|
|
86
|
-
}, []),
|
|
87
|
-
if (!
|
|
88
|
-
return
|
|
89
|
-
}, [
|
|
90
|
-
let r = await
|
|
91
|
-
n.question && a.push(
|
|
87
|
+
}, [b]), S = s(() => {
|
|
88
|
+
if (!m) throw Error("contentkit-ui: create the poll first");
|
|
89
|
+
return m;
|
|
90
|
+
}, [m]), C = s((t, n = {}) => x(async () => {
|
|
91
|
+
let r = await l.polls.create(t), i = [], a = [];
|
|
92
|
+
n.question && a.push(l.polls.uploadImage(r.id, n.question));
|
|
92
93
|
let o = [...r.options].sort((e, t) => e.position - t.position);
|
|
93
94
|
(t.options ?? []).forEach((e, t) => {
|
|
94
95
|
let i = n.options?.[t], s = o.find((t) => t.position === e.position) ?? o[t];
|
|
95
|
-
i && s && a.push(
|
|
96
|
+
i && s && a.push(l.polls.uploadOptionImage(r.id, s.id, i));
|
|
96
97
|
});
|
|
97
98
|
for (let t of await Promise.allSettled(a)) t.status === "rejected" && i.push(e(t.reason));
|
|
98
|
-
return
|
|
99
|
-
poll: a.length ? await
|
|
99
|
+
return p(r.id), {
|
|
100
|
+
poll: a.length ? await l.polls.get(r.id) : r,
|
|
100
101
|
imageErrors: i
|
|
101
102
|
};
|
|
102
|
-
}), [
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
]),
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
]),
|
|
111
|
-
let n = await
|
|
112
|
-
return t && (n.image_url = await
|
|
103
|
+
}), [l, x]), w = s((e) => x(() => l.polls.update(S(), e)), [
|
|
104
|
+
l,
|
|
105
|
+
x,
|
|
106
|
+
S
|
|
107
|
+
]), T = s(() => x(() => l.polls.delete(S())), [
|
|
108
|
+
l,
|
|
109
|
+
x,
|
|
110
|
+
S
|
|
111
|
+
]), E = s((e, t) => x(async () => {
|
|
112
|
+
let n = await l.polls.addOption(S(), { label: e });
|
|
113
|
+
return t && (n.image_url = await l.polls.uploadOptionImage(S(), n.id, t) ?? void 0), n;
|
|
113
114
|
}), [
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
]),
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
]),
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
]),
|
|
126
|
-
let n =
|
|
115
|
+
l,
|
|
116
|
+
x,
|
|
117
|
+
S
|
|
118
|
+
]), D = s((e, t) => x(async () => void await l.polls.updateOption(S(), e, { label: t })), [
|
|
119
|
+
l,
|
|
120
|
+
x,
|
|
121
|
+
S
|
|
122
|
+
]), O = s((e) => x(() => l.polls.deleteOption(S(), e)), [
|
|
123
|
+
l,
|
|
124
|
+
x,
|
|
125
|
+
S
|
|
126
|
+
]), k = s((e, t) => x(async () => {
|
|
127
|
+
let n = u.snapshot(h).data;
|
|
127
128
|
if (!n) return;
|
|
128
129
|
let r = [...n.options].sort((e, t) => e.position - t.position), i = r.findIndex((t) => t.id === e);
|
|
129
130
|
if (!(i < 0 || t < 0 || t >= r.length || i === t)) {
|
|
130
|
-
r.splice(t, 0, ...r.splice(i, 1)),
|
|
131
|
+
r.splice(t, 0, ...r.splice(i, 1)), u.set(h, {
|
|
131
132
|
...n,
|
|
132
133
|
options: r.map((e, t) => ({
|
|
133
134
|
...e,
|
|
@@ -135,42 +136,42 @@ function d(n, i = {}) {
|
|
|
135
136
|
}))
|
|
136
137
|
});
|
|
137
138
|
try {
|
|
138
|
-
await
|
|
139
|
+
await l.polls.reorderOptions(n.id, r);
|
|
139
140
|
} catch (e) {
|
|
140
|
-
throw
|
|
141
|
+
throw u.load(h, !0), e;
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
}), [
|
|
144
|
-
c,
|
|
145
145
|
l,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
146
|
+
u,
|
|
147
|
+
h,
|
|
148
|
+
x
|
|
149
|
+
]), A = s((e) => x(async () => void await l.polls.uploadImage(S(), e)), [
|
|
150
|
+
l,
|
|
151
|
+
x,
|
|
152
|
+
S
|
|
153
|
+
]), j = s((e, t) => x(async () => void await l.polls.uploadOptionImage(S(), e, t)), [
|
|
154
|
+
l,
|
|
155
|
+
x,
|
|
156
|
+
S
|
|
156
157
|
]);
|
|
157
158
|
return {
|
|
158
|
-
poll:
|
|
159
|
-
loading: !!
|
|
160
|
-
error:
|
|
161
|
-
saving:
|
|
162
|
-
create:
|
|
163
|
-
update:
|
|
164
|
-
remove:
|
|
165
|
-
addOption:
|
|
166
|
-
renameOption:
|
|
167
|
-
removeOption:
|
|
168
|
-
moveOption:
|
|
169
|
-
setImage:
|
|
170
|
-
setOptionImage:
|
|
159
|
+
poll: _,
|
|
160
|
+
loading: !!h && (g.loading || !g.loaded),
|
|
161
|
+
error: g.error,
|
|
162
|
+
saving: v > 0,
|
|
163
|
+
create: C,
|
|
164
|
+
update: w,
|
|
165
|
+
remove: T,
|
|
166
|
+
addOption: E,
|
|
167
|
+
renameOption: D,
|
|
168
|
+
removeOption: O,
|
|
169
|
+
moveOption: k,
|
|
170
|
+
setImage: A,
|
|
171
|
+
setOptionImage: j
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
174
|
//#endregion
|
|
174
|
-
export {
|
|
175
|
+
export { u as usePoll, f as usePollEditor, d as usePolls, l as withVote };
|
|
175
176
|
|
|
176
177
|
//# sourceMappingURL=polls.js.map
|
package/dist/react/polls.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"polls.js","names":[],"sources":["../../src/react/polls.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Poll, PollInput, PollOption, PollUpdate } from \"../client/generated/wire.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\n/** The poll after the caller votes for option; a vote is final. */\nexport function withVote(p: Poll, option: string): Poll {\n if (p.voted) return p;\n return { ...p, voted: true, my_option: option, total_votes: p.total_votes + 1, options: p.options.map((o) => (o.id === option ? { ...o, vote_count: o.vote_count + 1 } : o)) };\n}\n\nexport interface UsePoll {\n /** null: no such poll (or no live one). */\n poll: Poll | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** A vote or answer is in flight. */\n pending: boolean;\n /** Votes at once (a vote is final); rolled back if the server refuses it. */\n vote: (option: string) => Promise<void>;\n /** Stores or replaces the signed-in caller's free-text answer. */\n answer: (text: string) => Promise<void>;\n}\n\n/**\n * A poll with the caller's vote or answer. Without an id it is the newest\n * live poll (in language): the one a home page shows.\n */\nexport function usePoll(id?: string | null, o: { initial?: Poll; language?: string; client?: ContentKitClient } = {}): UsePoll {\n const { client, store, scope } = useContentScope(o.client);\n const key = id ? keyOf(\"poll\", scope, id) : keyOf(\"poll\", scope, \"latest\", o.language ?? \"\");\n const r = useResource<Poll | null>(\n store,\n key,\n { type: \"poll\", id: id ?? undefined },\n async (s) => (id ? client.polls.get(id, s) : ((await client.polls.list({ language: o.language, limit: 1 }, s))[0] ?? null)),\n { initial: o.initial },\n );\n const [pending, setPending] = useState(false);\n const poll = r.data ?? null;\n const vote = useCallback(\n async (option: string) => {\n const prev = store.snapshot<Poll | null>(key).data ?? poll;\n if (!prev) return;\n const token = store.mark(key);\n store.set(key, withVote(prev, option));\n setPending(true);\n try {\n await client.polls.vote(prev.id, option);\n } catch (e) {\n store.rollback(key, token, prev);\n throw toContentKitError(e);\n } finally {\n setPending(false);\n }\n },\n [client, store, key, poll],\n );\n const answer = useCallback(\n async (text: string) => {\n if (!poll) return;\n setPending(true);\n try {\n await client.polls.answer(poll.id, text);\n } finally {\n setPending(false);\n }\n },\n [client, poll],\n );\n return { poll, loading: r.loading || (!r.loaded && o.initial === undefined), error: r.error, reload: r.reload, pending, vote, answer };\n}\n\nexport interface PollFilter {\n language?: string;\n /** YYYY-MM */\n month?: string;\n /** YYYY-MM-DD */\n date?: string;\n /** Staff: every poll, scheduled and inactive ones included (PollWrite). */\n admin?: boolean;\n pageSize?: number;\n}\n\n/** Polls, newest first, a page at a time. */\nexport function usePolls(f: PollFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Poll> {\n const { client, store, scope } = useContentScope(o.client);\n const size = f.pageSize ?? 20;\n const q = { language: f.language, month: f.month, date: f.date };\n return useList(\n store,\n keyOf(\"polls\", scope, !!f.admin, q.language, q.month, q.date, size),\n { type: \"polls\" },\n offsetPages(size, (page, signal) => (f.admin ? client.polls.adminList({ ...q, ...page }, signal) : client.polls.list({ ...q, ...page }, signal))),\n );\n}\n\n/** Images for a new poll, uploaded once it exists: the question's, and each option's by index. */\nexport interface PollImages {\n question?: Blob | null;\n options?: readonly (Blob | null | undefined)[];\n}\n\nexport interface UsePollEditor {\n /** null before create (or while loading). */\n poll: Poll | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Creates the poll, then uploads its images; the editor then edits it. Image failures are returned, not thrown. */\n create: (input: PollInput, images?: PollImages) => Promise<{ poll: Poll; imageErrors: ContentKitError[] }>;\n update: (patch: PollUpdate) => Promise<Poll>;\n remove: () => Promise<void>;\n addOption: (label: string, image?: Blob | null) => Promise<PollOption>;\n renameOption: (option: string, label: string) => Promise<void>;\n /** Removes an option and its votes; a poll keeps at least two. */\n removeOption: (option: string) => Promise<void>;\n /** Moves an option to index to, at once; refetched if a write fails. */\n moveOption: (option: string, to: number) => Promise<void>;\n setImage: (file: Blob | null) => Promise<void>;\n setOptionImage: (option: string, file: Blob | null) => Promise<void>;\n}\n\n/** Staff: one poll's editor (PollWrite); null creates a new poll. */\nexport function usePollEditor(id: string | null | undefined, o: { client?: ContentKitClient } = {}): UsePollEditor {\n const { client, store, scope } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const pid = id ?? created;\n const key = pid ? keyOf(\"poll\", scope, pid) : null;\n const r = useResource<Poll | null>(store, key, { type: \"poll\", id: pid ?? undefined }, (s) => client.polls.get(pid!, s));\n const poll = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await fn();\n } finally {\n setInflight((n) => n - 1);\n }\n }, []);\n const need = useCallback(() => {\n if (!pid) throw new Error(\"contentkit-ui: create the poll first\");\n return pid;\n }, [pid]);\n\n const create = useCallback<UsePollEditor[\"create\"]>(\n (input, images = {}) =>\n run(async () => {\n const made = await client.polls.create(input);\n const imageErrors: ContentKitError[] = [];\n const tries: Promise<unknown>[] = [];\n if (images.question) tries.push(client.polls.uploadImage(made.id, images.question));\n const byPosition = [...made.options].sort((a, b) => a.position - b.position);\n (input.options ?? []).forEach((opt, i) => {\n const file = images.options?.[i];\n const target = byPosition.find((x) => x.position === opt.position) ?? byPosition[i];\n if (file && target) tries.push(client.polls.uploadOptionImage(made.id, target.id, file));\n });\n for (const res of await Promise.allSettled(tries)) if (res.status === \"rejected\") imageErrors.push(toContentKitError(res.reason));\n setCreated(made.id);\n return { poll: tries.length ? await client.polls.get(made.id) : made, imageErrors };\n }),\n [client, run],\n );\n const update = useCallback((patch: PollUpdate) => run(() => client.polls.update(need(), patch)), [client, run, need]);\n const remove = useCallback(() => run(() => client.polls.delete(need())), [client, run, need]);\n const addOption = useCallback(\n (label: string, image?: Blob | null) =>\n run(async () => {\n const opt = await client.polls.addOption(need(), { label });\n if (image) opt.image_url = (await client.polls.uploadOptionImage(need(), opt.id, image)) ?? undefined;\n return opt;\n }),\n [client, run, need],\n );\n const renameOption = useCallback((option: string, label: string) => run(async () => void (await client.polls.updateOption(need(), option, { label }))), [client, run, need]);\n const removeOption = useCallback((option: string) => run(() => client.polls.deleteOption(need(), option)), [client, run, need]);\n const moveOption = useCallback(\n (option: string, to: number) =>\n run(async () => {\n const current = store.snapshot<Poll | null>(key!).data;\n if (!current) return;\n const order = [...current.options].sort((a, b) => a.position - b.position);\n const from = order.findIndex((x) => x.id === option);\n if (from < 0 || to < 0 || to >= order.length || from === to) return;\n order.splice(to, 0, ...order.splice(from, 1));\n store.set<Poll | null>(key!, { ...current, options: order.map((x, position) => ({ ...x, position })) });\n try {\n await client.polls.reorderOptions(current.id, order);\n } catch (e) {\n store.load(key!, true);\n throw e;\n }\n }),\n [client, store, key, run],\n );\n const setImage = useCallback((file: Blob | null) => run(async () => void (await client.polls.uploadImage(need(), file))), [client, run, need]);\n const setOptionImage = useCallback(\n (option: string, file: Blob | null) => run(async () => void (await client.polls.uploadOptionImage(need(), option, file))),\n [client, run, need],\n );\n return {\n poll,\n loading: !!key && (r.loading || !r.loaded),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n remove,\n addOption,\n renameOption,\n removeOption,\n moveOption,\n setImage,\n setOptionImage,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"polls.js","names":[],"sources":["../../src/react/polls.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Poll, PollInput, PollOption, PollUpdate } from \"../client/generated/wire.js\";\nimport { useGuarded } from \"./context.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\n/** The poll after the caller votes for option; a vote is final. */\nexport function withVote(p: Poll, option: string): Poll {\n if (p.voted) return p;\n return { ...p, voted: true, my_option: option, total_votes: p.total_votes + 1, options: p.options.map((o) => (o.id === option ? { ...o, vote_count: o.vote_count + 1 } : o)) };\n}\n\nexport interface UsePoll {\n /** null: no such poll (or no live one). */\n poll: Poll | null;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** A vote or answer is in flight. */\n pending: boolean;\n /** Votes at once (a vote is final); rolled back if the server refuses it. */\n vote: (option: string) => Promise<void>;\n /** Stores or replaces the signed-in caller's free-text answer. */\n answer: (text: string) => Promise<void>;\n}\n\n/**\n * A poll with the caller's vote or answer. Without an id it is the newest\n * live poll (in language): the one a home page shows.\n */\nexport function usePoll(id?: string | null, o: { initial?: Poll; language?: string; client?: ContentKitClient } = {}): UsePoll {\n const { client, store, scope } = useContentScope(o.client);\n const key = id ? keyOf(\"poll\", scope, id) : keyOf(\"poll\", scope, \"latest\", o.language ?? \"\");\n const r = useResource<Poll | null>(\n store,\n key,\n { type: \"poll\", id: id ?? undefined },\n async (s) => (id ? client.polls.get(id, s) : ((await client.polls.list({ language: o.language, limit: 1 }, s))[0] ?? null)),\n { initial: o.initial },\n );\n const [pending, setPending] = useState(false);\n const poll = r.data ?? null;\n const vote = useCallback(\n async (option: string) => {\n const prev = store.snapshot<Poll | null>(key).data ?? poll;\n if (!prev) return;\n const token = store.mark(key);\n store.set(key, withVote(prev, option));\n setPending(true);\n try {\n await client.polls.vote(prev.id, option);\n } catch (e) {\n store.rollback(key, token, prev);\n throw toContentKitError(e);\n } finally {\n setPending(false);\n }\n },\n [client, store, key, poll],\n );\n const answer = useCallback(\n async (text: string) => {\n if (!poll) return;\n setPending(true);\n try {\n await client.polls.answer(poll.id, text);\n } finally {\n setPending(false);\n }\n },\n [client, poll],\n );\n return { poll, loading: r.loading || (!r.loaded && o.initial === undefined), error: r.error, reload: r.reload, pending, vote, answer };\n}\n\nexport interface PollFilter {\n language?: string;\n /** YYYY-MM */\n month?: string;\n /** YYYY-MM-DD */\n date?: string;\n /** Staff: every poll, scheduled and inactive ones included (root:polls:write). */\n admin?: boolean;\n pageSize?: number;\n}\n\n/** Polls, newest first, a page at a time. */\nexport function usePolls(f: PollFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Poll> {\n const { client, store, scope } = useContentScope(o.client);\n const size = f.pageSize ?? 20;\n const q = { language: f.language, month: f.month, date: f.date };\n return useList(\n store,\n keyOf(\"polls\", scope, !!f.admin, q.language, q.month, q.date, size),\n { type: \"polls\" },\n offsetPages(size, (page, signal) => (f.admin ? client.polls.adminList({ ...q, ...page }, signal) : client.polls.list({ ...q, ...page }, signal))),\n );\n}\n\n/** Images for a new poll, uploaded once it exists: the question's, and each option's by index. */\nexport interface PollImages {\n question?: Blob | null;\n options?: readonly (Blob | null | undefined)[];\n}\n\nexport interface UsePollEditor {\n /** null before create (or while loading). */\n poll: Poll | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Creates the poll, then uploads its images; the editor then edits it. Image failures are returned, not thrown. */\n create: (input: PollInput, images?: PollImages) => Promise<{ poll: Poll; imageErrors: ContentKitError[] }>;\n update: (patch: PollUpdate) => Promise<Poll>;\n remove: () => Promise<void>;\n addOption: (label: string, image?: Blob | null) => Promise<PollOption>;\n renameOption: (option: string, label: string) => Promise<void>;\n /** Removes an option and its votes; a poll keeps at least two. */\n removeOption: (option: string) => Promise<void>;\n /** Moves an option to index to, at once; refetched if a write fails. */\n moveOption: (option: string, to: number) => Promise<void>;\n setImage: (file: Blob | null) => Promise<void>;\n setOptionImage: (option: string, file: Blob | null) => Promise<void>;\n}\n\n/** Staff: one poll's editor (root:polls:write); null creates a new poll. */\nexport function usePollEditor(id: string | null | undefined, o: { client?: ContentKitClient } = {}): UsePollEditor {\n const { client, store, scope } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const pid = id ?? created;\n const key = pid ? keyOf(\"poll\", scope, pid) : null;\n const r = useResource<Poll | null>(store, key, { type: \"poll\", id: pid ?? undefined }, (s) => client.polls.get(pid!, s));\n const poll = 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 (!pid) throw new Error(\"contentkit-ui: create the poll first\");\n return pid;\n }, [pid]);\n\n const create = useCallback<UsePollEditor[\"create\"]>(\n (input, images = {}) =>\n run(async () => {\n const made = await client.polls.create(input);\n const imageErrors: ContentKitError[] = [];\n const tries: Promise<unknown>[] = [];\n if (images.question) tries.push(client.polls.uploadImage(made.id, images.question));\n const byPosition = [...made.options].sort((a, b) => a.position - b.position);\n (input.options ?? []).forEach((opt, i) => {\n const file = images.options?.[i];\n const target = byPosition.find((x) => x.position === opt.position) ?? byPosition[i];\n if (file && target) tries.push(client.polls.uploadOptionImage(made.id, target.id, file));\n });\n for (const res of await Promise.allSettled(tries)) if (res.status === \"rejected\") imageErrors.push(toContentKitError(res.reason));\n setCreated(made.id);\n return { poll: tries.length ? await client.polls.get(made.id) : made, imageErrors };\n }),\n [client, run],\n );\n const update = useCallback((patch: PollUpdate) => run(() => client.polls.update(need(), patch)), [client, run, need]);\n const remove = useCallback(() => run(() => client.polls.delete(need())), [client, run, need]);\n const addOption = useCallback(\n (label: string, image?: Blob | null) =>\n run(async () => {\n const opt = await client.polls.addOption(need(), { label });\n if (image) opt.image_url = (await client.polls.uploadOptionImage(need(), opt.id, image)) ?? undefined;\n return opt;\n }),\n [client, run, need],\n );\n const renameOption = useCallback((option: string, label: string) => run(async () => void (await client.polls.updateOption(need(), option, { label }))), [client, run, need]);\n const removeOption = useCallback((option: string) => run(() => client.polls.deleteOption(need(), option)), [client, run, need]);\n const moveOption = useCallback(\n (option: string, to: number) =>\n run(async () => {\n const current = store.snapshot<Poll | null>(key!).data;\n if (!current) return;\n const order = [...current.options].sort((a, b) => a.position - b.position);\n const from = order.findIndex((x) => x.id === option);\n if (from < 0 || to < 0 || to >= order.length || from === to) return;\n order.splice(to, 0, ...order.splice(from, 1));\n store.set<Poll | null>(key!, { ...current, options: order.map((x, position) => ({ ...x, position })) });\n try {\n await client.polls.reorderOptions(current.id, order);\n } catch (e) {\n store.load(key!, true);\n throw e;\n }\n }),\n [client, store, key, run],\n );\n const setImage = useCallback((file: Blob | null) => run(async () => void (await client.polls.uploadImage(need(), file))), [client, run, need]);\n const setOptionImage = useCallback(\n (option: string, file: Blob | null) => run(async () => void (await client.polls.uploadOptionImage(need(), option, file))),\n [client, run, need],\n );\n return {\n poll,\n loading: !!key && (r.loading || !r.loaded),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n remove,\n addOption,\n renameOption,\n removeOption,\n moveOption,\n setImage,\n setOptionImage,\n };\n}\n"],"mappings":"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"}
|