Merge branch 'bluesky-social:main' into patch-3
This commit is contained in:
commit
ab2b454be8
43 changed files with 1299 additions and 527 deletions
|
@ -18,6 +18,8 @@ import {Mark} from '@tiptap/core'
|
|||
import {Plugin, PluginKey} from '@tiptap/pm/state'
|
||||
import {Node as ProsemirrorNode} from '@tiptap/pm/model'
|
||||
import {Decoration, DecorationSet} from '@tiptap/pm/view'
|
||||
import {URL_REGEX} from '@atproto/api'
|
||||
|
||||
import {isValidDomain} from 'lib/strings/url-helpers'
|
||||
|
||||
export const LinkDecorator = Mark.create({
|
||||
|
@ -78,8 +80,7 @@ function linkDecorator() {
|
|||
|
||||
function iterateUris(str: string, cb: (from: number, to: number) => void) {
|
||||
let match
|
||||
const re =
|
||||
/(^|\s|\()((https?:\/\/[\S]+)|((?<domain>[a-z][a-z0-9]*(\.[a-z0-9]+)+)[\S]*))/gim
|
||||
const re = URL_REGEX
|
||||
while ((match = re.exec(str))) {
|
||||
let uri = match[2]
|
||||
if (!uri.startsWith('http')) {
|
||||
|
|
|
@ -18,28 +18,36 @@ import {Mark} from '@tiptap/core'
|
|||
import {Plugin, PluginKey} from '@tiptap/pm/state'
|
||||
import {Node as ProsemirrorNode} from '@tiptap/pm/model'
|
||||
import {Decoration, DecorationSet} from '@tiptap/pm/view'
|
||||
import {TAG_REGEX, TRAILING_PUNCTUATION_REGEX} from '@atproto/api'
|
||||
|
||||
function getDecorations(doc: ProsemirrorNode) {
|
||||
const decorations: Decoration[] = []
|
||||
|
||||
doc.descendants((node, pos) => {
|
||||
if (node.isText && node.text) {
|
||||
const regex = /(?:^|\s)(#[^\d\s]\S*)(?=\s)?/g
|
||||
const regex = TAG_REGEX
|
||||
const textContent = node.textContent
|
||||
|
||||
let match
|
||||
while ((match = regex.exec(textContent))) {
|
||||
const [matchedString, tag] = match
|
||||
const [matchedString, _, tag] = match
|
||||
|
||||
if (tag.length > 66) continue
|
||||
if (!tag || tag.replace(TRAILING_PUNCTUATION_REGEX, '').length > 64)
|
||||
continue
|
||||
|
||||
const [trailingPunc = ''] = tag.match(/\p{P}+$/u) || []
|
||||
const [trailingPunc = ''] = tag.match(TRAILING_PUNCTUATION_REGEX) || []
|
||||
const matchedFrom = match.index + matchedString.indexOf(tag)
|
||||
const matchedTo = matchedFrom + (tag.length - trailingPunc.length)
|
||||
|
||||
const from = match.index + matchedString.indexOf(tag)
|
||||
const to = from + (tag.length - trailingPunc.length)
|
||||
/*
|
||||
* The match is exclusive of `#` so we need to adjust the start of the
|
||||
* highlight by -1 to include the `#`
|
||||
*/
|
||||
const start = pos + matchedFrom - 1
|
||||
const end = pos + matchedTo
|
||||
|
||||
decorations.push(
|
||||
Decoration.inline(pos + from, pos + to, {
|
||||
Decoration.inline(start, end, {
|
||||
class: 'autolink',
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -52,7 +52,7 @@ export function HomeHeader(
|
|||
)
|
||||
|
||||
return (
|
||||
<HomeHeaderLayout>
|
||||
<HomeHeaderLayout tabBarAnchor={props.tabBarAnchor}>
|
||||
<TabBar
|
||||
key={items.join(',')}
|
||||
onPressSelected={props.onPressSelected}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||
import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile'
|
||||
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
|
||||
import {useShellLayout} from '#/state/shell/shell-layout'
|
||||
import {Logo} from '#/view/icons/Logo'
|
||||
import {Link, TextLink} from '../util/Link'
|
||||
import {Link} from '../util/Link'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconStyle,
|
||||
|
@ -16,41 +13,42 @@ import {useLingui} from '@lingui/react'
|
|||
import {msg} from '@lingui/macro'
|
||||
import {CogIcon} from '#/lib/icons'
|
||||
|
||||
export function HomeHeaderLayout({children}: {children: React.ReactNode}) {
|
||||
export function HomeHeaderLayout(props: {
|
||||
children: React.ReactNode
|
||||
tabBarAnchor: JSX.Element | null | undefined
|
||||
}) {
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
if (isMobile) {
|
||||
return <HomeHeaderLayoutMobile>{children}</HomeHeaderLayoutMobile>
|
||||
return <HomeHeaderLayoutMobile {...props} />
|
||||
} else {
|
||||
return <HomeHeaderLayoutTablet>{children}</HomeHeaderLayoutTablet>
|
||||
return <HomeHeaderLayoutDesktopAndTablet {...props} />
|
||||
}
|
||||
}
|
||||
|
||||
function HomeHeaderLayoutTablet({children}: {children: React.ReactNode}) {
|
||||
function HomeHeaderLayoutDesktopAndTablet({
|
||||
children,
|
||||
tabBarAnchor,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
tabBarAnchor: JSX.Element | null | undefined
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {headerMinimalShellTransform} = useMinimalShellMode()
|
||||
const {headerHeight} = useShellLayout()
|
||||
const {_} = useLingui()
|
||||
|
||||
return (
|
||||
// @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
|
||||
<Animated.View
|
||||
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
|
||||
onLayout={e => {
|
||||
headerHeight.value = e.nativeEvent.layout.height
|
||||
}}>
|
||||
<View style={[pal.view, styles.topBar]}>
|
||||
<TextLink
|
||||
type="title-lg"
|
||||
<>
|
||||
<View style={[pal.view, pal.border, styles.bar, styles.topBar]}>
|
||||
<Link
|
||||
href="/settings/following-feed"
|
||||
hitSlop={10}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Following Feed Preferences`)}
|
||||
accessibilityHint=""
|
||||
text={
|
||||
<FontAwesomeIcon
|
||||
icon="sliders"
|
||||
style={pal.textLight as FontAwesomeIconStyle}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
accessibilityHint="">
|
||||
<FontAwesomeIcon
|
||||
icon="sliders"
|
||||
style={pal.textLight as FontAwesomeIconStyle}
|
||||
/>
|
||||
</Link>
|
||||
<Logo width={28} />
|
||||
<Link
|
||||
href="/settings/saved-feeds"
|
||||
|
@ -61,32 +59,38 @@ function HomeHeaderLayoutTablet({children}: {children: React.ReactNode}) {
|
|||
<CogIcon size={22} strokeWidth={2} style={pal.textLight} />
|
||||
</Link>
|
||||
</View>
|
||||
{children}
|
||||
</Animated.View>
|
||||
{tabBarAnchor}
|
||||
<View style={[pal.view, pal.border, styles.bar, styles.tabBar]}>
|
||||
{children}
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
bar: {
|
||||
// @ts-ignore Web only
|
||||
left: 'calc(50% - 300px)',
|
||||
width: 600,
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
},
|
||||
topBar: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 18,
|
||||
paddingVertical: 8,
|
||||
marginTop: 8,
|
||||
width: '100%',
|
||||
paddingTop: 16,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
tabBar: {
|
||||
// @ts-ignore Web only
|
||||
position: 'sticky',
|
||||
zIndex: 1,
|
||||
// @ts-ignore Web only -prf
|
||||
left: 'calc(50% - 300px)',
|
||||
width: 600,
|
||||
top: 0,
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
zIndex: 1,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -23,6 +23,7 @@ export function HomeHeaderLayoutMobile({
|
|||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
tabBarAnchor: JSX.Element | null | undefined
|
||||
}) {
|
||||
const pal = usePalette('default')
|
||||
const {_} = useLingui()
|
||||
|
|
|
@ -159,7 +159,7 @@ export const TextLink = memo(function TextLink({
|
|||
dataSet,
|
||||
title,
|
||||
onPress,
|
||||
warnOnMismatchingLabel,
|
||||
disableMismatchWarning,
|
||||
navigationAction,
|
||||
...orgProps
|
||||
}: {
|
||||
|
@ -172,7 +172,7 @@ export const TextLink = memo(function TextLink({
|
|||
lineHeight?: number
|
||||
dataSet?: any
|
||||
title?: string
|
||||
warnOnMismatchingLabel?: boolean
|
||||
disableMismatchWarning?: boolean
|
||||
navigationAction?: 'push' | 'replace' | 'navigate'
|
||||
} & TextProps) {
|
||||
const {...props} = useLinkProps({to: sanitizeUrl(href)})
|
||||
|
@ -180,14 +180,14 @@ export const TextLink = memo(function TextLink({
|
|||
const {openModal, closeModal} = useModalControls()
|
||||
const openLink = useOpenLink()
|
||||
|
||||
if (warnOnMismatchingLabel && typeof text !== 'string') {
|
||||
if (!disableMismatchWarning && typeof text !== 'string') {
|
||||
console.error('Unable to detect mismatching label')
|
||||
}
|
||||
|
||||
props.onPress = React.useCallback(
|
||||
(e?: Event) => {
|
||||
const requiresWarning =
|
||||
warnOnMismatchingLabel &&
|
||||
!disableMismatchWarning &&
|
||||
linkRequiresWarning(href, typeof text === 'string' ? text : '')
|
||||
if (requiresWarning) {
|
||||
e?.preventDefault?.()
|
||||
|
@ -227,7 +227,7 @@ export const TextLink = memo(function TextLink({
|
|||
navigation,
|
||||
href,
|
||||
text,
|
||||
warnOnMismatchingLabel,
|
||||
disableMismatchWarning,
|
||||
navigationAction,
|
||||
openLink,
|
||||
],
|
||||
|
|
|
@ -13,11 +13,13 @@ import Animated from 'react-native-reanimated'
|
|||
import {useSetDrawerOpen} from '#/state/shell'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useTheme} from '#/alf'
|
||||
|
||||
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
|
||||
|
||||
export function ViewHeader({
|
||||
title,
|
||||
subtitle,
|
||||
canGoBack,
|
||||
showBackButton = true,
|
||||
hideOnScroll,
|
||||
|
@ -26,6 +28,7 @@ export function ViewHeader({
|
|||
renderButton,
|
||||
}: {
|
||||
title: string
|
||||
subtitle?: string
|
||||
canGoBack?: boolean
|
||||
showBackButton?: boolean
|
||||
hideOnScroll?: boolean
|
||||
|
@ -39,6 +42,7 @@ export function ViewHeader({
|
|||
const navigation = useNavigation<NavigationProp>()
|
||||
const {track} = useAnalytics()
|
||||
const {isDesktop, isTablet} = useWebMediaQueries()
|
||||
const t = useTheme()
|
||||
|
||||
const onPressBack = React.useCallback(() => {
|
||||
if (navigation.canGoBack()) {
|
||||
|
@ -71,42 +75,60 @@ export function ViewHeader({
|
|||
|
||||
return (
|
||||
<Container hideOnScroll={hideOnScroll || false} showBorder={showBorder}>
|
||||
{showBackButton ? (
|
||||
<TouchableOpacity
|
||||
testID="viewHeaderDrawerBtn"
|
||||
onPress={canGoBack ? onPressBack : onPressMenu}
|
||||
hitSlop={BACK_HITSLOP}
|
||||
style={canGoBack ? styles.backBtn : styles.backBtnWide}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={canGoBack ? _(msg`Back`) : _(msg`Menu`)}
|
||||
accessibilityHint={
|
||||
canGoBack ? '' : _(msg`Access navigation links and settings`)
|
||||
}>
|
||||
{canGoBack ? (
|
||||
<FontAwesomeIcon
|
||||
size={18}
|
||||
icon="angle-left"
|
||||
style={[styles.backIcon, pal.text]}
|
||||
/>
|
||||
) : !isTablet ? (
|
||||
<FontAwesomeIcon
|
||||
size={18}
|
||||
icon="bars"
|
||||
style={[styles.backIcon, pal.textLight]}
|
||||
/>
|
||||
<View style={{flex: 1}}>
|
||||
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
||||
{showBackButton ? (
|
||||
<TouchableOpacity
|
||||
testID="viewHeaderDrawerBtn"
|
||||
onPress={canGoBack ? onPressBack : onPressMenu}
|
||||
hitSlop={BACK_HITSLOP}
|
||||
style={canGoBack ? styles.backBtn : styles.backBtnWide}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={canGoBack ? _(msg`Back`) : _(msg`Menu`)}
|
||||
accessibilityHint={
|
||||
canGoBack ? '' : _(msg`Access navigation links and settings`)
|
||||
}>
|
||||
{canGoBack ? (
|
||||
<FontAwesomeIcon
|
||||
size={18}
|
||||
icon="angle-left"
|
||||
style={[styles.backIcon, pal.text]}
|
||||
/>
|
||||
) : !isTablet ? (
|
||||
<FontAwesomeIcon
|
||||
size={18}
|
||||
icon="bars"
|
||||
style={[styles.backIcon, pal.textLight]}
|
||||
/>
|
||||
) : null}
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
<View style={styles.titleContainer} pointerEvents="none">
|
||||
<Text type="title" style={[pal.text, styles.title]}>
|
||||
{title}
|
||||
</Text>
|
||||
<View style={styles.titleContainer} pointerEvents="none">
|
||||
<Text type="title" style={[pal.text, styles.title]}>
|
||||
{title}
|
||||
</Text>
|
||||
</View>
|
||||
{renderButton ? (
|
||||
renderButton()
|
||||
) : showBackButton ? (
|
||||
<View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
|
||||
) : null}
|
||||
</View>
|
||||
{subtitle ? (
|
||||
<View
|
||||
style={[styles.titleContainer, {marginTop: -3}]}
|
||||
pointerEvents="none">
|
||||
<Text
|
||||
style={[
|
||||
pal.text,
|
||||
styles.subtitle,
|
||||
t.atoms.text_contrast_medium,
|
||||
]}>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</View>
|
||||
) : undefined}
|
||||
</View>
|
||||
{renderButton ? (
|
||||
renderButton()
|
||||
) : showBackButton ? (
|
||||
<View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
|
||||
) : null}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
@ -185,7 +207,6 @@ function Container({
|
|||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
width: '100%',
|
||||
|
@ -207,12 +228,14 @@ const styles = StyleSheet.create({
|
|||
titleContainer: {
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
paddingRight: 10,
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
|
||||
subtitle: {
|
||||
fontSize: 13,
|
||||
},
|
||||
backBtn: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
|
|
|
@ -46,7 +46,7 @@ export function ContentHider({
|
|||
)
|
||||
}
|
||||
|
||||
const isMute = moderation.cause?.type === 'muted'
|
||||
const isMute = ['muted', 'muted-word'].includes(moderation.cause?.type || '')
|
||||
const desc = describeModerationCause(moderation.cause, 'content')
|
||||
return (
|
||||
<View testID={testID} style={[styles.outer, style]}>
|
||||
|
|
|
@ -47,7 +47,7 @@ export function PostHider({
|
|||
)
|
||||
}
|
||||
|
||||
const isMute = moderation.cause?.type === 'muted'
|
||||
const isMute = ['muted', 'muted-word'].includes(moderation.cause?.type || '')
|
||||
const desc = describeModerationCause(moderation.cause, 'content')
|
||||
return !override ? (
|
||||
<Pressable
|
||||
|
|
|
@ -114,7 +114,6 @@ export function RichText({
|
|||
href={link.uri}
|
||||
style={[style, lineHeightStyle, pal.link, {pointerEvents: 'auto'}]}
|
||||
dataSet={WORD_WRAP}
|
||||
warnOnMismatchingLabel
|
||||
selectable={selectable}
|
||||
/>,
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue