mirror of
https://github.com/tooot-app/app
synced 2025-04-07 15:11:09 +02:00
parent
d15e8cb652
commit
543ac86d03
1
src/@types/react-navigation.d.ts
vendored
1
src/@types/react-navigation.d.ts
vendored
@ -60,7 +60,6 @@ declare namespace Nav {
|
|||||||
url: Mastodon.AttachmentImage['url']
|
url: Mastodon.AttachmentImage['url']
|
||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
preview_url: Mastodon.AttachmentImage['preview_url']
|
|
||||||
remote_url?: Mastodon.AttachmentImage['remote_url']
|
remote_url?: Mastodon.AttachmentImage['remote_url']
|
||||||
}[]
|
}[]
|
||||||
id: Mastodon.Attachment['id']
|
id: Mastodon.Attachment['id']
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { RootState } from '@root/store'
|
import { RootState } from '@root/store'
|
||||||
import axios from 'axios'
|
import axios, { AxiosRequestConfig } from 'axios'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import li from 'li'
|
import li from 'li'
|
||||||
|
|
||||||
@ -14,7 +14,10 @@ export type Params = {
|
|||||||
}
|
}
|
||||||
headers?: { [key: string]: string }
|
headers?: { [key: string]: string }
|
||||||
body?: FormData
|
body?: FormData
|
||||||
onUploadProgress?: (progressEvent: any) => void
|
extras?: Omit<
|
||||||
|
AxiosRequestConfig,
|
||||||
|
'method' | 'url' | 'params' | 'headers' | 'data'
|
||||||
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiInstance = async <T = unknown>({
|
const apiInstance = async <T = unknown>({
|
||||||
@ -24,7 +27,7 @@ const apiInstance = async <T = unknown>({
|
|||||||
params,
|
params,
|
||||||
headers,
|
headers,
|
||||||
body,
|
body,
|
||||||
onUploadProgress
|
extras
|
||||||
}: Params): Promise<{ body: T; links: { prev?: string; next?: string } }> => {
|
}: Params): Promise<{ body: T; links: { prev?: string; next?: string } }> => {
|
||||||
const { store } = require('@root/store')
|
const { store } = require('@root/store')
|
||||||
const state = store.getState() as RootState
|
const state = store.getState() as RootState
|
||||||
@ -70,7 +73,7 @@ const apiInstance = async <T = unknown>({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
...(body && { data: body }),
|
...(body && { data: body }),
|
||||||
...(onUploadProgress && { onUploadProgress: onUploadProgress })
|
...extras
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let prev
|
let prev
|
||||||
|
@ -102,7 +102,11 @@ const GracefullyImage = React.memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[style, dimension, { backgroundColor: theme.shimmerDefault }]}
|
style={[
|
||||||
|
style,
|
||||||
|
dimension,
|
||||||
|
{ backgroundColor: theme.backgroundOverlayDefault }
|
||||||
|
]}
|
||||||
{...(onPress
|
{...(onPress
|
||||||
? hidden
|
? hidden
|
||||||
? { disabled: true }
|
? { disabled: true }
|
||||||
|
@ -121,7 +121,7 @@ const renderNode = ({
|
|||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
analytics('status_link_press')
|
analytics('status_link_press')
|
||||||
!disableDetails && !shouldBeTag
|
!disableDetails && !shouldBeTag
|
||||||
? await openLink(href)
|
? await openLink(href, navigation)
|
||||||
: navigation.push('Tab-Shared-Hashtag', {
|
: navigation.push('Tab-Shared-Hashtag', {
|
||||||
hashtag: content.substring(1)
|
hashtag: content.substring(1)
|
||||||
})
|
})
|
||||||
|
@ -48,7 +48,6 @@ const TimelineAttachment = React.memo(
|
|||||||
imageUrls.push({
|
imageUrls.push({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
url: attachment.url,
|
url: attachment.url,
|
||||||
preview_url: attachment.preview_url,
|
|
||||||
remote_url: attachment.remote_url,
|
remote_url: attachment.remote_url,
|
||||||
width: attachment.meta?.original?.width,
|
width: attachment.meta?.original?.width,
|
||||||
height: attachment.meta?.original?.height
|
height: attachment.meta?.original?.height
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import analytics from '@components/analytics'
|
import analytics from '@components/analytics'
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import openLink from '@components/openLink'
|
import openLink from '@components/openLink'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@ -13,13 +14,14 @@ export interface Props {
|
|||||||
const TimelineCard = React.memo(
|
const TimelineCard = React.memo(
|
||||||
({ card }: Props) => {
|
({ card }: Props) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const navigation = useNavigation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[styles.card, { borderColor: theme.border }]}
|
style={[styles.card, { borderColor: theme.border }]}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
analytics('timeline_shared_card_press')
|
analytics('timeline_shared_card_press')
|
||||||
await openLink(card.url)
|
await openLink(card.url, navigation)
|
||||||
}}
|
}}
|
||||||
testID='base'
|
testID='base'
|
||||||
>
|
>
|
||||||
|
@ -1,9 +1,127 @@
|
|||||||
|
import apiInstance from '@api/instance'
|
||||||
|
import { NavigationProp, ParamListBase } from '@react-navigation/native'
|
||||||
|
import { navigationRef } from '@root/Screens'
|
||||||
import { store } from '@root/store'
|
import { store } from '@root/store'
|
||||||
|
import { SearchResult } from '@utils/queryHooks/search'
|
||||||
|
import { getInstanceUrl } from '@utils/slices/instancesSlice'
|
||||||
import { getSettingsBrowser } from '@utils/slices/settingsSlice'
|
import { getSettingsBrowser } from '@utils/slices/settingsSlice'
|
||||||
import * as Linking from 'expo-linking'
|
import * as Linking from 'expo-linking'
|
||||||
import * as WebBrowser from 'expo-web-browser'
|
import * as WebBrowser from 'expo-web-browser'
|
||||||
|
|
||||||
const openLink = async (url: string) => {
|
// https://social.xmflsct.com/web/statuses/105590085754428765 <- default
|
||||||
|
// https://social.xmflsct.com/@tooot/105590085754428765 <- pretty
|
||||||
|
const matcherStatus = new RegExp(
|
||||||
|
/http[s]?:\/\/(.*)\/(web\/statuses|@.*)\/([0-9]*)/
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://social.xmflsct.com/web/accounts/14195 <- default
|
||||||
|
// https://social.xmflsct.com/@tooot <- pretty
|
||||||
|
const matcherAccount = new RegExp(
|
||||||
|
/http[s]?:\/\/(.*)\/(web\/accounts\/([0-9]*)|@.*)/
|
||||||
|
)
|
||||||
|
|
||||||
|
export let loadingLink = false
|
||||||
|
|
||||||
|
const openLink = async (
|
||||||
|
url: string,
|
||||||
|
navigation?: NavigationProp<
|
||||||
|
ParamListBase,
|
||||||
|
string,
|
||||||
|
Readonly<{
|
||||||
|
key: string
|
||||||
|
index: number
|
||||||
|
routeNames: string[]
|
||||||
|
history?: unknown[] | undefined
|
||||||
|
routes: any[]
|
||||||
|
type: string
|
||||||
|
stale: false
|
||||||
|
}>,
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
if (loadingLink) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNavigation = (
|
||||||
|
page: 'Tab-Shared-Toot' | 'Tab-Shared-Account',
|
||||||
|
options: {}
|
||||||
|
) => {
|
||||||
|
if (navigation) {
|
||||||
|
// @ts-ignore
|
||||||
|
navigation.push(page, options)
|
||||||
|
} else {
|
||||||
|
navigationRef.current?.navigate(page, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a tooot can be found
|
||||||
|
const matchedStatus = url.match(matcherStatus)
|
||||||
|
if (matchedStatus) {
|
||||||
|
// If the link in current instance
|
||||||
|
const instanceUrl = getInstanceUrl(store.getState())
|
||||||
|
if (matchedStatus[1] === instanceUrl) {
|
||||||
|
handleNavigation('Tab-Shared-Toot', {
|
||||||
|
toot: { id: matchedStatus[3] }
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingLink = true
|
||||||
|
let response
|
||||||
|
try {
|
||||||
|
response = await apiInstance<SearchResult>({
|
||||||
|
version: 'v2',
|
||||||
|
method: 'get',
|
||||||
|
url: 'search',
|
||||||
|
params: { type: 'statuses', q: url, limit: 1, resolve: true }
|
||||||
|
})
|
||||||
|
} catch {}
|
||||||
|
if (response && response.body && response.body.statuses.length) {
|
||||||
|
handleNavigation('Tab-Shared-Toot', {
|
||||||
|
toot: response.body.statuses[0]
|
||||||
|
})
|
||||||
|
loadingLink = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an account can be found
|
||||||
|
const matchedAccount = url.match(matcherAccount)
|
||||||
|
console.log(matchedAccount)
|
||||||
|
if (matchedAccount) {
|
||||||
|
// If the link in current instance
|
||||||
|
const instanceUrl = getInstanceUrl(store.getState())
|
||||||
|
if (matchedAccount[1] === instanceUrl) {
|
||||||
|
if (matchedAccount[3] && matchedAccount[3].match(/[0-9]*/)) {
|
||||||
|
handleNavigation('Tab-Shared-Account', {
|
||||||
|
account: { id: matchedAccount[3] }
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingLink = true
|
||||||
|
let response
|
||||||
|
try {
|
||||||
|
response = await apiInstance<SearchResult>({
|
||||||
|
version: 'v2',
|
||||||
|
method: 'get',
|
||||||
|
url: 'search',
|
||||||
|
params: { type: 'accounts', q: url, limit: 1, resolve: true }
|
||||||
|
})
|
||||||
|
} catch {}
|
||||||
|
if (response && response.body && response.body.accounts.length) {
|
||||||
|
handleNavigation('Tab-Shared-Account', {
|
||||||
|
account: response.body.accounts[0]
|
||||||
|
})
|
||||||
|
loadingLink = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingLink = false
|
||||||
switch (getSettingsBrowser(store.getState())) {
|
switch (getSettingsBrowser(store.getState())) {
|
||||||
case 'internal':
|
case 'internal':
|
||||||
await WebBrowser.openBrowserAsync(url, {
|
await WebBrowser.openBrowserAsync(url, {
|
||||||
|
@ -15,12 +15,3 @@ export type Position = {
|
|||||||
x: number
|
x: number
|
||||||
y: number
|
y: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImageSource = {
|
|
||||||
id: string
|
|
||||||
preview_url: string
|
|
||||||
remote_url?: string
|
|
||||||
url: string
|
|
||||||
width?: number
|
|
||||||
height?: number
|
|
||||||
}
|
|
||||||
|
@ -14,17 +14,18 @@ import {
|
|||||||
View,
|
View,
|
||||||
VirtualizedList
|
VirtualizedList
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { ImageSource } from './@types'
|
|
||||||
import ImageItem from './components/ImageItem'
|
import ImageItem from './components/ImageItem'
|
||||||
import useAnimatedComponents from './hooks/useAnimatedComponents'
|
import useAnimatedComponents from './hooks/useAnimatedComponents'
|
||||||
import useImageIndexChange from './hooks/useImageIndexChange'
|
import useImageIndexChange from './hooks/useImageIndexChange'
|
||||||
import useRequestClose from './hooks/useRequestClose'
|
import useRequestClose from './hooks/useRequestClose'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
images: ImageSource[]
|
images: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
||||||
imageIndex: number
|
imageIndex: number
|
||||||
onRequestClose: () => void
|
onRequestClose: () => void
|
||||||
onLongPress?: (image: ImageSource) => void
|
onLongPress?: (
|
||||||
|
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||||
|
) => void
|
||||||
onImageIndexChange?: (imageIndex: number) => void
|
onImageIndexChange?: (imageIndex: number) => void
|
||||||
backgroundColor?: string
|
backgroundColor?: string
|
||||||
swipeToCloseEnabled?: boolean
|
swipeToCloseEnabled?: boolean
|
||||||
@ -48,7 +49,11 @@ function ImageViewer ({
|
|||||||
delayLongPress = DEFAULT_DELAY_LONG_PRESS,
|
delayLongPress = DEFAULT_DELAY_LONG_PRESS,
|
||||||
HeaderComponent
|
HeaderComponent
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const imageList = React.createRef<VirtualizedList<ImageSource>>()
|
const imageList = React.createRef<
|
||||||
|
VirtualizedList<
|
||||||
|
Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||||
|
>
|
||||||
|
>()
|
||||||
const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose)
|
const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose)
|
||||||
const [currentImageIndex, onScroll] = useImageIndexChange(imageIndex, SCREEN)
|
const [currentImageIndex, onScroll] = useImageIndexChange(imageIndex, SCREEN)
|
||||||
const [headerTransform, toggleBarsVisible] = useAnimatedComponents()
|
const [headerTransform, toggleBarsVisible] = useAnimatedComponents()
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import React, { useState, useCallback } from 'react'
|
import React, { useState, useCallback } from 'react'
|
||||||
import { Animated, Dimensions, StyleSheet } from 'react-native'
|
import { Animated, Dimensions, StyleSheet } from 'react-native'
|
||||||
import { ImageSource } from '../@types'
|
|
||||||
import usePanResponder from '../hooks/usePanResponder'
|
import usePanResponder from '../hooks/usePanResponder'
|
||||||
import { getImageStyles, getImageTransform } from '../utils'
|
import { getImageStyles, getImageTransform } from '../utils'
|
||||||
|
|
||||||
@ -18,10 +17,12 @@ const SCREEN_WIDTH = SCREEN.width
|
|||||||
const SCREEN_HEIGHT = SCREEN.height
|
const SCREEN_HEIGHT = SCREEN.height
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
imageSrc: ImageSource
|
imageSrc: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||||
onRequestClose: () => void
|
onRequestClose: () => void
|
||||||
onZoom: (isZoomed: boolean) => void
|
onZoom: (isZoomed: boolean) => void
|
||||||
onLongPress: (image: ImageSource) => void
|
onLongPress: (
|
||||||
|
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||||
|
) => void
|
||||||
delayLongPress: number
|
delayLongPress: number
|
||||||
swipeToCloseEnabled?: boolean
|
swipeToCloseEnabled?: boolean
|
||||||
doubleTapToZoomEnabled?: boolean
|
doubleTapToZoomEnabled?: boolean
|
||||||
@ -89,7 +90,6 @@ const ImageItem = ({
|
|||||||
children={
|
children={
|
||||||
<GracefullyImage
|
<GracefullyImage
|
||||||
uri={{
|
uri={{
|
||||||
preview: imageSrc.preview_url,
|
|
||||||
original: imageSrc.url,
|
original: imageSrc.url,
|
||||||
remote: imageSrc.remote_url
|
remote: imageSrc.remote_url
|
||||||
}}
|
}}
|
||||||
|
@ -21,7 +21,6 @@ import {
|
|||||||
State,
|
State,
|
||||||
TapGestureHandler
|
TapGestureHandler
|
||||||
} from 'react-native-gesture-handler'
|
} from 'react-native-gesture-handler'
|
||||||
import { ImageSource } from '../@types'
|
|
||||||
import useDoubleTapToZoom from '../hooks/useDoubleTapToZoom'
|
import useDoubleTapToZoom from '../hooks/useDoubleTapToZoom'
|
||||||
import { getImageStyles, getImageTransform } from '../utils'
|
import { getImageStyles, getImageTransform } from '../utils'
|
||||||
|
|
||||||
@ -32,10 +31,12 @@ const SCREEN_WIDTH = SCREEN.width
|
|||||||
const SCREEN_HEIGHT = SCREEN.height
|
const SCREEN_HEIGHT = SCREEN.height
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
imageSrc: ImageSource
|
imageSrc: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||||
onRequestClose: () => void
|
onRequestClose: () => void
|
||||||
onZoom: (scaled: boolean) => void
|
onZoom: (scaled: boolean) => void
|
||||||
onLongPress: (image: ImageSource) => void
|
onLongPress: (
|
||||||
|
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||||
|
) => void
|
||||||
swipeToCloseEnabled?: boolean
|
swipeToCloseEnabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +145,6 @@ const ImageItem = ({
|
|||||||
children={
|
children={
|
||||||
<GracefullyImage
|
<GracefullyImage
|
||||||
uri={{
|
uri={{
|
||||||
preview: imageSrc.preview_url,
|
|
||||||
original: imageSrc.url,
|
original: imageSrc.url,
|
||||||
remote: imageSrc.remote_url
|
remote: imageSrc.remote_url
|
||||||
}}
|
}}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo, useEffect, useCallback } from 'react'
|
import { useMemo, useEffect } from 'react'
|
||||||
import {
|
import {
|
||||||
Animated,
|
Animated,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
|
@ -20,15 +20,9 @@ import {
|
|||||||
} from 'react-native-safe-area-context'
|
} from 'react-native-safe-area-context'
|
||||||
import ImageViewer from './ImageViewer/Root'
|
import ImageViewer from './ImageViewer/Root'
|
||||||
|
|
||||||
type ImageUrl = {
|
const saveImage = async (
|
||||||
url: string
|
image: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls'][0]
|
||||||
width?: number | undefined
|
) => {
|
||||||
height?: number | undefined
|
|
||||||
preview_url: string
|
|
||||||
remote_url?: string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveImage = async (image: ImageUrl) => {
|
|
||||||
const hasAndroidPermission = async () => {
|
const hasAndroidPermission = async () => {
|
||||||
const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
|
const permission = PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
|
||||||
|
|
||||||
@ -44,9 +38,17 @@ const saveImage = async (image: ImageUrl) => {
|
|||||||
if (Platform.OS === 'android' && !(await hasAndroidPermission())) {
|
if (Platform.OS === 'android' && !(await hasAndroidPermission())) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
CameraRoll.save(image.url || image.remote_url || image.preview_url)
|
CameraRoll.save(image.url)
|
||||||
.then(() => haptics('Success'))
|
.then(() => haptics('Success'))
|
||||||
.catch(() => haptics('Error'))
|
.catch(() => {
|
||||||
|
if (image.remote_url) {
|
||||||
|
CameraRoll.save(image.remote_url)
|
||||||
|
.then(() => haptics('Success'))
|
||||||
|
.catch(() => haptics('Error'))
|
||||||
|
} else {
|
||||||
|
haptics('Error')
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderComponent = React.memo(
|
const HeaderComponent = React.memo(
|
||||||
@ -57,7 +59,7 @@ const HeaderComponent = React.memo(
|
|||||||
}: {
|
}: {
|
||||||
navigation: ScreenImagesViewerProp['navigation']
|
navigation: ScreenImagesViewerProp['navigation']
|
||||||
currentIndex: number
|
currentIndex: number
|
||||||
imageUrls: ImageUrl[]
|
imageUrls: Nav.RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
||||||
}) => {
|
}) => {
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const { t } = useTranslation('screenImageViewer')
|
const { t } = useTranslation('screenImageViewer')
|
||||||
|
@ -11,7 +11,7 @@ export type QueryKey = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
type SearchResult = {
|
export type SearchResult = {
|
||||||
accounts: Mastodon.Account[]
|
accounts: Mastodon.Account[]
|
||||||
hashtags: Mastodon.Tag[]
|
hashtags: Mastodon.Tag[]
|
||||||
statuses: Mastodon.Status[]
|
statuses: Mastodon.Status[]
|
||||||
@ -23,7 +23,12 @@ const queryFunction = ({ queryKey }: { queryKey: QueryKey }) => {
|
|||||||
version: 'v2',
|
version: 'v2',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: 'search',
|
url: 'search',
|
||||||
params: { ...(type && { type }), ...(term && { q: term }), limit }
|
params: {
|
||||||
|
...(type && { type }),
|
||||||
|
...(term && { q: term }),
|
||||||
|
limit,
|
||||||
|
resolve: true
|
||||||
|
}
|
||||||
}).then(res => res.body)
|
}).then(res => res.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user