@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.
@@ -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 handleSubmit = () => {
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
- const params = formatParams(valueInputRef.current);
108
- const submitterEmail = listUsers?.find(user => user.userId === params.ownerId)?.email;
109
- fetchCreateTicket({
110
- ...params,
111
- submitterId: Number(userId),
112
- submitterEmail,
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
- setIsLoading(true);
253
- const params = {
254
- followers: [],
255
- submitterId: null,
256
- ownerId: null,
257
- submitterEmail: '',
258
- properties: {},
259
- message: '',
260
- attachmentsZendesk: [],
261
- };
262
- if (valueInput?.followers?.length) {
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, children: [_jsxs(DrawerHeader, { children: [_jsx(EditableName, { value: valueInput.title, onChange: handleOnchangeTitle, readonly: isUpdate, error: isEmptyField && (!valueInput.title || !valueInput.title.trim())
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 handleUpdateComment = () => {
155
- setIsLoading(true);
156
- const params = {
157
- followers: [],
158
- submitterId: null,
159
- ownerId: null,
160
- message: '',
161
- properties: {
162
- attachments: [],
163
- },
164
- submitterEmail: '',
165
- attachmentsZendesk: [],
166
- };
167
- if (valueInput?.followers?.length) {
168
- params.followers = valueInput?.followers;
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
- if (valueInput?.files?.length > 0) {
171
- params.properties.attachments = valueInput?.files;
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 || isLoadingDetails || isLoadingDataSouce || isLoadingFollower, 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",
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antscorp/antsomi-ui",
3
- "version": "1.3.7-beta.74",
3
+ "version": "1.3.7-beta.75",
4
4
  "description": "An enterprise-class UI design language and React UI library.",
5
5
  "sideEffects": [
6
6
  "dist/*",