@btst/stack 2.8.1 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +3 -2
  2. package/dist/components/markdown/index.d.cts +15 -2
  3. package/dist/components/markdown/index.d.mts +15 -2
  4. package/dist/components/markdown/index.d.ts +15 -2
  5. package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.cjs +30 -1
  6. package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.mjs +30 -1
  7. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.cjs +49 -9
  8. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.mjs +50 -10
  9. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.cjs +77 -9
  10. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.mjs +77 -9
  11. package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.cjs +24 -5
  12. package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.mjs +24 -5
  13. package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.cjs +47 -13
  14. package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.mjs +47 -13
  15. package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.cjs +1 -1
  16. package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.mjs +1 -1
  17. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +6 -2
  18. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +6 -2
  19. package/dist/packages/stack/src/plugins/media/api/adapters/local.cjs +55 -0
  20. package/dist/packages/stack/src/plugins/media/api/adapters/local.mjs +37 -0
  21. package/dist/packages/stack/src/plugins/media/api/getters.cjs +83 -0
  22. package/dist/packages/stack/src/plugins/media/api/getters.mjs +78 -0
  23. package/dist/packages/stack/src/plugins/media/api/mutations.cjs +88 -0
  24. package/dist/packages/stack/src/plugins/media/api/mutations.mjs +82 -0
  25. package/dist/packages/stack/src/plugins/media/api/plugin.cjs +525 -0
  26. package/dist/packages/stack/src/plugins/media/api/plugin.mjs +523 -0
  27. package/dist/packages/stack/src/plugins/media/api/query-key-defs.cjs +19 -0
  28. package/dist/packages/stack/src/plugins/media/api/query-key-defs.mjs +16 -0
  29. package/dist/packages/stack/src/plugins/media/api/serializers.cjs +17 -0
  30. package/dist/packages/stack/src/plugins/media/api/serializers.mjs +14 -0
  31. package/dist/packages/stack/src/plugins/media/api/storage-adapter.cjs +15 -0
  32. package/dist/packages/stack/src/plugins/media/api/storage-adapter.mjs +11 -0
  33. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.cjs +129 -0
  34. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.mjs +127 -0
  35. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.cjs +58 -0
  36. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.mjs +56 -0
  37. package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.cjs +94 -0
  38. package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.mjs +92 -0
  39. package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.cjs +171 -0
  40. package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.mjs +168 -0
  41. package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.cjs +308 -0
  42. package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.mjs +305 -0
  43. package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.cjs +104 -0
  44. package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.mjs +102 -0
  45. package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.cjs +70 -0
  46. package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.mjs +68 -0
  47. package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.cjs +21 -0
  48. package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.mjs +17 -0
  49. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.cjs +35 -0
  50. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.cjs +125 -0
  51. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.mjs +123 -0
  52. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.mjs +33 -0
  53. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.cjs +222 -0
  54. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.mjs +214 -0
  55. package/dist/packages/stack/src/plugins/media/client/plugin.cjs +94 -0
  56. package/dist/packages/stack/src/plugins/media/client/plugin.mjs +92 -0
  57. package/dist/packages/stack/src/plugins/media/client/upload.cjs +121 -0
  58. package/dist/packages/stack/src/plugins/media/client/upload.mjs +119 -0
  59. package/dist/packages/stack/src/plugins/media/client/utils/image-compression.cjs +67 -0
  60. package/dist/packages/stack/src/plugins/media/client/utils/image-compression.mjs +65 -0
  61. package/dist/packages/stack/src/plugins/media/db.cjs +62 -0
  62. package/dist/packages/stack/src/plugins/media/db.mjs +60 -0
  63. package/dist/packages/stack/src/plugins/media/schemas.cjs +41 -0
  64. package/dist/packages/stack/src/plugins/media/schemas.mjs +35 -0
  65. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.cjs +18 -1
  66. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.mjs +19 -2
  67. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.cjs +2 -2
  68. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.mjs +2 -2
  69. package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.cjs +3 -2
  70. package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.mjs +3 -2
  71. package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.cjs +12 -5
  72. package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.mjs +12 -5
  73. package/dist/plugins/blog/client/index.d.cts +58 -1
  74. package/dist/plugins/blog/client/index.d.mts +58 -1
  75. package/dist/plugins/blog/client/index.d.ts +58 -1
  76. package/dist/plugins/cms/client/index.d.cts +73 -3
  77. package/dist/plugins/cms/client/index.d.mts +73 -3
  78. package/dist/plugins/cms/client/index.d.ts +73 -3
  79. package/dist/plugins/kanban/api/index.d.cts +1 -1
  80. package/dist/plugins/kanban/api/index.d.mts +1 -1
  81. package/dist/plugins/kanban/api/index.d.ts +1 -1
  82. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  83. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  84. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  85. package/dist/plugins/kanban/client/index.d.cts +1 -1
  86. package/dist/plugins/kanban/client/index.d.mts +1 -1
  87. package/dist/plugins/kanban/client/index.d.ts +1 -1
  88. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  89. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  90. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  91. package/dist/plugins/media/api/adapters/s3.cjs +106 -0
  92. package/dist/plugins/media/api/adapters/s3.d.cts +60 -0
  93. package/dist/plugins/media/api/adapters/s3.d.mts +60 -0
  94. package/dist/plugins/media/api/adapters/s3.d.ts +60 -0
  95. package/dist/plugins/media/api/adapters/s3.mjs +104 -0
  96. package/dist/plugins/media/api/adapters/vercel-blob.cjs +53 -0
  97. package/dist/plugins/media/api/adapters/vercel-blob.d.cts +41 -0
  98. package/dist/plugins/media/api/adapters/vercel-blob.d.mts +41 -0
  99. package/dist/plugins/media/api/adapters/vercel-blob.d.ts +41 -0
  100. package/dist/plugins/media/api/adapters/vercel-blob.mjs +51 -0
  101. package/dist/plugins/media/api/index.cjs +26 -0
  102. package/dist/plugins/media/api/index.d.cts +116 -0
  103. package/dist/plugins/media/api/index.d.mts +116 -0
  104. package/dist/plugins/media/api/index.d.ts +116 -0
  105. package/dist/plugins/media/api/index.mjs +6 -0
  106. package/dist/plugins/media/client/components/index.cjs +10 -0
  107. package/dist/plugins/media/client/components/index.d.cts +55 -0
  108. package/dist/plugins/media/client/components/index.d.mts +55 -0
  109. package/dist/plugins/media/client/components/index.d.ts +55 -0
  110. package/dist/plugins/media/client/components/index.mjs +2 -0
  111. package/dist/plugins/media/client/hooks/index.cjs +13 -0
  112. package/dist/plugins/media/client/hooks/index.d.cts +53 -0
  113. package/dist/plugins/media/client/hooks/index.d.mts +53 -0
  114. package/dist/plugins/media/client/hooks/index.d.ts +53 -0
  115. package/dist/plugins/media/client/hooks/index.mjs +1 -0
  116. package/dist/plugins/media/client/index.cjs +9 -0
  117. package/dist/plugins/media/client/index.d.cts +242 -0
  118. package/dist/plugins/media/client/index.d.mts +242 -0
  119. package/dist/plugins/media/client/index.d.ts +242 -0
  120. package/dist/plugins/media/client/index.mjs +2 -0
  121. package/dist/plugins/media/client.css +1 -0
  122. package/dist/plugins/media/query-keys.cjs +72 -0
  123. package/dist/plugins/media/query-keys.d.cts +49 -0
  124. package/dist/plugins/media/query-keys.d.mts +49 -0
  125. package/dist/plugins/media/query-keys.d.ts +49 -0
  126. package/dist/plugins/media/query-keys.mjs +70 -0
  127. package/dist/plugins/media/style.css +1 -0
  128. package/dist/shared/{stack.DRpeDS6X.d.ts → stack.BMx2QYOK.d.ts} +25 -0
  129. package/dist/shared/stack.BttDsJJn.d.cts +109 -0
  130. package/dist/shared/stack.BttDsJJn.d.mts +109 -0
  131. package/dist/shared/stack.BttDsJJn.d.ts +109 -0
  132. package/dist/shared/stack.C7vfOBmO.d.mts +63 -0
  133. package/dist/shared/stack.CAni8dnD.d.cts +63 -0
  134. package/dist/shared/stack.CI8iRKKi.d.cts +286 -0
  135. package/dist/shared/stack.CLcnSF_b.d.cts +25 -0
  136. package/dist/shared/stack.CLcnSF_b.d.mts +25 -0
  137. package/dist/shared/stack.CLcnSF_b.d.ts +25 -0
  138. package/dist/shared/stack.CYSwntXC.d.ts +63 -0
  139. package/dist/shared/{stack.Jb0kQDJC.d.mts → stack.Cd6McBu1.d.mts} +25 -0
  140. package/dist/shared/stack.DJDjdG64.d.ts +286 -0
  141. package/dist/shared/{stack.BxFl46lB.d.cts → stack.DxQl8Wa1.d.cts} +25 -0
  142. package/dist/shared/stack.FgBVDSPi.d.mts +286 -0
  143. package/package.json +113 -4
  144. package/src/plugins/blog/client/components/forms/image-field.tsx +35 -4
  145. package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +67 -12
  146. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +106 -10
  147. package/src/plugins/blog/client/overrides.ts +58 -1
  148. package/src/plugins/cms/client/components/forms/content-form.tsx +26 -7
  149. package/src/plugins/cms/client/components/forms/file-upload.tsx +73 -15
  150. package/src/plugins/cms/client/overrides.ts +57 -2
  151. package/src/plugins/kanban/client/components/forms/board-form.tsx +1 -1
  152. package/src/plugins/kanban/client/components/forms/task-form.tsx +7 -1
  153. package/src/plugins/kanban/client/overrides.ts +25 -0
  154. package/src/plugins/media/__tests__/__stubs__/vercel-blob-server.ts +9 -0
  155. package/src/plugins/media/__tests__/getters.test.ts +274 -0
  156. package/src/plugins/media/__tests__/mutations.test.ts +299 -0
  157. package/src/plugins/media/__tests__/plugin.test.ts +752 -0
  158. package/src/plugins/media/__tests__/query-key-defs.test.ts +54 -0
  159. package/src/plugins/media/__tests__/storage-adapters.test.ts +351 -0
  160. package/src/plugins/media/api/adapters/local.ts +79 -0
  161. package/src/plugins/media/api/adapters/s3.ts +198 -0
  162. package/src/plugins/media/api/adapters/vercel-blob.ts +131 -0
  163. package/src/plugins/media/api/getters.ts +174 -0
  164. package/src/plugins/media/api/index.ts +41 -0
  165. package/src/plugins/media/api/mutations.ts +179 -0
  166. package/src/plugins/media/api/plugin.ts +855 -0
  167. package/src/plugins/media/api/query-key-defs.ts +41 -0
  168. package/src/plugins/media/api/serializers.ts +28 -0
  169. package/src/plugins/media/api/storage-adapter.ts +139 -0
  170. package/src/plugins/media/client/components/index.tsx +6 -0
  171. package/src/plugins/media/client/components/media-picker/asset-card.tsx +150 -0
  172. package/src/plugins/media/client/components/media-picker/asset-preview-button.tsx +67 -0
  173. package/src/plugins/media/client/components/media-picker/browse-tab.tsx +116 -0
  174. package/src/plugins/media/client/components/media-picker/folder-tree.tsx +188 -0
  175. package/src/plugins/media/client/components/media-picker/index.tsx +347 -0
  176. package/src/plugins/media/client/components/media-picker/upload-tab.tsx +108 -0
  177. package/src/plugins/media/client/components/media-picker/url-tab.tsx +72 -0
  178. package/src/plugins/media/client/components/media-picker/utils.ts +17 -0
  179. package/src/plugins/media/client/components/pages/library-page.internal.tsx +134 -0
  180. package/src/plugins/media/client/components/pages/library-page.tsx +42 -0
  181. package/src/plugins/media/client/hooks/index.tsx +9 -0
  182. package/src/plugins/media/client/hooks/use-media.tsx +289 -0
  183. package/src/plugins/media/client/index.ts +4 -0
  184. package/src/plugins/media/client/overrides.ts +127 -0
  185. package/src/plugins/media/client/plugin.tsx +184 -0
  186. package/src/plugins/media/client/upload.ts +171 -0
  187. package/src/plugins/media/client/utils/image-compression.ts +131 -0
  188. package/src/plugins/media/client.css +1 -0
  189. package/src/plugins/media/db.ts +62 -0
  190. package/src/plugins/media/query-keys.ts +96 -0
  191. package/src/plugins/media/schemas.ts +37 -0
  192. package/src/plugins/media/style.css +1 -0
  193. package/src/plugins/media/types.ts +26 -0
  194. package/dist/shared/{stack.BOokfhZD.d.cts → stack.B6S3cgwN.d.cts} +16 -16
  195. package/dist/shared/{stack.CWxAl9K3.d.mts → stack.Bzfx-_lq.d.mts} +16 -16
  196. package/dist/shared/{stack.BvCR4-9H.d.ts → stack.j5SFLC1d.d.ts} +16 -16
