@lowdefy/blocks-tiptap 5.6.0 → 6.1.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.
@@ -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,24 @@
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
  }
267
+
268
+ /* TipTap v2 injected this rule into every page with an editor; v3 does not.
269
+ The mention suggestion and group-members popovers are tippy.js popups, which
270
+ fade in with it and only unmount a hidden popup once that fade-out ends, so
271
+ without it a popup hidden by an outside click would stay on screen. */
272
+ .tippy-box[data-animation='fade'][data-state='hidden'] {
273
+ opacity: 0;
274
+ }
@@ -0,0 +1,106 @@
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 { useEffect, useRef } from 'react';
16
+ import { useEditor } from '@tiptap/react';
17
+ import buildExtensions from './buildExtensions.js';
18
+ // The TipTap editor of the TiptapInput and TiptapMentionInput blocks: creates it, registers the
19
+ // block's clear, setContent and focus methods, and keeps it in sync with the block's value,
20
+ // disabled state and placeholder.
21
+ function useTiptapEditor({ disabled, editorProps = {}, emit, insertImage, mentionExtension, methods, properties, uploadEnabled, value }) {
22
+ // The Placeholder extension is created once, so it reads the current text from a ref.
23
+ const placeholderRef = useRef();
24
+ placeholderRef.current = properties.placeholder ?? '';
25
+ const extensions = buildExtensions({
26
+ properties,
27
+ getPlaceholder: ()=>placeholderRef.current,
28
+ insertImage,
29
+ mentionExtension,
30
+ uploadEnabled
31
+ });
32
+ const editor = useEditor({
33
+ editorProps,
34
+ extensions,
35
+ content: value?.html || '',
36
+ editable: ()=>!disabled,
37
+ onUpdate ({ editor }) {
38
+ // User-driven change. Emit the full derived value to Lowdefy.
39
+ emit(editor);
40
+ methods.triggerEvent({
41
+ name: 'onChange'
42
+ });
43
+ }
44
+ });
45
+ // Register methods as soon as the editor is available.
46
+ useEffect(()=>{
47
+ if (!editor) return;
48
+ // emitUpdate false: the methods emit the value themselves and must not trigger onChange.
49
+ methods.registerMethod('clear', ()=>{
50
+ editor.commands.clearContent(false);
51
+ emit(editor);
52
+ });
53
+ methods.registerMethod('setContent', (args)=>{
54
+ editor.commands.setContent(args?.html ?? '', {
55
+ emitUpdate: false
56
+ });
57
+ emit(editor);
58
+ });
59
+ methods.registerMethod('focus', ()=>{
60
+ editor.commands.focus();
61
+ });
62
+ }, [
63
+ editor
64
+ ]);
65
+ // External value.html → editor sync. One-way only: we read value.html and
66
+ // push it into the editor with emitUpdate false so tiptap's onUpdate
67
+ // does not fire. No write-back via emit() — that would race with concurrent
68
+ // SetState calls (child effects fire before parent effects, so a sibling's
69
+ // onMount SetState could be overwritten by our derived emit). Derived
70
+ // fields (text/markdown/fileList, and mentions for TiptapMentionInput) are
71
+ // populated on user interaction via onUpdate; downstream consumers of seeded
72
+ // content should read value.html directly, or include the fields they need
73
+ // in their SetState payload.
74
+ useEffect(()=>{
75
+ if (!editor) return;
76
+ const next = value?.html ?? '';
77
+ const current = editor.getHTML();
78
+ if (next !== current) {
79
+ editor.commands.setContent(next, {
80
+ emitUpdate: false
81
+ });
82
+ }
83
+ }, [
84
+ value?.html,
85
+ editor
86
+ ]);
87
+ useEffect(()=>{
88
+ if (!editor) return;
89
+ editor.setOptions({
90
+ editable: !disabled
91
+ });
92
+ }, [
93
+ editor,
94
+ disabled
95
+ ]);
96
+ // Redraw the placeholder when its text changes.
97
+ useEffect(()=>{
98
+ if (!editor) return;
99
+ editor.view.dispatch(editor.state.tr);
100
+ }, [
101
+ editor,
102
+ properties.placeholder
103
+ ]);
104
+ return editor;
105
+ }
106
+ export default useTiptapEditor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/blocks-tiptap",
3
- "version": "5.6.0",
3
+ "version": "6.1.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Lowdefy TipTap rich-text editor blocks.",
6
6
  "homepage": "https://lowdefy.com",
