diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/modals/LinkWarning.tsx | 162 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 4 | ||||
-rw-r--r-- | src/view/com/modals/Modal.web.tsx | 3 | ||||
-rw-r--r-- | src/view/com/util/Link.tsx | 25 | ||||
-rw-r--r-- | src/view/com/util/text/RichText.tsx | 1 |
5 files changed, 193 insertions, 2 deletions
diff --git a/src/view/com/modals/LinkWarning.tsx b/src/view/com/modals/LinkWarning.tsx new file mode 100644 index 000000000..67a156af4 --- /dev/null +++ b/src/view/com/modals/LinkWarning.tsx @@ -0,0 +1,162 @@ +import React from 'react' +import {Linking, SafeAreaView, StyleSheet, View} from 'react-native' +import {ScrollView} from './util' +import {observer} from 'mobx-react-lite' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {Text} from '../util/text/Text' +import {Button} from '../util/forms/Button' +import {useStores} from 'state/index' +import {s, colors} from 'lib/styles' +import {usePalette} from 'lib/hooks/usePalette' +import {isWeb} from 'platform/detection' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {isPossiblyAUrl, splitApexDomain} from 'lib/strings/url-helpers' + +export const snapPoints = ['50%'] + +export const Component = observer(function Component({ + text, + href, +}: { + text: string + href: string +}) { + const pal = usePalette('default') + const store = useStores() + const {isMobile} = useWebMediaQueries() + const potentiallyMisleading = isPossiblyAUrl(text) + + const onPressVisit = () => { + store.shell.closeModal() + Linking.openURL(href) + } + + return ( + <SafeAreaView style={[s.flex1, pal.view]}> + <ScrollView + testID="linkWarningModal" + style={[s.flex1, isMobile && {paddingHorizontal: 18}]}> + <View style={styles.titleSection}> + {potentiallyMisleading ? ( + <> + <FontAwesomeIcon + icon="circle-exclamation" + color={pal.colors.text} + size={18} + /> + <Text type="title-lg" style={[pal.text, styles.title]}> + Potentially Misleading Link + </Text> + </> + ) : ( + <Text type="title-lg" style={[pal.text, styles.title]}> + Leaving Bluesky + </Text> + )} + </View> + + <View style={{gap: 10}}> + <Text type="lg" style={pal.text}> + This link is taking you to the following website: + </Text> + + <LinkBox href={href} /> + + {potentiallyMisleading && ( + <Text type="lg" style={pal.text}> + Make sure this is where you intend to go! + </Text> + )} + </View> + + <View style={[styles.btnContainer, isMobile && {paddingBottom: 40}]}> + <Button + testID="confirmBtn" + type="primary" + onPress={onPressVisit} + accessibilityLabel="Visit Site" + accessibilityHint="" + label="Visit Site" + labelContainerStyle={{justifyContent: 'center', padding: 4}} + labelStyle={[s.f18]} + /> + <Button + testID="cancelBtn" + type="default" + onPress={() => store.shell.closeModal()} + accessibilityLabel="Cancel" + accessibilityHint="" + label="Cancel" + labelContainerStyle={{justifyContent: 'center', padding: 4}} + labelStyle={[s.f18]} + /> + </View> + </ScrollView> + </SafeAreaView> + ) +}) + +function LinkBox({href}: {href: string}) { + const pal = usePalette('default') + 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={[pal.view, pal.border, styles.linkBox]}> + <Text type="lg" style={pal.textLight}> + {scheme} + <Text type="lg-bold" style={pal.text}> + {hostname} + </Text> + {rest} + </Text> + </View> + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingBottom: isWeb ? 0 : 40, + }, + titleSection: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + gap: 6, + paddingTop: isWeb ? 0 : 4, + paddingBottom: isWeb ? 14 : 10, + }, + title: { + textAlign: 'center', + fontWeight: '600', + }, + linkBox: { + paddingHorizontal: 12, + paddingVertical: 10, + borderRadius: 6, + borderWidth: 1, + }, + btn: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + borderRadius: 32, + padding: 14, + backgroundColor: colors.blue3, + }, + btnContainer: { + paddingTop: 20, + gap: 6, + }, +}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index cd2d2d9e9..4f3f424a3 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -33,6 +33,7 @@ import * as BirthDateSettingsModal from './BirthDateSettings' import * as VerifyEmailModal from './VerifyEmail' import * as ChangeEmailModal from './ChangeEmail' import * as SwitchAccountModal from './SwitchAccount' +import * as LinkWarningModal from './LinkWarning' const DEFAULT_SNAPPOINTS = ['90%'] @@ -148,6 +149,9 @@ export const ModalsContainer = observer(function ModalsContainer() { } else if (activeModal?.name === 'switch-account') { snapPoints = SwitchAccountModal.snapPoints element = <SwitchAccountModal.Component /> + } else if (activeModal?.name === 'link-warning') { + snapPoints = LinkWarningModal.snapPoints + element = <LinkWarningModal.Component {...activeModal} /> } else { return null } diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 685d9abe1..ee778d17d 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -30,6 +30,7 @@ import * as ModerationDetailsModal from './ModerationDetails' import * as BirthDateSettingsModal from './BirthDateSettings' import * as VerifyEmailModal from './VerifyEmail' import * as ChangeEmailModal from './ChangeEmail' +import * as LinkWarningModal from './LinkWarning' export const ModalsContainer = observer(function ModalsContainer() { const store = useStores() @@ -116,6 +117,8 @@ function Modal({modal}: {modal: ModalIface}) { element = <VerifyEmailModal.Component {...modal} /> } else if (modal.name === 'change-email') { element = <ChangeEmailModal.Component /> + } else if (modal.name === 'link-warning') { + element = <LinkWarningModal.Component {...modal} /> } else { return null } diff --git a/src/view/com/util/Link.tsx b/src/view/com/util/Link.tsx index d11bae6ba..6915d3e08 100644 --- a/src/view/com/util/Link.tsx +++ b/src/view/com/util/Link.tsx @@ -23,7 +23,11 @@ import {TypographyVariant} from 'lib/ThemeContext' import {NavigationProp} from 'lib/routes/types' import {router} from '../../../routes' import {useStores, RootStoreModel} from 'state/index' -import {convertBskyAppUrlIfNeeded, isExternalUrl} from 'lib/strings/url-helpers' +import { + convertBskyAppUrlIfNeeded, + isExternalUrl, + linkRequiresWarning, +} from 'lib/strings/url-helpers' import {isAndroid} from 'platform/detection' import {sanitizeUrl} from '@braintree/sanitize-url' import {PressableWithHover} from './PressableWithHover' @@ -143,6 +147,7 @@ export const TextLink = observer(function TextLink({ dataSet, title, onPress, + warnOnMismatchingLabel, ...orgProps }: { testID?: string @@ -154,13 +159,29 @@ export const TextLink = observer(function TextLink({ lineHeight?: number dataSet?: any title?: string + warnOnMismatchingLabel?: boolean } & TextProps) { const {...props} = useLinkProps({to: sanitizeUrl(href)}) const store = useStores() const navigation = useNavigation<NavigationProp>() + if (warnOnMismatchingLabel && typeof text !== 'string') { + console.error('Unable to detect mismatching label') + } + props.onPress = React.useCallback( (e?: Event) => { + const requiresWarning = + warnOnMismatchingLabel && + linkRequiresWarning(href, typeof text === 'string' ? text : '') + if (requiresWarning) { + e?.preventDefault?.() + store.shell.openModal({ + name: 'link-warning', + text: typeof text === 'string' ? text : '', + href, + }) + } if (onPress) { e?.preventDefault?.() // @ts-ignore function signature differs by platform -prf @@ -168,7 +189,7 @@ export const TextLink = observer(function TextLink({ } return onPressInner(store, navigation, sanitizeUrl(href), e) }, - [onPress, store, navigation, href], + [onPress, store, navigation, href, text, warnOnMismatchingLabel], ) const hrefAttrs = useMemo(() => { const isExternal = isExternalUrl(href) diff --git a/src/view/com/util/text/RichText.tsx b/src/view/com/util/text/RichText.tsx index 0dc13fd34..34c201829 100644 --- a/src/view/com/util/text/RichText.tsx +++ b/src/view/com/util/text/RichText.tsx @@ -89,6 +89,7 @@ export function RichText({ href={link.uri} style={[style, lineHeightStyle, pal.link]} dataSet={WORD_WRAP} + warnOnMismatchingLabel />, ) } else { |