@lowdefy/blocks-tiptap 5.6.0 → 6.0.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.
@@ -0,0 +1,90 @@
1
+ # Copyright 2020-2026 Lowdefy, Inc
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ - id: default
16
+ type: TiptapMentionInput
17
+ - id: properties.placeholder
18
+ type: TiptapMentionInput
19
+ properties:
20
+ placeholder: 'Type @ to mention...'
21
+ - id: properties.disabled
22
+ type: TiptapMentionInput
23
+ properties:
24
+ disabled: true
25
+ - id: properties.mentions.options
26
+ type: TiptapMentionInput
27
+ properties:
28
+ mentions:
29
+ options:
30
+ - label: Alice
31
+ value:
32
+ _id: user_1
33
+ - label: Bob
34
+ value:
35
+ _id: user_2
36
+ - id: properties.mentions.char = '#'
37
+ type: TiptapMentionInput
38
+ properties:
39
+ placeholder: 'Type # to tag'
40
+ mentions:
41
+ char: '#'
42
+ options:
43
+ - label: bug
44
+ value:
45
+ _id: tag_bug
46
+ - id: properties.mentions.limit with sections
47
+ type: TiptapMentionInput
48
+ properties:
49
+ mentions:
50
+ limit: 2
51
+ options:
52
+ - label: Alice
53
+ value:
54
+ _id: user_1
55
+ tag:
56
+ section: People
57
+ - label: Bob
58
+ value:
59
+ _id: user_2
60
+ tag:
61
+ section: People
62
+ - label: Finance
63
+ value:
64
+ _id: role_finance
65
+ tag:
66
+ section: Roles
67
+ - id: properties.mentions group chips with tag.color
68
+ type: TiptapMentionInput
69
+ properties:
70
+ mentions:
71
+ options:
72
+ - label: Alice
73
+ value:
74
+ _id: user_1
75
+ tag:
76
+ section: People
77
+ - label: Finance
78
+ value:
79
+ role: finance-admin
80
+ tag:
81
+ section: Roles
82
+ group: finance
83
+ color: '#722ed1'
84
+ - label: Developers
85
+ value:
86
+ role: developer
87
+ tag:
88
+ section: Roles
89
+ group: devs
90
+ color: '#13c2c2'
@@ -14,10 +14,10 @@
14
14
  limitations under the License.
15
15
  */ import { useRef } from 'react';
16
16
  import TurndownService from 'turndown';
17
- import s3FileUpload from '../utils/s3FileUpload.js';
17
+ import fileUpload from '../utils/fileUpload.js';
18
18
  // Ref-tracked controller for the mention editor's value. Mirrors TiptapInput's
19
19
  // useTiptapState but also extracts mentions from the document.
20
- function useTiptapMentionState({ value, methods }) {
20
+ function useTiptapMentionState({ value, methods, hasDownloadRequest }) {
21
21
  const valueRef = useRef(value);
22
22
  valueRef.current = value;
23
23
  const turndownService = new TurndownService();
@@ -60,9 +60,10 @@ function useTiptapMentionState({ value, methods }) {
60
60
  });
61
61
  };
