@btst/stack 2.8.1 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) 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/api/index.d.cts +2 -2
  74. package/dist/plugins/blog/api/index.d.mts +2 -2
  75. package/dist/plugins/blog/api/index.d.ts +2 -2
  76. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  77. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  78. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  79. package/dist/plugins/blog/client/index.d.cts +60 -3
  80. package/dist/plugins/blog/client/index.d.mts +60 -3
  81. package/dist/plugins/blog/client/index.d.ts +60 -3
  82. package/dist/plugins/blog/query-keys.d.cts +2 -2
  83. package/dist/plugins/blog/query-keys.d.mts +2 -2
  84. package/dist/plugins/blog/query-keys.d.ts +2 -2
  85. package/dist/plugins/cms/client/index.d.cts +73 -3
  86. package/dist/plugins/cms/client/index.d.mts +73 -3
  87. package/dist/plugins/cms/client/index.d.ts +73 -3
  88. package/dist/plugins/kanban/api/index.d.cts +1 -1
  89. package/dist/plugins/kanban/api/index.d.mts +1 -1
  90. package/dist/plugins/kanban/api/index.d.ts +1 -1
  91. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  92. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  93. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  94. package/dist/plugins/kanban/client/index.d.cts +1 -1
  95. package/dist/plugins/kanban/client/index.d.mts +1 -1
  96. package/dist/plugins/kanban/client/index.d.ts +1 -1
  97. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  98. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  99. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  100. package/dist/plugins/media/api/adapters/s3.cjs +106 -0
  101. package/dist/plugins/media/api/adapters/s3.d.cts +60 -0
  102. package/dist/plugins/media/api/adapters/s3.d.mts +60 -0
  103. package/dist/plugins/media/api/adapters/s3.d.ts +60 -0
  104. package/dist/plugins/media/api/adapters/s3.mjs +104 -0
  105. package/dist/plugins/media/api/adapters/vercel-blob.cjs +54 -0
  106. package/dist/plugins/media/api/adapters/vercel-blob.d.cts +41 -0
  107. package/dist/plugins/media/api/adapters/vercel-blob.d.mts +41 -0
  108. package/dist/plugins/media/api/adapters/vercel-blob.d.ts +41 -0
  109. package/dist/plugins/media/api/adapters/vercel-blob.mjs +52 -0
  110. package/dist/plugins/media/api/index.cjs +26 -0
  111. package/dist/plugins/media/api/index.d.cts +116 -0
  112. package/dist/plugins/media/api/index.d.mts +116 -0
  113. package/dist/plugins/media/api/index.d.ts +116 -0
  114. package/dist/plugins/media/api/index.mjs +6 -0
  115. package/dist/plugins/media/client/components/index.cjs +10 -0
  116. package/dist/plugins/media/client/components/index.d.cts +55 -0
  117. package/dist/plugins/media/client/components/index.d.mts +55 -0
  118. package/dist/plugins/media/client/components/index.d.ts +55 -0
  119. package/dist/plugins/media/client/components/index.mjs +2 -0
  120. package/dist/plugins/media/client/hooks/index.cjs +13 -0
  121. package/dist/plugins/media/client/hooks/index.d.cts +53 -0
  122. package/dist/plugins/media/client/hooks/index.d.mts +53 -0
  123. package/dist/plugins/media/client/hooks/index.d.ts +53 -0
  124. package/dist/plugins/media/client/hooks/index.mjs +1 -0
  125. package/dist/plugins/media/client/index.cjs +9 -0
  126. package/dist/plugins/media/client/index.d.cts +242 -0
  127. package/dist/plugins/media/client/index.d.mts +242 -0
  128. package/dist/plugins/media/client/index.d.ts +242 -0
  129. package/dist/plugins/media/client/index.mjs +2 -0
  130. package/dist/plugins/media/client.css +1 -0
  131. package/dist/plugins/media/query-keys.cjs +72 -0
  132. package/dist/plugins/media/query-keys.d.cts +49 -0
  133. package/dist/plugins/media/query-keys.d.mts +49 -0
  134. package/dist/plugins/media/query-keys.d.ts +49 -0
  135. package/dist/plugins/media/query-keys.mjs +70 -0
  136. package/dist/plugins/media/style.css +1 -0
  137. package/dist/shared/{stack.DOZ1EXjM.d.mts → stack.6mEHS2WH.d.mts} +3 -3
  138. package/dist/shared/{stack.DX-tQ93o.d.cts → stack.AJTXI7kw.d.cts} +3 -3
  139. package/dist/shared/{stack.DRpeDS6X.d.ts → stack.BMx2QYOK.d.ts} +25 -0
  140. package/dist/shared/stack.BUTXWiG-.d.ts +286 -0
  141. package/dist/shared/stack.C7Y9sBDg.d.mts +286 -0
  142. package/dist/shared/stack.C7vfOBmO.d.mts +63 -0
  143. package/dist/shared/stack.CAni8dnD.d.cts +63 -0
  144. package/dist/shared/stack.CLcnSF_b.d.cts +25 -0
  145. package/dist/shared/stack.CLcnSF_b.d.mts +25 -0
  146. package/dist/shared/stack.CLcnSF_b.d.ts +25 -0
  147. package/dist/shared/stack.CYSwntXC.d.ts +63 -0
  148. package/dist/shared/{stack.Jb0kQDJC.d.mts → stack.Cd6McBu1.d.mts} +25 -0
  149. package/dist/shared/stack.CoBj86jf.d.cts +109 -0
  150. package/dist/shared/stack.CoBj86jf.d.mts +109 -0
  151. package/dist/shared/stack.CoBj86jf.d.ts +109 -0
  152. package/dist/shared/{stack.BXxrFL9R.d.ts → stack.D7HSzZdG.d.ts} +5 -5
  153. package/dist/shared/{stack.DzOhpIYM.d.mts → stack.DjgpFWq3.d.cts} +5 -5
  154. package/dist/shared/{stack.BxFl46lB.d.cts → stack.DxQl8Wa1.d.cts} +25 -0
  155. package/dist/shared/{stack.BSqJrCTM.d.cts → stack.IUeyQKrm.d.mts} +5 -5
  156. package/dist/shared/{stack.VF6FhyZw.d.ts → stack.QYn-Px94.d.ts} +3 -3
  157. package/dist/shared/stack.vxskCkim.d.cts +286 -0
  158. package/package.json +113 -4
  159. package/src/plugins/blog/client/components/forms/image-field.tsx +35 -4
  160. package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +67 -12
  161. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +106 -10
  162. package/src/plugins/blog/client/overrides.ts +58 -1
  163. package/src/plugins/cms/client/components/forms/content-form.tsx +26 -7
  164. package/src/plugins/cms/client/components/forms/file-upload.tsx +73 -15
  165. package/src/plugins/cms/client/overrides.ts +57 -2
  166. package/src/plugins/kanban/client/components/forms/board-form.tsx +1 -1
  167. package/src/plugins/kanban/client/components/forms/task-form.tsx +7 -1
  168. package/src/plugins/kanban/client/overrides.ts +25 -0
  169. package/src/plugins/media/__tests__/__stubs__/vercel-blob-server.ts +9 -0
  170. package/src/plugins/media/__tests__/getters.test.ts +274 -0
  171. package/src/plugins/media/__tests__/mutations.test.ts +299 -0
  172. package/src/plugins/media/__tests__/plugin.test.ts +752 -0
  173. package/src/plugins/media/__tests__/query-key-defs.test.ts +54 -0
  174. package/src/plugins/media/__tests__/storage-adapters.test.ts +351 -0
  175. package/src/plugins/media/api/adapters/local.ts +79 -0
  176. package/src/plugins/media/api/adapters/s3.ts +198 -0
  177. package/src/plugins/media/api/adapters/vercel-blob.ts +132 -0
  178. package/src/plugins/media/api/getters.ts +174 -0
  179. package/src/plugins/media/api/index.ts +41 -0
  180. package/src/plugins/media/api/mutations.ts +179 -0
  181. package/src/plugins/media/api/plugin.ts +855 -0
  182. package/src/plugins/media/api/query-key-defs.ts +41 -0
  183. package/src/plugins/media/api/serializers.ts +28 -0
  184. package/src/plugins/media/api/storage-adapter.ts +139 -0
  185. package/src/plugins/media/client/components/index.tsx +6 -0
  186. package/src/plugins/media/client/components/media-picker/asset-card.tsx +150 -0
  187. package/src/plugins/media/client/components/media-picker/asset-preview-button.tsx +67 -0
  188. package/src/plugins/media/client/components/media-picker/browse-tab.tsx +116 -0
  189. package/src/plugins/media/client/components/media-picker/folder-tree.tsx +188 -0
  190. package/src/plugins/media/client/components/media-picker/index.tsx +347 -0
  191. package/src/plugins/media/client/components/media-picker/upload-tab.tsx +108 -0
  192. package/src/plugins/media/client/components/media-picker/url-tab.tsx +72 -0
  193. package/src/plugins/media/client/components/media-picker/utils.ts +17 -0
  194. package/src/plugins/media/client/components/pages/library-page.internal.tsx +134 -0
  195. package/src/plugins/media/client/components/pages/library-page.tsx +42 -0
  196. package/src/plugins/media/client/hooks/index.tsx +9 -0
  197. package/src/plugins/media/client/hooks/use-media.tsx +289 -0
  198. package/src/plugins/media/client/index.ts +4 -0
  199. package/src/plugins/media/client/overrides.ts +127 -0
  200. package/src/plugins/media/client/plugin.tsx +184 -0
  201. package/src/plugins/media/client/upload.ts +171 -0
  202. package/src/plugins/media/client/utils/image-compression.ts +131 -0
  203. package/src/plugins/media/client.css +1 -0
  204. package/src/plugins/media/db.ts +62 -0
  205. package/src/plugins/media/query-keys.ts +96 -0
  206. package/src/plugins/media/schemas.ts +37 -0
  207. package/src/plugins/media/style.css +1 -0
  208. package/src/plugins/media/types.ts +26 -0
  209. package/dist/shared/{stack.BWp0hcm9.d.ts → stack.BQmuNl5p.d.cts} +3 -3
  210. package/dist/shared/{stack.BWp0hcm9.d.cts → stack.BQmuNl5p.d.mts} +3 -3
  211. package/dist/shared/{stack.BWp0hcm9.d.mts → stack.BQmuNl5p.d.ts} +3 -3
  212. package/dist/shared/{stack.BvCR4-9H.d.ts → stack.D4Cea8II.d.ts} +3 -3
  213. package/dist/shared/{stack.CWxAl9K3.d.mts → stack.HE_IvqV5.d.mts} +3 -3
  214. package/dist/shared/{stack.BOokfhZD.d.cts → stack.Rtcvl8sS.d.cts} +3 -3