@@ -46,24 +46,23 @@
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",
52
- "@tiptap/core": "2.27.2",
53
- "@tiptap/extension-bubble-menu": "2.27.2",
54
- "@tiptap/extension-file-handler": "2.27.2",
55
- "@tiptap/extension-highlight": "2.27.2",
56
- "@tiptap/extension-image": "2.27.2",
57
- "@tiptap/extension-link": "2.27.2",
58
- "@tiptap/extension-mention": "2.27.2",
59
- "@tiptap/extension-placeholder": "2.27.2",
60
- "@tiptap/extension-table": "2.27.2",
61
- "@tiptap/extension-table-cell": "2.27.2",
62
- "@tiptap/extension-table-header": "2.27.2",
63
- "@tiptap/extension-table-row": "2.27.2",
64
- "@tiptap/pm": "2.27.2",
65
- "@tiptap/react": "2.27.2",
66
- "@tiptap/starter-kit": "2.27.2",
49
+ "@lowdefy/block-utils": "6.1.0",
50
+ "@lowdefy/blocks-antd": "6.1.0",
51
+ "@lowdefy/blocks-files": "6.1.0",
52
+ "@lowdefy/helpers": "6.1.0",
53
+ "@tiptap/core": "3.31.3",
54
+ "@tiptap/extension-bubble-menu": "3.31.3",
55
+ "@tiptap/extension-file-handler": "3.31.3",
56
+ "@tiptap/extension-highlight": "3.31.3",
57
+ "@tiptap/extension-image": "3.31.3",
58
+ "@tiptap/extension-link": "3.31.3",
59
+ "@tiptap/extension-mention": "3.31.3",
60
+ "@tiptap/extension-table": "3.31.3",
61
+ "@tiptap/extensions": "3.31.3",
62
+ "@tiptap/pm": "3.31.3",
63
+ "@tiptap/react": "3.31.3",
64
+ "@tiptap/starter-kit": "3.31.3",
65
+ "@tiptap/suggestion": "3.31.3",
67
66
  "react-icons": "5.6.0",
68
67
  "tippy.js": "6.3.7",
69
68
  "turndown": "7.2.0"
@@ -73,11 +72,11 @@
73
72
  "react-dom": ">=18"
74
73
  },
75
74
  "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",
75
+ "@lowdefy/block-dev-e2e": "6.1.0",
76
+ "@lowdefy/e2e-utils": "6.1.0",
77
+ "@playwright/test": "1.59.1",
78
+ "@swc/cli": "0.8.1",
79
+ "@swc/core": "1.15.32",
81
80
  "copyfiles": "2.4.1"
82
81
  },
83
82
  "publishConfig": {
@@ -86,7 +85,7 @@
86
85
  "scripts": {
87
86
  "build": "swc src --out-dir dist --config-file ../../../../.swcrc --cli-config-file ../../../../.swc-cli.json && pnpm copyfiles",
88
87
  "clean": "rm -rf dist",
89
- "copyfiles": "copyfiles -u 1 \"./src/**/*\" dist -e \"./src/**/*.js\" -e \"./src/**/*.yaml\" -e \"./src/**/*.snap\"",
88
+ "copyfiles": "copyfiles -u 1 \"./src/**/*\" dist -e \"./src/**/*.js\" -e \"./src/**/*.snap\"",
90
89
  "e2e": "playwright test --config e2e/playwright.config.js",
91
90
  "e2e:ui": "playwright test --config e2e/playwright.config.js --ui"
92
91
  }
@@ -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;