62
62
  const insertImage = async (editor, file, pos)=>{
63
- const url = await s3FileUpload({
63
+ const url = await fileUpload({
64
64
  file,
65
- methods
65
+ methods,
66
+ hasDownloadRequest
66
67
  });
67
68
  // The upload is async: bail if the editor was torn down meanwhile (e.g. the
68
69
  // surrounding page navigated away before the upload resolved), otherwise the
@@ -0,0 +1,63 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ import getLegacyObjectUrl from '@lowdefy/blocks-files/utils/getLegacyObjectUrl.js';
17
+ import getUploadPolicy from '@lowdefy/blocks-files/utils/getUploadPolicy.js';
18
+ import uploadFile from '@lowdefy/blocks-files/utils/uploadFile.js';
19
+ // Uploads a file through the app's upload-policy request (any storage
20
+ // provider) and resolves a display URL for the inline image. Inline images
21
+ // persist in saved markdown, so they must be public assets: the upload
22
+ // request should set the provider's public-read acl, and the download request
23
+ // should set public: true to return a stable, non-expiring URL. Without a
24
+ // download request the legacy unsigned object URL is used (S3-shaped,
25
+ // deprecated).
26
+ async function fileUpload({ file, methods, hasDownloadRequest }) {
27
+ if (type.isNone(file)) {
28
+ return;
29
+ }
30
+ const descriptor = await getUploadPolicy({
31
+ methods,
32
+ file
33
+ });
34
+ file.bucket = descriptor.bucket;
35
+ file.key = descriptor.key;
36
+ await uploadFile({
37
+ descriptor,
38
+ file
39
+ });
40
+ if (hasDownloadRequest) {
41
+ const response = await methods.triggerEvent({
42
+ name: '__getDownloadPolicy',
43
+ event: {
44
+ file: {
45
+ bucket: descriptor.bucket,
46
+ key: descriptor.key,
47
+ name: file.name,
48
+ type: file.type
49
+ }
50
+ }
51
+ });
52
+ if (response.success !== true) {
53
+ throw new Error('Download policy request error.');
54
+ }
55
+ file.url = response.responses.__getDownloadPolicy.response[0];
56
+ return file.url;
57
+ }
58
+ file.url = getLegacyObjectUrl({
59
+ descriptor
60
+ });
61
+ return file.url;
62
+ }
63
+ export default fileUpload;
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */ // Map validation.status → wrapper class. Matches the visual behavior of
16
16
  // passing status="error" | "warning" to an antd <Input />, but rendered
17
- // with our own CSS (see utils/style.module.css) because antd v6 uses
17
+ // with our own CSS (see utils/style.css) because antd v6 uses
18
18
  // CSS-in-JS, so the .ant-input-status-* classes are not global.
19
19
  function statusClass(status) {
20
20
  if (status === 'error') return 'tiptap-wrapper-error';
@@ -3,7 +3,7 @@
3
3
  stylesheet class — we replicate the input look using antd's theme
4
4
  CSS variables (--ant-color-*, --ant-border-radius) so the block
5
5
  tracks the active theme without depending on cssinjs. */
6
- :global(.tiptap-wrapper) {
6
+ .tiptap-wrapper {
7
7
  border: 1px solid var(--ant-color-border, #d9d9d9);
8
8
  border-radius: var(--ant-border-radius, 6px);
9
9
  background: var(--ant-color-bg-container, #fff);
@@ -13,73 +13,79 @@
13
13
  box-shadow 0.2s,
14
14
  background 0.2s;
15
15
  padding: 0;
16
+ /* Column flex so the editable fills the wrapper's min-height (see ProseMirror rule), making the
17
+ whole multi-line box a click/focus target — not just the first line of content. */
18
+ display: flex;
19
+ flex-direction: column;
16
20
  }
17
21
 
18
- :global(.tiptap-wrapper):hover {
22
+ .tiptap-wrapper:hover {
19
23
  border-color: var(--ant-color-primary-hover, #4096ff);
20
24
  }
21
25
 
22
- :global(.tiptap-wrapper):focus-within {
26
+ .tiptap-wrapper:focus-within {
23
27
  border-color: var(--ant-color-primary, #1677ff);
24
28
  box-shadow: 0 0 0 2px var(--ant-color-primary-bg, rgba(22, 119, 255, 0.1));
25
29
  outline: none;
26
30
  }
27
31
 
28
- :global(.tiptap-wrapper-borderless) {
32
+ .tiptap-wrapper-borderless {
29
33
  border-color: transparent;
30
34
  background: transparent;
31
35
  }
32
36
 
33
- :global(.tiptap-wrapper-borderless):hover,
34
- :global(.tiptap-wrapper-borderless):focus-within {
37
+ .tiptap-wrapper-borderless:hover,
38
+ .tiptap-wrapper-borderless:focus-within {
35
39
  border-color: transparent;
36
40
  box-shadow: none;
37
41
  }
38
42
 
39
- :global(.tiptap-wrapper-error) {
43
+ .tiptap-wrapper-error {
40
44
  border-color: var(--ant-color-error, #ff4d4f);
41
45
  }
42
46
 
43
- :global(.tiptap-wrapper-error):hover {
47
+ .tiptap-wrapper-error:hover {
44
48
  border-color: var(--ant-color-error-hover, var(--ant-color-error, #ff4d4f));
45
49
  }
46
50
 
47
- :global(.tiptap-wrapper-error):focus-within {
51
+ .tiptap-wrapper-error:focus-within {
48
52
  border-color: var(--ant-color-error, #ff4d4f);
49
53
  box-shadow: 0 0 0 2px var(--ant-color-error-bg, rgba(255, 77, 79, 0.1));
50
54
  }
51
55
 
52
- :global(.tiptap-wrapper-warning) {
56
+ .tiptap-wrapper-warning {
53
57
  border-color: var(--ant-color-warning, #faad14);
54
58
  }
55
59
 
56
- :global(.tiptap-wrapper-warning):hover {
60
+ .tiptap-wrapper-warning:hover {
57
61
  border-color: var(--ant-color-warning-hover, var(--ant-color-warning, #faad14));
58
62
  }
59
63
 
60
- :global(.tiptap-wrapper-warning):focus-within {
64
+ .tiptap-wrapper-warning:focus-within {
61
65
  border-color: var(--ant-color-warning, #faad14);
62
66
  box-shadow: 0 0 0 2px var(--ant-color-warning-bg, rgba(250, 173, 20, 0.1));
63
67
  }
64
68
 
65
- :global(.tiptap-wrapper-disabled) {
69
+ .tiptap-wrapper-disabled {
66
70
  background: var(--ant-color-bg-container-disabled, rgba(0, 0, 0, 0.04));
67
71
  color: var(--ant-color-text-disabled, rgba(0, 0, 0, 0.25));
68
72
  cursor: not-allowed;
69
73
  }
70
74
 
71
- :global(.tiptap-wrapper-disabled):hover {
75
+ .tiptap-wrapper-disabled:hover {
72
76
  border-color: var(--ant-color-border, #d9d9d9);
73
77
  }
74
78
 
75
- :global(.tiptap-wrapper-disabled) :global(.tiptap.ProseMirror) {
79
+ .tiptap-wrapper-disabled .tiptap.ProseMirror {
76
80
  cursor: not-allowed;
77
81
  }
78
82
 
79
- /* ProseMirror editable — sits inside the wrapper. */
80
- :global(.tiptap.ProseMirror) {
83
+ /* ProseMirror editable — sits inside the wrapper. Grow to fill the wrapper's min-height so a click
84
+ anywhere in the box (not only the first content line) lands in the editor and places the cursor. */
85
+ .tiptap.ProseMirror {
81
86
  padding: 8px;
82
87
  outline: none;
88
+ flex: 1 1 auto;
83
89
  }
84
90
 
85
91
  /* Editor content styling.
@@ -100,38 +106,38 @@
100
106
 
101
107
  /* Paragraphs: a small gap so successive lines (each a new <p> from pressing
102
108
  Enter) read as separate lines instead of merging. */
103
- :global(.tiptap) :global(p) {
109
+ .tiptap p {
104
110
  margin: 0 0 0.5em;
105
111
  }
106
112
 
107
- :global(.tiptap) > :global(p):last-child {
113
+ .tiptap > p:last-child {
108
114
  margin-bottom: 0;
109
115
  }
110
116
 
111
117
  /* Lists: restore the markers and indent the antd reset removes. */
112
- :global(.tiptap) :global(ul),
113
- :global(.tiptap) :global(ol) {
118
+ .tiptap ul,
119
+ .tiptap ol {
114
120
  margin: 0 0 0.5em;
115
121
  padding-left: 1.5em;
116
122
  }
117
123
 
118
- :global(.tiptap) :global(ul) {
124
+ .tiptap ul {
119
125
  list-style: disc;
120
126
  }
121
127
 
122
- :global(.tiptap) :global(ol) {
128
+ .tiptap ol {
123
129
  list-style: decimal;
124
130
  }
125
131
 
126
132
  /* Keep paragraphs inside list items and nested lists tight. */
127
- :global(.tiptap) :global(li) :global(p),
128
- :global(.tiptap) :global(li) :global(ul),
129
- :global(.tiptap) :global(li) :global(ol) {
133
+ .tiptap li p,
134
+ .tiptap li ul,
135
+ .tiptap li ol {
130
136
  margin-bottom: 0;
131
137
  }
132
138
 
133
139
  /* Blockquote: restore the indent and add a quote bar. */
134
- :global(.tiptap) :global(blockquote) {
140
+ .tiptap blockquote {
135
141
  margin: 0 0 0.5em;
136
142
  padding-left: 1em;
137
143
  border-left: 3px solid var(--ant-color-border, #d9d9d9);
@@ -140,44 +146,44 @@
140
146
 
141
147
  /* Headings: the reset flattens all six to weight 500 with no size scale.
142
148
  Restore a distinct, descending scale so heading levels are visible. */
143
- :global(.tiptap) :global(h1),
144
- :global(.tiptap) :global(h2),
145
- :global(.tiptap) :global(h3),
146
- :global(.tiptap) :global(h4),
147
- :global(.tiptap) :global(h5),
148
- :global(.tiptap) :global(h6) {
149
+ .tiptap h1,
150
+ .tiptap h2,
151
+ .tiptap h3,
152
+ .tiptap h4,
153
+ .tiptap h5,
154
+ .tiptap h6 {
149
155
  margin: 0 0 0.5em;
150
156
  font-weight: 600;
151
157
  line-height: 1.3;
152
158
  }
153
159
 
154
- :global(.tiptap) :global(h1) {
160
+ .tiptap h1 {
155
161
  font-size: 2em;
156
162
  }
157
163
 
158
- :global(.tiptap) :global(h2) {
164
+ .tiptap h2 {
159
165
  font-size: 1.5em;
160
166
  }
161
167
 
162
- :global(.tiptap) :global(h3) {
168
+ .tiptap h3 {
163
169
  font-size: 1.25em;
164
170
  }
165
171
 
166
- :global(.tiptap) :global(h4) {
172
+ .tiptap h4 {
167
173
  font-size: 1.1em;
168
174
  }
169
175
 
170
- :global(.tiptap) :global(h5) {
176
+ .tiptap h5 {
171
177
  font-size: 1em;
172
178
  }
173
179
 
174
- :global(.tiptap) :global(h6) {
180
+ .tiptap h6 {
175
181
  font-size: 0.9em;
176
182
  }
177
183
 
178
184
  /* Horizontal rule: the reset sets height 0 with no border, making it invisible.
179
185
  Render it as a themed divider. */
180
- :global(.tiptap) :global(hr) {
186
+ .tiptap hr {
181
187
  height: 0;
182
188
  margin: 0.5em 0;
183
189
  border: none;
@@ -186,7 +192,7 @@
186
192
 
187
193
  /* Inline code: monospace from the reset, but no background — give it the
188
194
  code chip look. */
189
- :global(.tiptap) :global(code) {
195
+ .tiptap code {
190
196
  padding: 0.1em 0.3em;
191
197
  border-radius: var(--ant-border-radius-sm, 4px);
192
198
  background: var(--ant-color-fill-tertiary, rgba(0, 0, 0, 0.04));
@@ -195,7 +201,7 @@
195
201
 
196
202
  /* Code block: a padded, scrollable monospace box. The nested <code> resets to
197
203
  plain so it doesn't inherit the inline-code chip styling above. */
198
- :global(.tiptap) :global(pre) {
204
+ .tiptap pre {
199
205
  margin: 0 0 0.5em;
200
206
  padding: 8px 12px;
201
207
  border-radius: var(--ant-border-radius, 6px);
@@ -203,7 +209,7 @@
203
209
  overflow-x: auto;
204
210
  }
205
211
 
206
- :global(.tiptap) :global(pre) :global(code) {
212
+ .tiptap pre code {
207
213
  padding: 0;
208
214
  border-radius: 0;
209
215
  background: none;
@@ -213,12 +219,12 @@
213
219
  /* Highlight marks normally carry an inline background-color from the extension;
214
220
  this is a fallback so a color-less <mark> (e.g. pasted or seeded HTML) stays
215
221
  visible instead of blending into the text. */
216
- :global(.tiptap) :global(mark) {
222
+ .tiptap mark {
217
223
  background-color: var(--ant-color-warning-bg, rgba(255, 229, 143, 0.5));
218
224
  color: inherit;
219
225
  }
220
226
 
221
- :global(.tiptap) :global(p.is-editor-empty):first-child::before {
227
+ .tiptap p.is-editor-empty:first-child::before {
222
228
  color: var(--ant-color-text-placeholder, rgba(0, 0, 0, 0.25));
223
229
  content: attr(data-placeholder);
224
230
  float: left;
@@ -226,7 +232,7 @@
226
232
  pointer-events: none;
227
233
  }
228
234
 
229
- :global(.tiptap-popover) {
235
+ .tiptap-popover {
230
236
  background: rgba(0, 0, 0, 0.85);
231
237
  color: var(--ant-color-text-light-solid, #fff);
232
238
  border-radius: var(--ant-border-radius, 8px);
@@ -235,7 +241,7 @@
235
241
  display: flex;
236
242
  }
237
243
 
238
- :global(.tiptap-icon) {
244
+ .tiptap-icon {
239
245
  border-radius: var(--ant-border-radius-sm, 4px);
240
246
  display: flex;
241
247
  width: 16px;
@@ -245,16 +251,16 @@
245
251
  color: var(--ant-color-text-light-solid, #fff);
246
252
  }
247
253
 
248
- :global(.tiptap-icon.disabled) {
254
+ .tiptap-icon.disabled {
249
255
  pointer-events: none;
250
256
  }
251
257
 
252
- :global(.tiptap-icon):hover {
258
+ .tiptap-icon:hover {
253
259
  background: var(--ant-color-icon-hover, rgba(0, 0, 0, 0.75));
254
260
  color: var(--ant-color-text-light-solid, #fff);
255
261
  cursor: pointer;
256
262
  }
257
263
 
258
- :global(.tiptap-icon) + :global(.tiptap-icon) {
264
+ .tiptap-icon + .tiptap-icon {
259
265
  margin-left: 4px;
260
266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/blocks-tiptap",
3
- "version": "5.6.0",
3
+ "version": "6.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Lowdefy TipTap rich-text editor blocks.",
6
6
  "homepage": "https://lowdefy.com",
@@ -46,9 +46,10 @@
46
46
  "dist/*"
47
47
  ],
48
48
  "dependencies": {
49
- "@lowdefy/block-utils": "5.6.0",
50
- "@lowdefy/blocks-antd": "5.6.0",
51
- "@lowdefy/helpers": "5.6.0",
49
+ "@lowdefy/block-utils": "6.0.0",
50
+ "@lowdefy/blocks-antd": "6.0.0",
51
+ "@lowdefy/blocks-files": "6.0.0",
52
+ "@lowdefy/helpers": "6.0.0",
52
53
  "@tiptap/core": "2.27.2",
53
54
  "@tiptap/extension-bubble-menu": "2.27.2",
54
55
  "@tiptap/extension-file-handler": "2.27.2",
@@ -73,11 +74,11 @@
73
74
  "react-dom": ">=18"
74
75
  },
75
76
  "devDependencies": {
76
- "@lowdefy/block-dev-e2e": "5.6.0",
77
- "@lowdefy/e2e-utils": "5.6.0",
78
- "@playwright/test": "1.50.1",
79
- "@swc/cli": "0.8.0",
80
- "@swc/core": "1.15.18",
77
+ "@lowdefy/block-dev-e2e": "6.0.0",
78
+ "@lowdefy/e2e-utils": "6.0.0",
79
+ "@playwright/test": "1.59.1",
80
+ "@swc/cli": "0.8.1",
81
+ "@swc/core": "1.15.32",
81
82
  "copyfiles": "2.4.1"
82
83
  },
83
84
  "publishConfig": {
@@ -86,7 +87,7 @@
86
87
  "scripts": {
87
88
  "build": "swc src --out-dir dist --config-file ../../../../.swcrc --cli-config-file ../../../../.swc-cli.json && pnpm copyfiles",
88
89
  "clean": "rm -rf dist",
89
- "copyfiles": "copyfiles -u 1 \"./src/**/*\" dist -e \"./src/**/*.js\" -e \"./src/**/*.yaml\" -e \"./src/**/*.snap\"",
90
+ "copyfiles": "copyfiles -u 1 \"./src/**/*\" dist -e \"./src/**/*.js\" -e \"./src/**/*.snap\"",
90
91
  "e2e": "playwright test --config e2e/playwright.config.js",
91
92
  "e2e:ui": "playwright test --config e2e/playwright.config.js --ui"
92
93
  }
@@ -1,60 +0,0 @@
1
- @import '../utils/style.module.css';
2
-
3
- :global(.tableWrapper),
4
- :global(.tiptap-table) {
5
- display: block;
6
- overflow-x: auto;
7
- max-width: 80%;
8
- }
9
-
10
- :global(.tableWrapper) :global(table),
11
- :global(.tiptap-table) :global(table) {
12
- border-collapse: collapse;
13
- width: 100%;
14
- min-width: 75px;
15
- }
16
-
17
- :global(.tableWrapper) :global(colgroup) :global(col),
18
- :global(.tiptap-table) :global(colgroup) :global(col) {
19
- width: auto;
20
- }
21
-
22
- :global(.tableWrapper) :global(table),
23
- :global(.tableWrapper) :global(th),
24
- :global(.tableWrapper) :global(td),
25
- :global(.tiptap-table) :global(table),
26
- :global(.tiptap-table) :global(th),
27
- :global(.tiptap-table) :global(td) {
28
- border: 1px solid var(--ant-color-border);
29
- }
30
-
31
- :global(.tableWrapper) :global(th),
32
- :global(.tableWrapper) :global(td),
33
- :global(.tiptap-table) :global(th),
34
- :global(.tiptap-table) :global(td) {
35
- padding: 2px;
36
- text-align: left;
37
- }
38
-
39
- :global(.tableWrapper) :global(th),
40
- :global(.tiptap-table) :global(th) {
41
- background-color: var(--ant-color-fill-tertiary);
42
- font-weight: bold;
43
- }
44
-
45
- :global(.tableWrapper) :global(tr):nth-child(even),
46
- :global(.tiptap-table) :global(tr):nth-child(even) {
47
- background-color: var(--ant-color-fill-quaternary);
48
- }
49
-
50
- :global(.tableWrapper) :global(tr):hover,
51
- :global(.tiptap-table) :global(tr):hover {
52
- background-color: var(--ant-color-primary-bg);
53
- }
54
-
55
- :global(.tableWrapper) :global(td) :global(p),
56
- :global(.tableWrapper) :global(th) :global(p),
57
- :global(.tiptap-table) :global(td) :global(p),
58
- :global(.tiptap-table) :global(th) :global(p) {
59
- margin: 0;
60
- }
@@ -1,53 +0,0 @@
1
- /*
2
- Copyright 2020-2026 Lowdefy, Inc
3
-
4
- Licensed under the Apache License, Version 2.0 (the "License");
5
- you may not use this file except in compliance with the License.
6
- You may obtain a copy of the License at
7
-
8
- http://www.apache.org/licenses/LICENSE-2.0
9
-
10
- Unless required by applicable law or agreed to in writing, software
11
- distributed under the License is distributed on an "AS IS" BASIS,
12
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- See the License for the specific language governing permissions and
14
- limitations under the License.
15
- */ async function s3FileUpload({ file, methods }) {
16
- if (!file) {
17
- return;
18
- }
19
- const { lastModified, name, size, type, uid } = file;
20
- const s3PostPolicyResponse = await methods.triggerEvent({
21
- name: '__getS3PostPolicy',
22
- event: {
23
- file: {
24
- name,
25
- lastModified,
26
- size,
27
- type,
28
- uid
29
- }
30
- }
31
- });
32
- if (s3PostPolicyResponse.success !== true) {
33
- throw new Error('S3 post policy request error.');
34
- }
35
- const { url, fields } = s3PostPolicyResponse.responses.__getS3PostPolicy.response[0];
36
- const { bucket, key } = fields;
37
- file.bucket = bucket;
38
- file.key = key;
39
- const formData = new FormData();
40
- Object.keys(fields).forEach((field)=>{
41
- formData.append(field, fields[field]);
42
- });
43
- formData.append('file', file);
44
- await fetch(url, {
45
- method: 'POST',
46
- body: formData
47
- });
48
- // createPresignedPost returns the bucket endpoint with a trailing slash, so only
49
- // add a separator when it is missing to avoid a double slash before the key.
50
- file.url = url.endsWith('/') ? `${url}${key}` : `${url}/${key}`;
51
- return file.url;
52
- }
53
- export default s3FileUpload;