@openrails/contentkit-ui 0.79.0 → 0.81.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 (295) hide show
  1. package/README.md +82 -54
  2. package/dist/client/beacons.d.ts +53 -0
  3. package/dist/client/beacons.js +72 -0
  4. package/dist/client/beacons.js.map +1 -0
  5. package/dist/client/catalog.d.ts +2 -2
  6. package/dist/client/catalog.js +1 -1
  7. package/dist/client/catalog.js.map +1 -1
  8. package/dist/client/client.d.ts +24 -11
  9. package/dist/client/client.js +37 -29
  10. package/dist/client/client.js.map +1 -1
  11. package/dist/client/content/comments.d.ts +12 -8
  12. package/dist/client/content/comments.js +33 -20
  13. package/dist/client/content/comments.js.map +1 -1
  14. package/dist/client/content/favorites.d.ts +24 -9
  15. package/dist/client/content/favorites.js +21 -22
  16. package/dist/client/content/favorites.js.map +1 -1
  17. package/dist/client/content/moderation.d.ts +1 -1
  18. package/dist/client/content/moderation.js.map +1 -1
  19. package/dist/client/content/reactions.d.ts +11 -6
  20. package/dist/client/content/reactions.js +23 -18
  21. package/dist/client/content/reactions.js.map +1 -1
  22. package/dist/client/content/types.d.ts +40 -21
  23. package/dist/client/content/types.js +5 -2
  24. package/dist/client/content/types.js.map +1 -1
  25. package/dist/client/contents.d.ts +55 -0
  26. package/dist/client/contents.js +144 -0
  27. package/dist/client/contents.js.map +1 -0
  28. package/dist/client/discovery.d.ts +126 -0
  29. package/dist/client/discovery.js +125 -0
  30. package/dist/client/discovery.js.map +1 -0
  31. package/dist/client/generated/error-codes.d.ts +1 -1
  32. package/dist/client/generated/error-codes.js +12 -0
  33. package/dist/client/generated/error-codes.js.map +1 -1
  34. package/dist/client/generated/routes.d.ts +1244 -370
  35. package/dist/client/generated/routes.js +1870 -554
  36. package/dist/client/generated/routes.js.map +1 -1
  37. package/dist/client/generated/wire.d.ts +960 -134
  38. package/dist/client/generated/wire.js.map +1 -1
  39. package/dist/client/group-fields.d.ts +25 -0
  40. package/dist/client/group-fields.js +63 -0
  41. package/dist/client/group-fields.js.map +1 -0
  42. package/dist/client/groups.d.ts +58 -0
  43. package/dist/client/groups.js +119 -0
  44. package/dist/client/groups.js.map +1 -0
  45. package/dist/client/http.d.ts +24 -12
  46. package/dist/client/http.js +20 -24
  47. package/dist/client/http.js.map +1 -1
  48. package/dist/client/index.d.ts +11 -6
  49. package/dist/client/media/api.d.ts +1 -1
  50. package/dist/client/media/api.js +4 -2
  51. package/dist/client/media/api.js.map +1 -1
  52. package/dist/client/media/client.d.ts +8 -9
  53. package/dist/client/media/client.js +58 -71
  54. package/dist/client/media/client.js.map +1 -1
  55. package/dist/client/media/ref.d.ts +30 -0
  56. package/dist/client/media/ref.js +10 -3
  57. package/dist/client/media/ref.js.map +1 -1
  58. package/dist/client/offers.d.ts +41 -0
  59. package/dist/client/offers.js +66 -0
  60. package/dist/client/offers.js.map +1 -0
  61. package/dist/client/route.d.ts +2 -0
  62. package/dist/client/route.js +2 -1
  63. package/dist/client/route.js.map +1 -1
  64. package/dist/client/versions.d.ts +20 -0
  65. package/dist/client/versions.js +53 -0
  66. package/dist/client/versions.js.map +1 -0
  67. package/dist/client.js +35 -30
  68. package/dist/components/admin/content-manager.d.ts +80 -0
  69. package/dist/components/admin/content-manager.js +591 -0
  70. package/dist/components/admin/content-manager.js.map +1 -0
  71. package/dist/components/admin/group-manager.d.ts +24 -0
  72. package/dist/components/admin/group-manager.js +244 -0
  73. package/dist/components/admin/group-manager.js.map +1 -0
  74. package/dist/components/admin/media-item-panel.js +1 -1
  75. package/dist/components/admin/media-item-panel.js.map +1 -1
  76. package/dist/components/content/card.d.ts +44 -0
  77. package/dist/components/content/card.js +285 -0
  78. package/dist/components/content/card.js.map +1 -0
  79. package/dist/components/content/editors.d.ts +40 -0
  80. package/dist/components/content/editors.js +764 -0
  81. package/dist/components/content/editors.js.map +1 -0
  82. package/dist/components/content/filters.d.ts +28 -0
  83. package/dist/components/content/filters.js +134 -0
  84. package/dist/components/content/filters.js.map +1 -0
  85. package/dist/components/content/grid.d.ts +62 -0
  86. package/dist/components/content/grid.js +233 -0
  87. package/dist/components/content/grid.js.map +1 -0
  88. package/dist/components/content/index.d.ts +8 -0
  89. package/dist/components/content/listing.d.ts +20 -0
  90. package/dist/components/content/listing.js +150 -0
  91. package/dist/components/content/listing.js.map +1 -0
  92. package/dist/components/content/offer-fields.d.ts +8 -0
  93. package/dist/components/content/offer-fields.js +127 -0
  94. package/dist/components/content/offer-fields.js.map +1 -0
  95. package/dist/components/content/paywall.d.ts +53 -0
  96. package/dist/components/content/paywall.js +221 -0
  97. package/dist/components/content/paywall.js.map +1 -0
  98. package/dist/components/content/search.d.ts +27 -0
  99. package/dist/components/content/search.js +164 -0
  100. package/dist/components/content/search.js.map +1 -0
  101. package/dist/components/groups/banner.d.ts +51 -0
  102. package/dist/components/groups/banner.js +194 -0
  103. package/dist/components/groups/banner.js.map +1 -0
  104. package/dist/components/groups/directory.d.ts +37 -0
  105. package/dist/components/groups/directory.js +117 -0
  106. package/dist/components/groups/directory.js.map +1 -0
  107. package/dist/components/groups/editor.d.ts +44 -0
  108. package/dist/components/groups/editor.js +860 -0
  109. package/dist/components/groups/editor.js.map +1 -0
  110. package/dist/components/groups/fields.d.ts +13 -0
  111. package/dist/components/groups/fields.js +68 -0
  112. package/dist/components/groups/fields.js.map +1 -0
  113. package/dist/components/groups/header.d.ts +25 -0
  114. package/dist/components/groups/header.js +131 -0
  115. package/dist/components/groups/header.js.map +1 -0
  116. package/dist/components/groups/index.d.ts +6 -0
  117. package/dist/components/groups/memberships.d.ts +55 -0
  118. package/dist/components/groups/memberships.js +527 -0
  119. package/dist/components/groups/memberships.js.map +1 -0
  120. package/dist/components/groups/parts.d.ts +49 -0
  121. package/dist/components/groups/parts.js +172 -0
  122. package/dist/components/groups/parts.js.map +1 -0
  123. package/dist/components/media-folder-editor.js +153 -152
  124. package/dist/components/media-folder-editor.js.map +1 -1
  125. package/dist/components/social/comments.d.ts +7 -5
  126. package/dist/components/social/comments.js +6 -2
  127. package/dist/components/social/comments.js.map +1 -1
  128. package/dist/components/social/engagement.d.ts +4 -3
  129. package/dist/components/social/engagement.js.map +1 -1
  130. package/dist/components/social/moderation.d.ts +1 -1
  131. package/dist/components/social/moderation.js +3 -3
  132. package/dist/components/social/moderation.js.map +1 -1
  133. package/dist/editor/rich-text-editor.d.ts +4 -4
  134. package/dist/editor/rich-text-editor.js.map +1 -1
  135. package/dist/index.d.ts +4 -2
  136. package/dist/index.js +23 -9
  137. package/dist/locales/admin/de.js +5 -66
  138. package/dist/locales/admin/de.js.map +1 -1
  139. package/dist/locales/admin/en.d.ts +5 -66
  140. package/dist/locales/admin/en.js +19 -80
  141. package/dist/locales/admin/en.js.map +1 -1
  142. package/dist/locales/admin/es.js +5 -66
  143. package/dist/locales/admin/es.js.map +1 -1
  144. package/dist/locales/admin/ja.js +5 -66
  145. package/dist/locales/admin/ja.js.map +1 -1
  146. package/dist/locales/admin/ko.js +5 -66
  147. package/dist/locales/admin/ko.js.map +1 -1
  148. package/dist/locales/admin/zh.js +5 -66
  149. package/dist/locales/admin/zh.js.map +1 -1
  150. package/dist/locales/content/de.d.ts +2 -0
  151. package/dist/locales/content/de.js +98 -0
  152. package/dist/locales/content/de.js.map +1 -0
  153. package/dist/locales/content/en.d.ts +93 -0
  154. package/dist/locales/content/en.js +98 -0
  155. package/dist/locales/content/en.js.map +1 -0
  156. package/dist/locales/content/es.d.ts +2 -0
  157. package/dist/locales/content/es.js +98 -0
  158. package/dist/locales/content/es.js.map +1 -0
  159. package/dist/locales/content/ja.d.ts +2 -0
  160. package/dist/locales/content/ja.js +98 -0
  161. package/dist/locales/content/ja.js.map +1 -0
  162. package/dist/locales/content/ko.d.ts +2 -0
  163. package/dist/locales/content/ko.js +98 -0
  164. package/dist/locales/content/ko.js.map +1 -0
  165. package/dist/locales/content/zh.d.ts +2 -0
  166. package/dist/locales/content/zh.js +98 -0
  167. package/dist/locales/content/zh.js.map +1 -0
  168. package/dist/locales/de.js +11 -2
  169. package/dist/locales/de.js.map +1 -1
  170. package/dist/locales/discovery/de.d.ts +2 -0
  171. package/dist/locales/discovery/de.js +68 -0
  172. package/dist/locales/discovery/de.js.map +1 -0
  173. package/dist/locales/discovery/en.d.ts +63 -0
  174. package/dist/locales/discovery/en.js +68 -0
  175. package/dist/locales/discovery/en.js.map +1 -0
  176. package/dist/locales/discovery/es.d.ts +2 -0
  177. package/dist/locales/discovery/es.js +68 -0
  178. package/dist/locales/discovery/es.js.map +1 -0
  179. package/dist/locales/discovery/ja.d.ts +2 -0
  180. package/dist/locales/discovery/ja.js +68 -0
  181. package/dist/locales/discovery/ja.js.map +1 -0
  182. package/dist/locales/discovery/ko.d.ts +2 -0
  183. package/dist/locales/discovery/ko.js +68 -0
  184. package/dist/locales/discovery/ko.js.map +1 -0
  185. package/dist/locales/discovery/zh.d.ts +2 -0
  186. package/dist/locales/discovery/zh.js +68 -0
  187. package/dist/locales/discovery/zh.js.map +1 -0
  188. package/dist/locales/en.d.ts +288 -67
  189. package/dist/locales/en.js +11 -2
  190. package/dist/locales/en.js.map +1 -1
  191. package/dist/locales/es.js +11 -2
  192. package/dist/locales/es.js.map +1 -1
  193. package/dist/locales/groups/de.d.ts +2 -0
  194. package/dist/locales/groups/de.js +133 -0
  195. package/dist/locales/groups/de.js.map +1 -0
  196. package/dist/locales/groups/en.d.ts +128 -0
  197. package/dist/locales/groups/en.js +133 -0
  198. package/dist/locales/groups/en.js.map +1 -0
  199. package/dist/locales/groups/es.d.ts +2 -0
  200. package/dist/locales/groups/es.js +133 -0
  201. package/dist/locales/groups/es.js.map +1 -0
  202. package/dist/locales/groups/ja.d.ts +2 -0
  203. package/dist/locales/groups/ja.js +133 -0
  204. package/dist/locales/groups/ja.js.map +1 -0
  205. package/dist/locales/groups/ko.d.ts +2 -0
  206. package/dist/locales/groups/ko.js +133 -0
  207. package/dist/locales/groups/ko.js.map +1 -0
  208. package/dist/locales/groups/zh.d.ts +2 -0
  209. package/dist/locales/groups/zh.js +133 -0
  210. package/dist/locales/groups/zh.js.map +1 -0
  211. package/dist/locales/ja.js +11 -2
  212. package/dist/locales/ja.js.map +1 -1
  213. package/dist/locales/ko.js +11 -2
  214. package/dist/locales/ko.js.map +1 -1
  215. package/dist/locales/social/de.js +2 -1
  216. package/dist/locales/social/de.js.map +1 -1
  217. package/dist/locales/social/en.d.ts +2 -1
  218. package/dist/locales/social/en.js +2 -1
  219. package/dist/locales/social/en.js.map +1 -1
  220. package/dist/locales/social/es.js +2 -1
  221. package/dist/locales/social/es.js.map +1 -1
  222. package/dist/locales/social/ja.js +2 -1
  223. package/dist/locales/social/ja.js.map +1 -1
  224. package/dist/locales/social/ko.js +2 -1
  225. package/dist/locales/social/ko.js.map +1 -1
  226. package/dist/locales/social/zh.js +2 -1
  227. package/dist/locales/social/zh.js.map +1 -1
  228. package/dist/locales/zh.js +11 -2
  229. package/dist/locales/zh.js.map +1 -1
  230. package/dist/react/admin.d.ts +1 -1
  231. package/dist/react/admin.js +8 -6
  232. package/dist/react/admin.js.map +1 -1
  233. package/dist/react/comments.d.ts +9 -7
  234. package/dist/react/comments.js +42 -27
  235. package/dist/react/comments.js.map +1 -1
  236. package/dist/react/contents.d.ts +57 -0
  237. package/dist/react/contents.js +214 -0
  238. package/dist/react/contents.js.map +1 -0
  239. package/dist/react/context.d.ts +1 -1
  240. package/dist/react/context.js.map +1 -1
  241. package/dist/react/crop.js +45 -44
  242. package/dist/react/crop.js.map +1 -1
  243. package/dist/react/detail.d.ts +30 -0
  244. package/dist/react/detail.js +42 -0
  245. package/dist/react/detail.js.map +1 -0
  246. package/dist/react/discovery.d.ts +138 -0
  247. package/dist/react/discovery.js +299 -0
  248. package/dist/react/discovery.js.map +1 -0
  249. package/dist/react/engagement.d.ts +5 -5
  250. package/dist/react/engagement.js +55 -41
  251. package/dist/react/engagement.js.map +1 -1
  252. package/dist/react/folder.js +54 -53
  253. package/dist/react/folder.js.map +1 -1
  254. package/dist/react/groups.d.ts +115 -0
  255. package/dist/react/groups.js +229 -0
  256. package/dist/react/groups.js.map +1 -0
  257. package/dist/react/index.d.ts +5 -1
  258. package/dist/react/public.d.ts +6 -5
  259. package/dist/react/public.js +19 -22
  260. package/dist/react/public.js.map +1 -1
  261. package/dist/react/read.js +105 -106
  262. package/dist/react/read.js.map +1 -1
  263. package/dist/react/resources.d.ts +2 -2
  264. package/dist/react/resources.js +69 -40
  265. package/dist/react/resources.js.map +1 -1
  266. package/dist/react/store.js +41 -39
  267. package/dist/react/store.js.map +1 -1
  268. package/dist/react/upload.js +54 -53
  269. package/dist/react/upload.js.map +1 -1
  270. package/dist/react/use-resource.d.ts +2 -1
  271. package/dist/react/use-resource.js +1 -0
  272. package/dist/react/use-resource.js.map +1 -1
  273. package/dist/react/versions.d.ts +57 -0
  274. package/dist/react/versions.js +94 -0
  275. package/dist/react/versions.js.map +1 -0
  276. package/dist/react/video.js +87 -86
  277. package/dist/react/video.js.map +1 -1
  278. package/dist/react.js +9 -5
  279. package/dist/styles.css +1 -1
  280. package/package.json +1 -1
  281. package/dist/client/content/posts.d.ts +0 -52
  282. package/dist/client/content/posts.js +0 -84
  283. package/dist/client/content/posts.js.map +0 -1
  284. package/dist/client/taxonomy.d.ts +0 -34
  285. package/dist/client/taxonomy.js +0 -84
  286. package/dist/client/taxonomy.js.map +0 -1
  287. package/dist/components/admin/post-manager.d.ts +0 -45
  288. package/dist/components/admin/post-manager.js +0 -602
  289. package/dist/components/admin/post-manager.js.map +0 -1
  290. package/dist/components/admin/taxonomy-manager.d.ts +0 -54
  291. package/dist/components/admin/taxonomy-manager.js +0 -976
  292. package/dist/components/admin/taxonomy-manager.js.map +0 -1
  293. package/dist/react/posts.d.ts +0 -61
  294. package/dist/react/posts.js +0 -183
  295. package/dist/react/posts.js.map +0 -1
