@openrails/contentkit-ui 0.87.0-rc.1 → 0.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +18 -11
  2. package/dist/client/beacons.js +5 -1
  3. package/dist/client/beacons.js.map +1 -1
  4. package/dist/client/contents.d.ts +8 -6
  5. package/dist/client/contents.js +10 -7
  6. package/dist/client/contents.js.map +1 -1
  7. package/dist/client/errors.d.ts +15 -3
  8. package/dist/client/errors.js +40 -14
  9. package/dist/client/errors.js.map +1 -1
  10. package/dist/client/generated/routes.d.ts +1 -1
  11. package/dist/client/generated/routes.js +1 -1
  12. package/dist/client/generated/routes.js.map +1 -1
  13. package/dist/client/generated/wire.d.ts +11 -3
  14. package/dist/client/generated/wire.js.map +1 -1
  15. package/dist/client/http.js +27 -27
  16. package/dist/client/http.js.map +1 -1
  17. package/dist/client/index.d.ts +2 -2
  18. package/dist/client/media/client.d.ts +10 -4
  19. package/dist/client/media/client.js +33 -7
  20. package/dist/client/media/client.js.map +1 -1
  21. package/dist/client/media/rules.d.ts +1 -1
  22. package/dist/client/media/rules.js +6 -6
  23. package/dist/client/media/rules.js.map +1 -1
  24. package/dist/client/media/transport.d.ts +1 -1
  25. package/dist/client/media/transport.js +16 -16
  26. package/dist/client/media/transport.js.map +1 -1
  27. package/dist/client/public.d.ts +2 -2
  28. package/dist/client/public.js +2 -2
  29. package/dist/client/public.js.map +1 -1
  30. package/dist/client.js +25 -25
  31. package/dist/components/admin/content-manager.js +233 -232
  32. package/dist/components/admin/content-manager.js.map +1 -1
  33. package/dist/components/content/editors.d.ts +8 -0
  34. package/dist/components/content/editors.js +299 -229
  35. package/dist/components/content/editors.js.map +1 -1
  36. package/dist/components/content/index.d.ts +1 -1
  37. package/dist/components/content/paywall.js +61 -61
  38. package/dist/components/content/paywall.js.map +1 -1
  39. package/dist/i18n/messages.js +49 -46
  40. package/dist/i18n/messages.js.map +1 -1
  41. package/dist/index.js +11 -11
  42. package/dist/locales/de.js +2 -1
  43. package/dist/locales/de.js.map +1 -1
  44. package/dist/locales/en.d.ts +1 -0
  45. package/dist/locales/en.js +2 -1
  46. package/dist/locales/en.js.map +1 -1
  47. package/dist/locales/es.js +2 -1
  48. package/dist/locales/es.js.map +1 -1
  49. package/dist/locales/ja.js +2 -1
  50. package/dist/locales/ja.js.map +1 -1
  51. package/dist/locales/ko.js +2 -1
  52. package/dist/locales/ko.js.map +1 -1
  53. package/dist/locales/zh.js +2 -1
  54. package/dist/locales/zh.js.map +1 -1
  55. package/dist/react/contents.d.ts +13 -5
  56. package/dist/react/contents.js +163 -149
  57. package/dist/react/contents.js.map +1 -1
  58. package/dist/react/detail.d.ts +7 -1
  59. package/dist/react/detail.js +9 -6
  60. package/dist/react/detail.js.map +1 -1
  61. package/dist/react/index.d.ts +1 -1
  62. package/dist/react/public.js +17 -16
  63. package/dist/react/public.js.map +1 -1
  64. package/dist/react.js +8 -8
  65. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  import { useGuarded as e } from "./context.js";
2
2
  import { keyOf as t, offsetPages as n, useContentScope as r, useList as i, useResource as a } from "./use-resource.js";
3
3
  import { useMediaRead as o } from "./read.js";
