@btst/stack 2.8.1 → 2.9.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 (196) hide show
  1. package/README.md +3 -2
  2. package/dist/components/markdown/index.d.cts +15 -2
  3. package/dist/components/markdown/index.d.mts +15 -2
  4. package/dist/components/markdown/index.d.ts +15 -2
  5. package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.cjs +30 -1
  6. package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.mjs +30 -1
  7. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.cjs +49 -9
  8. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.mjs +50 -10
  9. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.cjs +77 -9
  10. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.mjs +77 -9
  11. package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.cjs +24 -5
  12. package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.mjs +24 -5
  13. package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.cjs +47 -13
  14. package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.mjs +47 -13
  15. package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.cjs +1 -1
  16. package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.mjs +1 -1
  17. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +6 -2
  18. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +6 -2
  19. package/dist/packages/stack/src/plugins/media/api/adapters/local.cjs +55 -0
  20. package/dist/packages/stack/src/plugins/media/api/adapters/local.mjs +37 -0
  21. package/dist/packages/stack/src/plugins/media/api/getters.cjs +83 -0
  22. package/dist/packages/stack/src/plugins/media/api/getters.mjs +78 -0
  23. package/dist/packages/stack/src/plugins/media/api/mutations.cjs +88 -0
  24. package/dist/packages/stack/src/plugins/media/api/mutations.mjs +82 -0
  25. package/dist/packages/stack/src/plugins/media/api/plugin.cjs +525 -0
  26. package/dist/packages/stack/src/plugins/media/api/plugin.mjs +523 -0
  27. package/dist/packages/stack/src/plugins/media/api/query-key-defs.cjs +19 -0
  28. package/dist/packages/stack/src/plugins/media/api/query-key-defs.mjs +16 -0
  29. package/dist/packages/stack/src/plugins/media/api/serializers.cjs +17 -0
  30. package/dist/packages/stack/src/plugins/media/api/serializers.mjs +14 -0
  31. package/dist/packages/stack/src/plugins/media/api/storage-adapter.cjs +15 -0
  32. package/dist/packages/stack/src/plugins/media/api/storage-adapter.mjs +11 -0
  33. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.cjs +129 -0
  34. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.mjs +127 -0
  35. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.cjs +58 -0
  36. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.mjs +56 -0
  37. package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.cjs +94 -0
  38. package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.mjs +92 -0
  39. package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.cjs +171 -0
  40. package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.mjs +168 -0
  41. package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.cjs +308 -0
  42. package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.mjs +305 -0
  43. package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.cjs +104 -0
  44. package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.mjs +102 -0
  45. package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.cjs +70 -0
  46. package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.mjs +68 -0
  47. package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.cjs +21 -0
  48. package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.mjs +17 -0
  49. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.cjs +35 -0
  50. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.cjs +125 -0
  51. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.mjs +123 -0
  52. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.mjs +33 -0
  53. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.cjs +222 -0
  54. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.mjs +214 -0
  55. package/dist/packages/stack/src/plugins/media/client/plugin.cjs +94 -0
  56. package/dist/packages/stack/src/plugins/media/client/plugin.mjs +92 -0
  57. package/dist/packages/stack/src/plugins/media/client/upload.cjs +121 -0
  58. package/dist/packages/stack/src/plugins/media/client/upload.mjs +119 -0
  59. package/dist/packages/stack/src/plugins/media/client/utils/image-compression.cjs +67 -0
  60. package/dist/packages/stack/src/plugins/media/client/utils/image-compression.mjs +65 -0
  61. package/dist/packages/stack/src/plugins/media/db.cjs +62 -0
  62. package/dist/packages/stack/src/plugins/media/db.mjs +60 -0
  63. package/dist/packages/stack/src/plugins/media/schemas.cjs +41 -0
  64. package/dist/packages/stack/src/plugins/media/schemas.mjs +35 -0
  65. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.cjs +18 -1
  66. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.mjs +19 -2
  67. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.cjs +2 -2
  68. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.mjs +2 -2
  69. package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.cjs +3 -2
  70. package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.mjs +3 -2
  71. package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.cjs +12 -5
  72. package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.mjs +12 -5
  73. package/dist/plugins/blog/client/index.d.cts +58 -1
  74. package/dist/plugins/blog/client/index.d.mts +58 -1
  75. package/dist/plugins/blog/client/index.d.ts +58 -1
  76. package/dist/plugins/cms/client/index.d.cts +73 -3
  77. package/dist/plugins/cms/client/index.d.mts +73 -3
  78. package/dist/plugins/cms/client/index.d.ts +73 -3
  79. package/dist/plugins/kanban/api/index.d.cts +1 -1
  80. package/dist/plugins/kanban/api/index.d.mts +1 -1
  81. package/dist/plugins/kanban/api/index.d.ts +1 -1
  82. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  83. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  84. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  85. package/dist/plugins/kanban/client/index.d.cts +1 -1
  86. package/dist/plugins/kanban/client/index.d.mts +1 -1
  87. package/dist/plugins/kanban/client/index.d.ts +1 -1
  88. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  89. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  90. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  91. package/dist/plugins/media/api/adapters/s3.cjs +106 -0
  92. package/dist/plugins/media/api/adapters/s3.d.cts +60 -0
  93. package/dist/plugins/media/api/adapters/s3.d.mts +60 -0
  94. package/dist/plugins/media/api/adapters/s3.d.ts +60 -0
  95. package/dist/plugins/media/api/adapters/s3.mjs +104 -0
  96. package/dist/plugins/media/api/adapters/vercel-blob.cjs +53 -0
  97. package/dist/plugins/media/api/adapters/vercel-blob.d.cts +41 -0
  98. package/dist/plugins/media/api/adapters/vercel-blob.d.mts +41 -0
  99. package/dist/plugins/media/api/adapters/vercel-blob.d.ts +41 -0
  100. package/dist/plugins/media/api/adapters/vercel-blob.mjs +51 -0
  101. package/dist/plugins/media/api/index.cjs +26 -0
  102. package/dist/plugins/media/api/index.d.cts +116 -0
  103. package/dist/plugins/media/api/index.d.mts +116 -0
  104. package/dist/plugins/media/api/index.d.ts +116 -0
  105. package/dist/plugins/media/api/index.mjs +6 -0
  106. package/dist/plugins/media/client/components/index.cjs +10 -0
  107. package/dist/plugins/media/client/components/index.d.cts +55 -0
  108. package/dist/plugins/media/client/components/index.d.mts +55 -0
  109. package/dist/plugins/media/client/components/index.d.ts +55 -0
  110. package/dist/plugins/media/client/components/index.mjs +2 -0
  111. package/dist/plugins/media/client/hooks/index.cjs +13 -0
  112. package/dist/plugins/media/client/hooks/index.d.cts +53 -0
  113. package/dist/plugins/media/client/hooks/index.d.mts +53 -0
  114. package/dist/plugins/media/client/hooks/index.d.ts +53 -0
  115. package/dist/plugins/media/client/hooks/index.mjs +1 -0
  116. package/dist/plugins/media/client/index.cjs +9 -0
  117. package/dist/plugins/media/client/index.d.cts +242 -0
  118. package/dist/plugins/media/client/index.d.mts +242 -0
  119. package/dist/plugins/media/client/index.d.ts +242 -0
  120. package/dist/plugins/media/client/index.mjs +2 -0
  121. package/dist/plugins/media/client.css +1 -0
  122. package/dist/plugins/media/query-keys.cjs +72 -0
  123. package/dist/plugins/media/query-keys.d.cts +49 -0
  124. package/dist/plugins/media/query-keys.d.mts +49 -0
  125. package/dist/plugins/media/query-keys.d.ts +49 -0
  126. package/dist/plugins/media/query-keys.mjs +70 -0
  127. package/dist/plugins/media/style.css +1 -0
  128. package/dist/shared/{stack.DRpeDS6X.d.ts → stack.BMx2QYOK.d.ts} +25 -0
  129. package/dist/shared/stack.BttDsJJn.d.cts +109 -0
  130. package/dist/shared/stack.BttDsJJn.d.mts +109 -0
  131. package/dist/shared/stack.BttDsJJn.d.ts +109 -0
  132. package/dist/shared/stack.C7vfOBmO.d.mts +63 -0
  133. package/dist/shared/stack.CAni8dnD.d.cts +63 -0
  134. package/dist/shared/stack.CI8iRKKi.d.cts +286 -0
  135. package/dist/shared/stack.CLcnSF_b.d.cts +25 -0
  136. package/dist/shared/stack.CLcnSF_b.d.mts +25 -0
  137. package/dist/shared/stack.CLcnSF_b.d.ts +25 -0
  138. package/dist/shared/stack.CYSwntXC.d.ts +63 -0
  139. package/dist/shared/{stack.Jb0kQDJC.d.mts → stack.Cd6McBu1.d.mts} +25 -0
  140. package/dist/shared/stack.DJDjdG64.d.ts +286 -0
  141. package/dist/shared/{stack.BxFl46lB.d.cts → stack.DxQl8Wa1.d.cts} +25 -0
  142. package/dist/shared/stack.FgBVDSPi.d.mts +286 -0
  143. package/package.json +113 -4
  144. package/src/plugins/blog/client/components/forms/image-field.tsx +35 -4
  145. package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +67 -12
  146. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +106 -10
  147. package/src/plugins/blog/client/overrides.ts +58 -1
  148. package/src/plugins/cms/client/components/forms/content-form.tsx +26 -7
  149. package/src/plugins/cms/client/components/forms/file-upload.tsx +73 -15
  150. package/src/plugins/cms/client/overrides.ts +57 -2
  151. package/src/plugins/kanban/client/components/forms/board-form.tsx +1 -1
  152. package/src/plugins/kanban/client/components/forms/task-form.tsx +7 -1
  153. package/src/plugins/kanban/client/overrides.ts +25 -0
  154. package/src/plugins/media/__tests__/__stubs__/vercel-blob-server.ts +9 -0
  155. package/src/plugins/media/__tests__/getters.test.ts +274 -0
  156. package/src/plugins/media/__tests__/mutations.test.ts +299 -0
  157. package/src/plugins/media/__tests__/plugin.test.ts +752 -0
  158. package/src/plugins/media/__tests__/query-key-defs.test.ts +54 -0
  159. package/src/plugins/media/__tests__/storage-adapters.test.ts +351 -0
  160. package/src/plugins/media/api/adapters/local.ts +79 -0
  161. package/src/plugins/media/api/adapters/s3.ts +198 -0
  162. package/src/plugins/media/api/adapters/vercel-blob.ts +131 -0
  163. package/src/plugins/media/api/getters.ts +174 -0
  164. package/src/plugins/media/api/index.ts +41 -0
  165. package/src/plugins/media/api/mutations.ts +179 -0
  166. package/src/plugins/media/api/plugin.ts +855 -0
  167. package/src/plugins/media/api/query-key-defs.ts +41 -0
  168. package/src/plugins/media/api/serializers.ts +28 -0
  169. package/src/plugins/media/api/storage-adapter.ts +139 -0
  170. package/src/plugins/media/client/components/index.tsx +6 -0
  171. package/src/plugins/media/client/components/media-picker/asset-card.tsx +150 -0
  172. package/src/plugins/media/client/components/media-picker/asset-preview-button.tsx +67 -0
  173. package/src/plugins/media/client/components/media-picker/browse-tab.tsx +116 -0
  174. package/src/plugins/media/client/components/media-picker/folder-tree.tsx +188 -0
  175. package/src/plugins/media/client/components/media-picker/index.tsx +347 -0
  176. package/src/plugins/media/client/components/media-picker/upload-tab.tsx +108 -0
  177. package/src/plugins/media/client/components/media-picker/url-tab.tsx +72 -0
  178. package/src/plugins/media/client/components/media-picker/utils.ts +17 -0
  179. package/src/plugins/media/client/components/pages/library-page.internal.tsx +134 -0
  180. package/src/plugins/media/client/components/pages/library-page.tsx +42 -0
  181. package/src/plugins/media/client/hooks/index.tsx +9 -0
  182. package/src/plugins/media/client/hooks/use-media.tsx +289 -0
  183. package/src/plugins/media/client/index.ts +4 -0
  184. package/src/plugins/media/client/overrides.ts +127 -0
  185. package/src/plugins/media/client/plugin.tsx +184 -0
  186. package/src/plugins/media/client/upload.ts +171 -0
  187. package/src/plugins/media/client/utils/image-compression.ts +131 -0
  188. package/src/plugins/media/client.css +1 -0
  189. package/src/plugins/media/db.ts +62 -0
  190. package/src/plugins/media/query-keys.ts +96 -0
  191. package/src/plugins/media/schemas.ts +37 -0
  192. package/src/plugins/media/style.css +1 -0
  193. package/src/plugins/media/types.ts +26 -0
  194. package/dist/shared/{stack.BOokfhZD.d.cts → stack.B6S3cgwN.d.cts} +16 -16
  195. package/dist/shared/{stack.CWxAl9K3.d.mts → stack.Bzfx-_lq.d.mts} +16 -16
  196. package/dist/shared/{stack.BvCR4-9H.d.ts → stack.j5SFLC1d.d.ts} +16 -16
