@openrails/contentkit-ui 0.70.0 → 0.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +98 -10
  2. package/dist/client/content/bans.d.ts +2 -0
  3. package/dist/client/content/bans.js +24 -12
  4. package/dist/client/content/bans.js.map +1 -1
  5. package/dist/client/content/comments.d.ts +4 -2
  6. package/dist/client/content/comments.js.map +1 -1
  7. package/dist/client/content/polls.d.ts +10 -3
  8. package/dist/client/content/polls.js.map +1 -1
  9. package/dist/client/content/posts.d.ts +4 -4
  10. package/dist/client/content/posts.js.map +1 -1
  11. package/dist/client/generated/error-codes.js +2 -2
  12. package/dist/client/generated/error-codes.js.map +1 -1
  13. package/dist/client/generated/routes.d.ts +146 -120
  14. package/dist/client/generated/routes.js +245 -117
  15. package/dist/client/generated/routes.js.map +1 -1
  16. package/dist/client/generated/wire.d.ts +5 -0
  17. package/dist/client/generated/wire.js.map +1 -1
  18. package/dist/client/index.d.ts +2 -2
  19. package/dist/client/taxonomy.d.ts +6 -2
  20. package/dist/client/taxonomy.js +7 -0
  21. package/dist/client/taxonomy.js.map +1 -1
  22. package/dist/components/admin/poll-manager.d.ts +28 -0
  23. package/dist/components/admin/poll-manager.js +297 -0
  24. package/dist/components/admin/poll-manager.js.map +1 -0
  25. package/dist/components/admin/post-manager.d.ts +45 -0
  26. package/dist/components/admin/post-manager.js +602 -0
  27. package/dist/components/admin/post-manager.js.map +1 -0
  28. package/dist/components/admin/shell.d.ts +73 -0
  29. package/dist/components/admin/shell.js +148 -0
  30. package/dist/components/admin/shell.js.map +1 -0
  31. package/dist/components/admin/taxonomy-manager.d.ts +54 -0
  32. package/dist/components/admin/taxonomy-manager.js +976 -0
  33. package/dist/components/admin/taxonomy-manager.js.map +1 -0
  34. package/dist/components/admin/user-comment-standing.d.ts +25 -0
  35. package/dist/components/admin/user-comment-standing.js +229 -0
  36. package/dist/components/admin/user-comment-standing.js.map +1 -0
  37. package/dist/components/social/bans.d.ts +5 -2
  38. package/dist/components/social/bans.js +132 -134
  39. package/dist/components/social/bans.js.map +1 -1
  40. package/dist/components/social/index.d.ts +1 -1
  41. package/dist/components/social/moderation.d.ts +11 -13
  42. package/dist/components/social/moderation.js +299 -218
  43. package/dist/components/social/moderation.js.map +1 -1
  44. package/dist/components/social/parts.d.ts +3 -2
  45. package/dist/components/social/parts.js +74 -48
  46. package/dist/components/social/parts.js.map +1 -1
  47. package/dist/components/social/poll-editor.d.ts +1 -1
  48. package/dist/components/social/poll-editor.js +1 -0
  49. package/dist/components/social/poll-editor.js.map +1 -1
  50. package/dist/editor/index.d.ts +1 -0
  51. package/dist/editor/rich-text-editor.d.ts +13 -0
  52. package/dist/editor/rich-text-editor.js +228 -0
  53. package/dist/editor/rich-text-editor.js.map +1 -0
  54. package/dist/editor.js +2 -0
  55. package/dist/index.d.ts +6 -0
  56. package/dist/index.js +13 -8
  57. package/dist/locales/admin/de.d.ts +2 -0
  58. package/dist/locales/admin/de.js +176 -0
  59. package/dist/locales/admin/de.js.map +1 -0
  60. package/dist/locales/admin/en.d.ts +171 -0
  61. package/dist/locales/admin/en.js +176 -0
  62. package/dist/locales/admin/en.js.map +1 -0
  63. package/dist/locales/admin/es.d.ts +2 -0
  64. package/dist/locales/admin/es.js +176 -0
  65. package/dist/locales/admin/es.js.map +1 -0
  66. package/dist/locales/admin/ja.d.ts +2 -0
  67. package/dist/locales/admin/ja.js +176 -0
  68. package/dist/locales/admin/ja.js.map +1 -0
  69. package/dist/locales/admin/ko.d.ts +2 -0
  70. package/dist/locales/admin/ko.js +176 -0
  71. package/dist/locales/admin/ko.js.map +1 -0
  72. package/dist/locales/admin/zh.d.ts +2 -0
  73. package/dist/locales/admin/zh.js +176 -0
  74. package/dist/locales/admin/zh.js.map +1 -0
  75. package/dist/locales/de.js +5 -3
  76. package/dist/locales/de.js.map +1 -1
  77. package/dist/locales/en.d.ts +176 -0
  78. package/dist/locales/en.js +5 -3
  79. package/dist/locales/en.js.map +1 -1
  80. package/dist/locales/es.js +5 -3
  81. package/dist/locales/es.js.map +1 -1
  82. package/dist/locales/ja.js +5 -3
  83. package/dist/locales/ja.js.map +1 -1
  84. package/dist/locales/ko.js +5 -3
  85. package/dist/locales/ko.js.map +1 -1
  86. package/dist/locales/social/de.js +8 -1
  87. package/dist/locales/social/de.js.map +1 -1
  88. package/dist/locales/social/en.d.ts +7 -0
  89. package/dist/locales/social/en.js +8 -1
  90. package/dist/locales/social/en.js.map +1 -1
  91. package/dist/locales/social/es.js +8 -1
  92. package/dist/locales/social/es.js.map +1 -1
  93. package/dist/locales/social/ja.js +8 -1
  94. package/dist/locales/social/ja.js.map +1 -1
  95. package/dist/locales/social/ko.js +8 -1
  96. package/dist/locales/social/ko.js.map +1 -1
  97. package/dist/locales/social/zh.js +8 -1
  98. package/dist/locales/social/zh.js.map +1 -1
  99. package/dist/locales/zh.js +5 -3
  100. package/dist/locales/zh.js.map +1 -1
  101. package/dist/provider.d.ts +2 -0
  102. package/dist/provider.js +23 -19
  103. package/dist/provider.js.map +1 -1
  104. package/dist/react/admin.d.ts +55 -0
  105. package/dist/react/admin.js +97 -0
  106. package/dist/react/admin.js.map +1 -0
  107. package/dist/react/comments.d.ts +18 -2
  108. package/dist/react/comments.js +24 -8
  109. package/dist/react/comments.js.map +1 -1
  110. package/dist/react/context.d.ts +7 -1
  111. package/dist/react/context.js +6 -1
  112. package/dist/react/context.js.map +1 -1
  113. package/dist/react/index.d.ts +4 -3
  114. package/dist/react/polls.d.ts +2 -2
  115. package/dist/react/polls.js +88 -87
  116. package/dist/react/polls.js.map +1 -1
  117. package/dist/react/posts.d.ts +3 -3
  118. package/dist/react/posts.js +123 -122
  119. package/dist/react/posts.js.map +1 -1
  120. package/dist/react/resources.js +3 -0
  121. package/dist/react/resources.js.map +1 -1
  122. package/dist/react/use-resource.d.ts +16 -0
  123. package/dist/react/use-resource.js +20 -1
  124. package/dist/react/use-resource.js.map +1 -1
  125. package/dist/react.js +21 -20
  126. package/dist/styles.css +1 -1
  127. package/package.json +37 -2
package/README.md CHANGED
@@ -15,6 +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
19
  | `@openrails/contentkit-ui/locales/{de,es,ja,ko,zh}` | message bundles (English is built in) | |
19
20
  | `@openrails/contentkit-ui/styles.css` | the stylesheet, scoped under `.ckui` (the root entry also installs it) | |
20
21
 
@@ -81,11 +82,12 @@ operation. This does not change the operation's result or its authentication.
81
82
  `PUT` from the app's origin with `Content-Type` and `x-amz-checksum-sha256`.
82
83
  - **Content modules**, one per resource, each method one route of the
83
84
  generated route table with its generated types: `posts`, `comments`,
84
- `reactions`, `favorites`, `polls`, `bans` (`"owner"` or `"global"`),
85
+ `reactions`, `favorites`, `polls`, `bans` (`"owner"` or `"global"`; `get("global", user)`),
85
86
  `moderation`, `taxonomy`, `codes`. Lists take `limit`/`offset` (the review
86
87
  queue a `cursor`); staff lists are `posts.adminList` (`q` searches,
87
- `deleted: true` lists deleted posts for `posts.restore`), `polls.adminList`,
88
- `comments.adminList`.
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.
89
91
  - **Images of posts and polls** go to the item's server-named upload path:
90
92
  `media.uploadNamed(ref, file)` resolves the name content routes take;
91
93
  `media.uploadInline(postId, file)` resolves a body image's reference
@@ -159,8 +161,9 @@ const editor = usePost(postId);
159
161
  Also `useContentConfig` (`client.config()`, read once), `useCommentReplies`,