@@ -0,0 +1,17 @@
1
+ function matchesAccept(mimeType, accept) {
2
+ if (!accept || accept.length === 0) return true;
3
+ return accept.some((a) => {
4
+ if (a.endsWith("/*")) return mimeType.startsWith(a.slice(0, -1));
5
+ return mimeType === a;
6
+ });
7
+ }
8
+ function isImage(mimeType) {
9
+ return mimeType.startsWith("image/");
10
+ }
11
+ function formatBytes(bytes) {
12
+ if (bytes < 1024) return `${bytes} B`;
13
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
14
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
15
+ }
16
+
17
+ export { formatBytes, isImage, matchesAccept };
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const context = require('@btst/stack/context');
7
+ const components = require('@btst/stack/client/components');
8
+ const LucideIcons = require('lucide-react');
9
+
10
+ const LibraryPage = React.lazy(
11
+ () => import('./library-page.internal.cjs').then((m) => ({ default: m.LibraryPage }))
12
+ );
13
+ function LibraryLoading() {
14
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-96 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "size-8 animate-spin text-muted-foreground" }) });
15
+ }
16
+ function LibraryError({ error }) {
17
+ const message = error instanceof Error ? error.message : String(error);
18
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-96 items-center justify-center p-8 text-destructive", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: message }) });
19
+ }
20
+ function LibraryPageComponent() {
21
+ context.usePluginOverrides("media");
22
+ return /* @__PURE__ */ jsxRuntime.jsx(
23
+ components.ComposedRoute,
24
+ {
25
+ path: "/media",
26
+ PageComponent: LibraryPage,
27
+ ErrorComponent: LibraryError,
28
+ LoadingComponent: LibraryLoading,
29
+ NotFoundComponent: () => null,
30
+ onError: (error) => console.error("[btst/media] Library error:", error)
31
+ }
32
+ );
33
+ }
34
+
35
+ exports.LibraryPageComponent = LibraryPageComponent;
@@ -0,0 +1,125 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const useMedia = require('../../hooks/use-media.cjs');
7
+ const button = require('../../../../../../../ui/src/components/button.cjs');
8
+ const LucideIcons = require('lucide-react');
9
+ const utils = require('../../../../../../../ui/src/lib/utils.cjs');
10
+ const sonner = require('sonner');
11
+ const context = require('@btst/stack/context');
12
+ const useRouteLifecycle = require('../../../../../../../ui/src/hooks/use-route-lifecycle.cjs');
13
+ const browseTab = require('../media-picker/browse-tab.cjs');
14
+ const folderTree = require('../media-picker/folder-tree.cjs');
15
+
16
+ function LibraryPage() {
17
+ const overrides = context.usePluginOverrides("media", {});
18
+ useRouteLifecycle.useRouteLifecycle({
19
+ routeName: "library",
20
+ context: {
21
+ path: "/media",
22
+ isSSR: typeof window === "undefined"
23
+ },
24
+ overrides,
25
+ beforeRenderHook: (overrides2, context) => {
26
+ if (overrides2.onBeforeLibraryPageRendered) {
27
+ return overrides2.onBeforeLibraryPageRendered(context);
28
+ }
29
+ return true;
30
+ }
31
+ });
32
+ const [selectedFolder, setSelectedFolder] = React.useState(null);
33
+ const [dragging, setDragging] = React.useState(false);
34
+ const fileInputRef = React.useRef(null);
35
+ const { mutateAsync: uploadAsset, isPending: isUploading } = useMedia.useUploadAsset();
36
+ const { mutateAsync: deleteAsset } = useMedia.useDeleteAsset();
37
+ const { apiBaseURL = "" } = overrides;
38
+ const handleUpload = React.useCallback(
39
+ async (files) => {
40
+ const arr = Array.from(files);
41
+ for (const file of arr) {
42
+ try {
43
+ await uploadAsset({ file, folderId: selectedFolder ?? void 0 });
44
+ sonner.toast.success(`Uploaded ${file.name}`);
45
+ } catch (err) {
46
+ sonner.toast.error(err instanceof Error ? err.message : "Upload failed");
47
+ }
48
+ }
49
+ },
50
+ [selectedFolder, uploadAsset]
51
+ );
52
+ const handleDelete = async (id) => {
53
+ if (!confirm("Delete this asset?")) return;
54
+ try {
55
+ await deleteAsset(id);
56
+ sonner.toast.success("Deleted");
57
+ } catch (err) {
58
+ sonner.toast.error(err instanceof Error ? err.message : "Delete failed");
59
+ }
60
+ };
61
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-[calc(100dvh-4rem)] flex-col overflow-hidden md:flex-row", children: [
62
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-48 shrink-0 overflow-hidden border-b bg-muted/20 md:h-full md:max-h-none md:w-52 md:border-b-0 md:border-r", children: /* @__PURE__ */ jsxRuntime.jsx(folderTree.FolderTree, { selectedId: selectedFolder, onSelect: setSelectedFolder }) }),
63
+ /* @__PURE__ */ jsxRuntime.jsxs(
64
+ "div",
65
+ {
66
+ className: utils.cn(
67
+ "relative flex flex-1 flex-col overflow-hidden border-t md:border-t-0",
68
+ dragging && "ring-2 ring-inset ring-ring"
69
+ ),
70
+ onDragOver: (e) => {
71
+ e.preventDefault();
72
+ setDragging(true);
73
+ },
74
+ onDragLeave: () => setDragging(false),
75
+ onDrop: (e) => {
76
+ e.preventDefault();
77
+ setDragging(false);
78
+ void handleUpload(e.dataTransfer.files);
79
+ },
80
+ children: [
81
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 border-b px-4 py-3 sm:flex-row sm:items-center sm:justify-end", children: [
82
+ /* @__PURE__ */ jsxRuntime.jsxs(
83
+ button.Button,
84
+ {
85
+ size: "sm",
86
+ onClick: () => fileInputRef.current?.click(),
87
+ disabled: isUploading,
88
+ className: "w-full sm:w-auto",
89
+ children: [
90
+ isUploading ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "mr-2 size-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Upload, { className: "mr-2 size-3.5" }),
91
+ "Upload"
92
+ ]
93
+ }
94
+ ),
95
+ /* @__PURE__ */ jsxRuntime.jsx(
96
+ "input",
97
+ {
98
+ ref: fileInputRef,
99
+ type: "file",
100
+ multiple: true,
101
+ className: "hidden",
102
+ onChange: (e) => e.target.files && handleUpload(e.target.files)
103
+ }
104
+ )
105
+ ] }),
106
+ dragging && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center bg-background/80", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border-2 border-dashed border-ring p-8 text-center", children: [
107
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Upload, { className: "mx-auto mb-2 size-10 text-ring" }),
108
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: "Drop files to upload" })
109
+ ] }) }),
110
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0 p-3 sm:p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
111
+ browseTab.BrowseTab,
112
+ {
113
+ folderId: selectedFolder,
114
+ onDelete: handleDelete,
115
+ apiBaseURL,
116
+ emptyMessage: "No files yet. Drag & drop or click Upload."
117
+ }
118
+ ) })
119
+ ]
120
+ }
121
+ )
122
+ ] });
123
+ }
124
+
125
+ exports.LibraryPage = LibraryPage;
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useState, useRef, useCallback } from 'react';
4
+ import { useUploadAsset, useDeleteAsset } from '../../hooks/use-media.mjs';
5
+ import { Button } from '../../../../../../../ui/src/components/button.mjs';
6
+ import { Loader2, Upload } from 'lucide-react';
7
+ import { cn } from '../../../../../../../ui/src/lib/utils.mjs';
8
+ import { toast } from 'sonner';
9
+ import { usePluginOverrides } from '@btst/stack/context';
10
+ import { useRouteLifecycle } from '../../../../../../../ui/src/hooks/use-route-lifecycle.mjs';
11
+ import { BrowseTab } from '../media-picker/browse-tab.mjs';
12
+ import { FolderTree } from '../media-picker/folder-tree.mjs';
13
+
14
+ function LibraryPage() {
15
+ const overrides = usePluginOverrides("media", {});
16
+ useRouteLifecycle({
17
+ routeName: "library",
18
+ context: {
19
+ path: "/media",
20
+ isSSR: typeof window === "undefined"
21
+ },
22
+ overrides,
23
+ beforeRenderHook: (overrides2, context) => {
24
+ if (overrides2.onBeforeLibraryPageRendered) {
25
+ return overrides2.onBeforeLibraryPageRendered(context);
26
+ }
27
+ return true;
28
+ }
29
+ });
30
+ const [selectedFolder, setSelectedFolder] = useState(null);
31
+ const [dragging, setDragging] = useState(false);
32
+ const fileInputRef = useRef(null);
33
+ const { mutateAsync: uploadAsset, isPending: isUploading } = useUploadAsset();
34
+ const { mutateAsync: deleteAsset } = useDeleteAsset();
35
+ const { apiBaseURL = "" } = overrides;
36
+ const handleUpload = useCallback(
37
+ async (files) => {
38
+ const arr = Array.from(files);
39
+ for (const file of arr) {
40
+ try {
41
+ await uploadAsset({ file, folderId: selectedFolder ?? void 0 });
42
+ toast.success(`Uploaded ${file.name}`);
43
+ } catch (err) {
44
+ toast.error(err instanceof Error ? err.message : "Upload failed");
45
+ }
46
+ }
47
+ },
48
+ [selectedFolder, uploadAsset]
49
+ );
50
+ const handleDelete = async (id) => {
51
+ if (!confirm("Delete this asset?")) return;
52
+ try {
53
+ await deleteAsset(id);
54
+ toast.success("Deleted");
55
+ } catch (err) {
56
+ toast.error(err instanceof Error ? err.message : "Delete failed");
57
+ }
58
+ };
59
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-[calc(100dvh-4rem)] flex-col overflow-hidden md:flex-row", children: [
60
+ /* @__PURE__ */ jsx("div", { className: "max-h-48 shrink-0 overflow-hidden border-b bg-muted/20 md:h-full md:max-h-none md:w-52 md:border-b-0 md:border-r", children: /* @__PURE__ */ jsx(FolderTree, { selectedId: selectedFolder, onSelect: setSelectedFolder }) }),
61
+ /* @__PURE__ */ jsxs(
62
+ "div",
63
+ {
64
+ className: cn(
65
+ "relative flex flex-1 flex-col overflow-hidden border-t md:border-t-0",
66
+ dragging && "ring-2 ring-inset ring-ring"
67
+ ),
68
+ onDragOver: (e) => {
69
+ e.preventDefault();
70
+ setDragging(true);
71
+ },
72
+ onDragLeave: () => setDragging(false),
73
+ onDrop: (e) => {
74
+ e.preventDefault();
75
+ setDragging(false);
76
+ void handleUpload(e.dataTransfer.files);
77
+ },
78
+ children: [
79
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 border-b px-4 py-3 sm:flex-row sm:items-center sm:justify-end", children: [
80
+ /* @__PURE__ */ jsxs(
81
+ Button,
82
+ {
83
+ size: "sm",
84
+ onClick: () => fileInputRef.current?.click(),
85
+ disabled: isUploading,
86
+ className: "w-full sm:w-auto",
87
+ children: [
88
+ isUploading ? /* @__PURE__ */ jsx(Loader2, { className: "mr-2 size-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Upload, { className: "mr-2 size-3.5" }),
89
+ "Upload"
90
+ ]
91
+ }
92
+ ),
93
+ /* @__PURE__ */ jsx(
94
+ "input",
95
+ {
96
+ ref: fileInputRef,
97
+ type: "file",
98
+ multiple: true,
99
+ className: "hidden",
100
+ onChange: (e) => e.target.files && handleUpload(e.target.files)
101
+ }
102
+ )
103
+ ] }),
104
+ dragging && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center bg-background/80", children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border-2 border-dashed border-ring p-8 text-center", children: [
105
+ /* @__PURE__ */ jsx(Upload, { className: "mx-auto mb-2 size-10 text-ring" }),
106
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Drop files to upload" })
107
+ ] }) }),
108
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 p-3 sm:p-4", children: /* @__PURE__ */ jsx(
109
+ BrowseTab,
110
+ {
111
+ folderId: selectedFolder,
112
+ onDelete: handleDelete,
113
+ apiBaseURL,
114
+ emptyMessage: "No files yet. Drag & drop or click Upload."
115
+ }
116
+ ) })
117
+ ]
118
+ }
119
+ )
120
+ ] });
121
+ }
122
+
123
+ export { LibraryPage };
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { lazy } from 'react';
4
+ import { usePluginOverrides } from '@btst/stack/context';
5
+ import { ComposedRoute } from '@btst/stack/client/components';
6
+ import { Loader2 } from 'lucide-react';
7
+
8
+ const LibraryPage = lazy(
9
+ () => import('./library-page.internal.mjs').then((m) => ({ default: m.LibraryPage }))
10
+ );
11
+ function LibraryLoading() {
12
+ return /* @__PURE__ */ jsx("div", { className: "flex h-96 items-center justify-center", children: /* @__PURE__ */ jsx(Loader2, { className: "size-8 animate-spin text-muted-foreground" }) });
13
+ }
14
+ function LibraryError({ error }) {
15
+ const message = error instanceof Error ? error.message : String(error);
16
+ return /* @__PURE__ */ jsx("div", { className: "flex h-96 items-center justify-center p-8 text-destructive", children: /* @__PURE__ */ jsx("p", { className: "text-sm", children: message }) });
17
+ }
18
+ function LibraryPageComponent() {
19
+ usePluginOverrides("media");
20
+ return /* @__PURE__ */ jsx(
21
+ ComposedRoute,
22
+ {
23
+ path: "/media",
24
+ PageComponent: LibraryPage,
25
+ ErrorComponent: LibraryError,
26
+ LoadingComponent: LibraryLoading,
27
+ NotFoundComponent: () => null,
28
+ onError: (error) => console.error("[btst/media] Library error:", error)
29
+ }
30
+ );
31
+ }
32
+
33
+ export { LibraryPageComponent };
@@ -0,0 +1,222 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ const reactQuery = require('@tanstack/react-query');
5
+ const context = require('@btst/stack/context');
6
+ const client = require('@btst/stack/plugins/client');
7
+ const plugins_media_queryKeys = require('../../../../../../../plugins/media/query-keys.cjs');
8
+ const upload = require('../upload.cjs');
9
+
10
+ function useMediaConfig() {
11
+ return context.usePluginOverrides("media");
12
+ }
13
+ function useMediaApiClient() {
14
+ const { apiBaseURL, apiBasePath, headers } = useMediaConfig();
15
+ const client$1 = client.createApiClient({
16
+ baseURL: apiBaseURL,
17
+ basePath: apiBasePath
18
+ });
19
+ return { client: client$1, headers };
20
+ }
21
+ function useAssets(params) {
22
+ const { client, headers } = useMediaApiClient();
23
+ const queries = plugins_media_queryKeys.createMediaQueryKeys(client, headers);
24
+ const { queryClient } = useMediaConfig();
25
+ params?.limit ?? 20;
26
+ return reactQuery.useInfiniteQuery(
27
+ {
28
+ ...queries.mediaAssets.list(params),
29
+ initialPageParam: 0,
30
+ refetchOnMount: "always",
31
+ getNextPageParam: (lastPage, _allPages, lastPageParam) => {
32
+ const offset = (lastPage.offset ?? 0) + lastPage.items.length;
33
+ return offset < lastPage.total ? offset : void 0;
34
+ }
35
+ },
36
+ queryClient
37
+ );
38
+ }
39
+ function useFolders(parentId) {
40
+ const { client, headers } = useMediaApiClient();
41
+ const queries = plugins_media_queryKeys.createMediaQueryKeys(client, headers);
42
+ const { queryClient } = useMediaConfig();
43
+ return reactQuery.useQuery(
44
+ {
45
+ ...queries.mediaFolders.list(parentId)
46
+ },
47
+ queryClient
48
+ );
49
+ }
50
+ function useUploadAsset() {
51
+ const {
52
+ apiBaseURL,
53
+ apiBasePath,
54
+ headers,
55
+ uploadMode = "direct",
56
+ imageCompression,
57
+ queryClient: qc
58
+ } = useMediaConfig();
59
+ const reactQueryClient = reactQuery.useQueryClient(qc);
60
+ return reactQuery.useMutation(
61
+ {
62
+ mutationFn: async ({
63
+ file,
64
+ folderId
65
+ }) => upload.uploadAsset(
66
+ {
67
+ apiBaseURL,
68
+ apiBasePath,
69
+ headers,
70
+ uploadMode,
71
+ imageCompression
72
+ },
73
+ { file, folderId }
74
+ ),
75
+ onSuccess: () => {
76
+ reactQueryClient.invalidateQueries({ queryKey: ["mediaAssets"] });
77
+ }
78
+ },
79
+ qc
80
+ );
81
+ }
82
+ function useRegisterAsset() {
83
+ const {
84
+ apiBaseURL,
85
+ apiBasePath,
86
+ headers,
87
+ queryClient: qc
88
+ } = useMediaConfig();
89
+ const reactQueryClient = reactQuery.useQueryClient(qc);
90
+ return reactQuery.useMutation(
91
+ {
92
+ mutationFn: async (input) => {
93
+ const base = `${apiBaseURL}${apiBasePath}`;
94
+ const headersObj = new Headers(headers);
95
+ const res = await fetch(`${base}/media/assets`, {
96
+ method: "POST",
97
+ headers: {
98
+ ...Object.fromEntries(headersObj.entries()),
99
+ "Content-Type": "application/json"
100
+ },
101
+ body: JSON.stringify({
102
+ filename: input.filename,
103
+ originalName: input.filename,
104
+ mimeType: input.mimeType ?? "application/octet-stream",
105
+ size: input.size ?? 0,
106
+ url: input.url,
107
+ folderId: input.folderId
108
+ })
109
+ });
110
+ if (!res.ok) {
111
+ const err = await res.json().catch(() => ({ message: res.statusText }));
112
+ throw new Error(err.message ?? "Failed to register asset");
113
+ }
114
+ return res.json();
115
+ },
116
+ onSuccess: () => {
117
+ reactQueryClient.invalidateQueries({ queryKey: ["mediaAssets"] });
118
+ }
119
+ },
120
+ qc
121
+ );
122
+ }
123
+ function useDeleteAsset() {
124
+ const {
125
+ apiBaseURL,
126
+ apiBasePath,
127
+ headers,
128
+ queryClient: qc
129
+ } = useMediaConfig();
130
+ const reactQueryClient = reactQuery.useQueryClient(qc);
131
+ return reactQuery.useMutation(
132
+ {
133
+ mutationFn: async (id) => {
134
+ const base = `${apiBaseURL}${apiBasePath}`;
135
+ const headersObj = new Headers(headers);
136
+ const res = await fetch(`${base}/media/assets/${id}`, {
137
+ method: "DELETE",
138
+ headers: headersObj
139
+ });
140
+ if (!res.ok) {
141
+ const err = await res.json().catch(() => ({ message: res.statusText }));
142
+ throw new Error(err.message ?? "Delete failed");
143
+ }
144
+ },
145
+ onSuccess: () => {
146
+ reactQueryClient.invalidateQueries({ queryKey: ["mediaAssets"] });
147
+ }
148
+ },
149
+ qc
150
+ );
151
+ }
152
+ function useCreateFolder() {
153
+ const {
154
+ apiBaseURL,
155
+ apiBasePath,
156
+ headers,
157
+ queryClient: qc
158
+ } = useMediaConfig();
159
+ const reactQueryClient = reactQuery.useQueryClient(qc);
160
+ return reactQuery.useMutation(
161
+ {
162
+ mutationFn: async (input) => {
163
+ const base = `${apiBaseURL}${apiBasePath}`;
164
+ const headersObj = new Headers(headers);
165
+ const res = await fetch(`${base}/media/folders`, {
166
+ method: "POST",
167
+ headers: {
168
+ ...Object.fromEntries(headersObj.entries()),
169
+ "Content-Type": "application/json"
170
+ },
171
+ body: JSON.stringify(input)
172
+ });
173
+ if (!res.ok) {
174
+ const err = await res.json().catch(() => ({ message: res.statusText }));
175
+ throw new Error(err.message ?? "Failed to create folder");
176
+ }
177
+ return res.json();
178
+ },
179
+ onSuccess: () => {
180
+ reactQueryClient.invalidateQueries({ queryKey: ["mediaFolders"] });
181
+ }
182
+ },
183
+ qc
184
+ );
185
+ }
186
+ function useDeleteFolder() {
187
+ const {
188
+ apiBaseURL,
189
+ apiBasePath,
190
+ headers,
191
+ queryClient: qc
192
+ } = useMediaConfig();
193
+ const reactQueryClient = reactQuery.useQueryClient(qc);
194
+ return reactQuery.useMutation(
195
+ {
196
+ mutationFn: async (id) => {
197
+ const base = `${apiBaseURL}${apiBasePath}`;
198
+ const headersObj = new Headers(headers);
199
+ const res = await fetch(`${base}/media/folders/${id}`, {
200
+ method: "DELETE",
201
+ headers: headersObj
202
+ });
203
+ if (!res.ok) {
204
+ const err = await res.json().catch(() => ({ message: res.statusText }));
205
+ throw new Error(err.message ?? "Failed to delete folder");
206
+ }
207
+ },
208
+ onSuccess: () => {
209
+ reactQueryClient.invalidateQueries({ queryKey: ["mediaFolders"] });
210
+ }
211
+ },
212
+ qc
213
+ );
214
+ }
215
+
216
+ exports.useAssets = useAssets;
217
+ exports.useCreateFolder = useCreateFolder;
218
+ exports.useDeleteAsset = useDeleteAsset;
219
+ exports.useDeleteFolder = useDeleteFolder;
220
+ exports.useFolders = useFolders;
221
+ exports.useRegisterAsset = useRegisterAsset;
222
+ exports.useUploadAsset = useUploadAsset;