wip move to alf
This commit is contained in:
+22
-20
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react'
|
import React, {useCallback, useState} from 'react'
|
||||||
import {
|
import {
|
||||||
GestureResponderEvent,
|
GestureResponderEvent,
|
||||||
Pressable,
|
Pressable,
|
||||||
@@ -20,10 +20,11 @@ import {
|
|||||||
} from '#/lib/strings/url-helpers'
|
} from '#/lib/strings/url-helpers'
|
||||||
import {isNative, isWeb} from '#/platform/detection'
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {shouldClickOpenNewTab} from '#/platform/urls'
|
import {shouldClickOpenNewTab} from '#/platform/urls'
|
||||||
import {useModalControls} from '#/state/modals'
|
|
||||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||||
|
import {useCloseAllActiveElements} from '#/state/util'
|
||||||
import {atoms as a, flatten, TextStyleProp, useTheme, web} from '#/alf'
|
import {atoms as a, flatten, TextStyleProp, useTheme, web} from '#/alf'
|
||||||
import {Button, ButtonProps} from '#/components/Button'
|
import {Button, ButtonProps} from '#/components/Button'
|
||||||
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||||
import {Text, TextProps} from '#/components/Typography'
|
import {Text, TextProps} from '#/components/Typography'
|
||||||
import {router} from '#/routes'
|
import {router} from '#/routes'
|
||||||
@@ -87,10 +88,20 @@ export function useLink({
|
|||||||
typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
|
typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
|
||||||
})
|
})
|
||||||
const isExternal = isExternalUrl(href)
|
const isExternal = isExternalUrl(href)
|
||||||
const {openModal, closeModal} = useModalControls()
|
|
||||||
const openLink = useOpenLink()
|
const openLink = useOpenLink()
|
||||||
|
const dismissEverything = useCloseAllActiveElements()
|
||||||
|
const [_warningMode, setWarningMode] = useState<'open' | 'share'>('open')
|
||||||
|
const linkWarningControl = useDialogControl()
|
||||||
|
|
||||||
const onPress = React.useCallback(
|
const showWarningDialog = useCallback(
|
||||||
|
(mode: 'open' | 'share') => {
|
||||||
|
setWarningMode(mode)
|
||||||
|
linkWarningControl.open()
|
||||||
|
},
|
||||||
|
[linkWarningControl],
|
||||||
|
)
|
||||||
|
|
||||||
|
const onPress = useCallback(
|
||||||
(e: GestureResponderEvent) => {
|
(e: GestureResponderEvent) => {
|
||||||
const exitEarlyIfFalse = outerOnPress?.(e)
|
const exitEarlyIfFalse = outerOnPress?.(e)
|
||||||
|
|
||||||
@@ -108,11 +119,7 @@ export function useLink({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (requiresWarning) {
|
if (requiresWarning) {
|
||||||
openModal({
|
showWarningDialog('open')
|
||||||
name: 'link-warning',
|
|
||||||
text: displayText,
|
|
||||||
href: href,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
if (isExternal) {
|
if (isExternal) {
|
||||||
openLink(href)
|
openLink(href)
|
||||||
@@ -128,7 +135,7 @@ export function useLink({
|
|||||||
) {
|
) {
|
||||||
openLink(href)
|
openLink(href)
|
||||||
} else {
|
} else {
|
||||||
closeModal() // close any active modals
|
dismissEverything() // close any active dialogs
|
||||||
|
|
||||||
if (action === 'push') {
|
if (action === 'push') {
|
||||||
navigation.dispatch(StackActions.push(...router.matchPath(href)))
|
navigation.dispatch(StackActions.push(...router.matchPath(href)))
|
||||||
@@ -152,15 +159,15 @@ export function useLink({
|
|||||||
displayText,
|
displayText,
|
||||||
isExternal,
|
isExternal,
|
||||||
href,
|
href,
|
||||||
openModal,
|
|
||||||
openLink,
|
openLink,
|
||||||
closeModal,
|
|
||||||
action,
|
action,
|
||||||
navigation,
|
navigation,
|
||||||
|
showWarningDialog,
|
||||||
|
dismissEverything,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleLongPress = React.useCallback(() => {
|
const handleLongPress = useCallback(() => {
|
||||||
const requiresWarning = Boolean(
|
const requiresWarning = Boolean(
|
||||||
!disableMismatchWarning &&
|
!disableMismatchWarning &&
|
||||||
displayText &&
|
displayText &&
|
||||||
@@ -169,16 +176,11 @@ export function useLink({
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (requiresWarning) {
|
if (requiresWarning) {
|
||||||
openModal({
|
showWarningDialog('share')
|
||||||
name: 'link-warning',
|
|
||||||
text: displayText,
|
|
||||||
href: href,
|
|
||||||
share: true,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
shareUrl(href)
|
shareUrl(href)
|
||||||
}
|
}
|
||||||
}, [disableMismatchWarning, displayText, href, isExternal, openModal])
|
}, [disableMismatchWarning, displayText, href, isExternal, showWarningDialog])
|
||||||
|
|
||||||
const onLongPress =
|
const onLongPress =
|
||||||
isNative && isExternal && shareOnLongPress ? handleLongPress : undefined
|
isNative && isExternal && shareOnLongPress ? handleLongPress : undefined
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||||
|
import {shareUrl} from '#/lib/sharing'
|
||||||
|
import {isPossiblyAUrl, splitApexDomain} from '#/lib/strings/url-helpers'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function LinkWarningDialog({
|
||||||
|
control,
|
||||||
|
linkText,
|
||||||
|
linkHref,
|
||||||
|
mode = 'open',
|
||||||
|
}: {
|
||||||
|
control: Dialog.DialogControlProps
|
||||||
|
linkText: string
|
||||||
|
linkHref: string
|
||||||
|
mode?: 'open' | 'share'
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={control}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
<DialogInner linkHref={linkHref} linkText={linkText} mode={mode} />
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DialogInner({
|
||||||
|
linkText,
|
||||||
|
linkHref,
|
||||||
|
mode,
|
||||||
|
}: {
|
||||||
|
linkText: string
|
||||||
|
linkHref: string
|
||||||
|
mode: 'open' | 'share'
|
||||||
|
}) {
|
||||||
|
const control = Dialog.useDialogContext()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const potentiallyMisleading = isPossiblyAUrl(linkText)
|
||||||
|
const openLink = useOpenLink()
|
||||||
|
|
||||||
|
const share = mode === 'share'
|
||||||
|
|
||||||
|
const onPressVisit = () => {
|
||||||
|
control.close(() => {
|
||||||
|
if (share) {
|
||||||
|
shareUrl(linkHref)
|
||||||
|
} else {
|
||||||
|
openLink(linkHref)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Dialog.Inner label={''}>
|
||||||
|
<View style={[]}>
|
||||||
|
{potentiallyMisleading ? (
|
||||||
|
<>
|
||||||
|
{/* <FontAwesomeIcon
|
||||||
|
icon="circle-exclamation"
|
||||||
|
color={pal.colors.text}
|
||||||
|
size={18}
|
||||||
|
/> */}
|
||||||
|
<Text style={[]}>
|
||||||
|
<Trans>Potentially Misleading Link</Trans>
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Text style={[]}>
|
||||||
|
<Trans>Leaving Bluesky</Trans>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{gap: 10}}>
|
||||||
|
<Text>
|
||||||
|
<Trans>This link is taking you to the following website:</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<LinkBox href={linkHref} />
|
||||||
|
|
||||||
|
{potentiallyMisleading && (
|
||||||
|
<Text>
|
||||||
|
<Trans>Make sure this is where you intend to go!</Trans>
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[]}>
|
||||||
|
<Button
|
||||||
|
label={share ? _(msg`Share Link`) : _(msg`Visit Site`)}
|
||||||
|
onPress={onPressVisit}
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
variant="solid"
|
||||||
|
style={a.mt_xl}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Done</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
label={_(msg`Cancel`)}
|
||||||
|
onPress={() => control.close()}
|
||||||
|
color="secondary"
|
||||||
|
size="large"
|
||||||
|
variant="solid">
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Done</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</Dialog.Inner>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function LinkBox({href}: {href: string}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const [scheme, hostname, rest] = React.useMemo(() => {
|
||||||
|
try {
|
||||||
|
const urlp = new URL(href)
|
||||||
|
const [subdomain, apexdomain] = splitApexDomain(urlp.hostname)
|
||||||
|
return [
|
||||||
|
urlp.protocol + '//' + subdomain,
|
||||||
|
apexdomain,
|
||||||
|
urlp.pathname + urlp.search + urlp.hash,
|
||||||
|
]
|
||||||
|
} catch {
|
||||||
|
return ['', href, '']
|
||||||
|
}
|
||||||
|
}, [href])
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
t.atoms.bg,
|
||||||
|
t.atoms.border_contrast_medium,
|
||||||
|
a.border,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.px_md,
|
||||||
|
a.py_sm,
|
||||||
|
]}>
|
||||||
|
<Text style={[t.atoms.text_contrast_high]}>
|
||||||
|
{scheme}
|
||||||
|
<Text style={[t.atoms.text, a.font_bold]}>{hostname}</Text>
|
||||||
|
{rest}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user