@openrails/contentkit-ui 0.68.0 → 0.69.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 +55 -10
- package/dist/client/client.d.ts +4 -2
- package/dist/client/client.js +1 -0
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/bans.js +7 -7
- package/dist/client/content/bans.js.map +1 -1
- package/dist/client/content/comments.js +16 -14
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/favorites.js +5 -5
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/moderation.js +5 -5
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/polls.js +34 -31
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/content/posts.js +21 -20
- package/dist/client/content/posts.js.map +1 -1
- package/dist/client/content/reactions.js +4 -4
- package/dist/client/content/reactions.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +4 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/http.d.ts +6 -2
- package/dist/client/http.js +11 -3
- package/dist/client/http.js.map +1 -1
- package/dist/client/media/client.js +24 -24
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/rules.d.ts +2 -0
- package/dist/client/media/rules.js +6 -2
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/taxonomy.js +2 -2
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/components/media-folder-editor.d.ts +17 -3
- package/dist/components/media-folder-editor.js +146 -99
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/slot-upload.js +102 -100
- package/dist/components/slot-upload.js.map +1 -1
- package/dist/locales/de.js +1 -0
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts +1 -0
- package/dist/locales/en.js +1 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +1 -0
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +1 -0
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +1 -0
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/canonical.d.ts +2 -2
- package/dist/react/canonical.js +2 -12
- package/dist/react/canonical.js.map +1 -1
- package/dist/react/comments.js +35 -27
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +7 -0
- package/dist/react/context.js +20 -15
- package/dist/react/context.js.map +1 -1
- package/dist/react/engagement.js +2 -2
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.d.ts +29 -9
- package/dist/react/folder.js +170 -113
- package/dist/react/folder.js.map +1 -1
- package/dist/react/polls.js +3 -3
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.js +2 -2
- package/dist/react/posts.js.map +1 -1
- package/dist/react/provider.d.ts +7 -3
- package/dist/react/provider.js +34 -20
- package/dist/react/provider.js.map +1 -1
- package/dist/react/public.js +11 -11
- package/dist/react/public.js.map +1 -1
- package/dist/react/read.js +93 -92
- package/dist/react/read.js.map +1 -1
- package/dist/react/resources.d.ts +6 -3
- package/dist/react/resources.js +40 -40
- package/dist/react/resources.js.map +1 -1
- package/dist/react/slot.d.ts +4 -2
- package/dist/react/slot.js +96 -92
- package/dist/react/slot.js.map +1 -1
- package/dist/react/store.d.ts +1 -1
- package/dist/react/store.js +21 -20
- package/dist/react/store.js.map +1 -1
- package/dist/react/upload.d.ts +3 -2
- package/dist/react/upload.js +20 -15
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +1 -0
- package/dist/react/use-resource.js +38 -30
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +15 -15
- package/package.json +7 -5
package/dist/react/canonical.js
CHANGED
|
@@ -59,18 +59,8 @@ function c(c, l = {}) {
|
|
|
59
59
|
let e = [s("canonical", x), a("og:url", x)];
|
|
60
60
|
C && e.push(a("og:title", C)), w && e.push(a("og:image", w));
|
|
61
61
|
for (let [t, n] of Object.entries(JSON.parse(T))) e.push(s("alternate", n, t));
|
|
62
|
-
let t =
|
|
63
|
-
|
|
64
|
-
let e = document.head.querySelector(n.selector);
|
|
65
|
-
if (e) {
|
|
66
|
-
let r = e.getAttribute(n.attr);
|
|
67
|
-
e.setAttribute(n.attr, n.value), t.push(() => r === null ? e.removeAttribute(n.attr) : e.setAttribute(n.attr, r));
|
|
68
|
-
} else {
|
|
69
|
-
let e = o(n.create(), n.attr, n.value);
|
|
70
|
-
document.head.appendChild(e), t.push(() => e.remove());
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return () => t.forEach((e) => e());
|
|
62
|
+
let t = e.map((e) => o(document.head.querySelector(e.selector) ?? document.head.appendChild(e.create()), e.attr, e.value));
|
|
63
|
+
return () => t.forEach((e) => e.remove());
|
|
74
64
|
}, [
|
|
75
65
|
x,
|
|
76
66
|
C,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical.js","names":[],"sources":["../../src/react/canonical.ts"],"sourcesContent":["import { useContext, useEffect, useMemo } from \"react\";\nimport type { ContentLink } from \"../urls/index.js\";\nimport { ContentKitContext, useContentURLs } from \"./context.js\";\n\nexport interface CanonicalContentOptions {\n /** og:title. */\n title?: string;\n /** og:image, an absolute URL. */\n image?: string;\n /**\n * The current location (pathname, search, hash). Default\n * window.location; pass the router's so a navigation checks again.\n */\n location?: string;\n /** Languages the content exists in, for hreflang; default every configured language. */\n languages?: readonly string[];\n /** The language hreflang x-default points at. */\n defaultLanguage?: string;\n}\n\nexport interface CanonicalContent {\n /** The canonical path in the page's language; undefined until link arrives. */\n path?: string;\n /** Its absolute URL. */\n url?: string;\n /** hreflang alternates by language (and x-default). */\n alternates: Record<string, string>;\n}\n\ntype Tag = { selector: string; create: () => HTMLElement; attr: string; value: string };\n\nconst meta = (property: string, value: string): Tag => ({\n selector: `meta[property=\"${property}\"]`,\n create: () => withAttr(document.createElement(\"meta\"), \"property\", property),\n attr: \"content\",\n value,\n});\n\nfunction withAttr<E extends HTMLElement>(el: E, name: string, value: string): E {\n el.setAttribute(name, value);\n return el;\n}\n\nconst link = (rel: string, value: string, hreflang?: string): Tag => ({\n selector: hreflang ? `link[rel=\"${rel}\"][hreflang=\"${hreflang}\"]` : `link[rel=\"${rel}\"]:not([hreflang])`,\n create: () => {\n const el = withAttr(document.createElement(\"link\"), \"rel\", rel);\n if (hreflang) el.setAttribute(\"hreflang\", hreflang);\n return el;\n },\n attr: \"href\",\n value,\n});\n\n/**\n * A content page's canonical URL once its data (link) arrives: replaces the\n * address with the canonical path (code spelling, merged code, current slug)\n * through the provider's navigate (else history.replaceState), and keeps\n * `<link rel=canonical>`, og:url, og:title, og:image and hreflang alternates\n * (each language's own slug) in the head while mounted
|
|
1
|
+
{"version":3,"file":"canonical.js","names":[],"sources":["../../src/react/canonical.ts"],"sourcesContent":["import { useContext, useEffect, useMemo } from \"react\";\nimport type { ContentLink } from \"../urls/index.js\";\nimport { ContentKitContext, useContentURLs } from \"./context.js\";\n\nexport interface CanonicalContentOptions {\n /** og:title. */\n title?: string;\n /** og:image, an absolute URL. */\n image?: string;\n /**\n * The current location (pathname, search, hash). Default\n * window.location; pass the router's so a navigation checks again.\n */\n location?: string;\n /** Languages the content exists in, for hreflang; default every configured language. */\n languages?: readonly string[];\n /** The language hreflang x-default points at. */\n defaultLanguage?: string;\n}\n\nexport interface CanonicalContent {\n /** The canonical path in the page's language; undefined until link arrives. */\n path?: string;\n /** Its absolute URL. */\n url?: string;\n /** hreflang alternates by language (and x-default). */\n alternates: Record<string, string>;\n}\n\ntype Tag = { selector: string; create: () => HTMLElement; attr: string; value: string };\n\nconst meta = (property: string, value: string): Tag => ({\n selector: `meta[property=\"${property}\"]`,\n create: () => withAttr(document.createElement(\"meta\"), \"property\", property),\n attr: \"content\",\n value,\n});\n\nfunction withAttr<E extends HTMLElement>(el: E, name: string, value: string): E {\n el.setAttribute(name, value);\n return el;\n}\n\nconst link = (rel: string, value: string, hreflang?: string): Tag => ({\n selector: hreflang ? `link[rel=\"${rel}\"][hreflang=\"${hreflang}\"]` : `link[rel=\"${rel}\"]:not([hreflang])`,\n create: () => {\n const el = withAttr(document.createElement(\"link\"), \"rel\", rel);\n if (hreflang) el.setAttribute(\"hreflang\", hreflang);\n return el;\n },\n attr: \"href\",\n value,\n});\n\n/**\n * A content page's canonical URL once its data (link) arrives: replaces the\n * address with the canonical path (code spelling, merged code, current slug)\n * through the provider's navigate (else history.replaceState), and keeps\n * `<link rel=canonical>`, og:url, og:title, og:image and hreflang alternates\n * (each language's own slug) in the head while mounted; unmounting removes\n * the tags it set, server-rendered ones included. Needs the provider's `urls`.\n */\nexport function useCanonicalContent(content: ContentLink | null | undefined, o: CanonicalContentOptions = {}): CanonicalContent {\n const urls = useContentURLs();\n const navigate = useContext(ContentKitContext)?.navigate;\n const at = o.location ?? (typeof location === \"undefined\" ? \"\" : location.pathname + location.search + location.hash);\n const kind = content?.content_kind;\n const code = content?.code;\n const slug = content?.slug;\n const slugs = JSON.stringify(content?.slugs ?? {});\n const languages = o.languages?.join(\",\");\n const lnk = useMemo(\n () => (kind && code ? ({ content_kind: kind, code, slug: slug ?? \"\", slugs: JSON.parse(slugs) as Record<string, string> } satisfies ContentLink) : null),\n [kind, code, slug, slugs],\n );\n\n const result = useMemo((): CanonicalContent & { redirect?: string } => {\n if (!lnk) return { alternates: {} };\n const origin = urls.origin ?? (typeof location === \"undefined\" ? undefined : location.origin);\n const hash = at.includes(\"#\") ? at.slice(at.indexOf(\"#\")) : \"\";\n const c = urls.canonical(at, lnk);\n const path = c?.path ?? urls.path(lnk);\n const alternates = origin ? urls.hreflang(lnk, { origin, languages: languages?.split(\",\"), defaultLanguage: o.defaultLanguage }) : {};\n return { path, url: origin ? origin + path : undefined, alternates, redirect: c?.redirect ? c.location + hash : undefined };\n }, [lnk, at, urls, languages, o.defaultLanguage]);\n\n const { redirect } = result;\n useEffect(() => {\n if (!redirect) return;\n if (navigate) navigate(redirect, { replace: true });\n else history.replaceState(history.state, \"\", redirect);\n }, [redirect, navigate]);\n\n const { url, alternates } = result;\n const { title, image } = o;\n const alternatesKey = JSON.stringify(alternates);\n useEffect(() => {\n if (!url || typeof document === \"undefined\") return;\n const tags: Tag[] = [link(\"canonical\", url), meta(\"og:url\", url)];\n if (title) tags.push(meta(\"og:title\", title));\n if (image) tags.push(meta(\"og:image\", image));\n for (const [lang, href] of Object.entries(JSON.parse(alternatesKey) as Record<string, string>)) tags.push(link(\"alternate\", href, lang));\n const set = tags.map((t) => withAttr(document.head.querySelector<HTMLElement>(t.selector) ?? document.head.appendChild(t.create()), t.attr, t.value));\n // The tags describe this page: leaving it removes them, server-rendered ones\n // included (restoring those would leave this page's values on the next).\n return () => set.forEach((el) => el.remove());\n }, [url, title, image, alternatesKey]);\n\n return { path: result.path, url: result.url, alternates: result.alternates };\n}\n"],"mappings":"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"}
|
package/dist/react/comments.js
CHANGED
|
@@ -14,35 +14,35 @@ var s = [
|
|
|
14
14
|
"latest",
|
|
15
15
|
"admin-comments"
|
|
16
16
|
], c = /* @__PURE__ */ new WeakMap();
|
|
17
|
-
async function l(e, t, n, r) {
|
|
18
|
-
let
|
|
19
|
-
|
|
20
|
-
let
|
|
21
|
-
|
|
22
|
-
let
|
|
17
|
+
async function l(e, t, n, r, i) {
|
|
18
|
+
let a = c.get(t);
|
|
19
|
+
a || c.set(t, a = /* @__PURE__ */ new Map());
|
|
20
|
+
let l = (a.get(r.id) ?? 0) + 1;
|
|
21
|
+
a.set(r.id, l);
|
|
22
|
+
let u = /* @__PURE__ */ new Set(), d = (e, i = !1) => t.patch((e) => s.includes(e.type) && e.readScope === n, (t, n) => i && !u.has(n) ? t : (u.add(n), {
|
|
23
23
|
...t,
|
|
24
|
-
items: t.items.map((t) => t.id ===
|
|
24
|
+
items: t.items.map((t) => t.id === r.id ? {
|
|
25
25
|
...t,
|
|
26
26
|
...e
|
|
27
27
|
} : t)
|
|
28
28
|
}));
|
|
29
|
-
|
|
30
|
-
likes:
|
|
31
|
-
dislikes:
|
|
32
|
-
mine:
|
|
33
|
-
},
|
|
29
|
+
d(o({
|
|
30
|
+
likes: r.likes,
|
|
31
|
+
dislikes: r.dislikes,
|
|
32
|
+
mine: r.mine
|
|
33
|
+
}, i));
|
|
34
34
|
try {
|
|
35
|
-
await e.comments.react(
|
|
35
|
+
await e.comments.react(r.id, i);
|
|
36
36
|
} catch (e) {
|
|
37
|
-
throw
|
|
38
|
-
likes:
|
|
39
|
-
dislikes:
|
|
40
|
-
mine:
|
|
41
|
-
}), e;
|
|
37
|
+
throw a.get(r.id) === l && d({
|
|
38
|
+
likes: r.likes,
|
|
39
|
+
dislikes: r.dislikes,
|
|
40
|
+
mine: r.mine
|
|
41
|
+
}, !0), e;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
function u(i, o = {}) {
|
|
45
|
-
let { client: s, store: c,
|
|
45
|
+
let { client: s, store: c, scope: u } = n(o.client), d = o.pageSize ?? 20, f = o.sort ?? "newest", p = e("comments", u, i.kind, i.id, f, d), m = r(c, p, {
|
|
46
46
|
type: "comments",
|
|
47
47
|
ref: {
|
|
48
48
|
kind: i.kind,
|
|
@@ -62,7 +62,11 @@ function u(i, o = {}) {
|
|
|
62
62
|
s,
|
|
63
63
|
h,
|
|
64
64
|
g
|
|
65
|
-
]), v = a((e, t) => s.comments.edit(e, t), [s]), y = a((e) => s.comments.delete(e), [s]), b = a((e) => s.comments.restore(e), [s]), x = a((e, t) => l(s, c, e, t), [
|
|
65
|
+
]), v = a((e, t) => s.comments.edit(e, t), [s]), y = a((e) => s.comments.delete(e), [s]), b = a((e) => s.comments.restore(e), [s]), x = a((e, t) => l(s, c, u, e, t), [
|
|
66
|
+
s,
|
|
67
|
+
c,
|
|
68
|
+
u
|
|
69
|
+
]);
|
|
66
70
|
return {
|
|
67
71
|
...m,
|
|
68
72
|
post: _,
|
|
@@ -73,14 +77,14 @@ function u(i, o = {}) {
|
|
|
73
77
|
};
|
|
74
78
|
}
|
|
75
79
|
function d(i, a = {}) {
|
|
76
|
-
let { client: o, store: s,
|
|
80
|
+
let { client: o, store: s, scope: c } = n(a.client), l = a.pageSize ?? 10, u = a.enabled === !1 ? null : e("replies", c, i, l);
|
|
77
81
|
return r(s, u, {
|
|
78
82
|
type: "replies",
|
|
79
83
|
id: i
|
|
80
84
|
}, t(l, (e, t) => o.comments.replies(i, e, t)));
|
|
81
85
|
}
|
|
82
86
|
function f(t, r = {}) {
|
|
83
|
-
let { client: a, store: o,
|
|
87
|
+
let { client: a, store: o, scope: s } = n(r.client), c = i(o, e("standing", s, t.kind, t.id), {
|
|
84
88
|
type: "standing",
|
|
85
89
|
ref: {
|
|
86
90
|
kind: t.kind,
|
|
@@ -95,14 +99,18 @@ function f(t, r = {}) {
|
|
|
95
99
|
};
|
|
96
100
|
}
|
|
97
101
|
function p(i = {}) {
|
|
98
|
-
let { client: a, store: o,
|
|
102
|
+
let { client: a, store: o, scope: s } = n(i.client), c = i.pageSize ?? 20;
|
|
99
103
|
return r(o, e("latest", s, c), { type: "latest" }, t(c, (e, t) => a.comments.latest(e, t), !0));
|
|
100
104
|
}
|
|
101
105
|
function m(i = {}) {
|
|
102
|
-
let { client: o, store: s,
|
|
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({
|
|
103
107
|
...e,
|
|
104
108
|
contentKind: d
|
|
105
|
-
}, t))), p = a((e) => o.comments.delete(e), [o]), m = a((e) => o.comments.restore(e), [o]), h = a((e, t) => l(o, s, e, t), [
|
|
109
|
+
}, t))), p = a((e) => o.comments.delete(e), [o]), m = a((e) => o.comments.restore(e), [o]), h = a((e, t) => l(o, s, c, e, t), [
|
|
110
|
+
o,
|
|
111
|
+
s,
|
|
112
|
+
c
|
|
113
|
+
]);
|
|
106
114
|
return {
|
|
107
115
|
...f,
|
|
108
116
|
remove: p,
|
|
@@ -111,7 +119,7 @@ function m(i = {}) {
|
|
|
111
119
|
};
|
|
112
120
|
}
|
|
113
121
|
function h(t = {}) {
|
|
114
|
-
let { client: i, store: o,
|
|
122
|
+
let { client: i, store: o, scope: s } = n(t.client), c = t.kind ?? "comment", l = t.pageSize ?? 20, u = r(o, e("held", s, c, l), { type: "held" }, async (e, t) => {
|
|
115
123
|
let n = await i.moderation.held({
|
|
116
124
|
kind: c,
|
|
117
125
|
cursor: typeof e == "string" ? e : void 0,
|
|
@@ -128,7 +136,7 @@ function h(t = {}) {
|
|
|
128
136
|
};
|
|
129
137
|
}
|
|
130
138
|
function g(i = {}) {
|
|
131
|
-
let { client: o, store: s,
|
|
139
|
+
let { client: o, store: s, scope: c } = n(i.client), l = i.scope ?? "global", u = i.pageSize ?? 25, d = r(s, i.enabled === !1 ? null : e("bans", c, l, u), {
|
|
132
140
|
type: "bans",
|
|
133
141
|
scope: l
|
|
134
142
|
}, t(u, async (e, t) => (await o.bans.list(l, e, t)).map((e) => ({
|
|
@@ -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 { 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, 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 put = (counts: ReactionCounts) =>\n store.patch<{ items: Comment[]; next: unknown }>(\n (tag) => commentLists.includes(tag.type),\n (page) => ({ ...page, items: page.items.map((x) => (x.id === c.id ? { ...x, ...counts } : x)) }),\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 });\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, viewer } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n const sort = o.sort ?? \"newest\";\n const key = keyOf(\"comments\", viewer, 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, c, v), [client, store]);\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, viewer } = useContentScope(o.client);\n const size = o.pageSize ?? 10;\n const key = o.enabled === false ? null : keyOf(\"replies\", viewer, 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, viewer } = useContentScope(o.client);\n const r = useResource<CommentStanding>(store, keyOf(\"standing\", viewer, 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, viewer } = useContentScope(o.client);\n const size = o.pageSize ?? 20;\n return useList(store, keyOf(\"latest\", viewer, 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, viewer } = 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\", viewer, 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, c, v), [client, store]);\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, viewer } = useContentScope(o.client);\n const kind = o.kind ?? \"comment\";\n const size = o.pageSize ?? 20;\n const list = useList<HeldItem>(store, keyOf(\"held\", viewer, 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, viewer } = 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\", viewer, 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 { 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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"}
|
package/dist/react/context.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface ContentKitContextValue {
|
|
|
27
27
|
* undefined when the host does not say. Content reads are kept per viewer.
|
|
28
28
|
*/
|
|
29
29
|
viewer?: string | null;
|
|
30
|
+
/** Request language; keep it in sync with the client's language callback. */
|
|
31
|
+
language?: string;
|
|
32
|
+
/** Change when the current viewer's permissions or entitlements change. */
|
|
33
|
+
accessRevision?: string | number;
|
|
34
|
+
readScope: string;
|
|
30
35
|
/** Asks the visitor to sign in; with it, signed-out visitors are prompted instead of acting anonymously. */
|
|
31
36
|
onSignIn?: () => void;
|
|
32
37
|
/** The host's content URL config (createContentURLs). */
|
|
@@ -37,6 +42,8 @@ export interface ContentKitContextValue {
|
|
|
37
42
|
store: ReadStore;
|
|
38
43
|
}
|
|
39
44
|
export declare const ContentKitContext: import('react').Context<ContentKitContextValue | null>;
|
|
45
|
+
/** Values that can change a read without changing its content reference. */
|
|
46
|
+
export declare function useReadScope(): string;
|
|
40
47
|
/** The ContentKitProvider's value; throws outside one. */
|
|
41
48
|
export declare function useContentKit(): ContentKitContextValue;
|
|
42
49
|
/** The client a hook or component uses: its own, else the provider's; throws without either. */
|
package/dist/react/context.js
CHANGED
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
import { toContentKitError as e } from "../client/errors.js";
|
|
2
|
-
import { createContext as t, useCallback as n, useContext as r,
|
|
2
|
+
import { createContext as t, useCallback as n, useContext as r, useLayoutEffect as i, useRef as a } from "react";
|
|
3
3
|
//#region src/react/context.ts
|
|
4
|
-
var
|
|
5
|
-
function
|
|
6
|
-
|
|
4
|
+
var o = t(null);
|
|
5
|
+
function s() {
|
|
6
|
+
return r(o)?.readScope ?? "{}";
|
|
7
|
+
}
|
|
8
|
+
function c() {
|
|
9
|
+
let e = r(o);
|
|
7
10
|
if (!e) throw Error("contentkit-ui: wrap the tree in <ContentKitProvider client>");
|
|
8
11
|
return e;
|
|
9
12
|
}
|
|
10
|
-
function
|
|
11
|
-
let t =
|
|
13
|
+
function l(e) {
|
|
14
|
+
let t = u(e);
|
|
12
15
|
if (!t) throw Error("contentkit-ui: pass `client` or render inside <ContentKitProvider client>");
|
|
13
16
|
return t;
|
|
14
17
|
}
|
|
15
|
-
function
|
|
16
|
-
let t = r(a);
|
|
17
|
-
return
|
|
18
|
+
function u(e) {
|
|
19
|
+
let t = r(o), n = e ?? t?.client ?? null, a = s();
|
|
20
|
+
return i(() => {
|
|
21
|
+
n?.setReadScope(a);
|
|
22
|
+
}, [n, a]), n;
|
|
18
23
|
}
|
|
19
|
-
function
|
|
20
|
-
let e = r(
|
|
24
|
+
function d() {
|
|
25
|
+
let e = r(o)?.urls;
|
|
21
26
|
if (!e) throw Error("contentkit-ui: pass `urls` to <ContentKitProvider>");
|
|
22
27
|
return e;
|
|
23
28
|
}
|
|
24
|
-
function
|
|
25
|
-
let
|
|
26
|
-
return s.current = t ??
|
|
29
|
+
function f(t) {
|
|
30
|
+
let i = r(o)?.onError, s = a(t ?? i);
|
|
31
|
+
return s.current = t ?? i, n((t, n, r) => {
|
|
27
32
|
let i = e(t);
|
|
28
33
|
i.code !== "aborted" && s.current?.(i, r === void 0 ? { operation: n } : {
|
|
29
34
|
operation: n,
|
|
@@ -32,6 +37,6 @@ function u(t) {
|
|
|
32
37
|
}, []);
|
|
33
38
|
}
|
|
34
39
|
//#endregion
|
|
35
|
-
export {
|
|
40
|
+
export { o as ContentKitContext, c as useContentKit, l as useContentKitClient, d as useContentURLs, f as useErrorReporter, u as useOptionalContentKitClient, s as useReadScope };
|
|
36
41
|
|
|
37
42
|
//# sourceMappingURL=context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../src/react/context.ts"],"sourcesContent":["import { createContext, useCallback, useContext, 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 /** 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/** 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
|
|
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":";;;AAkFA,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"}
|
package/dist/react/engagement.js
CHANGED
|
@@ -9,7 +9,7 @@ var s = {
|
|
|
9
9
|
mine: 0
|
|
10
10
|
};
|
|
11
11
|
function c(o, c = {}) {
|
|
12
|
-
let { client: l, store: u,
|
|
12
|
+
let { client: l, store: u, scope: d } = n(c.client), f = t("reaction", d, o.kind, o.id), p = r(u, f, {
|
|
13
13
|
type: "reaction",
|
|
14
14
|
ref: {
|
|
15
15
|
kind: o.kind,
|
|
@@ -43,7 +43,7 @@ function c(o, c = {}) {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
function l(i, s = {}) {
|
|
46
|
-
let { client: c, store: l,
|
|
46
|
+
let { client: c, store: l, scope: u } = n(s.client), d = t("favorite", u, i.kind, i.id), f = r(l, d, {
|
|
47
47
|
type: "favorite",
|
|
48
48
|
ref: {
|
|
49
49
|
kind: i.kind,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engagement.js","names":[],"sources":["../../src/react/engagement.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport { toContentKitError } from \"../client/errors.js\";\nimport type { Reaction } from \"../client/content/types.js\";\nimport type { FavoriteState, ReactionCounts, RefBody } from \"../client/generated/wire.js\";\nimport { withReaction } from \"./comments.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\nexport interface UseReaction {\n counts: ReactionCounts;\n loading: boolean;\n error?: ContentKitError;\n /** Sets the caller's reaction at once; rolled back if the server refuses it. */\n set: (value: Reaction) => Promise<void>;\n /** Sets value, or clears it when it is the current one. */\n toggle: (value: 1 | -1) => Promise<void>;\n}\n\nconst ZERO: ReactionCounts = { likes: 0, dislikes: 0, mine: 0 };\n\n/** An item's likes and dislikes and the caller's own reaction (initial: counts the host already has). */\nexport function useReaction(ref: RefBody, o: { initial?: ReactionCounts; client?: ContentKitClient } = {}): UseReaction {\n const { client, store,
|
|
1
|
+
{"version":3,"file":"engagement.js","names":[],"sources":["../../src/react/engagement.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport { toContentKitError } from \"../client/errors.js\";\nimport type { Reaction } from \"../client/content/types.js\";\nimport type { FavoriteState, ReactionCounts, RefBody } from \"../client/generated/wire.js\";\nimport { withReaction } from \"./comments.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\nexport interface UseReaction {\n counts: ReactionCounts;\n loading: boolean;\n error?: ContentKitError;\n /** Sets the caller's reaction at once; rolled back if the server refuses it. */\n set: (value: Reaction) => Promise<void>;\n /** Sets value, or clears it when it is the current one. */\n toggle: (value: 1 | -1) => Promise<void>;\n}\n\nconst ZERO: ReactionCounts = { likes: 0, dislikes: 0, mine: 0 };\n\n/** An item's likes and dislikes and the caller's own reaction (initial: counts the host already has). */\nexport function useReaction(ref: RefBody, o: { initial?: ReactionCounts; client?: ContentKitClient } = {}): UseReaction {\n const { client, store, scope } = useContentScope(o.client);\n const key = keyOf(\"reaction\", scope, ref.kind, ref.id);\n const r = useResource<ReactionCounts>(store, key, { type: \"reaction\", ref: { kind: ref.kind, id: ref.id } }, (s) => client.reactions.get(ref, s), { initial: o.initial });\n const counts = r.data ?? ZERO;\n const { kind, id } = ref;\n const set = useCallback(\n async (value: Reaction) => {\n const prev = store.snapshot<ReactionCounts>(key).data ?? counts;\n const token = store.mark(key);\n store.set(key, withReaction(prev, value));\n try {\n await client.reactions.set({ kind, id }, value);\n } catch (e) {\n store.rollback(key, token, prev);\n throw toContentKitError(e);\n }\n },\n [client, store, key, kind, id, counts],\n );\n const toggle = useCallback((value: 1 | -1) => set(counts.mine === value ? 0 : value), [set, counts.mine]);\n return { counts, loading: r.loading || (!r.loaded && o.initial === undefined), error: r.error, set, toggle };\n}\n\nexport interface UseFavorite {\n favorited: boolean;\n /** How many favorited the item; undefined until read. */\n count?: number;\n loading: boolean;\n /** A write is in flight. */\n pending: boolean;\n error?: ContentKitError;\n /** Sets it at once (count moved by one); rolled back if the server refuses it. */\n set: (favorited: boolean) => Promise<void>;\n toggle: () => Promise<void>;\n}\n\n/**\n * An item's favorite count and whether the signed-in caller favorited it\n * (initial: what the host already has). Signed out, favorited stays false.\n */\nexport function useFavorite(ref: RefBody, o: { initial?: FavoriteState; client?: ContentKitClient } = {}): UseFavorite {\n const { client, store, scope } = useContentScope(o.client);\n const key = keyOf(\"favorite\", scope, ref.kind, ref.id);\n const r = useResource<FavoriteState>(store, key, { type: \"favorite\", ref: { kind: ref.kind, id: ref.id } }, (s) => client.favorites.get(ref, s), { initial: o.initial });\n const favorited = !!r.data?.favorited;\n const [pending, setPending] = useState(false);\n const { kind, id } = ref;\n const set = useCallback(\n async (next: boolean) => {\n const prev = store.snapshot<FavoriteState>(key).data;\n const token = store.mark(key);\n if (prev && prev.favorited !== next) store.set<FavoriteState>(key, { favorited: next, count: Math.max(0, prev.count + (next ? 1 : -1)) });\n setPending(true);\n try {\n await client.favorites.set({ kind, id }, next);\n } catch (e) {\n if (prev) store.rollback(key, token, prev);\n throw toContentKitError(e);\n } finally {\n setPending(false);\n }\n },\n [client, store, key, kind, id],\n );\n const toggle = useCallback(() => set(!favorited), [set, favorited]);\n return { favorited, count: r.data?.count, loading: r.loading || (!r.loaded && o.initial === undefined), pending, error: r.error, set, toggle };\n}\n"],"mappings":";;;;;AAmBA,IAAM,IAAuB;CAAE,OAAO;CAAG,UAAU;CAAG,MAAM;AAAE;AAG9D,SAAgB,EAAY,GAAc,IAA6D,CAAC,GAAgB;CACtH,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAM,EAAM,YAAY,GAAO,EAAI,MAAM,EAAI,EAAE,GAC/C,IAAI,EAA4B,GAAO,GAAK;EAAE,MAAM;EAAY,KAAK;GAAE,MAAM,EAAI;GAAM,IAAI,EAAI;EAAG;CAAE,IAAI,MAAM,EAAO,UAAU,IAAI,GAAK,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,CAAC,GAClK,IAAS,EAAE,QAAQ,GACnB,EAAE,SAAM,UAAO,GACf,IAAM,EACV,OAAO,MAAoB;EACzB,IAAM,IAAO,EAAM,SAAyB,CAAG,CAAC,CAAC,QAAQ,GACnD,IAAQ,EAAM,KAAK,CAAG;EAC5B,EAAM,IAAI,GAAK,EAAa,GAAM,CAAK,CAAC;EACxC,IAAI;GACF,MAAM,EAAO,UAAU,IAAI;IAAE;IAAM;GAAG,GAAG,CAAK;EAChD,SAAS,GAAG;GAEV,MADA,EAAM,SAAS,GAAK,GAAO,CAAI,GACzB,EAAkB,CAAC;EAC3B;CACF,GACA;EAAC;EAAQ;EAAO;EAAK;EAAM;EAAI;CAAM,CACvC,GACM,IAAS,GAAa,MAAkB,EAAI,EAAO,SAAS,IAAQ,IAAI,CAAK,GAAG,CAAC,GAAK,EAAO,IAAI,CAAC;CACxG,OAAO;EAAE;EAAQ,SAAS,EAAE,WAAY,CAAC,EAAE,UAAU,EAAE,YAAY,KAAA;EAAY,OAAO,EAAE;EAAO;EAAK;CAAO;AAC7G;AAmBA,SAAgB,EAAY,GAAc,IAA4D,CAAC,GAAgB;CACrH,IAAM,EAAE,WAAQ,UAAO,aAAU,EAAgB,EAAE,MAAM,GACnD,IAAM,EAAM,YAAY,GAAO,EAAI,MAAM,EAAI,EAAE,GAC/C,IAAI,EAA2B,GAAO,GAAK;EAAE,MAAM;EAAY,KAAK;GAAE,MAAM,EAAI;GAAM,IAAI,EAAI;EAAG;CAAE,IAAI,MAAM,EAAO,UAAU,IAAI,GAAK,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,CAAC,GACjK,IAAY,CAAC,CAAC,EAAE,MAAM,WACtB,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,EAAE,SAAM,UAAO,GACf,IAAM,EACV,OAAO,MAAkB;EACvB,IAAM,IAAO,EAAM,SAAwB,CAAG,CAAC,CAAC,MAC1C,IAAQ,EAAM,KAAK,CAAG;EAE5B,AADI,KAAQ,EAAK,cAAc,KAAM,EAAM,IAAmB,GAAK;GAAE,WAAW;GAAM,OAAO,KAAK,IAAI,GAAG,EAAK,SAAS,IAAO,IAAI,GAAG;EAAE,CAAC,GACxI,EAAW,EAAI;EACf,IAAI;GACF,MAAM,EAAO,UAAU,IAAI;IAAE;IAAM;GAAG,GAAG,CAAI;EAC/C,SAAS,GAAG;GAEV,MADI,KAAM,EAAM,SAAS,GAAK,GAAO,CAAI,GACnC,EAAkB,CAAC;EAC3B,UAAU;GACR,EAAW,EAAK;EAClB;CACF,GACA;EAAC;EAAQ;EAAO;EAAK;EAAM;CAAE,CAC/B,GACM,IAAS,QAAkB,EAAI,CAAC,CAAS,GAAG,CAAC,GAAK,CAAS,CAAC;CAClE,OAAO;EAAE;EAAW,OAAO,EAAE,MAAM;EAAO,SAAS,EAAE,WAAY,CAAC,EAAE,UAAU,EAAE,YAAY,KAAA;EAAY;EAAS,OAAO,EAAE;EAAO;EAAK;CAAO;AAC/I"}
|
package/dist/react/folder.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface MediaFolderOptions {
|
|
|
10
10
|
* The upload paths shown and added to, e.g. ["images/{name}",
|
|
11
11
|
* "videos/{name}"]: one group each. Default every pattern path of the kind
|
|
12
12
|
* the server does not name. A file goes to the first path whose rule takes
|
|
13
|
-
* its type
|
|
13
|
+
* its type.
|
|
14
14
|
*/
|
|
15
15
|
paths?: readonly string[];
|
|
16
16
|
/**
|
|
@@ -38,15 +38,26 @@ export interface FolderGroup {
|
|
|
38
38
|
files: FileInfo[];
|
|
39
39
|
}
|
|
40
40
|
export interface UseMediaFolder {
|
|
41
|
+
/** The item; null until the host has one (files added meanwhile wait for it). */
|
|
42
|
+
ref: RefBody | null;
|
|
43
|
+
/** The options the folder was opened with. */
|
|
44
|
+
options: MediaFolderOptions;
|
|
41
45
|
/** The editor read of the item (every window, polled while processing). */
|
|
42
46
|
read: UseMediaRead;
|
|
43
47
|
groups: FolderGroup[];
|
|
44
48
|
/** Every group's uploads. */
|
|
45
49
|
uploads: FileInfo[];
|
|
50
|
+
/** The upload queue; a file leaves it once the editor read lists it. */
|
|
46
51
|
queue: UseUploadQueue;
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Files added before the item and its editor read (the upload rules) were
|
|
54
|
+
* known. They are screened and queued when the rules arrive, or refused
|
|
55
|
+
* when the read fails or states no rules (the actor may not edit the item).
|
|
56
|
+
*/
|
|
57
|
+
waiting: readonly File[];
|
|
58
|
+
/** Screens files against the rules and queues the rest (once the rules are known); refusals land in refused. */
|
|
59
|
+
add: (files: Iterable<File>) => void;
|
|
60
|
+
/** The last screening's refusals. */
|
|
50
61
|
refused: Screened["refused"];
|
|
51
62
|
dismiss: () => void;
|
|
52
63
|
/** Moves an upload to index among its group's uploads. */
|
|
@@ -57,13 +68,21 @@ export interface UseMediaFolder {
|
|
|
57
68
|
edit: (path: string, edit: Edit | null) => Promise<void>;
|
|
58
69
|
/** Uploads file in place of the upload at path (same position and meta, no edit). */
|
|
59
70
|
replace: (path: string, file: File) => Promise<void>;
|
|
60
|
-
/** Commits the uploaded files in queue order
|
|
71
|
+
/** Commits the uploaded files in queue order. */
|
|
61
72
|
commit: () => Promise<FileInfo[]>;
|
|
62
73
|
/** Stops every upload, aborts its multipart upload and empties the queue (before deleting a draft). */
|
|
63
74
|
discard: () => void;
|
|
64
75
|
/** An update (move, remove, rename, edit, replace) is running. */
|
|
65
76
|
updating: boolean;
|
|
66
77
|
committing: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Work is in flight: files wait for the rules, uploads are queued or
|
|
80
|
+
* running, an automatic commit is due, or a commit or update runs.
|
|
81
|
+
* Leaving the page now interrupts it.
|
|
82
|
+
*/
|
|
83
|
+
busy: boolean;
|
|
84
|
+
/** The item's uploads plus the files on their way in (waiting, queued, uploading, uploaded, being added); failed uploads are not counted. */
|
|
85
|
+
fileCount: number;
|
|
67
86
|
/** The last update's or commit's failure; cleared by the next success. */
|
|
68
87
|
error?: ContentKitError;
|
|
69
88
|
}
|
|
@@ -76,8 +95,9 @@ export declare function thumbnailOf(read: ReadResult | null | undefined, upload:
|
|
|
76
95
|
/**
|
|
77
96
|
* An item's folder for its editors: the editor read, an upload queue, the
|
|
78
97
|
* kind's upload rules screening files before they upload, and the updates
|
|
79
|
-
* (move, remove, rename, edit, replace).
|
|
80
|
-
*
|
|
81
|
-
*
|
|
98
|
+
* (move, remove, rename, edit, replace). Files added before the editor read
|
|
99
|
+
* arrives wait for its rules; with a null ref they wait for the item too (a
|
|
100
|
+
* draft created on the first drop). Uploads the worker fails after the read
|
|
101
|
+
* are reported once ("folder.process"). Another ref gets a fresh queue.
|
|
82
102
|
*/
|
|
83
|
-
export declare function useMediaFolder(ref: RefBody, o?: MediaFolderOptions): UseMediaFolder;
|
|
103
|
+
export declare function useMediaFolder(ref: RefBody | null | undefined, o?: MediaFolderOptions): UseMediaFolder;
|