@amityco/react-native-social-uikit 4.0.0-4221f0d.0 → 4.0.0-48d9564c.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.
- package/lib/commonjs/providers/file-provider.js +39 -19
- package/lib/commonjs/providers/file-provider.js.map +1 -1
- package/lib/commonjs/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.js +37 -1
- package/lib/commonjs/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.js.map +1 -1
- package/lib/commonjs/v4/component/LoadingImage/index.js +20 -22
- package/lib/commonjs/v4/component/LoadingImage/index.js.map +1 -1
- package/lib/commonjs/v4/component/LoadingImage/styles.js +19 -2
- package/lib/commonjs/v4/component/LoadingImage/styles.js.map +1 -1
- package/lib/commonjs/v4/component/LoadingVideo/index.js +11 -3
- package/lib/commonjs/v4/component/LoadingVideo/index.js.map +1 -1
- package/lib/commonjs/v4/component/PreviewLink/LinkPreview.js +3 -3
- package/lib/commonjs/v4/component/PreviewLink/LinkPreview.js.map +1 -1
- package/lib/commonjs/v4/component/PreviewLink/utils.js +9 -73
- package/lib/commonjs/v4/component/PreviewLink/utils.js.map +1 -1
- package/lib/module/providers/file-provider.js +39 -19
- package/lib/module/providers/file-provider.js.map +1 -1
- package/lib/module/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.js +37 -1
- package/lib/module/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.js.map +1 -1
- package/lib/module/v4/component/LoadingImage/index.js +20 -21
- package/lib/module/v4/component/LoadingImage/index.js.map +1 -1
- package/lib/module/v4/component/LoadingImage/styles.js +19 -2
- package/lib/module/v4/component/LoadingImage/styles.js.map +1 -1
- package/lib/module/v4/component/LoadingVideo/index.js +11 -3
- package/lib/module/v4/component/LoadingVideo/index.js.map +1 -1
- package/lib/module/v4/component/PreviewLink/LinkPreview.js +3 -3
- package/lib/module/v4/component/PreviewLink/LinkPreview.js.map +1 -1
- package/lib/module/v4/component/PreviewLink/utils.js +9 -73
- package/lib/module/v4/component/PreviewLink/utils.js.map +1 -1
- package/lib/typescript/src/providers/file-provider.d.ts.map +1 -1
- package/lib/typescript/src/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.d.ts.map +1 -1
- package/lib/typescript/src/v4/component/LoadingImage/index.d.ts +2 -1
- package/lib/typescript/src/v4/component/LoadingImage/index.d.ts.map +1 -1
- package/lib/typescript/src/v4/component/LoadingImage/styles.d.ts +21 -0
- package/lib/typescript/src/v4/component/LoadingImage/styles.d.ts.map +1 -1
- package/lib/typescript/src/v4/component/LoadingVideo/index.d.ts +2 -1
- package/lib/typescript/src/v4/component/LoadingVideo/index.d.ts.map +1 -1
- package/lib/typescript/src/v4/component/PreviewLink/utils.d.ts +6 -1
- package/lib/typescript/src/v4/component/PreviewLink/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/providers/file-provider.tsx +43 -20
- package/src/v4/PublicApi/Pages/AmityPostComposerPage/AmityPostComposerPage.tsx +46 -0
- package/src/v4/component/LoadingImage/index.tsx +27 -22
- package/src/v4/component/LoadingImage/styles.ts +17 -0
- package/src/v4/component/LoadingVideo/index.tsx +12 -3
- package/src/v4/component/PreviewLink/LinkPreview.tsx +3 -3
- package/src/v4/component/PreviewLink/utils.ts +9 -108
|
@@ -38,29 +38,52 @@ 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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
formData.append('files', {
|
|
50
|
+
name: fileName,
|
|
51
|
+
type: fileType,
|
|
52
|
+
uri: uri,
|
|
53
|
+
});
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
const { data: file } = await FileRepository.uploadImage(
|
|
56
|
+
formData,
|
|
57
|
+
(percent) => {
|
|
58
|
+
perCentCallback && perCentCallback(percent);
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (file) {
|
|
63
|
+
console.log('file =>', file);
|
|
64
|
+
resolve(file);
|
|
65
|
+
} else {
|
|
66
|
+
reject({ message: 'Upload failed - no file data returned' });
|
|
67
|
+
}
|
|
68
|
+
} catch (error: any) {
|
|
69
|
+
if (
|
|
70
|
+
error?.message?.includes('INVALID_IMAGE') ||
|
|
71
|
+
error?.message?.includes('Inappropriate')
|
|
72
|
+
) {
|
|
73
|
+
reject({
|
|
74
|
+
message: 'Inappropriate image',
|
|
75
|
+
details: 'Please choose a different image to upload.',
|
|
76
|
+
code: 'INVALID_IMAGE',
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
reject({
|
|
80
|
+
message: 'Upload failed',
|
|
81
|
+
details:
|
|
82
|
+
error?.message ||
|
|
83
|
+
"We couldn't complete your upload. Please try again.",
|
|
84
|
+
originalError: error,
|
|
85
|
+
});
|
|
58
86
|
}
|
|
59
|
-
);
|
|
60
|
-
if (file) {
|
|
61
|
-
resolve(file);
|
|
62
|
-
} else {
|
|
63
|
-
reject('Upload error');
|
|
64
87
|
}
|
|
65
88
|
});
|
|
66
89
|
}
|
|
@@ -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 ||
|
|
@@ -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,38 @@ const LoadingImage = ({
|
|
|
84
82
|
source
|
|
85
83
|
);
|
|
86
84
|
} else {
|
|
85
|
+
setIsUploading(false);
|
|
87
86
|
handleLoadEnd();
|
|
88
|
-
|
|
87
|
+
setIsProcess(false);
|
|
89
88
|
setIsUploadError(true);
|
|
89
|
+
onUploadError?.(true, source);
|
|
90
90
|
}
|
|
91
91
|
} catch (error) {
|
|
92
92
|
handleLoadEnd();
|
|
93
|
-
|
|
93
|
+
setIsProcess(false);
|
|
94
94
|
setIsUploadError(true);
|
|
95
|
+
onUploadError?.(true, source);
|
|
95
96
|
}
|
|
96
97
|
}, [
|
|
97
|
-
dispatch,
|
|
98
98
|
handleLoadEnd,
|
|
99
99
|
index,
|
|
100
100
|
onLoadFinish,
|
|
101
|
+
onUploadError,
|
|
101
102
|
setIsUploading,
|
|
102
|
-
showToastMessage,
|
|
103
103
|
source,
|
|
104
104
|
]);
|
|
105
105
|
|
|
106
106
|
const handleDelete = async () => {
|
|
107
|
-
if (!
|
|
108
|
-
if (!isEditMode) {
|
|
107
|
+
if (fileId && !isEditMode) {
|
|
109
108
|
await deleteAmityFile(fileId);
|
|
110
109
|
}
|
|
111
110
|
onClose && onClose(source, fileId, postId);
|
|
112
111
|
};
|
|
113
112
|
useEffect(() => {
|
|
113
|
+
setIsUploadError(false);
|
|
114
|
+
onUploadError?.(false, source);
|
|
115
|
+
setProgress(0);
|
|
116
|
+
setIsProcess(false);
|
|
114
117
|
if (isUploaded) {
|
|
115
118
|
setLoading(false);
|
|
116
119
|
} else {
|
|
@@ -151,19 +154,21 @@ const LoadingImage = ({
|
|
|
151
154
|
)}
|
|
152
155
|
</View>
|
|
153
156
|
)}
|
|
154
|
-
{!loading && isUploadError
|
|
155
|
-
<
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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>
|
|
157
|
+
{!loading && isUploadError && (
|
|
158
|
+
<View style={styles.failedOverlay}>
|
|
159
|
+
<TouchableOpacity style={styles.errorOverlay} onPress={onRetryUpload}>
|
|
160
|
+
<SvgXml xml={toastIcon()} width="28" height="28" />
|
|
161
|
+
</TouchableOpacity>
|
|
162
|
+
</View>
|
|
166
163
|
)}
|
|
164
|
+
|
|
165
|
+
<TouchableOpacity
|
|
166
|
+
style={styles.closeButton}
|
|
167
|
+
disabled={(loading || isProcess) && !isUploadError}
|
|
168
|
+
onPress={handleDelete}
|
|
169
|
+
>
|
|
170
|
+
<SvgXml xml={closeIcon(theme.colors.base)} width="12" height="12" />
|
|
171
|
+
</TouchableOpacity>
|
|
167
172
|
</View>
|
|
168
173
|
);
|
|
169
174
|
};
|
|
@@ -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 (!
|
|
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
|
|
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
|
|
41
|
-
? { uri: image
|
|
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
|
|
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
|
-
|
|
81
|
-
const abortController = new AbortController();
|
|
81
|
+
const request = await Client.fetchLinkPreview(url);
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
}
|