package/README.md CHANGED
@@ -15,7 +15,7 @@ pnpm add @openrails/contentkit-ui@X.Y.Z
15
15
  | `@openrails/contentkit-ui/react` | `ContentKitProvider` and every hook | `react` |
16
16
  | `@openrails/contentkit-ui/client` | `createContentKitClient`, `ContentKitError`, generated wire types, crop/aspect/rendition/ABR/gallery math | nothing |
17
17
  | `@openrails/contentkit-ui/urls` | content codes, `[/{lang}]/{route}/{CODE}[/{slug}]` paths, `canonical` | nothing |
18
- | `@openrails/contentkit-ui/editor` | `RichTextEditor`, `richTextEditor` (PostManager's body editor) | the optional peers `@tiptap/core`, `@tiptap/pm`, `@tiptap/react`, `@tiptap/starter-kit`, `@tiptap/extension-image`, `@tiptap/extensions` (3.31+) |
18
+ | `@openrails/contentkit-ui/editor` | `RichTextEditor`, `richTextEditor` (ContentManager's body editor) | the optional peers `@tiptap/core`, `@tiptap/pm`, `@tiptap/react`, `@tiptap/starter-kit`, `@tiptap/extension-image`, `@tiptap/extensions` (3.31+) |
19
19
  | `@openrails/contentkit-ui/locales/{de,es,ja,ko,zh}` | message bundles (English is built in) | |
20
20
  | `@openrails/contentkit-ui/styles.css` | the stylesheet, scoped under `.ckui` (the root entry also installs it) | |
21
21
 
@@ -31,7 +31,7 @@ import { ContentKitUiProvider } from "@openrails/contentkit-ui";
31
31
  import { createContentURLs } from "@openrails/contentkit-ui/urls";
32
32
 
33
33
  const contentkit = createContentKitClient({
34
- baseUrl: "/api/v1/contentkit", // where the host mounts ContentKit
34
+ baseUrl: "/api/contentkit", // the host's contentkit.Routes.Prefix
35
35
  mounts: { upload: "/api/v1/user-uploads" }, // optional: modules mounted elsewhere (a string, or (ref) => string)
36
36
  fetch: auth.authFetch, // an authenticating transport
37
37
  token: () => auth.getAccessToken(), // HLS playlists on the API origin (hls.js uses XHR)
@@ -72,8 +72,9 @@ The framework-free client exposes `setReadScope(key)` and passes that key as
72
72
  the second `subscribe` / `onChange` argument, or `null` when the scope changed during the
73
73
  operation. This does not change the operation's result or its authentication.
74
74
 
75
- - **Mounts.** Without `mounts`, modules live under `baseUrl`: content at the
76
- root, `/media` (reads, HLS), `/media/upload`, `/codes`, `/taxonomy`.
75
+ - **Mounts.** Without `mounts`, modules live under `{baseUrl}/v1`: content,
76
+ groups and the content model at its root, `/media` (reads, HLS),
77
+ `/media/upload`, `/codes`. A mount is a module's full base URL.
77
78
  - **`client.media`**: `upload`, `put` (upload, commit, wait), `commit`, `read`,
78
79
  `waitFor`, `editorView`, `getFrame`, `hlsBase(ref, dir)`, `discard`, and
79
80
  `xhrSetup` for players (`<MediaGallery xhrSetup={contentkit.media.xhrSetup} />`).
@@ -81,23 +82,25 @@ operation. This does not change the operation's result or its authentication.
81
82
  resumable multipart above (`onState`/`resume`). The bucket must allow CORS
82
83
  `PUT` from the app's origin with `Content-Type` and `x-amz-checksum-sha256`.
83
84
  - **Content modules**, one per resource, each method one route of the
84
- generated route table with its generated types: `posts`, `comments`,
85
+ generated route table with its generated types: `contents`, `catalog`, `comments`,
85
86
  `reactions`, `favorites`, `polls`, `bans` (`"owner"` or `"global"`; `get("global", user)`),
86
- `moderation`, `taxonomy`, `codes`. Lists take `limit`/`offset` (the review
87
- queue a `cursor`); staff lists are `posts.adminList` (`q` searches,
88
- `deleted: true` lists deleted posts for `posts.restore`), `polls.adminList` (`state`, `q`),
89
- `comments.adminList` (`contentKind`, `author`); `taxonomy.nodeAssignments(id)`
90
- pages a node's contents.
91
- - **Images of posts and polls** go to the item's server-named upload path:
92
- `media.uploadNamed(ref, file)` resolves the name content routes take;
93
- `media.uploadInline(postId, file)` resolves a body image's reference
94
- (`ref`, `contentkit:i-{uuid}`, what the body stores: `imageRef(name)`) and
95
- a URL that shows it now; `posts.uploadCover`, `polls.uploadImage` and
87
+ `moderation`, `groups`, `codes`. Lists take `limit`/`offset` (the review
88
+ queue and group listings a `cursor`); `contents.manage` lists what the caller
89
+ manages (`kind`, `state` incl. `deleted` for `contents.restore`, `q`); `catalog.totals({kinds, languages})`
90
+ counts what is open; staff lists are `polls.adminList` (`state`, `q`),
91
+ `comments.adminList` (`contentKind`, `author`); `groups.members(id)` pages
92
+ a group's memberships, `groups.ofContent(id)` / `setOfContent` a content's,
93
+ `groups.uploadCover(group, file)` its cover; `client.kinds()` the content model.
94
+ - **Images of content and polls** go to the item's server-named upload path:
95
+ `media.uploadNamed(ref, file)` resolves the name content routes take
96
+ (`contents.setCover`); `media.uploadInline({kind, id}, file)` resolves a
97
+ body image's reference (`ref`, `contentkit:i-{uuid}`, what the body stores:
98
+ `imageRef(name)`) and a URL that shows it now; `polls.uploadImage` and
96
99
  `polls.uploadOptionImage` do both steps. Reads resolve references: a
97
- post's `body` shows the images' current public files and `images` maps
98
- each name to its URL. `folders` renames the post and poll media kinds. A
99
- post's public image files exist only while it is published; `usePost`
100
- shows an unpublished post's images to its editors (below).
100
+ content's `body` shows the images' current public files and `images` maps
101
+ each name to its URL. `folders` renames the poll media kind. Public image
102
+ files exist only while the item is live; `useContent` shows an unpublished
103
+ item's images to its editors (below).
101
104
  - **`client.config()`** says what the content module allows: which
102
105
  interactions signed-out visitors may make (`content.Options.Anonymous`,
103
106
  none by default) and the longest comment (`CommentMaxLength`, 2,200 by
@@ -145,23 +148,23 @@ const { poll, vote } = usePoll(null, { language }); // the newest live poll
145
148
  const editor = usePollEditor(pollId); // staff
146
149
  ```
147
150
 
148
- A post editor puts `editorBody` in its rich-text field and inserts what
151
+ A content editor puts `text.body` in its rich-text field and inserts what
149
152
  `uploadImage(file)` resolves: URLs that show the images now (the file for a
150
- fresh upload, public files, signed editor views of an unpublished post's
151
- images, read and refreshed as needed). `update({ body })` stores each of
152
- those URLs as the image's reference (`storedBody(html)` does the same for a
153
- host's own save); `coverSrc` shows the cover, `imageSrc(name)` any image.
153
+ fresh upload, public files, signed editor views of an unpublished item's
154
+ images, read and refreshed as needed). `putText({ title, body })` stores each
155
+ of those URLs as the image's reference (`storedBody(html)` does the same for
156
+ a host's own save); `coverSrc` shows the cover, `imageSrc(name)` any image.
154
157
 
155
158
  ```tsx
156
- const editor = usePost(postId);
157
- <RichText value={editor.editorBody} onImage={editor.uploadImage} onSave={(html) => editor.update({ body: html })} />
159
+ const editor = useContent(id, "blog", "en");
160
+ <RichText value={editor.text?.body} onImage={editor.uploadImage} onSave={(html) => editor.putText({ title, body: html })} />
158
161
  <img src={editor.coverSrc} alt="" />
159
162
  ```
160
163
 
161
164
  Also `useContentConfig` (`client.config()`, read once), `useCommentReplies`,
162
165
  `useCanComment` (may the caller comment, may signed-out visitors, the ban
163
166
  that stops it, and what it may do to others' comments), `useLatestComments`,
164
- `usePolls`, `usePosts` (staff: `admin`, `deleted`, `q`), `usePost` (with `restore`), and for staff `useAdminComments`
167
+ `usePolls`, `useManagedContents` (`kind`, `state`, `q`), `useContent` (with `publish`, `restore`), and for staff `useAdminComments`
165
168
  (`contentKind`, `author`), `useModerationQueue`, `useCommentBans`, `useGlobalCommentBan` (one user's), and
166
169
  `useStaffAccess`, `useAdminLocation`, `useFlowClient` and `usePaged`, which the admin flows are built from.
167
170
 
@@ -226,7 +229,7 @@ op. Nothing is uploaded.
226
229
  ### usePublicImage
227
230
 
228
231
  ```tsx
229
- const { image, rule, isDefault } = usePublicImage("user", userId, "avatar");
232
+ const { image, rule, isDefault } = usePublicImage({ kind: "user", id: userId }, "avatar");
230
233
  <SlotImage image={image} fallback={<Initials name={name} />} />
231
234
  ```
232
235
 
@@ -415,6 +418,37 @@ Staff building blocks: `CommentBans` (paged) with `CommentBanDialog`, and
415
418
  <Poll language={lang} />
416
419
  ```
417
420
 
421
+ ## Groups
422
+
423
+ Tags, artists, series, characters, channels and rails: the kinds the site's
424
+ content model declares. `useKinds()` reads it (`GET /v1/kinds`); until that
425
+ route is served, pass the host's model as `initial` (and `kinds` to the
426
+ components below).
427
+
428
+ ```tsx
429
+ const { items, hasMore, loadMore } = useGroups({ kind: "artist", letter: "a", sort: "count" }); // parent, scope, contentKind, language
430
+ const g = useGroup(id); // g.group: names, text, attributes, parent, scope, children, edges, counts
431
+ await g.update({ attributes: { artist_type: "circle" } }); // staff: create, names, edges, merge, remove, restore
432
+ await g.uploadCover(file); // into the group kind's folder (cover/{name}), then selected
433
+ const members = useGroupMembers(id); // add, remove, reorder
434
+ const content = useContentGroups(contentId); // content.set(memberships, kinds)
435
+ ```
436
+
437
+ - **`GroupDirectory`**: one kind's groups with A–Z buckets (`letter` and
438
+ `onLetterChange` to keep it in the URL), content counts, `layout` list or
439
+ grid, paged. **`GroupHeader`**: a group page's cover, names, description,
440
+ typed attributes, parent, scope and sub-groups. Links go to each group's
441
+ canonical path (`href` overrides) through the provider's `navigate`.
442
+ - **`GroupEditor`** (staff): create, or edit slug, parent, scope, declared
443
+ attributes, names and aliases per language, description and sort key per
444
+ language, cover, related groups; `GroupMergeDialog`; delete and restore.
445
+ - **`MembershipEditor`** (staff): a content's groups per group kind, with the
446
+ version (`versions`), role, position and window each kind allows; one save.
447
+ **`GroupMembers`**: a group's members, reordered, windowed, used as cover.
448
+ - `labels` names kinds, attributes, roles and values by identifier; others
449
+ show humanized. Covers render `cover.renditions`; `renderCover` stands in
450
+ until group reads carry them.
451
+
418
452
  ## Admin flows
419
453
 
420
454
  Drop-in staff screens for a host's own admin area: each makes its own reads
@@ -448,7 +482,7 @@ const nav = useMemo(() => ({ pathname: location.pathname, search: location.searc
448
482
  `userHref`, `sections`, `pageSize`. `useCommentModerationAccess()`.
449
483
 
450
484
  ```tsx
451
- <CommentModeration client={contentkit} basePath="/admin/comments" navigation={nav} contentKinds={["video", "post"]} />
485
+ <CommentModeration client={contentkit} basePath="/admin/comments" navigation={nav} contentKinds={["video", "blog"]} />
452
486
  ```
453
487
 
454
488
  - **`PollManager`**: every poll by language, month or day, state (live,
@@ -461,39 +495,23 @@ const nav = useMemo(() => ({ pathname: location.pathname, search: location.searc
461
495
  <PollManager client={contentkit} basePath="/admin/polls" navigation={nav} languages={["en", "ja"]} />
462
496
  ```
463
497
 
464
- - **`PostManager`**: every post by language, draft state and text, deleted
465
- ones to restore; title, slug, language, excerpt, schedule, cover and body
466
- with inline images; draft or publish; the moderation state. Props:
467
- `languages`, `renderEditor` (`richTextEditor` from `/editor`, or the
468
- host's; a plain HTML field without it), `postHref`, `pageSize`.
469
- `usePostManagerAccess()`.
498
+ - **`ContentManager`**: the items of one `kind` the caller manages, by
499
+ state and text, deleted ones to restore; title, slug, description,
500
+ schedule, cover and body with inline images per language; publish or
501
+ unpublish; the moderation state. Props: `kind`, `languages`,
502
+ `renderEditor` (`richTextEditor` from `/editor`, or the host's; a plain
503
+ HTML field without it), `contentHref`, `pageSize`.
504
+ `useContentManagerAccess()`.
470
505
 
471
506
  ```tsx
472
507
  import { richTextEditor } from "@openrails/contentkit-ui/editor";
473
- <PostManager client={contentkit} basePath="/admin/posts" navigation={nav} renderEditor={richTextEditor} />
508
+ <ContentManager kind="blog" client={contentkit} basePath="/admin/blog" navigation={nav} renderEditor={richTextEditor} />
474
509
  ```
475
510
 
476
511
  `RichTextEditor` (`value`, `onChange`, `uploadImage`) is Tiptap: headings,
477
512
  emphasis, lists, quotes, links and images picked, pasted or dropped. Its
478
513
  HTML keeps to that schema, so bodies stay a small, predictable subset.
479
514
 
480
- - **`TaxonomyManager`**: the taxonomy per kind (`kinds`: `kind`, `label`,
481
- `relations` such as a character `member_of` a series, `cover` in a media
482
- kind). Each kind's nodes by name, state, language, minimum count and order
483
- at `basePath/{kind}`; a node at `basePath/{kind}/{id}`: slug, names and
484
- aliases per language, host fields (`renderFields`), relations with a
485
- typeahead (both directions), cover, counts and assigned contents
486
- (`renderContent`); create, delete, restore, and merge with a typed
487
- confirmation. Props: `kinds`, `languages`, `contentKind`, `renderFields`,
488
- `renderContent`, `pageSize`. `useTaxonomyManagerAccess()`. Host-owned
489
- fields stay in host tables.
490
-
491
- ```tsx
492
- <TaxonomyManager client={contentkit} basePath="/admin/taxonomy" navigation={nav} languages={["en", "ja"]}
493
- kinds={[{ kind: "tag", label: "Tags" }, { kind: "series", label: "Series", cover: { kind: "series" } },
494
- { kind: "character", label: "Characters", relations: [{ relation: "member_of", to: "series", label: "Series" }] }]} />
495
- ```
496
-
497
515
  - **`MediaItemPanel`**: one video item for its editors: encode state and
498
516
  progress, failures with their reason, replace the source, subtitle
499
517
  sidecars (language, label, forced), the poster from a frame or an image,
@@ -518,6 +536,16 @@ const nav = useMemo(() => ({ pathname: location.pathname, search: location.searc
518
536
  The per-item queue engine behind `useUploadQueue` is `ItemUploadQueue` in
519
537
  `/client`.
520
538
 
539
+ - **`GroupManager`**: groups per kind (tabs at `basePath/{kind}`): by
540
+ letter, order and language; a new group at `basePath/{kind}/new`; a group
541
+ at `basePath/{kind}/{id}` with `GroupEditor` and `GroupMembers`. Props:
542
+ `groupKinds` (default the model's site-owned ones), `kinds`, `labels`,
543
+ `contentKind`, `renderContent`, `pageSize`. `useGroupManagerAccess()`.
544
+
545
+ ```tsx
546
+ <GroupManager client={contentkit} basePath="/admin/groups" navigation={nav} labels={{ artist: "Artists" }} />
547
+ ```
548
+
521
549
  - **`UserCommentStanding`**: for a user page, the person's site-wide comment
522
550
  ban (ban, replace, lift) and their recent comments (delete, restore). Props:
523
551
  `user` (their id), `name`, `itemHref`, `pageSize`.
@@ -569,7 +597,7 @@ pnpm demo # the stack with seeded demo pages, until Ctrl-C
569
597
  ```
570
598
 
571
599
  The suites need Docker, Go and ffmpeg. `e2e/server` composes the real
572
- `contentkit.Runtime` (content, taxonomy, codes, media upload and read) with
600
+ ContentKit's engine (content, groups, codes, media upload and read) with
573
601
  AuthKit for identity over PGroonga PostgreSQL 18, MinIO, and media-worker and
574
602
  media-gateway built from this checkout (`e2e/compose.yaml`). Each run brings
575
603
  the stack up under its own project name and takes it down; the server exits
@@ -0,0 +1,53 @@
1
+ import { DiscoveryClient } from './discovery.js';
2
+ import { ExposureInput } from './generated/wire.js';
3
+ export interface BeaconOptions {
4
+ /** A signed-out visitor's session; default one per browser tab. */
5
+ session?: () => string | undefined;
6
+ /** How long events gather before they are sent, ms. Default 2000. */
7
+ delay?: number;
8
+ }
9
+ /** Where a click or a view came from: the list render it was shown in. */
10
+ export interface Attribution {
11
+ render_id?: string;
12
+ surface?: string;
13
+ /** 1-based position in that render. */
14
+ position?: number;
15
+ }
16
+ export interface ViewSession {
17
+ /** Reports the view so far: pages read, seconds watched or percent scrolled; resume is where to pick up. */
18
+ update(o: {
19
+ progress?: number;
20
+ duration_s?: number;
21
+ resume?: string;
22
+ }): void;
23
+ }
24
+ export declare const eventId: () => string;
25
+ /** A signed-out reader's session: one per browser tab, kept across reloads. */
26
+ export declare function readerSession(): string;
27
+ /**
28
+ * Batched, best-effort signals and exposures: gathered for `delay`, sent in
29
+ * one request each, flushed with keepalive when the page hides. A site
30
+ * without the signal plane (not_configured) turns them off.
31
+ */
32
+ export declare class Beacons {
33
+ private readonly discovery;
34
+ private signals;
35
+ private exposures;
36
+ private timer;
37
+ private off;
38
+ private readonly session;
39
+ private readonly delay;
40
+ constructor(discovery: DiscoveryClient, o?: BeaconOptions);
41
+ /** A click on a listed card. */
42
+ click(content: string, a?: Attribution & {
43
+ version_id?: string;
44
+ }): void;
45
+ /** A consumption session of a content (or one version): each update a revision of one event. */
46
+ view(content: string, version?: string, a?: Attribution): ViewSession;
47
+ /** A result list served, rendered or shown. */
48
+ exposure(e: ExposureInput): void;
49
+ /** Sends what has gathered now. */
50
+ flush(keepalive?: boolean): Promise<void>;
51
+ private signal;
52
+ private schedule;
53
+ }
@@ -0,0 +1,72 @@
1
+ import { toContentKitError as e } from "./errors.js";
2
+ //#region src/client/beacons.ts
3
+ var t = 100, n = "contentkit.session", r = () => typeof crypto < "u" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`, i;
4
+ function a() {
5
+ try {
6
+ let e = sessionStorage.getItem(n);
7
+ if (e) return e;
8
+ let t = r();
9
+ return sessionStorage.setItem(n, t), t;
10
+ } catch {
11
+ return i ??= r();
12
+ }
13
+ }
14
+ var o = class {
15
+ discovery;
16
+ signals = [];
17
+ exposures = [];
18
+ timer;
19
+ off = !1;
20
+ session;
21
+ delay;
22
+ constructor(e, t = {}) {
23
+ this.discovery = e, this.session = t.session ?? a, this.delay = t.delay ?? 2e3, typeof addEventListener == "function" && (addEventListener("pagehide", () => void this.flush(!0)), addEventListener("visibilitychange", () => {
24
+ typeof document < "u" && document.visibilityState === "hidden" && this.flush(!0);
25
+ }));
26
+ }
27
+ click(e, t = {}) {
28
+ this.signal({
29
+ content_id: e,
30
+ type: "click",
31
+ event_id: r(),
32
+ occurred_at: (/* @__PURE__ */ new Date()).toISOString(),
33
+ ...t
34
+ });
35
+ }
36
+ view(e, t, n = {}) {
37
+ let i = r(), a = 0;
38
+ return { update: (r) => this.signal({
39
+ content_id: e,
40
+ version_id: t,
41
+ type: "view",
42
+ event_id: i,
43
+ revision: ++a,
44
+ occurred_at: (/* @__PURE__ */ new Date()).toISOString(),
45
+ ...n,
46
+ ...r
47
+ }) };
48
+ }
49
+ exposure(e) {
50
+ !this.off && e.shown.length && (this.exposures.push(e), this.schedule());
51
+ }
52
+ async flush(n = !1) {
53
+ clearTimeout(this.timer), this.timer = void 0;
54
+ let r = this.signals.splice(0), i = this.exposures.splice(0);
55
+ if (this.off) return;
56
+ let a = this.session(), o = [];
57
+ for (let e = 0; e < r.length; e += t) o.push(this.discovery.signals(r.slice(e, e + t), a, { keepalive: n }));
58
+ for (let e = 0; e < i.length; e += t) o.push(this.discovery.exposures(i.slice(e, e + t), a, { keepalive: n }));
59
+ (await Promise.allSettled(o)).some((t) => t.status === "rejected" && e(t.reason).code === "not_configured") && (this.off = !0);
60
+ }
61
+ signal(e) {
62
+ this.off || (e.type === "view" && (this.signals = this.signals.filter((t) => t.event_id !== e.event_id)), this.signals.push(e), this.schedule());
63
+ }
64
+ schedule() {
65
+ if (this.signals.length + this.exposures.length >= t) return void this.flush();
66
+ this.timer ??= setTimeout(() => void this.flush(), this.delay);
67
+ }
68
+ };
69
+ //#endregion
70
+ export { o as Beacons, r as eventId, a as readerSession };
71
+
72
+ //# sourceMappingURL=beacons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacons.js","names":[],"sources":["../../src/client/beacons.ts"],"sourcesContent":["import type { DiscoveryClient } from \"./discovery.js\";\nimport { toContentKitError } from \"./errors.js\";\nimport type { ExposureInput, SignalInput } from \"./generated/wire.js\";\n\nexport interface BeaconOptions {\n /** A signed-out visitor's session; default one per browser tab. */\n session?: () => string | undefined;\n /** How long events gather before they are sent, ms. Default 2000. */\n delay?: number;\n}\n\n/** Where a click or a view came from: the list render it was shown in. */\nexport interface Attribution {\n render_id?: string;\n surface?: string;\n /** 1-based position in that render. */\n position?: number;\n}\n\nexport interface ViewSession {\n /** Reports the view so far: pages read, seconds watched or percent scrolled; resume is where to pick up. */\n update(o: { progress?: number; duration_s?: number; resume?: string }): void;\n}\n\nconst MAX = 100;\nconst SESSION_KEY = \"contentkit.session\";\nexport const eventId = () => (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`);\n\nlet unstored: string | undefined;\n\n/** A signed-out reader's session: one per browser tab, kept across reloads. */\nexport function readerSession(): string {\n try {\n const kept = sessionStorage.getItem(SESSION_KEY);\n if (kept) return kept;\n const made = eventId();\n sessionStorage.setItem(SESSION_KEY, made);\n return made;\n } catch {\n return (unstored ??= eventId());\n }\n}\n\n/**\n * Batched, best-effort signals and exposures: gathered for `delay`, sent in\n * one request each, flushed with keepalive when the page hides. A site\n * without the signal plane (not_configured) turns them off.\n */\nexport class Beacons {\n private signals: SignalInput[] = [];\n private exposures: ExposureInput[] = [];\n private timer: ReturnType<typeof setTimeout> | undefined;\n private off = false;\n private readonly session: () => string | undefined;\n private readonly delay: number;\n\n constructor(\n private readonly discovery: DiscoveryClient,\n o: BeaconOptions = {},\n ) {\n this.session = o.session ?? readerSession;\n this.delay = o.delay ?? 2000;\n if (typeof addEventListener === \"function\") {\n addEventListener(\"pagehide\", () => void this.flush(true));\n addEventListener(\"visibilitychange\", () => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") void this.flush(true);\n });\n }\n }\n\n /** A click on a listed card. */\n click(content: string, a: Attribution & { version_id?: string } = {}): void {\n this.signal({ content_id: content, type: \"click\", event_id: eventId(), occurred_at: new Date().toISOString(), ...a });\n }\n\n /** A consumption session of a content (or one version): each update a revision of one event. */\n view(content: string, version?: string, a: Attribution = {}): ViewSession {\n const event = eventId();\n let revision = 0;\n return {\n update: (o) => this.signal({ content_id: content, version_id: version, type: \"view\", event_id: event, revision: ++revision, occurred_at: new Date().toISOString(), ...a, ...o }),\n };\n }\n\n /** A result list served, rendered or shown. */\n exposure(e: ExposureInput): void {\n if (this.off || !e.shown.length) return;\n this.exposures.push(e);\n this.schedule();\n }\n\n /** Sends what has gathered now. */\n async flush(keepalive = false): Promise<void> {\n clearTimeout(this.timer);\n this.timer = undefined;\n const signals = this.signals.splice(0);\n const exposures = this.exposures.splice(0);\n if (this.off) return;\n const session = this.session();\n const sends: Promise<void>[] = [];\n for (let i = 0; i < signals.length; i += MAX) sends.push(this.discovery.signals(signals.slice(i, i + MAX), session, { keepalive }));\n for (let i = 0; i < exposures.length; i += MAX) sends.push(this.discovery.exposures(exposures.slice(i, i + MAX), session, { keepalive }));\n const done = await Promise.allSettled(sends);\n if (done.some((r) => r.status === \"rejected\" && toContentKitError(r.reason).code === \"not_configured\")) this.off = true;\n }\n\n private signal(s: SignalInput): void {\n if (this.off) return;\n // A view's later revision replaces its earlier one still waiting.\n if (s.type === \"view\") this.signals = this.signals.filter((x) => x.event_id !== s.event_id);\n this.signals.push(s);\n this.schedule();\n }\n\n private schedule(): void {\n if (this.signals.length + this.exposures.length >= MAX) return void this.flush();\n this.timer ??= setTimeout(() => void this.flush(), this.delay);\n }\n}\n"],"mappings":";;AAwBA,IAAM,IAAM,KACN,IAAc,sBACP,UAAiB,OAAO,SAAW,OAAe,gBAAgB,SAAS,OAAO,WAAW,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,KAE1K;AAGJ,SAAgB,IAAwB;CACtC,IAAI;EACF,IAAM,IAAO,eAAe,QAAQ,CAAW;EAC/C,IAAI,GAAM,OAAO;EACjB,IAAM,IAAO,EAAQ;EAErB,OADA,eAAe,QAAQ,GAAa,CAAI,GACjC;CACT,QAAQ;EACN,OAAQ,MAAa,EAAQ;CAC/B;AACF;AAOA,IAAa,IAAb,MAAqB;CASA;CARnB,UAAiC,CAAC;CAClC,YAAqC,CAAC;CACtC;CACA,MAAc;CACd;CACA;CAEA,YACE,GACA,IAAmB,CAAC,GACpB;EAGA,AALiB,KAAA,YAAA,GAGjB,KAAK,UAAU,EAAE,WAAW,GAC5B,KAAK,QAAQ,EAAE,SAAS,KACpB,OAAO,oBAAqB,eAC9B,iBAAiB,kBAAkB,KAAK,KAAK,MAAM,EAAI,CAAC,GACxD,iBAAiB,0BAA0B;GACzC,AAAI,OAAO,WAAa,OAAe,SAAS,oBAAoB,YAAU,KAAU,MAAM,EAAI;EACpG,CAAC;CAEL;CAGA,MAAM,GAAiB,IAA2C,CAAC,GAAS;EAC1E,KAAK,OAAO;GAAE,YAAY;GAAS,MAAM;GAAS,UAAU,EAAQ;GAAG,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;GAAG,GAAG;EAAE,CAAC;CACtH;CAGA,KAAK,GAAiB,GAAkB,IAAiB,CAAC,GAAgB;EACxE,IAAM,IAAQ,EAAQ,GAClB,IAAW;EACf,OAAO,EACL,SAAS,MAAM,KAAK,OAAO;GAAE,YAAY;GAAS,YAAY;GAAS,MAAM;GAAQ,UAAU;GAAO,UAAU,EAAE;GAAU,8BAAa,IAAI,KAAK,EAAA,CAAE,YAAY;GAAG,GAAG;GAAG,GAAG;EAAE,CAAC,EACjL;CACF;CAGA,SAAS,GAAwB;EAC3B,MAAK,OAAQ,EAAE,MAAM,WACzB,KAAK,UAAU,KAAK,CAAC,GACrB,KAAK,SAAS;CAChB;CAGA,MAAM,MAAM,IAAY,IAAsB;EAE5C,AADA,aAAa,KAAK,KAAK,GACvB,KAAK,QAAQ,KAAA;EACb,IAAM,IAAU,KAAK,QAAQ,OAAO,CAAC,GAC/B,IAAY,KAAK,UAAU,OAAO,CAAC;EACzC,IAAI,KAAK,KAAK;EACd,IAAM,IAAU,KAAK,QAAQ,GACvB,IAAyB,CAAC;EAChC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAQ,QAAQ,KAAK,GAAK,EAAM,KAAK,KAAK,UAAU,QAAQ,EAAQ,MAAM,GAAG,IAAI,CAAG,GAAG,GAAS,EAAE,aAAU,CAAC,CAAC;EAClI,KAAK,IAAI,IAAI,GAAG,IAAI,EAAU,QAAQ,KAAK,GAAK,EAAM,KAAK,KAAK,UAAU,UAAU,EAAU,MAAM,GAAG,IAAI,CAAG,GAAG,GAAS,EAAE,aAAU,CAAC,CAAC;EAExI,CAAI,MADe,QAAQ,WAAW,CAAK,EAAA,CAClC,MAAM,MAAM,EAAE,WAAW,cAAc,EAAkB,EAAE,MAAM,CAAC,CAAC,SAAS,gBAAgB,MAAG,KAAK,MAAM;CACrH;CAEA,OAAe,GAAsB;EAC/B,KAAK,QAEL,EAAE,SAAS,WAAQ,KAAK,UAAU,KAAK,QAAQ,QAAQ,MAAM,EAAE,aAAa,EAAE,QAAQ,IAC1F,KAAK,QAAQ,KAAK,CAAC,GACnB,KAAK,SAAS;CAChB;CAEA,WAAyB;EACvB,IAAI,KAAK,QAAQ,SAAS,KAAK,UAAU,UAAU,GAAK,OAAO,KAAK,KAAK,MAAM;EAC/E,KAAK,UAAU,iBAAiB,KAAK,KAAK,MAAM,GAAG,KAAK,KAAK;CAC/D;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { Totals } from './generated/wire.js';
2
2
  import { Http } from './http.js';
3
- /** GET /catalog/totals filters: kinds to total (every listed kind when empty), languages to break them down by (every counted one when empty). */
3
+ /** GET /catalog/totals filters: kinds to total (every kind when empty), languages to break them down by (every counted one when empty). */
4
4
  export interface CatalogTotalsQuery {
5
5
  kinds?: readonly string[];
6
6
  languages?: readonly string[];
@@ -9,6 +9,6 @@ export interface CatalogTotalsQuery {
9
9
  export declare class CatalogClient {
10
10
  private readonly http;
11
11
  constructor(http: Http);
12
- /** Works per kind, in all and per language: how many anyone opens and how many each tier's holders open. */
12
+ /** Content items per kind, in all and per language: how many anyone's listing shows and opens, and how many each tier's holders open. */
13
13
  totals(q?: CatalogTotalsQuery, signal?: AbortSignal): Promise<Totals>;
14
14
  }
@@ -6,7 +6,7 @@ var t = class {
6
6
  this.http = e;
7
7
  }
8
8
  totals(t = {}, n) {
9
- return e(this.http, "GET", "/totals", {
9
+ return e(this.http, "GET", "/catalog/totals", {
10
10
  query: {
11
11
  kinds: t.kinds?.join(","),
12
12
  languages: t.languages?.join(",")
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.js","names":[],"sources":["../../src/client/catalog.ts"],"sourcesContent":["import type { Totals } from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call } from \"./route.js\";\n\n/** GET /catalog/totals filters: kinds to total (every listed kind when empty), languages to break them down by (every counted one when empty). */\nexport interface CatalogTotalsQuery {\n kinds?: readonly string[];\n languages?: readonly string[];\n}\n\n/** The public catalog. */\nexport class CatalogClient {\n constructor(private readonly http: Http) {}\n\n /** Works per kind, in all and per language: how many anyone opens and how many each tier's holders open. */\n totals(q: CatalogTotalsQuery = {}, signal?: AbortSignal): Promise<Totals> {\n return call(this.http, \"GET\", \"/totals\", { query: { kinds: q.kinds?.join(\",\"), languages: q.languages?.join(\",\") }, signal });\n }\n}\n"],"mappings":";;AAWA,IAAa,IAAb,MAA2B;CACI;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,OAAO,IAAwB,CAAC,GAAG,GAAuC;EACxE,OAAO,EAAK,KAAK,MAAM,OAAO,WAAW;GAAE,OAAO;IAAE,OAAO,EAAE,OAAO,KAAK,GAAG;IAAG,WAAW,EAAE,WAAW,KAAK,GAAG;GAAE;GAAG;EAAO,CAAC;CAC9H;AACF"}
1
+ {"version":3,"file":"catalog.js","names":[],"sources":["../../src/client/catalog.ts"],"sourcesContent":["import type { Totals } from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call } from \"./route.js\";\n\n/** GET /catalog/totals filters: kinds to total (every kind when empty), languages to break them down by (every counted one when empty). */\nexport interface CatalogTotalsQuery {\n kinds?: readonly string[];\n languages?: readonly string[];\n}\n\n/** The public catalog. */\nexport class CatalogClient {\n constructor(private readonly http: Http) {}\n\n /** Content items per kind, in all and per language: how many anyone's listing shows and opens, and how many each tier's holders open. */\n totals(q: CatalogTotalsQuery = {}, signal?: AbortSignal): Promise<Totals> {\n return call(this.http, \"GET\", \"/catalog/totals\", { query: { kinds: q.kinds?.join(\",\"), languages: q.languages?.join(\",\") }, signal });\n }\n}\n"],"mappings":";;AAWA,IAAa,IAAb,MAA2B;CACI;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,OAAO,IAAwB,CAAC,GAAG,GAAuC;EACxE,OAAO,EAAK,KAAK,MAAM,OAAO,mBAAmB;GAAE,OAAO;IAAE,OAAO,EAAE,OAAO,KAAK,GAAG;IAAG,WAAW,EAAE,WAAW,KAAK,GAAG;GAAE;GAAG;EAAO,CAAC;CACtI;AACF"}
@@ -1,21 +1,24 @@
1
1
  import { CatalogClient } from './catalog.js';
2
2
  import { CodesClient } from './codes.js';
3
+ import { ContentsClient } from './contents.js';
3
4
  import { BansClient } from './content/bans.js';
4
5
  import { CommentsClient } from './content/comments.js';
5
6
  import { FavoritesClient } from './content/favorites.js';
6
7
  import { ModerationClient } from './content/moderation.js';
7
8
  import { PollsClient } from './content/polls.js';
8
- import { PostsClient } from './content/posts.js';
9
9
  import { ReactionsClient } from './content/reactions.js';
10
- import { Config, RefBody } from './generated/wire.js';
10
+ import { Batch, Config, ContentModel, RefBody, Result } from './generated/wire.js';
11
+ import { DiscoveryClient } from './discovery.js';
12
+ import { GroupsClient } from './groups.js';
13
+ import { OffersClient } from './offers.js';
14
+ import { VersionsClient } from './versions.js';
11
15
  import { ContentKitChange, ContentKitModule, HttpOptions } from './http.js';
12
16
  import { MediaClient, ContentFolders, MediaOptions } from './media/client.js';
13
17
  import { NotificationsClient } from './notifications.js';
14
- import { TaxonomyClient } from './taxonomy.js';
15
18
  export interface ContentKitClientOptions extends HttpOptions {
16
19
  /** Upload tuning (transport, part concurrency, retries). */
17
20
  media?: MediaOptions;
18
- /** The media kinds of post and poll folders, when the host renamed them (content.Media). */
21
+ /** The media kind of poll folders, when the host renamed it (content.Media). */
19
22
  folders?: ContentFolders;
20
23
  }
21
24
  /**
@@ -25,8 +28,10 @@ export interface ContentKitClientOptions extends HttpOptions {
25
28
  export interface ContentKitClient {
26
29
  /** Uploads, multipart, commits, reads, frames, HLS bases, hls.js `xhrSetup` and inline images. */
27
30
  readonly media: MediaClient;
28
- /** Published posts, staff CRUD, reactions, cover and body images. */
29
- readonly posts: PostsClient;
31
+ /** Content items: reads, and writes for their creators, owner-group editors and staff. */
32
+ readonly contents: ContentsClient;
33
+ /** The catalog's totals by kind, language and audience. */
34
+ readonly catalog: CatalogClient;
30
35
  /** Threads, replies, edits, tombstones, reactions, standing, the latest feed, moderation. */
31
36
  readonly comments: CommentsClient;
32
37
  /** Likes and dislikes of an item. */
@@ -37,18 +42,26 @@ export interface ContentKitClient {
37
42
  readonly polls: PollsClient;
38
43
  /** Comment bans, owner and global. */
39
44
  readonly bans: BansClient;
40
- /** The review queue of held comments and posts. */
45
+ /** The review queue of held comments and content. */
41
46
  readonly moderation: ModerationClient;
42
- /** The taxonomy admin API. */
43
- readonly taxonomy: TaxonomyClient;
47
+ /** Groups: listings, pages, staff writes and memberships. */
48
+ readonly groups: GroupsClient;
49
+ /** Lists, search, typeahead, rails, release dates, content filters, history, signals and exposures. */
50
+ readonly discovery: DiscoveryClient;
51
+ /** Content versions: reader plans, reader metadata, forks and downloads. */
52
+ readonly versions: VersionsClient;
53
+ /** Offers on contents, versions and groups; group joins; the reader's library. */
54
+ readonly offers: OffersClient;
44
55
  /** Content-code lookup. */
45
56
  readonly codes: CodesClient;
46
- /** The public catalog: listing totals by kind and audience. */
47
- readonly catalog: CatalogClient;
48
57
  /** The signed-in person's native and imported notifications. */
49
58
  readonly notifications: NotificationsClient;
50
59
  /** What the content module allows: which interactions signed-out visitors may make. */
51
60
  config(signal?: AbortSignal): Promise<Config>;
61
+ /** The site's content model: languages, tiers, kinds, group kinds and content filters. */
62
+ kinds(signal?: AbortSignal): Promise<ContentModel>;
63
+ /** Staff bulk import (root:import:write): groups, contents, memberships, offers and aliases, idempotent per source. */
64
+ import(batch: Batch): Promise<Result>;
52
65
  /** A module's base URL as configured (no trailing slash). */
53
66
  url(module: ContentKitModule, ref?: RefBody): string;
54
67
  /** Cache context only, not authentication. The React provider keeps this in sync. */
@@ -2,39 +2,47 @@ import { call as e } from "./route.js";
2
2
  import { MediaClient as t } from "./media/client.js";
3
3
  import { CatalogClient as n } from "./catalog.js";
4
4
  import { CodesClient as r } from "./codes.js";
5
- import { BansClient as i } from "./content/bans.js";
6
- import { CommentsClient as a } from "./content/comments.js";
7
- import { FavoritesClient as o } from "./content/favorites.js";
8
- import { ModerationClient as s } from "./content/moderation.js";
9
- import { PollsClient as c } from "./content/polls.js";
10
- import { PostsClient as l } from "./content/posts.js";
11
- import { ReactionsClient as u } from "./content/reactions.js";
12
- import { Http as d } from "./http.js";
13
- import { NotificationsClient as f } from "./notifications.js";
14
- import { TaxonomyClient as p } from "./taxonomy.js";
5
+ import { ContentsClient as i } from "./contents.js";
6
+ import { BansClient as a } from "./content/bans.js";
7
+ import { CommentsClient as o } from "./content/comments.js";
8
+ import { DiscoveryClient as s } from "./discovery.js";
9
+ import { FavoritesClient as c } from "./content/favorites.js";
10
+ import { ModerationClient as l } from "./content/moderation.js";
11
+ import { PollsClient as u } from "./content/polls.js";
12
+ import { ReactionsClient as d } from "./content/reactions.js";
13
+ import { GroupsClient as f } from "./groups.js";
14
+ import { OffersClient as p } from "./offers.js";
15
+ import { VersionsClient as m } from "./versions.js";
16
+ import { Http as h } from "./http.js";
17
+ import { NotificationsClient as g } from "./notifications.js";
15
18
  //#region src/client/client.ts
16
- function m(m) {
17
- let { media: h, folders: g, ..._ } = m, v = new d(_), y = new t(v, h, g);
19
+ function _(_) {
20
+ let { media: v, folders: y, ...b } = _, x = new h(b), S = new t(x, v, y);
18
21
  return {
19
- media: y,
20
- posts: new l(v, y),
21
- comments: new a(v),
22
- reactions: new u(v),
23
- favorites: new o(v),
24
- polls: new c(v, y),
25
- bans: new i(v),
26
- moderation: new s(v),
27
- taxonomy: new p(v),
28
- codes: new r(v),
29
- catalog: new n(v),
30
- notifications: new f(v),
31
- config: (t) => e(v, "GET", "/config", { signal: t }),
32
- url: (e, t) => v.mount(e, t),
33
- setReadScope: (e) => v.setReadScope(e),
34
- subscribe: (e) => v.subscribe(e)
22
+ media: S,
23
+ contents: new i(x),
24
+ catalog: new n(x),
25
+ comments: new o(x),
26
+ reactions: new d(x),
27
+ favorites: new c(x),
28
+ polls: new u(x, S),
29
+ bans: new a(x),
30
+ moderation: new l(x),
31
+ groups: new f(x, S),
32
+ discovery: new s(x),
33
+ versions: new m(x),
34
+ offers: new p(x),
35
+ codes: new r(x),
36
+ notifications: new g(x),
37
+ config: (t) => e(x, "GET", "/config", { signal: t }),
38
+ kinds: (t) => e(x, "GET", "/kinds", { signal: t }),
39
+ import: (t) => e(x, "POST", "/import", { body: t }),
40
+ url: (e, t) => x.mount(e, t),
41
+ setReadScope: (e) => x.setReadScope(e),
42
+ subscribe: (e) => x.subscribe(e)
35
43
  };
36
44
  }
37
45
  //#endregion
38
- export { m as createContentKitClient };
46
+ export { _ as createContentKitClient };
39
47
 
40
48
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../src/client/client.ts"],"sourcesContent":["import { CatalogClient } from \"./catalog.js\";\nimport { CodesClient } from \"./codes.js\";\nimport { BansClient } from \"./content/bans.js\";\nimport { CommentsClient } from \"./content/comments.js\";\nimport { FavoritesClient } from \"./content/favorites.js\";\nimport { ModerationClient } from \"./content/moderation.js\";\nimport { PollsClient } from \"./content/polls.js\";\nimport { PostsClient } from \"./content/posts.js\";\nimport { ReactionsClient } from \"./content/reactions.js\";\nimport type { Config, RefBody } from \"./generated/wire.js\";\nimport { Http, type ContentKitChange, type ContentKitModule, type HttpOptions } from \"./http.js\";\nimport { MediaClient, type ContentFolders, type MediaOptions } from \"./media/client.js\";\nimport { call } from \"./route.js\";\nimport { NotificationsClient } from \"./notifications.js\";\nimport { TaxonomyClient } from \"./taxonomy.js\";\n\nexport interface ContentKitClientOptions extends HttpOptions {\n /** Upload tuning (transport, part concurrency, retries). */\n media?: MediaOptions;\n /** The media kinds of post and poll folders, when the host renamed them (content.Media). */\n folders?: ContentFolders;\n}\n\n/**\n * One ContentKit deployment as the browser sees it. Each module is a property\n * over one shared transport (mounts, auth, language, errors, change events).\n */\nexport interface ContentKitClient {\n /** Uploads, multipart, commits, reads, frames, HLS bases, hls.js `xhrSetup` and inline images. */\n readonly media: MediaClient;\n /** Published posts, staff CRUD, reactions, cover and body images. */\n readonly posts: PostsClient;\n /** Threads, replies, edits, tombstones, reactions, standing, the latest feed, moderation. */\n readonly comments: CommentsClient;\n /** Likes and dislikes of an item. */\n readonly reactions: ReactionsClient;\n /** The signed-in caller's favorites. */\n readonly favorites: FavoritesClient;\n /** Polls: votes, free-text answers and the staff editor. */\n readonly polls: PollsClient;\n /** Comment bans, owner and global. */\n readonly bans: BansClient;\n /** The review queue of held comments and posts. */\n readonly moderation: ModerationClient;\n /** The taxonomy admin API. */\n readonly taxonomy: TaxonomyClient;\n /** Content-code lookup. */\n readonly codes: CodesClient;\n /** The public catalog: listing totals by kind and audience. */\n readonly catalog: CatalogClient;\n /** The signed-in person's native and imported notifications. */\n readonly notifications: NotificationsClient;\n\n /** What the content module allows: which interactions signed-out visitors may make. */\n config(signal?: AbortSignal): Promise<Config>;\n /** A module's base URL as configured (no trailing slash). */\n url(module: ContentKitModule, ref?: RefBody): string;\n /** Cache context only, not authentication. The React provider keeps this in sync. */\n setReadScope(scope: string): void;\n /** Every successful mutation; a null scope means its data predates a context change. */\n subscribe(listener: (change: ContentKitChange, scope: string | null) => void): () => void;\n}\n\nexport function createContentKitClient(options: ContentKitClientOptions): ContentKitClient {\n const { media: mediaOptions, folders, ...http } = options;\n const h = new Http(http);\n const media = new MediaClient(h, mediaOptions, folders);\n return {\n media,\n posts: new PostsClient(h, media),\n comments: new CommentsClient(h),\n reactions: new ReactionsClient(h),\n favorites: new FavoritesClient(h),\n polls: new PollsClient(h, media),\n bans: new BansClient(h),\n moderation: new ModerationClient(h),\n taxonomy: new TaxonomyClient(h),\n codes: new CodesClient(h),\n catalog: new CatalogClient(h),\n notifications: new NotificationsClient(h),\n config: (signal) => call(h, \"GET\", \"/config\", { signal }),\n url: (module, ref) => h.mount(module, ref),\n setReadScope: (scope) => h.setReadScope(scope),\n subscribe: (listener) => h.subscribe(listener),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA+DA,SAAgB,EAAuB,GAAoD;CACzF,IAAM,EAAE,OAAO,GAAc,YAAS,GAAG,MAAS,GAC5C,IAAI,IAAI,EAAK,CAAI,GACjB,IAAQ,IAAI,EAAY,GAAG,GAAc,CAAO;CACtD,OAAO;EACL;EACA,OAAO,IAAI,EAAY,GAAG,CAAK;EAC/B,UAAU,IAAI,EAAe,CAAC;EAC9B,WAAW,IAAI,EAAgB,CAAC;EAChC,WAAW,IAAI,EAAgB,CAAC;EAChC,OAAO,IAAI,EAAY,GAAG,CAAK;EAC/B,MAAM,IAAI,EAAW,CAAC;EACtB,YAAY,IAAI,EAAiB,CAAC;EAClC,UAAU,IAAI,EAAe,CAAC;EAC9B,OAAO,IAAI,EAAY,CAAC;EACxB,SAAS,IAAI,EAAc,CAAC;EAC5B,eAAe,IAAI,EAAoB,CAAC;EACxC,SAAS,MAAW,EAAK,GAAG,OAAO,WAAW,EAAE,UAAO,CAAC;EACxD,MAAM,GAAQ,MAAQ,EAAE,MAAM,GAAQ,CAAG;EACzC,eAAe,MAAU,EAAE,aAAa,CAAK;EAC7C,YAAY,MAAa,EAAE,UAAU,CAAQ;CAC/C;AACF"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../src/client/client.ts"],"sourcesContent":["import { CatalogClient } from \"./catalog.js\";\nimport { CodesClient } from \"./codes.js\";\nimport { ContentsClient } from \"./contents.js\";\nimport { BansClient } from \"./content/bans.js\";\nimport { CommentsClient } from \"./content/comments.js\";\nimport { FavoritesClient } from \"./content/favorites.js\";\nimport { ModerationClient } from \"./content/moderation.js\";\nimport { PollsClient } from \"./content/polls.js\";\nimport { ReactionsClient } from \"./content/reactions.js\";\nimport type { Batch, Config, ContentModel, RefBody, Result } from \"./generated/wire.js\";\nimport { DiscoveryClient } from \"./discovery.js\";\nimport { GroupsClient } from \"./groups.js\";\nimport { OffersClient } from \"./offers.js\";\nimport { VersionsClient } from \"./versions.js\";\nimport { Http, type ContentKitChange, type ContentKitModule, type HttpOptions } from \"./http.js\";\nimport { MediaClient, type ContentFolders, type MediaOptions } from \"./media/client.js\";\nimport { call } from \"./route.js\";\nimport { NotificationsClient } from \"./notifications.js\";\n\nexport interface ContentKitClientOptions extends HttpOptions {\n /** Upload tuning (transport, part concurrency, retries). */\n media?: MediaOptions;\n /** The media kind of poll folders, when the host renamed it (content.Media). */\n folders?: ContentFolders;\n}\n\n/**\n * One ContentKit deployment as the browser sees it. Each module is a property\n * over one shared transport (mounts, auth, language, errors, change events).\n */\nexport interface ContentKitClient {\n /** Uploads, multipart, commits, reads, frames, HLS bases, hls.js `xhrSetup` and inline images. */\n readonly media: MediaClient;\n /** Content items: reads, and writes for their creators, owner-group editors and staff. */\n readonly contents: ContentsClient;\n /** The catalog's totals by kind, language and audience. */\n readonly catalog: CatalogClient;\n /** Threads, replies, edits, tombstones, reactions, standing, the latest feed, moderation. */\n readonly comments: CommentsClient;\n /** Likes and dislikes of an item. */\n readonly reactions: ReactionsClient;\n /** The signed-in caller's favorites. */\n readonly favorites: FavoritesClient;\n /** Polls: votes, free-text answers and the staff editor. */\n readonly polls: PollsClient;\n /** Comment bans, owner and global. */\n readonly bans: BansClient;\n /** The review queue of held comments and content. */\n readonly moderation: ModerationClient;\n /** Groups: listings, pages, staff writes and memberships. */\n readonly groups: GroupsClient;\n /** Lists, search, typeahead, rails, release dates, content filters, history, signals and exposures. */\n readonly discovery: DiscoveryClient;\n /** Content versions: reader plans, reader metadata, forks and downloads. */\n readonly versions: VersionsClient;\n /** Offers on contents, versions and groups; group joins; the reader's library. */\n readonly offers: OffersClient;\n /** Content-code lookup. */\n readonly codes: CodesClient;\n /** The signed-in person's native and imported notifications. */\n readonly notifications: NotificationsClient;\n\n /** What the content module allows: which interactions signed-out visitors may make. */\n config(signal?: AbortSignal): Promise<Config>;\n /** The site's content model: languages, tiers, kinds, group kinds and content filters. */\n kinds(signal?: AbortSignal): Promise<ContentModel>;\n /** Staff bulk import (root:import:write): groups, contents, memberships, offers and aliases, idempotent per source. */\n import(batch: Batch): Promise<Result>;\n /** A module's base URL as configured (no trailing slash). */\n url(module: ContentKitModule, ref?: RefBody): string;\n /** Cache context only, not authentication. The React provider keeps this in sync. */\n setReadScope(scope: string): void;\n /** Every successful mutation; a null scope means its data predates a context change. */\n subscribe(listener: (change: ContentKitChange, scope: string | null) => void): () => void;\n}\n\nexport function createContentKitClient(options: ContentKitClientOptions): ContentKitClient {\n const { media: mediaOptions, folders, ...http } = options;\n const h = new Http(http);\n const media = new MediaClient(h, mediaOptions, folders);\n return {\n media,\n contents: new ContentsClient(h),\n catalog: new CatalogClient(h),\n comments: new CommentsClient(h),\n reactions: new ReactionsClient(h),\n favorites: new FavoritesClient(h),\n polls: new PollsClient(h, media),\n bans: new BansClient(h),\n moderation: new ModerationClient(h),\n groups: new GroupsClient(h, media),\n discovery: new DiscoveryClient(h),\n versions: new VersionsClient(h),\n offers: new OffersClient(h),\n codes: new CodesClient(h),\n notifications: new NotificationsClient(h),\n config: (signal) => call(h, \"GET\", \"/config\", { signal }),\n kinds: (signal) => call(h, \"GET\", \"/kinds\", { signal }),\n import: (batch) => call(h, \"POST\", \"/import\", { body: batch }),\n url: (module, ref) => h.mount(module, ref),\n setReadScope: (scope) => h.setReadScope(scope),\n subscribe: (listener) => h.subscribe(listener),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA4EA,SAAgB,EAAuB,GAAoD;CACzF,IAAM,EAAE,OAAO,GAAc,YAAS,GAAG,MAAS,GAC5C,IAAI,IAAI,EAAK,CAAI,GACjB,IAAQ,IAAI,EAAY,GAAG,GAAc,CAAO;CACtD,OAAO;EACL;EACA,UAAU,IAAI,EAAe,CAAC;EAC9B,SAAS,IAAI,EAAc,CAAC;EAC5B,UAAU,IAAI,EAAe,CAAC;EAC9B,WAAW,IAAI,EAAgB,CAAC;EAChC,WAAW,IAAI,EAAgB,CAAC;EAChC,OAAO,IAAI,EAAY,GAAG,CAAK;EAC/B,MAAM,IAAI,EAAW,CAAC;EACtB,YAAY,IAAI,EAAiB,CAAC;EAClC,QAAQ,IAAI,EAAa,GAAG,CAAK;EACjC,WAAW,IAAI,EAAgB,CAAC;EAChC,UAAU,IAAI,EAAe,CAAC;EAC9B,QAAQ,IAAI,EAAa,CAAC;EAC1B,OAAO,IAAI,EAAY,CAAC;EACxB,eAAe,IAAI,EAAoB,CAAC;EACxC,SAAS,MAAW,EAAK,GAAG,OAAO,WAAW,EAAE,UAAO,CAAC;EACxD,QAAQ,MAAW,EAAK,GAAG,OAAO,UAAU,EAAE,UAAO,CAAC;EACtD,SAAS,MAAU,EAAK,GAAG,QAAQ,WAAW,EAAE,MAAM,EAAM,CAAC;EAC7D,MAAM,GAAQ,MAAQ,EAAE,MAAM,GAAQ,CAAG;EACzC,eAAe,MAAU,EAAE,aAAa,CAAK;EAC7C,YAAY,MAAa,EAAE,UAAU,CAAQ;CAC/C;AACF"}