mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at quote-hover 329 lines 8.7 kB view raw
1import React from 'react' 2import {GestureResponderEvent} from 'react-native' 3import {sanitizeUrl} from '@braintree/sanitize-url' 4import {StackActions, useLinkProps} from '@react-navigation/native' 5 6import {AllNavigatorParams} from '#/lib/routes/types' 7import {shareUrl} from '#/lib/sharing' 8import { 9 convertBskyAppUrlIfNeeded, 10 isExternalUrl, 11 linkRequiresWarning, 12} from '#/lib/strings/url-helpers' 13import {isNative, isWeb} from '#/platform/detection' 14import {useModalControls} from '#/state/modals' 15import {useOpenLink} from '#/state/preferences/in-app-browser' 16import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' 17import {atoms as a, flatten, TextStyleProp, useTheme, web} from '#/alf' 18import {Button, ButtonProps} from '#/components/Button' 19import {useInteractionState} from '#/components/hooks/useInteractionState' 20import {Text, TextProps} from '#/components/Typography' 21import {router} from '#/routes' 22 23/** 24 * Only available within a `Link`, since that inherits from `Button`. 25 * `InlineLink` provides no context. 26 */ 27export {useButtonContext as useLinkContext} from '#/components/Button' 28 29type BaseLinkProps = Pick< 30 Parameters<typeof useLinkProps<AllNavigatorParams>>[0], 31 'to' 32> & { 33 testID?: string 34 35 /** 36 * Label for a11y. Defaults to the href. 37 */ 38 label?: string 39 40 /** 41 * The React Navigation `StackAction` to perform when the link is pressed. 42 */ 43 action?: 'push' | 'replace' | 'navigate' 44 45 /** 46 * If true, will warn the user if the link text does not match the href. 47 * 48 * Note: atm this only works for `InlineLink`s with a string child. 49 */ 50 disableMismatchWarning?: boolean 51 52 /** 53 * Callback for when the link is pressed. Prevent default and return `false` 54 * to exit early and prevent navigation. 55 * 56 * DO NOT use this for navigation, that's what the `to` prop is for. 57 */ 58 onPress?: (e: GestureResponderEvent) => void | false 59 60 /** 61 * Web-only attribute. Sets `download` attr on web. 62 */ 63 download?: string 64 65 /** 66 * Native-only attribute. If true, will open the share sheet on long press. 67 */ 68 shareOnLongPress?: boolean 69} 70 71export function useLink({ 72 to, 73 displayText, 74 action = 'push', 75 disableMismatchWarning, 76 onPress: outerOnPress, 77 shareOnLongPress, 78}: BaseLinkProps & { 79 displayText: string 80}) { 81 const navigation = useNavigationDeduped() 82 const {href} = useLinkProps<AllNavigatorParams>({ 83 to: 84 typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to, 85 }) 86 const isExternal = isExternalUrl(href) 87 const {openModal, closeModal} = useModalControls() 88 const openLink = useOpenLink() 89 90 const onPress = React.useCallback( 91 (e: GestureResponderEvent) => { 92 const exitEarlyIfFalse = outerOnPress?.(e) 93 94 if (exitEarlyIfFalse === false) return 95 96 const requiresWarning = Boolean( 97 !disableMismatchWarning && 98 displayText && 99 isExternal && 100 linkRequiresWarning(href, displayText), 101 ) 102 103 if (requiresWarning) { 104 e.preventDefault() 105 106 openModal({ 107 name: 'link-warning', 108 text: displayText, 109 href: href, 110 }) 111 } else { 112 e.preventDefault() 113 114 if (isExternal) { 115 openLink(href) 116 } else { 117 /** 118 * A `GestureResponderEvent`, but cast to `any` to avoid using a bunch 119 * of @ts-ignore below. 120 */ 121 const event = e as any 122 const isMiddleClick = isWeb && event.button === 1 123 const isMetaKey = 124 isWeb && 125 (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) 126 const shouldOpenInNewTab = isMetaKey || isMiddleClick 127 128 if ( 129 shouldOpenInNewTab || 130 href.startsWith('http') || 131 href.startsWith('mailto') 132 ) { 133 openLink(href) 134 } else { 135 closeModal() // close any active modals 136 137 if (action === 'push') { 138 navigation.dispatch(StackActions.push(...router.matchPath(href))) 139 } else if (action === 'replace') { 140 navigation.dispatch( 141 StackActions.replace(...router.matchPath(href)), 142 ) 143 } else if (action === 'navigate') { 144 // @ts-ignore 145 navigation.navigate(...router.matchPath(href)) 146 } else { 147 throw Error('Unsupported navigator action.') 148 } 149 } 150 } 151 } 152 }, 153 [ 154 outerOnPress, 155 disableMismatchWarning, 156 displayText, 157 isExternal, 158 href, 159 openModal, 160 openLink, 161 closeModal, 162 action, 163 navigation, 164 ], 165 ) 166 167 const handleLongPress = React.useCallback(() => { 168 const requiresWarning = Boolean( 169 !disableMismatchWarning && 170 displayText && 171 isExternal && 172 linkRequiresWarning(href, displayText), 173 ) 174 175 if (requiresWarning) { 176 openModal({ 177 name: 'link-warning', 178 text: displayText, 179 href: href, 180 share: true, 181 }) 182 } else { 183 shareUrl(href) 184 } 185 }, [disableMismatchWarning, displayText, href, isExternal, openModal]) 186 187 const onLongPress = 188 isNative && isExternal && shareOnLongPress ? handleLongPress : undefined 189 190 return { 191 isExternal, 192 href, 193 onPress, 194 onLongPress, 195 } 196} 197 198export type LinkProps = Omit<BaseLinkProps, 'disableMismatchWarning'> & 199 Omit<ButtonProps, 'onPress' | 'disabled' | 'label'> 200 201/** 202 * A interactive element that renders as a `<a>` tag on the web. On mobile it 203 * will translate the `href` to navigator screens and params and dispatch a 204 * navigation action. 205 * 206 * Intended to behave as a web anchor tag. For more complex routing, use a 207 * `Button`. 208 */ 209export function Link({ 210 children, 211 to, 212 action = 'push', 213 onPress: outerOnPress, 214 download, 215 ...rest 216}: LinkProps) { 217 const {href, isExternal, onPress} = useLink({ 218 to, 219 displayText: typeof children === 'string' ? children : '', 220 action, 221 onPress: outerOnPress, 222 }) 223 224 return ( 225 <Button 226 label={href} 227 {...rest} 228 style={[a.justify_start, flatten(rest.style)]} 229 role="link" 230 accessibilityRole="link" 231 href={href} 232 onPress={download ? undefined : onPress} 233 {...web({ 234 hrefAttrs: { 235 target: download ? undefined : isExternal ? 'blank' : undefined, 236 rel: isExternal ? 'noopener noreferrer' : undefined, 237 download, 238 }, 239 dataSet: { 240 // no underline, only `InlineLink` has underlines 241 noUnderline: '1', 242 }, 243 })}> 244 {children} 245 </Button> 246 ) 247} 248 249export type InlineLinkProps = React.PropsWithChildren< 250 BaseLinkProps & TextStyleProp & Pick<TextProps, 'selectable'> 251> 252 253export function InlineLinkText({ 254 children, 255 to, 256 action = 'push', 257 disableMismatchWarning, 258 style, 259 onPress: outerOnPress, 260 download, 261 selectable, 262 label, 263 shareOnLongPress, 264 ...rest 265}: InlineLinkProps) { 266 const t = useTheme() 267 const stringChildren = typeof children === 'string' 268 const {href, isExternal, onPress, onLongPress} = useLink({ 269 to, 270 displayText: stringChildren ? children : '', 271 action, 272 disableMismatchWarning, 273 onPress: outerOnPress, 274 shareOnLongPress, 275 }) 276 const { 277 state: hovered, 278 onIn: onHoverIn, 279 onOut: onHoverOut, 280 } = useInteractionState() 281 const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 282 const { 283 state: pressed, 284 onIn: onPressIn, 285 onOut: onPressOut, 286 } = useInteractionState() 287 const flattenedStyle = flatten(style) || {} 288 289 return ( 290 <Text 291 selectable={selectable} 292 accessibilityHint="" 293 accessibilityLabel={label || href} 294 {...rest} 295 style={[ 296 {color: t.palette.primary_500}, 297 (hovered || focused || pressed) && { 298 ...web({outline: 0}), 299 textDecorationLine: 'underline', 300 textDecorationColor: flattenedStyle.color ?? t.palette.primary_500, 301 }, 302 flattenedStyle, 303 ]} 304 role="link" 305 onPress={download ? undefined : onPress} 306 onLongPress={onLongPress} 307 onPressIn={onPressIn} 308 onPressOut={onPressOut} 309 onFocus={onFocus} 310 onBlur={onBlur} 311 onMouseEnter={onHoverIn} 312 onMouseLeave={onHoverOut} 313 accessibilityRole="link" 314 href={href} 315 {...web({ 316 hrefAttrs: { 317 target: download ? undefined : isExternal ? 'blank' : undefined, 318 rel: isExternal ? 'noopener noreferrer' : undefined, 319 download, 320 }, 321 dataSet: { 322 // default to no underline, apply this ourselves 323 noUnderline: '1', 324 }, 325 })}> 326 {children} 327 </Text> 328 ) 329}