@@ -0,0 +1,65 @@
1
+ function loadImage(file) {
2
+ return new Promise((resolve, reject) => {
3
+ const url = URL.createObjectURL(file);
4
+ const img = new Image();
5
+ img.onload = () => {
6
+ URL.revokeObjectURL(url);
7
+ resolve(img);
8
+ };
9
+ img.onerror = () => {
10
+ URL.revokeObjectURL(url);
11
+ reject(new Error(`Failed to load image: ${file.name}`));
12
+ };
13
+ img.src = url;
14
+ });
15
+ }
16
+ const SKIP_TYPES = /* @__PURE__ */ new Set(["image/svg+xml", "image/gif"]);
17
+ async function compressImage(file, options = {}) {
18
+ if (!file.type.startsWith("image/") || SKIP_TYPES.has(file.type)) {
19
+ return file;
20
+ }
21
+ if (typeof document === "undefined") return file;
22
+ const {
23
+ maxWidth = 2048,
24
+ maxHeight = 2048,
25
+ quality = 0.85,
26
+ outputFormat
27
+ } = options;
28
+ const img = await loadImage(file);
29
+ let { width, height } = img;
30
+ const needsResize = width > maxWidth || height > maxHeight;
31
+ const needsFormatChange = outputFormat !== void 0 && outputFormat !== file.type;
32
+ if (!needsResize && !needsFormatChange) return file;
33
+ if (needsResize) {
34
+ const ratio = Math.min(maxWidth / width, maxHeight / height);
35
+ width = Math.round(width * ratio);
36
+ height = Math.round(height * ratio);
37
+ }
38
+ const canvas = document.createElement("canvas");
39
+ canvas.width = width;
40
+ canvas.height = height;
41
+ const ctx = canvas.getContext("2d");
42
+ if (!ctx) return file;
43
+ ctx.drawImage(img, 0, 0, width, height);
44
+ const mimeType = outputFormat ?? file.type;
45
+ return new Promise((resolve, reject) => {
46
+ canvas.toBlob(
47
+ (blob) => {
48
+ if (!blob) {
49
+ reject(new Error("canvas.toBlob returned null"));
50
+ return;
51
+ }
52
+ let name = file.name;
53
+ if (outputFormat && outputFormat !== file.type) {
54
+ const ext = outputFormat.split("/")[1] ?? "jpg";
55
+ name = name.replace(/\.[^.]+$/, `.${ext}`);
56
+ }
57
+ resolve(new File([blob], name, { type: mimeType }));
58
+ },
59
+ mimeType,
60
+ quality
61
+ );
62
+ });
63
+ }
64
+
65
+ export { compressImage };
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ const db = require('@btst/db');
4
+
5
+ const mediaSchema = db.createDbPlugin("media", {
6
+ asset: {
7
+ modelName: "mediaAsset",
8
+ fields: {
9
+ filename: {
10
+ type: "string",
11
+ required: true
12
+ },
13
+ originalName: {
14
+ type: "string",
15
+ required: true
16
+ },
17
+ mimeType: {
18
+ type: "string",
19
+ required: true
20
+ },
21
+ size: {
22
+ type: "number",
23
+ required: true
24
+ },
25
+ url: {
26
+ type: "string",
27
+ required: true
28
+ },
29
+ folderId: {
30
+ type: "string",
31
+ required: false
32
+ },
33
+ alt: {
34
+ type: "string",
35
+ required: false
36
+ },
37
+ createdAt: {
38
+ type: "date",
39
+ defaultValue: () => /* @__PURE__ */ new Date()
40
+ }
41
+ }
42
+ },
43
+ folder: {
44
+ modelName: "mediaFolder",
45
+ fields: {
46
+ name: {
47
+ type: "string",
48
+ required: true
49
+ },
50
+ parentId: {
51
+ type: "string",
52
+ required: false
53
+ },
54
+ createdAt: {
55
+ type: "date",
56
+ defaultValue: () => /* @__PURE__ */ new Date()
57
+ }
58
+ }
59
+ }
60
+ });
61
+
62
+ exports.mediaSchema = mediaSchema;
@@ -0,0 +1,60 @@
1
+ import { createDbPlugin } from '@btst/db';
2
+
3
+ const mediaSchema = createDbPlugin("media", {
4
+ asset: {
5
+ modelName: "mediaAsset",
6
+ fields: {
7
+ filename: {
8
+ type: "string",
9
+ required: true
10
+ },
11
+ originalName: {
12
+ type: "string",
13
+ required: true
14
+ },
15
+ mimeType: {
16
+ type: "string",
17
+ required: true
18
+ },
19
+ size: {
20
+ type: "number",
21
+ required: true
22
+ },
23
+ url: {
24
+ type: "string",
25
+ required: true
26
+ },
27
+ folderId: {
28
+ type: "string",
29
+ required: false
30
+ },
31
+ alt: {
32
+ type: "string",
33
+ required: false
34
+ },
35
+ createdAt: {
36
+ type: "date",
37
+ defaultValue: () => /* @__PURE__ */ new Date()
38
+ }
39
+ }
40
+ },
41
+ folder: {
42
+ modelName: "mediaFolder",
43
+ fields: {
44
+ name: {
45
+ type: "string",
46
+ required: true
47
+ },
48
+ parentId: {
49
+ type: "string",
50
+ required: false
51
+ },
52
+ createdAt: {
53
+ type: "date",
54
+ defaultValue: () => /* @__PURE__ */ new Date()
55
+ }
56
+ }
57
+ }
58
+ });
59
+
60
+ export { mediaSchema };
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const z = require('zod');
4
+
5
+ const AssetListQuerySchema = z.z.object({
6
+ folderId: z.z.string().optional(),
7
+ mimeType: z.z.string().optional(),
8
+ query: z.z.string().optional(),
9
+ offset: z.z.coerce.number().int().min(0).optional(),
10
+ limit: z.z.coerce.number().int().min(1).max(100).optional()
11
+ });
12
+ const createAssetSchema = z.z.object({
13
+ filename: z.z.string().min(1),
14
+ originalName: z.z.string().min(1),
15
+ mimeType: z.z.string().min(1),
16
+ // Allow 0 for URL-registered assets where size is unknown at registration time.
17
+ size: z.z.number().int().min(0),
18
+ url: z.z.httpUrl(),
19
+ folderId: z.z.string().optional(),
20
+ alt: z.z.string().optional()
21
+ });
22
+ const updateAssetSchema = z.z.object({
23
+ alt: z.z.string().optional(),
24
+ folderId: z.z.string().nullable().optional()
25
+ });
26
+ const createFolderSchema = z.z.object({
27
+ name: z.z.string().min(1),
28
+ parentId: z.z.string().optional()
29
+ });
30
+ const uploadTokenRequestSchema = z.z.object({
31
+ filename: z.z.string().min(1),
32
+ mimeType: z.z.string().min(1),
33
+ size: z.z.number().int().positive(),
34
+ folderId: z.z.string().optional()
35
+ });
36
+
37
+ exports.AssetListQuerySchema = AssetListQuerySchema;
38
+ exports.createAssetSchema = createAssetSchema;
39
+ exports.createFolderSchema = createFolderSchema;
40
+ exports.updateAssetSchema = updateAssetSchema;
41
+ exports.uploadTokenRequestSchema = uploadTokenRequestSchema;
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+
3
+ const AssetListQuerySchema = z.object({
4
+ folderId: z.string().optional(),
5
+ mimeType: z.string().optional(),
6
+ query: z.string().optional(),
7
+ offset: z.coerce.number().int().min(0).optional(),
8
+ limit: z.coerce.number().int().min(1).max(100).optional()
9
+ });
10
+ const createAssetSchema = z.object({
11
+ filename: z.string().min(1),
12
+ originalName: z.string().min(1),
13
+ mimeType: z.string().min(1),
14
+ // Allow 0 for URL-registered assets where size is unknown at registration time.
15
+ size: z.number().int().min(0),
16
+ url: z.httpUrl(),
17
+ folderId: z.string().optional(),
18
+ alt: z.string().optional()
19
+ });
20
+ const updateAssetSchema = z.object({
21
+ alt: z.string().optional(),
22
+ folderId: z.string().nullable().optional()
23
+ });
24
+ const createFolderSchema = z.object({
25
+ name: z.string().min(1),
26
+ parentId: z.string().optional()
27
+ });
28
+ const uploadTokenRequestSchema = z.object({
29
+ filename: z.string().min(1),
30
+ mimeType: z.string().min(1),
31
+ size: z.number().int().positive(),
32
+ folderId: z.string().optional()
33
+ });
34
+
35
+ export { AssetListQuerySchema, createAssetSchema, createFolderSchema, updateAssetSchema, uploadTokenRequestSchema };
@@ -7,6 +7,7 @@ const React = require('react');
7
7
  const button = require('../../../button.cjs');
