@btst/stack 2.8.0 → 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 +115 -6
  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,129 @@
1
+ 'use strict';
2
+
3
+ const jsxRuntime = require('react/jsx-runtime');
4
+ const useMedia = require('../../hooks/use-media.cjs');
5
+ const utils$1 = require('../../../../../../../ui/src/lib/utils.cjs');
6
+ const LucideIcons = require('lucide-react');
7
+ const utils = require('./utils.cjs');
8
+ const context = require('@btst/stack/context');
9
+ const assetPreviewButton = require('./asset-preview-button.cjs');
10
+ const sonner = require('sonner');
11
+
12
+ function AssetCard({
13
+ asset,
14
+ onToggle,
15
+ selected = false,
16
+ onDelete,
17
+ apiBaseURL
18
+ }) {
19
+ const { mutateAsync: deleteAsset } = useMedia.useDeleteAsset();
20
+ const { Image: ImageComponent } = context.usePluginOverrides("media", {});
21
+ const imageAsset = utils.isImage(asset.mimeType);
22
+ const selectable = typeof onToggle === "function";
23
+ const copyUrl = () => {
24
+ let fullUrl;
25
+ try {
26
+ fullUrl = new URL(asset.url, apiBaseURL).href;
27
+ } catch {
28
+ fullUrl = asset.url;
29
+ }
30
+ navigator.clipboard.writeText(fullUrl).then(() => sonner.toast.success("URL copied"));
31
+ };
32
+ const handleDelete = () => {
33
+ if (onDelete) {
34
+ return onDelete(asset.id);
35
+ }
36
+ if (confirm(`Delete "${asset.originalName}"?`)) {
37
+ return deleteAsset(asset.id).catch(console.error);
38
+ }
39
+ };
40
+ return /* @__PURE__ */ jsxRuntime.jsxs(
41
+ "div",
42
+ {
43
+ role: selectable ? "button" : void 0,
44
+ tabIndex: selectable ? 0 : void 0,
45
+ "data-testid": "media-asset-item",
46
+ onClick: onToggle,
47
+ onKeyDown: (e) => {
48
+ if (selectable && (e.key === "Enter" || e.key === " ")) {
49
+ e.preventDefault();
50
+ onToggle();
51
+ }
52
+ },
53
+ className: utils$1.cn(
54
+ "group relative cursor-pointer rounded-md border bg-muted/30 p-1 transition-all hover:border-ring hover:shadow-sm",
55
+ !selectable && "cursor-default",
56
+ selected && "border-ring ring-1 ring-ring"
57
+ ),
58
+ children: [
59
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-28 items-center justify-center overflow-hidden rounded bg-muted", children: imageAsset ? ImageComponent ? /* @__PURE__ */ jsxRuntime.jsx(
60
+ ImageComponent,
61
+ {
62
+ src: asset.url,
63
+ alt: asset.alt || asset.originalName,
64
+ className: "h-full w-full object-cover",
65
+ width: 160,
66
+ height: 80
67
+ }
68
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
69
+ "img",
70
+ {
71
+ src: asset.url,
72
+ alt: asset.alt || asset.originalName,
73
+ className: "h-full w-full object-cover",
74
+ loading: "lazy"
75
+ }
76
+ ) : /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.File, { className: "size-8 text-muted-foreground" }) }),
77
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 px-0.5", children: [
78
+ /* @__PURE__ */ jsxRuntime.jsx(
79
+ "p",
80
+ {
81
+ className: "truncate text-xs font-medium leading-tight",
82
+ title: asset.originalName,
83
+ children: asset.originalName
84
+ }
85
+ ),
86
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground", children: utils.formatBytes(asset.size) })
87
+ ] }),
88
+ selected && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-1 top-1 rounded-full bg-primary p-0.5 text-primary-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Check, { className: "size-3" }) }),
89
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-1 top-1 hidden gap-1 group-hover:flex", children: [
90
+ apiBaseURL ? /* @__PURE__ */ jsxRuntime.jsx(
91
+ "button",
92
+ {
93
+ type: "button",
94
+ title: "Copy URL",
95
+ onClick: (e) => {
96
+ e.stopPropagation();
97
+ copyUrl();
98
+ },
99
+ className: "rounded bg-background/80 p-0.5 shadow hover:bg-background",
100
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Copy, { className: "size-3" })
101
+ }
102
+ ) : null,
103
+ imageAsset ? /* @__PURE__ */ jsxRuntime.jsx(
104
+ assetPreviewButton.AssetPreviewButton,
105
+ {
106
+ asset,
107
+ className: "rounded bg-background/80 p-0.5 shadow hover:bg-background"
108
+ }
109
+ ) : null,
110
+ /* @__PURE__ */ jsxRuntime.jsx(
111
+ "button",
112
+ {
113
+ type: "button",
114
+ title: "Delete",
115
+ onClick: (e) => {
116
+ e.stopPropagation();
117
+ void handleDelete();
118
+ },
119
+ className: "rounded bg-destructive/80 p-0.5 text-white hover:bg-destructive",
120
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Trash2, { className: "size-3" })
121
+ }
122
+ )
123
+ ] })
124
+ ]
125
+ }
126
+ );
127
+ }
128
+
129
+ exports.AssetCard = AssetCard;
@@ -0,0 +1,127 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useDeleteAsset } from '../../hooks/use-media.mjs';
3
+ import { cn } from '../../../../../../../ui/src/lib/utils.mjs';
4
+ import { File, Check, Copy, Trash2 } from 'lucide-react';
5
+ import { isImage, formatBytes } from './utils.mjs';
6
+ import { usePluginOverrides } from '@btst/stack/context';
7
+ import { AssetPreviewButton } from './asset-preview-button.mjs';
8
+ import { toast } from 'sonner';
9
+
10
+ function AssetCard({
11
+ asset,
12
+ onToggle,
13
+ selected = false,
14
+ onDelete,
15
+ apiBaseURL
16
+ }) {
17
+ const { mutateAsync: deleteAsset } = useDeleteAsset();
18
+ const { Image: ImageComponent } = usePluginOverrides("media", {});
19
+ const imageAsset = isImage(asset.mimeType);
20
+ const selectable = typeof onToggle === "function";
21
+ const copyUrl = () => {
22
+ let fullUrl;
23
+ try {
24
+ fullUrl = new URL(asset.url, apiBaseURL).href;
25
+ } catch {
26
+ fullUrl = asset.url;
27
+ }
28
+ navigator.clipboard.writeText(fullUrl).then(() => toast.success("URL copied"));
29
+ };
30
+ const handleDelete = () => {
31
+ if (onDelete) {
32
+ return onDelete(asset.id);
33
+ }
34
+ if (confirm(`Delete "${asset.originalName}"?`)) {
35
+ return deleteAsset(asset.id).catch(console.error);
36
+ }
37
+ };
38
+ return /* @__PURE__ */ jsxs(
39
+ "div",
40
+ {
41
+ role: selectable ? "button" : void 0,
42
+ tabIndex: selectable ? 0 : void 0,
43
+ "data-testid": "media-asset-item",
44
+ onClick: onToggle,
45
+ onKeyDown: (e) => {
46
+ if (selectable && (e.key === "Enter" || e.key === " ")) {
47
+ e.preventDefault();
48
+ onToggle();
49
+ }
50
+ },
51
+ className: cn(
52
+ "group relative cursor-pointer rounded-md border bg-muted/30 p-1 transition-all hover:border-ring hover:shadow-sm",
53
+ !selectable && "cursor-default",
54
+ selected && "border-ring ring-1 ring-ring"
55
+ ),
56
+ children: [
57
+ /* @__PURE__ */ jsx("div", { className: "flex h-28 items-center justify-center overflow-hidden rounded bg-muted", children: imageAsset ? ImageComponent ? /* @__PURE__ */ jsx(
58
+ ImageComponent,
59
+ {
60
+ src: asset.url,
61
+ alt: asset.alt || asset.originalName,
62
+ className: "h-full w-full object-cover",
63
+ width: 160,
64
+ height: 80
65
+ }
66
+ ) : /* @__PURE__ */ jsx(
67
+ "img",
68
+ {
69
+ src: asset.url,
70
+ alt: asset.alt || asset.originalName,
71
+ className: "h-full w-full object-cover",
72
+ loading: "lazy"
73
+ }
74
+ ) : /* @__PURE__ */ jsx(File, { className: "size-8 text-muted-foreground" }) }),
75
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 px-0.5", children: [
76
+ /* @__PURE__ */ jsx(
77
+ "p",
78
+ {
79
+ className: "truncate text-xs font-medium leading-tight",
80
+ title: asset.originalName,
81
+ children: asset.originalName
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: formatBytes(asset.size) })
85
+ ] }),
86
+ selected && /* @__PURE__ */ jsx("div", { className: "absolute right-1 top-1 rounded-full bg-primary p-0.5 text-primary-foreground", children: /* @__PURE__ */ jsx(Check, { className: "size-3" }) }),
87
+ /* @__PURE__ */ jsxs("div", { className: "absolute right-1 top-1 hidden gap-1 group-hover:flex", children: [
88
+ apiBaseURL ? /* @__PURE__ */ jsx(
89
+ "button",
90
+ {
91
+ type: "button",
92
+ title: "Copy URL",
93
+ onClick: (e) => {
94
+ e.stopPropagation();
95
+ copyUrl();
96
+ },
97
+ className: "rounded bg-background/80 p-0.5 shadow hover:bg-background",
98
+ children: /* @__PURE__ */ jsx(Copy, { className: "size-3" })
99
+ }
100
+ ) : null,
101
+ imageAsset ? /* @__PURE__ */ jsx(
102
+ AssetPreviewButton,
103
+ {
104
+ asset,
105
+ className: "rounded bg-background/80 p-0.5 shadow hover:bg-background"
106
+ }
107
+ ) : null,
108
+ /* @__PURE__ */ jsx(
109
+ "button",
110
+ {
111
+ type: "button",
112
+ title: "Delete",
113
+ onClick: (e) => {
114
+ e.stopPropagation();
115
+ void handleDelete();
116
+ },
117
+ className: "rounded bg-destructive/80 p-0.5 text-white hover:bg-destructive",
118
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-3" })
119
+ }
120
+ )
121
+ ] })
122
+ ]
123
+ }
124
+ );
125
+ }
126
+
127
+ export { AssetCard };
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ const jsxRuntime = require('react/jsx-runtime');
5
+ const React = require('react');
6
+ const dialog = require('../../../../../../../ui/src/components/dialog.cjs');
7
+ const LucideIcons = require('lucide-react');
8
+
9
+ function AssetPreviewButton({
10
+ asset,
11
+ className
12
+ }) {
13
+ const [open, setOpen] = React.useState(false);
14
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
15
+ /* @__PURE__ */ jsxRuntime.jsx(
16
+ "button",
17
+ {
18
+ type: "button",
19
+ title: "Preview",
20
+ "aria-label": `Preview ${asset.originalName}`,
21
+ onClick: (event) => {
22
+ event.stopPropagation();
23
+ setOpen(true);
24
+ },
25
+ className,
26
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Eye, { className: "size-3" })
27
+ }
28
+ ),
29
+ /* @__PURE__ */ jsxRuntime.jsx(dialog.Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(
30
+ dialog.DialogContent,
31
+ {
32
+ showCloseButton: false,
33
+ className: "h-screen w-screen max-w-none border-0 bg-black/95 p-4 shadow-none sm:max-w-none sm:rounded-none sm:p-6",
34
+ children: [
35
+ /* @__PURE__ */ jsxRuntime.jsx(dialog.DialogHeader, { className: "sr-only", children: /* @__PURE__ */ jsxRuntime.jsx(dialog.DialogTitle, { children: asset.alt || asset.originalName }) }),
36
+ /* @__PURE__ */ jsxRuntime.jsx(
37
+ dialog.DialogClose,
38
+ {
39
+ className: "absolute right-4 top-4 z-10 rounded bg-black/60 p-2 text-white transition hover:bg-black/80",
40
+ "aria-label": "Close preview",
41
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.X, { className: "size-4" })
42
+ }
43
+ ),
44
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-h-full w-full items-start justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(
45
+ "img",
46
+ {
47
+ src: asset.url,
48
+ alt: asset.alt || asset.originalName,
49
+ className: "block h-auto w-auto max-w-none"
50
+ }
51
+ ) }) })
52
+ ]
53
+ }
54
+ ) })
55
+ ] });
56
+ }
57
+
58
+ exports.AssetPreviewButton = AssetPreviewButton;
@@ -0,0 +1,56 @@
1
+ "use client";
2
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
+ import { useState } from 'react';
4
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogClose } from '../../../../../../../ui/src/components/dialog.mjs';
5
+ import { Eye, X } from 'lucide-react';
6
+
7
+ function AssetPreviewButton({
8
+ asset,
9
+ className
10
+ }) {
11
+ const [open, setOpen] = useState(false);
12
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
13
+ /* @__PURE__ */ jsx(
14
+ "button",
15
+ {
16
+ type: "button",
17
+ title: "Preview",
18
+ "aria-label": `Preview ${asset.originalName}`,
19
+ onClick: (event) => {
20
+ event.stopPropagation();
21
+ setOpen(true);
22
+ },
23
+ className,
24
+ children: /* @__PURE__ */ jsx(Eye, { className: "size-3" })
25
+ }
26
+ ),
27
+ /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(
28
+ DialogContent,
29
+ {
30
+ showCloseButton: false,
31
+ className: "h-screen w-screen max-w-none border-0 bg-black/95 p-4 shadow-none sm:max-w-none sm:rounded-none sm:p-6",
32
+ children: [
33
+ /* @__PURE__ */ jsx(DialogHeader, { className: "sr-only", children: /* @__PURE__ */ jsx(DialogTitle, { children: asset.alt || asset.originalName }) }),
34
+ /* @__PURE__ */ jsx(
35
+ DialogClose,
36
+ {
37
+ className: "absolute right-4 top-4 z-10 rounded bg-black/60 p-2 text-white transition hover:bg-black/80",
38
+ "aria-label": "Close preview",
39
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
40
+ }
41
+ ),
42
+ /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-auto", children: /* @__PURE__ */ jsx("div", { className: "flex min-h-full w-full items-start justify-center", children: /* @__PURE__ */ jsx(
43
+ "img",
44
+ {
45
+ src: asset.url,
46
+ alt: asset.alt || asset.originalName,
47
+ className: "block h-auto w-auto max-w-none"
48
+ }
49
+ ) }) })
50
+ ]
51
+ }
52
+ ) })
53
+ ] });
54
+ }
55
+
56
+ export { AssetPreviewButton };
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ const jsxRuntime = require('react/jsx-runtime');
4
+ const React = require('react');
5
+ const useMedia = require('../../hooks/use-media.cjs');
6
+ const input = require('../../../../../../../ui/src/components/input.cjs');
7
+ const button = require('../../../../../../../ui/src/components/button.cjs');
8
+ const LucideIcons = require('lucide-react');
9
+ const assetCard = require('./asset-card.cjs');
10
+ const utils = require('./utils.cjs');
11
+
12
+ function BrowseTab({
13
+ folderId,
14
+ selected = [],
15
+ accept,
16
+ onToggle,
17
+ onDelete,
18
+ apiBaseURL,
19
+ emptyMessage = "No files found"
20
+ }) {
21
+ const [search, setSearch] = React.useState("");
22
+ const [debouncedSearch, setDebouncedSearch] = React.useState("");
23
+ const debounceRef = React.useRef(null);
24
+ const selectable = typeof onToggle === "function";
25
+ const handleSearch = (v) => {
26
+ setSearch(v);
27
+ if (debounceRef.current) clearTimeout(debounceRef.current);
28
+ debounceRef.current = setTimeout(() => setDebouncedSearch(v), 300);
29
+ };
30
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useMedia.useAssets({
31
+ folderId: folderId ?? void 0,
32
+ query: debouncedSearch || void 0,
33
+ limit: 40
34
+ });
35
+ const allAssets = data?.pages.flatMap((p) => p.items) ?? [];
36
+ const filtered = accept ? allAssets.filter((a) => utils.matchesAccept(a.mimeType, accept)) : allAssets;
37
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 flex-col gap-2", children: [
38
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
39
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Search, { className: "absolute left-2 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }),
40
+ /* @__PURE__ */ jsxRuntime.jsx(
41
+ input.Input,
42
+ {
43
+ value: search,
44
+ onChange: (e) => handleSearch(e.target.value),
45
+ placeholder: "Search files\u2026",
46
+ className: "h-8 pl-7 text-sm"
47
+ }
48
+ ),
49
+ search && /* @__PURE__ */ jsxRuntime.jsx(
50
+ "button",
51
+ {
52
+ type: "button",
53
+ onClick: () => {
54
+ setSearch("");
55
+ setDebouncedSearch("");
56
+ },
57
+ className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
58
+ children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.X, { className: "size-3.5" })
59
+ }
60
+ )
61
+ ] }),
62
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "size-6 animate-spin text-muted-foreground" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-2 text-sm text-muted-foreground", children: [
63
+ /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Image, { className: "size-8" }),
64
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: emptyMessage })
65
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto overscroll-contain", children: [
66
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2 pb-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5", children: filtered.map((asset) => /* @__PURE__ */ jsxRuntime.jsx(
67
+ assetCard.AssetCard,
68
+ {
69
+ asset,
70
+ selected: selected.some((s) => s.id === asset.id),
71
+ onToggle: selectable ? () => onToggle(asset) : void 0,
72
+ onDelete,
73
+ apiBaseURL
74
+ },
75
+ asset.id
76
+ )) }),
77
+ hasNextPage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
78
+ button.Button,
79
+ {
80
+ variant: "ghost",
81
+ size: "sm",
82
+ onClick: () => fetchNextPage(),
83
+ disabled: isFetchingNextPage,
84
+ children: [
85
+ isFetchingNextPage ? /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "mr-1 size-3 animate-spin" }) : null,
86
+ "Load more"
87
+ ]
88
+ }
89
+ ) })
90
+ ] })
91
+ ] });
92
+ }
93
+
94
+ exports.BrowseTab = BrowseTab;
@@ -0,0 +1,92 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState, useRef } from 'react';
3
+ import { useAssets } from '../../hooks/use-media.mjs';
4
+ import { Input } from '../../../../../../../ui/src/components/input.mjs';
5
+ import { Button } from '../../../../../../../ui/src/components/button.mjs';
6
+ import { Search, X, Loader2, Image } from 'lucide-react';
7
+ import { AssetCard } from './asset-card.mjs';
8
+ import { matchesAccept } from './utils.mjs';
9
+
10
+ function BrowseTab({
11
+ folderId,
12
+ selected = [],
13
+ accept,
14
+ onToggle,
15
+ onDelete,
16
+ apiBaseURL,
17
+ emptyMessage = "No files found"
18
+ }) {
19
+ const [search, setSearch] = useState("");
20
+ const [debouncedSearch, setDebouncedSearch] = useState("");
21
+ const debounceRef = useRef(null);
22
+ const selectable = typeof onToggle === "function";
23
+ const handleSearch = (v) => {
24
+ setSearch(v);
25
+ if (debounceRef.current) clearTimeout(debounceRef.current);
26
+ debounceRef.current = setTimeout(() => setDebouncedSearch(v), 300);
27
+ };
28
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useAssets({
29
+ folderId: folderId ?? void 0,
30
+ query: debouncedSearch || void 0,
31
+ limit: 40
32
+ });
33
+ const allAssets = data?.pages.flatMap((p) => p.items) ?? [];
34
+ const filtered = accept ? allAssets.filter((a) => matchesAccept(a.mimeType, accept)) : allAssets;
35
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col gap-2", children: [
36
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
37
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }),
38
+ /* @__PURE__ */ jsx(
39
+ Input,
40
+ {
41
+ value: search,
42
+ onChange: (e) => handleSearch(e.target.value),
43
+ placeholder: "Search files\u2026",
44
+ className: "h-8 pl-7 text-sm"
45
+ }
46
+ ),
47
+ search && /* @__PURE__ */ jsx(
48
+ "button",
49
+ {
50
+ type: "button",
51
+ onClick: () => {
52
+ setSearch("");
53
+ setDebouncedSearch("");
54
+ },
55
+ className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground",
56
+ children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
57
+ }
58
+ )
59
+ ] }),
60
+ isLoading ? /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsx(Loader2, { className: "size-6 animate-spin text-muted-foreground" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-2 text-sm text-muted-foreground", children: [
61
+ /* @__PURE__ */ jsx(Image, { className: "size-8" }),
62
+ /* @__PURE__ */ jsx("p", { children: emptyMessage })
63
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto overscroll-contain", children: [
64
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-2 pb-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5", children: filtered.map((asset) => /* @__PURE__ */ jsx(
65
+ AssetCard,
66
+ {
67
+ asset,
68
+ selected: selected.some((s) => s.id === asset.id),
69
+ onToggle: selectable ? () => onToggle(asset) : void 0,
70
+ onDelete,
71
+ apiBaseURL
72
+ },
73
+ asset.id
74
+ )) }),
75
+ hasNextPage && /* @__PURE__ */ jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsxs(
76
+ Button,
77
+ {
78
+ variant: "ghost",
79
+ size: "sm",
80
+ onClick: () => fetchNextPage(),
81
+ disabled: isFetchingNextPage,
82
+ children: [
83
+ isFetchingNextPage ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1 size-3 animate-spin" }) : null,
84
+ "Load more"
85
+ ]
86
+ }
87
+ ) })
88
+ ] })
89
+ ] });
90
+ }
91
+
92
+ export { BrowseTab };