@amityco/react-native-social-uikit 4.0.0-1fa7ca8.0 → 4.0.0-3dd1feab.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.
Files changed (46) hide show
  1. package/lib/commonjs/providers/file-provider.js +38 -19
  2. package/lib/commonjs/providers/file-provider.js.map +1 -1
  3. package/lib/commonjs/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.js +38 -2
  4. package/lib/commonjs/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.js.map +1 -1
  5. package/lib/commonjs/v4/component/LoadingImage/index.js +21 -22
  6. package/lib/commonjs/v4/component/LoadingImage/index.js.map +1 -1
  7. package/lib/commonjs/v4/component/LoadingImage/styles.js +19 -2
  8. package/lib/commonjs/v4/component/LoadingImage/styles.js.map +1 -1
  9. package/lib/commonjs/v4/component/LoadingVideo/index.js +11 -3
  10. package/lib/commonjs/v4/component/LoadingVideo/index.js.map +1 -1
  11. package/lib/commonjs/v4/component/PreviewLink/LinkPreview.js +3 -3
  12. package/lib/commonjs/v4/component/PreviewLink/LinkPreview.js.map +1 -1
  13. package/lib/commonjs/v4/component/PreviewLink/utils.js +9 -73
  14. package/lib/commonjs/v4/component/PreviewLink/utils.js.map +1 -1
  15. package/lib/module/providers/file-provider.js +38 -19
  16. package/lib/module/providers/file-provider.js.map +1 -1
  17. package/lib/module/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.js +38 -2
  18. package/lib/module/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.js.map +1 -1
  19. package/lib/module/v4/component/LoadingImage/index.js +21 -21
  20. package/lib/module/v4/component/LoadingImage/index.js.map +1 -1
  21. package/lib/module/v4/component/LoadingImage/styles.js +19 -2
  22. package/lib/module/v4/component/LoadingImage/styles.js.map +1 -1
  23. package/lib/module/v4/component/LoadingVideo/index.js +11 -3
  24. package/lib/module/v4/component/LoadingVideo/index.js.map +1 -1
  25. package/lib/module/v4/component/PreviewLink/LinkPreview.js +3 -3
  26. package/lib/module/v4/component/PreviewLink/LinkPreview.js.map +1 -1
  27. package/lib/module/v4/component/PreviewLink/utils.js +9 -73
  28. package/lib/module/v4/component/PreviewLink/utils.js.map +1 -1
  29. package/lib/typescript/src/providers/file-provider.d.ts.map +1 -1
  30. package/lib/typescript/src/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.d.ts.map +1 -1
  31. package/lib/typescript/src/v4/component/LoadingImage/index.d.ts +2 -1
  32. package/lib/typescript/src/v4/component/LoadingImage/index.d.ts.map +1 -1
  33. package/lib/typescript/src/v4/component/LoadingImage/styles.d.ts +21 -0
  34. package/lib/typescript/src/v4/component/LoadingImage/styles.d.ts.map +1 -1
  35. package/lib/typescript/src/v4/component/LoadingVideo/index.d.ts +2 -1
  36. package/lib/typescript/src/v4/component/LoadingVideo/index.d.ts.map +1 -1
  37. package/lib/typescript/src/v4/component/PreviewLink/utils.d.ts +6 -1
  38. package/lib/typescript/src/v4/component/PreviewLink/utils.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/providers/file-provider.tsx +42 -20
  41. package/src/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.tsx +47 -1
  42. package/src/v4/component/LoadingImage/index.tsx +28 -22
  43. package/src/v4/component/LoadingImage/styles.ts +17 -0
  44. package/src/v4/component/LoadingVideo/index.tsx +12 -3
  45. package/src/v4/component/PreviewLink/LinkPreview.tsx +3 -3
  46. package/src/v4/component/PreviewLink/utils.ts +9 -108