8
8
  const label = require('../../../label.cjs');
9
9
  const input = require('../../../input.cjs');
10
+ const separator = require('../../../separator.cjs');
10
11
 
11
12
  function _interopNamespaceCompat(e) {
12
13
  if (e && typeof e === 'object' && 'default' in e) return e;
@@ -24,7 +25,8 @@ const React__namespace = /*#__PURE__*/_interopNamespaceCompat(React);
24
25
 
25
26
  const ImageEditBlock = ({
26
27
  editor,
27
- close
28
+ close,
29
+ imagePickerTrigger: ImagePickerTrigger
28
30
  }) => {
29
31
  const fileInputRef = React__namespace.useRef(null);
30
32
  const [link, setLink] = React__namespace.useState("");
@@ -79,6 +81,21 @@ const ImageEditBlock = ({
79
81
  ] })
80
82
  ] }),
81
83
  /* @__PURE__ */ jsxRuntime.jsx(button.Button, { type: "button", className: "w-full", onClick: handleClick, children: "Upload from your computer" }),
84
+ ImagePickerTrigger && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
85
+ /* @__PURE__ */ jsxRuntime.jsx(separator.Separator, {}),
86
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
87
+ /* @__PURE__ */ jsxRuntime.jsx(label.Label, { className: "text-muted-foreground text-xs uppercase tracking-wider", children: "Or browse media library" }),
88
+ /* @__PURE__ */ jsxRuntime.jsx(
89
+ ImagePickerTrigger,
90
+ {
91
+ onSelect: (url) => {
92
+ editor.commands.setImages([{ src: url }]);
93
+ close();
94
+ }
95
+ }
96
+ )
97
+ ] })
98
+ ] }),
82
99
  /* @__PURE__ */ jsxRuntime.jsx(
83
100
  "input",
84
101
  {
@@ -1,12 +1,14 @@
1
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import { Button } from '../../../button.mjs';
4
4
  import { Label } from '../../../label.mjs';
5
5
  import { Input } from '../../../input.mjs';
6
+ import { Separator } from '../../../separator.mjs';
6
7
 
7
8
  const ImageEditBlock = ({
8
9
  editor,
9
- close
10
+ close,
11
+ imagePickerTrigger: ImagePickerTrigger
10
12
  }) => {
11
13
  const fileInputRef = React.useRef(null);
12
14
  const [link, setLink] = React.useState("");
@@ -61,6 +63,21 @@ const ImageEditBlock = ({
61
63
  ] })
62
64
  ] }),
