@antscorp/antsomi-ui 1.3.7-beta.74 → 1.3.7-beta.75
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.
- package/dist/index.js +319 -319
- package/es/components/organism/TicketEditor/Content.js +74 -35
- package/es/components/organism/TicketEditor/util.d.ts +1 -0
- package/es/components/organism/TicketEditor/util.js +1 -0
- package/es/components/organism/TicketEditorV2/Content.js +62 -28
- package/es/components/organism/TicketEditorV2/util.d.ts +1 -0
- package/es/components/organism/TicketEditorV2/util.js +1 -0
- package/es/utils/uploadEmbeddedImages.d.ts +7 -0
- package/es/utils/uploadEmbeddedImages.js +65 -0
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ import { Spin, Tooltip, Icon, Button, EditableName, SelectAccount, QuillEditor,
|
|
|
10
10
|
import Service from './Service';
|
|
11
11
|
import { MESSAGE_TYPE, TICKET_CUSTOM_MESSAGE_KEY } from './constant';
|
|
12
12
|
import { WrapperContent, WrapperContentInput, WrapperEditor, WrapperIconEditor, WrapperInputFile, WrapperLable, WrapperLeftContent, WrapperLinkItemFiles, WrapperMessageContent, WrapperRightContent, WrapperTextEdit, WrapperTextInput, DrawerHeader, } from './styled';
|
|
13
|
-
import { formatAccountId, formatDatarender, formatParams, handleValidateContent, postCustomEvent, } from './util';
|
|
13
|
+
import { formatAccountId, formatDatarender, formatParams, handleValidateContent, postCustomEvent, uploadEmbeddedImages, } from './util';
|
|
14
14
|
import { TCallBack } from '../../molecules/QuillEditor/QuillEditor';
|
|
15
15
|
import { isBoolean } from 'lodash';
|
|
16
16
|
const initValueInput = {
|
|
@@ -49,6 +49,7 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
49
49
|
isError: false,
|
|
50
50
|
message: '',
|
|
51
51
|
});
|
|
52
|
+
const [embeddedImageError, setEmbeddedImageError] = useState('');
|
|
52
53
|
const isUpdate = action === 'edit' && ticketId;
|
|
53
54
|
const valueInputRef = useRef(valueInput);
|
|
54
55
|
valueInputRef.current = valueInput;
|
|
@@ -98,19 +99,53 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
98
99
|
setIsLoading(false);
|
|
99
100
|
});
|
|
100
101
|
};
|
|
101
|
-
const
|
|
102
|
+
const uploadImagesBeforeSubmit = async (message) => {
|
|
103
|
+
setIsLoadingUpload(true);
|
|
104
|
+
setEmbeddedImageError('');
|
|
105
|
+
try {
|
|
106
|
+
const uploadedMessage = await uploadEmbeddedImages(message, Service.tickets.callApi.uploadImg({
|
|
107
|
+
domain: domainUpload,
|
|
108
|
+
token,
|
|
109
|
+
userId,
|
|
110
|
+
}));
|
|
111
|
+
if (uploadedMessage !== message) {
|
|
112
|
+
setTextValue(uploadedMessage);
|
|
113
|
+
updateValueInput({ message: uploadedMessage });
|
|
114
|
+
}
|
|
115
|
+
return uploadedMessage;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
setEmbeddedImageError(error instanceof Error && error.message === 'FILE_TOO_LARGE'
|
|
119
|
+
? '*Maximum upload file size: 10MB'
|
|
120
|
+
: '*Image upload failed. Please try again.');
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
setIsLoadingUpload(false);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const handleSubmit = async () => {
|
|
102
128
|
const { title, ownerId, message } = valueInputRef.current;
|
|
103
129
|
if (!title || !ownerId.length || !message) {
|
|
104
130
|
setIsEmptyField(true);
|
|
105
131
|
}
|
|
106
132
|
else {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
try {
|
|
134
|
+
const uploadedMessage = await uploadImagesBeforeSubmit(message);
|
|
135
|
+
const params = formatParams({
|
|
136
|
+
...valueInputRef.current,
|
|
137
|
+
message: uploadedMessage,
|
|
138
|
+
});
|
|
139
|
+
const submitterEmail = listUsers?.find(user => user.userId === params.ownerId)?.email;
|
|
140
|
+
fetchCreateTicket({
|
|
141
|
+
...params,
|
|
142
|
+
submitterId: Number(userId),
|
|
143
|
+
submitterEmail,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
// The upload error is shown below the editor.
|
|
148
|
+
}
|
|
114
149
|
}
|
|
115
150
|
};
|
|
116
151
|
const handleOnchangeTitle = title => {
|
|
@@ -235,6 +270,7 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
235
270
|
}
|
|
236
271
|
}, [isUpdate, openDrawer]);
|
|
237
272
|
const handleEditorChange = content => {
|
|
273
|
+
setEmbeddedImageError('');
|
|
238
274
|
setTextValue(content);
|
|
239
275
|
updateValueInput({ message: content });
|
|
240
276
|
// setIsLoadingUpload(content.includes('<img src="data:image/png;base64'));
|
|
@@ -248,32 +284,34 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
248
284
|
}
|
|
249
285
|
fetchUpdateFollwers(params);
|
|
250
286
|
};
|
|
251
|
-
const handleUpdateComment = () => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
params.followers = formatAccountId(valueInput?.followers);
|
|
264
|
-
}
|
|
265
|
-
if (valueInput?.files?.length > 0) {
|
|
266
|
-
params.properties = {
|
|
267
|
-
attachments: valueInput?.files,
|
|
287
|
+
const handleUpdateComment = async () => {
|
|
288
|
+
const currentValueInput = valueInputRef.current;
|
|
289
|
+
try {
|
|
290
|
+
const uploadedMessage = await uploadImagesBeforeSubmit(currentValueInput.message);
|
|
291
|
+
const params = {
|
|
292
|
+
followers: [],
|
|
293
|
+
submitterId: userId,
|
|
294
|
+
ownerId: Number(currentValueInput?.ownerId[0]?.userId),
|
|
295
|
+
submitterEmail: listUsers?.find(user => +user.userId === +userId)?.email,
|
|
296
|
+
properties: {},
|
|
297
|
+
message: uploadedMessage,
|
|
298
|
+
attachmentsZendesk: currentValueInput.fileZendesk.map(file => file.token),
|
|
268
299
|
};
|
|
300
|
+
if (currentValueInput?.followers?.length) {
|
|
301
|
+
params.followers = formatAccountId(currentValueInput.followers);
|
|
302
|
+
}
|
|
303
|
+
if (currentValueInput?.files?.length > 0) {
|
|
304
|
+
params.properties = {
|
|
305
|
+
attachments: currentValueInput.files,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
setIsLoading(true);
|
|
309
|
+
fetchUpdateComment(params);
|
|
310
|
+
handleUpdateFollowers(currentValueInput?.followers?.map((fol) => fol.userId));
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
// The upload error is shown below the editor.
|
|
269
314
|
}
|
|
270
|
-
params.submitterId = userId;
|
|
271
|
-
params.submitterEmail = listUsers?.find(user => +user.userId === +userId)?.email;
|
|
272
|
-
params.ownerId = Number(valueInput?.ownerId[0]?.userId);
|
|
273
|
-
params.message = valueInput?.message;
|
|
274
|
-
// params.attachmentsZendesk = valueInput.fileZendesk.map(file => file.token);
|
|
275
|
-
fetchUpdateComment(params);
|
|
276
|
-
handleUpdateFollowers(valueInput?.followers?.map((fol) => fol.userId));
|
|
277
315
|
};
|
|
278
316
|
const handleUploadFile = async (file) => {
|
|
279
317
|
setIsLoading(true);
|
|
@@ -398,7 +436,8 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
398
436
|
isLoadingDetails ||
|
|
399
437
|
isLoadingDataSouce ||
|
|
400
438
|
isLoadingFollower ||
|
|
401
|
-
isLoadingSelectedAccount
|
|
439
|
+
isLoadingSelectedAccount ||
|
|
440
|
+
isLoadingUpload, children: [_jsxs(DrawerHeader, { children: [_jsx(EditableName, { value: valueInput.title, onChange: handleOnchangeTitle, readonly: isUpdate, error: isEmptyField && (!valueInput.title || !valueInput.title.trim())
|
|
402
441
|
? "Title can't be empty"
|
|
403
442
|
: '' }), _jsx("div", { id: "header-journey-right", className: "right-content", children: !isUpdate && (_jsx(Button, { disabled: isLoadingUpload, type: "primary", onClick: handleSubmit, children: "Save" })) })] }), _jsxs(WrapperContent, { style: { height: '100%' }, children: [_jsxs(WrapperLeftContent, { children: [_jsxs(WrapperContentInput, { style: { alignItems: 'flex-start' }, children: [_jsxs(WrapperLable, { children: ["Owner", _jsx("span", { style: { color: '#ff0000' }, children: "*" })] }), _jsxs("div", { children: [_jsx(SelectAccount, { type: "default", initData: (valueInput?.ownerId || []).map((item) => item.userId), nameKey: "userName", userIdKey: "userId", users: listUsers, isViewMode: isUpdate || !isAllAccount || accountEdit !== 4, onChange: (_, users) => updateValueInput({ ownerId: users }), onlyOne: true }), isEmptyField && !valueInput?.ownerId?.length && (_jsx("div", { className: "error-message", children: "*This field can't be empty" }))] })] }), _jsxs(WrapperContentInput, { style: { marginTop: '15px', alignItems: 'flex-start' }, children: [_jsx(WrapperLable, { children: "Follower(s)" }), _jsx(SelectAccount, { type: "default", initData: [
|
|
404
443
|
...accountManage,
|
|
@@ -422,7 +461,7 @@ const Content = ({ portalId, token, action, ticketId, listUsers, domainTicket, d
|
|
|
422
461
|
background: `${!handleValidateContent(textValue) || isLoadingUpload
|
|
423
462
|
? '#ccc'
|
|
424
463
|
: '#1f5fac'}`,
|
|
425
|
-
}, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
|
|
464
|
+
}, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), embeddedImageError ? (_jsx("div", { className: "error-message", children: embeddedImageError })) : errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
|
|
426
465
|
!valueInput?.message && (_jsx("div", { className: "error-message", children: "*This field can't be empty" })))] }), isUpdate && (_jsx(Spin, { spinning: isLoadingComment, children: _jsx(WrapperMessageContent, { children: listComment?.map(comment => (_jsx(MessageComponent, { toUser: comment?.toUser, fromUser: comment?.fromUser, followers: comment?.followers, message: comment?.message, date: comment?.createdDate, attachments: comment?.attachments, timeZone: timeZone, submitterEmail: comment?.submitterEmail }, comment?.id))) }) }))] })] })] })] }));
|
|
427
466
|
};
|
|
428
467
|
export default Content;
|
|
@@ -28,6 +28,7 @@ export declare function getEntriesV2(res: any, def: any): {
|
|
|
28
28
|
messageAPI?: undefined;
|
|
29
29
|
};
|
|
30
30
|
export declare const formatAccountId: (data: any) => any;
|
|
31
|
+
export { dataUrlToFile, uploadEmbeddedImages } from '../../../utils/uploadEmbeddedImages';
|
|
31
32
|
export declare const formatParams: (data: any) => {
|
|
32
33
|
title: string;
|
|
33
34
|
ownerId: undefined;
|
|
@@ -294,6 +294,7 @@ export const formatAccountId = data => {
|
|
|
294
294
|
const results = data?.map(item => item?.userId);
|
|
295
295
|
return results;
|
|
296
296
|
};
|
|
297
|
+
export { dataUrlToFile, uploadEmbeddedImages } from '../../../utils/uploadEmbeddedImages';
|
|
297
298
|
export const formatParams = data => {
|
|
298
299
|
const params = {
|
|
299
300
|
title: '',
|
|
@@ -8,7 +8,7 @@ import MessageComponent from './components/MessageComponent';
|
|
|
8
8
|
import { Spin, Tooltip, Icon, Button, SelectAccount, QuillEditor, } from '@antscorp/antsomi-ui/es/components';
|
|
9
9
|
import Service from './Service';
|
|
10
10
|
import { WrapperContent, WrapperContentInput, WrapperEditor, WrapperIconEditor, WrapperInputFile, WrapperLable, WrapperLeftContent, WrapperLinkItemFiles, WrapperMessageContent, WrapperRightContent, WrapperTextEdit, WrapperTextInput, } from './styled';
|
|
11
|
-
import { formatDatarender, handleValidateContent } from './util';
|
|
11
|
+
import { formatDatarender, handleValidateContent, uploadEmbeddedImages, } from './util';
|
|
12
12
|
import { get, keyBy } from 'lodash';
|
|
13
13
|
import { TCallBack } from '../../molecules/QuillEditor/QuillEditor';
|
|
14
14
|
const initValueInput = {
|
|
@@ -44,6 +44,7 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
44
44
|
isError: false,
|
|
45
45
|
message: '',
|
|
46
46
|
});
|
|
47
|
+
const [embeddedImageError, setEmbeddedImageError] = useState('');
|
|
47
48
|
const isUpdate = action === 'edit' && ticketId;
|
|
48
49
|
const valueInputRef = useRef(valueInput);
|
|
49
50
|
valueInputRef.current = valueInput;
|
|
@@ -140,6 +141,7 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
140
141
|
}, 1000);
|
|
141
142
|
}, []);
|
|
142
143
|
const handleEditorChange = content => {
|
|
144
|
+
setEmbeddedImageError('');
|
|
143
145
|
setTextValue(content);
|
|
144
146
|
updateValueInput({ message: content });
|
|
145
147
|
// setIsLoadingUpload(content.includes('<img src="data:image/png;base64'));
|
|
@@ -151,33 +153,61 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
151
153
|
params.followers = arrFolowers;
|
|
152
154
|
fetchUpdateFollwers(params);
|
|
153
155
|
};
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
156
|
+
const uploadImagesBeforeSubmit = async (message) => {
|
|
157
|
+
setIsLoadingUpload(true);
|
|
158
|
+
setEmbeddedImageError('');
|
|
159
|
+
try {
|
|
160
|
+
const uploadedMessage = await uploadEmbeddedImages(message, Service.tickets.callApi.uploadFile({
|
|
161
|
+
domain: domainUpload,
|
|
162
|
+
}));
|
|
163
|
+
if (uploadedMessage !== message) {
|
|
164
|
+
setTextValue(uploadedMessage);
|
|
165
|
+
updateValueInput({ message: uploadedMessage });
|
|
166
|
+
}
|
|
167
|
+
return uploadedMessage;
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
setEmbeddedImageError(error instanceof Error && error.message === 'FILE_TOO_LARGE'
|
|
171
|
+
? '*Maximum upload file size: 10MB'
|
|
172
|
+
: '*Image upload failed. Please try again.');
|
|
173
|
+
throw error;
|
|
169
174
|
}
|
|
170
|
-
|
|
171
|
-
|
|
175
|
+
finally {
|
|
176
|
+
setIsLoadingUpload(false);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const handleUpdateComment = async () => {
|
|
180
|
+
const currentValueInput = valueInputRef.current;
|
|
181
|
+
try {
|
|
182
|
+
const uploadedMessage = await uploadImagesBeforeSubmit(currentValueInput.message);
|
|
183
|
+
const params = {
|
|
184
|
+
followers: [],
|
|
185
|
+
submitterId: null,
|
|
186
|
+
ownerId: null,
|
|
187
|
+
message: uploadedMessage,
|
|
188
|
+
properties: {
|
|
189
|
+
attachments: [],
|
|
190
|
+
},
|
|
191
|
+
submitterEmail: '',
|
|
192
|
+
attachmentsZendesk: [],
|
|
193
|
+
};
|
|
194
|
+
if (currentValueInput?.followers?.length) {
|
|
195
|
+
params.followers = currentValueInput.followers;
|
|
196
|
+
}
|
|
197
|
+
if (currentValueInput?.files?.length > 0) {
|
|
198
|
+
params.properties.attachments = currentValueInput.files;
|
|
199
|
+
}
|
|
200
|
+
const mapUsers = keyBy(listUsers, 'email');
|
|
201
|
+
params.submitterId = +mapUsers[email]?.userId || 0;
|
|
202
|
+
params.submitterEmail = email;
|
|
203
|
+
params.attachmentsZendesk = currentValueInput.fileZendesk?.map(file => file?.token);
|
|
204
|
+
setIsLoading(true);
|
|
205
|
+
fetchUpdateComment(params);
|
|
206
|
+
handleUpdateFollowers(currentValueInput.followers?.map((fol) => fol.userId));
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
// The upload error is shown below the editor.
|
|
172
210
|
}
|
|
173
|
-
const mapUsers = keyBy(listUsers, 'email');
|
|
174
|
-
params.submitterId = +mapUsers[email]?.userId || 0;
|
|
175
|
-
params.submitterEmail = email;
|
|
176
|
-
params.ownerId = null;
|
|
177
|
-
params.message = valueInput?.message;
|
|
178
|
-
// params.attachmentsZendesk = valueInput?.fileZendesk?.map(file => file?.token);
|
|
179
|
-
fetchUpdateComment(params);
|
|
180
|
-
handleUpdateFollowers(valueInput?.followers?.map((fol) => fol.userId));
|
|
181
211
|
};
|
|
182
212
|
const onChangeFollowers = arrFolowers => {
|
|
183
213
|
updateValueInput({ followers: arrFolowers });
|
|
@@ -312,7 +342,11 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
312
342
|
break;
|
|
313
343
|
}
|
|
314
344
|
};
|
|
315
|
-
return (_jsxs("div", { style: { height: '100%' }, children: [_jsxs(Helmet, { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("title", { children: browserTitle })] }), _jsx(Spin, { style: { height: '100vh' }, spinning: isLoading ||
|
|
345
|
+
return (_jsxs("div", { style: { height: '100%' }, children: [_jsxs(Helmet, { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("title", { children: browserTitle })] }), _jsx(Spin, { style: { height: '100vh' }, spinning: isLoading ||
|
|
346
|
+
isLoadingDetails ||
|
|
347
|
+
isLoadingDataSouce ||
|
|
348
|
+
isLoadingFollower ||
|
|
349
|
+
isLoadingUpload, children: _jsxs(WrapperContent, { style: { height: '100%' }, children: [_jsxs(WrapperLeftContent, { children: [_jsxs(WrapperContentInput, { style: { alignItems: 'flex-start' }, children: [_jsxs(WrapperLable, { children: ["Owner", _jsx("span", { style: { color: '#ff0000' }, children: "*" })] }), _jsx("div", { children: ownerEmail })] }), _jsxs(WrapperContentInput, { style: { marginTop: '15px', alignItems: 'flex-start' }, children: [_jsx(WrapperLable, { children: "Follower(s)" }), _jsx(SelectAccount, { type: "default", initData: ticketDetails?.followers || [], nameKey: "userName", userIdKey: "userId", users: listUsers, onChange: (_, followers) => onChangeFollowers(followers) })] }), dataSelects?.map((data) => _jsx(_Fragment, { children: renderOptions(data) })), _jsxs(WrapperContentInput, { style: { marginTop: '15px' }, children: [_jsx(WrapperLable, { htmlFor: "referenceUrl", children: "Reference URL" }), isUpdate ? (ticketDetails.referenceUrl ? (_jsx(Tooltip, { title: ticketDetails.referenceUrl, placement: "top", children: _jsx(WrapperTextEdit, { color: "#005fb8", href: ticketDetails.referenceUrl, target: "_blank", children: ticketDetails.referenceUrl }) })) : (_jsx(WrapperTextEdit, { color: "#005fb8", href: ticketDetails.referenceUrl, target: "_blank", children: ticketDetails.referenceUrl }))) : (_jsx(WrapperTextInput, { placeholder: "Reference URL", id: "referenceUrl",
|
|
316
350
|
// width="300px"
|
|
317
351
|
onChange: handleOnchangeInput, name: "referenceUrl", value: valueInput.referenceUrl }))] })] }), _jsxs(WrapperRightContent, { children: [_jsxs(WrapperEditor, { children: [_jsxs(_Fragment, { children: [_jsx(QuillEditor, { value: textValue, uploadService: Service.tickets.callApi.uploadFile({
|
|
318
352
|
domain: domainUpload,
|
|
@@ -320,7 +354,7 @@ const Content = ({ apiKey, domain, portalId, token, action, ticketId, listUsers,
|
|
|
320
354
|
background: `${!handleValidateContent(textValue) || isLoadingUpload
|
|
321
355
|
? '#ccc'
|
|
322
356
|
: '#1f5fac'}`,
|
|
323
|
-
}, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
|
|
357
|
+
}, onClick: handleUpdateComment, children: "Reply" }))] })] })] }), embeddedImageError ? (_jsx("div", { className: "error-message", children: embeddedImageError })) : errFile.isError ? (_jsx("div", { className: "error-message", children: errFile.message })) : (isEmptyField &&
|
|
324
358
|
!valueInput?.message && (_jsx("div", { className: "error-message", children: "* This field can't be empty" })))] }), isUpdate && (_jsx(Spin, { spinning: isLoadingComment, children: _jsx(WrapperMessageContent, { children: listComment?.map(comment => (_jsx(MessageComponent, { toUser: comment?.toUser, fromUser: comment?.fromUser, followers: comment?.followers,
|
|
325
359
|
// mailFollower={comment?.mailFollower}
|
|
326
360
|
message: comment?.message, date: comment?.createdDate, attachments: comment?.attachments, timeZone: timeZone, submitterEmail: comment?.submitterEmail }, comment?.id))) }) }))] })] }) })] }));
|
|
@@ -28,6 +28,7 @@ export declare function getEntriesV2(res: any, def: any): {
|
|
|
28
28
|
messageAPI?: undefined;
|
|
29
29
|
};
|
|
30
30
|
export declare const formatAccountId: (data: any) => any;
|
|
31
|
+
export { dataUrlToFile, uploadEmbeddedImages } from '../../../utils/uploadEmbeddedImages';
|
|
31
32
|
export declare const formatParams: (data: any) => {
|
|
32
33
|
title: string;
|
|
33
34
|
ownerId: undefined;
|
|
@@ -287,6 +287,7 @@ export const formatAccountId = data => {
|
|
|
287
287
|
const results = data?.map(item => item?.userId);
|
|
288
288
|
return results;
|
|
289
289
|
};
|
|
290
|
+
export { dataUrlToFile, uploadEmbeddedImages } from '../../../utils/uploadEmbeddedImages';
|
|
290
291
|
export const formatParams = data => {
|
|
291
292
|
const params = {
|
|
292
293
|
title: '',
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type TUploadEmbeddedImages = (params: {
|
|
2
|
+
files: File[];
|
|
3
|
+
mode: 'file';
|
|
4
|
+
}) => Promise<unknown>;
|
|
5
|
+
export declare const dataUrlToFile: (dataUrl: string, index: number) => File;
|
|
6
|
+
export declare const uploadEmbeddedImages: (message: string, uploadService: TUploadEmbeddedImages) => Promise<string>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const DATA_IMAGE_PREFIX = /^\s*data:image\//i;
|
|
2
|
+
const DATA_IMAGE_URL = /^data:(image\/[^;,]+);base64,(.+)$/i;
|
|
3
|
+
const getUploadedImageUrl = (uploadedImage) => {
|
|
4
|
+
if (!uploadedImage || typeof uploadedImage !== 'object' || !('url' in uploadedImage)) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
const { url } = uploadedImage;
|
|
8
|
+
if (typeof url !== 'string' || !url.trim() || /^\s*data:/i.test(url)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const parsedUrl = new URL(url.trim());
|
|
13
|
+
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'
|
|
14
|
+
? parsedUrl.href
|
|
15
|
+
: null;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export const dataUrlToFile = (dataUrl, index) => {
|
|
22
|
+
const matches = dataUrl.trim().match(DATA_IMAGE_URL);
|
|
23
|
+
if (!matches) {
|
|
24
|
+
throw new Error('INVALID_IMAGE_DATA');
|
|
25
|
+
}
|
|
26
|
+
const [, rawMimeType, base64] = matches;
|
|
27
|
+
const mimeType = rawMimeType.toLowerCase();
|
|
28
|
+
const padding = base64.endsWith('==') ? 2 : base64.endsWith('=') ? 1 : 0;
|
|
29
|
+
const fileSize = (base64.length * 3) / 4 - padding;
|
|
30
|
+
if (fileSize > 10 * 1024 * 1024) {
|
|
31
|
+
throw new Error('FILE_TOO_LARGE');
|
|
32
|
+
}
|
|
33
|
+
const binary = atob(base64);
|
|
34
|
+
const bytes = new Uint8Array(binary.length);
|
|
35
|
+
for (let byteIndex = 0; byteIndex < binary.length; byteIndex += 1) {
|
|
36
|
+
bytes[byteIndex] = binary.charCodeAt(byteIndex);
|
|
37
|
+
}
|
|
38
|
+
const extension = mimeType.split('/')[1].replace('jpeg', 'jpg');
|
|
39
|
+
return new File([bytes], `ticket-image-${index + 1}.${extension}`, { type: mimeType });
|
|
40
|
+
};
|
|
41
|
+
export const uploadEmbeddedImages = async (message, uploadService) => {
|
|
42
|
+
const container = document.createElement('div');
|
|
43
|
+
container.innerHTML = message;
|
|
44
|
+
const images = Array.from(container.querySelectorAll('img')).filter(image => DATA_IMAGE_PREFIX.test(image.getAttribute('src') || ''));
|
|
45
|
+
if (!images.length) {
|
|
46
|
+
return message;
|
|
47
|
+
}
|
|
48
|
+
const files = images.map((image, index) => dataUrlToFile(image.getAttribute('src') || '', index));
|
|
49
|
+
const response = await uploadService({ files, mode: 'file' });
|
|
50
|
+
const uploadedImages = response && typeof response === 'object' && 'data' in response && Array.isArray(response.data)
|
|
51
|
+
? response.data
|
|
52
|
+
: [];
|
|
53
|
+
const uploadedUrls = uploadedImages.map(getUploadedImageUrl);
|
|
54
|
+
if (uploadedUrls.length !== images.length || uploadedUrls.some(uploadedUrl => !uploadedUrl)) {
|
|
55
|
+
throw new Error('UPLOAD_FAILED');
|
|
56
|
+
}
|
|
57
|
+
images.forEach((image, index) => {
|
|
58
|
+
image.setAttribute('src', uploadedUrls[index]);
|
|
59
|
+
});
|
|
60
|
+
const hasEmbeddedImage = Array.from(container.querySelectorAll('img')).some(image => DATA_IMAGE_PREFIX.test(image.getAttribute('src') || ''));
|
|
61
|
+
if (hasEmbeddedImage) {
|
|
62
|
+
throw new Error('UPLOAD_FAILED');
|
|
63
|
+
}
|
|
64
|
+
return container.innerHTML;
|
|
65
|
+
};
|