@openrails/contentkit-ui 0.69.0 → 0.71.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 +17 -13
- package/dist/client/content/comments.d.ts +2 -2
- package/dist/client/content/comments.js +3 -3
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/polls.d.ts +2 -2
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/content/posts.d.ts +20 -12
- package/dist/client/content/posts.js +5 -13
- 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/content/types.d.ts +2 -2
- package/dist/client/content/types.js +1 -1
- package/dist/client/content/types.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 +120 -172
- package/dist/client/generated/routes.js +207 -191
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +6 -1
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/media/client.d.ts +7 -5
- package/dist/client/media/client.js +1 -1
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/rules.d.ts +2 -2
- package/dist/client/media/rules.js +1 -5
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/taxonomy.d.ts +1 -1
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/client.js +5 -5
- package/dist/components/social/poll-editor.d.ts +1 -1
- package/dist/components/social/poll-editor.js.map +1 -1
- package/dist/react/comments.d.ts +1 -1
- package/dist/react/comments.js.map +1 -1
- package/dist/react/polls.d.ts +2 -2
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.d.ts +12 -10
- package/dist/react/posts.js +132 -99
- package/dist/react/posts.js.map +1 -1
- package/dist/react/public.d.ts +2 -2
- package/dist/react/public.js +7 -4
- package/dist/react/public.js.map +1 -1
- package/package.json +1 -1
package/dist/react/posts.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useMediaRead as e } from "./read.js";
|
|
2
2
|
import { keyOf as t, offsetPages as n, useContentScope as r, useList as i, useResource as a } from "./use-resource.js";
|
|
3
|
-
import {
|
|
3
|
+
import { IMAGE_SCHEME as o, imageRef as s, imageRefs as c } from "../client/content/posts.js";
|
|
4
|
+
import { useCallback as l, useEffect as u, useMemo as d, useRef as f, useState as p } from "react";
|
|
4
5
|
//#region src/react/posts.ts
|
|
5
|
-
function
|
|
6
|
+
function m(e = {}, a = {}) {
|
|
6
7
|
let { client: o, store: s, scope: c } = r(a.client), l = e.pageSize ?? 20;
|
|
7
8
|
return i(s, t("posts", c, !!e.admin, e.language, e.sort, e.draft, e.deleted, e.q, l), {
|
|
8
9
|
type: "posts",
|
|
@@ -19,131 +20,163 @@ function d(e = {}, a = {}) {
|
|
|
19
20
|
sort: e.sort
|
|
20
21
|
}, n)));
|
|
21
22
|
}
|
|
22
|
-
var
|
|
23
|
-
|
|
23
|
+
var h = (e) => [...new Set([...(e ?? "").matchAll(c())].map((e) => e[1]))], g = (e) => {
|
|
24
|
+
let t = e.slice(e.lastIndexOf("/") + 1), n = t.lastIndexOf(".");
|
|
25
|
+
return n > 0 ? t.slice(0, n) : t;
|
|
26
|
+
};
|
|
27
|
+
function _(e, t = Date.now()) {
|
|
24
28
|
return !e.is_draft && !e.moderation && (!e.live_at || Date.parse(e.live_at) <= t);
|
|
25
29
|
}
|
|
26
|
-
function
|
|
27
|
-
let { client:
|
|
30
|
+
function v(n, i = {}) {
|
|
31
|
+
let { client: m, store: _, scope: v } = r(i.client), [y, b] = p(null), x = n ?? y, S = x ? t("post", v, x) : null, C = a(_, S, {
|
|
28
32
|
type: "post",
|
|
29
|
-
id:
|
|
30
|
-
}, (e) =>
|
|
31
|
-
|
|
33
|
+
id: x ?? void 0
|
|
34
|
+
}, (e) => m.posts.get(x, e), { initial: i.initial }), w = C.data ?? null, [T, E] = p(0), D = l(async (e) => {
|
|
35
|
+
E((e) => e + 1);
|
|
32
36
|
try {
|
|
33
37
|
return await e();
|
|
34
38
|
} finally {
|
|
35
|
-
|
|
39
|
+
E((e) => e - 1);
|
|
36
40
|
}
|
|
37
|
-
}, []),
|
|
38
|
-
if (!
|
|
39
|
-
return
|
|
40
|
-
}, [
|
|
41
|
-
|
|
42
|
-
let e =
|
|
41
|
+
}, []), O = l(() => {
|
|
42
|
+
if (!x) throw Error("contentkit-ui: create the post first");
|
|
43
|
+
return x;
|
|
44
|
+
}, [x]), [k, A] = p(/* @__PURE__ */ new Map()), j = f([]);
|
|
45
|
+
u(() => {
|
|
46
|
+
let e = j.current;
|
|
43
47
|
return () => {
|
|
44
|
-
for (let t of e
|
|
48
|
+
for (let t of e) URL.revokeObjectURL(t);
|
|
45
49
|
};
|
|
46
50
|
}, []);
|
|
47
|
-
let
|
|
48
|
-
let e =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
let M = d(() => new Map(Object.entries(w?.images ?? {})), [w?.images]), N = d(() => {
|
|
52
|
+
let e = h(w?.body);
|
|
53
|
+
return w?.cover && !w.cover_url && !e.includes(w.cover) && e.push(w.cover), e.filter((e) => !k.has(e));
|
|
54
|
+
}, [
|
|
55
|
+
w?.body,
|
|
56
|
+
w?.cover,
|
|
57
|
+
w?.cover_url,
|
|
58
|
+
k
|
|
59
|
+
]), P = d(() => m.media.postRef(x ?? "none"), [m, x]), F = e(P, {
|
|
55
60
|
editor: !0,
|
|
56
|
-
read:
|
|
57
|
-
client:
|
|
58
|
-
}),
|
|
61
|
+
read: N.length ? void 0 : null,
|
|
62
|
+
client: m
|
|
63
|
+
}), I = d(() => {
|
|
59
64
|
let e = /* @__PURE__ */ new Map();
|
|
60
|
-
for (let t of
|
|
61
|
-
let n = t.upload && t.editor_url ? p(t.path) : void 0;
|
|
62
|
-
n && e.set(n, t.editor_url);
|
|
63
|
-
}
|
|
65
|
+
for (let t of F.read?.files ?? []) t.upload && t.editor_url && e.set(g(t.path), t.editor_url);
|
|
64
66
|
return e;
|
|
65
|
-
}, [
|
|
66
|
-
|
|
67
|
-
if (!
|
|
68
|
-
|
|
67
|
+
}, [F.read]), L = (e) => !!F.read?.files.some((t) => t.failed && g(t.path) === e), R = !!F.read && N.some((e) => !I.has(e) && !L(e)), z = f(0), { reload: B } = F;
|
|
68
|
+
u(() => {
|
|
69
|
+
if (!R) {
|
|
70
|
+
z.current = 0;
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
|
-
if (
|
|
73
|
+
if (z.current >= 6) return;
|
|
72
74
|
let e = setTimeout(() => {
|
|
73
|
-
|
|
74
|
-
}, 1e3 * 2 **
|
|
75
|
+
z.current++, B();
|
|
76
|
+
}, 1e3 * 2 ** z.current);
|
|
75
77
|
return () => clearTimeout(e);
|
|
76
78
|
}, [
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
R,
|
|
80
|
+
F.read,
|
|
81
|
+
B
|
|
80
82
|
]);
|
|
81
|
-
let
|
|
83
|
+
let V = f(/* @__PURE__ */ new Map());
|
|
84
|
+
u(() => {
|
|
85
|
+
for (let e of [
|
|
86
|
+
k,
|
|
87
|
+
M,
|
|
88
|
+
I
|
|
89
|
+
]) for (let [t, n] of e) V.current.set(n, t);
|
|
90
|
+
}, [
|
|
91
|
+
k,
|
|
92
|
+
M,
|
|
93
|
+
I
|
|
94
|
+
]);
|
|
95
|
+
let H = l((e) => {
|
|
82
96
|
if (!e) return;
|
|
83
|
-
let t =
|
|
84
|
-
return
|
|
85
|
-
}, [
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
let t =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
let t = e.startsWith("contentkit:") ? e.slice(o.length) : e;
|
|
98
|
+
return k.get(t) ?? M.get(t) ?? I.get(t) ?? e;
|
|
99
|
+
}, [
|
|
100
|
+
k,
|
|
101
|
+
M,
|
|
102
|
+
I
|
|
103
|
+
]), U = d(() => w?.body.replace(c(), (e, t) => k.get(t) ?? I.get(t) ?? e), [
|
|
104
|
+
w?.body,
|
|
105
|
+
k,
|
|
106
|
+
I
|
|
107
|
+
]), W = l((e) => {
|
|
108
|
+
let t = new Map(V.current);
|
|
109
|
+
for (let e of [
|
|
110
|
+
k,
|
|
111
|
+
M,
|
|
112
|
+
I
|
|
113
|
+
]) for (let [n, r] of e) t.set(r, n);
|
|
114
|
+
let n = e;
|
|
115
|
+
for (let [e, r] of t) n = n.split(e).join(s(r)).split(e.replaceAll("&", "&")).join(s(r));
|
|
116
|
+
return n;
|
|
117
|
+
}, [
|
|
118
|
+
k,
|
|
119
|
+
M,
|
|
120
|
+
I
|
|
121
|
+
]), G = l((e, t) => {
|
|
122
|
+
if (typeof URL.createObjectURL != "function") return;
|
|
123
|
+
let n = URL.createObjectURL(t);
|
|
124
|
+
return j.current.push(n), A((t) => new Map(t).set(e, n)), n;
|
|
125
|
+
}, []), K = l((e) => D(async () => {
|
|
126
|
+
let t = await m.posts.create(e);
|
|
127
|
+
return b(t.id), t;
|
|
128
|
+
}), [m, D]), q = l((e) => D(() => m.posts.update(O(), e.body === void 0 ? e : {
|
|
97
129
|
...e,
|
|
98
|
-
body:
|
|
130
|
+
body: W(e.body)
|
|
99
131
|
})), [
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
]),
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
]),
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
]),
|
|
113
|
-
if (!e) return
|
|
114
|
-
let t = await
|
|
115
|
-
return
|
|
132
|
+
m,
|
|
133
|
+
D,
|
|
134
|
+
O,
|
|
135
|
+
W
|
|
136
|
+
]), J = l(() => D(() => m.posts.delete(O())), [
|
|
137
|
+
m,
|
|
138
|
+
D,
|
|
139
|
+
O
|
|
140
|
+
]), Y = l(() => D(() => m.posts.restore(O())), [
|
|
141
|
+
m,
|
|
142
|
+
D,
|
|
143
|
+
O
|
|
144
|
+
]), X = l((e) => D(async () => {
|
|
145
|
+
if (!e) return m.posts.setCover(O(), null);
|
|
146
|
+
let t = await m.media.uploadNamed(m.media.postRef(O()), e), n = await m.posts.setCover(O(), t.name), r = G(t.name, e) ?? n;
|
|
147
|
+
return r && V.current.set(r, t.name), r;
|
|
116
148
|
}), [
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
]),
|
|
122
|
-
let t = await
|
|
123
|
-
return V(
|
|
149
|
+
m,
|
|
150
|
+
D,
|
|
151
|
+
O,
|
|
152
|
+
G
|
|
153
|
+
]), Z = l((e) => D(async () => {
|
|
154
|
+
let t = await m.media.uploadInline(O(), e), n = G(t.name, e) ?? t.url ?? t.ref;
|
|
155
|
+
return V.current.set(n, t.name), n;
|
|
124
156
|
}), [
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
157
|
+
m,
|
|
158
|
+
D,
|
|
159
|
+
O,
|
|
160
|
+
G
|
|
129
161
|
]);
|
|
130
162
|
return {
|
|
131
|
-
post:
|
|
132
|
-
loading: !!
|
|
133
|
-
error:
|
|
134
|
-
saving:
|
|
135
|
-
create:
|
|
136
|
-
update:
|
|
137
|
-
remove:
|
|
138
|
-
restore:
|
|
139
|
-
setCover:
|
|
140
|
-
uploadImage:
|
|
141
|
-
imageSrc:
|
|
142
|
-
|
|
143
|
-
|
|
163
|
+
post: w,
|
|
164
|
+
loading: !!S && (C.loading || !C.loaded && i.initial === void 0),
|
|
165
|
+
error: C.error,
|
|
166
|
+
saving: T > 0,
|
|
167
|
+
create: K,
|
|
168
|
+
update: q,
|
|
169
|
+
remove: J,
|
|
170
|
+
restore: Y,
|
|
171
|
+
setCover: X,
|
|
172
|
+
uploadImage: Z,
|
|
173
|
+
imageSrc: H,
|
|
174
|
+
coverSrc: H(w?.cover),
|
|
175
|
+
editorBody: U,
|
|
176
|
+
storedBody: W
|
|
144
177
|
};
|
|
145
178
|
}
|
|
146
179
|
//#endregion
|
|
147
|
-
export {
|
|
180
|
+
export { _ as isPublished, v as usePost, m as usePosts };
|
|
148
181
|
|
|
149
182
|
//# sourceMappingURL=posts.js.map
|
package/dist/react/posts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posts.js","names":[],"sources":["../../src/react/posts.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Sort } from \"../client/content/types.js\";\nimport type { Post, PostInput } from \"../client/generated/wire.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface PostFilter {\n language?: string;\n sort?: Sort;\n /** Staff: every post, drafts and scheduled ones included (PostWrite). */\n admin?: boolean;\n /** With admin: true only drafts, false no drafts. */\n draft?: boolean;\n /** With admin: the deleted posts instead of the live ones. */\n deleted?: boolean;\n /** With admin: only posts whose title, excerpt or body contains this text. */\n q?: string;\n pageSize?: number;\n}\n\n/** Posts, a page at a time: the published ones, or every one for staff. */\nexport function usePosts(f: PostFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Post> {\n const { client, store, scope } = useContentScope(o.client);\n const size = f.pageSize ?? 20;\n return useList(\n store,\n keyOf(\"posts\", scope, !!f.admin, f.language, f.sort, f.draft, f.deleted, f.q, size),\n { type: \"posts\", scope: f.admin && f.deleted ? \"deleted\" : undefined },\n offsetPages(size, (q, signal) =>\n f.admin\n ? client.posts.adminList({ ...q, language: f.language, draft: f.draft, deleted: f.deleted, q: f.q }, signal)\n : client.posts.list({ ...q, language: f.language, sort: f.sort }, signal),\n ),\n );\n}\n\nexport interface UsePost {\n /** null before create (or while loading). */\n post: Post | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Creates the post; the hook then edits it. */\n create: (input: PostInput) => Promise<Post>;\n /** Updates the given fields; a body from the editor is stored with its images' public URLs (storedBody). */\n update: (patch: PostInput) => Promise<Post>;\n remove: () => Promise<void>;\n /** Restores the deleted post (PostWrite). */\n restore: () => Promise<Post>;\n /** Uploads the cover (null clears it); resolves with a URL that shows it now. */\n setCover: (file: Blob | null) => Promise<string | null>;\n /** Uploads an image for the body; resolves with a URL that shows it now, to place in the editor. */\n uploadImage: (file: Blob) => Promise<string>;\n /**\n * A URL that shows one of the post's images now (the cover, a body image):\n * an unpublished post's images are not public yet, so its editors see them\n * through signed URLs of an editor read; a fresh upload shows from the\n * file itself. Any other URL is returned as is.\n */\n imageSrc: (url: string | null | undefined) => string | undefined;\n /** post.body with its images through imageSrc: what a rich-text editor shows. */\n editorBody: string | undefined;\n /** HTML from the editor with the URLs imageSrc gave back to the images' public URLs. */\n storedBody: (html: string) => string;\n}\n\nconst NAME = \"i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\";\nconst nameIn = (url: string) => url.match(new RegExp(NAME))?.[0];\n// An image URL in HTML: everything around a name up to a quote, a space or a bracket.\nconst imageURLs = new RegExp(`[^\\\\s\"'()<>]*${NAME}[^\\\\s\"'()<>]*`, \"g\");\n\n/** Whether readers see the post, and so its images' public files exist. */\nexport function isPublished(post: Post, now = Date.now()): boolean {\n return !post.is_draft && !post.moderation && (!post.live_at || Date.parse(post.live_at) <= now);\n}\n\n/** One post: read, and for staff (PostWrite) create, update, delete, restore, cover and body images. null creates a new post. */\nexport function usePost(id: string | null | undefined, o: { initial?: Post; client?: ContentKitClient } = {}): UsePost {\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(\"post\", scope, pid) : null;\n const r = useResource<Post | null>(store, key, { type: \"post\", id: pid ?? undefined }, (s) => client.posts.get(pid!, s), { initial: o.initial });\n const post = 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 post first\");\n return pid;\n }, [pid]);\n\n // Images: fresh uploads show from their file; an unpublished post's from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const given = useRef(new Map<string, string>()); // each URL imageSrc gave -> the image's public URL\n useEffect(() => {\n const urls = given.current;\n return () => {\n for (const u of urls.keys()) if (u.startsWith(\"blob:\")) URL.revokeObjectURL(u);\n };\n }, []);\n const names = useMemo(() => {\n const out = new Map<string, string>();\n for (const u of [...(post?.body.match(imageURLs) ?? []), post?.cover_url ?? \"\"]) {\n const n = nameIn(u);\n if (n && !out.has(n)) out.set(n, u);\n }\n return out;\n }, [post?.body, post?.cover_url]);\n const unread = !!post && !isPublished(post) && [...names.keys()].some((n) => !previews.has(n));\n const folder = useMemo(() => client.media.postRef(pid ?? \"none\"), [client, pid]);\n const read = useMediaRead(folder, { editor: true, read: unread ? undefined : null, client });\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of read.read?.files ?? []) {\n const n = f.upload && f.editor_url ? nameIn(f.path) : undefined;\n if (n) out.set(n, f.editor_url!);\n }\n return out;\n }, [read.read]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const missing = unread && !!read.read && [...names.keys()].some((n) => !previews.has(n) && !views.has(n) && !read.read!.files.some((f) => f.failed && nameIn(f.path) === n));\n const tries = useRef(0);\n const { reload } = read;\n useEffect(() => {\n if (!missing) {\n tries.current = 0;\n return;\n }\n if (tries.current >= 6) return;\n const t = setTimeout(() => {\n tries.current++;\n reload();\n }, 1000 * 2 ** tries.current);\n return () => clearTimeout(t);\n }, [missing, read.read, reload]);\n\n const imageSrc = useCallback(\n (url: string | null | undefined) => {\n if (!url) return undefined;\n const n = nameIn(url);\n const shown = n ? (previews.get(n) ?? views.get(n)) : undefined;\n if (!shown) return url;\n given.current.set(shown, url);\n return shown;\n },\n [previews, views],\n );\n const editorBody = useMemo(() => post?.body.replace(imageURLs, (u) => imageSrc(u) ?? u), [post?.body, imageSrc]);\n const storedBody = useCallback((html: string) => {\n let out = html;\n for (const [shown, url] of given.current) out = out.split(shown).join(url).split(shown.replaceAll(\"&\", \"&\")).join(url);\n return out;\n }, []);\n const preview = useCallback((name: string, url: string, file: Blob) => {\n if (typeof URL.createObjectURL !== \"function\") return url;\n const shown = URL.createObjectURL(file);\n given.current.set(shown, url);\n setPreviews((m) => new Map(m).set(name, shown));\n return shown;\n }, []);\n\n const create = useCallback(\n (input: PostInput) =>\n run(async () => {\n const post = await client.posts.create(input);\n setCreated(post.id);\n return post;\n }),\n [client, run],\n );\n const update = useCallback(\n (patch: PostInput) => run(() => client.posts.update(need(), patch.body === undefined ? patch : { ...patch, body: storedBody(patch.body) })),\n [client, run, need, storedBody],\n );\n const remove = useCallback(() => run(() => client.posts.delete(need())), [client, run, need]);\n const restore = useCallback(() => run(() => client.posts.restore(need())), [client, run, need]);\n const setCover = useCallback(\n (file: Blob | null) =>\n run(async () => {\n if (!file) return client.posts.setCover(need(), null);\n const up = await client.media.uploadNamed(client.media.postRef(need()), file);\n const url = await client.posts.setCover(need(), up.name);\n return url && preview(up.name, url, file);\n }),\n [client, run, need, preview],\n );\n const uploadImage = useCallback(\n (file: Blob) =>\n run(async () => {\n const up = await client.media.uploadInline(need(), file);\n return preview(up.name, up.url, file);\n }),\n [client, run, need, preview],\n );\n return {\n post,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n remove,\n restore,\n setCover,\n uploadImage,\n imageSrc,\n editorBody,\n storedBody,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"posts.js","names":[],"sources":["../../src/react/posts.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Sort } from \"../client/content/types.js\";\nimport type { Post, PostInput } from \"../client/generated/wire.js\";\nimport { IMAGE_SCHEME, imageRef, imageRefs } from \"../client/content/posts.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface PostFilter {\n language?: string;\n sort?: Sort;\n /** Staff: every post, drafts and scheduled ones included (root:posts:write). */\n admin?: boolean;\n /** With admin: true only drafts, false no drafts. */\n draft?: boolean;\n /** With admin: the deleted posts instead of the live ones. */\n deleted?: boolean;\n /** With admin: only posts whose title, excerpt or body contains this text. */\n q?: string;\n pageSize?: number;\n}\n\n/** Posts, a page at a time: the published ones, or every one for staff. */\nexport function usePosts(f: PostFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Post> {\n const { client, store, scope } = useContentScope(o.client);\n const size = f.pageSize ?? 20;\n return useList(\n store,\n keyOf(\"posts\", scope, !!f.admin, f.language, f.sort, f.draft, f.deleted, f.q, size),\n { type: \"posts\", scope: f.admin && f.deleted ? \"deleted\" : undefined },\n offsetPages(size, (q, signal) =>\n f.admin\n ? client.posts.adminList({ ...q, language: f.language, draft: f.draft, deleted: f.deleted, q: f.q }, signal)\n : client.posts.list({ ...q, language: f.language, sort: f.sort }, signal),\n ),\n );\n}\n\nexport interface UsePost {\n /** null before create (or while loading). */\n post: Post | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Creates the post; the hook then edits it. */\n create: (input: PostInput) => Promise<Post>;\n /** Updates the given fields; a body from the editor is stored with image references (storedBody). */\n update: (patch: PostInput) => Promise<Post>;\n remove: () => Promise<void>;\n /** Restores the deleted post (root:posts:write). */\n restore: () => Promise<Post>;\n /** Uploads the cover (null clears it); resolves with a URL that shows it now. */\n setCover: (file: Blob | null) => Promise<string | null>;\n /** Uploads an image for the body; resolves with a URL that shows it now, to place in the editor. */\n uploadImage: (file: Blob) => Promise<string>;\n /**\n * A URL that shows one of the post's images now, by name (\"i-{uuid}\") or\n * reference: a fresh upload from its file, else its public file\n * (`post.images`), else, while it has none (an unpublished post's), its\n * signed editor view from an editor read. Anything else is returned as is.\n */\n imageSrc: (image: string | null | undefined) => string | undefined;\n /** The cover through imageSrc. */\n coverSrc: string | undefined;\n /** post.body with its images through imageSrc: what a rich-text editor shows. */\n editorBody: string | undefined;\n /** HTML from the editor with every image URL this hook showed turned back into the image's reference. */\n storedBody: (html: string) => string;\n}\n\n/** The names of the images a body still references, once each. */\nconst refNames = (body: string | undefined) => [...new Set([...(body ?? \"\").matchAll(imageRefs())].map((m) => m[1]!))];\nconst uploadName = (path: string) => {\n const base = path.slice(path.lastIndexOf(\"/\") + 1);\n const dot = base.lastIndexOf(\".\");\n return dot > 0 ? base.slice(0, dot) : base;\n};\n\n/** Whether readers see the post, and so its images' public files exist. */\nexport function isPublished(post: Post, now = Date.now()): boolean {\n return !post.is_draft && !post.moderation && (!post.live_at || Date.parse(post.live_at) <= now);\n}\n\n/** One post: read, and for staff (root:posts:write) create, update, delete, restore, cover and body images. null creates a new post. */\nexport function usePost(id: string | null | undefined, o: { initial?: Post; client?: ContentKitClient } = {}): UsePost {\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(\"post\", scope, pid) : null;\n const r = useResource<Post | null>(store, key, { type: \"post\", id: pid ?? undefined }, (s) => client.posts.get(pid!, s), { initial: o.initial });\n const post = 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 post first\");\n return pid;\n }, [pid]);\n\n // Images: fresh uploads show from their file, published ones from post.images,\n // the rest (an unpublished post's) from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => {\n for (const u of urls) URL.revokeObjectURL(u);\n };\n }, []);\n const published = useMemo(() => new Map(Object.entries(post?.images ?? {})), [post?.images]);\n const pending = useMemo(() => {\n const names = refNames(post?.body);\n if (post?.cover && !post.cover_url && !names.includes(post.cover)) names.push(post.cover);\n return names.filter((n) => !previews.has(n));\n }, [post?.body, post?.cover, post?.cover_url, previews]);\n const folder = useMemo(() => client.media.postRef(pid ?? \"none\"), [client, pid]);\n const read = useMediaRead(folder, { editor: true, read: pending.length ? undefined : null, client });\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of read.read?.files ?? []) if (f.upload && f.editor_url) out.set(uploadName(f.path), f.editor_url);\n return out;\n }, [read.read]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const failed = (n: string) => !!read.read?.files.some((f) => f.failed && uploadName(f.path) === n);\n const missing = !!read.read && pending.some((n) => !views.has(n) && !failed(n));\n const tries = useRef(0);\n const { reload } = read;\n useEffect(() => {\n if (!missing) {\n tries.current = 0;\n return;\n }\n if (tries.current >= 6) return;\n const t = setTimeout(() => {\n tries.current++;\n reload();\n }, 1000 * 2 ** tries.current);\n return () => clearTimeout(t);\n }, [missing, read.read, reload]);\n\n // Every URL shown for an image, of this and earlier reads, back to its name.\n const shown = useRef(new Map<string, string>());\n useEffect(() => {\n for (const m of [previews, published, views]) for (const [n, u] of m) shown.current.set(u, n);\n }, [previews, published, views]);\n\n const imageSrc = useCallback(\n (image: string | null | undefined) => {\n if (!image) return undefined;\n const n = image.startsWith(IMAGE_SCHEME) ? image.slice(IMAGE_SCHEME.length) : image;\n return previews.get(n) ?? published.get(n) ?? views.get(n) ?? image;\n },\n [previews, published, views],\n );\n const editorBody = useMemo(() => post?.body.replace(imageRefs(), (ref, n: string) => previews.get(n) ?? views.get(n) ?? ref), [post?.body, previews, views]);\n const storedBody = useCallback(\n (html: string) => {\n const back = new Map(shown.current);\n for (const m of [previews, published, views]) for (const [n, u] of m) back.set(u, n);\n let out = html;\n for (const [u, n] of back) out = out.split(u).join(imageRef(n)).split(u.replaceAll(\"&\", \"&\")).join(imageRef(n));\n return out;\n },\n [previews, published, views],\n );\n const preview = useCallback((name: string, file: Blob): string | undefined => {\n if (typeof URL.createObjectURL !== \"function\") return undefined;\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((m) => new Map(m).set(name, url));\n return url;\n }, []);\n\n const create = useCallback(\n (input: PostInput) =>\n run(async () => {\n const post = await client.posts.create(input);\n setCreated(post.id);\n return post;\n }),\n [client, run],\n );\n const update = useCallback(\n (patch: PostInput) => run(() => client.posts.update(need(), patch.body === undefined ? patch : { ...patch, body: storedBody(patch.body) })),\n [client, run, need, storedBody],\n );\n const remove = useCallback(() => run(() => client.posts.delete(need())), [client, run, need]);\n const restore = useCallback(() => run(() => client.posts.restore(need())), [client, run, need]);\n const setCover = useCallback(\n (file: Blob | null) =>\n run(async () => {\n if (!file) return client.posts.setCover(need(), null);\n const up = await client.media.uploadNamed(client.media.postRef(need()), file);\n const cover = await client.posts.setCover(need(), up.name);\n const url = preview(up.name, file) ?? cover;\n if (url) shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, preview],\n );\n const uploadImage = useCallback(\n (file: Blob) =>\n run(async () => {\n const up = await client.media.uploadInline(need(), file);\n const url = preview(up.name, file) ?? up.url ?? up.ref;\n shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, preview],\n );\n return {\n post,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n remove,\n restore,\n setCover,\n uploadImage,\n imageSrc,\n coverSrc: imageSrc(post?.cover),\n editorBody,\n storedBody,\n };\n}\n"],"mappings":"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"}
|
package/dist/react/public.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface PublicImageOptions {
|
|
|
21
21
|
client?: ContentKitClient | null;
|
|
22
22
|
}
|
|
23
23
|
export interface UsePublicImage {
|
|
24
|
-
/** The item's current renditions; the kind's default image when
|
|
24
|
+
/** The item's current renditions; else the kind's default image (when the preset has one); null while unknown or when there is neither. */
|
|
25
25
|
image: PublicPreset | null;
|
|
26
26
|
/** The preset's rules (aspect, widths, min_width) once the presets load. */
|
|
27
27
|
rule?: PresetRule;
|
|
@@ -34,6 +34,6 @@ export interface UsePublicImage {
|
|
|
34
34
|
/**
|
|
35
35
|
* An item's public image for a preset ("avatar", "cover"): the exact
|
|
36
36
|
* renditions from a read's `public` (or the host's listing), else the kind's
|
|
37
|
-
* default image. Reads refresh after commits through the same client.
|
|
37
|
+
* default image, else none. Reads refresh after commits through the same client.
|
|
38
38
|
*/
|
|
39
39
|
export declare function usePublicImage(kind: string, id: string, preset: string, o?: PublicImageOptions): UsePublicImage;
|
package/dist/react/public.js
CHANGED
|
@@ -33,18 +33,21 @@ function d(a, s, c, l = {}) {
|
|
|
33
33
|
return {
|
|
34
34
|
...o(() => {
|
|
35
35
|
let t = h ? l.image : _.read?.public?.find((e) => e.preset === c);
|
|
36
|
-
|
|
36
|
+
if (t && t.renditions.length) return {
|
|
37
37
|
image: {
|
|
38
38
|
preset: c,
|
|
39
39
|
renditions: t.renditions,
|
|
40
40
|
aspect: "aspect" in t && t.aspect || m?.aspect
|
|
41
41
|
},
|
|
42
42
|
isDefault: !1
|
|
43
|
-
}
|
|
43
|
+
};
|
|
44
|
+
if (!h && !_.read) return {
|
|
44
45
|
image: null,
|
|
45
46
|
isDefault: !1
|
|
46
|
-
}
|
|
47
|
-
|
|
47
|
+
};
|
|
48
|
+
let n = m && e(m, s);
|
|
49
|
+
return n ? {
|
|
50
|
+
image: n,
|
|
48
51
|
isDefault: !0
|
|
49
52
|
} : {
|
|
50
53
|
image: null,
|
package/dist/react/public.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.js","names":[],"sources":["../../src/react/public.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { ReadResult } from \"../client/generated/wire.js\";\nimport type { PresetRule } from \"../client/generated/wire.js\";\nimport { defaultImage, isPattern, ruleDir } from \"../client/media/rules.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport { useOptionalContentKitClient } from \"./context.js\";\nimport { useMediaRead } from \"./read.js\";\n\nexport interface UsePresets {\n /** Every kind's public presets; empty until they load (or when the server has none). */\n presets: PresetRule[];\n loaded: boolean;\n error?: ContentKitError;\n}\n\nconst settled = new WeakMap<ContentKitClient, { presets: PresetRule[]; error?: ContentKitError }>();\nconst inflight = new WeakMap<ContentKitClient, Promise<void>>();\n\n/** The public presets (`GET /media/presets`), fetched once per client. */\nexport function usePresets(o: { client?: ContentKitClient | null } = {}): UsePresets {\n const client = useOptionalContentKitClient(o.client);\n const [, rerender] = useState(0);\n const done = client ? settled.get(client) : undefined;\n useEffect(() => {\n if (!client || settled.has(client)) return;\n let live = true;\n let load = inflight.get(client);\n if (!load) {\n load = client.media.presets().then(\n (presets) => void settled.set(client, { presets }),\n (error: ContentKitError) => void settled.set(client, { presets: [], error }),\n ).finally(() => inflight.delete(client));\n inflight.set(client, load);\n }\n void load.then(() => live && rerender((n) => n + 1));\n return () => {\n live = false;\n };\n }, [client]);\n return { presets: done?.presets ?? [], loaded: !!done || !client, error: done?.error };\n}\n\nexport interface PublicImageOptions {\n /** The host's listing of the item's image (its API's): shown without a read. */\n image?: PublicPreset | null;\n /** A read of the item the host already has. */\n read?: ReadResult | null;\n /** Overrides the provider's client. */\n client?: ContentKitClient | null;\n}\n\nexport interface UsePublicImage {\n /** The item's current renditions; the kind's default image when
|
|
1
|
+
{"version":3,"file":"public.js","names":[],"sources":["../../src/react/public.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { ReadResult } from \"../client/generated/wire.js\";\nimport type { PresetRule } from \"../client/generated/wire.js\";\nimport { defaultImage, isPattern, ruleDir } from \"../client/media/rules.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport { useOptionalContentKitClient } from \"./context.js\";\nimport { useMediaRead } from \"./read.js\";\n\nexport interface UsePresets {\n /** Every kind's public presets; empty until they load (or when the server has none). */\n presets: PresetRule[];\n loaded: boolean;\n error?: ContentKitError;\n}\n\nconst settled = new WeakMap<ContentKitClient, { presets: PresetRule[]; error?: ContentKitError }>();\nconst inflight = new WeakMap<ContentKitClient, Promise<void>>();\n\n/** The public presets (`GET /media/presets`), fetched once per client. */\nexport function usePresets(o: { client?: ContentKitClient | null } = {}): UsePresets {\n const client = useOptionalContentKitClient(o.client);\n const [, rerender] = useState(0);\n const done = client ? settled.get(client) : undefined;\n useEffect(() => {\n if (!client || settled.has(client)) return;\n let live = true;\n let load = inflight.get(client);\n if (!load) {\n load = client.media.presets().then(\n (presets) => void settled.set(client, { presets }),\n (error: ContentKitError) => void settled.set(client, { presets: [], error }),\n ).finally(() => inflight.delete(client));\n inflight.set(client, load);\n }\n void load.then(() => live && rerender((n) => n + 1));\n return () => {\n live = false;\n };\n }, [client]);\n return { presets: done?.presets ?? [], loaded: !!done || !client, error: done?.error };\n}\n\nexport interface PublicImageOptions {\n /** The host's listing of the item's image (its API's): shown without a read. */\n image?: PublicPreset | null;\n /** A read of the item the host already has. */\n read?: ReadResult | null;\n /** Overrides the provider's client. */\n client?: ContentKitClient | null;\n}\n\nexport interface UsePublicImage {\n /** The item's current renditions; else the kind's default image (when the preset has one); null while unknown or when there is neither. */\n image: PublicPreset | null;\n /** The preset's rules (aspect, widths, min_width) once the presets load. */\n rule?: PresetRule;\n /** image is the kind's default, not the item's own. */\n isDefault: boolean;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\n/**\n * An item's public image for a preset (\"avatar\", \"cover\"): the exact\n * renditions from a read's `public` (or the host's listing), else the kind's\n * default image, else none. Reads refresh after commits through the same client.\n */\nexport function usePublicImage(kind: string, id: string, preset: string, o: PublicImageOptions = {}): UsePublicImage {\n const client = useOptionalContentKitClient(o.client);\n const { presets, loaded } = usePresets({ client });\n const rule = presets.find((p) => p.kind === kind && p.name === preset);\n const listed = o.image !== undefined;\n // The read waits for the presets, so it lists only the preset's upload.\n const prefix = rule ? (isPattern(rule.from) ? ruleDir(rule.from) : rule.from) : undefined;\n const r = useMediaRead({ kind, id }, { prefix, read: listed || !loaded ? null : o.read, client });\n const image = useMemo((): { image: PublicPreset | null; isDefault: boolean } => {\n const own = listed ? o.image : r.read?.public?.find((p) => p.preset === preset);\n if (own && own.renditions.length) return { image: { preset, renditions: own.renditions, aspect: (\"aspect\" in own && own.aspect) || rule?.aspect }, isDefault: false };\n if (!listed && !r.read) return { image: null, isDefault: false };\n const def = rule && defaultImage(rule, id);\n return def ? { image: def, isDefault: true } : { image: null, isDefault: false };\n }, [listed, o.image, r.read, preset, rule, id]);\n return { ...image, rule, loading: !loaded || r.loading, error: r.error, reload: r.reload };\n}\n"],"mappings":";;;;;AAiBA,IAAM,oBAAU,IAAI,QAA8E,GAC5F,oBAAW,IAAI,QAAyC;AAG9D,SAAgB,EAAW,IAA0C,CAAC,GAAe;CACnF,IAAM,IAAS,EAA4B,EAAE,MAAM,GAC7C,GAAG,KAAY,EAAS,CAAC,GACzB,IAAO,IAAS,EAAQ,IAAI,CAAM,IAAI,KAAA;CAiB5C,OAhBA,QAAgB;EACd,IAAI,CAAC,KAAU,EAAQ,IAAI,CAAM,GAAG;EACpC,IAAI,IAAO,IACP,IAAO,EAAS,IAAI,CAAM;EAS9B,OARK,MACH,IAAO,EAAO,MAAM,QAAQ,CAAC,CAAC,MAC3B,MAAY,KAAK,EAAQ,IAAI,GAAQ,EAAE,WAAQ,CAAC,IAChD,MAA2B,KAAK,EAAQ,IAAI,GAAQ;GAAE,SAAS,CAAC;GAAG;EAAM,CAAC,CAC7E,CAAC,CAAC,cAAc,EAAS,OAAO,CAAM,CAAC,GACvC,EAAS,IAAI,GAAQ,CAAI,IAE3B,EAAU,WAAW,KAAQ,GAAU,MAAM,IAAI,CAAC,CAAC,SACtC;GACX,IAAO;EACT;CACF,GAAG,CAAC,CAAM,CAAC,GACJ;EAAE,SAAS,GAAM,WAAW,CAAC;EAAG,QAAQ,CAAC,CAAC,KAAQ,CAAC;EAAQ,OAAO,GAAM;CAAM;AACvF;AA4BA,SAAgB,EAAe,GAAc,GAAY,GAAgB,IAAwB,CAAC,GAAmB;CACnH,IAAM,IAAS,EAA4B,EAAE,MAAM,GAC7C,EAAE,YAAS,cAAW,EAAW,EAAE,UAAO,CAAC,GAC3C,IAAO,EAAQ,MAAM,MAAM,EAAE,SAAS,KAAQ,EAAE,SAAS,CAAM,GAC/D,IAAS,EAAE,UAAU,KAAA,GAErB,IAAS,IAAQ,EAAU,EAAK,IAAI,IAAI,EAAQ,EAAK,IAAI,IAAI,EAAK,OAAQ,KAAA,GAC1E,IAAI,EAAa;EAAE;EAAM;CAAG,GAAG;EAAE;EAAQ,MAAM,KAAU,CAAC,IAAS,OAAO,EAAE;EAAM;CAAO,CAAC;CAQhG,OAAO;EAAE,GAPK,QAAkE;GAC9E,IAAM,IAAM,IAAS,EAAE,QAAQ,EAAE,MAAM,QAAQ,MAAM,MAAM,EAAE,WAAW,CAAM;GAC9E,IAAI,KAAO,EAAI,WAAW,QAAQ,OAAO;IAAE,OAAO;KAAE;KAAQ,YAAY,EAAI;KAAY,QAAS,YAAY,KAAO,EAAI,UAAW,GAAM;IAAO;IAAG,WAAW;GAAM;GACpK,IAAI,CAAC,KAAU,CAAC,EAAE,MAAM,OAAO;IAAE,OAAO;IAAM,WAAW;GAAM;GAC/D,IAAM,IAAM,KAAQ,EAAa,GAAM,CAAE;GACzC,OAAO,IAAM;IAAE,OAAO;IAAK,WAAW;GAAK,IAAI;IAAE,OAAO;IAAM,WAAW;GAAM;EACjF,GAAG;GAAC;GAAQ,EAAE;GAAO,EAAE;GAAM;GAAQ;GAAM;EAAE,CACjC;EAAO;EAAM,SAAS,CAAC,KAAU,EAAE;EAAS,OAAO,EAAE;EAAO,QAAQ,EAAE;CAAO;AAC3F"}
|
package/package.json
CHANGED