@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
@@ -14,25 +14,36 @@ import { CMS_LOCALIZATION } from '../../localization/index.mjs';
14
14
  import { CMSFileUpload } from './file-upload.mjs';
15
15
  import { RelationField } from './relation-field.mjs';
16
16
 
17
- function buildFieldConfigFromJsonSchema(jsonSchema, uploadImage, fieldComponents) {
17
+ function buildFieldConfigFromJsonSchema(jsonSchema, uploadImage, fieldComponents, imagePicker, imageInputField) {
18
18
  const baseConfig = buildFieldConfigFromJsonSchema$1(jsonSchema, fieldComponents);
19
19
  const properties = jsonSchema.properties;
20
20
  if (!properties) return baseConfig;
21
21
  for (const [key, prop] of Object.entries(properties)) {
22
22
  if (prop.fieldType === "file" && !fieldComponents?.["file"]) {
23
- if (!uploadImage) {
23
+ if (!uploadImage && !imageInputField) {
24
24
  baseConfig[key] = {
25
25
  ...baseConfig[key],
26
26
  fieldType: () => /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-destructive bg-destructive/10 p-3 text-sm text-destructive", children: [
27
27
  "File upload requires an ",
28
28
  /* @__PURE__ */ jsx("code", { children: "uploadImage" }),
29
+ " or",
30
+ " ",
31
+ /* @__PURE__ */ jsx("code", { children: "imageInputField" }),
29
32
  " function in CMS overrides."
30
33
  ] })
31
34
  };
32
35
  } else {
33
36
  baseConfig[key] = {
34
37
  ...baseConfig[key],
35
- fieldType: (props) => /* @__PURE__ */ jsx(CMSFileUpload, { ...props, uploadImage })
38
+ fieldType: (props) => /* @__PURE__ */ jsx(
39
+ CMSFileUpload,
40
+ {
41
+ ...props,
42
+ uploadImage: uploadImage ?? (() => Promise.resolve("")),
43
+ imageInputField,
44
+ imagePicker
45
+ }
46
+ )
36
47
  };
37
48
  }
38
49
  }
@@ -73,6 +84,8 @@ function ContentForm({
73
84
  const {
74
85
  localization: customLocalization,
75
86
  uploadImage,
87
+ imagePicker,
88
+ imageInputField,
76
89
  fieldComponents
77
90
  } = usePluginOverrides("cms");
78
91
  const localization = { ...CMS_LOCALIZATION, ...customLocalization };
@@ -113,8 +126,14 @@ function ContentForm({
113
126
  }
114
127
  }, [jsonSchema]);
115
128
  const fieldConfig = useMemo(
116
- () => buildFieldConfigFromJsonSchema(jsonSchema, uploadImage, fieldComponents),
117
- [jsonSchema, uploadImage, fieldComponents]
129
+ () => buildFieldConfigFromJsonSchema(
130
+ jsonSchema,
131
+ uploadImage,
132
+ fieldComponents,
133
+ imagePicker,
134
+ imageInputField
135
+ ),
136
+ [jsonSchema, uploadImage, fieldComponents, imagePicker, imageInputField]
118
137
  );
119
138
  const slugSourceField = useMemo(
120
139
  () => findSlugSourceField(jsonSchema),
@@ -17,7 +17,9 @@ function CMSFileUpload({
17
17
  fieldConfigItem,
18
18
  fieldProps,
19
19
  field,
20
- uploadImage
20
+ uploadImage,
21
+ imageInputField: ImageInputField,
22
+ imagePicker: ImagePickerTrigger
21
23
  }) {
22
24
  const {
23
25
  showLabel: _showLabel,
@@ -61,6 +63,27 @@ function CMSFileUpload({
61
63
  setPreviewUrl(null);
62
64
  field.onChange("");
63
65
  }, [field]);
66
+ if (ImageInputField) {
67
+ return /* @__PURE__ */ jsxRuntime.jsxs(form.FormItem, { children: [
68
+ showLabel && /* @__PURE__ */ jsxRuntime.jsx(
69
+ label,
70
+ {
71
+ label: fieldConfigItem?.label || label$1,
72
+ isRequired
73
+ }
74
+ ),
75
+ /* @__PURE__ */ jsxRuntime.jsx(form.FormControl, { children: /* @__PURE__ */ jsxRuntime.jsx(
76
+ ImageInputField,
77
+ {
78
+ value: field.value || "",
79
+ onChange: field.onChange,
80
+ isRequired
81
+ }
82
+ ) }),
83
+ /* @__PURE__ */ jsxRuntime.jsx(tooltip, { fieldConfigItem }),
84
+ /* @__PURE__ */ jsxRuntime.jsx(form.FormMessage, {})
85
+ ] });
86
+ }
64
87
  return /* @__PURE__ */ jsxRuntime.jsxs(form.FormItem, { children: [
65
88
  showLabel && /* @__PURE__ */ jsxRuntime.jsx(
66
89
  label,
@@ -69,20 +92,31 @@ function CMSFileUpload({
69
92
  isRequired
70
93
  }
71
94
  ),
72
- !previewUrl && /* @__PURE__ */ jsxRuntime.jsx(form.FormControl, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
73
- /* @__PURE__ */ jsxRuntime.jsx(
74
- input.Input,
95
+ !previewUrl && /* @__PURE__ */ jsxRuntime.jsx(form.FormControl, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
96
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
97
+ /* @__PURE__ */ jsxRuntime.jsx(
98
+ input.Input,
99
+ {
100
+ type: "file",
101
+ accept: "image/*",
102
+ ...safeFieldProps,
103
+ onChange: handleFileChange,
104
+ disabled: isUploading,
105
+ className: "cursor-pointer",
106
+ "data-testid": "image-upload-input"
107
+ }
108
+ ),
109
+ isUploading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/80", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "h-4 w-4 animate-spin" }) })
110
+ ] }),
111
+ ImagePickerTrigger && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "image-picker-trigger", children: /* @__PURE__ */ jsxRuntime.jsx(
112
+ ImagePickerTrigger,
75
113
  {
76
- type: "file",
77
- accept: "image/*",
78
- ...safeFieldProps,
79
- onChange: handleFileChange,
80
- disabled: isUploading,
81
- className: "cursor-pointer",
82
- "data-testid": "image-upload-input"
114
+ onSelect: (url) => {
115
+ setPreviewUrl(url);
116
+ field.onChange(url);
117
+ }
83
118
  }
84
- ),
85
- isUploading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/80", children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Loader2, { className: "h-4 w-4 animate-spin" }) })
119
+ ) })
86
120
  ] }) }),
87
121
  previewUrl && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
88
122
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative h-20 w-20 overflow-hidden rounded-md border", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -15,7 +15,9 @@ function CMSFileUpload({
15
15
  fieldConfigItem,
16
16
  fieldProps,
17
17
  field,
18
- uploadImage
18
+ uploadImage,
19
+ imageInputField: ImageInputField,
20
+ imagePicker: ImagePickerTrigger
19
21
  }) {
20
22
  const {
21
23
  showLabel: _showLabel,
@@ -59,6 +61,27 @@ function CMSFileUpload({
59
61
  setPreviewUrl(null);
60
62
  field.onChange("");
61
63
  }, [field]);
64
+ if (ImageInputField) {
65
+ return /* @__PURE__ */ jsxs(FormItem, { children: [
66
+ showLabel && /* @__PURE__ */ jsx(
67
+ AutoFormLabel,
68
+ {
69
+ label: fieldConfigItem?.label || label,
70
+ isRequired
71
+ }
72
+ ),
73
+ /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(
74
+ ImageInputField,
75
+ {
76
+ value: field.value || "",
77
+ onChange: field.onChange,
78
+ isRequired
79
+ }
80
+ ) }),
81
+ /* @__PURE__ */ jsx(AutoFormTooltip, { fieldConfigItem }),
82
+ /* @__PURE__ */ jsx(FormMessage, {})
83
+ ] });
84
+ }
62
85
  return /* @__PURE__ */ jsxs(FormItem, { children: [
63
86
  showLabel && /* @__PURE__ */ jsx(
64
87
  AutoFormLabel,
@@ -67,20 +90,31 @@ function CMSFileUpload({
67
90
  isRequired
68
91
  }
69
92
  ),
70
- !previewUrl && /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
71
- /* @__PURE__ */ jsx(
72
- Input,
93
+ !previewUrl && /* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
94
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
95
+ /* @__PURE__ */ jsx(
96
+ Input,
97
+ {
98
+ type: "file",
99
+ accept: "image/*",
100
+ ...safeFieldProps,
101
+ onChange: handleFileChange,
102
+ disabled: isUploading,
103
+ className: "cursor-pointer",
104
+ "data-testid": "image-upload-input"
105
+ }
106
+ ),
107
+ isUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/80", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) })
108
+ ] }),
109
+ ImagePickerTrigger && /* @__PURE__ */ jsx("div", { "data-testid": "image-picker-trigger", children: /* @__PURE__ */ jsx(
110
+ ImagePickerTrigger,
73
111
  {
74
- type: "file",
75
- accept: "image/*",
76
- ...safeFieldProps,
77
- onChange: handleFileChange,
78
- disabled: isUploading,
79
- className: "cursor-pointer",
80
- "data-testid": "image-upload-input"
112
+ onSelect: (url) => {
113
+ setPreviewUrl(url);
114
+ field.onChange(url);
115
+ }
81
116
  }
82
- ),
83
- isUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/80", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) })
117
+ ) })
84
118
  ] }) }),