@@ -0,0 +1,242 @@
1
+ import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
2
+ import * as _btst_yar from '@btst/yar';
3
+ import { ComponentType } from 'react';
4
+ import { QueryClient } from '@tanstack/react-query';
5
+ import { S as SerializedAsset } from '../../../shared/stack.CLcnSF_b.js';
6
+
7
+ interface MediaLoaderContext {
8
+ path: string;
9
+ isSSR: boolean;
10
+ apiBaseURL: string;
11
+ apiBasePath: string;
12
+ headers?: HeadersInit;
13
+ }
14
+ interface MediaClientHooks {
15
+ /** Called before the media library data is fetched during SSR. Throw to cancel. */
16
+ beforeLoadLibrary?: (context: MediaLoaderContext) => Promise<void> | void;
17
+ /** Called after the media library data is fetched during SSR. */
18
+ afterLoadLibrary?: (context: MediaLoaderContext) => Promise<void> | void;
19
+ /** Called when an error occurs during the SSR loader. */
20
+ onLoadError?: (error: Error, context: MediaLoaderContext) => Promise<void> | void;
21
+ }
22
+ interface MediaClientConfig {
23
+ /** Base URL for API calls (e.g., "http://localhost:3000") */
24
+ apiBaseURL: string;
25
+ /** Path where the API is mounted (e.g., "/api/data") */
26
+ apiBasePath: string;
27
+ /** Base URL of your site for SEO meta tags */
28
+ siteBaseURL: string;
29
+ /** Path where pages are mounted (e.g., "/pages") */
30
+ siteBasePath: string;
31
+ /** React Query client — used by the SSR loader to prefetch data */
32
+ queryClient: QueryClient;
33
+ /** Optional headers forwarded with SSR API requests (e.g. auth cookies) */
34
+ headers?: HeadersInit;
35
+ /** Optional lifecycle hooks for the media client plugin */
36
+ hooks?: MediaClientHooks;
37
+ /**
38
+ * Optional page component overrides.
39
+ * Replace any plugin page with a custom React component.
40
+ * The built-in component is used as the fallback when not provided.
41
+ */
42
+ pageComponents?: {
43
+ /** Replaces the media library page */
44
+ library?: ComponentType;
45
+ };
46
+ }
47
+ /**
48
+ * Media client plugin.
49
+ * Registers the /media library route.
50
+ *
51
+ * Configure overrides in StackProvider:
52
+ * ```tsx
53
+ * <StackProvider overrides={{ media: { apiBaseURL, apiBasePath, queryClient, uploadMode: "direct", navigate } }}>
54
+ * ```
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import { mediaClientPlugin } from "@btst/stack/plugins/media/client"
59
+ *
60
+ * const clientPlugins = [
61
+ * mediaClientPlugin({ apiBaseURL, apiBasePath, siteBaseURL, siteBasePath, queryClient }),
62
+ * // ...other plugins
63
+ * ]
64
+ * ```
65
+ */
66
+ declare const mediaClientPlugin: (config: MediaClientConfig) => _btst_stack_plugins_client.ClientPlugin<Record<string, never>, {
67
+ library: {
68
+ (inputCtx_0?: _btst_yar.InputContext<"/media", _btst_yar.RouteOptions> | undefined): {
69
+ PageComponent?: ComponentType<{}> | undefined;
70
+ LoadingComponent?: ComponentType<{}> | undefined;
71
+ ErrorComponent?: ComponentType<{}> | undefined;
72
+ loader?: (() => Promise<void>) | undefined;
73
+ meta?: (() => ({
74
+ title: string;
75
+ name?: undefined;
76
+ content?: undefined;
77
+ property?: undefined;
78
+ } | {
79
+ name: string;
80
+ content: string;
81
+ title?: undefined;
82
+ property?: undefined;
83
+ } | {
84
+ property: string;
85
+ content: string;
86
+ title?: undefined;
87
+ name?: undefined;
88
+ })[]) | undefined;
89
+ extra?: (() => any) | undefined;
90
+ };
91
+ options: _btst_yar.RouteOptions | undefined;
92
+ path: "/media";
93
+ meta: _btst_yar.RouteMeta | undefined;
94
+ };
95
+ }>;
96
+
97
+ /**
98
+ * Canvas-based client-side image compression.
99
+ *
100
+ * Skips SVG and GIF (vector data / animation would be lost on a canvas round-trip).
101
+ * All other image/* types are scaled down to fit within maxWidth × maxHeight
102
+ * (preserving aspect ratio) and re-encoded at the configured quality.
103
+ */
104
+ interface ImageCompressionOptions {
105
+ /**
106
+ * Maximum width in pixels. Images wider than this are scaled down.
107
+ * @default 2048
108
+ */
109
+ maxWidth?: number;
110
+ /**
111
+ * Maximum height in pixels. Images taller than this are scaled down.
112
+ * @default 2048
113
+ */
114
+ maxHeight?: number;
115
+ /**
116
+ * Encoding quality (0–1). Applies to JPEG and WebP.
117
+ * @default 0.85
118
+ */
119
+ quality?: number;
120
+ /**
121
+ * Output MIME type. Defaults to the source image's MIME type.
122
+ * Set to `"image/webp"` for better compression at the cost of format change.
123
+ */
124
+ outputFormat?: string;
125
+ }
126
+
127
+ /**
128
+ * Upload mode — must match the storage adapter configured in mediaBackendPlugin.
129
+ * - `"direct"` — local filesystem adapter, files are uploaded via `POST /media/upload`
130
+ * - `"s3"` — AWS S3 / R2 / MinIO, the client fetches a presigned token then PUTs directly to S3
131
+ * - `"vercel-blob"` — Vercel Blob, uses the `@vercel/blob/client` SDK for direct upload
132
+ */
133
+ type MediaUploadMode = "direct" | "s3" | "vercel-blob";
134
+ /**
135
+ * Overridable components and functions for the Media plugin.
136
+ *
137
+ * External consumers provide these when registering the media client plugin
138
+ * via the StackProvider overrides.
139
+ */
140
+ interface MediaPluginOverrides {
141
+ /**
142
+ * Base URL for API calls (e.g., "http://localhost:3000").
143
+ */
144
+ apiBaseURL: string;
145
+ /**
146
+ * Path where the API is mounted (e.g., "/api/data").
147
+ */
148
+ apiBasePath: string;
149
+ /**
150
+ * React Query client — used by the MediaPicker to cache and fetch assets.
151
+ */
152
+ queryClient: QueryClient;
153
+ /**
154
+ * Upload mode — must match the storageAdapter configured in mediaBackendPlugin.
155
+ * @default "direct"
156
+ */
157
+ uploadMode?: MediaUploadMode;
158
+ /**
159
+ * Optional headers to pass with API requests (e.g., for SSR auth).
160
+ */
161
+ headers?: HeadersInit;
162
+ /**
163
+ * Navigation function for programmatic navigation.
164
+ */
165
+ navigate: (path: string) => void | Promise<void>;
166
+ /**
167
+ * Link component for navigation within the media library page.
168
+ */
169
+ Link?: ComponentType<React.ComponentProps<"a"> & Record<string, any>>;
170
+ /**
171
+ * Image component for rendering asset thumbnails and previews.
172
+ *
173
+ * When provided, replaces the default `<img>` element in asset cards,
174
+ * the media library grid, and the ImageInputField preview. Use this
175
+ * to plug in Next.js `<Image>` for automatic optimisation.
176
+ *
177
+ * @example
178
+ * ```tsx
179
+ * Image: (props) => <NextImage {...props} />
180
+ * ```
181
+ */
182
+ Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, any>>;
183
+ /**
184
+ * Client-side image compression applied before upload via the Canvas API.
185
+ *
186
+ * Images are scaled down to fit within `maxWidth` × `maxHeight` (preserving
187
+ * aspect ratio) and re-encoded at `quality`. SVG and GIF files are always
188
+ * passed through unchanged.
189
+ *
190
+ * Set to `false` to disable compression entirely.
191
+ *
192
+ * @default { maxWidth: 2048, maxHeight: 2048, quality: 0.85 }
193
+ */
194
+ imageCompression?: ImageCompressionOptions | false;
195
+ /**
196
+ * Called when a media route is rendered.
197
+ */
198
+ onRouteRender?: (routeName: string, context: MediaRouteContext) => void | Promise<void>;
199
+ /**
200
+ * Called when a media route encounters an error.
201
+ */
202
+ onRouteError?: (routeName: string, error: Error, context: MediaRouteContext) => void | Promise<void>;
203
+ /**
204
+ * Called before the media library page is rendered.
205
+ * Return `false` to prevent rendering (e.g., redirect unauthenticated users).
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * media: {
210
+ * onBeforeLibraryPageRendered: (context) => !!currentUser?.isAdmin,
211
+ * onRouteError: (routeName, error, context) => navigate("/login"),
212
+ * }
213
+ * ```
214
+ */
215
+ onBeforeLibraryPageRendered?: (context: MediaRouteContext) => boolean;
216
+ }
217
+ interface MediaRouteContext {
218
+ /** Current route path */
219
+ path: string;
220
+ /** Route parameters */
221
+ params?: Record<string, string>;
222
+ /** Whether rendering on server (true) or client (false) */
223
+ isSSR: boolean;
224
+ [key: string]: unknown;
225
+ }
226
+
227
+ type MediaUploadClientConfig = Pick<MediaPluginOverrides, "apiBaseURL" | "apiBasePath" | "headers" | "uploadMode" | "imageCompression">;
228
+ interface UploadAssetInput {
229
+ file: File;
230
+ folderId?: string;
231
+ }
232
+ /**
233
+ * Upload an asset using the media plugin's configured storage mode.
234
+ *
235
+ * Use this in non-React contexts like editor `uploadImage` callbacks. React
236
+ * components should usually prefer `useUploadAsset()`, which wraps this helper
237
+ * and handles cache invalidation.
238
+ */
239
+ declare function uploadAsset(config: MediaUploadClientConfig, input: UploadAssetInput): Promise<SerializedAsset>;
240
+
241
+ export { mediaClientPlugin, uploadAsset };
242
+ export type { MediaPluginOverrides, MediaUploadClientConfig, MediaUploadMode, UploadAssetInput };
@@ -0,0 +1,2 @@
1
+ export { mediaClientPlugin } from '../../../packages/stack/src/plugins/media/client/plugin.mjs';
2
+ export { uploadAsset } from '../../../packages/stack/src/plugins/media/client/upload.mjs';
@@ -0,0 +1 @@
1
+ /* Media plugin client styles — included in consumer CSS via @import "@btst/stack/plugins/media/css" */
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ const queryKeyFactory = require('@lukemorales/query-key-factory');
4
+ const queryKeyDefs = require('../../packages/stack/src/plugins/media/api/query-key-defs.cjs');
5
+
6
+ function isErrorResponse(response) {
7
+ return typeof response === "object" && response !== null && "error" in response && response.error !== null && response.error !== void 0;
8
+ }
9
+ function toError(error) {
10
+ if (error instanceof Error) return error;
11
+ if (typeof error === "object" && error !== null) {
12
+ const errorObj = error;
13
+ const message = (typeof errorObj.message === "string" ? errorObj.message : null) || JSON.stringify(error);
14
+ const err = new Error(message);
15
+ Object.assign(err, error);
16
+ return err;
17
+ }
18
+ return new Error(String(error));
19
+ }
20
+ function createMediaQueryKeys(client, headers) {
21
+ return queryKeyFactory.mergeQueryKeys(
22
+ queryKeyFactory.createQueryKeys("mediaAssets", {
23
+ list: (params) => ({
24
+ queryKey: [queryKeyDefs.assetListDiscriminator(params)],
25
+ queryFn: async ({ pageParam }) => {
26
+ const response = await client("/media/assets", {
27
+ method: "GET",
28
+ query: {
29
+ folderId: params?.folderId,
30
+ mimeType: params?.mimeType,
31
+ query: params?.query,
32
+ offset: pageParam ?? params?.offset ?? 0,
33
+ limit: params?.limit ?? 20
34
+ },
35
+ headers
36
+ });
37
+ if (isErrorResponse(response)) throw toError(response.error);
38
+ const data = response.data;
39
+ return data;
40
+ }
41
+ }),
42
+ detail: (id) => ({
43
+ queryKey: [id],
44
+ queryFn: async () => {
45
+ const response = await client("/media/assets", {
46
+ method: "GET",
47
+ query: { id },
48
+ headers
49
+ });
50
+ if (isErrorResponse(response)) throw toError(response.error);
51
+ return response.data;
52
+ }
53
+ })
54
+ }),
55
+ queryKeyFactory.createQueryKeys("mediaFolders", {
56
+ list: (parentId) => ({
57
+ queryKey: [parentId ?? "root"],
58
+ queryFn: async () => {
59
+ const response = await client("/media/folders", {
60
+ method: "GET",
61
+ query: parentId !== void 0 ? { parentId: parentId ?? void 0 } : {},
62
+ headers
63
+ });
64
+ if (isErrorResponse(response)) throw toError(response.error);
65
+ return response.data;
66
+ }
67
+ })
68
+ })
69
+ );
70
+ }
71
+
72
+ exports.createMediaQueryKeys = createMediaQueryKeys;
@@ -0,0 +1,49 @@
1
+ import * as _tanstack_react_query from '@tanstack/react-query';
2
+ import { M as MediaApiRouter, A as AssetListDiscriminator } from '../../shared/stack.CI8iRKKi.cjs';
3
+ import { createApiClient } from '@btst/stack/plugins/client';
4
+ import { S as SerializedAsset, a as SerializedFolder } from '../../shared/stack.CLcnSF_b.cjs';
5
+ import { A as AssetListParams } from '../../shared/stack.CAni8dnD.cjs';
6
+ import '@btst/stack/plugins/api';
7
+ import '../../shared/stack.BttDsJJn.cjs';
8
+ import 'better-call';
9
+ import 'zod';
10
+ import '@btst/db';
11
+
12
+ declare function createMediaQueryKeys(client: ReturnType<typeof createApiClient<MediaApiRouter>>, headers?: HeadersInit): {
13
+ mediaAssets: {
14
+ _def: readonly ["mediaAssets"];
15
+ } & {
16
+ list: ((params?: AssetListParams | undefined) => Omit<Omit<{
17
+ queryKey: readonly ["mediaAssets", "list", AssetListDiscriminator];
18
+ queryFn: _tanstack_react_query.QueryFunction<unknown, readonly ["mediaAssets", "list", AssetListDiscriminator]>;
19
+ }, "queryFn"> & {
20
+ _def: readonly ["mediaAssets", "list"];
21
+ }, "_def">) & {
22
+ _def: readonly ["mediaAssets", "list"];
23
+ };
24
+ detail: ((id: string) => Omit<{
25
+ queryKey: readonly ["mediaAssets", "detail", string];
26
+ queryFn: _tanstack_react_query.QueryFunction<SerializedAsset | null, readonly ["mediaAssets", "detail", string]>;
27
+ } & {
28
+ _def: readonly ["mediaAssets", "detail"];
29
+ }, "_def">) & {
30
+ _def: readonly ["mediaAssets", "detail"];
31
+ };
32
+ };
33
+ mediaFolders: {
34
+ _def: readonly ["mediaFolders"];
35
+ } & {
36
+ list: ((parentId?: string | null | undefined) => Omit<{
37
+ queryKey: readonly ["mediaFolders", "list", string];
38
+ queryFn: _tanstack_react_query.QueryFunction<SerializedFolder[], readonly ["mediaFolders", "list", string]>;
39
+ } & {
40
+ _def: readonly ["mediaFolders", "list"];
41
+ }, "_def">) & {
42
+ _def: readonly ["mediaFolders", "list"];
43
+ };
44
+ };
45
+ };
46
+ type MediaQueryKeys = ReturnType<typeof createMediaQueryKeys>;
47
+
48
+ export { createMediaQueryKeys };
49
+ export type { MediaQueryKeys };
@@ -0,0 +1,49 @@
1
+ import * as _tanstack_react_query from '@tanstack/react-query';
2
+ import { M as MediaApiRouter, A as AssetListDiscriminator } from '../../shared/stack.FgBVDSPi.mjs';
3
+ import { createApiClient } from '@btst/stack/plugins/client';
4
+ import { S as SerializedAsset, a as SerializedFolder } from '../../shared/stack.CLcnSF_b.mjs';
5
+ import { A as AssetListParams } from '../../shared/stack.C7vfOBmO.mjs';
6
+ import '@btst/stack/plugins/api';
7
+ import '../../shared/stack.BttDsJJn.mjs';
8
+ import 'better-call';
9
+ import 'zod';
10
+ import '@btst/db';
11
+
12
+ declare function createMediaQueryKeys(client: ReturnType<typeof createApiClient<MediaApiRouter>>, headers?: HeadersInit): {
13
+ mediaAssets: {
14
+ _def: readonly ["mediaAssets"];
15
+ } & {
16
+ list: ((params?: AssetListParams | undefined) => Omit<Omit<{
17
+ queryKey: readonly ["mediaAssets", "list", AssetListDiscriminator];
18
+ queryFn: _tanstack_react_query.QueryFunction<unknown, readonly ["mediaAssets", "list", AssetListDiscriminator]>;
19
+ }, "queryFn"> & {
20
+ _def: readonly ["mediaAssets", "list"];
21
+ }, "_def">) & {
22
+ _def: readonly ["mediaAssets", "list"];
23
+ };
24
+ detail: ((id: string) => Omit<{
25
+ queryKey: readonly ["mediaAssets", "detail", string];
26
+ queryFn: _tanstack_react_query.QueryFunction<SerializedAsset | null, readonly ["mediaAssets", "detail", string]>;
27
+ } & {
28
+ _def: readonly ["mediaAssets", "detail"];
29
+ }, "_def">) & {
30
+ _def: readonly ["mediaAssets", "detail"];
31
+ };
32
+ };
33
+ mediaFolders: {
34
+ _def: readonly ["mediaFolders"];
35
+ } & {
36
+ list: ((parentId?: string | null | undefined) => Omit<{
37
+ queryKey: readonly ["mediaFolders", "list", string];
38
+ queryFn: _tanstack_react_query.QueryFunction<SerializedFolder[], readonly ["mediaFolders", "list", string]>;
39
+ } & {
40
+ _def: readonly ["mediaFolders", "list"];
41
+ }, "_def">) & {
42
+ _def: readonly ["mediaFolders", "list"];
43
+ };
44
+ };
45
+ };
46
+ type MediaQueryKeys = ReturnType<typeof createMediaQueryKeys>;
47
+
48
+ export { createMediaQueryKeys };
49
+ export type { MediaQueryKeys };
@@ -0,0 +1,49 @@
1
+ import * as _tanstack_react_query from '@tanstack/react-query';
2
+ import { M as MediaApiRouter, A as AssetListDiscriminator } from '../../shared/stack.DJDjdG64.js';
3
+ import { createApiClient } from '@btst/stack/plugins/client';
4
+ import { S as SerializedAsset, a as SerializedFolder } from '../../shared/stack.CLcnSF_b.js';
5
+ import { A as AssetListParams } from '../../shared/stack.CYSwntXC.js';
6
+ import '@btst/stack/plugins/api';
7
+ import '../../shared/stack.BttDsJJn.js';
8
+ import 'better-call';
9
+ import 'zod';
10
+ import '@btst/db';
11
+
12
+ declare function createMediaQueryKeys(client: ReturnType<typeof createApiClient<MediaApiRouter>>, headers?: HeadersInit): {
13
+ mediaAssets: {
14
+ _def: readonly ["mediaAssets"];
15
+ } & {
16
+ list: ((params?: AssetListParams | undefined) => Omit<Omit<{
17
+ queryKey: readonly ["mediaAssets", "list", AssetListDiscriminator];
18
+ queryFn: _tanstack_react_query.QueryFunction<unknown, readonly ["mediaAssets", "list", AssetListDiscriminator]>;
19
+ }, "queryFn"> & {
20
+ _def: readonly ["mediaAssets", "list"];
21
+ }, "_def">) & {
22
+ _def: readonly ["mediaAssets", "list"];
23
+ };
24
+ detail: ((id: string) => Omit<{
25
+ queryKey: readonly ["mediaAssets", "detail", string];
26
+ queryFn: _tanstack_react_query.QueryFunction<SerializedAsset | null, readonly ["mediaAssets", "detail", string]>;
27
+ } & {
28
+ _def: readonly ["mediaAssets", "detail"];
29
+ }, "_def">) & {
30
+ _def: readonly ["mediaAssets", "detail"];
31
+ };
32
+ };
33
+ mediaFolders: {
34
+ _def: readonly ["mediaFolders"];
35
+ } & {
36
+ list: ((parentId?: string | null | undefined) => Omit<{
37
+ queryKey: readonly ["mediaFolders", "list", string];
38
+ queryFn: _tanstack_react_query.QueryFunction<SerializedFolder[], readonly ["mediaFolders", "list", string]>;
39
+ } & {
40
+ _def: readonly ["mediaFolders", "list"];
41
+ }, "_def">) & {
42
+ _def: readonly ["mediaFolders", "list"];
43
+ };
44
+ };
45
+ };
46
+ type MediaQueryKeys = ReturnType<typeof createMediaQueryKeys>;
47
+
48
+ export { createMediaQueryKeys };
49
+ export type { MediaQueryKeys };
@@ -0,0 +1,70 @@
1
+ import { mergeQueryKeys, createQueryKeys } from '@lukemorales/query-key-factory';
2
+ import { assetListDiscriminator } from '../../packages/stack/src/plugins/media/api/query-key-defs.mjs';
3
+
4
+ function isErrorResponse(response) {
5
+ return typeof response === "object" && response !== null && "error" in response && response.error !== null && response.error !== void 0;
6
+ }
7
+ function toError(error) {
8
+ if (error instanceof Error) return error;
9
+ if (typeof error === "object" && error !== null) {
10
+ const errorObj = error;
11
+ const message = (typeof errorObj.message === "string" ? errorObj.message : null) || JSON.stringify(error);
12
+ const err = new Error(message);
13
+ Object.assign(err, error);
14
+ return err;
15
+ }
16
+ return new Error(String(error));
17
+ }
18
+ function createMediaQueryKeys(client, headers) {
19
+ return mergeQueryKeys(
20
+ createQueryKeys("mediaAssets", {
21
+ list: (params) => ({
22
+ queryKey: [assetListDiscriminator(params)],
23
+ queryFn: async ({ pageParam }) => {
24
+ const response = await client("/media/assets", {
25
+ method: "GET",
26
+ query: {
27
+ folderId: params?.folderId,
28
+ mimeType: params?.mimeType,
29
+ query: params?.query,
30
+ offset: pageParam ?? params?.offset ?? 0,
31
+ limit: params?.limit ?? 20
32
+ },
33
+ headers
34
+ });
35
+ if (isErrorResponse(response)) throw toError(response.error);
36
+ const data = response.data;
37
+ return data;
38
+ }
39
+ }),
40
+ detail: (id) => ({
41
+ queryKey: [id],
42
+ queryFn: async () => {
43
+ const response = await client("/media/assets", {
44
+ method: "GET",
45
+ query: { id },
46
+ headers
47
+ });
48
+ if (isErrorResponse(response)) throw toError(response.error);
49
+ return response.data;
50
+ }
51
+ })
52
+ }),
53
+ createQueryKeys("mediaFolders", {
54
+ list: (parentId) => ({
55
+ queryKey: [parentId ?? "root"],
56
+ queryFn: async () => {
57
+ const response = await client("/media/folders", {
58
+ method: "GET",
59
+ query: parentId !== void 0 ? { parentId: parentId ?? void 0 } : {},
60
+ headers
61
+ });
62
+ if (isErrorResponse(response)) throw toError(response.error);
63
+ return response.data;
64
+ }
65
+ })
66
+ })
67
+ );
68
+ }
69
+
70
+ export { createMediaQueryKeys };
@@ -0,0 +1 @@
1
+ @source "./client/**/*.{ts,tsx}";
@@ -141,6 +141,31 @@ interface KanbanPluginOverrides {
141
141
  * Optional headers to pass with API requests (e.g., for SSR auth)
142
142
  */
143
143
  headers?: HeadersInit;
144
+ /**
145
+ * Function used to upload a new image file from the task description editor
146
+ * and return its URL. This is separate from `imagePicker`, which selects an
147
+ * existing asset URL.
148
+ */
149
+ uploadImage?: (file: File) => Promise<string>;
150
+ /**
151
+ * Optional trigger component for a media picker.
152
+ * When provided, it appears inside the image insertion dialog of the task description editor,
153
+ * letting users browse and select previously uploaded assets.
154
+ *
155
+ * @example
156
+ * ```tsx
157
+ * imagePicker: ({ onSelect }) => (
158
+ * <MediaPicker
159
+ * trigger={<Button size="sm" variant="outline">Browse media</Button>}
160
+ * accept={["image/*"]}
161
+ * onSelect={(assets) => onSelect(assets[0].url)}
162
+ * />
163
+ * )
164
+ * ```
165
+ */
166
+ imagePicker?: ComponentType<{
167
+ onSelect: (url: string) => void;
168
+ }>;
144
169
  /**
145
170
  * Resolve user info from an assigneeId
146
171
  * Called when rendering task cards/forms that have an assignee