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

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 (211) hide show
  1. package/README.md +28 -7
  2. package/dist/client/beacons.d.ts +13 -4
  3. package/dist/client/beacons.js +14 -7
  4. package/dist/client/beacons.js.map +1 -1
  5. package/dist/client/client.d.ts +3 -0
  6. package/dist/client/client.js +29 -27
  7. package/dist/client/client.js.map +1 -1
  8. package/dist/client/codes.d.ts +6 -1
  9. package/dist/client/codes.js +4 -1
  10. package/dist/client/codes.js.map +1 -1
  11. package/dist/client/content/types.d.ts +4 -2
  12. package/dist/client/content/types.js.map +1 -1
  13. package/dist/client/discovery.js +3 -0
  14. package/dist/client/discovery.js.map +1 -1
  15. package/dist/client/errors.d.ts +6 -0
  16. package/dist/client/errors.js +6 -2
  17. package/dist/client/errors.js.map +1 -1
  18. package/dist/client/gallery.d.ts +3 -5
  19. package/dist/client/gallery.js +16 -20
  20. package/dist/client/gallery.js.map +1 -1
  21. package/dist/client/generated/error-codes.d.ts +1 -1
  22. package/dist/client/generated/error-codes.js +9 -1
  23. package/dist/client/generated/error-codes.js.map +1 -1
  24. package/dist/client/generated/routes.d.ts +29 -3
  25. package/dist/client/generated/routes.js +44 -3
  26. package/dist/client/generated/routes.js.map +1 -1
  27. package/dist/client/generated/wire.d.ts +60 -10
  28. package/dist/client/generated/wire.js.map +1 -1
  29. package/dist/client/index.d.ts +2 -1
  30. package/dist/client/media/queue.d.ts +4 -2
  31. package/dist/client/media/queue.js +18 -12
  32. package/dist/client/media/queue.js.map +1 -1
  33. package/dist/client/media/rules.d.ts +7 -1
  34. package/dist/client/media/rules.js +27 -21
  35. package/dist/client/media/rules.js.map +1 -1
  36. package/dist/client/media/windows.js +1 -1
  37. package/dist/client/media/windows.js.map +1 -1
  38. package/dist/client/stats.d.ts +32 -0
  39. package/dist/client/stats.js +35 -0
  40. package/dist/client/stats.js.map +1 -0
  41. package/dist/client/versions.d.ts +6 -4
  42. package/dist/client/versions.js +24 -26
  43. package/dist/client/versions.js.map +1 -1
  44. package/dist/client.js +30 -29
  45. package/dist/components/admin/content-manager.js +206 -206
  46. package/dist/components/admin/content-manager.js.map +1 -1
  47. package/dist/components/admin/group-manager.js +71 -70
  48. package/dist/components/admin/group-manager.js.map +1 -1
  49. package/dist/components/admin/media-item-panel.d.ts +24 -7
  50. package/dist/components/admin/media-item-panel.js +209 -197
  51. package/dist/components/admin/media-item-panel.js.map +1 -1
  52. package/dist/components/admin/poll-manager.js +25 -25
  53. package/dist/components/admin/stats-dashboard.d.ts +22 -0
  54. package/dist/components/admin/stats-dashboard.js +257 -0
  55. package/dist/components/admin/stats-dashboard.js.map +1 -0
  56. package/dist/components/content/card.d.ts +5 -3
  57. package/dist/components/content/card.js +155 -133
  58. package/dist/components/content/card.js.map +1 -1
  59. package/dist/components/content/download-button.d.ts +26 -0
  60. package/dist/components/content/download-button.js +51 -0
  61. package/dist/components/content/download-button.js.map +1 -0
  62. package/dist/components/content/editors.js +455 -427
  63. package/dist/components/content/editors.js.map +1 -1
  64. package/dist/components/content/grid.d.ts +8 -1
  65. package/dist/components/content/grid.js +153 -104
  66. package/dist/components/content/grid.js.map +1 -1
  67. package/dist/components/content/index.d.ts +4 -2
  68. package/dist/components/content/listing.d.ts +3 -1
  69. package/dist/components/content/listing.js +34 -34
  70. package/dist/components/content/listing.js.map +1 -1
  71. package/dist/components/content/paywall.d.ts +17 -5
  72. package/dist/components/content/paywall.js +115 -100
  73. package/dist/components/content/paywall.js.map +1 -1
  74. package/dist/components/content/reader-stats.d.ts +12 -0
  75. package/dist/components/content/reader-stats.js +47 -0
  76. package/dist/components/content/reader-stats.js.map +1 -0
  77. package/dist/components/groups/banner.js +33 -33
  78. package/dist/components/groups/directory.js +11 -11
  79. package/dist/components/groups/editor.js +293 -293
  80. package/dist/components/groups/editor.js.map +1 -1
  81. package/dist/components/groups/header.js +27 -27
  82. package/dist/components/groups/header.js.map +1 -1
  83. package/dist/components/groups/memberships.js +180 -180
  84. package/dist/components/groups/parts.d.ts +0 -2
  85. package/dist/components/groups/parts.js +9 -27
  86. package/dist/components/groups/parts.js.map +1 -1
  87. package/dist/components/media-folder-editor.js +239 -189
  88. package/dist/components/media-folder-editor.js.map +1 -1
  89. package/dist/components/media-gallery.d.ts +1 -4
  90. package/dist/components/media-gallery.js +19 -34
  91. package/dist/components/media-gallery.js.map +1 -1
  92. package/dist/components/notifications.js +17 -17
  93. package/dist/components/slot-upload.d.ts +2 -2
  94. package/dist/components/slot-upload.js +84 -79
  95. package/dist/components/slot-upload.js.map +1 -1
  96. package/dist/components/social/bans.js +34 -34
  97. package/dist/components/social/comments.js +1 -1
  98. package/dist/components/social/comments.js.map +1 -1
  99. package/dist/components/social/engagement.d.ts +2 -1
  100. package/dist/components/social/engagement.js +5 -3
  101. package/dist/components/social/engagement.js.map +1 -1
  102. package/dist/components/social/moderation.js +52 -48
  103. package/dist/components/social/moderation.js.map +1 -1
  104. package/dist/components/video-player.d.ts +2 -0
  105. package/dist/components/video-player.js +318 -314
  106. package/dist/components/video-player.js.map +1 -1
  107. package/dist/i18n/context.d.ts +2 -0
  108. package/dist/i18n/context.js +23 -5
  109. package/dist/i18n/context.js.map +1 -1
  110. package/dist/index.d.ts +2 -1
  111. package/dist/index.js +13 -10
  112. package/dist/locales/admin/de.js +27 -0
  113. package/dist/locales/admin/de.js.map +1 -1
  114. package/dist/locales/admin/en.d.ts +27 -0
  115. package/dist/locales/admin/en.js +27 -0
  116. package/dist/locales/admin/en.js.map +1 -1
  117. package/dist/locales/admin/es.js +27 -0
  118. package/dist/locales/admin/es.js.map +1 -1
  119. package/dist/locales/admin/ja.js +27 -0
  120. package/dist/locales/admin/ja.js.map +1 -1
  121. package/dist/locales/admin/ko.js +27 -0
  122. package/dist/locales/admin/ko.js.map +1 -1
  123. package/dist/locales/admin/zh.js +27 -0
  124. package/dist/locales/admin/zh.js.map +1 -1
  125. package/dist/locales/content/de.js +6 -0
  126. package/dist/locales/content/de.js.map +1 -1
  127. package/dist/locales/content/en.d.ts +6 -0
  128. package/dist/locales/content/en.js +6 -0
  129. package/dist/locales/content/en.js.map +1 -1
  130. package/dist/locales/content/es.js +6 -0
  131. package/dist/locales/content/es.js.map +1 -1
  132. package/dist/locales/content/ja.js +6 -0
  133. package/dist/locales/content/ja.js.map +1 -1
  134. package/dist/locales/content/ko.js +6 -0
  135. package/dist/locales/content/ko.js.map +1 -1
  136. package/dist/locales/content/zh.js +6 -0
  137. package/dist/locales/content/zh.js.map +1 -1
  138. package/dist/locales/de.js +14 -2
  139. package/dist/locales/de.js.map +1 -1
  140. package/dist/locales/discovery/de.js +3 -0
  141. package/dist/locales/discovery/de.js.map +1 -1
  142. package/dist/locales/discovery/en.d.ts +3 -0
  143. package/dist/locales/discovery/en.js +3 -0
  144. package/dist/locales/discovery/en.js.map +1 -1
  145. package/dist/locales/discovery/es.js +3 -0
  146. package/dist/locales/discovery/es.js.map +1 -1
  147. package/dist/locales/discovery/ja.js +3 -0
  148. package/dist/locales/discovery/ja.js.map +1 -1
  149. package/dist/locales/discovery/ko.js +3 -0
  150. package/dist/locales/discovery/ko.js.map +1 -1
  151. package/dist/locales/discovery/zh.js +3 -0
  152. package/dist/locales/discovery/zh.js.map +1 -1
  153. package/dist/locales/en.d.ts +50 -2
  154. package/dist/locales/en.js +14 -2
  155. package/dist/locales/en.js.map +1 -1
  156. package/dist/locales/es.js +14 -2
  157. package/dist/locales/es.js.map +1 -1
  158. package/dist/locales/ja.js +14 -2
  159. package/dist/locales/ja.js.map +1 -1
  160. package/dist/locales/ko.js +14 -2
  161. package/dist/locales/ko.js.map +1 -1
  162. package/dist/locales/zh.js +14 -2
  163. package/dist/locales/zh.js.map +1 -1
  164. package/dist/react/admin.js +22 -23
  165. package/dist/react/admin.js.map +1 -1
  166. package/dist/react/canonical.d.ts +4 -3
  167. package/dist/react/canonical.js +34 -33
  168. package/dist/react/canonical.js.map +1 -1
  169. package/dist/react/comments.d.ts +1 -1
  170. package/dist/react/comments.js +3 -2
  171. package/dist/react/comments.js.map +1 -1
  172. package/dist/react/contents.js +144 -144
  173. package/dist/react/contents.js.map +1 -1
  174. package/dist/react/context.d.ts +1 -1
  175. package/dist/react/context.js.map +1 -1
  176. package/dist/react/detail.d.ts +12 -6
  177. package/dist/react/detail.js +31 -24
  178. package/dist/react/detail.js.map +1 -1
  179. package/dist/react/discovery.d.ts +11 -0
  180. package/dist/react/discovery.js +49 -30
  181. package/dist/react/discovery.js.map +1 -1
  182. package/dist/react/engagement.d.ts +6 -2
  183. package/dist/react/engagement.js +2 -6
  184. package/dist/react/engagement.js.map +1 -1
  185. package/dist/react/folder.d.ts +7 -2
  186. package/dist/react/folder.js +165 -147
  187. package/dist/react/folder.js.map +1 -1
  188. package/dist/react/groups.d.ts +0 -1
  189. package/dist/react/groups.js +13 -12
  190. package/dist/react/groups.js.map +1 -1
  191. package/dist/react/index.d.ts +6 -4
  192. package/dist/react/location.d.ts +11 -0
  193. package/dist/react/location.js +18 -0
  194. package/dist/react/location.js.map +1 -0
  195. package/dist/react/public.d.ts +8 -0
  196. package/dist/react/public.js +32 -27
  197. package/dist/react/public.js.map +1 -1
  198. package/dist/react/stats.d.ts +24 -0
  199. package/dist/react/stats.js +28 -0
  200. package/dist/react/stats.js.map +1 -0
  201. package/dist/react/use-resource.js +8 -7
  202. package/dist/react/use-resource.js.map +1 -1
  203. package/dist/react/versions.d.ts +25 -5
  204. package/dist/react/versions.js +61 -40
  205. package/dist/react/versions.js.map +1 -1
  206. package/dist/react.js +26 -24
  207. package/dist/styles.css +1 -1
  208. package/dist/urls/index.d.ts +35 -10
  209. package/dist/urls.js +100 -70
  210. package/dist/urls.js.map +1 -1
  211. package/package.json +1 -1