85
119
  previewUrl && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
86
120
  /* @__PURE__ */ jsx("div", { className: "relative h-20 w-20 overflow-hidden rounded-md border", children: /* @__PURE__ */ jsx(
@@ -37,7 +37,7 @@ function BoardForm({ board, onClose, onSuccess }) {
37
37
  setError(err instanceof Error ? err.message : "An error occurred");
38
38
  }
39
39
  };
40
- return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
40
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-4 overflow-x-hidden", children: [
41
41
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
42
42
  /* @__PURE__ */ jsxRuntime.jsx(label.Label, { htmlFor: "name", children: "Name *" }),
43
43
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -35,7 +35,7 @@ function BoardForm({ board, onClose, onSuccess }) {
35
35
  setError(err instanceof Error ? err.message : "An error occurred");
36
36
  }
37
37
  };
38
- return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
38
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4 overflow-x-hidden", children: [
39
39
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
40
40
  /* @__PURE__ */ jsx(Label, { htmlFor: "name", children: "Name *" }),
41
41
  /* @__PURE__ */ jsx(
@@ -10,6 +10,7 @@ const label = require('../../../../../../../ui/src/components/label.cjs');
10
10
  const select = require('../../../../../../../ui/src/components/select.cjs');
11
11
  const minimalTiptap = require('../../../../../../../ui/src/components/minimal-tiptap/minimal-tiptap.cjs');
12
12
  const searchSelect = require('../../../../../../../ui/src/components/search-select.cjs');
13
+ const context = require('@btst/stack/context');
13
14
  const kanbanHooks = require('../../hooks/kanban-hooks.cjs');
14
15
  const utils = require('../../../utils.cjs');
15
16
 
@@ -24,6 +25,7 @@ function TaskForm({
24
25
  onDelete
25
26
  }) {
26
27
  const isEditing = !!taskId;
28
+ const { uploadImage, imagePicker: imagePickerTrigger } = context.usePluginOverrides("kanban");
27
29
  const {
28
30
  createTask,
29
31
  updateTask,
@@ -101,7 +103,7 @@ function TaskForm({
101
103
  setError(err instanceof Error ? err.message : "An error occurred");
102
104
  }
103
105
  };
104
- return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
106
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "space-y-4 overflow-x-hidden", children: [
105
107
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
106
108
  /* @__PURE__ */ jsxRuntime.jsx(label.Label, { htmlFor: "title", children: "Title *" }),
107
109
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -160,7 +162,9 @@ function TaskForm({
160
162
  onChange: (value) => setDescription(typeof value === "string" ? value : ""),
161
163
  output: "markdown",
162
164
  placeholder: "Describe the task...",
163
- className: "min-h-[150px]"
165
+ className: "min-h-[150px]",
166
+ uploader: uploadImage,
167
+ imagePickerTrigger
164
168
  }
165
169
  )
166
170
  ] }),
@@ -8,6 +8,7 @@ import { Label } from '../../../../../../../ui/src/components/label.mjs';
8
8
  import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../../../../../../ui/src/components/select.mjs';
9
9
  import { MinimalTiptapEditor } from '../../../../../../../ui/src/components/minimal-tiptap/minimal-tiptap.mjs';
10
10
  import SearchSelect from '../../../../../../../ui/src/components/search-select.mjs';
11
+ import { usePluginOverrides } from '@btst/stack/context';
11
12
  import { useTaskMutations, useSearchUsers } from '../../hooks/kanban-hooks.mjs';
12
13
  import { PRIORITY_OPTIONS } from '../../../utils.mjs';
13
14
 
@@ -22,6 +23,7 @@ function TaskForm({
22
23
  onDelete
23
24
  }) {
24
25
  const isEditing = !!taskId;
26
+ const { uploadImage, imagePicker: imagePickerTrigger } = usePluginOverrides("kanban");
25
27
  const {
26
28
  createTask,
27
29
  updateTask,
@@ -99,7 +101,7 @@ function TaskForm({
99
101
  setError(err instanceof Error ? err.message : "An error occurred");
100
102
  }
101
103
  };
102
- return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
104
+ return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4 overflow-x-hidden", children: [
103
105
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
104
106
  /* @__PURE__ */ jsx(Label, { htmlFor: "title", children: "Title *" }),
105
107
  /* @__PURE__ */ jsx(
@@ -158,7 +160,9 @@ function TaskForm({
158
160
  onChange: (value) => setDescription(typeof value === "string" ? value : ""),
159
161
  output: "markdown",
160
162
  placeholder: "Describe the task...",
161
- className: "min-h-[150px]"
163
+ className: "min-h-[150px]",
164
+ uploader: uploadImage,
165
+ imagePickerTrigger
162
166
  }
163
167
  )
164
168
  ] }),
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const crypto = require('node:crypto');
6
+
7
+ function _interopNamespaceCompat(e) {
8
+ if (e && typeof e === 'object' && 'default' in e) return e;
9
+ const n = Object.create(null);
10
+ if (e) {
11
+ for (const k in e) {
12
+ n[k] = e[k];
13
+ }
14
+ }
15
+ n.default = e;
16
+ return n;
17
+ }
18
+
19
+ const fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
20
+ const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
21
+ const crypto__namespace = /*#__PURE__*/_interopNamespaceCompat(crypto);
22
+
23
+ function localAdapter(options = {}) {
24
+ const uploadDir = options.uploadDir ?? "./public/uploads";
25
+ const publicPath = options.publicPath ?? "/uploads";
26
+ return {
27
+ type: "local",
28
+ async upload(buffer, { filename }) {
29
+ await fs__namespace.mkdir(uploadDir, { recursive: true });
30
+ const ext = path__namespace.extname(filename);
31
+ const base = path__namespace.basename(filename, ext);
32
+ const unique = crypto__namespace.randomBytes(8).toString("hex");
33
+ const storedFilename = `${base}-${unique}${ext}`;
34
+ const filePath = path__namespace.join(uploadDir, storedFilename);
35
+ await fs__namespace.writeFile(filePath, buffer);
36
+ const url = `${publicPath.replace(/\/$/, "")}/${encodeURIComponent(storedFilename)}`;
37
+ return { url };
38
+ },
39
+ async delete(url) {
40
+ const encodedFilename = url.split("/").pop();
41
+ if (!encodedFilename) return;
42
+ const filename = decodeURIComponent(encodedFilename);
43
+ const filePath = path__namespace.join(uploadDir, filename);
44
+ try {
45
+ await fs__namespace.unlink(filePath);
46
+ } catch (err) {
47
+ if (err.code !== "ENOENT") {
48
+ throw err;
49
+ }
50
+ }
51
+ }
52
+ };
53
+ }
54
+
55
+ exports.localAdapter = localAdapter;
@@ -0,0 +1,37 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import * as crypto from 'node:crypto';
4
+
5
+ function localAdapter(options = {}) {
6
+ const uploadDir = options.uploadDir ?? "./public/uploads";
7
+ const publicPath = options.publicPath ?? "/uploads";
8
+ return {
9
+ type: "local",
10
+ async upload(buffer, { filename }) {
11
+ await fs.mkdir(uploadDir, { recursive: true });
12
+ const ext = path.extname(filename);
13
+ const base = path.basename(filename, ext);
14
+ const unique = crypto.randomBytes(8).toString("hex");
15
+ const storedFilename = `${base}-${unique}${ext}`;
16
+ const filePath = path.join(uploadDir, storedFilename);
17
+ await fs.writeFile(filePath, buffer);
18
+ const url = `${publicPath.replace(/\/$/, "")}/${encodeURIComponent(storedFilename)}`;
19
+ return { url };
20
+ },
21
+ async delete(url) {
22
+ const encodedFilename = url.split("/").pop();
23
+ if (!encodedFilename) return;
24
+ const filename = decodeURIComponent(encodedFilename);
25
+ const filePath = path.join(uploadDir, filename);
26
+ try {
27
+ await fs.unlink(filePath);
28
+ } catch (err) {
29
+ if (err.code !== "ENOENT") {
30
+ throw err;
31
+ }
32
+ }
33
+ }
34
+ };
35
+ }
36
+
37
+ export { localAdapter };
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ async function listAssets(adapter, params) {
4
+ const query = params ?? {};
5
+ const whereConditions = [];
6
+ if (query.folderId !== void 0) {
7
+ whereConditions.push({
8
+ field: "folderId",
9
+ value: query.folderId,
10
+ operator: "eq"
11
+ });
12
+ }
13
+ if (query.mimeType) {
14
+ whereConditions.push({
15
+ field: "mimeType",
16
+ value: query.mimeType,
17
+ operator: "eq"
18
+ });
19
+ }
20
+ const needsInMemoryFilter = !!query.query;
21
+ const dbWhere = whereConditions.length > 0 ? whereConditions : void 0;
22
+ const dbTotal = !needsInMemoryFilter ? await adapter.count({ model: "mediaAsset", where: dbWhere }) : void 0;
23
+ let assets = await adapter.findMany({
24
+ model: "mediaAsset",
25
+ limit: !needsInMemoryFilter ? query.limit : void 0,
26
+ offset: !needsInMemoryFilter ? query.offset : void 0,
27
+ where: dbWhere,
28
+ sortBy: { field: "createdAt", direction: "desc" }
29
+ });
30
+ if (query.query) {
31
+ const searchLower = query.query.toLowerCase();
32
+ assets = assets.filter(
33
+ (asset) => asset.filename.toLowerCase().includes(searchLower) || asset.originalName.toLowerCase().includes(searchLower) || asset.alt?.toLowerCase().includes(searchLower)
34
+ );
35
+ }
36
+ if (needsInMemoryFilter) {
37
+ const total = assets.length;
38
+ const offset = query.offset ?? 0;
39
+ const limit = query.limit;
40
+ assets = assets.slice(
41
+ offset,
42
+ limit !== void 0 ? offset + limit : void 0
43
+ );
44
+ return { items: assets, total, limit: query.limit, offset: query.offset };
45
+ }
46
+ return {
47
+ items: assets,
48
+ total: dbTotal ?? assets.length,
49
+ limit: query.limit,
50
+ offset: query.offset
51
+ };
52
+ }
53
+ async function getAssetById(adapter, id) {
54
+ return adapter.findOne({
55
+ model: "mediaAsset",
56
+ where: [{ field: "id", value: id, operator: "eq" }]
57
+ });
58
+ }
59
+ async function listFolders(adapter, params) {
60
+ const where = params?.parentId !== void 0 ? [
61
+ {
62
+ field: "parentId",
63
+ value: params.parentId,
64
+ operator: "eq"
65
+ }
66
+ ] : void 0;
67
+ return adapter.findMany({
68
+ model: "mediaFolder",
69
+ where,
70
+ sortBy: { field: "name", direction: "asc" }
71
+ });
72
+ }
73
+ async function getFolderById(adapter, id) {
74
+ return adapter.findOne({
75
+ model: "mediaFolder",
76
+ where: [{ field: "id", value: id, operator: "eq" }]
77
+ });
78
+ }
79
+
80
+ exports.getAssetById = getAssetById;
81
+ exports.getFolderById = getFolderById;
82
+ exports.listAssets = listAssets;
83
+ exports.listFolders = listFolders;
@@ -0,0 +1,78 @@
1
+ async function listAssets(adapter, params) {
2
+ const query = params ?? {};
3
+ const whereConditions = [];
4
+ if (query.folderId !== void 0) {
5
+ whereConditions.push({
6
+ field: "folderId",
7
+ value: query.folderId,
8
+ operator: "eq"
9
+ });
10
+ }
11
+ if (query.mimeType) {
12
+ whereConditions.push({
13
+ field: "mimeType",
14
+ value: query.mimeType,
15
+ operator: "eq"
16
+ });
17
+ }
18
+ const needsInMemoryFilter = !!query.query;
19
+ const dbWhere = whereConditions.length > 0 ? whereConditions : void 0;
20
+ const dbTotal = !needsInMemoryFilter ? await adapter.count({ model: "mediaAsset", where: dbWhere }) : void 0;
21
+ let assets = await adapter.findMany({
22
+ model: "mediaAsset",
23
+ limit: !needsInMemoryFilter ? query.limit : void 0,
24
+ offset: !needsInMemoryFilter ? query.offset : void 0,
25
+ where: dbWhere,
26
+ sortBy: { field: "createdAt", direction: "desc" }
27
+ });
28
+ if (query.query) {
29
+ const searchLower = query.query.toLowerCase();
30
+ assets = assets.filter(
31
+ (asset) => asset.filename.toLowerCase().includes(searchLower) || asset.originalName.toLowerCase().includes(searchLower) || asset.alt?.toLowerCase().includes(searchLower)
32
+ );
33
+ }
34
+ if (needsInMemoryFilter) {
35
+ const total = assets.length;
36
+ const offset = query.offset ?? 0;
37
+ const limit = query.limit;
38
+ assets = assets.slice(
39
+ offset,
40
+ limit !== void 0 ? offset + limit : void 0
41
+ );
42
+ return { items: assets, total, limit: query.limit, offset: query.offset };
43
+ }
44
+ return {
45
+ items: assets,
46
+ total: dbTotal ?? assets.length,
47
+ limit: query.limit,
48
+ offset: query.offset
49
+ };
50
+ }
51
+ async function getAssetById(adapter, id) {
52
+ return adapter.findOne({
53
+ model: "mediaAsset",
54
+ where: [{ field: "id", value: id, operator: "eq" }]
55
+ });
56
+ }
57
+ async function listFolders(adapter, params) {
58
+ const where = params?.parentId !== void 0 ? [
59
+ {
60
+ field: "parentId",
61
+ value: params.parentId,
62
+ operator: "eq"
63
+ }
64
+ ] : void 0;
65
+ return adapter.findMany({
66
+ model: "mediaFolder",
67
+ where,
68
+ sortBy: { field: "name", direction: "asc" }
69
+ });
70
+ }
71
+ async function getFolderById(adapter, id) {
72
+ return adapter.findOne({
73
+ model: "mediaFolder",
74
+ where: [{ field: "id", value: id, operator: "eq" }]
75
+ });
76
+ }
77
+
78
+ export { getAssetById, getFolderById, listAssets, listFolders };
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ async function createAsset(adapter, input) {
4
+ return adapter.create({
5
+ model: "mediaAsset",
6
+ data: {
7
+ filename: input.filename,
8
+ originalName: input.originalName,
9
+ mimeType: input.mimeType,
10
+ size: input.size,
11
+ url: input.url,
12
+ folderId: input.folderId,
13
+ alt: input.alt,
14
+ createdAt: /* @__PURE__ */ new Date()
15
+ }
16
+ });
17
+ }
18
+ async function updateAsset(adapter, id, input) {
19
+ const update = {};
20
+ if (input.alt !== void 0) {
21
+ update.alt = input.alt;
22
+ }
23
+ if ("folderId" in input) {
24
+ update.folderId = input.folderId;
25
+ }
26
+ return adapter.update({
27
+ model: "mediaAsset",
28
+ where: [{ field: "id", value: id, operator: "eq" }],
29
+ update
30
+ });
31
+ }
32
+ async function deleteAsset(adapter, id) {
33
+ await adapter.delete({
34
+ model: "mediaAsset",
35
+ where: [{ field: "id", value: id, operator: "eq" }]
36
+ });
37
+ }
38
+ async function createFolder(adapter, input) {
39
+ return adapter.create({
40
+ model: "mediaFolder",
41
+ data: {
42
+ name: input.name,
43
+ parentId: input.parentId,
44
+ createdAt: /* @__PURE__ */ new Date()
45
+ }
46
+ });
47
+ }
48
+ async function deleteFolder(adapter, id) {
49
+ const allFolderIds = [id];
50
+ const queue = [id];
51
+ while (queue.length > 0) {
52
+ const parentId = queue.shift();
53
+ const children = await adapter.findMany({
54
+ model: "mediaFolder",
55
+ where: [{ field: "parentId", value: parentId, operator: "eq" }]
56
+ });
57
+ for (const child of children) {
58
+ allFolderIds.push(child.id);
59
+ queue.push(child.id);
60
+ }
61
+ }
62
+ let totalAssets = 0;
63
+ for (const folderId of allFolderIds) {
64
+ totalAssets += await adapter.count({
65
+ model: "mediaAsset",
66
+ where: [{ field: "folderId", value: folderId, operator: "eq" }]
67
+ });
68
+ }
69
+ if (totalAssets > 0) {
70
+ throw new Error(
71
+ `Cannot delete folder: it or one of its subfolders contains ${totalAssets} asset(s). Move or delete them first.`
72
+ );
73
+ }
74
+ await adapter.transaction(async (tx) => {
75
+ for (const folderId of [...allFolderIds].reverse()) {
76
+ await tx.delete({
77
+ model: "mediaFolder",
78
+ where: [{ field: "id", value: folderId, operator: "eq" }]
79
+ });
80
+ }
81
+ });
82
+ }
83
+
84
+ exports.createAsset = createAsset;
85
+ exports.createFolder = createFolder;
86
+ exports.deleteAsset = deleteAsset;
87
+ exports.deleteFolder = deleteFolder;
88
+ exports.updateAsset = updateAsset;