63
65
  /* @__PURE__ */ jsx(Button, { type: "button", className: "w-full", onClick: handleClick, children: "Upload from your computer" }),
66
+ ImagePickerTrigger && /* @__PURE__ */ jsxs(Fragment, { children: [
67
+ /* @__PURE__ */ jsx(Separator, {}),
68
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
69
+ /* @__PURE__ */ jsx(Label, { className: "text-muted-foreground text-xs uppercase tracking-wider", children: "Or browse media library" }),
70
+ /* @__PURE__ */ jsx(
71
+ ImagePickerTrigger,
72
+ {
73
+ onSelect: (url) => {
74
+ editor.commands.setImages([{ src: url }]);
75
+ close();
76
+ }
77
+ }
78
+ )
79
+ ] })
80
+ ] }),
64
81
  /* @__PURE__ */ jsx(
65
82
  "input",
66
83
  {
@@ -7,7 +7,7 @@ const toolbarButton = require('../toolbar-button.cjs');
7
7
  const dialog = require('../../../dialog.cjs');
8
8
  const imageEditBlock = require('./image-edit-block.cjs');
9
9
 
10
- const ImageEditDialog = ({ editor, size, variant }) => {
10
+ const ImageEditDialog = ({ editor, size, variant, imagePickerTrigger }) => {
11
11
  const [open, setOpen] = React.useState(false);
12
12
  return /* @__PURE__ */ jsxRuntime.jsxs(dialog.Dialog, { open, onOpenChange: setOpen, children: [
13
13
  /* @__PURE__ */ jsxRuntime.jsx(dialog.DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -26,7 +26,7 @@ const ImageEditDialog = ({ editor, size, variant }) => {
26
26
  /* @__PURE__ */ jsxRuntime.jsx(dialog.DialogTitle, { children: "Select image" }),
27
27
  /* @__PURE__ */ jsxRuntime.jsx(dialog.DialogDescription, { className: "sr-only", children: "Upload an image from your computer" })
28
28
  ] }),
29
- /* @__PURE__ */ jsxRuntime.jsx(imageEditBlock.ImageEditBlock, { editor, close: () => setOpen(false) })
29
+ /* @__PURE__ */ jsxRuntime.jsx(imageEditBlock.ImageEditBlock, { editor, close: () => setOpen(false), imagePickerTrigger })
30
30
  ] })
31
31
  ] });