@@ -38,29 +38,51 @@ export async function uploadImageFile(
38
38
  perCentCallback?: (percent: number) => void
39
39
  ): Promise<Amity.File<'image'>[]> {
40
40
  return await new Promise(async (resolve, reject) => {
41
- const formData = new FormData();
42
- const parts = filePath.split('/');
43
- const fileName = parts[parts.length - 1];
44
- const fileType = Platform.OS === 'ios' ? 'image/jpeg' : 'image/jpg';
45
- const uri =
46
- Platform.OS === 'android' ? filePath : filePath.replace('file://', '');
41
+ try {
42
+ const formData = new FormData();
43
+ const parts = filePath.split('/');
44
+ const fileName = parts[parts.length - 1];
45
+ const fileType = Platform.OS === 'ios' ? 'image/jpeg' : 'image/jpg';
46
+ const uri =
47
+ Platform.OS === 'android' ? filePath : filePath.replace('file://', '');
47
48
 
48
- formData.append('files', {
49
- name: fileName,
50
- type: fileType,
51
- uri: uri,
52
- });
49
+ formData.append('files', {
50
+ name: fileName,
51
+ type: fileType,
52
+ uri: uri,
53
+ });
53
54
 
54
- const { data: file } = await FileRepository.uploadImage(
55
- formData,
56
- (percent) => {
57
- perCentCallback && perCentCallback(percent);
55
+ const { data: file } = await FileRepository.uploadImage(
56
+ formData,
57
+ (percent) => {
58
+ perCentCallback && perCentCallback(percent);
59
+ }
60
+ );
61
+
62
+ if (file) {
63
+ resolve(file);
64
+ } else {
65
+ reject({ message: 'Upload failed - no file data returned' });
66
+ }
67
+ } catch (error: any) {
68
+ if (
69
+ error?.message?.includes('INVALID_IMAGE') ||
70
+ error?.message?.includes('Inappropriate')
71
+ ) {
72
+ reject({
73
+ message: 'Inappropriate image',
74
+ details: 'Please choose a different image to upload.',
75
+ code: 'INVALID_IMAGE',
76
+ });
77
+ } else {
78
+ reject({
79
+ message: 'Upload failed',
80
+ details:
81
+ error?.message ||
82
+ "We couldn't complete your upload. Please try again.",
83
+ originalError: error,
84
+ });
58
85
  }
59
- );
60
- if (file) {
61
- resolve(file);
62
- } else {
63
- reject('Upload error');
64
86
  }
65
87
  });
66
88
  }
@@ -107,12 +107,16 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
107
107
  const [deletedPostIds, setDeletedPostIds] = useState<string[]>([]);
108
108
  const [isUploading, setIsUploading] = useState(false);
109
109
  const [hasChangedAttachment, setHasChangedAttachment] = useState(false);
110
+ const [imageErrors, setImageErrors] = useState<Set<string>>(new Set());
111
+ const [videoErrors, setVideoErrors] = useState<Set<string>>(new Set());
110
112
  const privateCommunityId = !community?.isPublic && community?.communityId;
111
113
  const title = isEditMode
112
114
  ? 'Edit Post'
113
115
  : community?.displayName ?? 'My Timeline';
114
116
  const isInputValid =
115
117
  !isUploading &&
118
+ imageErrors.size === 0 &&
119
+ videoErrors.size === 0 &&
116
120
  inputMessage.trim().length <= MAXIMUM_POST_CHARACTERS &&
117
121
  (inputMessage.trim().length > 0 ||
118
122
  displayImages.length > 0 ||
@@ -301,7 +305,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
301
305
  'Discard this post',
302
306
  'The post will be permanently deleted. It cannot be undone',
303
307
  [
304
- { text: 'Keey Editing', style: 'cancel' },
308
+ { text: 'Keep Editing', style: 'cancel' },
305
309
  {
306
310
  text: 'Discard',
307
311
  style: 'destructive',
@@ -598,10 +602,45 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
598
602
  }
599
603
  }, [displayVideos.length, processMedia]);
600
604
 
605
+ const handleImageUploadError = useCallback(
606
+ (hasError: boolean, source: string) => {
607
+ setImageErrors((prev) => {
608
+ const newSet = new Set(prev);
609
+ if (hasError) {
610
+ newSet.add(source);
611
+ } else {
612
+ newSet.delete(source);
613
+ }
614
+ return newSet;
615
+ });
616
+ },
617
+ []
618
+ );
619
+
620
+ const handleVideoUploadError = useCallback(
621
+ (hasError: boolean, source: string) => {
622
+ setVideoErrors((prev) => {
623
+ const newSet = new Set(prev);
624
+ if (hasError) {
625
+ newSet.add(source);
626
+ } else {
627
+ newSet.delete(source);
628
+ }
629
+ return newSet;
630
+ });
631
+ },
632
+ []
633
+ );
634
+
601
635
  const handleOnCloseImage = useCallback(
602
636
  (originalPath: string, _, postId: string) => {
603
637
  setHasChangedAttachment(true);
604
638
  setDeletedPostIds((prev) => [...prev, postId]);
639
+ setImageErrors((prev) => {
640
+ const newSet = new Set(prev);
641
+ newSet.delete(originalPath);
642
+ return newSet;
643
+ });
605
644
  setDisplayImages((prevData) => {
606
645
  const newData = prevData.filter(
607
646
  (item: IDisplayImage) => item.url !== originalPath
@@ -615,6 +654,11 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
615
654
  (originalPath: string, _, postId: string) => {
616
655
  setHasChangedAttachment(true);
617
656
  setDeletedPostIds((prev) => [...prev, postId]);
657
+ setVideoErrors((prev) => {
658
+ const newSet = new Set(prev);
659
+ newSet.delete(originalPath);
660
+ return newSet;
661
+ });
618
662
  setDisplayVideos((prevData) => {
619
663
  const newData = prevData.filter(
620
664
  (item: IDisplayImage) => item.url !== originalPath
@@ -760,6 +804,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
760
804
  onClose={handleOnCloseImage}
761
805
  index={index} //TODO: Fix this without index
762
806
  onLoadFinish={handleOnFinishImage}
807
+ onUploadError={handleImageUploadError}
763
808
  isUploaded={item.isUploaded}
764
809
  fileId={item.fileId}
765
810
  fileCount={displayImages.length}
@@ -783,6 +828,7 @@ const AmityPostComposerPage: FC<AmityPostComposerPageType> = ({
783
828
  onClose={handleOnCloseVideo}
784
829
  index={index} //TODO: Fix this without index
785
830
  onLoadFinish={handleOnFinishVideo}
831
+ onUploadError={handleVideoUploadError}
786
832
  isUploaded={item.isUploaded}
787
833
  fileId={item.fileId}
788
834
  thumbNail={item.thumbNail as string}
@@ -10,8 +10,6 @@ import { closeIcon, toastIcon } from '../../../svg/svg-xml-list';
10
10
  import { useStyles } from './styles';
11
11
  import { useTheme } from 'react-native-paper';
12
12
  import type { MyMD3Theme } from '../../../providers/amity-ui-kit-provider';
13
- import uiSlice from '../../../redux/slices/uiSlice';
14
- import { useUIKitDispatch } from '../../../redux/store';
15
13
 
16
14
  interface OverlayImageProps {
17
15
  source: string;
@@ -23,6 +21,7 @@ interface OverlayImageProps {
23
21
  index: number,
24
22
  originalPath: string
25
23
  ) => void;
24
+ onUploadError?: (hasError: boolean, source: string) => void;
26
25
  index?: number;
27
26
  isUploaded: boolean;
28
27
  fileId?: string;
@@ -36,6 +35,7 @@ const LoadingImage = ({
36
35
  onClose,
37
36
  index,
38
37
  onLoadFinish,
38
+ onUploadError,
39
39
  isUploaded = false,
40
40
  fileId = '',
41
41
  isEditMode = false,
@@ -44,8 +44,6 @@ const LoadingImage = ({
44
44
  setIsUploading,
45
45
  }: OverlayImageProps) => {
46
46
  const theme = useTheme() as MyMD3Theme;
47
- const dispatch = useUIKitDispatch();
48
- const { showToastMessage } = uiSlice.actions;
49
47
  const [loading, setLoading] = useState(true);
50
48
  const [progress, setProgress] = useState(0);
51
49
  const [isProcess, setIsProcess] = useState<boolean>(false);
@@ -84,33 +82,39 @@ const LoadingImage = ({
84
82
  source
85
83
  );
86
84
  } else {
85
+ setIsUploading(false);
87
86
  handleLoadEnd();
88
- dispatch(showToastMessage({ toastMessage: 'Failed to upload file' }));
87
+ setIsProcess(false);
89
88
  setIsUploadError(true);
89
+ onUploadError?.(true, source);
90
90
  }
91
91
  } catch (error) {
92
92
  handleLoadEnd();
93
- dispatch(showToastMessage({ toastMessage: 'Failed to upload file' }));
93
+ setIsProcess(false);
94
+ setIsUploading(false);
94
95
  setIsUploadError(true);
96
+ onUploadError?.(true, source);
95
97
  }
96
98
  }, [
97
- dispatch,
98
99
  handleLoadEnd,
99
100
  index,
100
101
  onLoadFinish,
102
+ onUploadError,
101
103
  setIsUploading,
102
- showToastMessage,
103
104
  source,
104
105
  ]);
105
106
 
106
107
  const handleDelete = async () => {
107
- if (!fileId) return null;
108
- if (!isEditMode) {
108
+ if (fileId && !isEditMode) {
109
109
  await deleteAmityFile(fileId);
110
110
  }
111
111
  onClose && onClose(source, fileId, postId);
112
112
  };
113
113
  useEffect(() => {
114
+ setIsUploadError(false);
115
+ onUploadError?.(false, source);
116
+ setProgress(0);
117
+ setIsProcess(false);
114
118
  if (isUploaded) {
115
119
  setLoading(false);
116
120
  } else {
@@ -151,19 +155,21 @@ const LoadingImage = ({
151
155
  )}
152
156
  </View>
153
157
  )}
154
- {!loading && isUploadError ? (
155
- <TouchableOpacity style={styles.overlay} onPress={onRetryUpload}>
156
- <SvgXml xml={toastIcon()} width="24" height="24" />
157
- </TouchableOpacity>
158
- ) : (
159
- <TouchableOpacity
160
- style={styles.closeButton}
161
- disabled={loading || isProcess}
162
- onPress={handleDelete}
163
- >
164
- <SvgXml xml={closeIcon(theme.colors.base)} width="12" height="12" />
165
- </TouchableOpacity>
158
+ {!loading && isUploadError && (
159
+ <View style={styles.failedOverlay}>
160
+ <TouchableOpacity style={styles.errorOverlay} onPress={onRetryUpload}>
161
+ <SvgXml xml={toastIcon()} width="28" height="28" />
162
+ </TouchableOpacity>
163
+ </View>
166
164
  )}
165
+
166
+ <TouchableOpacity
167
+ style={styles.closeButton}
168
+ disabled={(loading || isProcess) && !isUploadError}
169
+ onPress={handleDelete}
170
+ >
171
+ <SvgXml xml={closeIcon(theme.colors.base)} width="12" height="12" />
172
+ </TouchableOpacity>
167
173
  </View>
168
174
  );
169
175
  };
@@ -18,12 +18,28 @@ export const useStyles = () => {
18
18
  height: '100%',
19
19
  resizeMode: 'cover',
20
20
  borderRadius: 5,
21
+ position: 'relative',
21
22
  },
22
23
  overlay: {
23
24
  ...StyleSheet.absoluteFillObject,
24
25
  justifyContent: 'center',
25
26
  alignItems: 'center',
26
27
  },
28
+ failedOverlay: {
29
+ ...StyleSheet.absoluteFillObject,
30
+ justifyContent: 'center',
31
+ alignItems: 'center',
32
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
33
+ borderRadius: 5,
34
+ },
35
+ errorOverlay: {
36
+ position: 'absolute',
37
+ top: '50%',
38
+ left: 0,
39
+ right: 0,
40
+ alignItems: 'center',
41
+ justifyContent: 'center',
42
+ },
27
43
  progressBar: {
28
44
  marginVertical: 10,
29
45
  },
@@ -40,6 +56,7 @@ export const useStyles = () => {
40
56
  padding: 7,
41
57
  backgroundColor: 'rgba(0, 0, 0, 0.4)',
42
58
  borderRadius: 72,
59
+ zIndex: 10,
43
60
  },
44
61
  });
45
62
  };
@@ -35,6 +35,7 @@ interface OverlayImageProps {
35
35
  originalPath: string,
36
36
  thumbNail: string
37
37
  ) => void;
38
+ onUploadError?: (hasError: boolean, source: string) => void;
38
39
  index?: number;
39
40
  isUploaded: boolean;
40
41
  fileId?: string;
@@ -50,6 +51,7 @@ const LoadingVideo = ({
50
51
  onClose,
51
52
  index,
52
53
  onLoadFinish,
54
+ onUploadError,
53
55
  isUploaded = false,
54
56
  thumbNail,
55
57
  onPlay,
@@ -129,23 +131,30 @@ const LoadingVideo = ({
129
131
  } else {
130
132
  handleLoadEnd();
131
133
  dispatch(showToastMessage({ toastMessage: 'Failed to upload file' }));
134
+ setIsProcess(false);
132
135
  setIsUploadError(true);
136
+ onUploadError?.(true, source);
133
137
  }
134
138
  } catch (error) {
135
139
  handleLoadEnd();
136
140
  dispatch(showToastMessage({ toastMessage: 'Failed to upload file' }));
141
+ setIsProcess(false);
137
142
  setIsUploadError(true);
143
+ onUploadError?.(true, source);
138
144
  }
139
145
  }, [source]);
140
146
 
141
147
  const handleDelete = async () => {
142
- if (!fileId) return null;
143
- if (!isEditMode) {
148
+ if (fileId && !isEditMode) {
144
149
  await deleteAmityFile(fileId);
145
150
  }
146
151
  onClose && onClose(source, fileId, postId);
147
152
  };
148
153
  useEffect(() => {
154
+ setIsUploadError(false);
155
+ onUploadError?.(false, source);
156
+ setProgress(0);
157
+ setIsProcess(false);
149
158
  if (isUploaded) {
150
159
  setLoading(false);
151
160
  } else {
@@ -216,7 +225,7 @@ const LoadingVideo = ({
216
225
  ) : (
217
226
  <TouchableOpacity
218
227
  style={styles.closeButton}
219
- disabled={loading || isProcess}
228
+ disabled={(loading || isProcess) && !isUploadError}
220
229
  onPress={handleDelete}
221
230
  >
222
231
  <SvgXml xml={closeIcon(theme.colors.base)} width="12" height="12" />
@@ -21,7 +21,7 @@ export const LinkPreview = React.memo(
21
21
 
22
22
  const fetchData = async () => {
23
23
  setData(undefined);
24
- const newData = await getPreviewData(text, 5000);
24
+ const newData = await getPreviewData(text);
25
25
  if (!isCancelled) {
26
26
  setData(newData);
27
27
  }
@@ -37,8 +37,8 @@ export const LinkPreview = React.memo(
37
37
 
38
38
  const renderImageNode = React.useCallback(
39
39
  (image: PreviewDataImage) => {
40
- const imageUrl = image?.url
41
- ? { uri: image.url }
40
+ const imageUrl = image
41
+ ? { uri: image }
42
42
  : require('../../assets/images/previewLinkDefaultBackground.png');
43
43
 
44
44
  return (
@@ -2,6 +2,7 @@ import { decode } from 'html-entities';
2
2
  import { Image } from 'react-native';
3
3
 
4
4
  import { PreviewData, PreviewDataImage, Size } from './types';
5
+ import { Client } from '@amityco/ts-sdk-react-native';
5
6
 
6
7
  export const getActualImageUrl = (baseUrl: string, imageUrl?: string) => {
7
8
  let actualImageUrl = imageUrl?.trim();
@@ -54,7 +55,7 @@ export const getImageSize = (url: string) => {
54
55
 
55
56
  // Functions below use functions from the same file and mocks are not working
56
57
  /* istanbul ignore next */
57
- export const getPreviewData = async (text: string, requestTimeout = 5000) => {
58
+ export const getPreviewData = async (text: string) => {
58
59
  const previewData: PreviewData = {
59
60
  description: undefined,
60
61
  image: undefined,
@@ -77,114 +78,14 @@ export const getPreviewData = async (text: string, requestTimeout = 5000) => {
77
78
  url = 'https://' + url;
78
79
  }
79
80
 
80
- let abortControllerTimeout: number;
81
- const abortController = new AbortController();
81
+ const request = await Client.fetchLinkPreview(url);
82
82
 
83
- const request = fetch(url, {
84
- headers: {
85
- 'User-Agent':
86
- 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36',
87
- },
88
- signal: abortController.signal,
89
- });
90
-
91
- abortControllerTimeout = setTimeout(() => {
92
- abortController.abort();
93
- }, requestTimeout);
94
-
95
- const response = await request;
96
-
97
- clearTimeout(abortControllerTimeout);
98
-
99
- previewData.link = url;
100
-
101
- const contentType = response.headers.get('content-type') ?? '';
102
-
103
- if (REGEX_IMAGE_CONTENT_TYPE.test(contentType)) {
104
- const image = await getPreviewDataImage(url);
105
- previewData.image = image;
106
- return previewData;
107
- }
108
-
109
- const html = await response.text();
110
-
111
- // Some pages return undefined
112
- if (!html) return previewData;
113
-
114
- const head = html.substring(0, html.indexOf('<body'));
115
-
116
- // Get page title
117
- const title = REGEX_TITLE.exec(head);
118
- previewData.title = getHtmlEntitiesDecodedText(title?.[1]);
119
-
120
- let matches: RegExpMatchArray | null;
121
- const meta: RegExpMatchArray[] = [];
122
- while ((matches = REGEX_META.exec(head)) !== null) {
123
- // @ts-ignore
124
- meta.push([...matches]);
125
- }
126
-
127
- const metaPreviewData = meta.reduce<{
128
- description?: string;
129
- imageUrl?: string;
130
- title?: string;
131
- }>(
132
- (acc, curr) => {
133
- // Verify that we have property/name and content
134
- // Note that if a page will specify property, name and content in the same meta, regex will fail
135
- if (!curr[2] || !curr[3]) return acc;
136
-
137
- // Only take the first occurrence
138
- // For description take the meta description tag into consideration
139
- const description =
140
- !acc.description &&
141
- (getContent(curr[2], curr[3], 'og:description') ||
142
- getContent(curr[2], curr[3], 'description'));
143
- const ogImage =
144
- !acc.imageUrl && getContent(curr[2], curr[3], 'og:image');
145
- const ogTitle = !acc.title && getContent(curr[2], curr[3], 'og:title');
146
-
147
- return {
148
- description: description
149
- ? getHtmlEntitiesDecodedText(description)
150
- : acc.description,
151
- imageUrl: ogImage ? getActualImageUrl(url, ogImage) : acc.imageUrl,
152
- title: ogTitle ? getHtmlEntitiesDecodedText(ogTitle) : acc.title,
153
- };
154
- },
155
- { title: previewData.title }
156
- );
157
-
158
- previewData.description = metaPreviewData.description;
159
- previewData.image = await getPreviewDataImage(metaPreviewData.imageUrl);
160
- previewData.title = metaPreviewData.title;
161
-
162
- if (!previewData.image) {
163
- let imageMatches: RegExpMatchArray | null;
164
- const tags: RegExpMatchArray[] = [];
165
- while ((imageMatches = REGEX_IMAGE_TAG.exec(html)) !== null) {
166
- // @ts-ignore
167
- tags.push([...imageMatches]);
168
- }
169
-
170
- let images: PreviewDataImage[] = [];
171
-
172
- for (const tag of tags
173
- .filter((t) => !t[1].startsWith('data'))
174
- .slice(0, 5)) {
175
- const image = await getPreviewDataImage(getActualImageUrl(url, tag[1]));
176
-
177
- if (!image) continue;
178
-
179
- images = [...images, image];
180
- }
181
-
182
- previewData.image = images.sort(
183
- (a, b) => b.height * b.width - a.height * a.width
184
- )[0];
185
- }
186
-
187
- return previewData;
83
+ return {
84
+ description: request.description || undefined,
85
+ image: request.image || undefined,
86
+ link: url,
87
+ title: request.title || undefined,
88
+ };
188
89
  } catch {
189
90
  return previewData;
190
91
  }