diff --git a/src/components/Post/Embed/index.tsx b/src/components/Post/Embed/index.tsx
index 927f8bf98b..a125db7845 100644
--- a/src/components/Post/Embed/index.tsx
+++ b/src/components/Post/Embed/index.tsx
@@ -42,6 +42,7 @@ import {
QuoteEmbedViewContext,
} from './types'
import {VideoEmbed} from './VideoEmbed'
+import {GalleryBleed} from '#/components/images/Gallery'
export {PostEmbedViewContext, QuoteEmbedViewContext} from './types'
@@ -320,43 +321,45 @@ export function QuoteEmbed({
)
return (
-
-
- {({active}) => (
- <>
- {!active && !linkDisabled && (
-
- )}
- {linkDisabled ? (
-
- {contents}
-
- ) : (
-
- {contents}
-
- )}
- >
- )}
-
-
+
+
+
+ {({active}) => (
+ <>
+ {!active && !linkDisabled && (
+
+ )}
+ {linkDisabled ? (
+
+ {contents}
+
+ ) : (
+
+ {contents}
+
+ )}
+ >
+ )}
+
+
+
)
}
diff --git a/src/components/images/Gallery/index-old.tsx b/src/components/images/Gallery/index-old.tsx
new file mode 100644
index 0000000000..3b04a55b0e
--- /dev/null
+++ b/src/components/images/Gallery/index-old.tsx
@@ -0,0 +1,332 @@
+import {
+ cloneElement,
+ createContext,
+ useContext,
+ useMemo,
+ useRef,
+ useState,
+ isValidElement,
+} from 'react'
+import {FlatList, Pressable, useWindowDimensions, View} from 'react-native'
+import {DrawerGestureContext} from 'react-native-drawer-layout'
+import {Gesture, GestureDetector} from 'react-native-gesture-handler'
+import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated'
+import {Image} from 'expo-image'
+import {type AppBskyEmbedImages} from '@atproto/api'
+import {utils} from '@bsky.app/alf'
+import {Trans, useLingui} from '@lingui/react/macro'
+
+import {mergeRefs} from '#/lib/merge-refs'
+import {type Dimensions} from '#/lib/media/types'
+import {useA11y} from '#/state/a11y'
+import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
+import {atoms as a, useTheme} from '#/alf'
+import {AutoSizedImage} from '#/components/images/AutoSizedImage'
+import {MediaInsetBorder} from '#/components/MediaInsetBorder'
+import {PostEmbedViewContext} from '#/components/Post/Embed/types'
+import {Text} from '#/components/Typography'
+import {useAnalytics} from '#/analytics'
+
+const CONTAINER_ASPECT_RATIO = 3 / 2
+const ITEM_GAP = 8 // tokens.space.sm
+const MIN_PEEK = 40
+
+interface GalleryProps {
+ images: AppBskyEmbedImages.ViewImage[]
+ onPress?: (
+ index: number,
+ containerRefs: AnimatedRef[],
+ fetchedDims: (Dimensions | null)[],
+ ) => void
+ onPressIn?: (index: number) => void
+ viewContext?: PostEmbedViewContext
+}
+
+const Context = createContext<{
+ ref: React.RefObject
+}>({
+ ref: {current: null},
+})
+
+export function GalleryBleed({children}: {children: React.ReactNode}) {
+ const ref = useRef(null)
+
+ if (!isValidElement(children)) {
+ throw new Error('GalleryBleed children must be a single React element')
+ }
+
+ const node = children as React.ReactElement
+
+ return (
+
+ {cloneElement(node, {
+ ref: mergeRefs([ref, node?.props?.ref]),
+ })}
+
+ )
+}
+
+export function Gallery({
+ images,
+ onPress,
+ onPressIn,
+ viewContext,
+}: GalleryProps) {
+ const t = useTheme()
+ const {t: l} = useLingui()
+ const ax = useAnalytics()
+ const {screenReaderEnabled} = useA11y()
+ const largeAltBadge = useLargeAltBadgeEnabled()
+ const currentPageRef = useRef(0)
+ const {width: windowWidth} = useWindowDimensions()
+ const [leftOffset, setLeftOffset] = useState(0)
+ const [containerWidth, setContainerWidth] = useState(0)
+
+ const containerRefs = useRef[]>([]).current
+ const thumbDimsRef = useRef<(Dimensions | null)[]>([])
+
+ const ref0 = useAnimatedRef()
+ const ref1 = useAnimatedRef()
+ const ref2 = useAnimatedRef()
+ const ref3 = useAnimatedRef()
+ const refs = [ref0, ref1, ref2, ref3]
+ for (let i = 0; i < images.length; i++) {
+ containerRefs[i] = refs[i]
+ }
+
+ const isWithinQuote =
+ viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
+ const hideBadges = isWithinQuote
+
+ const containerHeight =
+ containerWidth > 0 ? containerWidth / CONTAINER_ASPECT_RATIO : 0
+ // Bleed: full-width carousel that extends to screen edges
+ // In quotes: small bleed to the quote card border (p_md = 12px)
+ const QUOTE_PADDING = 12
+ const bleed = !isWithinQuote
+ const insetLeft = bleed
+ ? leftOffset || windowWidth - containerWidth
+ : QUOTE_PADDING
+ const insetRight = bleed
+ ? windowWidth - insetLeft - containerWidth
+ : QUOTE_PADDING
+
+ const getItemWidth = (image: AppBskyEmbedImages.ViewImage, index: number) => {
+ const ar = image.aspectRatio
+ let width = containerHeight // default to square-ish
+ if (ar && ar.width > 0 && ar.height > 0) {
+ const ratio = ar.width / ar.height
+ // Width derived from image's own aspect ratio at the fixed container height
+ // Clamp aspect ratio between 2:3 (portrait) and 3:2 (landscape)
+ const clamped = Math.max(2 / 3, Math.min(ratio, 3 / 2))
+ width = containerHeight * clamped
+ }
+ // Ensure the first image leaves room for a peek of the next
+ if (index === 0 && images.length > 1) {
+ width = Math.min(width, containerWidth - MIN_PEEK)
+ }
+ return width
+ }
+
+ if (screenReaderEnabled) {
+ return (
+
+ {images.map((image, index) => (
+
+ onPress?.(index, [containerRef], [dims])
+ }
+ onPressIn={() => onPressIn?.(index)}
+ hideBadge={
+ viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
+ }
+ />
+ ))}
+
+ )
+ }
+
+ return (
+ 0
+ ? {height: containerHeight, overflow: 'visible'}
+ : {aspectRatio: CONTAINER_ASPECT_RATIO}
+ }
+ onLayout={e => {
+ const w = e.nativeEvent.layout.width
+ if (w > 0) {
+ setContainerWidth(w)
+ }
+ e.target.measureInWindow((x: number) => {
+ if (x > 0) {
+ setLeftOffset(x)
+ }
+ })
+ }}>
+ {containerWidth > 0 && (
+
+ {
+ const offsetX = e.nativeEvent.contentOffset.x
+ // Determine which item is most visible based on scroll position
+ let accumulated = insetLeft // account for left content padding
+ let page = 0
+ for (let i = 0; i < images.length; i++) {
+ const w = getItemWidth(images[i], i) + ITEM_GAP
+ if (offsetX < accumulated + w / 2) {
+ page = i
+ break
+ }
+ accumulated += w
+ page = i
+ }
+ if (page !== currentPageRef.current) {
+ ax.metric('post:gallery:swipe', {
+ fromIndex: currentPageRef.current,
+ toIndex: page,
+ totalImages: images.length,
+ })
+ currentPageRef.current = page
+ }
+ }}
+ scrollEventThrottle={16}
+ keyExtractor={(_, index) => String(index)}
+ renderItem={({item: image, index}) => (
+
+ {
+ ax.metric('post:gallery:openLightbox', {
+ imageIndex: index,
+ totalImages: images.length,
+ })
+ onPress(
+ index,
+ containerRefs.slice(0, images.length),
+ thumbDimsRef.current.slice(),
+ )
+ }
+ : undefined
+ }
+ onPressIn={onPressIn ? () => onPressIn(index) : undefined}
+ android_ripple={{
+ color: utils.alpha(t.atoms.bg.backgroundColor, 0.2),
+ foreground: true,
+ }}
+ accessibilityRole="button"
+ accessibilityLabel={
+ image.alt || l`Image ${index + 1} of ${images.length}`
+ }
+ accessibilityHint={l`Opens full image`}
+ style={[
+ a.flex_1,
+ a.rounded_md,
+ a.overflow_hidden,
+ t.atoms.bg_contrast_25,
+ ]}>
+ {
+ thumbDimsRef.current[index] = {
+ width: e.source.width,
+ height: e.source.height,
+ }
+ }}
+ loading={index === 0 ? 'eager' : 'lazy'}
+ />
+
+
+ {image.alt && !hideBadges ? (
+
+
+ ALT
+
+
+ ) : null}
+
+ )}
+ />
+
+ )}
+
+ )
+}
+
+function DrawerGestureBlocker({children}: {children: React.ReactNode}) {
+ const drawerGesture = useContext(DrawerGestureContext)
+
+ const nativeGesture = useMemo(() => {
+ const gesture = Gesture.Native()
+ if (drawerGesture) {
+ gesture.blocksExternalGesture(drawerGesture)
+ }
+ return gesture
+ }, [drawerGesture])
+
+ return {children}
+}
diff --git a/src/components/images/Gallery/index.web.tsx b/src/components/images/Gallery/index-old.web.tsx
similarity index 100%
rename from src/components/images/Gallery/index.web.tsx
rename to src/components/images/Gallery/index-old.web.tsx
diff --git a/src/components/images/Gallery/index.tsx b/src/components/images/Gallery/index.tsx
index 39cb0a6a44..7607b4ea8a 100644
--- a/src/components/images/Gallery/index.tsx
+++ b/src/components/images/Gallery/index.tsx
@@ -1,4 +1,12 @@
-import {useContext, useMemo, useRef, useState} from 'react'
+import {
+ cloneElement,
+ createContext,
+ useContext,
+ useMemo,
+ useRef,
+ useState,
+ isValidElement,
+} from 'react'
import {FlatList, Pressable, useWindowDimensions, View} from 'react-native'
import {DrawerGestureContext} from 'react-native-drawer-layout'
import {Gesture, GestureDetector} from 'react-native-gesture-handler'
@@ -8,15 +16,17 @@ import {type AppBskyEmbedImages} from '@atproto/api'
import {utils} from '@bsky.app/alf'
import {Trans, useLingui} from '@lingui/react/macro'
+import {mergeRefs} from '#/lib/merge-refs'
import {type Dimensions} from '#/lib/media/types'
import {useA11y} from '#/state/a11y'
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
-import {atoms as a, useTheme} from '#/alf'
+import {atoms as a, useTheme, useBreakpoints} from '#/alf'
import {AutoSizedImage} from '#/components/images/AutoSizedImage'
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {PostEmbedViewContext} from '#/components/Post/Embed/types'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
+import {BlockDrawerGesture} from '#/view/shell/BlockDrawerGesture'
const CONTAINER_ASPECT_RATIO = 3 / 2
const ITEM_GAP = 8 // tokens.space.sm
@@ -33,6 +43,36 @@ interface GalleryProps {
viewContext?: PostEmbedViewContext
}
+const Context = createContext<{
+ ref: React.RefObject
+}>({
+ ref: {current: null},
+})
+
+export function GalleryBleed({children}: {children: React.ReactNode}) {
+ const ref = useRef(null)
+
+ if (!isValidElement(children)) {
+ throw new Error('GalleryBleed children must be a single React element')
+ }
+
+ const node = children as React.ReactElement
+
+ return (
+
+ {cloneElement(node, {
+ ref: mergeRefs([ref, node?.props?.ref]),
+ })}
+
+ )
+}
+
+export function useGalleryBleedRef() {
+ const {ref} = useContext(Context)
+ // TODO throw?
+ return ref
+}
+
export function Gallery({
images,
onPress,
@@ -44,256 +84,157 @@ export function Gallery({
const ax = useAnalytics()
const {screenReaderEnabled} = useA11y()
const largeAltBadge = useLargeAltBadgeEnabled()
- const currentPageRef = useRef(0)
- const {width: windowWidth} = useWindowDimensions()
- const [leftOffset, setLeftOffset] = useState(0)
- const [containerWidth, setContainerWidth] = useState(0)
-
- const containerRefs = useRef[]>([]).current
- const thumbDimsRef = useRef<(Dimensions | null)[]>([])
-
- const ref0 = useAnimatedRef()
- const ref1 = useAnimatedRef()
- const ref2 = useAnimatedRef()
- const ref3 = useAnimatedRef()
- const refs = [ref0, ref1, ref2, ref3]
- for (let i = 0; i < images.length; i++) {
- containerRefs[i] = refs[i]
- }
-
- const isWithinQuote =
- viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
- const hideBadges = isWithinQuote
-
- const containerHeight =
- containerWidth > 0 ? containerWidth / CONTAINER_ASPECT_RATIO : 0
- // Bleed: full-width carousel that extends to screen edges
- // In quotes: small bleed to the quote card border (p_md = 12px)
- const QUOTE_PADDING = 12
- const bleed = !isWithinQuote
- const insetLeft = bleed
- ? leftOffset || windowWidth - containerWidth
- : QUOTE_PADDING
- const insetRight = bleed
- ? windowWidth - insetLeft - containerWidth
- : QUOTE_PADDING
-
- const getItemWidth = (image: AppBskyEmbedImages.ViewImage, index: number) => {
- const ar = image.aspectRatio
- let width = containerHeight // default to square-ish
- if (ar && ar.width > 0 && ar.height > 0) {
- const ratio = ar.width / ar.height
- // Width derived from image's own aspect ratio at the fixed container height
- // Clamp aspect ratio between 2:3 (portrait) and 3:2 (landscape)
- const clamped = Math.max(2 / 3, Math.min(ratio, 3 / 2))
- width = containerHeight * clamped
+ const bps = useBreakpoints()
+ const window = useWindowDimensions()
+ const contentHeight = useMemo(() => {
+ if (bps.gtMobile) {
+ return 300
+ } else if (bps.gtPhone) {
+ return 260
+ } else {
+ return 200
}
- // Ensure the first image leaves room for a peek of the next
- if (index === 0 && images.length > 1) {
- width = Math.min(width, containerWidth - MIN_PEEK)
- }
- return width
- }
+ }, [bps])
- if (screenReaderEnabled) {
- return (
-
- {images.map((image, index) => (
-
- onPress?.(index, [containerRef], [dims])
- }
- onPressIn={() => onPressIn?.(index)}
- hideBadge={
- viewContext === PostEmbedViewContext.FeedEmbedRecordWithMedia
- }
- />
- ))}
-
- )
+ /*
+ * Container overflow styles
+ */
+ const bleedRef = useGalleryBleedRef()
+ const [bleedDims, setBleedDims] = useState<{
+ left: number
+ right: number
+ width: number
+ }>()
+ const measureBleed = () => {
+ bleedRef?.current?.measureInWindow((x, _y, width) => {
+ setBleedDims({left: x, right: x + width, width})
+ })
}
+ const contentRef = useRef(null)
+ const [contentDims, setContentDims] = useState<{
+ left: number
+ right: number
+ width: number
+ }>()
+ const measureContent = () => {
+ contentRef?.current?.measureInWindow((x, _y, width) => {
+ setContentDims({left: x, right: x + width, width})
+ })
+ }
+ const insetLeft =
+ bleedDims && contentDims
+ ? Math.max(0, contentDims.left - bleedDims.left)
+ : 999
+ const insetRight =
+ bleedDims && contentDims
+ ? Math.max(0, bleedDims.right - contentDims.right)
+ : 999
+ const width = bleedDims ? bleedDims.width : Math.min(600, window.width)
+ /* End container overflow styles */
return (
0
- ? {height: containerHeight, overflow: 'visible'}
- : {aspectRatio: CONTAINER_ASPECT_RATIO}
- }
- onLayout={e => {
- const w = e.nativeEvent.layout.width
- if (w > 0) {
- setContainerWidth(w)
- }
- e.target.measureInWindow((x: number) => {
- if (x > 0) {
- setLeftOffset(x)
- }
- })
+ ref={contentRef}
+ style={[
+ a.w_full,
+ {
+ height: contentHeight,
+ overflow: 'visible',
+ },
+ ]}
+ onLayout={() => {
+ measureBleed()
+ measureContent()
}}>
- {containerWidth > 0 && (
-
- {
- const offsetX = e.nativeEvent.contentOffset.x
- // Determine which item is most visible based on scroll position
- let accumulated = insetLeft // account for left content padding
- let page = 0
- for (let i = 0; i < images.length; i++) {
- const w = getItemWidth(images[i], i) + ITEM_GAP
- if (offsetX < accumulated + w / 2) {
- page = i
- break
- }
- accumulated += w
- page = i
- }
- if (page !== currentPageRef.current) {
- ax.metric('post:gallery:swipe', {
- fromIndex: currentPageRef.current,
- toIndex: page,
- totalImages: images.length,
- })
- currentPageRef.current = page
- }
- }}
- scrollEventThrottle={16}
- keyExtractor={(_, index) => String(index)}
- renderItem={({item: image, index}) => (
-
- {
- ax.metric('post:gallery:openLightbox', {
- imageIndex: index,
- totalImages: images.length,
- })
- onPress(
- index,
- containerRefs.slice(0, images.length),
- thumbDimsRef.current.slice(),
- )
- }
- : undefined
- }
- onPressIn={onPressIn ? () => onPressIn(index) : undefined}
- android_ripple={{
- color: utils.alpha(t.atoms.bg.backgroundColor, 0.2),
- foreground: true,
- }}
- accessibilityRole="button"
- accessibilityLabel={
- image.alt || l`Image ${index + 1} of ${images.length}`
- }
- accessibilityHint={l`Opens full image`}
- style={[
- a.flex_1,
- a.rounded_md,
- a.overflow_hidden,
- t.atoms.bg_contrast_25,
- ]}>
- {
- thumbDimsRef.current[index] = {
- width: e.source.width,
- height: e.source.height,
- }
- }}
- loading={index === 0 ? 'eager' : 'lazy'}
- />
-
-
- {image.alt && !hideBadges ? (
-
-
- ALT
-
-
- ) : null}
-
- )}
- />
-
- )}
+
+ item.thumb}
+ renderItem={({item}) => {
+ return
+ }}
+ style={[{
+ height: contentHeight,
+ marginLeft: -insetLeft,
+ width,
+ }, a.debug]}
+ contentContainerStyle={{
+ gap: ITEM_GAP,
+ paddingLeft: insetLeft,
+ paddingRight: insetRight,
+ }}
+ />
+
)
}
-function DrawerGestureBlocker({children}: {children: React.ReactNode}) {
- const drawerGesture = useContext(DrawerGestureContext)
-
- const nativeGesture = useMemo(() => {
- const gesture = Gesture.Native()
- if (drawerGesture) {
- gesture.blocksExternalGesture(drawerGesture)
- }
- return gesture
- }, [drawerGesture])
-
- return {children}
+function getAspectRatio({
+ width,
+ height,
+}: {width?: number; height?: number} = {}) {
+ if (width && width > 0 && height && height > 0) {
+ return width / height
+ }
+ return undefined
+}
+
+function computeDims({
+ height,
+ aspectRatio,
+}: {
+ height: number
+ aspectRatio?: number
+}) {
+ /*
+ * Old images, or images from other clients can sometimes not have
+ * aspectRatio populated. In these cases, default to square and we'll
+ * resize once the image loads.
+ */
+ const width = Math.floor(height * (aspectRatio ?? 1))
+ return {width, height, aspectRatio}
+}
+
+function GalleryImage({
+ contentHeight: height,
+ image,
+}: {
+ contentHeight: number
+ image: AppBskyEmbedImages.ViewImage
+}) {
+ const t = useTheme()
+ const [aspectRatio, setAspectRatio] = useState(() =>
+ getAspectRatio(image.aspectRatio),
+ )
+ const dims = computeDims({height, aspectRatio})
+
+ return (
+
+ {
+ const ar = getAspectRatio(e.source)
+ if (ar && ar !== aspectRatio) {
+ setAspectRatio(ar)
+ }
+ }}
+ />
+
+ )
}
diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx
index 42574c7408..ff639d7292 100644
--- a/src/screens/PostThread/components/ThreadItemAnchor.tsx
+++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx
@@ -58,6 +58,7 @@ import {WhoCanReply} from '#/components/WhoCanReply'
import {useAnalytics} from '#/analytics'
import {useActorStatus} from '#/features/liveNow'
import * as bsky from '#/types/bsky'
+import {GalleryBleed} from '#/components/images/Gallery'
export function ThreadItemAnchor({
item,
@@ -308,234 +309,243 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({
return (
<>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ {sanitizeDisplayName(
+ post.author.displayName ||
+ sanitizeHandle(post.author.handle),
+ moderation.ui('displayName'),
+ )}
+
+
+
+
+
+
- {sanitizeDisplayName(
- post.author.displayName ||
- sanitizeHandle(post.author.handle),
- moderation.ui('displayName'),
- )}
+ {sanitizeHandle(post.author.handle, '@')}
-
-
-
-
-
-
- {sanitizeHandle(post.author.handle, '@')}
-
-
-
-
-
-
-
-
-
-
-
-
- {richText?.text ? (
-
- ) : undefined}
-
- {post.embed && (
-
-
+
- )}
-
-
- {post.repostCount !== 0 ||
- post.likeCount !== 0 ||
- post.quoteCount !== 0 ||
- post.bookmarkCount !== 0 ? (
- // Show this section unless we're *sure* it has no engagement.
+
+
+
+
+
+
+
+
+
+ {richText?.text ? (
+
+ ) : undefined}
+
+ {post.embed && (
+
+
+
+ )}
+
+
+ {post.repostCount !== 0 ||
+ post.likeCount !== 0 ||
+ post.quoteCount !== 0 ||
+ post.bookmarkCount !== 0 ? (
+ // Show this section unless we're *sure* it has no engagement.
+
+ {post.repostCount != null && post.repostCount !== 0 ? (
+
+
+
+
+ {formatPostStatCount(post.repostCount)}
+ {' '}
+
+
+
+
+ ) : null}
+ {post.quoteCount != null &&
+ post.quoteCount !== 0 &&
+ !post.viewer?.embeddingDisabled ? (
+
+
+
+
+ {formatPostStatCount(post.quoteCount)}
+ {' '}
+
+
+
+
+ ) : null}
+ {post.likeCount != null && post.likeCount !== 0 ? (
+
+
+
+
+ {formatPostStatCount(post.likeCount)}
+ {' '}
+
+
+
+
+ ) : null}
+ {post.bookmarkCount != null && post.bookmarkCount !== 0 ? (
+
+
+
+ {formatPostStatCount(post.bookmarkCount)}
+ {' '}
+
+
+
+ ) : null}
+
+ ) : null}
- {post.repostCount != null && post.repostCount !== 0 ? (
-
-
-
-
- {formatPostStatCount(post.repostCount)}
- {' '}
-
-
-
-
- ) : null}
- {post.quoteCount != null &&
- post.quoteCount !== 0 &&
- !post.viewer?.embeddingDisabled ? (
-
-
-
-
- {formatPostStatCount(post.quoteCount)}
- {' '}
-
-
-
-
- ) : null}
- {post.likeCount != null && post.likeCount !== 0 ? (
-
-
-
-
- {formatPostStatCount(post.likeCount)}
- {' '}
-
-
-
-
- ) : null}
- {post.bookmarkCount != null && post.bookmarkCount !== 0 ? (
-
-
-
- {formatPostStatCount(post.bookmarkCount)}
- {' '}
-
-
-
- ) : null}
+
+
+
- ) : null}
-
-
-
-
+
-
-
+
>
)
})
diff --git a/src/screens/PostThread/components/ThreadItemPost.tsx b/src/screens/PostThread/components/ThreadItemPost.tsx
index 87aa551330..0c9bfd14a4 100644
--- a/src/screens/PostThread/components/ThreadItemPost.tsx
+++ b/src/screens/PostThread/components/ThreadItemPost.tsx
@@ -45,6 +45,7 @@ import * as Skele from '#/components/Skeleton'
import {SubtleHover} from '#/components/SubtleHover'
import {Text} from '#/components/Typography'
import {useActorStatus} from '#/features/liveNow'
+import {GalleryBleed} from '#/components/images/Gallery'
export type ThreadItemPostProps = {
item: Extract
@@ -131,18 +132,20 @@ const ThreadItemPostOuterWrapper = memo(function ThreadItemPostOuterWrapper({
!item.ui.showParentReplyLine && overrides?.topBorder !== true
return (
-
- {children}
-
+
+
+ {children}
+
+
)
})
diff --git a/src/screens/PostThread/components/ThreadItemTreePost.tsx b/src/screens/PostThread/components/ThreadItemTreePost.tsx
index 82de0247e2..8626c5edff 100644
--- a/src/screens/PostThread/components/ThreadItemTreePost.tsx
+++ b/src/screens/PostThread/components/ThreadItemTreePost.tsx
@@ -44,6 +44,7 @@ import {RichText} from '#/components/RichText'
import * as Skele from '#/components/Skeleton'
import {SubtleHover} from '#/components/SubtleHover'
import {Text} from '#/components/Typography'
+import {GalleryBleed} from '#/components/images/Gallery'
/**
* Mimic the space in PostMeta
@@ -129,33 +130,35 @@ const ThreadItemTreePostOuterWrapper = memo(
const indents = Math.max(0, item.ui.indent - 1)
return (
-
- {Array.from(Array(indents)).map((_, n: number) => {
- const isSkipped = item.ui.skippedIndentIndices.has(n)
- return (
-
+
- )
- })}
- {children}
-
+ ],
+ ]}>
+ {Array.from(Array(indents)).map((_, n: number) => {
+ const isSkipped = item.ui.skippedIndentIndices.has(n)
+ return (
+
+ )
+ })}
+ {children}
+
+
)
},
)
diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx
index 691010cdc6..7b51c84579 100644
--- a/src/view/com/posts/PostFeedItem.tsx
+++ b/src/view/com/posts/PostFeedItem.tsx
@@ -51,6 +51,7 @@ import {useAnalytics} from '#/analytics'
import {useActorStatus} from '#/features/liveNow'
import * as bsky from '#/types/bsky'
import {PostFeedReason} from './PostFeedReason'
+import {GalleryBleed} from '#/components/images/Gallery'
interface FeedItemProps {
record: AppBskyFeedPost.Record
@@ -294,118 +295,120 @@ let FeedItemInner = ({
}, [reason])
return (
- {
- setHover(true)
- }}
- onPointerLeave={() => {
- setHover(false)
- }}>
-
-
-
- {isThreadChild && (
-
- )}
-
-
-
- {reason && (
-
- )}
-
-
-
-
-
-
- {isThreadParent && (
-
- )}
-
-
-
- {showReplyTo &&
- (parentAuthor || isParentBlocked || isParentNotFound) && (
-
+ {
+ setHover(true)
+ }}
+ onPointerLeave={() => {
+ setHover(false)
+ }}>
+
+
+
+ {isThreadChild && (
+
)}
-
-
-
+
+
+
+ {reason && (
+
+ )}
+
-
-
-
+
+
+
+ {isThreadParent && (
+
+ )}
+
+
+
+ {showReplyTo &&
+ (parentAuthor || isParentBlocked || isParentNotFound) && (
+
+ )}
+
+
+
+
+
+
+
+
+
)
}
FeedItemInner = memo(FeedItemInner)