32
32
  };
@@ -5,7 +5,7 @@ import { ToolbarButton } from '../toolbar-button.mjs';
5
5
  import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '../../../dialog.mjs';
6
6
  import { ImageEditBlock } from './image-edit-block.mjs';
7
7
 
8
- const ImageEditDialog = ({ editor, size, variant }) => {
8
+ const ImageEditDialog = ({ editor, size, variant, imagePickerTrigger }) => {
9
9
  const [open, setOpen] = useState(false);
10
10
  return /* @__PURE__ */ jsxs(Dialog, { open, onOpenChange: setOpen, children: [
11
11
  /* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
@@ -24,7 +24,7 @@ const ImageEditDialog = ({ editor, size, variant }) => {
24
24
  /* @__PURE__ */ jsx(DialogTitle, { children: "Select image" }),
25
25
  /* @__PURE__ */ jsx(DialogDescription, { className: "sr-only", children: "Upload an image from your computer" })
26
26
  ] }),
27
- /* @__PURE__ */ jsx(ImageEditBlock, { editor, close: () => setOpen(false) })
27
+ /* @__PURE__ */ jsx(ImageEditBlock, { editor, close: () => setOpen(false), imagePickerTrigger })
28
28
  ] })
29
29
  ] });
30
30
  };
@@ -42,11 +42,12 @@ const SectionFive = ({
42
42
  activeActions = formatActions.map((action) => action.value),
43
43
  mainActionCount = 0,
44
44
  size,
45
- variant
45
+ variant,
46
+ imagePickerTrigger
46
47
  }) => {
47
48
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
48
49
  /* @__PURE__ */ jsxRuntime.jsx(linkEditPopover.LinkEditPopover, { editor, size, variant }),
49
- /* @__PURE__ */ jsxRuntime.jsx(imageEditDialog.ImageEditDialog, { editor, size, variant }),
50
+ /* @__PURE__ */ jsxRuntime.jsx(imageEditDialog.ImageEditDialog, { editor, size, variant, imagePickerTrigger }),
50
51
  /* @__PURE__ */ jsxRuntime.jsx(
51
52
  toolbarSection.ToolbarSection,
52
53
  {
@@ -38,11 +38,12 @@ const SectionFive = ({
38
38
  activeActions = formatActions.map((action) => action.value),
39
39
  mainActionCount = 0,
40
40
  size,
41
- variant
41
+ variant,
42
+ imagePickerTrigger
42
43
  }) => {
43
44
  return /* @__PURE__ */ jsxs(Fragment, { children: [
44
45
  /* @__PURE__ */ jsx(LinkEditPopover, { editor, size, variant }),
45
- /* @__PURE__ */ jsx(ImageEditDialog, { editor, size, variant }),
46
+ /* @__PURE__ */ jsx(ImageEditDialog, { editor, size, variant, imagePickerTrigger }),
46
47
  /* @__PURE__ */ jsx(
47
48
  ToolbarSection,
48
49
  {
@@ -16,7 +16,10 @@ const useMinimalTiptap = require('./hooks/use-minimal-tiptap.cjs');
16
16
  const measuredContainer = require('./components/measured-container.cjs');
17
17
  const useTiptapEditor = require('./hooks/use-tiptap-editor.cjs');
18
18
 
19
- const Toolbar = ({ editor }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-border flex h-12 shrink-0 overflow-x-auto border-b p-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-max items-center gap-px", children: [
19
+ const Toolbar = ({
20
+ editor,
21
+ imagePickerTrigger
22
+ }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-border flex h-12 shrink-0 overflow-x-auto border-b p-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-max items-center gap-px", children: [
20
23
  /* @__PURE__ */ jsxRuntime.jsx(one.SectionOne, { editor, activeLevels: [1, 2, 3, 4, 5, 6] }),
21
24
  /* @__PURE__ */ jsxRuntime.jsx(separator.Separator, { orientation: "vertical", className: "mx-2" }),
22
25
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -51,7 +54,8 @@ const Toolbar = ({ editor }) => /* @__PURE__ */ jsxRuntime.jsx("div", { classNam
51
54
  {
52
55
  editor,
53
56
  activeActions: ["codeBlock", "blockquote", "horizontalRule"],
54
- mainActionCount: 0
57
+ mainActionCount: 0,
58
+ imagePickerTrigger
55
59
  }
56
60
  )
57
61
  ] }) });
@@ -60,6 +64,7 @@ const MinimalTiptapEditor = ({
60
64
  onChange,
61
65
  className,
62
66
  editorContentClassName,
67
+ imagePickerTrigger,
63
68
  ...props
64
69
  }) => {
65
70
  const editor = useMinimalTiptap.useMinimalTiptapEditor({
@@ -75,7 +80,8 @@ const MinimalTiptapEditor = ({
75
80
  {
76
81
  editor,
77
82
  className,
78
- editorContentClassName
83
+ editorContentClassName,
84
+ imagePickerTrigger
79
85
  }
80
86
  ) });
81
87
  };
@@ -83,7 +89,8 @@ MinimalTiptapEditor.displayName = "MinimalTiptapEditor";
83
89
  const MainMinimalTiptapEditor = ({
84
90
  editor: providedEditor,
85
91
  className,
86
- editorContentClassName
92
+ editorContentClassName,
93
+ imagePickerTrigger
87
94
  }) => {
88
95
  const { editor } = useTiptapEditor.useTiptapEditor(providedEditor);
89
96
  if (!editor) {
@@ -100,7 +107,7 @@ const MainMinimalTiptapEditor = ({
100
107
  className
101
108
  ),
102
109
  children: [
103
- /* @__PURE__ */ jsxRuntime.jsx(Toolbar, { editor }),
110
+ /* @__PURE__ */ jsxRuntime.jsx(Toolbar, { editor, imagePickerTrigger }),
104
111
  /* @__PURE__ */ jsxRuntime.jsx(
105
112
  index.EditorContent,
106
113
  {
@@ -12,7 +12,10 @@ import { useMinimalTiptapEditor } from './hooks/use-minimal-tiptap.mjs';
12
12
  import { MeasuredContainer } from './components/measured-container.mjs';
13
13
  import { useTiptapEditor } from './hooks/use-tiptap-editor.mjs';
14
14
 
15
- const Toolbar = ({ editor }) => /* @__PURE__ */ jsx("div", { className: "border-border flex h-12 shrink-0 overflow-x-auto border-b p-2", children: /* @__PURE__ */ jsxs("div", { className: "flex w-max items-center gap-px", children: [
15
+ const Toolbar = ({
16
+ editor,
17
+ imagePickerTrigger
18
+ }) => /* @__PURE__ */ jsx("div", { className: "border-border flex h-12 shrink-0 overflow-x-auto border-b p-2", children: /* @__PURE__ */ jsxs("div", { className: "flex w-max items-center gap-px", children: [
16
19
  /* @__PURE__ */ jsx(SectionOne, { editor, activeLevels: [1, 2, 3, 4, 5, 6] }),
17
20
  /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "mx-2" }),
18
21
  /* @__PURE__ */ jsx(
@@ -47,7 +50,8 @@ const Toolbar = ({ editor }) => /* @__PURE__ */ jsx("div", { className: "border-
47
50
  {
48
51
  editor,
49
52
  activeActions: ["codeBlock", "blockquote", "horizontalRule"],
50
- mainActionCount: 0
53
+ mainActionCount: 0,
54
+ imagePickerTrigger
51
55
  }
52
56
  )
53
57
  ] }) });
@@ -56,6 +60,7 @@ const MinimalTiptapEditor = ({
56
60
  onChange,
57
61
  className,
58
62
  editorContentClassName,
63
+ imagePickerTrigger,
59
64
  ...props
60
65
  }) => {
61
66
  const editor = useMinimalTiptapEditor({
@@ -71,7 +76,8 @@ const MinimalTiptapEditor = ({
71
76
  {
72
77
  editor,
73
78
  className,
74
- editorContentClassName
79
+ editorContentClassName,
80
+ imagePickerTrigger
75
81
  }
76
82
  ) });
77
83
  };
@@ -79,7 +85,8 @@ MinimalTiptapEditor.displayName = "MinimalTiptapEditor";
79
85
  const MainMinimalTiptapEditor = ({
80
86
  editor: providedEditor,
81
87
  className,
82
- editorContentClassName
88
+ editorContentClassName,
89
+ imagePickerTrigger
83
90
  }) => {
84
91
  const { editor } = useTiptapEditor(providedEditor);
85
92
  if (!editor) {
@@ -96,7 +103,7 @@ const MainMinimalTiptapEditor = ({
96
103
  className
97
104
  ),
98
105
  children: [
99
- /* @__PURE__ */ jsx(Toolbar, { editor }),
106
+ /* @__PURE__ */ jsx(Toolbar, { editor, imagePickerTrigger }),
100
107
  /* @__PURE__ */ jsx(
101
108
  EditorContent,
102
109
  {
@@ -392,6 +392,17 @@ declare const BLOG_LOCALIZATION: {
392
392
  };
393
393
  type BlogLocalization = typeof BLOG_LOCALIZATION;
394
394
 
395
+ /**
396
+ * Props for the overridable blog featured image input component.
397
+ */
398
+ interface BlogImageInputFieldProps {
399
+ /** Current image URL value */
400
+ value: string;
401
+ /** Called when the image URL changes */
402
+ onChange: (value: string) => void;
403
+ /** Whether the field is required */
404
+ isRequired?: boolean;
405
+ }
395
406
  /**
396
407
  * Context passed to lifecycle hooks
397
408
  */
@@ -435,9 +446,55 @@ interface BlogPluginOverrides {
435
446
  */
436
447
  Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, any>>;
437
448
  /**
438
- * Function used to upload an image and return its URL.
449
+ * Function used to upload a new image file and return its URL.
450
+ * This is separate from `imagePicker`, which selects an existing asset URL.
439
451
  */
440
452
  uploadImage: (file: File) => Promise<string>;
453
+ /**
454
+ * Optional custom component for the featured image field.
455
+ *
456
+ * When provided it replaces the default file-upload input entirely.
457
+ * The component receives `value` (current URL string) and `onChange` (setter).
458
+ *
459
+ * Typical use case: render a preview when a value is set, and a media-picker
460
+ * trigger when no value is set.
461
+ *
462
+ * @example
463
+ * ```tsx
464
+ * imageInputField: ({ value, onChange }) =>
465
+ * value ? (
466
+ * <div>
467
+ * <img src={value} alt="Preview" />
468
+ * <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
469
+ * onSelect={(assets) => onChange(assets[0].url)} />
470
+ * </div>
471
+ * ) : (
472
+ * <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
473
+ * onSelect={(assets) => onChange(assets[0].url)} />
474
+ * )
475
+ * ```
476
+ */
477
+ imageInputField?: ComponentType<BlogImageInputFieldProps>;
478
+ /**
479
+ * Optional trigger component for a media picker.
480
+ * When provided, it is rendered adjacent to the Markdown editor and allows
481
+ * users to browse and select previously uploaded assets.
482
+ * Receives `onSelect(url)` — insert the chosen URL into the editor.
483
+ *
484
+ * @example
485
+ * ```tsx
486
+ * imagePicker: ({ onSelect }) => (
487
+ * <MediaPicker
488
+ * trigger={<Button size="sm" variant="outline">Browse media</Button>}
489
+ * accept={["image/*"]}
490
+ * onSelect={(assets) => onSelect(assets[0].url)}
491
+ * />
492
+ * )
493
+ * ```
494
+ */
495
+ imagePicker?: ComponentType<{
496
+ onSelect: (url: string) => void;
497
+ }>;
441
498
  /**
442
499
  * Localization object for the blog plugin
443
500
  */
@@ -392,6 +392,17 @@ declare const BLOG_LOCALIZATION: {
392
392
  };
393
393
  type BlogLocalization = typeof BLOG_LOCALIZATION;
394
394
 
395
+ /**
396
+ * Props for the overridable blog featured image input component.
397
+ */
398
+ interface BlogImageInputFieldProps {
399
+ /** Current image URL value */
400
+ value: string;
401
+ /** Called when the image URL changes */
402
+ onChange: (value: string) => void;
403
+ /** Whether the field is required */
404
+ isRequired?: boolean;
405
+ }
395
406
  /**
396
407
  * Context passed to lifecycle hooks
397
408
  */
@@ -435,9 +446,55 @@ interface BlogPluginOverrides {
435
446
  */
436
447
  Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, any>>;
437
448
  /**
438
- * Function used to upload an image and return its URL.
449
+ * Function used to upload a new image file and return its URL.
450
+ * This is separate from `imagePicker`, which selects an existing asset URL.
439
451
  */
440
452
  uploadImage: (file: File) => Promise<string>;
453
+ /**
454
+ * Optional custom component for the featured image field.
455
+ *
456
+ * When provided it replaces the default file-upload input entirely.
457
+ * The component receives `value` (current URL string) and `onChange` (setter).
458
+ *
459
+ * Typical use case: render a preview when a value is set, and a media-picker
460
+ * trigger when no value is set.
461
+ *
462
+ * @example
463
+ * ```tsx
464
+ * imageInputField: ({ value, onChange }) =>
465
+ * value ? (
466
+ * <div>
467
+ * <img src={value} alt="Preview" />
468
+ * <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
469
+ * onSelect={(assets) => onChange(assets[0].url)} />
470
+ * </div>
471
+ * ) : (
472
+ * <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
473
+ * onSelect={(assets) => onChange(assets[0].url)} />
474
+ * )
475
+ * ```
476
+ */
477
+ imageInputField?: ComponentType<BlogImageInputFieldProps>;
478
+ /**
479
+ * Optional trigger component for a media picker.
480
+ * When provided, it is rendered adjacent to the Markdown editor and allows
481
+ * users to browse and select previously uploaded assets.
482
+ * Receives `onSelect(url)` — insert the chosen URL into the editor.
483
+ *
484
+ * @example
485
+ * ```tsx
486
+ * imagePicker: ({ onSelect }) => (
487
+ * <MediaPicker
488
+ * trigger={<Button size="sm" variant="outline">Browse media</Button>}
489
+ * accept={["image/*"]}
490
+ * onSelect={(assets) => onSelect(assets[0].url)}
491
+ * />
492
+ * )
493
+ * ```
494
+ */
495
+ imagePicker?: ComponentType<{
496
+ onSelect: (url: string) => void;
497
+ }>;
441
498
  /**
442
499
  * Localization object for the blog plugin
443
500
  */