160
162
  `useCanComment` (may the caller comment, may signed-out visitors, the ban
161
163
  that stops it, and what it may do to others' comments), `useLatestComments`,
162
- `usePolls`, `usePosts` (staff: `admin`, `deleted`, `q`), `usePost` (with `restore`), and for staff `useAdminComments`,
163
- `useModerationQueue`, `useCommentBans`.
164
+ `usePolls`, `usePosts` (staff: `admin`, `deleted`, `q`), `usePost` (with `restore`), and for staff `useAdminComments`
165
+ (`contentKind`, `author`), `useModerationQueue`, `useCommentBans`, `useGlobalCommentBan` (one user's), and
166
+ `useStaffAccess`, `useAdminLocation`, `useFlowClient` and `usePaged`, which the admin flows are built from.
164
167
 
165
168
  Also: `useCrop`, `useVideoImages`, `useFrameStrip`, `useVideoFrame`,
166
169
  `useVideoPoster`, `useEncodeProgress`, `useHlsPlayer`, `useCarousel`,
@@ -404,17 +407,102 @@ server allows it (`content.Options.Anonymous`, read from the comment standing
404
407
  and `client.config()`); elsewhere they are asked to sign in (`onSignIn`, the
405
408
  provider's or their own) or, without one, told to.
406
409
 
407
- Staff: `CommentModeration` (every comment, the review queue, the site's
408
- bans), `CommentBans` with `CommentBanDialog`, and `PollEditor`. They show what
409
- the caller's permissions allow and say so when a route refuses; the host
410
- decides who sees them.
410
+ Staff building blocks: `CommentBans` (paged) with `CommentBanDialog`, and
411
+ `PollEditor` (deleting asks for a typed confirmation). The complete staff screens are the admin flows below.
411
412
 
412
413
  ```tsx
413
414
  <Comments item={{ kind: "video", id }} count={video.comment_count} userHref={(u) => `/u/${u.username}`} />
414
415
  <Poll language={lang} />
415
- <CommentModeration contentKinds={["video", "post"]} itemHref={(i) => `/${i.kind}/${i.id}`} />
416
416
  ```
417
417
 
418
+ ## Admin flows
419
+
420
+ Drop-in staff screens for a host's own admin area: each makes its own reads
421
+ and writes, shows loading, empty, error (with retry) and no-access states,
422
+ confirms destructive acts and shows only what the caller's permissions allow.
423
+ The host writes no API calls. Every flow takes:
424
+
425
+ | Prop | |
426
+ | --- | --- |
427
+ | `client`, or `baseUrl` + `auth` | the ContentKit client, or where ContentKit is mounted and auth-ui's `AuthClient` (anything with `authFetch`); else the provider's client |
428
+ | `basePath`, `navigation` | multi-view flows: views are paths under `basePath`, filters and pages its query; `navigation` is the host router (`{ pathname, search, navigate(to, { replace }) }`), else the history API; without `basePath`, views stay in memory |
429
+ | `guard` | runs every write (auth-ui's `useStepUpGuard()`) |
430
+ | `appearance`, `className` | theme `"inherit"` by default: the host's shadcn variables (`--background`, `--primary`, …) and `.dark` |
431
+ | `language`, `messages` | the UI language (its bundle loads) and overrides; default the provider's |
432
+ | `onError` | every failure the flow shows, for a toast or a log |
433
+
434
+ Each has an access hook (`use…Access`) for hiding its tab: per area, whether
435
+ the caller's permission admits it. `useConfirm()` is the flows' promise-based
436
+ confirmation (`typed` for acts that cannot be undone).
437
+
438
+ ```tsx
439
+ const location = useLocation();
440
+ const navigate = useNavigate();
441
+ const nav = useMemo(() => ({ pathname: location.pathname, search: location.search, navigate }), [location, navigate]);
442
+ ```
443
+
444
+ - **`CommentModeration`**: every comment (deleted, held and rejected ones with
445
+ their text) by content kind or author, to delete, restore or ban its author;
446
+ the review queue; the site's bans. Sections at `basePath`, `/queue`,
447
+ `/bans`; `?kind`, `?author`, `?page`. Props: `contentKinds`, `itemHref`,
448
+ `userHref`, `sections`, `pageSize`. `useCommentModerationAccess()`.
449
+
450
+ ```tsx
451
+ <CommentModeration client={contentkit} basePath="/admin/comments" navigation={nav} contentKinds={["video", "post"]} />
452
+ ```
453
+
454
+ - **`PollManager`**: every poll by language, month or day, state (live,
455
+ scheduled, closed, inactive) and question; create and edit through
456
+ `PollEditor` at `basePath/new` and `basePath/{id}`; delete with a typed
457
+ confirmation. Props: `languages`, `freeText`, `maxOptions`, `pageSize`.
458
+ `usePollManagerAccess()`.
459
+
460
+ ```tsx
461
+ <PollManager client={contentkit} basePath="/admin/polls" navigation={nav} languages={["en", "ja"]} />
462
+ ```
463
+
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()`.
470
+
471
+ ```tsx
472
+ import { richTextEditor } from "@openrails/contentkit-ui/editor";
473
+ <PostManager client={contentkit} basePath="/admin/posts" navigation={nav} renderEditor={richTextEditor} />
474
+ ```
475
+
476
+ `RichTextEditor` (`value`, `onChange`, `uploadImage`) is Tiptap: headings,
477
+ emphasis, lists, quotes, links and images picked, pasted or dropped. Its
478
+ HTML keeps to that schema, so bodies stay a small, predictable subset.
479
+
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
+ - **`UserCommentStanding`**: for a user page, the person's site-wide comment
498
+ ban (ban, replace, lift) and their recent comments (delete, restore). Props:
499
+ `user` (their id), `name`, `itemHref`, `pageSize`.
500
+ `useUserCommentStandingAccess()`.
501
+
502
+ ```tsx
503
+ <UserCommentStanding client={contentkit} user={u.id} name={u.username} />
504
+ ```
505
+
418
506
  ## Errors
419
507
 
420
508
  Every call rejects with `ContentKitError`: `code` (the server's, or `network`,
@@ -8,6 +8,8 @@ export declare class BansClient {
8
8
  constructor(http: Http);
9
9
  /** The scope's bans, newest first, expired ones included. */
10
10
  list(scope: BanScope, q?: PageQuery, signal?: AbortSignal): Promise<CommentBan[]>;
11
+ /** The user's site-wide ban, expired or not; null when they have none (root:comments:ban). */
12
+ get(scope: "global", user: string, signal?: AbortSignal): Promise<CommentBan | null>;
11
13
  /** Bans user from commenting in the scope, replacing any ban there; no until lasts until lifted. */
12
14
  ban(scope: BanScope, user: string, input?: BanInput): Promise<CommentBan>;
13
15
  /** Lifts user's ban in the scope; idempotent. */
@@ -1,40 +1,52 @@
1
- import { call as e } from "../route.js";
1
+ import { ContentKitError as e } from "../errors.js";
2
+ import { call as t } from "../route.js";
2
3
  //#region src/client/content/bans.ts
3
- var t = class {
4
+ var n = class {
4
5
  http;
5
6
  constructor(e) {
6
7
  this.http = e;
7
8
  }
8
- list(t, n = {}, r) {
9
- return t === "global" ? e(this.http, "GET", "/global-comment-bans", {
9
+ list(e, n = {}, r) {
10
+ return e === "global" ? t(this.http, "GET", "/global-comment-bans", {
10
11
  query: n,
11
12
  signal: r
12
- }) : e(this.http, "GET", "/comment-bans", {
13
+ }) : t(this.http, "GET", "/comment-bans", {
13
14
  query: n,
14
15
  signal: r
15
16
  });
16
17
  }
17
- async ban(t, n, r = {}) {
18
+ async get(n, r, i) {
19
+ try {
20
+ return await t(this.http, "GET", "/global-comment-bans/{user}", {
21
+ params: { user: r },
22
+ signal: i
23
+ });
24
+ } catch (t) {
25
+ if (t instanceof e && t.code === "not_found") return null;
26
+ throw t;
27
+ }
28
+ }
29
+ async ban(e, n, r = {}) {
18
30
  let i = this.http.captureChanges(), a = {
19
31
  params: { user: n },
20
32
  body: r
21
- }, o = await (t === "global" ? e(this.http, "PUT", "/global-comment-bans/{user}", a) : e(this.http, "PUT", "/comment-bans/{user}", a));
33
+ }, o = await (e === "global" ? t(this.http, "PUT", "/global-comment-bans/{user}", a) : t(this.http, "PUT", "/comment-bans/{user}", a));
22
34
  return i({
23
35
  type: "ban.saved",
24
- scope: t,
36
+ scope: e,
25
37
  ban: o
26
38
  }), o;
27
39
  }
28
- async lift(t, n) {
40
+ async lift(e, n) {
29
41
  let r = this.http.captureChanges(), i = { params: { user: n } };
30
- await (t === "global" ? e(this.http, "DELETE", "/global-comment-bans/{user}", i) : e(this.http, "DELETE", "/comment-bans/{user}", i)), r({
42
+ await (e === "global" ? t(this.http, "DELETE", "/global-comment-bans/{user}", i) : t(this.http, "DELETE", "/comment-bans/{user}", i)), r({
31
43
  type: "ban.lifted",
32
- scope: t,
44
+ scope: e,
33
45
  user: n
34
46
  });
35
47
  }
36
48
  };
37
49
  //#endregion
38
- export { t as BansClient };
50
+ export { n as BansClient };
39
51
 
40
52
  //# sourceMappingURL=bans.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bans.js","names":[],"sources":["../../../src/client/content/bans.ts"],"sourcesContent":["import type { BanInput, CommentBan } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport { call, type PageQuery } from \"../route.js\";\nimport type { BanScope } from \"./types.js\";\n\n/** Comment bans: \"owner\" on the caller's own content, \"global\" across the site (CommentBan). */\nexport class BansClient {\n constructor(private readonly http: Http) {}\n\n /** The scope's bans, newest first, expired ones included. */\n list(scope: BanScope, q: PageQuery = {}, signal?: AbortSignal): Promise<CommentBan[]> {\n return scope === \"global\"\n ? call(this.http, \"GET\", \"/global-comment-bans\", { query: q, signal })\n : call(this.http, \"GET\", \"/comment-bans\", { query: q, signal });\n }\n /** Bans user from commenting in the scope, replacing any ban there; no until lasts until lifted. */\n async ban(scope: BanScope, user: string, input: BanInput = {}): Promise<CommentBan> {\n const emit = this.http.captureChanges();\n const o = { params: { user }, body: input };\n const ban: CommentBan = await (scope === \"global\"\n ? call(this.http, \"PUT\", \"/global-comment-bans/{user}\", o)\n : call(this.http, \"PUT\", \"/comment-bans/{user}\", o));\n emit({ type: \"ban.saved\", scope, ban });\n return ban;\n }\n /** Lifts user's ban in the scope; idempotent. */\n async lift(scope: BanScope, user: string): Promise<void> {\n const emit = this.http.captureChanges();\n const o = { params: { user } };\n await (scope === \"global\" ? call(this.http, \"DELETE\", \"/global-comment-bans/{user}\", o) : call(this.http, \"DELETE\", \"/comment-bans/{user}\", o));\n emit({ type: \"ban.lifted\", scope, user });\n }\n}\n"],"mappings":";;AAMA,IAAa,IAAb,MAAwB;CACO;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,KAAK,GAAiB,IAAe,CAAC,GAAG,GAA6C;EACpF,OAAO,MAAU,WACb,EAAK,KAAK,MAAM,OAAO,wBAAwB;GAAE,OAAO;GAAG;EAAO,CAAC,IACnE,EAAK,KAAK,MAAM,OAAO,iBAAiB;GAAE,OAAO;GAAG;EAAO,CAAC;CAClE;CAEA,MAAM,IAAI,GAAiB,GAAc,IAAkB,CAAC,GAAwB;EAClF,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAI;GAAE,QAAQ,EAAE,QAAK;GAAG,MAAM;EAAM,GACpC,IAAkB,OAAO,MAAU,WACrC,EAAK,KAAK,MAAM,OAAO,+BAA+B,CAAC,IACvD,EAAK,KAAK,MAAM,OAAO,wBAAwB,CAAC;EAEpD,OADA,EAAK;GAAE,MAAM;GAAa;GAAO;EAAI,CAAC,GAC/B;CACT;CAEA,MAAM,KAAK,GAAiB,GAA6B;EACvD,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAI,EAAE,QAAQ,EAAE,QAAK,EAAE;EAE7B,AADA,OAAO,MAAU,WAAW,EAAK,KAAK,MAAM,UAAU,+BAA+B,CAAC,IAAI,EAAK,KAAK,MAAM,UAAU,wBAAwB,CAAC,IAC7I,EAAK;GAAE,MAAM;GAAc;GAAO;EAAK,CAAC;CAC1C;AACF"}
1
+ {"version":3,"file":"bans.js","names":[],"sources":["../../../src/client/content/bans.ts"],"sourcesContent":["import { ContentKitError } from \"../errors.js\";\nimport type { BanInput, CommentBan } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport { call, type PageQuery } from \"../route.js\";\nimport type { BanScope } from \"./types.js\";\n\n/** Comment bans: \"owner\" on the caller's own content, \"global\" across the site (CommentBan). */\nexport class BansClient {\n constructor(private readonly http: Http) {}\n\n /** The scope's bans, newest first, expired ones included. */\n list(scope: BanScope, q: PageQuery = {}, signal?: AbortSignal): Promise<CommentBan[]> {\n return scope === \"global\"\n ? call(this.http, \"GET\", \"/global-comment-bans\", { query: q, signal })\n : call(this.http, \"GET\", \"/comment-bans\", { query: q, signal });\n }\n /** The user's site-wide ban, expired or not; null when they have none (root:comments:ban). */\n async get(scope: \"global\", user: string, signal?: AbortSignal): Promise<CommentBan | null> {\n try {\n return await call(this.http, \"GET\", \"/global-comment-bans/{user}\", { params: { user }, signal });\n } catch (e) {\n if (e instanceof ContentKitError && e.code === \"not_found\") return null;\n throw e;\n }\n }\n /** Bans user from commenting in the scope, replacing any ban there; no until lasts until lifted. */\n async ban(scope: BanScope, user: string, input: BanInput = {}): Promise<CommentBan> {\n const emit = this.http.captureChanges();\n const o = { params: { user }, body: input };\n const ban: CommentBan = await (scope === \"global\"\n ? call(this.http, \"PUT\", \"/global-comment-bans/{user}\", o)\n : call(this.http, \"PUT\", \"/comment-bans/{user}\", o));\n emit({ type: \"ban.saved\", scope, ban });\n return ban;\n }\n /** Lifts user's ban in the scope; idempotent. */\n async lift(scope: BanScope, user: string): Promise<void> {\n const emit = this.http.captureChanges();\n const o = { params: { user } };\n await (scope === \"global\" ? call(this.http, \"DELETE\", \"/global-comment-bans/{user}\", o) : call(this.http, \"DELETE\", \"/comment-bans/{user}\", o));\n emit({ type: \"ban.lifted\", scope, user });\n }\n}\n"],"mappings":";;;AAOA,IAAa,IAAb,MAAwB;CACO;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,KAAK,GAAiB,IAAe,CAAC,GAAG,GAA6C;EACpF,OAAO,MAAU,WACb,EAAK,KAAK,MAAM,OAAO,wBAAwB;GAAE,OAAO;GAAG;EAAO,CAAC,IACnE,EAAK,KAAK,MAAM,OAAO,iBAAiB;GAAE,OAAO;GAAG;EAAO,CAAC;CAClE;CAEA,MAAM,IAAI,GAAiB,GAAc,GAAkD;EACzF,IAAI;GACF,OAAO,MAAM,EAAK,KAAK,MAAM,OAAO,+BAA+B;IAAE,QAAQ,EAAE,QAAK;IAAG;GAAO,CAAC;EACjG,SAAS,GAAG;GACV,IAAI,aAAa,KAAmB,EAAE,SAAS,aAAa,OAAO;GACnE,MAAM;EACR;CACF;CAEA,MAAM,IAAI,GAAiB,GAAc,IAAkB,CAAC,GAAwB;EAClF,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAI;GAAE,QAAQ,EAAE,QAAK;GAAG,MAAM;EAAM,GACpC,IAAkB,OAAO,MAAU,WACrC,EAAK,KAAK,MAAM,OAAO,+BAA+B,CAAC,IACvD,EAAK,KAAK,MAAM,OAAO,wBAAwB,CAAC;EAEpD,OADA,EAAK;GAAE,MAAM;GAAa;GAAO;EAAI,CAAC,GAC/B;CACT;CAEA,MAAM,KAAK,GAAiB,GAA6B;EACvD,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAI,EAAE,QAAQ,EAAE,QAAK,EAAE;EAE7B,AADA,OAAO,MAAU,WAAW,EAAK,KAAK,MAAM,UAAU,+BAA+B,CAAC,IAAI,EAAK,KAAK,MAAM,UAAU,wBAAwB,CAAC,IAC7I,EAAK;GAAE,MAAM;GAAc;GAAO;EAAK,CAAC;CAC1C;AACF"}
@@ -8,6 +8,8 @@ export interface CommentQuery extends PageQuery {
8
8
  export interface AdminCommentQuery extends PageQuery {
9
9
  /** Only comments on this content kind. */
10
10
  contentKind?: string;
11
+ /** Only comments by this user id. */
12
+ author?: string;
11
13
  }
12
14
  /** Comments on an item: threads, replies, edits, tombstones, reactions, standing, the latest feed and moderation. */
13
15
  export declare class CommentsClient {
@@ -29,8 +31,8 @@ export declare class CommentsClient {
29
31
  standing(ref: RefBody, signal?: AbortSignal): Promise<CommentStanding>;
30
32
  /** The newest published comments across the site, with their items; a page may under-fill. */
31
33
  latest(q?: PageQuery, signal?: AbortSignal): Promise<FeedItem[]>;
32
- /** Every comment, newest first, deleted, held and rejected ones with their bodies (CommentModerate). */
34
+ /** Every comment, newest first, deleted, held and rejected ones with their bodies (root:comments:moderate). */
33
35
  adminList(q?: AdminCommentQuery, signal?: AbortSignal): Promise<AdminComment[]>;
34
- /** Restores a deleted comment (CommentModerate). */
36
+ /** Restores a deleted comment (root:comments:moderate). */
35
37
  restore(id: string): Promise<void>;
36
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"comments.js","names":[],"sources":["../../../src/client/content/comments.ts"],"sourcesContent":["import type { AdminComment, Comment, CommentInput, CommentStanding, FeedItem, ReactionCounts, RefBody } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport { call, type PageQuery } from \"../route.js\";\nimport { reactionVerb, type Reaction, type Sort } from \"./types.js\";\n\nexport interface CommentQuery extends PageQuery {\n sort?: Sort;\n}\n\nexport interface AdminCommentQuery extends PageQuery {\n /** Only comments on this content kind. */\n contentKind?: string;\n}\n\n// Content ids are the host's opaque text (not only media's UUIDv7s).\nconst target = (ref: RefBody) => ({ kind: ref.kind, id: ref.id });\n\n/** Comments on an item: threads, replies, edits, tombstones, reactions, standing, the latest feed and moderation. */\nexport class CommentsClient {\n constructor(private readonly http: Http) {}\n\n /** The item's top-level comments with reply counts; the caller also gets its own held and rejected ones. */\n list(ref: RefBody, q: CommentQuery = {}, signal?: AbortSignal): Promise<Comment[]> {\n return call(this.http, \"GET\", \"/{kind}/{id}/comments\", { params: target(ref), query: q, signal });\n }\n /** A top-level comment's replies, oldest first. */\n replies(id: string, q: PageQuery = {}, signal?: AbortSignal): Promise<Comment[]> {\n return call(this.http, \"GET\", \"/comments/{cid}/replies\", { params: { cid: id }, query: q, signal });\n }\n /** Comments on the item, or replies to a top-level comment (reply_to_id); anonymous callers give anon_name. */\n async create(ref: RefBody, input: CommentInput): Promise<Comment> {\n const emit = this.http.captureChanges();\n const comment: Comment = await call(this.http, \"POST\", \"/{kind}/{id}/comments\", { params: target(ref), body: input });\n emit({ type: \"comment.created\", ref: target(ref), comment });\n return comment;\n }\n /** Replaces the body: its author, or a moderator. */\n async edit(id: string, body: string): Promise<Comment> {\n const emit = this.http.captureChanges();\n const comment: Comment = await call(this.http, \"PATCH\", \"/comments/{cid}\", { params: { cid: id }, body: { body } });\n emit({ type: \"comment.updated\", comment });\n return comment;\n }\n /** Leaves a tombstone: its author, or a moderator. */\n async delete(id: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/comments/{cid}\", { params: { cid: id } });\n emit({ type: \"comment.deleted\", id });\n }\n /** Sets the caller's reaction to a comment. */\n async react(id: string, value: Reaction): Promise<ReactionCounts> {\n const emit = this.http.captureChanges();\n const params = { cid: id };\n const counts: ReactionCounts =\n value === 0 ? await call(this.http, \"DELETE\", \"/comments/{cid}/reaction\", { params }) : await call(this.http, \"POST\", `/comments/{cid}/${reactionVerb(value)}` as const, { params });\n emit({ type: \"comment.reacted\", id, counts });\n return counts;\n }\n /** Whether the caller may comment on the item, the ban that stops it, and what it may do to others' comments. */\n standing(ref: RefBody, signal?: AbortSignal): Promise<CommentStanding> {\n return call(this.http, \"GET\", \"/{kind}/{id}/can-comment\", { params: target(ref), signal });\n }\n /** The newest published comments across the site, with their items; a page may under-fill. */\n latest(q: PageQuery = {}, signal?: AbortSignal): Promise<FeedItem[]> {\n return call(this.http, \"GET\", \"/comments/latest\", { query: q, signal });\n }\n /** Every comment, newest first, deleted, held and rejected ones with their bodies (CommentModerate). */\n adminList(q: AdminCommentQuery = {}, signal?: AbortSignal): Promise<AdminComment[]> {\n const { contentKind, ...page } = q;\n return call(this.http, \"GET\", \"/comments/admin\", { query: { ...page, content_kind: contentKind }, signal });\n }\n /** Restores a deleted comment (CommentModerate). */\n async restore(id: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"POST\", \"/comments/{cid}/restore\", { params: { cid: id } });\n emit({ type: \"comment.restored\", id });\n }\n}\n"],"mappings":";;;AAeA,IAAM,KAAU,OAAkB;CAAE,MAAM,EAAI;CAAM,IAAI,EAAI;AAAG,IAGlD,IAAb,MAA4B;CACG;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,KAAK,GAAc,IAAkB,CAAC,GAAG,GAA0C;EACjF,OAAO,EAAK,KAAK,MAAM,OAAO,yBAAyB;GAAE,QAAQ,EAAO,CAAG;GAAG,OAAO;GAAG;EAAO,CAAC;CAClG;CAEA,QAAQ,GAAY,IAAe,CAAC,GAAG,GAA0C;EAC/E,OAAO,EAAK,KAAK,MAAM,OAAO,2BAA2B;GAAE,QAAQ,EAAE,KAAK,EAAG;GAAG,OAAO;GAAG;EAAO,CAAC;CACpG;CAEA,MAAM,OAAO,GAAc,GAAuC;EAChE,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAmB,MAAM,EAAK,KAAK,MAAM,QAAQ,yBAAyB;GAAE,QAAQ,EAAO,CAAG;GAAG,MAAM;EAAM,CAAC;EAEpH,OADA,EAAK;GAAE,MAAM;GAAmB,KAAK,EAAO,CAAG;GAAG;EAAQ,CAAC,GACpD;CACT;CAEA,MAAM,KAAK,GAAY,GAAgC;EACrD,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAmB,MAAM,EAAK,KAAK,MAAM,SAAS,mBAAmB;GAAE,QAAQ,EAAE,KAAK,EAAG;GAAG,MAAM,EAAE,QAAK;EAAE,CAAC;EAElH,OADA,EAAK;GAAE,MAAM;GAAmB;EAAQ,CAAC,GAClC;CACT;CAEA,MAAM,OAAO,GAA2B;EACtC,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,UAAU,mBAAmB,EAAE,QAAQ,EAAE,KAAK,EAAG,EAAE,CAAC,GAC1E,EAAK;GAAE,MAAM;GAAmB;EAAG,CAAC;CACtC;CAEA,MAAM,MAAM,GAAY,GAA0C;EAChE,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAS,EAAE,KAAK,EAAG,GACnB,IACJ,MAAU,IAAI,MAAM,EAAK,KAAK,MAAM,UAAU,4BAA4B,EAAE,UAAO,CAAC,IAAI,MAAM,EAAK,KAAK,MAAM,QAAQ,mBAAmB,EAAa,CAAK,KAAc,EAAE,UAAO,CAAC;EAErL,OADA,EAAK;GAAE,MAAM;GAAmB;GAAI;EAAO,CAAC,GACrC;CACT;CAEA,SAAS,GAAc,GAAgD;EACrE,OAAO,EAAK,KAAK,MAAM,OAAO,4BAA4B;GAAE,QAAQ,EAAO,CAAG;GAAG;EAAO,CAAC;CAC3F;CAEA,OAAO,IAAe,CAAC,GAAG,GAA2C;EACnE,OAAO,EAAK,KAAK,MAAM,OAAO,oBAAoB;GAAE,OAAO;GAAG;EAAO,CAAC;CACxE;CAEA,UAAU,IAAuB,CAAC,GAAG,GAA+C;EAClF,IAAM,EAAE,gBAAa,GAAG,MAAS;EACjC,OAAO,EAAK,KAAK,MAAM,OAAO,mBAAmB;GAAE,OAAO;IAAE,GAAG;IAAM,cAAc;GAAY;GAAG;EAAO,CAAC;CAC5G;CAEA,MAAM,QAAQ,GAA2B;EACvC,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,QAAQ,2BAA2B,EAAE,QAAQ,EAAE,KAAK,EAAG,EAAE,CAAC,GAChF,EAAK;GAAE,MAAM;GAAoB;EAAG,CAAC;CACvC;AACF"}
1
+ {"version":3,"file":"comments.js","names":[],"sources":["../../../src/client/content/comments.ts"],"sourcesContent":["import type { AdminComment, Comment, CommentInput, CommentStanding, FeedItem, ReactionCounts, RefBody } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport { call, type PageQuery } from \"../route.js\";\nimport { reactionVerb, type Reaction, type Sort } from \"./types.js\";\n\nexport interface CommentQuery extends PageQuery {\n sort?: Sort;\n}\n\nexport interface AdminCommentQuery extends PageQuery {\n /** Only comments on this content kind. */\n contentKind?: string;\n /** Only comments by this user id. */\n author?: string;\n}\n\n// Content ids are the host's opaque text (not only media's UUIDv7s).\nconst target = (ref: RefBody) => ({ kind: ref.kind, id: ref.id });\n\n/** Comments on an item: threads, replies, edits, tombstones, reactions, standing, the latest feed and moderation. */\nexport class CommentsClient {\n constructor(private readonly http: Http) {}\n\n /** The item's top-level comments with reply counts; the caller also gets its own held and rejected ones. */\n list(ref: RefBody, q: CommentQuery = {}, signal?: AbortSignal): Promise<Comment[]> {\n return call(this.http, \"GET\", \"/{kind}/{id}/comments\", { params: target(ref), query: q, signal });\n }\n /** A top-level comment's replies, oldest first. */\n replies(id: string, q: PageQuery = {}, signal?: AbortSignal): Promise<Comment[]> {\n return call(this.http, \"GET\", \"/comments/{cid}/replies\", { params: { cid: id }, query: q, signal });\n }\n /** Comments on the item, or replies to a top-level comment (reply_to_id); anonymous callers give anon_name. */\n async create(ref: RefBody, input: CommentInput): Promise<Comment> {\n const emit = this.http.captureChanges();\n const comment: Comment = await call(this.http, \"POST\", \"/{kind}/{id}/comments\", { params: target(ref), body: input });\n emit({ type: \"comment.created\", ref: target(ref), comment });\n return comment;\n }\n /** Replaces the body: its author, or a moderator. */\n async edit(id: string, body: string): Promise<Comment> {\n const emit = this.http.captureChanges();\n const comment: Comment = await call(this.http, \"PATCH\", \"/comments/{cid}\", { params: { cid: id }, body: { body } });\n emit({ type: \"comment.updated\", comment });\n return comment;\n }\n /** Leaves a tombstone: its author, or a moderator. */\n async delete(id: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/comments/{cid}\", { params: { cid: id } });\n emit({ type: \"comment.deleted\", id });\n }\n /** Sets the caller's reaction to a comment. */\n async react(id: string, value: Reaction): Promise<ReactionCounts> {\n const emit = this.http.captureChanges();\n const params = { cid: id };\n const counts: ReactionCounts =\n value === 0 ? await call(this.http, \"DELETE\", \"/comments/{cid}/reaction\", { params }) : await call(this.http, \"POST\", `/comments/{cid}/${reactionVerb(value)}` as const, { params });\n emit({ type: \"comment.reacted\", id, counts });\n return counts;\n }\n /** Whether the caller may comment on the item, the ban that stops it, and what it may do to others' comments. */\n standing(ref: RefBody, signal?: AbortSignal): Promise<CommentStanding> {\n return call(this.http, \"GET\", \"/{kind}/{id}/can-comment\", { params: target(ref), signal });\n }\n /** The newest published comments across the site, with their items; a page may under-fill. */\n latest(q: PageQuery = {}, signal?: AbortSignal): Promise<FeedItem[]> {\n return call(this.http, \"GET\", \"/comments/latest\", { query: q, signal });\n }\n /** Every comment, newest first, deleted, held and rejected ones with their bodies (root:comments:moderate). */\n adminList(q: AdminCommentQuery = {}, signal?: AbortSignal): Promise<AdminComment[]> {\n const { contentKind, ...rest } = q;\n return call(this.http, \"GET\", \"/comments/admin\", { query: { ...rest, content_kind: contentKind }, signal });\n }\n /** Restores a deleted comment (root:comments:moderate). */\n async restore(id: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"POST\", \"/comments/{cid}/restore\", { params: { cid: id } });\n emit({ type: \"comment.restored\", id });\n }\n}\n"],"mappings":";;;AAiBA,IAAM,KAAU,OAAkB;CAAE,MAAM,EAAI;CAAM,IAAI,EAAI;AAAG,IAGlD,IAAb,MAA4B;CACG;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,KAAK,GAAc,IAAkB,CAAC,GAAG,GAA0C;EACjF,OAAO,EAAK,KAAK,MAAM,OAAO,yBAAyB;GAAE,QAAQ,EAAO,CAAG;GAAG,OAAO;GAAG;EAAO,CAAC;CAClG;CAEA,QAAQ,GAAY,IAAe,CAAC,GAAG,GAA0C;EAC/E,OAAO,EAAK,KAAK,MAAM,OAAO,2BAA2B;GAAE,QAAQ,EAAE,KAAK,EAAG;GAAG,OAAO;GAAG;EAAO,CAAC;CACpG;CAEA,MAAM,OAAO,GAAc,GAAuC;EAChE,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAmB,MAAM,EAAK,KAAK,MAAM,QAAQ,yBAAyB;GAAE,QAAQ,EAAO,CAAG;GAAG,MAAM;EAAM,CAAC;EAEpH,OADA,EAAK;GAAE,MAAM;GAAmB,KAAK,EAAO,CAAG;GAAG;EAAQ,CAAC,GACpD;CACT;CAEA,MAAM,KAAK,GAAY,GAAgC;EACrD,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAmB,MAAM,EAAK,KAAK,MAAM,SAAS,mBAAmB;GAAE,QAAQ,EAAE,KAAK,EAAG;GAAG,MAAM,EAAE,QAAK;EAAE,CAAC;EAElH,OADA,EAAK;GAAE,MAAM;GAAmB;EAAQ,CAAC,GAClC;CACT;CAEA,MAAM,OAAO,GAA2B;EACtC,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,UAAU,mBAAmB,EAAE,QAAQ,EAAE,KAAK,EAAG,EAAE,CAAC,GAC1E,EAAK;GAAE,MAAM;GAAmB;EAAG,CAAC;CACtC;CAEA,MAAM,MAAM,GAAY,GAA0C;EAChE,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAS,EAAE,KAAK,EAAG,GACnB,IACJ,MAAU,IAAI,MAAM,EAAK,KAAK,MAAM,UAAU,4BAA4B,EAAE,UAAO,CAAC,IAAI,MAAM,EAAK,KAAK,MAAM,QAAQ,mBAAmB,EAAa,CAAK,KAAc,EAAE,UAAO,CAAC;EAErL,OADA,EAAK;GAAE,MAAM;GAAmB;GAAI;EAAO,CAAC,GACrC;CACT;CAEA,SAAS,GAAc,GAAgD;EACrE,OAAO,EAAK,KAAK,MAAM,OAAO,4BAA4B;GAAE,QAAQ,EAAO,CAAG;GAAG;EAAO,CAAC;CAC3F;CAEA,OAAO,IAAe,CAAC,GAAG,GAA2C;EACnE,OAAO,EAAK,KAAK,MAAM,OAAO,oBAAoB;GAAE,OAAO;GAAG;EAAO,CAAC;CACxE;CAEA,UAAU,IAAuB,CAAC,GAAG,GAA+C;EAClF,IAAM,EAAE,gBAAa,GAAG,MAAS;EACjC,OAAO,EAAK,KAAK,MAAM,OAAO,mBAAmB;GAAE,OAAO;IAAE,GAAG;IAAM,cAAc;GAAY;GAAG;EAAO,CAAC;CAC5G;CAEA,MAAM,QAAQ,GAA2B;EACvC,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,QAAQ,2BAA2B,EAAE,QAAQ,EAAE,KAAK,EAAG,EAAE,CAAC,GAChF,EAAK;GAAE,MAAM;GAAoB;EAAG,CAAC;CACvC;AACF"}
@@ -9,15 +9,22 @@ export interface PollQuery extends PageQuery {
9
9
  /** Polls live on this day, YYYY-MM-DD. */
10
10
  date?: string;
11
11
  }
12
- /** Site-wide polls: multiple choice or free text; staff write them (PollWrite). */
12
+ /** A poll's state in the staff list. */
13
+ export type PollState = "live" | "scheduled" | "closed" | "inactive";
14
+ export interface PollAdminQuery extends PollQuery {
15
+ state?: PollState;
16
+ /** Only polls whose question contains this text, ignoring case. */
17
+ q?: string;
18
+ }
19
+ /** Site-wide polls: multiple choice or free text; staff write them (root:polls:write). */
13
20
  export declare class PollsClient {
14
21
  private readonly http;
15
22
  private readonly media;
16
23
  constructor(http: Http, media: MediaClient);
17
24
  /** Live polls, newest first. */
18
25
  list(q?: PollQuery, signal?: AbortSignal): Promise<Poll[]>;
19
- /** Every poll, scheduled and inactive ones included (PollWrite). */
20
- adminList(q?: PollQuery, signal?: AbortSignal): Promise<Poll[]>;
26
+ /** Every poll, scheduled and inactive ones included, by state and question (root:polls:write). */
27
+ adminList(q?: PollAdminQuery, signal?: AbortSignal): Promise<Poll[]>;
21
28
  /** A poll with the caller's vote or answer. */
22
29
  get(id: string, signal?: AbortSignal): Promise<Poll>;
23
30
  /** Creates a poll with its options (images are set once it exists). */
@@ -1 +1 @@
1
- {"version":3,"file":"polls.js","names":[],"sources":["../../../src/client/content/polls.ts"],"sourcesContent":["import type { Poll, PollImage, PollInput, PollOption, PollOptionPatch, PollUpdate } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport type { MediaClient, NamedOptions } from \"../media/client.js\";\nimport { call, type PageQuery } from \"../route.js\";\n\nexport interface PollQuery extends PageQuery {\n language?: string;\n /** Polls live in this month, YYYY-MM. */\n month?: string;\n /** Polls live on this day, YYYY-MM-DD. */\n date?: string;\n}\n\n/** Site-wide polls: multiple choice or free text; staff write them (PollWrite). */\nexport class PollsClient {\n constructor(\n private readonly http: Http,\n private readonly media: MediaClient,\n ) {}\n\n /** Live polls, newest first. */\n list(q: PollQuery = {}, signal?: AbortSignal): Promise<Poll[]> {\n return call(this.http, \"GET\", \"/polls\", { query: q, signal });\n }\n /** Every poll, scheduled and inactive ones included (PollWrite). */\n adminList(q: PollQuery = {}, signal?: AbortSignal): Promise<Poll[]> {\n return call(this.http, \"GET\", \"/polls/admin\", { query: q, signal });\n }\n /** A poll with the caller's vote or answer. */\n get(id: string, signal?: AbortSignal): Promise<Poll> {\n return call(this.http, \"GET\", \"/polls/{id}\", { params: { id }, signal });\n }\n /** Creates a poll with its options (images are set once it exists). */\n async create(input: PollInput): Promise<Poll> {\n const emit = this.http.captureChanges();\n const poll: Poll = await call(this.http, \"POST\", \"/polls\", { body: input });\n emit({ type: \"poll.created\", poll });\n return poll;\n }\n /** Updates the given fields. */\n async update(id: string, patch: PollUpdate): Promise<Poll> {\n const emit = this.http.captureChanges();\n const poll: Poll = await call(this.http, \"PATCH\", \"/polls/{id}\", { params: { id }, body: patch });\n emit({ type: \"poll.updated\", poll });\n return poll;\n }\n /** Deletes the poll and its images. */\n async delete(id: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/polls/{id}\", { params: { id } });\n emit({ type: \"poll.deleted\", id });\n }\n /** Votes for an option of an open multiple-choice poll; a vote is final, and voting again changes nothing. */\n async vote(id: string, option: string): Promise<Poll> {\n const emit = this.http.captureChanges();\n const poll: Poll = await call(this.http, \"POST\", \"/polls/{id}/vote\", { params: { id }, body: { option_id: option } });\n emit({ type: \"poll.updated\", poll });\n return poll;\n }\n /** Stores or replaces the signed-in caller's answer to an open free-text poll. */\n async answer(id: string, text: string): Promise<Poll> {\n const emit = this.http.captureChanges();\n const poll: Poll = await call(this.http, \"POST\", \"/polls/{id}/answer\", { params: { id }, body: { text } });\n emit({ type: \"poll.updated\", poll });\n return poll;\n }\n /** Sets the question image to an inline image name of the poll's folder, or clears it; resolves with its URL. */\n async setImage(id: string, image: string | null): Promise<string | null> {\n const emit = this.http.captureChanges();\n const r: PollImage = await call(this.http, \"PUT\", \"/polls/{id}/image\", { params: { id }, body: { image: image ?? \"\" } });\n emit({ type: \"poll.changed\", id });\n return r.image_url;\n }\n /** Uploads file to the poll's folder as the question image (null clears it). */\n async uploadImage(id: string, file: Blob | null, o?: NamedOptions): Promise<string | null> {\n return this.setImage(id, file ? (await this.media.uploadNamed(this.media.pollRef(id), file, o)).name : null);\n }\n /** Adds an option, at the end unless position is given. */\n async addOption(id: string, option: PollOptionPatch): Promise<PollOption> {\n const emit = this.http.captureChanges();\n const o: PollOption = await call(this.http, \"POST\", \"/polls/{id}/options\", { params: { id }, body: option });\n emit({ type: \"poll.changed\", id });\n return o;\n }\n async updateOption(id: string, option: string, patch: PollOptionPatch): Promise<PollOption> {\n const emit = this.http.captureChanges();\n const o: PollOption = await call(this.http, \"PATCH\", \"/polls/{id}/options/{oid}\", { params: { id, oid: option }, body: patch });\n emit({ type: \"poll.changed\", id });\n return o;\n }\n /** Removes an option and its votes; a poll keeps at least two. */\n async deleteOption(id: string, option: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/polls/{id}/options/{oid}\", { params: { id, oid: option } });\n emit({ type: \"poll.changed\", id });\n }\n /** Gives the options the positions of their order, patching those that moved. */\n async reorderOptions(id: string, options: readonly Pick<PollOption, \"id\" | \"position\">[]): Promise<void> {\n const emit = this.http.captureChanges();\n for (const [position, o] of options.entries()) {\n if (o.position !== position) await call(this.http, \"PATCH\", \"/polls/{id}/options/{oid}\", { params: { id, oid: o.id }, body: { position } });\n }\n emit({ type: \"poll.changed\", id });\n }\n async setOptionImage(id: string, option: string, image: string | null): Promise<string | null> {\n const emit = this.http.captureChanges();\n const r: PollImage = await call(this.http, \"PUT\", \"/polls/{id}/options/{oid}/image\", { params: { id, oid: option }, body: { image: image ?? \"\" } });\n emit({ type: \"poll.changed\", id });\n return r.image_url;\n }\n /** Uploads file to the poll's folder as the option's image (null clears it). */\n async uploadOptionImage(id: string, option: string, file: Blob | null, o?: NamedOptions): Promise<string | null> {\n return this.setOptionImage(id, option, file ? (await this.media.uploadNamed(this.media.pollRef(id), file, o)).name : null);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"polls.js","names":[],"sources":["../../../src/client/content/polls.ts"],"sourcesContent":["import type { Poll, PollImage, PollInput, PollOption, PollOptionPatch, PollUpdate } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport type { MediaClient, NamedOptions } from \"../media/client.js\";\nimport { call, type PageQuery } from \"../route.js\";\n\nexport interface PollQuery extends PageQuery {\n language?: string;\n /** Polls live in this month, YYYY-MM. */\n month?: string;\n /** Polls live on this day, YYYY-MM-DD. */\n date?: string;\n}\n\n/** A poll's state in the staff list. */\nexport type PollState = \"live\" | \"scheduled\" | \"closed\" | \"inactive\";\n\nexport interface PollAdminQuery extends PollQuery {\n state?: PollState;\n /** Only polls whose question contains this text, ignoring case. */\n q?: string;\n}\n\n/** Site-wide polls: multiple choice or free text; staff write them (root:polls:write). */\nexport class PollsClient {\n constructor(\n private readonly http: Http,\n private readonly media: MediaClient,\n ) {}\n\n /** Live polls, newest first. */\n list(q: PollQuery = {}, signal?: AbortSignal): Promise<Poll[]> {\n return call(this.http, \"GET\", \"/polls\", { query: q, signal });\n }\n /** Every poll, scheduled and inactive ones included, by state and question (root:polls:write). */\n adminList(q: PollAdminQuery = {}, signal?: AbortSignal): Promise<Poll[]> {\n return call(this.http, \"GET\", \"/polls/admin\", { query: q, signal });\n }\n /** A poll with the caller's vote or answer. */\n get(id: string, signal?: AbortSignal): Promise<Poll> {\n return call(this.http, \"GET\", \"/polls/{id}\", { params: { id }, signal });\n }\n /** Creates a poll with its options (images are set once it exists). */\n async create(input: PollInput): Promise<Poll> {\n const emit = this.http.captureChanges();\n const poll: Poll = await call(this.http, \"POST\", \"/polls\", { body: input });\n emit({ type: \"poll.created\", poll });\n return poll;\n }\n /** Updates the given fields. */\n async update(id: string, patch: PollUpdate): Promise<Poll> {\n const emit = this.http.captureChanges();\n const poll: Poll = await call(this.http, \"PATCH\", \"/polls/{id}\", { params: { id }, body: patch });\n emit({ type: \"poll.updated\", poll });\n return poll;\n }\n /** Deletes the poll and its images. */\n async delete(id: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/polls/{id}\", { params: { id } });\n emit({ type: \"poll.deleted\", id });\n }\n /** Votes for an option of an open multiple-choice poll; a vote is final, and voting again changes nothing. */\n async vote(id: string, option: string): Promise<Poll> {\n const emit = this.http.captureChanges();\n const poll: Poll = await call(this.http, \"POST\", \"/polls/{id}/vote\", { params: { id }, body: { option_id: option } });\n emit({ type: \"poll.updated\", poll });\n return poll;\n }\n /** Stores or replaces the signed-in caller's answer to an open free-text poll. */\n async answer(id: string, text: string): Promise<Poll> {\n const emit = this.http.captureChanges();\n const poll: Poll = await call(this.http, \"POST\", \"/polls/{id}/answer\", { params: { id }, body: { text } });\n emit({ type: \"poll.updated\", poll });\n return poll;\n }\n /** Sets the question image to an inline image name of the poll's folder, or clears it; resolves with its URL. */\n async setImage(id: string, image: string | null): Promise<string | null> {\n const emit = this.http.captureChanges();\n const r: PollImage = await call(this.http, \"PUT\", \"/polls/{id}/image\", { params: { id }, body: { image: image ?? \"\" } });\n emit({ type: \"poll.changed\", id });\n return r.image_url;\n }\n /** Uploads file to the poll's folder as the question image (null clears it). */\n async uploadImage(id: string, file: Blob | null, o?: NamedOptions): Promise<string | null> {\n return this.setImage(id, file ? (await this.media.uploadNamed(this.media.pollRef(id), file, o)).name : null);\n }\n /** Adds an option, at the end unless position is given. */\n async addOption(id: string, option: PollOptionPatch): Promise<PollOption> {\n const emit = this.http.captureChanges();\n const o: PollOption = await call(this.http, \"POST\", \"/polls/{id}/options\", { params: { id }, body: option });\n emit({ type: \"poll.changed\", id });\n return o;\n }\n async updateOption(id: string, option: string, patch: PollOptionPatch): Promise<PollOption> {\n const emit = this.http.captureChanges();\n const o: PollOption = await call(this.http, \"PATCH\", \"/polls/{id}/options/{oid}\", { params: { id, oid: option }, body: patch });\n emit({ type: \"poll.changed\", id });\n return o;\n }\n /** Removes an option and its votes; a poll keeps at least two. */\n async deleteOption(id: string, option: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/polls/{id}/options/{oid}\", { params: { id, oid: option } });\n emit({ type: \"poll.changed\", id });\n }\n /** Gives the options the positions of their order, patching those that moved. */\n async reorderOptions(id: string, options: readonly Pick<PollOption, \"id\" | \"position\">[]): Promise<void> {\n const emit = this.http.captureChanges();\n for (const [position, o] of options.entries()) {\n if (o.position !== position) await call(this.http, \"PATCH\", \"/polls/{id}/options/{oid}\", { params: { id, oid: o.id }, body: { position } });\n }\n emit({ type: \"poll.changed\", id });\n }\n async setOptionImage(id: string, option: string, image: string | null): Promise<string | null> {\n const emit = this.http.captureChanges();\n const r: PollImage = await call(this.http, \"PUT\", \"/polls/{id}/options/{oid}/image\", { params: { id, oid: option }, body: { image: image ?? \"\" } });\n emit({ type: \"poll.changed\", id });\n return r.image_url;\n }\n /** Uploads file to the poll's folder as the option's image (null clears it). */\n async uploadOptionImage(id: string, option: string, file: Blob | null, o?: NamedOptions): Promise<string | null> {\n return this.setOptionImage(id, option, file ? (await this.media.uploadNamed(this.media.pollRef(id), file, o)).name : null);\n }\n}\n"],"mappings":"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"}
@@ -33,15 +33,15 @@ export declare class PostsClient {
33
33
  constructor(http: Http, media: MediaClient);
34
34
  /** Published posts. */
35
35
  list(q?: PostQuery, signal?: AbortSignal): Promise<Post[]>;
36
- /** Every post for staff: drafts, scheduled, held and rejected ones included; deleted ones on their own; q searches (PostWrite). */
36
+ /** Every post for staff: drafts, scheduled, held and rejected ones included; deleted ones on their own; q searches (root:posts:write). */
37
37
  adminList(q?: PostAdminQuery, signal?: AbortSignal): Promise<Post[]>;
38
38
  get(id: string, signal?: AbortSignal): Promise<Post>;
39
- /** Creates a post (PostWrite); moderation "held" when the moderator holds it. */
39
+ /** Creates a post (root:posts:write); moderation "held" when the moderator holds it. */
40
40
  create(input: PostInput): Promise<Post>;
41
- /** Updates the given fields (PostWrite). */
41
+ /** Updates the given fields (root:posts:write). */
42
42
  update(id: string, input: PostInput): Promise<Post>;
43
43
  delete(id: string): Promise<void>;
44
- /** Restores a deleted post as it was (PostWrite); conflict when a live post took its slug. */
44
+ /** Restores a deleted post as it was (root:posts:write); conflict when a live post took its slug. */
45
45
  restore(id: string): Promise<Post>;
46
46
  /** Sets the cover to an inline image name ("i-{uuid}") of the post's folder, or clears it; resolves with its public URL (null until it has one). */
47
47
  setCover(id: string, image: string | null): Promise<string | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"posts.js","names":[],"sources":["../../../src/client/content/posts.ts"],"sourcesContent":["import type { InlineImage, Post, PostCover, PostInput } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport type { MediaClient, NamedOptions } from \"../media/client.js\";\nimport { call, type PageQuery } from \"../route.js\";\nimport type { Sort } from \"./types.js\";\n\n/** The URL scheme of an image reference in a post body. */\nexport const IMAGE_SCHEME = \"contentkit:\";\n\n/**\n * The reference a post body stores for one of its images, by name\n * (\"contentkit:i-{uuid}\"), where the image's URL would go: reads resolve it to\n * the image's current public file (`Post.images`).\n */\nexport const imageRef = (name: string) => IMAGE_SCHEME + name;\n\n/** Matches every image reference; the name is group 1. */\nexport const imageRefs = () => /contentkit:(i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/g;\n\nexport interface PostQuery extends PageQuery {\n language?: string;\n sort?: Sort;\n}\n\nexport interface PostAdminQuery extends PageQuery {\n language?: string;\n /** true: only drafts; false: only posts that are not drafts. */\n draft?: boolean;\n /** true: only deleted posts (with deleted_at); otherwise only live ones. */\n deleted?: boolean;\n /** Only posts whose title, excerpt or body contains this text, ignoring case. */\n q?: string;\n}\n\n/** Posts: the published list, staff CRUD, search and restore, reactions, the cover and body images. */\nexport class PostsClient {\n constructor(\n private readonly http: Http,\n private readonly media: MediaClient,\n ) {}\n\n /** Published posts. */\n list(q: PostQuery = {}, signal?: AbortSignal): Promise<Post[]> {\n return call(this.http, \"GET\", \"/posts\", { query: q, signal });\n }\n /** Every post for staff: drafts, scheduled, held and rejected ones included; deleted ones on their own; q searches (PostWrite). */\n adminList(q: PostAdminQuery = {}, signal?: AbortSignal): Promise<Post[]> {\n return call(this.http, \"GET\", \"/posts/admin\", { query: q, signal });\n }\n get(id: string, signal?: AbortSignal): Promise<Post> {\n return call(this.http, \"GET\", \"/posts/{id}\", { params: { id }, signal });\n }\n /** Creates a post (PostWrite); moderation \"held\" when the moderator holds it. */\n async create(input: PostInput): Promise<Post> {\n const emit = this.http.captureChanges();\n const post: Post = await call(this.http, \"POST\", \"/posts\", { body: input });\n emit({ type: \"post.created\", post });\n return post;\n }\n /** Updates the given fields (PostWrite). */\n async update(id: string, input: PostInput): Promise<Post> {\n const emit = this.http.captureChanges();\n const post: Post = await call(this.http, \"PATCH\", \"/posts/{id}\", { params: { id }, body: input });\n emit({ type: \"post.updated\", post });\n return post;\n }\n async delete(id: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/posts/{id}\", { params: { id } });\n emit({ type: \"post.deleted\", id });\n }\n /** Restores a deleted post as it was (PostWrite); conflict when a live post took its slug. */\n async restore(id: string): Promise<Post> {\n const emit = this.http.captureChanges();\n const post: Post = await call(this.http, \"POST\", \"/posts/{id}/restore\", { params: { id } });\n emit({ type: \"post.restored\", post });\n return post;\n }\n /** Sets the cover to an inline image name (\"i-{uuid}\") of the post's folder, or clears it; resolves with its public URL (null until it has one). */\n async setCover(id: string, image: string | null): Promise<string | null> {\n const emit = this.http.captureChanges();\n const r: PostCover = await call(this.http, \"PUT\", \"/posts/{id}/cover\", { params: { id }, body: { image: image ?? \"\" } });\n emit({ type: \"post.changed\", id });\n return r.cover_url;\n }\n /** Uploads file to the post's folder and makes it the cover (null clears it); resolves with its public URL (null until it has one). */\n async uploadCover(id: string, file: Blob | null, o?: NamedOptions): Promise<string | null> {\n if (!file) return this.setCover(id, null);\n const up = await this.media.uploadNamed(this.media.postRef(id), file, o);\n return this.setCover(id, up.name);\n }\n /** An inline image of the post's folder: the reference to store in its body and a URL that shows it to the editor now (null while none exists). */\n inlineImage(id: string, image: string, signal?: AbortSignal): Promise<InlineImage> {\n return call(this.http, \"POST\", \"/posts/{id}/images\", { params: { id }, body: { image }, signal });\n }\n}\n"],"mappings":";;AAOA,IAAa,IAAe,eAOf,KAAY,MAAiB,IAAe,GAG5C,UAAkB,gFAkBlB,IAAb,MAAyB;CAEJ;CACA;CAFnB,YACE,GACA,GACA;EADiB,AADA,KAAA,OAAA,GACA,KAAA,QAAA;CAChB;CAGH,KAAK,IAAe,CAAC,GAAG,GAAuC;EAC7D,OAAO,EAAK,KAAK,MAAM,OAAO,UAAU;GAAE,OAAO;GAAG;EAAO,CAAC;CAC9D;CAEA,UAAU,IAAoB,CAAC,GAAG,GAAuC;EACvE,OAAO,EAAK,KAAK,MAAM,OAAO,gBAAgB;GAAE,OAAO;GAAG;EAAO,CAAC;CACpE;CACA,IAAI,GAAY,GAAqC;EACnD,OAAO,EAAK,KAAK,MAAM,OAAO,eAAe;GAAE,QAAQ,EAAE,MAAG;GAAG;EAAO,CAAC;CACzE;CAEA,MAAM,OAAO,GAAiC;EAC5C,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAa,MAAM,EAAK,KAAK,MAAM,QAAQ,UAAU,EAAE,MAAM,EAAM,CAAC;EAE1E,OADA,EAAK;GAAE,MAAM;GAAgB;EAAK,CAAC,GAC5B;CACT;CAEA,MAAM,OAAO,GAAY,GAAiC;EACxD,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAa,MAAM,EAAK,KAAK,MAAM,SAAS,eAAe;GAAE,QAAQ,EAAE,MAAG;GAAG,MAAM;EAAM,CAAC;EAEhG,OADA,EAAK;GAAE,MAAM;GAAgB;EAAK,CAAC,GAC5B;CACT;CACA,MAAM,OAAO,GAA2B;EACtC,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,UAAU,eAAe,EAAE,QAAQ,EAAE,MAAG,EAAE,CAAC,GACjE,EAAK;GAAE,MAAM;GAAgB;EAAG,CAAC;CACnC;CAEA,MAAM,QAAQ,GAA2B;EACvC,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAa,MAAM,EAAK,KAAK,MAAM,QAAQ,uBAAuB,EAAE,QAAQ,EAAE,MAAG,EAAE,CAAC;EAE1F,OADA,EAAK;GAAE,MAAM;GAAiB;EAAK,CAAC,GAC7B;CACT;CAEA,MAAM,SAAS,GAAY,GAA8C;EACvE,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAe,MAAM,EAAK,KAAK,MAAM,OAAO,qBAAqB;GAAE,QAAQ,EAAE,MAAG;GAAG,MAAM,EAAE,OAAO,KAAS,GAAG;EAAE,CAAC;EAEvH,OADA,EAAK;GAAE,MAAM;GAAgB;EAAG,CAAC,GAC1B,EAAE;CACX;CAEA,MAAM,YAAY,GAAY,GAAmB,GAA0C;EACzF,IAAI,CAAC,GAAM,OAAO,KAAK,SAAS,GAAI,IAAI;EACxC,IAAM,IAAK,MAAM,KAAK,MAAM,YAAY,KAAK,MAAM,QAAQ,CAAE,GAAG,GAAM,CAAC;EACvE,OAAO,KAAK,SAAS,GAAI,EAAG,IAAI;CAClC;CAEA,YAAY,GAAY,GAAe,GAA4C;EACjF,OAAO,EAAK,KAAK,MAAM,QAAQ,sBAAsB;GAAE,QAAQ,EAAE,MAAG;GAAG,MAAM,EAAE,SAAM;GAAG;EAAO,CAAC;CAClG;AACF"}
1
+ {"version":3,"file":"posts.js","names":[],"sources":["../../../src/client/content/posts.ts"],"sourcesContent":["import type { InlineImage, Post, PostCover, PostInput } from \"../generated/wire.js\";\nimport type { Http } from \"../http.js\";\nimport type { MediaClient, NamedOptions } from \"../media/client.js\";\nimport { call, type PageQuery } from \"../route.js\";\nimport type { Sort } from \"./types.js\";\n\n/** The URL scheme of an image reference in a post body. */\nexport const IMAGE_SCHEME = \"contentkit:\";\n\n/**\n * The reference a post body stores for one of its images, by name\n * (\"contentkit:i-{uuid}\"), where the image's URL would go: reads resolve it to\n * the image's current public file (`Post.images`).\n */\nexport const imageRef = (name: string) => IMAGE_SCHEME + name;\n\n/** Matches every image reference; the name is group 1. */\nexport const imageRefs = () => /contentkit:(i-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/g;\n\nexport interface PostQuery extends PageQuery {\n language?: string;\n sort?: Sort;\n}\n\nexport interface PostAdminQuery extends PageQuery {\n language?: string;\n /** true: only drafts; false: only posts that are not drafts. */\n draft?: boolean;\n /** true: only deleted posts (with deleted_at); otherwise only live ones. */\n deleted?: boolean;\n /** Only posts whose title, excerpt or body contains this text, ignoring case. */\n q?: string;\n}\n\n/** Posts: the published list, staff CRUD, search and restore, reactions, the cover and body images. */\nexport class PostsClient {\n constructor(\n private readonly http: Http,\n private readonly media: MediaClient,\n ) {}\n\n /** Published posts. */\n list(q: PostQuery = {}, signal?: AbortSignal): Promise<Post[]> {\n return call(this.http, \"GET\", \"/posts\", { query: q, signal });\n }\n /** Every post for staff: drafts, scheduled, held and rejected ones included; deleted ones on their own; q searches (root:posts:write). */\n adminList(q: PostAdminQuery = {}, signal?: AbortSignal): Promise<Post[]> {\n return call(this.http, \"GET\", \"/posts/admin\", { query: q, signal });\n }\n get(id: string, signal?: AbortSignal): Promise<Post> {\n return call(this.http, \"GET\", \"/posts/{id}\", { params: { id }, signal });\n }\n /** Creates a post (root:posts:write); moderation \"held\" when the moderator holds it. */\n async create(input: PostInput): Promise<Post> {\n const emit = this.http.captureChanges();\n const post: Post = await call(this.http, \"POST\", \"/posts\", { body: input });\n emit({ type: \"post.created\", post });\n return post;\n }\n /** Updates the given fields (root:posts:write). */\n async update(id: string, input: PostInput): Promise<Post> {\n const emit = this.http.captureChanges();\n const post: Post = await call(this.http, \"PATCH\", \"/posts/{id}\", { params: { id }, body: input });\n emit({ type: \"post.updated\", post });\n return post;\n }\n async delete(id: string): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"DELETE\", \"/posts/{id}\", { params: { id } });\n emit({ type: \"post.deleted\", id });\n }\n /** Restores a deleted post as it was (root:posts:write); conflict when a live post took its slug. */\n async restore(id: string): Promise<Post> {\n const emit = this.http.captureChanges();\n const post: Post = await call(this.http, \"POST\", \"/posts/{id}/restore\", { params: { id } });\n emit({ type: \"post.restored\", post });\n return post;\n }\n /** Sets the cover to an inline image name (\"i-{uuid}\") of the post's folder, or clears it; resolves with its public URL (null until it has one). */\n async setCover(id: string, image: string | null): Promise<string | null> {\n const emit = this.http.captureChanges();\n const r: PostCover = await call(this.http, \"PUT\", \"/posts/{id}/cover\", { params: { id }, body: { image: image ?? \"\" } });\n emit({ type: \"post.changed\", id });\n return r.cover_url;\n }\n /** Uploads file to the post's folder and makes it the cover (null clears it); resolves with its public URL (null until it has one). */\n async uploadCover(id: string, file: Blob | null, o?: NamedOptions): Promise<string | null> {\n if (!file) return this.setCover(id, null);\n const up = await this.media.uploadNamed(this.media.postRef(id), file, o);\n return this.setCover(id, up.name);\n }\n /** An inline image of the post's folder: the reference to store in its body and a URL that shows it to the editor now (null while none exists). */\n inlineImage(id: string, image: string, signal?: AbortSignal): Promise<InlineImage> {\n return call(this.http, \"POST\", \"/posts/{id}/images\", { params: { id }, body: { image }, signal });\n }\n}\n"],"mappings":";;AAOA,IAAa,IAAe,eAOf,KAAY,MAAiB,IAAe,GAG5C,UAAkB,gFAkBlB,IAAb,MAAyB;CAEJ;CACA;CAFnB,YACE,GACA,GACA;EADiB,AADA,KAAA,OAAA,GACA,KAAA,QAAA;CAChB;CAGH,KAAK,IAAe,CAAC,GAAG,GAAuC;EAC7D,OAAO,EAAK,KAAK,MAAM,OAAO,UAAU;GAAE,OAAO;GAAG;EAAO,CAAC;CAC9D;CAEA,UAAU,IAAoB,CAAC,GAAG,GAAuC;EACvE,OAAO,EAAK,KAAK,MAAM,OAAO,gBAAgB;GAAE,OAAO;GAAG;EAAO,CAAC;CACpE;CACA,IAAI,GAAY,GAAqC;EACnD,OAAO,EAAK,KAAK,MAAM,OAAO,eAAe;GAAE,QAAQ,EAAE,MAAG;GAAG;EAAO,CAAC;CACzE;CAEA,MAAM,OAAO,GAAiC;EAC5C,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAa,MAAM,EAAK,KAAK,MAAM,QAAQ,UAAU,EAAE,MAAM,EAAM,CAAC;EAE1E,OADA,EAAK;GAAE,MAAM;GAAgB;EAAK,CAAC,GAC5B;CACT;CAEA,MAAM,OAAO,GAAY,GAAiC;EACxD,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAa,MAAM,EAAK,KAAK,MAAM,SAAS,eAAe;GAAE,QAAQ,EAAE,MAAG;GAAG,MAAM;EAAM,CAAC;EAEhG,OADA,EAAK;GAAE,MAAM;GAAgB;EAAK,CAAC,GAC5B;CACT;CACA,MAAM,OAAO,GAA2B;EACtC,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,UAAU,eAAe,EAAE,QAAQ,EAAE,MAAG,EAAE,CAAC,GACjE,EAAK;GAAE,MAAM;GAAgB;EAAG,CAAC;CACnC;CAEA,MAAM,QAAQ,GAA2B;EACvC,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAa,MAAM,EAAK,KAAK,MAAM,QAAQ,uBAAuB,EAAE,QAAQ,EAAE,MAAG,EAAE,CAAC;EAE1F,OADA,EAAK;GAAE,MAAM;GAAiB;EAAK,CAAC,GAC7B;CACT;CAEA,MAAM,SAAS,GAAY,GAA8C;EACvE,IAAM,IAAO,KAAK,KAAK,eAAe,GAChC,IAAe,MAAM,EAAK,KAAK,MAAM,OAAO,qBAAqB;GAAE,QAAQ,EAAE,MAAG;GAAG,MAAM,EAAE,OAAO,KAAS,GAAG;EAAE,CAAC;EAEvH,OADA,EAAK;GAAE,MAAM;GAAgB;EAAG,CAAC,GAC1B,EAAE;CACX;CAEA,MAAM,YAAY,GAAY,GAAmB,GAA0C;EACzF,IAAI,CAAC,GAAM,OAAO,KAAK,SAAS,GAAI,IAAI;EACxC,IAAM,IAAK,MAAM,KAAK,MAAM,YAAY,KAAK,MAAM,QAAQ,CAAE,GAAG,GAAM,CAAC;EACvE,OAAO,KAAK,SAAS,GAAI,EAAG,IAAI;CAClC;CAEA,YAAY,GAAY,GAAe,GAA4C;EACjF,OAAO,EAAK,KAAK,MAAM,QAAQ,sBAAsB;GAAE,QAAQ,EAAE,MAAG;GAAG,MAAM,EAAE,SAAM;GAAG;EAAO,CAAC;CAClG;AACF"}
@@ -102,11 +102,11 @@ var e = {
102
102
  },
103
103
  unauthorized: {
104
104
  status: 401,
105
- meaning: "The route needs a signed-in actor."
105
+ meaning: "The request needs a valid credential: it has none where the route needs one, or one that is expired, revoked or invalid. WWW-Authenticate says which."
106
106
  },
107
107
  unavailable: {
108
108
  status: 503,
109
- meaning: "Media storage cannot be reached, or a commit's recovery is pending; retry."
109
+ meaning: "The host's auth or media storage cannot be reached, or a commit's recovery is pending; retry."
110
110
  },
111
111
  upgrade_required: {
112
112
  status: 503,
@@ -1 +1 @@
1
- {"version":3,"file":"error-codes.js","names":[],"sources":["../../../src/client/generated/error-codes.ts"],"sourcesContent":["// Generated by `go generate ./internal/contract` from ContentKit's error-code registry. Do not edit.\n\nexport type ErrorCode =\n | \"animation_not_allowed\"\n | \"animation_too_long\"\n | \"animation_unsupported\"\n | \"checksum_mismatch\"\n | \"comment_banned\"\n | \"comment_too_long\"\n | \"conflict\"\n | \"forbidden\"\n | \"gone\"\n | \"image_too_large\"\n | \"image_too_small\"\n | \"image_unreadable\"\n | \"incomplete\"\n | \"internal_error\"\n | \"invalid_request\"\n | \"moderation_rejected\"\n | \"not_configured\"\n | \"not_found\"\n | \"not_uploaded\"\n | \"quota_exceeded\"\n | \"rate_limited\"\n | \"tenant_mismatch\"\n | \"too_large\"\n | \"too_many_files\"\n | \"type_not_allowed\"\n | \"unauthorized\"\n | \"unavailable\"\n | \"upgrade_required\"\n | \"video_aspect_unsupported\"\n | \"video_over_budget\"\n | \"video_too_large\"\n | \"video_too_long\"\n;\n\n// Each code's HTTP status and meaning. A message on the wire is for people;\n// classify by code.\nexport const CONTENTKIT_ERROR_CODES: Readonly<Record<ErrorCode, { status: number; meaning: string }>> = {\n animation_not_allowed: { status: 422, meaning: \"An animated image where the upload path refuses animation.\" },\n animation_too_long: { status: 422, meaning: \"The animation has more frames or seconds than the processor allows.\" },\n animation_unsupported: { status: 415, meaning: \"An AVIF or HEIF image sequence, which is decoded as one frame.\" },\n checksum_mismatch: { status: 422, meaning: \"The stored bytes differ from the declared SHA-256.\" },\n comment_banned: { status: 403, meaning: \"The actor is banned from commenting on this target; `ban` says the scope, the reason and when it ends.\" },\n comment_too_long: { status: 422, meaning: \"The comment is longer than the site allows; `details.max` is the limit in characters.\" },\n conflict: { status: 409, meaning: \"A concurrent change, a path or slug already taken, or an operation id already used for another commit.\" },\n forbidden: { status: 403, meaning: \"The actor may not do this.\" },\n gone: { status: 410, meaning: \"The content was removed.\" },\n image_too_large: { status: 413, meaning: \"The image has more pixels or frames than the processor decodes.\" },\n image_too_small: { status: 422, meaning: \"The edited image is narrower than its public preset's minimum width.\" },\n image_unreadable: { status: 422, meaning: \"The file is not a decodable image of its declared type.\" },\n incomplete: { status: 409, meaning: \"A multipart upload is missing parts.\" },\n internal_error: { status: 500, meaning: \"A server fault; the cause is in the server log.\" },\n invalid_request: { status: 400, meaning: \"The request is malformed: its body, a query parameter or a path value.\" },\n moderation_rejected: { status: 422, meaning: \"The moderator refused the text; `error` says why.\" },\n not_configured: { status: 501, meaning: \"The capability needs a port the host did not configure; retrying does not help.\" },\n not_found: { status: 404, meaning: \"Nothing by that name, or nothing the actor may see.\" },\n not_uploaded: { status: 409, meaning: \"A commit names an upload that has not landed; `blobs` lists the ones to upload again.\" },\n quota_exceeded: { status: 413, meaning: \"The uploader's storage quota is used up.\" },\n rate_limited: { status: 429, meaning: \"Too many requests; retry after `retry_after` seconds (also the Retry-After header).\" },\n tenant_mismatch: { status: 500, meaning: \"A host port answered with another tenant's data: a configuration fault.\" },\n too_large: { status: 413, meaning: \"The file is larger than its upload path allows; `details` has `size` and `max_bytes`.\" },\n too_many_files: { status: 409, meaning: \"The commit would put more files in an upload path, or the item, than allowed; `details.max` is the limit.\" },\n type_not_allowed: { status: 415, meaning: \"The file type is not allowed in the upload path; `details.allowed` lists the types.\" },\n unauthorized: { status: 401, meaning: \"The route needs a signed-in actor.\" },\n unavailable: { status: 503, meaning: \"Media storage cannot be reached, or a commit's recovery is pending; retry.\" },\n upgrade_required: { status: 503, meaning: \"The media item was stored before v0.68 and is served once the host's media upgrade (contentkit_media_upgrade) has converted it.\" },\n video_aspect_unsupported: { status: 422, meaning: \"The video's display aspect is outside what its upload path accepts; `details` has `min_aspect` and `max_aspect` (width/height).\" },\n video_over_budget: { status: 422, meaning: \"The planned encode costs more than its upload path allows, or the source averages under a frame a second.\" },\n video_too_large: { status: 422, meaning: \"The video's frames are larger than its upload path allows.\" },\n video_too_long: { status: 422, meaning: \"The video or audio runs longer than its upload path allows.\" },\n};\n"],"mappings":";AAuCA,IAAa,IAA2F;CACtG,uBAAuB;EAAE,QAAQ;EAAK,SAAS;CAA6D;CAC5G,oBAAoB;EAAE,QAAQ;EAAK,SAAS;CAAsE;CAClH,uBAAuB;EAAE,QAAQ;EAAK,SAAS;CAAiE;CAChH,mBAAmB;EAAE,QAAQ;EAAK,SAAS;CAAqD;CAChG,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAAyG;CACjJ,kBAAkB;EAAE,QAAQ;EAAK,SAAS;CAAwF;CAClI,UAAU;EAAE,QAAQ;EAAK,SAAS;CAAyG;CAC3I,WAAW;EAAE,QAAQ;EAAK,SAAS;CAA6B;CAChE,MAAM;EAAE,QAAQ;EAAK,SAAS;CAA2B;CACzD,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAAkE;CAC3G,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAAuE;CAChH,kBAAkB;EAAE,QAAQ;EAAK,SAAS;CAA0D;CACpG,YAAY;EAAE,QAAQ;EAAK,SAAS;CAAuC;CAC3E,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAAkD;CAC1F,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAAyE;CAClH,qBAAqB;EAAE,QAAQ;EAAK,SAAS;CAAoD;CACjG,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAAkF;CAC1H,WAAW;EAAE,QAAQ;EAAK,SAAS;CAAsD;CACzF,cAAc;EAAE,QAAQ;EAAK,SAAS;CAAwF;CAC9H,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAA2C;CACnF,cAAc;EAAE,QAAQ;EAAK,SAAS;CAAsF;CAC5H,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAA0E;CACnH,WAAW;EAAE,QAAQ;EAAK,SAAS;CAAwF;CAC3H,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAA4G;CACpJ,kBAAkB;EAAE,QAAQ;EAAK,SAAS;CAAsF;CAChI,cAAc;EAAE,QAAQ;EAAK,SAAS;CAAqC;CAC3E,aAAa;EAAE,QAAQ;EAAK,SAAS;CAA6E;CAClH,kBAAkB;EAAE,QAAQ;EAAK,SAAS;CAAkI;CAC5K,0BAA0B;EAAE,QAAQ;EAAK,SAAS;CAAkI;CACpL,mBAAmB;EAAE,QAAQ;EAAK,SAAS;CAA4G;CACvJ,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAA6D;CACtG,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAA8D;AACxG"}
1
+ {"version":3,"file":"error-codes.js","names":[],"sources":["../../../src/client/generated/error-codes.ts"],"sourcesContent":["// Generated by `go generate ./internal/contract` from ContentKit's error-code registry. Do not edit.\n\nexport type ErrorCode =\n | \"animation_not_allowed\"\n | \"animation_too_long\"\n | \"animation_unsupported\"\n | \"checksum_mismatch\"\n | \"comment_banned\"\n | \"comment_too_long\"\n | \"conflict\"\n | \"forbidden\"\n | \"gone\"\n | \"image_too_large\"\n | \"image_too_small\"\n | \"image_unreadable\"\n | \"incomplete\"\n | \"internal_error\"\n | \"invalid_request\"\n | \"moderation_rejected\"\n | \"not_configured\"\n | \"not_found\"\n | \"not_uploaded\"\n | \"quota_exceeded\"\n | \"rate_limited\"\n | \"tenant_mismatch\"\n | \"too_large\"\n | \"too_many_files\"\n | \"type_not_allowed\"\n | \"unauthorized\"\n | \"unavailable\"\n | \"upgrade_required\"\n | \"video_aspect_unsupported\"\n | \"video_over_budget\"\n | \"video_too_large\"\n | \"video_too_long\"\n;\n\n// Each code's HTTP status and meaning. A message on the wire is for people;\n// classify by code.\nexport const CONTENTKIT_ERROR_CODES: Readonly<Record<ErrorCode, { status: number; meaning: string }>> = {\n animation_not_allowed: { status: 422, meaning: \"An animated image where the upload path refuses animation.\" },\n animation_too_long: { status: 422, meaning: \"The animation has more frames or seconds than the processor allows.\" },\n animation_unsupported: { status: 415, meaning: \"An AVIF or HEIF image sequence, which is decoded as one frame.\" },\n checksum_mismatch: { status: 422, meaning: \"The stored bytes differ from the declared SHA-256.\" },\n comment_banned: { status: 403, meaning: \"The actor is banned from commenting on this target; `ban` says the scope, the reason and when it ends.\" },\n comment_too_long: { status: 422, meaning: \"The comment is longer than the site allows; `details.max` is the limit in characters.\" },\n conflict: { status: 409, meaning: \"A concurrent change, a path or slug already taken, or an operation id already used for another commit.\" },\n forbidden: { status: 403, meaning: \"The actor may not do this.\" },\n gone: { status: 410, meaning: \"The content was removed.\" },\n image_too_large: { status: 413, meaning: \"The image has more pixels or frames than the processor decodes.\" },\n image_too_small: { status: 422, meaning: \"The edited image is narrower than its public preset's minimum width.\" },\n image_unreadable: { status: 422, meaning: \"The file is not a decodable image of its declared type.\" },\n incomplete: { status: 409, meaning: \"A multipart upload is missing parts.\" },\n internal_error: { status: 500, meaning: \"A server fault; the cause is in the server log.\" },\n invalid_request: { status: 400, meaning: \"The request is malformed: its body, a query parameter or a path value.\" },\n moderation_rejected: { status: 422, meaning: \"The moderator refused the text; `error` says why.\" },\n not_configured: { status: 501, meaning: \"The capability needs a port the host did not configure; retrying does not help.\" },\n not_found: { status: 404, meaning: \"Nothing by that name, or nothing the actor may see.\" },\n not_uploaded: { status: 409, meaning: \"A commit names an upload that has not landed; `blobs` lists the ones to upload again.\" },\n quota_exceeded: { status: 413, meaning: \"The uploader's storage quota is used up.\" },\n rate_limited: { status: 429, meaning: \"Too many requests; retry after `retry_after` seconds (also the Retry-After header).\" },\n tenant_mismatch: { status: 500, meaning: \"A host port answered with another tenant's data: a configuration fault.\" },\n too_large: { status: 413, meaning: \"The file is larger than its upload path allows; `details` has `size` and `max_bytes`.\" },\n too_many_files: { status: 409, meaning: \"The commit would put more files in an upload path, or the item, than allowed; `details.max` is the limit.\" },\n type_not_allowed: { status: 415, meaning: \"The file type is not allowed in the upload path; `details.allowed` lists the types.\" },\n unauthorized: { status: 401, meaning: \"The request needs a valid credential: it has none where the route needs one, or one that is expired, revoked or invalid. WWW-Authenticate says which.\" },\n unavailable: { status: 503, meaning: \"The host's auth or media storage cannot be reached, or a commit's recovery is pending; retry.\" },\n upgrade_required: { status: 503, meaning: \"The media item was stored before v0.68 and is served once the host's media upgrade (contentkit_media_upgrade) has converted it.\" },\n video_aspect_unsupported: { status: 422, meaning: \"The video's display aspect is outside what its upload path accepts; `details` has `min_aspect` and `max_aspect` (width/height).\" },\n video_over_budget: { status: 422, meaning: \"The planned encode costs more than its upload path allows, or the source averages under a frame a second.\" },\n video_too_large: { status: 422, meaning: \"The video's frames are larger than its upload path allows.\" },\n video_too_long: { status: 422, meaning: \"The video or audio runs longer than its upload path allows.\" },\n};\n"],"mappings":";AAuCA,IAAa,IAA2F;CACtG,uBAAuB;EAAE,QAAQ;EAAK,SAAS;CAA6D;CAC5G,oBAAoB;EAAE,QAAQ;EAAK,SAAS;CAAsE;CAClH,uBAAuB;EAAE,QAAQ;EAAK,SAAS;CAAiE;CAChH,mBAAmB;EAAE,QAAQ;EAAK,SAAS;CAAqD;CAChG,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAAyG;CACjJ,kBAAkB;EAAE,QAAQ;EAAK,SAAS;CAAwF;CAClI,UAAU;EAAE,QAAQ;EAAK,SAAS;CAAyG;CAC3I,WAAW;EAAE,QAAQ;EAAK,SAAS;CAA6B;CAChE,MAAM;EAAE,QAAQ;EAAK,SAAS;CAA2B;CACzD,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAAkE;CAC3G,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAAuE;CAChH,kBAAkB;EAAE,QAAQ;EAAK,SAAS;CAA0D;CACpG,YAAY;EAAE,QAAQ;EAAK,SAAS;CAAuC;CAC3E,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAAkD;CAC1F,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAAyE;CAClH,qBAAqB;EAAE,QAAQ;EAAK,SAAS;CAAoD;CACjG,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAAkF;CAC1H,WAAW;EAAE,QAAQ;EAAK,SAAS;CAAsD;CACzF,cAAc;EAAE,QAAQ;EAAK,SAAS;CAAwF;CAC9H,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAA2C;CACnF,cAAc;EAAE,QAAQ;EAAK,SAAS;CAAsF;CAC5H,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAA0E;CACnH,WAAW;EAAE,QAAQ;EAAK,SAAS;CAAwF;CAC3H,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAA4G;CACpJ,kBAAkB;EAAE,QAAQ;EAAK,SAAS;CAAsF;CAChI,cAAc;EAAE,QAAQ;EAAK,SAAS;CAAwJ;CAC9L,aAAa;EAAE,QAAQ;EAAK,SAAS;CAAgG;CACrI,kBAAkB;EAAE,QAAQ;EAAK,SAAS;CAAkI;CAC5K,0BAA0B;EAAE,QAAQ;EAAK,SAAS;CAAkI;CACpL,mBAAmB;EAAE,QAAQ;EAAK,SAAS;CAA4G;CACvJ,iBAAiB;EAAE,QAAQ;EAAK,SAAS;CAA6D;CACtG,gBAAgB;EAAE,QAAQ;EAAK,SAAS;CAA8D;AACxG"}