4
- import { BODY_SUFFIX as s, IMAGE_SCHEME as c, imageRef as l, imageRefs as u } from "../client/contents.js";
5
- import { useCallback as d, useEffect as f, useMemo as p, useRef as m, useState as h } from "react";
4
+ import { IMAGE_SCHEME as s, imageRef as c, imageRefs as l } from "../client/contents.js";
5
+ import { useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
6
6
  //#region src/react/contents.ts
7
- function g(e = {}, a = {}) {
7
+ function h(e = {}, a = {}) {
8
8
  let { client: o, store: s, scope: c } = r(a.client), { pageSize: l, ...u } = e, d = l ?? 20;
9
9
  return i(s, t("contents", c, u.kind, u.state, u.owner_group, u.q, u.language, d), {
10
10
  type: "contents",
@@ -14,201 +14,215 @@ function g(e = {}, a = {}) {
14
14
  ...e
15
15
  }, t)));
16
16
  }
17
- var ee = (e) => [...new Set([...(e ?? "").matchAll(u())].map((e) => e[1]))], _ = (e) => {
17
+ var ee = (e) => [...new Set([...(e ?? "").matchAll(l())].map((e) => e[1]))], g = (e) => {
18
18
  let t = e.slice(e.lastIndexOf("/") + 1), n = t.lastIndexOf(".");
19
19
  return n > 0 ? t.slice(0, n) : t;
20
20
  };
21
- function v(n, i, g, v = {}) {
22
- let { client: y, store: te, scope: ne } = r(v.client), [b, re] = h(null), x = n ?? b, S = x ? t("content", ne, x) : null, C = a(te, S, {
21
+ function _(n, i, h, _ = {}) {
22
+ let { client: v, store: y, scope: te } = r(_.client), [b, x] = m(null), S = n ?? b, C = S ? t("content", te, S) : null, w = a(y, C, {
23
23
  type: "content",
24
- id: x ?? void 0
25
- }, (e) => y.contents.get(x, g, e), { initial: v.initial }), w = C.data ?? null, [ie, T] = h(0), E = e(), D = d(async (e) => {
26
- T((e) => e + 1);
24
+ id: S ?? void 0
25
+ }, (e) => v.contents.get(S, h, e), { initial: _.initial }), T = w.data ?? null, [ne, E] = m(0), D = e(), O = u(async (e) => {
26
+ E((e) => e + 1);
27
27
  try {
28
- return await E(e);
28
+ return await D(e);
29
29
  } finally {
30
- T((e) => e - 1);
30
+ E((e) => e - 1);
31
31
  }
32
- }, [E]), O = d(() => {
33
- if (!x) throw Error("contentkit-ui: create the item first");
34
- return x;
35
- }, [x]), k = w?.text?.[g], A = k?.body, j = w?.cover, [M, ae] = h(/* @__PURE__ */ new Map()), N = m([]);
36
- f(() => {
37
- let e = N.current;
32
+ }, [D]), k = u(() => {
33
+ if (!S) throw Error("contentkit-ui: create the item first");
34
+ return S;
35
+ }, [S]), A = T?.text?.[h], j = A?.body, M = f(() => {
36
+ let e = [...T?.versions ?? []].sort((e, t) => e.position - t.position);
37
+ return e.find((e) => e.id === _.version) ?? e[0] ?? null;
38
+ }, [T?.versions, _.version]), N = M?.id, P = M?.cover, F = u(() => {
39
+ if (!N) throw Error("contentkit-ui: the item has no version yet");
40
+ return N;
41
+ }, [N]), [I, L] = m(/* @__PURE__ */ new Map()), R = p([]);
42
+ d(() => {
43
+ let e = R.current;
38
44
  return () => {
39
45
  for (let t of e) URL.revokeObjectURL(t);
40
46
  };
41
47
  }, []);
42
- let P = p(() => new Map(Object.entries(w?.images ?? {})), [w?.images]), F = !!j && !j.url && !M.has(j.image) && !P.has(j.image), I = p(() => ee(A).filter((e) => !M.has(e) && !P.has(e)), [
43
- A,
44
- M,
45
- P
46
- ]), L = p(() => F ? [...I, j.image] : I, [
47
- F,
48
+ let z = f(() => {
49
+ let e = new Map(Object.entries(T?.images ?? {}));
50
+ return P?.image && P.url && e.set(P.image, P.url), e;
51
+ }, [
52
+ T?.images,
53
+ P?.image,
54
+ P?.url
55
+ ]), B = !!P?.image && !P.url && !I.has(P.image) && !z.has(P.image), V = f(() => ee(j).filter((e) => !I.has(e) && !z.has(e)), [
56
+ j,
48
57
  I,
49
- j
50
- ]), R = p(() => ({
58
+ z
59
+ ]), H = f(() => B ? [...V, P.image] : V, [
60
+ B,
61
+ V,
62
+ P
63
+ ]), U = f(() => ({
51
64
  kind: i,
52
- id: x ?? "none"
53
- }), [i, x]), oe = p(() => ({
54
- kind: i + s,
55
- id: x ?? "none"
56
- }), [i, x]), z = o(R, {
57
- editor: !0,
58
- read: F ? void 0 : null,
59
- client: y
60
- }), B = o(oe, {
65
+ id: S ?? "none",
66
+ version: N
67
+ }), [
68
+ i,
69
+ S,
70
+ N
71
+ ]), W = o(U, {
61
72
  editor: !0,
62
- read: I.length ? void 0 : null,
63
- client: y
64
- }), V = p(() => [...z.read?.files ?? [], ...B.read?.files ?? []], [z.read, B.read]), H = p(() => {
73
+ read: H.length && N ? void 0 : null,
74
+ client: v
75
+ }), G = f(() => W.read?.files ?? [], [W.read]), K = f(() => {
65
76
  let e = /* @__PURE__ */ new Map();
66
- for (let t of V) t.upload && t.editor_url && e.set(_(t.path), t.editor_url);
77
+ for (let t of G) t.upload && t.editor_url && e.set(g(t.path), t.editor_url);
67
78
  return e;
68
- }, [V]), U = (e) => V.some((t) => t.failed && _(t.path) === e), W = (!!z.read || !!B.read) && L.some((e) => !H.has(e) && !U(e)), G = m(0), K = z.reload, q = B.reload, J = d(() => {
69
- K(), q();
70
- }, [K, q]);
71
- f(() => {
72
- if (!W) {
73
- G.current = 0;
79
+ }, [G]), re = (e) => G.some((t) => t.failed && g(t.path) === e), q = !!W.read && H.some((e) => !K.has(e) && !re(e)), J = p(0), Y = W.reload;
80
+ d(() => {
81
+ if (!q) {
82
+ J.current = 0;
74
83
  return;
75
84
  }
76
- if (G.current >= 6) return;
85
+ if (J.current >= 6) return;
77
86
  let e = setTimeout(() => {
78
- G.current++, J();
79
- }, 1e3 * 2 ** G.current);
87
+ J.current++, Y();
88
+ }, 1e3 * 2 ** J.current);
80
89
  return () => clearTimeout(e);
81
90
  }, [
82
- W,
83
- V,
84
- J
91
+ q,
92
+ G,
93
+ Y
85
94
  ]);
86
- let Y = m(/* @__PURE__ */ new Map());
87
- f(() => {
95
+ let X = p(/* @__PURE__ */ new Map());
96
+ d(() => {
88
97
  for (let e of [
89
- M,
90
- P,
91
- H
92
- ]) for (let [t, n] of e) Y.current.set(n, t);
98
+ I,
99
+ z,
100
+ K
101
+ ]) for (let [t, n] of e) X.current.set(n, t);
93
102
  }, [
94
- M,
95
- P,
96
- H
103
+ I,
104
+ z,
105
+ K
97
106
  ]);
98
- let X = d((e) => {
107
+ let Z = u((e) => {
99
108
  if (!e) return;
100
- let t = e.startsWith("contentkit:") ? e.slice(c.length) : e;
101
- return M.get(t) ?? P.get(t) ?? H.get(t) ?? e;
109
+ let t = e.startsWith("contentkit:") ? e.slice(s.length) : e;
110
+ return I.get(t) ?? z.get(t) ?? K.get(t) ?? e;
102
111
  }, [
103
- M,
104
- P,
105
- H
106
- ]), se = p(() => k ? {
107
- ...k,
108
- body: (k.body ?? "").replace(u(), (e, t) => M.get(t) ?? P.get(t) ?? H.get(t) ?? e)
112
+ I,
113
+ z,
114
+ K
115
+ ]), ie = f(() => A ? {
116
+ ...A,
117
+ body: (A.body ?? "").replace(l(), (e, t) => I.get(t) ?? z.get(t) ?? K.get(t) ?? e)
109
118
  } : void 0, [
110
- k,
111
- M,
112
- P,
113
- H
114
- ]), Z = d((e) => {
115
- let t = new Map(Y.current);
119
+ A,
120
+ I,
121
+ z,
122
+ K
123
+ ]), Q = u((e) => {
124
+ let t = new Map(X.current);
116
125
  for (let e of [
117
- M,
118
- P,
119
- H
126
+ I,
127
+ z,
128
+ K
120
129
  ]) for (let [n, r] of e) t.set(r, n);
121
130
  let n = e;
122
- for (let [e, r] of t) n = n.split(e).join(l(r)).split(e.replaceAll("&", "&")).join(l(r));
131
+ for (let [e, r] of t) n = n.split(e).join(c(r)).split(e.replaceAll("&", "&")).join(c(r));
123
132
  return n;
124
133
  }, [
125
- M,
126
- P,
127
- H
128
- ]), Q = d((e, t) => {
134
+ I,
135
+ z,
136
+ K
137
+ ]), $ = u((e, t) => {
129
138
  if (typeof URL.createObjectURL != "function") return;
130
139
  let n = URL.createObjectURL(t);
131
- return N.current.push(n), ae((t) => new Map(t).set(e, n)), n;
132
- }, []), ce = d((e) => D(async () => {
133
- let t = await y.contents.create(e);
134
- return re(t.id), t;
135
- }), [y, D]), le = d((e) => D(() => y.contents.update(O(), e)), [
136
- y,
137
- D,
138
- O
139
- ]), ue = d((e) => D(() => y.contents.putText(O(), g, e.body === void 0 ? e : {
140
+ return R.current.push(n), L((t) => new Map(t).set(e, n)), n;
141
+ }, []), ae = u((e) => O(async () => {
142
+ let t = await v.contents.create(e);
143
+ return x(t.id), t;
144
+ }), [v, O]), oe = u((e) => O(() => v.contents.update(k(), e)), [
145
+ v,
146
+ O,
147
+ k
148
+ ]), se = u((e) => O(() => v.contents.putText(k(), h, e.body === void 0 ? e : {
140
149
  ...e,
141
- body: Z(e.body)
150
+ body: Q(e.body)
142
151
  })), [
143
- y,
144
- D,
152
+ v,
153
+ O,
154
+ k,
155
+ h,
156
+ Q
157
+ ]), ce = u((e) => O(() => v.contents.publish(k(), e)), [
158
+ v,
159
+ O,
160
+ k
161
+ ]), le = u(() => O(() => v.contents.unpublish(k())), [
162
+ v,
163
+ O,
164
+ k
165
+ ]), ue = u(() => O(() => v.contents.remove(k())), [
166
+ v,
167
+ O,
168
+ k
169
+ ]), de = u(() => O(() => v.contents.restore(k())), [
170
+ v,
145
171
  O,
146
- g,
147
- Z
148
- ]), $ = d((e) => D(() => y.contents.publish(O(), e)), [
149
- y,
150
- D,
151
- O
152
- ]), de = d(() => D(() => y.contents.unpublish(O())), [
153
- y,
154
- D,
155
- O
156
- ]), fe = d(() => D(() => y.contents.remove(O())), [
157
- y,
158
- D,
159
- O
160
- ]), pe = d(() => D(() => y.contents.restore(O())), [
161
- y,
162
- D,
163
- O
164
- ]), me = d((e) => D(async () => {
165
- if (!e) return await y.contents.setCover(O(), null), null;
166
- let t = await y.media.uploadNamed({
172
+ k
173
+ ]), fe = u((e) => O(async () => {
174
+ if (!e) return await v.contents.setCover(k(), F(), null), null;
175
+ let t = {
167
176
  kind: i,
168
- id: O()
169
- }, e), n = await y.contents.setCover(O(), t.name), r = Q(t.name, e) ?? n.cover?.url ?? null;
170
- return r && Y.current.set(r, t.name), r;
177
+ id: k(),
178
+ version: F()
179
+ }, n = await v.media.uploadNamed(t, e, { path: await v.media.presetPath(i, "cover", e) }), r = await v.contents.setCover(t.id, t.version, n.name), a = $(n.name, e) ?? r.cover?.url ?? null;
180
+ return a && X.current.set(a, n.name), a;
171
181
  }), [
172
- y,
173
- D,
182
+ v,
174
183
  O,
184
+ k,
185
+ F,
175
186
  i,
176
- Q
177
- ]), he = d((e) => D(async () => {
178
- let t = await y.media.uploadInline({
179
- kind: i + s,
180
- id: O()
181
- }, e), n = Q(t.name, e) ?? t.url ?? t.ref;
182
- return Y.current.set(n, t.name), n;
187
+ $
188
+ ]), pe = u((e) => O(async () => {
189
+ let t = await v.media.uploadInline({
190
+ kind: i,
191
+ id: k(),
192
+ version: F()
193
+ }, e), n = $(t.name, e) ?? t.url ?? t.ref;
194
+ return X.current.set(n, t.name), n;
183
195
  }), [
184
- y,
185
- D,
196
+ v,
186
197
  O,
198
+ k,
199
+ F,
187
200
  i,
188
- Q
201
+ $
189
202
  ]);
190
203
  return {
191
- content: w,
192
- reload: C.reload,
193
- loading: !!S && (C.loading || !C.loaded && v.initial === void 0),
194
- error: C.error,
195
- saving: ie > 0,
196
- create: ce,
197
- update: le,
198
- putText: ue,
199
- publish: $,
200
- unpublish: de,
201
- remove: fe,
202
- restore: pe,
203
- setCover: me,
204
- uploadImage: he,
205
- imageSrc: X,
206
- coverSrc: X(j?.image),
207
- text: se,
208
- storedBody: Z
204
+ content: T,
205
+ reload: w.reload,
206
+ loading: !!C && (w.loading || !w.loaded && _.initial === void 0),
207
+ error: w.error,
208
+ saving: ne > 0,
209
+ create: ae,
210
+ update: oe,
211
+ putText: se,
212
+ publish: ce,
213
+ unpublish: le,
214
+ remove: ue,
215
+ restore: de,
216
+ version: M,
217
+ setCover: fe,
218
+ uploadImage: pe,
219
+ imageSrc: Z,
220
+ coverSrc: P?.image ? Z(P.image) : P?.url ?? void 0,
221
+ text: ie,
222
+ storedBody: Q
209
223
  };
210
224
  }
211
225
  //#endregion
212
- export { v as useContent, g as useManagedContents };
226
+ export { _ as useContent, h as useManagedContents };
213
227
 
214
228
  //# sourceMappingURL=contents.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contents.js","names":[],"sources":["../../src/react/contents.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { CatalogText, Content, ContentInput, ContentPatch } from \"../client/generated/wire.js\";\nimport { BODY_SUFFIX, IMAGE_SCHEME, imageRef, imageRefs, type ManageQuery } from \"../client/contents.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { useGuarded } from \"./context.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface ManageFilter extends Omit<ManageQuery, \"limit\" | \"offset\"> {\n pageSize?: number;\n}\n\n/** The content items the caller manages, newest first, a page at a time. */\nexport function useManagedContents(f: ManageFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Content> {\n const { client, store, scope } = useContentScope(o.client);\n const { pageSize, ...q } = f;\n const size = pageSize ?? 20;\n return useList(\n store,\n keyOf(\"contents\", scope, q.kind, q.state, q.owner_group, q.q, q.language, size),\n { type: \"contents\", scope: q.state === \"deleted\" ? \"deleted\" : undefined },\n offsetPages(size, (page, signal) => client.contents.manage({ ...q, ...page }, signal)),\n );\n}\n\nexport interface UseContent {\n /** null before create (or while loading). */\n content: Content | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Reads the item again. */\n reload: () => void;\n /** Creates the item as a draft; the hook then edits it. */\n create: (input: ContentInput) => Promise<Content>;\n update: (patch: ContentPatch) => Promise<Content>;\n /** Sets the text in the hook's language; a body from the editor is stored with image references (storedBody). */\n putText: (text: CatalogText) => Promise<Content>;\n /** Publishes now, or schedules for at. */\n publish: (at?: string) => Promise<Content>;\n unpublish: () => Promise<Content>;\n remove: () => Promise<void>;\n restore: () => Promise<Content>;\n /** Uploads the cover (null clears it); resolves with a URL that shows it now. */\n setCover: (file: Blob | null) => Promise<string | null>;\n /** Uploads an image for the body; resolves with a URL that shows it now, to place in the editor. */\n uploadImage: (file: Blob) => Promise<string>;\n /**\n * A URL that shows one of the item's images now, by name (\"i-{uuid}\") or\n * reference: a fresh upload from its file, else its public file\n * (`content.images`), else, while it has none (an unpublished item's), its\n * signed editor view from an editor read. Anything else is returned as is.\n */\n imageSrc: (image: string | null | undefined) => string | undefined;\n /** The cover through imageSrc. */\n coverSrc: string | undefined;\n /** The language's stored text, its body's images through imageSrc: what an editor shows. */\n text: CatalogText | undefined;\n /** HTML from the editor with every image URL this hook showed turned back into the image's reference. */\n storedBody: (html: string) => string;\n}\n\n/** The names of the images a body references, once each. */\nconst refNames = (body: string | undefined) => [...new Set([...(body ?? \"\").matchAll(imageRefs())].map((m) => m[1]!))];\nconst uploadName = (path: string) => {\n const base = path.slice(path.lastIndexOf(\"/\") + 1);\n const dot = base.lastIndexOf(\".\");\n return dot > 0 ? base.slice(0, dot) : base;\n};\n\n/**\n * One content item of kind for its editors: read, create, text in language,\n * publish, delete, restore, cover and body images. null creates a new item.\n */\nexport function useContent(id: string | null | undefined, kind: string, language: string, o: { initial?: Content; client?: ContentKitClient } = {}): UseContent {\n const { client, store, scope } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const cid = id ?? created;\n const key = cid ? keyOf(\"content\", scope, cid) : null;\n const r = useResource<Content | null>(store, key, { type: \"content\", id: cid ?? undefined }, (s) => client.contents.get(cid!, language, s), { initial: o.initial });\n const content = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const guarded = useGuarded();\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await guarded(fn);\n } finally {\n setInflight((n) => n - 1);\n }\n }, [guarded]);\n const need = useCallback(() => {\n if (!cid) throw new Error(\"contentkit-ui: create the item first\");\n return cid;\n }, [cid]);\n const stored = content?.text?.[language];\n const body = stored?.body;\n const cover = content?.cover;\n\n // Images: fresh uploads show from their file, published ones from\n // content.images, the rest (an unpublished item's) from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => {\n for (const u of urls) URL.revokeObjectURL(u);\n };\n }, []);\n const published = useMemo(() => new Map(Object.entries(content?.images ?? {})), [content?.images]);\n // The cover is the item's folder's; body images are its body folder's ({kind}.body).\n const coverPending = !!cover && !cover.url && !previews.has(cover.image) && !published.has(cover.image);\n const bodyPending = useMemo(() => refNames(body).filter((n) => !previews.has(n) && !published.has(n)), [body, previews, published]);\n const pending = useMemo(() => (coverPending ? [...bodyPending, cover!.image] : bodyPending), [coverPending, bodyPending, cover]);\n const folder = useMemo(() => ({ kind, id: cid ?? \"none\" }), [kind, cid]);\n const bodyFolder = useMemo(() => ({ kind: kind + BODY_SUFFIX, id: cid ?? \"none\" }), [kind, cid]);\n const coverRead = useMediaRead(folder, { editor: true, read: coverPending ? undefined : null, client });\n const bodyRead = useMediaRead(bodyFolder, { editor: true, read: bodyPending.length ? undefined : null, client });\n const files = useMemo(() => [...(coverRead.read?.files ?? []), ...(bodyRead.read?.files ?? [])], [coverRead.read, bodyRead.read]);\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of files) if (f.upload && f.editor_url) out.set(uploadName(f.path), f.editor_url);\n return out;\n }, [files]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const failed = (n: string) => files.some((f) => f.failed && uploadName(f.path) === n);\n const missing = (!!coverRead.read || !!bodyRead.read) && pending.some((n) => !views.has(n) && !failed(n));\n const tries = useRef(0);\n const reloadCover = coverRead.reload;\n const reloadBody = bodyRead.reload;\n const reload = useCallback(() => {\n reloadCover();\n reloadBody();\n }, [reloadCover, reloadBody]);\n useEffect(() => {\n if (!missing) {\n tries.current = 0;\n return;\n }\n if (tries.current >= 6) return;\n const t = setTimeout(() => {\n tries.current++;\n reload();\n }, 1000 * 2 ** tries.current);\n return () => clearTimeout(t);\n }, [missing, files, reload]);\n\n // Every URL shown for an image, of this and earlier reads, back to its name.\n const shown = useRef(new Map<string, string>());\n useEffect(() => {\n for (const m of [previews, published, views]) for (const [n, u] of m) shown.current.set(u, n);\n }, [previews, published, views]);\n\n const imageSrc = useCallback(\n (image: string | null | undefined) => {\n if (!image) return undefined;\n const n = image.startsWith(IMAGE_SCHEME) ? image.slice(IMAGE_SCHEME.length) : image;\n return previews.get(n) ?? published.get(n) ?? views.get(n) ?? image;\n },\n [previews, published, views],\n );\n const text = useMemo(\n () => (stored ? { ...stored, body: (stored.body ?? \"\").replace(imageRefs(), (ref, n: string) => previews.get(n) ?? published.get(n) ?? views.get(n) ?? ref) } : undefined),\n [stored, previews, published, views],\n );\n const storedBody = useCallback(\n (html: string) => {\n const back = new Map(shown.current);\n for (const m of [previews, published, views]) for (const [n, u] of m) back.set(u, n);\n let out = html;\n for (const [u, n] of back) out = out.split(u).join(imageRef(n)).split(u.replaceAll(\"&\", \"&amp;\")).join(imageRef(n));\n return out;\n },\n [previews, published, views],\n );\n const preview = useCallback((name: string, file: Blob): string | undefined => {\n if (typeof URL.createObjectURL !== \"function\") return undefined;\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((m) => new Map(m).set(name, url));\n return url;\n }, []);\n\n const create = useCallback(\n (input: ContentInput) =>\n run(async () => {\n const c = await client.contents.create(input);\n setCreated(c.id);\n return c;\n }),\n [client, run],\n );\n const update = useCallback((patch: ContentPatch) => run(() => client.contents.update(need(), patch)), [client, run, need]);\n const putText = useCallback(\n (t: CatalogText) => run(() => client.contents.putText(need(), language, t.body === undefined ? t : { ...t, body: storedBody(t.body) })),\n [client, run, need, language, storedBody],\n );\n const publish = useCallback((at?: string) => run(() => client.contents.publish(need(), at)), [client, run, need]);\n const unpublish = useCallback(() => run(() => client.contents.unpublish(need())), [client, run, need]);\n const remove = useCallback(() => run(() => client.contents.remove(need())), [client, run, need]);\n const restore = useCallback(() => run(() => client.contents.restore(need())), [client, run, need]);\n const setCover = useCallback(\n (file: Blob | null) =>\n run(async () => {\n if (!file) {\n await client.contents.setCover(need(), null);\n return null;\n }\n const up = await client.media.uploadNamed({ kind, id: need() }, file);\n const saved = await client.contents.setCover(need(), up.name);\n const url = preview(up.name, file) ?? saved.cover?.url ?? null;\n if (url) shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, kind, preview],\n );\n const uploadImage = useCallback(\n (file: Blob) =>\n run(async () => {\n const up = await client.media.uploadInline({ kind: kind + BODY_SUFFIX, id: need() }, file);\n const url = preview(up.name, file) ?? up.url ?? up.ref;\n shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, kind, preview],\n );\n return {\n content,\n reload: r.reload,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n putText,\n publish,\n unpublish,\n remove,\n restore,\n setCover,\n uploadImage,\n imageSrc,\n coverSrc: imageSrc(cover?.image),\n text,\n storedBody,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"contents.js","names":[],"sources":["../../src/react/contents.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { CatalogText, Content, ContentInput, ContentPatch, Version } from \"../client/generated/wire.js\";\nimport { IMAGE_SCHEME, imageRef, imageRefs, type ManageQuery } from \"../client/contents.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { useGuarded } from \"./context.js\";\nimport { keyOf, offsetPages, useContentScope, useList, useResource, type UseList } from \"./use-resource.js\";\n\nexport interface ManageFilter extends Omit<ManageQuery, \"limit\" | \"offset\"> {\n pageSize?: number;\n}\n\n/** The content items the caller manages, newest first, a page at a time. */\nexport function useManagedContents(f: ManageFilter = {}, o: { client?: ContentKitClient } = {}): UseList<Content> {\n const { client, store, scope } = useContentScope(o.client);\n const { pageSize, ...q } = f;\n const size = pageSize ?? 20;\n return useList(\n store,\n keyOf(\"contents\", scope, q.kind, q.state, q.owner_group, q.q, q.language, size),\n { type: \"contents\", scope: q.state === \"deleted\" ? \"deleted\" : undefined },\n offsetPages(size, (page, signal) => client.contents.manage({ ...q, ...page }, signal)),\n );\n}\n\nexport interface UseContent {\n /** null before create (or while loading). */\n content: Content | null;\n loading: boolean;\n error?: ContentKitError;\n /** Writes in flight. */\n saving: boolean;\n /** Reads the item again. */\n reload: () => void;\n /** Creates the item as a draft; the hook then edits it. */\n create: (input: ContentInput) => Promise<Content>;\n update: (patch: ContentPatch) => Promise<Content>;\n /** Sets the text in the hook's language; a body from the editor is stored with image references (storedBody). */\n putText: (text: CatalogText) => Promise<Content>;\n /** Publishes now, or schedules for at. */\n publish: (at?: string) => Promise<Content>;\n unpublish: () => Promise<Content>;\n remove: () => Promise<void>;\n restore: () => Promise<Content>;\n /**\n * The version whose cover and body images the hook edits: the one asked\n * for, else the first. Every version has its own cover and its own copy of\n * the body's images.\n */\n version: Version | null;\n /** Uploads the version's cover (null returns to its first upload); resolves with a URL that shows it now. */\n setCover: (file: Blob | null) => Promise<string | null>;\n /** Uploads an image for the body into the version, copied into the others; resolves with a URL that shows it now, to place in the editor. */\n uploadImage: (file: Blob) => Promise<string>;\n /**\n * A URL that shows one of the item's images now, by name (\"i-{uuid}\") or\n * reference: a fresh upload from its file, else its public file\n * (`content.images`), else, while it has none (an unpublished item's), its\n * signed editor view from an editor read. Anything else is returned as is.\n */\n imageSrc: (image: string | null | undefined) => string | undefined;\n /** The version's cover through imageSrc. */\n coverSrc: string | undefined;\n /** The language's stored text, its body's images through imageSrc: what an editor shows. */\n text: CatalogText | undefined;\n /** HTML from the editor with every image URL this hook showed turned back into the image's reference. */\n storedBody: (html: string) => string;\n}\n\n/** The names of the images a body references, once each. */\nconst refNames = (body: string | undefined) => [...new Set([...(body ?? \"\").matchAll(imageRefs())].map((m) => m[1]!))];\nconst uploadName = (path: string) => {\n const base = path.slice(path.lastIndexOf(\"/\") + 1);\n const dot = base.lastIndexOf(\".\");\n return dot > 0 ? base.slice(0, dot) : base;\n};\n\n/**\n * One content item of kind for its editors: read, create, text in language,\n * publish, delete, restore, a version's cover and the body's images. null\n * creates a new item.\n */\nexport function useContent(\n id: string | null | undefined,\n kind: string,\n language: string,\n o: { initial?: Content; client?: ContentKitClient; version?: string } = {},\n): UseContent {\n const { client, store, scope } = useContentScope(o.client);\n const [created, setCreated] = useState<string | null>(null);\n const cid = id ?? created;\n const key = cid ? keyOf(\"content\", scope, cid) : null;\n const r = useResource<Content | null>(store, key, { type: \"content\", id: cid ?? undefined }, (s) => client.contents.get(cid!, language, s), { initial: o.initial });\n const content = r.data ?? null;\n const [inflight, setInflight] = useState(0);\n const guarded = useGuarded();\n const run = useCallback(async <T>(fn: () => Promise<T>): Promise<T> => {\n setInflight((n) => n + 1);\n try {\n return await guarded(fn);\n } finally {\n setInflight((n) => n - 1);\n }\n }, [guarded]);\n const need = useCallback(() => {\n if (!cid) throw new Error(\"contentkit-ui: create the item first\");\n return cid;\n }, [cid]);\n const stored = content?.text?.[language];\n const body = stored?.body;\n const version = useMemo(() => {\n const vs = [...(content?.versions ?? [])].sort((a, b) => a.position - b.position);\n return vs.find((v) => v.id === o.version) ?? vs[0] ?? null;\n }, [content?.versions, o.version]);\n const vid = version?.id;\n const cover = version?.cover;\n const needVersion = useCallback(() => {\n if (!vid) throw new Error(\"contentkit-ui: the item has no version yet\");\n return vid;\n }, [vid]);\n\n // Images: fresh uploads show from their file, published ones from\n // content.images, the rest (an unpublished item's) from an editor read.\n const [previews, setPreviews] = useState<ReadonlyMap<string, string>>(new Map());\n const blobs = useRef<string[]>([]);\n useEffect(() => {\n const urls = blobs.current;\n return () => {\n for (const u of urls) URL.revokeObjectURL(u);\n };\n }, []);\n const published = useMemo(() => {\n const out = new Map(Object.entries(content?.images ?? {}));\n if (cover?.image && cover.url) out.set(cover.image, cover.url);\n return out;\n }, [content?.images, cover?.image, cover?.url]);\n // The cover and the body's images are the version's.\n const coverPending = !!cover?.image && !cover.url && !previews.has(cover.image) && !published.has(cover.image);\n const bodyPending = useMemo(() => refNames(body).filter((n) => !previews.has(n) && !published.has(n)), [body, previews, published]);\n const pending = useMemo(() => (coverPending ? [...bodyPending, cover!.image!] : bodyPending), [coverPending, bodyPending, cover]);\n const folder = useMemo(() => ({ kind, id: cid ?? \"none\", version: vid }), [kind, cid, vid]);\n const folderRead = useMediaRead(folder, { editor: true, read: pending.length && vid ? undefined : null, client });\n const files = useMemo(() => folderRead.read?.files ?? [], [folderRead.read]);\n const views = useMemo(() => {\n const out = new Map<string, string>();\n for (const f of files) if (f.upload && f.editor_url) out.set(uploadName(f.path), f.editor_url);\n return out;\n }, [files]);\n // Editor views render on the first editor read that misses them: read again, backing off.\n const failed = (n: string) => files.some((f) => f.failed && uploadName(f.path) === n);\n const missing = !!folderRead.read && pending.some((n) => !views.has(n) && !failed(n));\n const tries = useRef(0);\n const reload = folderRead.reload;\n useEffect(() => {\n if (!missing) {\n tries.current = 0;\n return;\n }\n if (tries.current >= 6) return;\n const t = setTimeout(() => {\n tries.current++;\n reload();\n }, 1000 * 2 ** tries.current);\n return () => clearTimeout(t);\n }, [missing, files, reload]);\n\n // Every URL shown for an image, of this and earlier reads, back to its name.\n const shown = useRef(new Map<string, string>());\n useEffect(() => {\n for (const m of [previews, published, views]) for (const [n, u] of m) shown.current.set(u, n);\n }, [previews, published, views]);\n\n const imageSrc = useCallback(\n (image: string | null | undefined) => {\n if (!image) return undefined;\n const n = image.startsWith(IMAGE_SCHEME) ? image.slice(IMAGE_SCHEME.length) : image;\n return previews.get(n) ?? published.get(n) ?? views.get(n) ?? image;\n },\n [previews, published, views],\n );\n const text = useMemo(\n () => (stored ? { ...stored, body: (stored.body ?? \"\").replace(imageRefs(), (ref, n: string) => previews.get(n) ?? published.get(n) ?? views.get(n) ?? ref) } : undefined),\n [stored, previews, published, views],\n );\n const storedBody = useCallback(\n (html: string) => {\n const back = new Map(shown.current);\n for (const m of [previews, published, views]) for (const [n, u] of m) back.set(u, n);\n let out = html;\n for (const [u, n] of back) out = out.split(u).join(imageRef(n)).split(u.replaceAll(\"&\", \"&amp;\")).join(imageRef(n));\n return out;\n },\n [previews, published, views],\n );\n const preview = useCallback((name: string, file: Blob): string | undefined => {\n if (typeof URL.createObjectURL !== \"function\") return undefined;\n const url = URL.createObjectURL(file);\n blobs.current.push(url);\n setPreviews((m) => new Map(m).set(name, url));\n return url;\n }, []);\n\n const create = useCallback(\n (input: ContentInput) =>\n run(async () => {\n const c = await client.contents.create(input);\n setCreated(c.id);\n return c;\n }),\n [client, run],\n );\n const update = useCallback((patch: ContentPatch) => run(() => client.contents.update(need(), patch)), [client, run, need]);\n const putText = useCallback(\n (t: CatalogText) => run(() => client.contents.putText(need(), language, t.body === undefined ? t : { ...t, body: storedBody(t.body) })),\n [client, run, need, language, storedBody],\n );\n const publish = useCallback((at?: string) => run(() => client.contents.publish(need(), at)), [client, run, need]);\n const unpublish = useCallback(() => run(() => client.contents.unpublish(need())), [client, run, need]);\n const remove = useCallback(() => run(() => client.contents.remove(need())), [client, run, need]);\n const restore = useCallback(() => run(() => client.contents.restore(need())), [client, run, need]);\n const setCover = useCallback(\n (file: Blob | null) =>\n run(async () => {\n if (!file) {\n await client.contents.setCover(need(), needVersion(), null);\n return null;\n }\n const ref = { kind, id: need(), version: needVersion() };\n const up = await client.media.uploadNamed(ref, file, { path: await client.media.presetPath(kind, \"cover\", file) });\n const saved = await client.contents.setCover(ref.id, ref.version, up.name);\n const url = preview(up.name, file) ?? saved.cover?.url ?? null;\n if (url) shown.current.set(url, up.name);\n return url;\n }),\n [client, run, need, needVersion, kind, preview],\n );\n const uploadImage = useCallback(\n (file: Blob) =>\n run(async () => {\n const up = await client.media.uploadInline({ kind, id: need(), version: needVersion() }, 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, needVersion, kind, preview],\n );\n return {\n content,\n reload: r.reload,\n loading: !!key && (r.loading || (!r.loaded && o.initial === undefined)),\n error: r.error,\n saving: inflight > 0,\n create,\n update,\n putText,\n publish,\n unpublish,\n remove,\n restore,\n version,\n setCover,\n uploadImage,\n imageSrc,\n coverSrc: cover?.image ? imageSrc(cover.image) : (cover?.url ?? undefined),\n text,\n storedBody,\n };\n}\n"],"mappings":"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"}
@@ -3,8 +3,14 @@ import { ContentKitError } from '../client/errors.js';
3
3
  import { Content, Decision, Version } from '../client/generated/wire.js';
4
4
  /** A content's live versions, first first. */
5
5
  export declare function liveVersions(c: Content | null | undefined): Version[];
6
- /** The version a reader of language opens by default: the first live one serving it, else the first live one. */
6
+ /**
7
+ * The version a reader of language opens by default: the first live one by
8
+ * position serving it, else the first live one. The server picks cards'
9
+ * versions, covers and meta by the same rule.
10
+ */
7
11
  export declare function defaultVersion(c: Content | null | undefined, language?: string): Version | null;
12
+ /** defaultVersion among versions: the first by position serving language, else the first. */
13
+ export declare function editionOf(versions: readonly Version[], language?: string): Version | null;
8
14
  /** The version selected names: its code (any spelling, the page's ?v=) or its id; undefined when none. */
9
15
  export declare function findVersion(versions: readonly Version[], selected: string | null | undefined): Version | undefined;
10
16
  export interface UseContentDetail {
@@ -7,21 +7,24 @@ function o(e) {
7
7
  return (e?.versions ?? []).filter((e) => e.live).sort((e, t) => e.position - t.position);
8
8
  }
9
9
  function s(e, t) {
10
- let n = o(e);
11
- return (t ? n.find((e) => e.languages.includes(t)) : void 0) ?? n[0] ?? null;
10
+ return c(o(e), t);
12
11
  }
13
12
  function c(e, t) {
13
+ let n = [...e].sort((e, t) => e.position - t.position);
14
+ return (t ? n.find((e) => e.languages.includes(t)) : void 0) ?? n[0] ?? null;
15
+ }
16
+ function l(e, t) {
14
17
  if (!t) return;
15
18
  let n = r(t);
16
19
  return e.find((e) => e.id === t) ?? (n ? e.find((e) => e.code === n) : void 0);
17
20
  }
18
- function l(r, l = {}) {
19
- let { client: u, store: d, scope: f, language: p } = t(l.client), m = l.language ?? p, [h] = i({ location: l.location }), g = l.version === void 0 ? h : l.version, _ = r ? e("content-detail", f, r, m) : null, v = n(d, _, {
21
+ function u(r, c = {}) {
22
+ let { client: u, store: d, scope: f, language: p } = t(c.client), m = c.language ?? p, [h] = i({ location: c.location }), g = c.version === void 0 ? h : c.version, _ = r ? e("content-detail", f, r, m) : null, v = n(d, _, {
20
23
  type: "content-detail",
21
24
  id: r ?? void 0
22
25
  }, (e) => u.contents.get(r, m, e)), y = v.data ?? null;
23
26
  return a(() => {
24
- let e = o(y), t = c(e, g) ?? s(y, m);
27
+ let e = o(y), t = l(e, g) ?? s(y, m);
25
28
  return {
26
29
  content: y,
27
30
  versions: e,
@@ -44,6 +47,6 @@ function l(r, l = {}) {
44
47
  ]);
45
48
  }
46
49
  //#endregion
47
- export { s as defaultVersion, c as findVersion, o as liveVersions, l as useContentDetail };
50
+ export { s as defaultVersion, c as editionOf, l as findVersion, o as liveVersions, u as useContentDetail };
48
51
 
49
52
  //# sourceMappingURL=detail.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"detail.js","names":[],"sources":["../../src/react/detail.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Content, Decision, Version } from \"../client/generated/wire.js\";\nimport { parseVersionCode } from \"../urls/index.js\";\nimport { useVersionParam } from \"./location.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\n/** A content's live versions, first first. */\nexport function liveVersions(c: Content | null | undefined): Version[] {\n return (c?.versions ?? []).filter((v) => v.live).sort((a, b) => a.position - b.position);\n}\n\n/** The version a reader of language opens by default: the first live one serving it, else the first live one. */\nexport function defaultVersion(c: Content | null | undefined, language?: string): Version | null {\n const live = liveVersions(c);\n return (language ? live.find((v) => v.languages.includes(language)) : undefined) ?? live[0] ?? null;\n}\n\n/** The version selected names: its code (any spelling, the page's ?v=) or its id; undefined when none. */\nexport function findVersion(versions: readonly Version[], selected: string | null | undefined): Version | undefined {\n if (!selected) return undefined;\n const code = parseVersionCode(selected);\n return versions.find((v) => v.id === selected) ?? (code ? versions.find((v) => v.code === code) : undefined);\n}\n\nexport interface UseContentDetail {\n content: Content | null;\n /** Live versions, first first. */\n versions: Version[];\n /** The version shown: the one asked for (?v=) when live, else the language's default. */\n version: Version | null;\n /** The shown version's access decision: allowed, what is sold, previews. */\n decision: Decision | null;\n /** Every language a live version serves, in version order. */\n languages: string[];\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\nexport interface ContentDetailOptions {\n language?: string;\n /** The version to show, by code or id; default the page's ?v=. Unknown or not live: the language's default. */\n version?: string | null;\n /** The page's location for its ?v=; default window.location. */\n location?: string;\n client?: ContentKitClient;\n}\n\n/**\n * A content page's data: text in the reader's language, live versions with\n * their segments and access decisions, groups, attributes and the version to show.\n */\nexport function useContentDetail(id: string | null | undefined, o: ContentDetailOptions = {}): UseContentDetail {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const [param] = useVersionParam({ location: o.location });\n const selected = o.version === undefined ? param : o.version;\n const key = id ? keyOf(\"content-detail\", scope, id, language) : null;\n const r = useResource<Content>(store, key, { type: \"content-detail\", id: id ?? undefined }, (s) => client.contents.get(id!, language, s));\n const content = r.data ?? null;\n return useMemo(() => {\n const versions = liveVersions(content);\n const version = findVersion(versions, selected) ?? defaultVersion(content, language);\n return {\n content,\n versions,\n version,\n decision: version?.decision ?? content?.decision ?? null,\n languages: [...new Set(versions.flatMap((v) => v.languages))],\n loading: !!key && (r.loading || !r.loaded),\n error: r.error,\n reload: r.reload,\n };\n }, [content, selected, language, key, r.loading, r.loaded, r.error, r.reload]);\n}\n"],"mappings":";;;;;AASA,SAAgB,EAAa,GAA0C;CACrE,QAAQ,GAAG,YAAY,CAAC,EAAA,CAAG,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACzF;AAGA,SAAgB,EAAe,GAA+B,GAAmC;CAC/F,IAAM,IAAO,EAAa,CAAC;CAC3B,QAAQ,IAAW,EAAK,MAAM,MAAM,EAAE,UAAU,SAAS,CAAQ,CAAC,IAAI,KAAA,MAAc,EAAK,MAAM;AACjG;AAGA,SAAgB,EAAY,GAA8B,GAA0D;CAClH,IAAI,CAAC,GAAU;CACf,IAAM,IAAO,EAAiB,CAAQ;CACtC,OAAO,EAAS,MAAM,MAAM,EAAE,OAAO,CAAQ,MAAM,IAAO,EAAS,MAAM,MAAM,EAAE,SAAS,CAAI,IAAI,KAAA;AACpG;AA8BA,SAAgB,EAAiB,GAA+B,IAA0B,CAAC,GAAqB;CAC9G,IAAM,EAAE,WAAQ,UAAO,UAAO,UAAU,MAAa,EAAgB,EAAE,MAAM,GACvE,IAAW,EAAE,YAAY,GACzB,CAAC,KAAS,EAAgB,EAAE,UAAU,EAAE,SAAS,CAAC,GAClD,IAAW,EAAE,YAAY,KAAA,IAAY,IAAQ,EAAE,SAC/C,IAAM,IAAK,EAAM,kBAAkB,GAAO,GAAI,CAAQ,IAAI,MAC1D,IAAI,EAAqB,GAAO,GAAK;EAAE,MAAM;EAAkB,IAAI,KAAM,KAAA;CAAU,IAAI,MAAM,EAAO,SAAS,IAAI,GAAK,GAAU,CAAC,CAAC,GAClI,IAAU,EAAE,QAAQ;CAC1B,OAAO,QAAc;EACnB,IAAM,IAAW,EAAa,CAAO,GAC/B,IAAU,EAAY,GAAU,CAAQ,KAAK,EAAe,GAAS,CAAQ;EACnF,OAAO;GACL;GACA;GACA;GACA,UAAU,GAAS,YAAY,GAAS,YAAY;GACpD,WAAW,CAAC,GAAG,IAAI,IAAI,EAAS,SAAS,MAAM,EAAE,SAAS,CAAC,CAAC;GAC5D,SAAS,CAAC,CAAC,MAAQ,EAAE,WAAW,CAAC,EAAE;GACnC,OAAO,EAAE;GACT,QAAQ,EAAE;EACZ;CACF,GAAG;EAAC;EAAS;EAAU;EAAU;EAAK,EAAE;EAAS,EAAE;EAAQ,EAAE;EAAO,EAAE;CAAM,CAAC;AAC/E"}
1
+ {"version":3,"file":"detail.js","names":[],"sources":["../../src/react/detail.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport type { Content, Decision, Version } from \"../client/generated/wire.js\";\nimport { parseVersionCode } from \"../urls/index.js\";\nimport { useVersionParam } from \"./location.js\";\nimport { keyOf, useContentScope, useResource } from \"./use-resource.js\";\n\n/** A content's live versions, first first. */\nexport function liveVersions(c: Content | null | undefined): Version[] {\n return (c?.versions ?? []).filter((v) => v.live).sort((a, b) => a.position - b.position);\n}\n\n/**\n * The version a reader of language opens by default: the first live one by\n * position serving it, else the first live one. The server picks cards'\n * versions, covers and meta by the same rule.\n */\nexport function defaultVersion(c: Content | null | undefined, language?: string): Version | null {\n return editionOf(liveVersions(c), language);\n}\n\n/** defaultVersion among versions: the first by position serving language, else the first. */\nexport function editionOf(versions: readonly Version[], language?: string): Version | null {\n const sorted = [...versions].sort((a, b) => a.position - b.position);\n return (language ? sorted.find((v) => v.languages.includes(language)) : undefined) ?? sorted[0] ?? null;\n}\n\n/** The version selected names: its code (any spelling, the page's ?v=) or its id; undefined when none. */\nexport function findVersion(versions: readonly Version[], selected: string | null | undefined): Version | undefined {\n if (!selected) return undefined;\n const code = parseVersionCode(selected);\n return versions.find((v) => v.id === selected) ?? (code ? versions.find((v) => v.code === code) : undefined);\n}\n\nexport interface UseContentDetail {\n content: Content | null;\n /** Live versions, first first. */\n versions: Version[];\n /** The version shown: the one asked for (?v=) when live, else the language's default. */\n version: Version | null;\n /** The shown version's access decision: allowed, what is sold, previews. */\n decision: Decision | null;\n /** Every language a live version serves, in version order. */\n languages: string[];\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n}\n\nexport interface ContentDetailOptions {\n language?: string;\n /** The version to show, by code or id; default the page's ?v=. Unknown or not live: the language's default. */\n version?: string | null;\n /** The page's location for its ?v=; default window.location. */\n location?: string;\n client?: ContentKitClient;\n}\n\n/**\n * A content page's data: text in the reader's language, live versions with\n * their segments and access decisions, groups, attributes and the version to show.\n */\nexport function useContentDetail(id: string | null | undefined, o: ContentDetailOptions = {}): UseContentDetail {\n const { client, store, scope, language: provided } = useContentScope(o.client);\n const language = o.language ?? provided;\n const [param] = useVersionParam({ location: o.location });\n const selected = o.version === undefined ? param : o.version;\n const key = id ? keyOf(\"content-detail\", scope, id, language) : null;\n const r = useResource<Content>(store, key, { type: \"content-detail\", id: id ?? undefined }, (s) => client.contents.get(id!, language, s));\n const content = r.data ?? null;\n return useMemo(() => {\n const versions = liveVersions(content);\n const version = findVersion(versions, selected) ?? defaultVersion(content, language);\n return {\n content,\n versions,\n version,\n decision: version?.decision ?? content?.decision ?? null,\n languages: [...new Set(versions.flatMap((v) => v.languages))],\n loading: !!key && (r.loading || !r.loaded),\n error: r.error,\n reload: r.reload,\n };\n }, [content, selected, language, key, r.loading, r.loaded, r.error, r.reload]);\n}\n"],"mappings":";;;;;AASA,SAAgB,EAAa,GAA0C;CACrE,QAAQ,GAAG,YAAY,CAAC,EAAA,CAAG,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AACzF;AAOA,SAAgB,EAAe,GAA+B,GAAmC;CAC/F,OAAO,EAAU,EAAa,CAAC,GAAG,CAAQ;AAC5C;AAGA,SAAgB,EAAU,GAA8B,GAAmC;CACzF,IAAM,IAAS,CAAC,GAAG,CAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CACnE,QAAQ,IAAW,EAAO,MAAM,MAAM,EAAE,UAAU,SAAS,CAAQ,CAAC,IAAI,KAAA,MAAc,EAAO,MAAM;AACrG;AAGA,SAAgB,EAAY,GAA8B,GAA0D;CAClH,IAAI,CAAC,GAAU;CACf,IAAM,IAAO,EAAiB,CAAQ;CACtC,OAAO,EAAS,MAAM,MAAM,EAAE,OAAO,CAAQ,MAAM,IAAO,EAAS,MAAM,MAAM,EAAE,SAAS,CAAI,IAAI,KAAA;AACpG;AA8BA,SAAgB,EAAiB,GAA+B,IAA0B,CAAC,GAAqB;CAC9G,IAAM,EAAE,WAAQ,UAAO,UAAO,UAAU,MAAa,EAAgB,EAAE,MAAM,GACvE,IAAW,EAAE,YAAY,GACzB,CAAC,KAAS,EAAgB,EAAE,UAAU,EAAE,SAAS,CAAC,GAClD,IAAW,EAAE,YAAY,KAAA,IAAY,IAAQ,EAAE,SAC/C,IAAM,IAAK,EAAM,kBAAkB,GAAO,GAAI,CAAQ,IAAI,MAC1D,IAAI,EAAqB,GAAO,GAAK;EAAE,MAAM;EAAkB,IAAI,KAAM,KAAA;CAAU,IAAI,MAAM,EAAO,SAAS,IAAI,GAAK,GAAU,CAAC,CAAC,GAClI,IAAU,EAAE,QAAQ;CAC1B,OAAO,QAAc;EACnB,IAAM,IAAW,EAAa,CAAO,GAC/B,IAAU,EAAY,GAAU,CAAQ,KAAK,EAAe,GAAS,CAAQ;EACnF,OAAO;GACL;GACA;GACA;GACA,UAAU,GAAS,YAAY,GAAS,YAAY;GACpD,WAAW,CAAC,GAAG,IAAI,IAAI,EAAS,SAAS,MAAM,EAAE,SAAS,CAAC,CAAC;GAC5D,SAAS,CAAC,CAAC,MAAQ,EAAE,WAAW,CAAC,EAAE;GACnC,OAAO,EAAE;GACT,QAAQ,EAAE;EACZ;CACF,GAAG;EAAC;EAAS;EAAU;EAAU;EAAK,EAAE;EAAS,EAAE;EAAQ,EAAE;EAAO,EAAE;CAAM,CAAC;AAC/E"}
@@ -13,7 +13,7 @@ export { useFavorite, useReaction, type UseFavorite, type UseReaction } from './
13
13
  export { usePoll, usePollEditor, usePolls, withVote, type PollFilter, type PollImages, type UsePoll, type UsePollEditor } from './polls.js';
14
14
  export { useContent, useManagedContents, type ManageFilter, type UseContent } from './contents.js';
15
15
  export { useActivity, useMyStats, useTopContents, type UseStats } from './stats.js';
16
- export { defaultVersion, findVersion, liveVersions, useContentDetail, type ContentDetailOptions, type UseContentDetail } from './detail.js';
16
+ export { defaultVersion, editionOf, findVersion, liveVersions, useContentDetail, type ContentDetailOptions, type UseContentDetail } from './detail.js';
17
17
  export { useVersionParam } from './location.js';
18
18
  export { memberId, useContentGroups, useGroup, useGroupMembers, useGroups, useKinds, useMyGroups, type GroupFilter, type GroupSort, type Member, type UseContentGroups, type UseGroup, type UseGroupMembers, type UseKinds, } from './groups.js';
19
19
  export { beaconsFor, useBeacons, useContentFilters, useContents, useExposure, useHistory, useLockedCount, useMyFavorites, useMyReactions, useRecentSearches, useRecommendations, useReleaseDates, useSearch, useSimilar, useTypeahead, useViewSignal, type ExposureOptions, type MyInteraction, type ReleaseDate, type UseContentFilters, type UseExposure, type UseHistory, type UseRail, type UseRecentSearches, type UseTypeahead, } from './discovery.js';
@@ -23,27 +23,27 @@ function f(e = {}) {
23
23
  };
24
24
  }
25
25
  function p(r, a, s = {}) {
26
- let { kind: l, id: u } = r, d = i(s.client), { presets: p, loaded: m } = f({ client: d }), h = p.find((e) => e.kind === l && e.name === a), g = s.image !== void 0, _ = h ? t(h.from) ? n(h.from) : h.from : void 0, v = o(r, {
27
- prefix: _,
28
- read: g || !m ? null : s.read,
29
- client: d
26
+ let { kind: l, id: u, version: d } = r, p = i(s.client), { presets: m, loaded: h } = f({ client: p }), g = m.find((e) => e.kind === l && e.name === a), _ = s.image !== void 0, v = g ? t(g.from) ? n(g.from) : g.from : void 0, y = o(r, {
27
+ prefix: v,
28
+ read: _ || !h ? null : s.read,
29
+ client: p
30
30
  });
31
31
  return {
32
32
  ...c(() => {
33
- let t = g ? s.image : v.read?.public?.find((e) => e.preset === a);
33
+ let t = _ ? s.image : y.read?.public?.find((e) => e.preset === a);
34
34
  if (t && t.renditions.length) return {
35
35
  image: {
36
36
  preset: a,
37
37
  renditions: t.renditions,
38
- aspect: "aspect" in t && t.aspect || h?.aspect
38
+ aspect: "aspect" in t && t.aspect || g?.aspect
39
39
  },
40
40
  isDefault: !1
41
41
  };
42
- if (!g && !v.read) return {
42
+ if (!_ && !y.read) return {
43
43
  image: null,
44
44
  isDefault: !1
45
45
  };
46
- let n = h && e(h, u);
46
+ let n = g && e(g, u, d);
47
47
  return n ? {
48
48
  image: n,
49
49
  isDefault: !0
@@ -52,17 +52,18 @@ function p(r, a, s = {}) {
52
52
  isDefault: !1
53
53
  };
54
54
  }, [
55
- g,
55
+ _,
56
56
  s.image,
57
- v.read,
57
+ y.read,
58
58
  a,
59
- h,
60
- u
59
+ g,
60
+ u,
61
+ d
61
62
  ]),
62
- rule: h,
63
- loading: !m || v.loading,
64
- error: v.error,
65
- reload: v.reload
63
+ rule: g,
64
+ loading: !h || y.loading,
65
+ error: y.error,
66
+ reload: y.reload
66
67
  };
67
68
  }
68
69
  function m(e, t = {}) {