@@ -3,19 +3,19 @@ import { fill as t, publicURL as n } from "../public.js";
3
3
  import { ratio as r } from "../aspect.js";
4
4
  import { stem as i } from "./client.js";
5
5
  //#region src/client/media/rules.ts
6
- var a = (e) => e?.uploads ?? [], o = "{name}", s = (e) => e.endsWith(o), c = (e) => s(e) ? e.slice(0, -6) : e;
7
- function l(e, t) {
6
+ var a = [], o = (e) => e?.uploads ?? a, s = "{name}", c = (e) => e.endsWith(s), l = (e) => c(e) ? e.slice(0, -6) : e;
7
+ function u(e, t) {
8
8
  let n = i(t);
9
- return e.find((e) => !s(e.path) && e.path === n) ?? e.find((e) => {
10
- if (!s(e.path)) return !1;
11
- let t = c(e.path);
9
+ return e.find((e) => !c(e.path) && e.path === n) ?? e.find((e) => {
10
+ if (!c(e.path)) return !1;
11
+ let t = l(e.path);
12
12
  return n.startsWith(t) && n.length > t.length && !n.slice(t.length).includes("/");
13
13
  });
14
14
  }
15
- function u(e, t, n = [t]) {
16
- return (e?.files ?? []).filter((e) => e.upload && !e.unattached && l(n, e.path)?.path === t.path);
15
+ function d(e, t, n = [t]) {
16
+ return (e?.files ?? []).filter((e) => e.upload && !e.unattached && u(n, e.path)?.path === t.path);
17
17
  }
18
- var d = {
18
+ var f = {
19
19
  jpg: "image/jpeg",
20
20
  jpeg: "image/jpeg",
21
21
  png: "image/png",
@@ -35,27 +35,27 @@ var d = {
35
35
  ssa: "text/x-ssa",
36
36
  zip: "application/zip"
37
37
  };
38
- function f(e, t) {
38
+ function p(e, t) {
39
39
  if (e.type && (!t || t.includes(e.type))) return e;
40
- let n = d[e.name.split(".").pop()?.toLowerCase() ?? ""];
40
+ let n = f[e.name.split(".").pop()?.toLowerCase() ?? ""];
41
41
  return n && n !== e.type ? new File([e], e.name, {
42
42
  type: n,
43
43
  lastModified: e.lastModified
44
44
  }) : e;
45
45
  }
46
- function p(e) {
46
+ function m(e) {
47
47
  let t = [...new Set(e.flatMap((e) => e.types))];
48
48
  if (!t.length) return;
49
- let n = Object.entries(d).filter(([, e]) => t.includes(e)).map(([e]) => `.${e}`);
49
+ let n = Object.entries(f).filter(([, e]) => t.includes(e)).map(([e]) => `.${e}`);
50
50
  return [...t, ...n].join(",");
51
51
  }
52
- function m(t, n, r = () => 0) {
52
+ function h(t, n, r = () => 0) {
53
53
  let i = {
54
54
  accepted: [],
55
55
  refused: []
56
56
  }, a = /* @__PURE__ */ new Map(), o = [...new Set(n.flatMap((e) => e.types))];
57
57
  for (let s of t) {
58
- let t = f(s, o), c = n.find((e) => !e.types.length || e.types.includes(t.type));
58
+ let t = p(s, o), c = n.find((e) => !e.types.length || e.types.includes(t.type));
59
59
  if (!c) {
60
60
  let n = {
61
61
  type: t.type || void 0,
@@ -104,7 +104,7 @@ function m(t, n, r = () => 0) {
104
104
  }
105
105
  return i;
106
106
  }
107
- function h(e, t) {
107
+ function g(e, t) {
108
108
  let n = new Set([...t].map(i));
109
109
  if (!n.has(i(e))) return e;
110
110
  let r = e.lastIndexOf("."), a = r > 0 ? e.slice(0, r) : e, o = r > 0 ? e.slice(r) : "";
@@ -113,17 +113,23 @@ function h(e, t) {
113
113
  if (!n.has(i(t))) return t;
114
114
  }
115
115
  }
116
- function g(e) {
116
+ function _(e) {
117
117
  let t = (e) => String(Math.round(e * 100) / 100);
118
118
  return e >= 1 ? `${t(e)}:1` : `1:${t(1 / e)}`;
119
119
  }
120
- function _(e, t, n, r) {
121
- let i = e.filter((e) => e.kind === t && l([{ path: e.from }], n));
120
+ function v(e, t, n, r) {
121
+ let i = e.filter((e) => e.kind === t && u([{ path: e.from }], n));
122
122
  return (r ? i.find((e) => e.name === r) : void 0) ?? i.find((e) => e.aspect) ?? i[0];
123
123
  }
124
- function v(e, i) {
124
+ function y(e, t) {
125
+ return !e.default || e.first || e.to.includes(s) ? null : x(e, t);
126
+ }
127
+ function b(e, t) {
128
+ return e.stable ? x(e, t) : null;
129
+ }
130
+ function x(e, i) {
125
131
  let a = r(e.aspect);
126
- return !e.default || e.first || e.to.includes(o) ? null : {
132
+ return {
127
133
  preset: e.name,
128
134
  aspect: e.aspect,
129
135
  renditions: e.widths.map((r) => ({
@@ -134,6 +140,6 @@ function v(e, i) {
134
140
  };
135
141
  }
136
142
  //#endregion
137
- export { p as acceptOf, v as defaultImage, u as filesFor, s as isPattern, _ as presetFor, g as ratioLabel, c as ruleDir, l as ruleFor, m as screenFiles, h as uniqueName, a as uploadRules, f as withMediaType };
143
+ export { m as acceptOf, y as defaultImage, d as filesFor, c as isPattern, v as presetFor, _ as ratioLabel, l as ruleDir, u as ruleFor, h as screenFiles, b as stableImage, g as uniqueName, o as uploadRules, p as withMediaType };
138
144
 
139
145
  //# sourceMappingURL=rules.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rules.js","names":[],"sources":["../../../src/client/media/rules.ts"],"sourcesContent":["import { ContentKitError } from \"../errors.js\";\nimport type { ErrorDetails, PresetRule, ReadResult, UploadRule } from \"../generated/wire.js\";\nimport { fill, publicURL, type PublicPreset } from \"../public.js\";\nimport { ratio } from \"../aspect.js\";\nimport { stem } from \"./client.js\";\n\n/** An editor read's upload rules (a viewer read has none). */\nexport const uploadRules = (read: ReadResult | null | undefined): UploadRule[] => read?.uploads ?? [];\n\nconst NAME = \"{name}\";\n\n/** A pattern path (\"originals/{name}\") holds many files; a literal (\"cover\") one. */\nexport const isPattern = (path: string) => path.endsWith(NAME);\n\n/** The directory of a pattern (\"originals/\"), or the literal itself. */\nexport const ruleDir = (path: string) => (isPattern(path) ? path.slice(0, -NAME.length) : path);\n\n/** The rule an upload path (with or without its extension) falls under: a literal wins over a pattern. */\nexport function ruleFor<R extends { path: string }>(rules: readonly R[], path: string): R | undefined {\n const s = stem(path);\n return (\n rules.find((r) => !isPattern(r.path) && r.path === s) ??\n rules.find((r) => {\n if (!isPattern(r.path)) return false;\n const dir = ruleDir(r.path);\n return s.startsWith(dir) && s.length > dir.length && !s.slice(dir.length).includes(\"/\");\n })\n );\n}\n\n/** Uploads of the read under rule (attached, in manifest order). */\nexport function filesFor(read: ReadResult | null | undefined, rule: { path: string }, rules: readonly { path: string }[] = [rule]) {\n return (read?.files ?? []).filter((f) => f.upload && !f.unattached && ruleFor(rules, f.path)?.path === rule.path);\n}\n\nconst BY_EXTENSION: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n webp: \"image/webp\",\n gif: \"image/gif\",\n avif: \"image/avif\",\n mp4: \"video/mp4\",\n m4v: \"video/mp4\",\n mov: \"video/quicktime\",\n mkv: \"video/x-matroska\",\n webm: \"video/webm\",\n m4a: \"audio/mp4\",\n mp3: \"audio/mpeg\",\n vtt: \"text/vtt\",\n srt: \"application/x-subrip\",\n ass: \"text/x-ass\",\n ssa: \"text/x-ssa\",\n zip: \"application/zip\",\n};\n\n/** The file with a content type from its extension when the browser left it empty or nonstandard (.mkv, .mov, .srt). */\nexport function withMediaType(file: File, allowed?: readonly string[]): File {\n if (file.type && (!allowed || allowed.includes(file.type))) return file;\n const type = BY_EXTENSION[file.name.split(\".\").pop()?.toLowerCase() ?? \"\"];\n return type && type !== file.type ? new File([file], file.name, { type, lastModified: file.lastModified }) : file;\n}\n\n/** `accept` for a file input from the rules' types (plus the extensions browsers leave untyped). */\nexport function acceptOf(rules: readonly UploadRule[]): string | undefined {\n const types = [...new Set(rules.flatMap((r) => r.types))];\n if (!types.length) return undefined;\n const exts = Object.entries(BY_EXTENSION).filter(([, t]) => types.includes(t)).map(([e]) => `.${e}`);\n return [...types, ...exts].join(\",\");\n}\n\nexport interface Screened {\n /** Files to upload, each with its rule (typed by extension when needed). */\n accepted: { file: File; rule: UploadRule }[];\n /** Files the server would refuse, with the refusal it would answer. */\n refused: { file: File; error: ContentKitError }[];\n}\n\n/**\n * Checks files against the rules before any upload: a type some rule takes\n * (the first that does), its size, and the rule's file cap counting\n * have(rule) files already there. A rule without types, size or cap lets\n * that check through; the server decides.\n */\nexport function screenFiles(files: Iterable<File>, rules: readonly UploadRule[], have: (rule: UploadRule) => number = () => 0): Screened {\n const out: Screened = { accepted: [], refused: [] };\n const counts = new Map<UploadRule, number>();\n const allowed = [...new Set(rules.flatMap((r) => r.types))];\n for (const picked of files) {\n const file = withMediaType(picked, allowed);\n const rule = rules.find((r) => !r.types.length || r.types.includes(file.type));\n if (!rule) {\n const details: ErrorDetails = { type: file.type || undefined, allowed };\n out.refused.push({ file, error: new ContentKitError(\"type_not_allowed\", `${file.name}: type not allowed`, { status: 415, details }) });\n continue;\n }\n if (rule.max_bytes > 0 && file.size > rule.max_bytes) {\n const details: ErrorDetails = { type: file.type, size: file.size, max_bytes: rule.max_bytes };\n out.refused.push({ file, error: new ContentKitError(\"too_large\", `${file.name}: too large`, { status: 413, details }) });\n continue;\n }\n const n = counts.get(rule) ?? have(rule);\n if (rule.max && n >= rule.max) {\n const details: ErrorDetails = { max: rule.max };\n out.refused.push({ file, error: new ContentKitError(\"too_many_files\", `${file.name}: at most ${rule.max} files`, { status: 409, details }) });\n continue;\n }\n counts.set(rule, n + 1);\n out.accepted.push({ file, rule });\n }\n return out;\n}\n\n/** name, or name-2, name-3… so its stem is not among taken (names or paths without the directory). */\nexport function uniqueName(name: string, taken: Iterable<string>): string {\n const stems = new Set([...taken].map(stem));\n if (!stems.has(stem(name))) return name;\n const dot = name.lastIndexOf(\".\");\n const base = dot > 0 ? name.slice(0, dot) : name;\n const ext = dot > 0 ? name.slice(dot) : \"\";\n for (let n = 2; ; n++) {\n const candidate = `${base}-${n}${ext}`;\n if (!stems.has(stem(candidate))) return candidate;\n }\n}\n\n/** \"16:9\" style label of a width/height ratio: 2.4 → \"2.4:1\", 0.4167 → \"1:2.4\". */\nexport function ratioLabel(r: number): string {\n const round = (v: number) => String(Math.round(v * 100) / 100);\n return r >= 1 ? `${round(r)}:1` : `1:${round(1 / r)}`;\n}\n\n/** The kind's public preset for an upload path: the one named name, else the first with an aspect, else the first. */\nexport function presetFor(presets: readonly PresetRule[], kind: string, path: string, name?: string): PresetRule | undefined {\n const at = presets.filter((p) => p.kind === kind && ruleFor([{ path: p.from }], path));\n return (name ? at.find((p) => p.name === name) : undefined) ?? at.find((p) => p.aspect) ?? at[0];\n}\n\n/**\n * The kind's default image for a preset, at every width: what the gateway\n * serves for an item without its own (a URL from the template never names a\n * current file). null when the preset has no default (`rule.default`).\n */\nexport function defaultImage(rule: PresetRule, id: string): PublicPreset | null {\n const r = ratio(rule.aspect);\n if (!rule.default || rule.first || rule.to.includes(NAME)) return null;\n return {\n preset: rule.name,\n aspect: rule.aspect,\n renditions: rule.widths.map((w) => ({ url: publicURL(rule.base, rule.namespace, rule.kind, id, fill(rule.to, { w })), w, ...(r ? { h: Math.round(w / r) } : {}) })),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"rules.js","names":[],"sources":["../../../src/client/media/rules.ts"],"sourcesContent":["import { ContentKitError } from \"../errors.js\";\nimport type { ErrorDetails, PresetRule, ReadResult, UploadRule } from \"../generated/wire.js\";\nimport { fill, publicURL, type PublicPreset } from \"../public.js\";\nimport { ratio } from \"../aspect.js\";\nimport { stem } from \"./client.js\";\n\nconst NO_RULES: UploadRule[] = [];\n\n/** An editor read's upload rules (a viewer read has none): one empty list, so memos keyed on them hold. */\nexport const uploadRules = (read: ReadResult | null | undefined): UploadRule[] => read?.uploads ?? NO_RULES;\n\nconst NAME = \"{name}\";\n\n/** A pattern path (\"originals/{name}\") holds many files; a literal (\"cover\") one. */\nexport const isPattern = (path: string) => path.endsWith(NAME);\n\n/** The directory of a pattern (\"originals/\"), or the literal itself. */\nexport const ruleDir = (path: string) => (isPattern(path) ? path.slice(0, -NAME.length) : path);\n\n/** The rule an upload path (with or without its extension) falls under: a literal wins over a pattern. */\nexport function ruleFor<R extends { path: string }>(rules: readonly R[], path: string): R | undefined {\n const s = stem(path);\n return (\n rules.find((r) => !isPattern(r.path) && r.path === s) ??\n rules.find((r) => {\n if (!isPattern(r.path)) return false;\n const dir = ruleDir(r.path);\n return s.startsWith(dir) && s.length > dir.length && !s.slice(dir.length).includes(\"/\");\n })\n );\n}\n\n/** Uploads of the read under rule (attached, in manifest order). */\nexport function filesFor(read: ReadResult | null | undefined, rule: { path: string }, rules: readonly { path: string }[] = [rule]) {\n return (read?.files ?? []).filter((f) => f.upload && !f.unattached && ruleFor(rules, f.path)?.path === rule.path);\n}\n\nconst BY_EXTENSION: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n webp: \"image/webp\",\n gif: \"image/gif\",\n avif: \"image/avif\",\n mp4: \"video/mp4\",\n m4v: \"video/mp4\",\n mov: \"video/quicktime\",\n mkv: \"video/x-matroska\",\n webm: \"video/webm\",\n m4a: \"audio/mp4\",\n mp3: \"audio/mpeg\",\n vtt: \"text/vtt\",\n srt: \"application/x-subrip\",\n ass: \"text/x-ass\",\n ssa: \"text/x-ssa\",\n zip: \"application/zip\",\n};\n\n/** The file with a content type from its extension when the browser left it empty or nonstandard (.mkv, .mov, .srt). */\nexport function withMediaType(file: File, allowed?: readonly string[]): File {\n if (file.type && (!allowed || allowed.includes(file.type))) return file;\n const type = BY_EXTENSION[file.name.split(\".\").pop()?.toLowerCase() ?? \"\"];\n return type && type !== file.type ? new File([file], file.name, { type, lastModified: file.lastModified }) : file;\n}\n\n/** `accept` for a file input from the rules' types (plus the extensions browsers leave untyped). */\nexport function acceptOf(rules: readonly UploadRule[]): string | undefined {\n const types = [...new Set(rules.flatMap((r) => r.types))];\n if (!types.length) return undefined;\n const exts = Object.entries(BY_EXTENSION).filter(([, t]) => types.includes(t)).map(([e]) => `.${e}`);\n return [...types, ...exts].join(\",\");\n}\n\nexport interface Screened {\n /** Files to upload, each with its rule (typed by extension when needed). */\n accepted: { file: File; rule: UploadRule }[];\n /** Files the server would refuse, with the refusal it would answer. */\n refused: { file: File; error: ContentKitError }[];\n}\n\n/**\n * Checks files against the rules before any upload: a type some rule takes\n * (the first that does), its size, and the rule's file cap counting\n * have(rule) files already there. A rule without types, size or cap lets\n * that check through; the server decides.\n */\nexport function screenFiles(files: Iterable<File>, rules: readonly UploadRule[], have: (rule: UploadRule) => number = () => 0): Screened {\n const out: Screened = { accepted: [], refused: [] };\n const counts = new Map<UploadRule, number>();\n const allowed = [...new Set(rules.flatMap((r) => r.types))];\n for (const picked of files) {\n const file = withMediaType(picked, allowed);\n const rule = rules.find((r) => !r.types.length || r.types.includes(file.type));\n if (!rule) {\n const details: ErrorDetails = { type: file.type || undefined, allowed };\n out.refused.push({ file, error: new ContentKitError(\"type_not_allowed\", `${file.name}: type not allowed`, { status: 415, details }) });\n continue;\n }\n if (rule.max_bytes > 0 && file.size > rule.max_bytes) {\n const details: ErrorDetails = { type: file.type, size: file.size, max_bytes: rule.max_bytes };\n out.refused.push({ file, error: new ContentKitError(\"too_large\", `${file.name}: too large`, { status: 413, details }) });\n continue;\n }\n const n = counts.get(rule) ?? have(rule);\n if (rule.max && n >= rule.max) {\n const details: ErrorDetails = { max: rule.max };\n out.refused.push({ file, error: new ContentKitError(\"too_many_files\", `${file.name}: at most ${rule.max} files`, { status: 409, details }) });\n continue;\n }\n counts.set(rule, n + 1);\n out.accepted.push({ file, rule });\n }\n return out;\n}\n\n/** name, or name-2, name-3… so its stem is not among taken (names or paths without the directory). */\nexport function uniqueName(name: string, taken: Iterable<string>): string {\n const stems = new Set([...taken].map(stem));\n if (!stems.has(stem(name))) return name;\n const dot = name.lastIndexOf(\".\");\n const base = dot > 0 ? name.slice(0, dot) : name;\n const ext = dot > 0 ? name.slice(dot) : \"\";\n for (let n = 2; ; n++) {\n const candidate = `${base}-${n}${ext}`;\n if (!stems.has(stem(candidate))) return candidate;\n }\n}\n\n/** \"16:9\" style label of a width/height ratio: 2.4 → \"2.4:1\", 0.4167 → \"1:2.4\". */\nexport function ratioLabel(r: number): string {\n const round = (v: number) => String(Math.round(v * 100) / 100);\n return r >= 1 ? `${round(r)}:1` : `1:${round(1 / r)}`;\n}\n\n/** The kind's public preset for an upload path: the one named name, else the first with an aspect, else the first. */\nexport function presetFor(presets: readonly PresetRule[], kind: string, path: string, name?: string): PresetRule | undefined {\n const at = presets.filter((p) => p.kind === kind && ruleFor([{ path: p.from }], path));\n return (name ? at.find((p) => p.name === name) : undefined) ?? at.find((p) => p.aspect) ?? at[0];\n}\n\n/**\n * The kind's default image for a preset, at every width: what the gateway\n * serves for an item without its own (a URL from the template never names a\n * current file). null when the preset has no default (`rule.default`).\n */\nexport function defaultImage(rule: PresetRule, id: string): PublicPreset | null {\n if (!rule.default || rule.first || rule.to.includes(NAME)) return null;\n return logicalImage(rule, id);\n}\n\n/**\n * A Stable preset's image of an item (`rule.stable`, e.g. the content\n * model's `avatars`): URLs built from the id that show its current image,\n * else the default. null for any other preset.\n */\nexport function stableImage(rule: PresetRule, id: string): PublicPreset | null {\n return rule.stable ? logicalImage(rule, id) : null;\n}\n\nfunction logicalImage(rule: PresetRule, id: string): PublicPreset {\n const r = ratio(rule.aspect);\n return {\n preset: rule.name,\n aspect: rule.aspect,\n renditions: rule.widths.map((w) => ({ url: publicURL(rule.base, rule.namespace, rule.kind, id, fill(rule.to, { w })), w, ...(r ? { h: Math.round(w / r) } : {}) })),\n };\n}\n"],"mappings":"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"}
@@ -13,7 +13,7 @@ function n(e) {
13
13
  let n = t.reduce((e, t) => t.expires >= e.expires ? t : e), r = /* @__PURE__ */ new Map();
14
14
  for (let e of [...t].sort((e, t) => e.expires - t.expires)) for (let t of e.files) t.url && r.set(t.path, t);
15
15
  let i = n.files.map((e) => {
16
- if (e.url || e.locked) return e;
16
+ if (e.url) return e;
17
17
  let t = r.get(e.path);
18
18
  return t ? {
19
19
  ...e,
@@ -1 +1 @@
1
- {"version":3,"file":"windows.js","names":[],"sources":["../../../src/client/media/windows.ts"],"sourcesContent":["import type { FileInfo, ReadResult } from \"../generated/wire.js\";\n\n/** Files per window when a read is split into windows. */\nexport const READ_CHUNK = 50;\n\n/** Offsets of chunk-sized windows covering files [start, end), clipped to total when known. */\nexport function chunkOffsets(start: number, end: number, total?: number, chunk = READ_CHUNK): number[] {\n const out: number[] = [];\n const stop = total === undefined ? end : Math.min(end, total);\n if (start >= stop) return out;\n for (let o = Math.floor(Math.max(start, 0) / chunk) * chunk; o < stop; o += chunk) out.push(o);\n return out;\n}\n\n/**\n * One item's window reads as one read: the newest window's listing and\n * metadata, each file's URL from the newest window that signed it, and the\n * earliest expiry. Every window lists every file; only its range has URLs.\n */\nexport function mergeReads(reads: readonly (ReadResult | null | undefined)[]): ReadResult | null {\n const present = reads.filter((r): r is ReadResult => !!r);\n if (present.length === 0) return null;\n if (present.length === 1) return present[0]!;\n const newest = present.reduce((a, b) => (b.expires >= a.expires ? b : a));\n const urls = new Map<string, FileInfo>();\n for (const r of [...present].sort((a, b) => a.expires - b.expires)) for (const f of r.files) if (f.url) urls.set(f.path, f);\n const files = newest.files.map((f) => {\n if (f.url || f.locked) return f;\n const signed = urls.get(f.path);\n return signed ? { ...f, url: signed.url, ...(signed.download ? { download: signed.download } : {}) } : f;\n });\n const start = Math.min(...present.map((r) => r.offset));\n const end = Math.max(...present.map((r) => r.offset + r.limit));\n return { ...newest, files, offset: start, limit: end - start, expires: Math.min(...present.map((r) => r.expires)) };\n}\n\n/** An upload the worker has not finished: staged, or with work pending, and not failed. */\nexport const isProcessing = (f: FileInfo) => !!f.upload && !f.failed && (!!f.staged || (f.pending?.length ?? 0) > 0);\n\n/** An editor read with uploads still processing (not stalled because the item is full). */\nexport function processing(read: ReadResult | null | undefined): boolean {\n if (!read || read.full) return false;\n return read.state === \"processing\" || read.files.some(isProcessing);\n}\n"],"mappings":";AAGA,IAAa,IAAa;AAG1B,SAAgB,EAAa,GAAe,GAAa,GAAgB,IAAA,IAA8B;CACrG,IAAM,IAAgB,CAAC,GACjB,IAAO,MAAU,KAAA,IAAY,IAAM,KAAK,IAAI,GAAK,CAAK;CAC5D,IAAI,KAAS,GAAM,OAAO;CAC1B,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI,GAAO,CAAC,IAAI,CAAK,IAAI,GAAO,IAAI,GAAM,KAAK,GAAO,EAAI,KAAK,CAAC;CAC7F,OAAO;AACT;AAOA,SAAgB,EAAW,GAAsE;CAC/F,IAAM,IAAU,EAAM,QAAQ,MAAuB,CAAC,CAAC,CAAC;CACxD,IAAI,EAAQ,WAAW,GAAG,OAAO;CACjC,IAAI,EAAQ,WAAW,GAAG,OAAO,EAAQ;CACzC,IAAM,IAAS,EAAQ,QAAQ,GAAG,MAAO,EAAE,WAAW,EAAE,UAAU,IAAI,CAAE,GAClE,oBAAO,IAAI,IAAsB;CACvC,KAAK,IAAM,KAAK,CAAC,GAAG,CAAO,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,KAAK,IAAM,KAAK,EAAE,OAAO,AAAI,EAAE,OAAK,EAAK,IAAI,EAAE,MAAM,CAAC;CAC1H,IAAM,IAAQ,EAAO,MAAM,KAAK,MAAM;EACpC,IAAI,EAAE,OAAO,EAAE,QAAQ,OAAO;EAC9B,IAAM,IAAS,EAAK,IAAI,EAAE,IAAI;EAC9B,OAAO,IAAS;GAAE,GAAG;GAAG,KAAK,EAAO;GAAK,GAAI,EAAO,WAAW,EAAE,UAAU,EAAO,SAAS,IAAI,CAAC;EAAG,IAAI;CACzG,CAAC,GACK,IAAQ,KAAK,IAAI,GAAG,EAAQ,KAAK,MAAM,EAAE,MAAM,CAAC,GAChD,IAAM,KAAK,IAAI,GAAG,EAAQ,KAAK,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC;CAC9D,OAAO;EAAE,GAAG;EAAQ;EAAO,QAAQ;EAAO,OAAO,IAAM;EAAO,SAAS,KAAK,IAAI,GAAG,EAAQ,KAAK,MAAM,EAAE,OAAO,CAAC;CAAE;AACpH;AAGA,IAAa,KAAgB,MAAgB,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,UAAU,KAAK;AAGlH,SAAgB,EAAW,GAA8C;CAEvE,OADI,CAAC,KAAQ,EAAK,OAAa,KACxB,EAAK,UAAU,gBAAgB,EAAK,MAAM,KAAK,CAAY;AACpE"}
1
+ {"version":3,"file":"windows.js","names":[],"sources":["../../../src/client/media/windows.ts"],"sourcesContent":["import type { FileInfo, ReadResult } from \"../generated/wire.js\";\n\n/** Files per window when a read is split into windows. */\nexport const READ_CHUNK = 50;\n\n/** Offsets of chunk-sized windows covering files [start, end), clipped to total when known. */\nexport function chunkOffsets(start: number, end: number, total?: number, chunk = READ_CHUNK): number[] {\n const out: number[] = [];\n const stop = total === undefined ? end : Math.min(end, total);\n if (start >= stop) return out;\n for (let o = Math.floor(Math.max(start, 0) / chunk) * chunk; o < stop; o += chunk) out.push(o);\n return out;\n}\n\n/**\n * One item's window reads as one read: the newest window's listing and\n * metadata, each file's URL from the newest window that signed it, and the\n * earliest expiry. Every window lists every file; only its range has URLs.\n */\nexport function mergeReads(reads: readonly (ReadResult | null | undefined)[]): ReadResult | null {\n const present = reads.filter((r): r is ReadResult => !!r);\n if (present.length === 0) return null;\n if (present.length === 1) return present[0]!;\n const newest = present.reduce((a, b) => (b.expires >= a.expires ? b : a));\n const urls = new Map<string, FileInfo>();\n for (const r of [...present].sort((a, b) => a.expires - b.expires)) for (const f of r.files) if (f.url) urls.set(f.path, f);\n const files = newest.files.map((f) => {\n if (f.url) return f;\n const signed = urls.get(f.path);\n return signed ? { ...f, url: signed.url, ...(signed.download ? { download: signed.download } : {}) } : f;\n });\n const start = Math.min(...present.map((r) => r.offset));\n const end = Math.max(...present.map((r) => r.offset + r.limit));\n return { ...newest, files, offset: start, limit: end - start, expires: Math.min(...present.map((r) => r.expires)) };\n}\n\n/** An upload the worker has not finished: staged, or with work pending, and not failed. */\nexport const isProcessing = (f: FileInfo) => !!f.upload && !f.failed && (!!f.staged || (f.pending?.length ?? 0) > 0);\n\n/** An editor read with uploads still processing (not stalled because the item is full). */\nexport function processing(read: ReadResult | null | undefined): boolean {\n if (!read || read.full) return false;\n return read.state === \"processing\" || read.files.some(isProcessing);\n}\n"],"mappings":";AAGA,IAAa,IAAa;AAG1B,SAAgB,EAAa,GAAe,GAAa,GAAgB,IAAA,IAA8B;CACrG,IAAM,IAAgB,CAAC,GACjB,IAAO,MAAU,KAAA,IAAY,IAAM,KAAK,IAAI,GAAK,CAAK;CAC5D,IAAI,KAAS,GAAM,OAAO;CAC1B,KAAK,IAAI,IAAI,KAAK,MAAM,KAAK,IAAI,GAAO,CAAC,IAAI,CAAK,IAAI,GAAO,IAAI,GAAM,KAAK,GAAO,EAAI,KAAK,CAAC;CAC7F,OAAO;AACT;AAOA,SAAgB,EAAW,GAAsE;CAC/F,IAAM,IAAU,EAAM,QAAQ,MAAuB,CAAC,CAAC,CAAC;CACxD,IAAI,EAAQ,WAAW,GAAG,OAAO;CACjC,IAAI,EAAQ,WAAW,GAAG,OAAO,EAAQ;CACzC,IAAM,IAAS,EAAQ,QAAQ,GAAG,MAAO,EAAE,WAAW,EAAE,UAAU,IAAI,CAAE,GAClE,oBAAO,IAAI,IAAsB;CACvC,KAAK,IAAM,KAAK,CAAC,GAAG,CAAO,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,KAAK,IAAM,KAAK,EAAE,OAAO,AAAI,EAAE,OAAK,EAAK,IAAI,EAAE,MAAM,CAAC;CAC1H,IAAM,IAAQ,EAAO,MAAM,KAAK,MAAM;EACpC,IAAI,EAAE,KAAK,OAAO;EAClB,IAAM,IAAS,EAAK,IAAI,EAAE,IAAI;EAC9B,OAAO,IAAS;GAAE,GAAG;GAAG,KAAK,EAAO;GAAK,GAAI,EAAO,WAAW,EAAE,UAAU,EAAO,SAAS,IAAI,CAAC;EAAG,IAAI;CACzG,CAAC,GACK,IAAQ,KAAK,IAAI,GAAG,EAAQ,KAAK,MAAM,EAAE,MAAM,CAAC,GAChD,IAAM,KAAK,IAAI,GAAG,EAAQ,KAAK,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC;CAC9D,OAAO;EAAE,GAAG;EAAQ;EAAO,QAAQ;EAAO,OAAO,IAAM;EAAO,SAAS,KAAK,IAAI,GAAG,EAAQ,KAAK,MAAM,EAAE,OAAO,CAAC;CAAE;AACpH;AAGA,IAAa,KAAgB,MAAgB,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,UAAU,KAAK;AAGlH,SAAgB,EAAW,GAA8C;CAEvE,OADI,CAAC,KAAQ,EAAK,OAAa,KACxB,EAAK,UAAU,gBAAgB,EAAK,MAAM,KAAK,CAAY;AACpE"}
@@ -0,0 +1,32 @@
1
+ import { Activity, MyStats, TopContents } from './generated/wire.js';
2
+ import { Period } from './discovery.js';
3
+ import { Http } from './http.js';
4
+ export interface ActivityQuery {
5
+ kind: string;
6
+ /** The last days, today included: 7 by default, at most 90. */
7
+ days?: number;
8
+ }
9
+ export interface TopQuery {
10
+ kind: string;
11
+ /** views (the default) or comments. */
12
+ metric?: "views" | "comments";
13
+ /** 30d by default. */
14
+ period?: Period;
15
+ access?: readonly ("public" | "tier" | "group" | "ppv" | "group_ppv")[];
16
+ tiers?: readonly string[];
17
+ /** Titles and paths in this language; the site's first by default. */
18
+ language?: string;
19
+ /** 10 by default, at most 100. */
20
+ limit?: number;
21
+ }
22
+ /** Content statistics: staff activity and rankings (root:content:view-restricted), and the signed-in reader's own. */
23
+ export declare class StatsClient {
24
+ private readonly http;
25
+ constructor(http: Http);
26
+ /** A kind's views per UTC day, newest first: view sessions, distinct viewers and signed-in viewers. */
27
+ activity(q: ActivityQuery, signal?: AbortSignal): Promise<Activity>;
28
+ /** A kind's live, listed content ranked by views or published comments over a period. */
29
+ top(q: TopQuery, signal?: AbortSignal): Promise<TopContents>;
30
+ /** The signed-in reader's statistics: contents finished per kind, comments and the reactions they received. */
31
+ mine(signal?: AbortSignal): Promise<MyStats>;
32
+ }
@@ -0,0 +1,35 @@
1
+ import { call as e } from "./route.js";
2
+ //#region src/client/stats.ts
3
+ var t = class {
4
+ http;
5
+ constructor(e) {
6
+ this.http = e;
7
+ }
8
+ activity(t, n) {
9
+ return e(this.http, "GET", "/stats/activity", {
10
+ query: {
11
+ kind: t.kind,
12
+ days: t.days
13
+ },
14
+ signal: n
15
+ });
16
+ }
17
+ top(t, n) {
18
+ let { access: r, tiers: i, ...a } = t;
19
+ return e(this.http, "GET", "/stats/top", {
20
+ query: {
21
+ ...a,
22
+ access: r?.join(","),
23
+ tiers: i?.join(",")
24
+ },
25
+ signal: n
26
+ });
27
+ }
28
+ mine(t) {
29
+ return e(this.http, "GET", "/me/stats", { signal: t });
30
+ }
31
+ };
32
+ //#endregion
33
+ export { t as StatsClient };
34
+
35
+ //# sourceMappingURL=stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.js","names":[],"sources":["../../src/client/stats.ts"],"sourcesContent":["import type { Activity, MyStats, TopContents } from \"./generated/wire.js\";\nimport type { Period } from \"./discovery.js\";\nimport type { Http } from \"./http.js\";\nimport { call } from \"./route.js\";\n\nexport interface ActivityQuery {\n kind: string;\n /** The last days, today included: 7 by default, at most 90. */\n days?: number;\n}\n\nexport interface TopQuery {\n kind: string;\n /** views (the default) or comments. */\n metric?: \"views\" | \"comments\";\n /** 30d by default. */\n period?: Period;\n access?: readonly (\"public\" | \"tier\" | \"group\" | \"ppv\" | \"group_ppv\")[];\n tiers?: readonly string[];\n /** Titles and paths in this language; the site's first by default. */\n language?: string;\n /** 10 by default, at most 100. */\n limit?: number;\n}\n\n/** Content statistics: staff activity and rankings (root:content:view-restricted), and the signed-in reader's own. */\nexport class StatsClient {\n constructor(private readonly http: Http) {}\n\n /** A kind's views per UTC day, newest first: view sessions, distinct viewers and signed-in viewers. */\n activity(q: ActivityQuery, signal?: AbortSignal): Promise<Activity> {\n return call(this.http, \"GET\", \"/stats/activity\", { query: { kind: q.kind, days: q.days }, signal });\n }\n /** A kind's live, listed content ranked by views or published comments over a period. */\n top(q: TopQuery, signal?: AbortSignal): Promise<TopContents> {\n const { access, tiers, ...rest } = q;\n return call(this.http, \"GET\", \"/stats/top\", { query: { ...rest, access: access?.join(\",\"), tiers: tiers?.join(\",\") }, signal });\n }\n /** The signed-in reader's statistics: contents finished per kind, comments and the reactions they received. */\n mine(signal?: AbortSignal): Promise<MyStats> {\n return call(this.http, \"GET\", \"/me/stats\", { signal });\n }\n}\n"],"mappings":";;AA0BA,IAAa,IAAb,MAAyB;CACM;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,SAAS,GAAkB,GAAyC;EAClE,OAAO,EAAK,KAAK,MAAM,OAAO,mBAAmB;GAAE,OAAO;IAAE,MAAM,EAAE;IAAM,MAAM,EAAE;GAAK;GAAG;EAAO,CAAC;CACpG;CAEA,IAAI,GAAa,GAA4C;EAC3D,IAAM,EAAE,WAAQ,UAAO,GAAG,MAAS;EACnC,OAAO,EAAK,KAAK,MAAM,OAAO,cAAc;GAAE,OAAO;IAAE,GAAG;IAAM,QAAQ,GAAQ,KAAK,GAAG;IAAG,OAAO,GAAO,KAAK,GAAG;GAAE;GAAG;EAAO,CAAC;CAChI;CAEA,KAAK,GAAwC;EAC3C,OAAO,EAAK,KAAK,MAAM,OAAO,aAAa,EAAE,UAAO,CAAC;CACvD;AACF"}
@@ -1,4 +1,4 @@
1
- import { ForkBody, Pending, ReaderMeta, ReaderPlan } from './generated/wire.js';
1
+ import { FileDownload, ForkBody, Pending, ReaderMeta, ReaderPlan } from './generated/wire.js';
2
2
  import { Http } from './http.js';
3
3
  /** A version's answer while it is prepared (202). */
4
4
  export declare const isPending: (x: unknown) => x is Pending;
@@ -13,8 +13,10 @@ export declare class VersionsClient {
13
13
  /** Editors: fills a version without media from a sibling, in a durable job. */
14
14
  fork(content: string, version: string, body: ForkBody): Promise<Pending>;
15
15
  /**
16
- * The version's pages as a zip, charged to the viewer's download quota;
17
- * Pending while it is prepared (ask again).
16
+ * Grants the version's download, what its kind serves (the original, a
17
+ * rendition, its pages as a zip), charged to the viewer's download quota:
18
+ * `url` (absolute) serves it as an attachment until `expires_at`. Pending
19
+ * while it is prepared (ask again).
18
20
  */
19
- download(content: string, version: string, signal?: AbortSignal): Promise<Blob | Pending>;
21
+ download(content: string, version: string, signal?: AbortSignal): Promise<FileDownload | Pending>;
20
22
  }
@@ -1,58 +1,56 @@
1
- import { ContentKitError as e } from "./errors.js";
2
- import { call as t, routeURL as n } from "./route.js";
1
+ import { call as e, routeURL as t } from "./route.js";
3
2
  //#region src/client/versions.ts
4
- var r = (e) => !!e && typeof e == "object" && "status" in e && !("pages" in e), i = class {
3
+ var n = (e) => !!e && typeof e == "object" && "status" in e && !("pages" in e) && !("url" in e), r = class {
5
4
  http;
6
5
  constructor(e) {
7
6
  this.http = e;
8
7
  }
9
- reader(e, n, r) {
10
- return t(this.http, "GET", "/contents/{id}/versions/{version}/reader", {
8
+ reader(t, n, r) {
9
+ return e(this.http, "GET", "/contents/{id}/versions/{version}/reader", {
11
10
  params: {
12
- id: e,
11
+ id: t,
13
12
  version: n
14
13
  },
15
14
  signal: r
16
15
  });
17
16
  }
18
- async setReader(e, n, r) {
17
+ async setReader(t, n, r) {
19
18
  let i = this.http.captureChanges();
20
- await t(this.http, "PUT", "/contents/{id}/versions/{version}/reader", {
19
+ await e(this.http, "PUT", "/contents/{id}/versions/{version}/reader", {
21
20
  params: {
22
- id: e,
21
+ id: t,
23
22
  version: n
24
23
  },
25
24
  body: r
26
25
  }), i({
27
26
  type: "reader.changed",
28
- content: e,
27
+ content: t,
29
28
  version: n
30
29
  });
31
30
  }
32
- fork(e, n, r) {
33
- return t(this.http, "POST", "/contents/{id}/versions/{version}/fork", {
31
+ fork(t, n, r) {
32
+ return e(this.http, "POST", "/contents/{id}/versions/{version}/fork", {
34
33
  params: {
35
- id: e,
34
+ id: t,
36
35
  version: n
37
36
  },
38
37
  body: r
39
38
  });
40
39
  }
41
- async download(t, i, a) {
42
- let o = n(this.http, "GET", "/contents/{id}/versions/{version}/download", { params: {
43
- id: t,
44
- version: i
45
- } }), s = await this.http.request(o, {
46
- signal: a,
47
- blob: !0
48
- });
49
- if (!s.type.startsWith("application/json")) return s;
50
- let c = JSON.parse(await s.text());
51
- if (r(c)) return c;
52
- throw new e("internal_error", "unexpected download answer");
40
+ async download(e, r, i) {
41
+ let a = t(this.http, "GET", "/contents/{id}/versions/{version}/download", { params: {
42
+ id: e,
43
+ version: r
44
+ } }), o = await this.http.request(a, { signal: i });
45
+ if (n(o)) return o;
46
+ let s = new URL(a, globalThis.location?.href ?? "http://localhost/");
47
+ return {
48
+ ...o,
49
+ url: new URL(o.url, s).toString()
50
+ };
53
51
  }
54
52
  };
55
53
  //#endregion
56
- export { i as VersionsClient, r as isPending };
54
+ export { r as VersionsClient, n as isPending };
57
55
 
58
56
  //# sourceMappingURL=versions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"versions.js","names":[],"sources":["../../src/client/versions.ts"],"sourcesContent":["import { ContentKitError } from \"./errors.js\";\nimport type { ForkBody, Pending, ReaderMeta, ReaderPlan } from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call, routeURL } from \"./route.js\";\n\n/** A version's answer while it is prepared (202). */\nexport const isPending = (x: unknown): x is Pending => !!x && typeof x === \"object\" && \"status\" in x && !(\"pages\" in x);\n\n/** Content versions: reader plans, reader metadata, forks and downloads. */\nexport class VersionsClient {\n constructor(private readonly http: Http) {}\n\n /** The version's reader plan for this viewer; Pending while it has no current publication. */\n reader(content: string, version: string, signal?: AbortSignal): Promise<ReaderPlan | Pending> {\n return call(this.http, \"GET\", \"/contents/{id}/versions/{version}/reader\", { params: { id: content, version }, signal });\n }\n /** Editors: replaces the version's reader metadata (anchors, audio cues, defaults). */\n async setReader(content: string, version: string, meta: ReaderMeta): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"PUT\", \"/contents/{id}/versions/{version}/reader\", { params: { id: content, version }, body: meta });\n emit({ type: \"reader.changed\", content, version });\n }\n /** Editors: fills a version without media from a sibling, in a durable job. */\n fork(content: string, version: string, body: ForkBody): Promise<Pending> {\n return call(this.http, \"POST\", \"/contents/{id}/versions/{version}/fork\", { params: { id: content, version }, body });\n }\n /**\n * The version's pages as a zip, charged to the viewer's download quota;\n * Pending while it is prepared (ask again).\n */\n async download(content: string, version: string, signal?: AbortSignal): Promise<Blob | Pending> {\n const url = routeURL(this.http, \"GET\", \"/contents/{id}/versions/{version}/download\", { params: { id: content, version } });\n const out = await this.http.request<Blob>(url, { signal, blob: true });\n if (!out.type.startsWith(\"application/json\")) return out;\n const body: unknown = JSON.parse(await out.text());\n if (isPending(body)) return body;\n throw new ContentKitError(\"internal_error\", \"unexpected download answer\");\n }\n}\n"],"mappings":";;;AAMA,IAAa,KAAa,MAA6B,CAAC,CAAC,KAAK,OAAO,KAAM,YAAY,YAAY,KAAK,EAAE,WAAW,IAGxG,IAAb,MAA4B;CACG;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,OAAO,GAAiB,GAAiB,GAAqD;EAC5F,OAAO,EAAK,KAAK,MAAM,OAAO,4CAA4C;GAAE,QAAQ;IAAE,IAAI;IAAS;GAAQ;GAAG;EAAO,CAAC;CACxH;CAEA,MAAM,UAAU,GAAiB,GAAiB,GAAiC;EACjF,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,OAAO,4CAA4C;GAAE,QAAQ;IAAE,IAAI;IAAS;GAAQ;GAAG,MAAM;EAAK,CAAC,GACzH,EAAK;GAAE,MAAM;GAAkB;GAAS;EAAQ,CAAC;CACnD;CAEA,KAAK,GAAiB,GAAiB,GAAkC;EACvE,OAAO,EAAK,KAAK,MAAM,QAAQ,0CAA0C;GAAE,QAAQ;IAAE,IAAI;IAAS;GAAQ;GAAG;EAAK,CAAC;CACrH;CAKA,MAAM,SAAS,GAAiB,GAAiB,GAA+C;EAC9F,IAAM,IAAM,EAAS,KAAK,MAAM,OAAO,8CAA8C,EAAE,QAAQ;GAAE,IAAI;GAAS;EAAQ,EAAE,CAAC,GACnH,IAAM,MAAM,KAAK,KAAK,QAAc,GAAK;GAAE;GAAQ,MAAM;EAAK,CAAC;EACrE,IAAI,CAAC,EAAI,KAAK,WAAW,kBAAkB,GAAG,OAAO;EACrD,IAAM,IAAgB,KAAK,MAAM,MAAM,EAAI,KAAK,CAAC;EACjD,IAAI,EAAU,CAAI,GAAG,OAAO;EAC5B,MAAM,IAAI,EAAgB,kBAAkB,4BAA4B;CAC1E;AACF"}
1
+ {"version":3,"file":"versions.js","names":[],"sources":["../../src/client/versions.ts"],"sourcesContent":["import type { FileDownload, ForkBody, Pending, ReaderMeta, ReaderPlan } from \"./generated/wire.js\";\nimport type { Http } from \"./http.js\";\nimport { call, routeURL } from \"./route.js\";\n\n/** A version's answer while it is prepared (202). */\nexport const isPending = (x: unknown): x is Pending => !!x && typeof x === \"object\" && \"status\" in x && !(\"pages\" in x) && !(\"url\" in x);\n\n/** Content versions: reader plans, reader metadata, forks and downloads. */\nexport class VersionsClient {\n constructor(private readonly http: Http) {}\n\n /** The version's reader plan for this viewer; Pending while it has no current publication. */\n reader(content: string, version: string, signal?: AbortSignal): Promise<ReaderPlan | Pending> {\n return call(this.http, \"GET\", \"/contents/{id}/versions/{version}/reader\", { params: { id: content, version }, signal });\n }\n /** Editors: replaces the version's reader metadata (anchors, audio cues, defaults). */\n async setReader(content: string, version: string, meta: ReaderMeta): Promise<void> {\n const emit = this.http.captureChanges();\n await call(this.http, \"PUT\", \"/contents/{id}/versions/{version}/reader\", { params: { id: content, version }, body: meta });\n emit({ type: \"reader.changed\", content, version });\n }\n /** Editors: fills a version without media from a sibling, in a durable job. */\n fork(content: string, version: string, body: ForkBody): Promise<Pending> {\n return call(this.http, \"POST\", \"/contents/{id}/versions/{version}/fork\", { params: { id: content, version }, body });\n }\n /**\n * Grants the version's download, what its kind serves (the original, a\n * rendition, its pages as a zip), charged to the viewer's download quota:\n * `url` (absolute) serves it as an attachment until `expires_at`. Pending\n * while it is prepared (ask again).\n */\n async download(content: string, version: string, signal?: AbortSignal): Promise<FileDownload | Pending> {\n const url = routeURL(this.http, \"GET\", \"/contents/{id}/versions/{version}/download\", { params: { id: content, version } });\n const out = await this.http.request<FileDownload | Pending>(url, { signal });\n if (isPending(out)) return out;\n // A marked zip's URL is the archive route's, relative to this one.\n const base = new URL(url, globalThis.location?.href ?? \"http://localhost/\");\n return { ...out, url: new URL(out.url, base).toString() };\n }\n}\n"],"mappings":";;AAKA,IAAa,KAAa,MAA6B,CAAC,CAAC,KAAK,OAAO,KAAM,YAAY,YAAY,KAAK,EAAE,WAAW,MAAM,EAAE,SAAS,IAGzH,IAAb,MAA4B;CACG;CAA7B,YAAY,GAA6B;EAAZ,KAAA,OAAA;CAAa;CAG1C,OAAO,GAAiB,GAAiB,GAAqD;EAC5F,OAAO,EAAK,KAAK,MAAM,OAAO,4CAA4C;GAAE,QAAQ;IAAE,IAAI;IAAS;GAAQ;GAAG;EAAO,CAAC;CACxH;CAEA,MAAM,UAAU,GAAiB,GAAiB,GAAiC;EACjF,IAAM,IAAO,KAAK,KAAK,eAAe;EAEtC,AADA,MAAM,EAAK,KAAK,MAAM,OAAO,4CAA4C;GAAE,QAAQ;IAAE,IAAI;IAAS;GAAQ;GAAG,MAAM;EAAK,CAAC,GACzH,EAAK;GAAE,MAAM;GAAkB;GAAS;EAAQ,CAAC;CACnD;CAEA,KAAK,GAAiB,GAAiB,GAAkC;EACvE,OAAO,EAAK,KAAK,MAAM,QAAQ,0CAA0C;GAAE,QAAQ;IAAE,IAAI;IAAS;GAAQ;GAAG;EAAK,CAAC;CACrH;CAOA,MAAM,SAAS,GAAiB,GAAiB,GAAuD;EACtG,IAAM,IAAM,EAAS,KAAK,MAAM,OAAO,8CAA8C,EAAE,QAAQ;GAAE,IAAI;GAAS;EAAQ,EAAE,CAAC,GACnH,IAAM,MAAM,KAAK,KAAK,QAAgC,GAAK,EAAE,UAAO,CAAC;EAC3E,IAAI,EAAU,CAAG,GAAG,OAAO;EAE3B,IAAM,IAAO,IAAI,IAAI,GAAK,WAAW,UAAU,QAAQ,mBAAmB;EAC1E,OAAO;GAAE,GAAG;GAAK,KAAK,IAAI,IAAI,EAAI,KAAK,CAAI,CAAC,CAAC,SAAS;EAAE;CAC1D;AACF"}
package/dist/client.js CHANGED
@@ -10,32 +10,33 @@ import { sha256Hex as D } from "./client/media/hash.js";
10
10
  import { Pacer as O } from "./client/media/pacer.js";
11
11
  import { defaultTransport as k, fetchTransport as A, xhrTransport as j } from "./client/media/transport.js";
12
12
  import { MediaClient as M, backoff as N, samePath as P, stem as F } from "./client/media/client.js";
13
- import { acceptOf as I, defaultImage as L, filesFor as R, isPattern as z, ratioLabel as B, ruleDir as V, ruleFor as H, screenFiles as U, uniqueName as W, uploadRules as G, withMediaType as K } from "./client/media/rules.js";
14
- import { centeredCrop as q, constrainCrop as J, editOf as Y, editedSize as X, fromRotated as Z, rotation as Q, sameEdit as $, toOriginal as ee, toRotated as te } from "./client/crop.js";
15
- import { encodeRemaining as ne } from "./client/media/encode.js";
16
- import { ItemUploadQueue as re } from "./client/media/queue.js";
17
- import { decodeImage as ie, isAnimatedImage as ae } from "./client/image.js";
18
- import { READ_CHUNK as oe, chunkOffsets as se, isProcessing as ce, mergeReads as le, processing as ue } from "./client/media/windows.js";
19
- import { ABR_DEFAULTS as de, NETWORK_FAILURES_BEFORE_ERROR as fe, REFRESH_BEFORE_EXPIRY as pe, abrHlsConfig as me, capRung as he, classifyHlsError as ge, classifyMediaError as _e, expiryDelay as ve, hlsConfig as ye, initialEstimate as be, refreshable as xe, startRung as Se, statusKind as Ce } from "./client/playback.js";
20
- import { formatDuration as we, galleryItems as Te, isAudioType as Ee, isImageType as De, isSubtitleType as Oe, isVideoType as ke, stageAspect as Ae } from "./client/gallery.js";
21
- import { PLAYBACK_SPEEDS as je, PLAYER_TRACKS_KEY as Me, PLAYER_VOLUME_KEY as Ne, parseSpriteVtt as Pe, playerKeyAction as Fe, resumeAt as Ie } from "./client/player.js";
22
- import { reactionVerb as Le, sameTarget as Re } from "./client/content/types.js";
23
- import { CatalogClient as ze } from "./client/catalog.js";
24
- import { CodesClient as Be } from "./client/codes.js";
25
- import { BODY_SUFFIX as Ve, ContentsClient as He, IMAGE_SCHEME as Ue, imageRef as We, imageRefs as Ge } from "./client/contents.js";
26
- import { BansClient as Ke } from "./client/content/bans.js";
27
- import { CommentsClient as qe } from "./client/content/comments.js";
28
- import { Beacons as Je, eventId as Ye, readerSession as Xe } from "./client/beacons.js";
29
- import { DiscoveryClient as Ze } from "./client/discovery.js";
30
- import { FavoritesClient as Qe } from "./client/content/favorites.js";
31
- import { ModerationClient as $e } from "./client/content/moderation.js";
32
- import { PollsClient as et } from "./client/content/polls.js";
33
- import { ReactionsClient as tt } from "./client/content/reactions.js";
34
- import { GroupsClient as nt } from "./client/groups.js";
35
- import { OffersClient as rt } from "./client/offers.js";
36
- import { VersionsClient as it, isPending as at } from "./client/versions.js";
37
- import { NotificationsClient as ot } from "./client/notifications.js";
38
- import { createContentKitClient as st } from "./client/client.js";
39
- import { LETTERS as ct, attributeInput as lt, fold as ut, fromLocalInput as dt, humanize as ft, letterOf as pt, parseAttribute as mt, parseAttributes as ht, slugify as gt, toLocalInput as _t } from "./client/group-fields.js";
40
- import { emptyReaderMeta as vt, matchAudio as yt, readerMetaProblems as bt, readerNames as xt } from "./client/reader-meta.js";
41
- export { de as ABR_DEFAULTS, Ve as BODY_SUFFIX, Ke as BansClient, Je as Beacons, i as CLIENT_ERROR_CODES, ze as CatalogClient, Be as CodesClient, qe as CommentsClient, a as ContentKitError, He as ContentsClient, e as DEFAULT_DENSITY, Ze as DiscoveryClient, o as ERROR_CODES, Qe as FavoritesClient, nt as GroupsClient, Ue as IMAGE_SCHEME, re as ItemUploadQueue, ct as LETTERS, w as MAX_PART_SIZE, T as MAX_SINGLE_PUT, E as MIN_PART_SIZE, C as MediaApi, M as MediaClient, $e as ModerationClient, fe as NETWORK_FAILURES_BEFORE_ERROR, ot as NotificationsClient, rt as OffersClient, je as PLAYBACK_SPEEDS, Me as PLAYER_TRACKS_KEY, Ne as PLAYER_VOLUME_KEY, O as Pacer, et as PollsClient, oe as READ_CHUNK, pe as REFRESH_BEFORE_EXPIRY, tt as ReactionsClient, it as VersionsClient, me as abrHlsConfig, I as acceptOf, h as aspectOf, lt as attributeInput, N as backoff, _ as call, he as capRung, q as centeredCrop, y as checkRef, se as chunkOffsets, ge as classifyHlsError, _e as classifyMediaError, J as constrainCrop, st as createContentKitClient, ie as decodeImage, L as defaultImage, k as defaultTransport, t as densityFor, Y as editOf, X as editedSize, vt as emptyReaderMeta, ne as encodeRemaining, Ye as eventId, ve as expiryDelay, s as failureError, A as fetchTransport, R as filesFor, d as fill, ut as fold, we as formatDuration, dt as fromLocalInput, Z as fromRotated, Te as galleryItems, ye as hlsConfig, ft as humanize, We as imageRef, Ge as imageRefs, be as initialEstimate, ae as isAnimatedImage, Ee as isAudioType, b as isContentId, c as isContentKitError, De as isImageType, z as isPattern, at as isPending, ce as isProcessing, Oe as isSubtitleType, ke as isVideoType, x as itemKey, pt as letterOf, yt as matchAudio, le as mergeReads, mt as parseAttribute, ht as parseAttributes, Pe as parseSpriteVtt, n as pickRendition, Fe as playerKeyAction, ue as processing, f as publicRenditions, p as publicURL, g as ratio, B as ratioLabel, Le as reactionVerb, l as readContentKitError, bt as readerMetaProblems, xt as readerNames, Xe as readerSession, xe as refreshable, Ie as resumeAt, Q as rotation, v as routeURL, V as ruleDir, H as ruleFor, $ as sameEdit, S as sameItem, P as samePath, Re as sameTarget, U as screenFiles, D as sha256Hex, gt as slugify, r as sortRenditions, m as srcSet, Ae as stageAspect, Se as startRung, Ce as statusKind, F as stem, u as toContentKitError, _t as toLocalInput, ee as toOriginal, te as toRotated, W as uniqueName, G as uploadRules, K as withMediaType, j as xhrTransport };
13
+ import { acceptOf as I, defaultImage as L, filesFor as R, isPattern as z, ratioLabel as B, ruleDir as V, ruleFor as H, screenFiles as U, stableImage as W, uniqueName as G, uploadRules as K, withMediaType as q } from "./client/media/rules.js";
14
+ import { centeredCrop as J, constrainCrop as Y, editOf as X, editedSize as Z, fromRotated as Q, rotation as $, sameEdit as ee, toOriginal as te, toRotated as ne } from "./client/crop.js";
15
+ import { encodeRemaining as re } from "./client/media/encode.js";
16
+ import { ItemUploadQueue as ie } from "./client/media/queue.js";
17
+ import { decodeImage as ae, isAnimatedImage as oe } from "./client/image.js";
18
+ import { reactionVerb as se, sameTarget as ce } from "./client/content/types.js";
19
+ import { READ_CHUNK as le, chunkOffsets as ue, isProcessing as de, mergeReads as fe, processing as pe } from "./client/media/windows.js";
20
+ import { ABR_DEFAULTS as me, NETWORK_FAILURES_BEFORE_ERROR as he, REFRESH_BEFORE_EXPIRY as ge, abrHlsConfig as _e, capRung as ve, classifyHlsError as ye, classifyMediaError as be, expiryDelay as xe, hlsConfig as Se, initialEstimate as Ce, refreshable as we, startRung as Te, statusKind as Ee } from "./client/playback.js";
21
+ import { formatDuration as De, galleryItems as Oe, isAudioType as ke, isImageType as Ae, isSubtitleType as je, isVideoType as Me, stageAspect as Ne } from "./client/gallery.js";
22
+ import { PLAYBACK_SPEEDS as Pe, PLAYER_TRACKS_KEY as Fe, PLAYER_VOLUME_KEY as Ie, parseSpriteVtt as Le, playerKeyAction as Re, resumeAt as ze } from "./client/player.js";
23
+ import { CatalogClient as Be } from "./client/catalog.js";
24
+ import { CodesClient as Ve } from "./client/codes.js";
25
+ import { BODY_SUFFIX as He, ContentsClient as Ue, IMAGE_SCHEME as We, imageRef as Ge, imageRefs as Ke } from "./client/contents.js";
26
+ import { BansClient as qe } from "./client/content/bans.js";
27
+ import { CommentsClient as Je } from "./client/content/comments.js";
28
+ import { Beacons as Ye, eventId as Xe, readerSession as Ze } from "./client/beacons.js";
29
+ import { DiscoveryClient as Qe } from "./client/discovery.js";
30
+ import { FavoritesClient as $e } from "./client/content/favorites.js";
31
+ import { ModerationClient as et } from "./client/content/moderation.js";
32
+ import { PollsClient as tt } from "./client/content/polls.js";
33
+ import { ReactionsClient as nt } from "./client/content/reactions.js";
34
+ import { GroupsClient as rt } from "./client/groups.js";
35
+ import { OffersClient as it } from "./client/offers.js";
36
+ import { StatsClient as at } from "./client/stats.js";
37
+ import { VersionsClient as ot, isPending as st } from "./client/versions.js";
38
+ import { NotificationsClient as ct } from "./client/notifications.js";
39
+ import { createContentKitClient as lt } from "./client/client.js";
40
+ import { LETTERS as ut, attributeInput as dt, fold as ft, fromLocalInput as pt, humanize as mt, letterOf as ht, parseAttribute as gt, parseAttributes as _t, slugify as vt, toLocalInput as yt } from "./client/group-fields.js";
41
+ import { emptyReaderMeta as bt, matchAudio as xt, readerMetaProblems as St, readerNames as Ct } from "./client/reader-meta.js";
42
+ export { me as ABR_DEFAULTS, He as BODY_SUFFIX, qe as BansClient, Ye as Beacons, i as CLIENT_ERROR_CODES, Be as CatalogClient, Ve as CodesClient, Je as CommentsClient, a as ContentKitError, Ue as ContentsClient, e as DEFAULT_DENSITY, Qe as DiscoveryClient, o as ERROR_CODES, $e as FavoritesClient, rt as GroupsClient, We as IMAGE_SCHEME, ie as ItemUploadQueue, ut as LETTERS, w as MAX_PART_SIZE, T as MAX_SINGLE_PUT, E as MIN_PART_SIZE, C as MediaApi, M as MediaClient, et as ModerationClient, he as NETWORK_FAILURES_BEFORE_ERROR, ct as NotificationsClient, it as OffersClient, Pe as PLAYBACK_SPEEDS, Fe as PLAYER_TRACKS_KEY, Ie as PLAYER_VOLUME_KEY, O as Pacer, tt as PollsClient, le as READ_CHUNK, ge as REFRESH_BEFORE_EXPIRY, nt as ReactionsClient, at as StatsClient, ot as VersionsClient, _e as abrHlsConfig, I as acceptOf, h as aspectOf, dt as attributeInput, N as backoff, _ as call, ve as capRung, J as centeredCrop, y as checkRef, ue as chunkOffsets, ye as classifyHlsError, be as classifyMediaError, Y as constrainCrop, lt as createContentKitClient, ae as decodeImage, L as defaultImage, k as defaultTransport, t as densityFor, X as editOf, Z as editedSize, bt as emptyReaderMeta, re as encodeRemaining, Xe as eventId, xe as expiryDelay, s as failureError, A as fetchTransport, R as filesFor, d as fill, ft as fold, De as formatDuration, pt as fromLocalInput, Q as fromRotated, Oe as galleryItems, Se as hlsConfig, mt as humanize, Ge as imageRef, Ke as imageRefs, Ce as initialEstimate, oe as isAnimatedImage, ke as isAudioType, b as isContentId, c as isContentKitError, Ae as isImageType, z as isPattern, st as isPending, de as isProcessing, je as isSubtitleType, Me as isVideoType, x as itemKey, ht as letterOf, xt as matchAudio, fe as mergeReads, gt as parseAttribute, _t as parseAttributes, Le as parseSpriteVtt, n as pickRendition, Re as playerKeyAction, pe as processing, f as publicRenditions, p as publicURL, g as ratio, B as ratioLabel, se as reactionVerb, l as readContentKitError, St as readerMetaProblems, Ct as readerNames, Ze as readerSession, we as refreshable, ze as resumeAt, $ as rotation, v as routeURL, V as ruleDir, H as ruleFor, ee as sameEdit, S as sameItem, P as samePath, ce as sameTarget, U as screenFiles, D as sha256Hex, vt as slugify, r as sortRenditions, m as srcSet, W as stableImage, Ne as stageAspect, Te as startRung, Ee as statusKind, F as stem, u as toContentKitError, yt as toLocalInput, te as toOriginal, ne as toRotated, G as uniqueName, K as uploadRules, q as withMediaType, j as xhrTransport };