diff --git a/src/components/images/Gallery/const.ts b/src/components/images/Gallery/const.ts index 2c7918349b..443b65ecb8 100644 --- a/src/components/images/Gallery/const.ts +++ b/src/components/images/Gallery/const.ts @@ -1,6 +1,3 @@ export const ITEM_GAP = 8 // tokens.space.sm export const MIN_ASPECT_RATIO = 2 / 3 // portrait limit export const MAX_ASPECT_RATIO = 3 / 2 // landscape limit - -export const POST_META_NO_CONTENT_OFFSET = {paddingTop: 10} -export const POST_EMBED_NO_CONTENT_OFFSET = {paddingTop: 6} diff --git a/src/components/images/Gallery/index.tsx b/src/components/images/Gallery/index.tsx index 6c4de9876d..7c3a84e9d0 100644 --- a/src/components/images/Gallery/index.tsx +++ b/src/components/images/Gallery/index.tsx @@ -42,6 +42,7 @@ import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' export * from './const' +export * from './maybeApplyGalleryOffsetStyles' interface GalleryProps { images: AppBskyEmbedImages.ViewImage[] diff --git a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts new file mode 100644 index 0000000000..5081b5a2d5 --- /dev/null +++ b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts @@ -0,0 +1,102 @@ +import { + AppBskyEmbedImages, + AppBskyEmbedRecordWithMedia, + type AppBskyFeedDefs, + AppBskyFeedPost, + type ModerationCause, + type ModerationUI, +} from '@atproto/api' + +import {unique} from '#/lib/moderation' +import {type AppModerationCause} from '#/components/Pills' +import {Features, features} from '#/analytics/features' +import * as bsky from '#/types/bsky' + +export const POST_META_NO_CONTENT_OFFSET = {paddingTop: 10} +export const POST_EMBED_NO_CONTENT_OFFSET = {paddingTop: 6} + +export function maybeApplyGalleryOffsetStyles( + placement: 'meta' | 'embed', + { + post, + modui, + additionalCauses, + }: { + post: AppBskyFeedDefs.PostView + modui: ModerationUI + additionalCauses?: ModerationCause[] | AppModerationCause[] + }, +) { + // don't ever check gates like this, except this one time + if (!features.isOn(Features.PostGalleryEmbedEnable)) return + + if ( + !bsky.dangerousIsType( + post.record, + AppBskyFeedPost.isRecord, + ) + ) { + return + } + + /* + * First check if we even have images + */ + const embed = post.record.embed + const isImageEmbed = + embed && + bsky.dangerousIsType( + embed, + AppBskyEmbedImages.isMain, + ) + const isRecordWithMedia = + embed && + bsky.dangerousIsType( + embed, + AppBskyEmbedRecordWithMedia.isMain, + ) + let hasImages = false + if (isImageEmbed) { + // one image, not a gallery + if (embed.images.length === 1) return + hasImages = true + } + if (isRecordWithMedia) { + if ( + bsky.dangerousIsType( + embed.media, + AppBskyEmbedImages.isMain, + ) + ) { + // one image, not a gallery + if (embed.media.images.length === 1) return + } + hasImages = true + } + if (!hasImages) return + + /* + * Then check if we have any text + */ + let hasLabels = false + if (modui.alert) { + hasLabels = modui.alerts.filter(unique).length > 0 + } + if (modui.inform) { + hasLabels = hasLabels || modui.informs.filter(unique).length > 0 + } + if (additionalCauses?.length) { + hasLabels = true + } + + /* + * If no text or labels, then we need a lil bump + */ + const shouldApplyOffset = !post.record.text && !hasLabels + + return shouldApplyOffset + ? placement === 'meta' + ? POST_META_NO_CONTENT_OFFSET + : POST_EMBED_NO_CONTENT_OFFSET + : {} +} diff --git a/src/screens/PostThread/components/ThreadItemPost.tsx b/src/screens/PostThread/components/ThreadItemPost.tsx index 11ddd1b026..841c2af745 100644 --- a/src/screens/PostThread/components/ThreadItemPost.tsx +++ b/src/screens/PostThread/components/ThreadItemPost.tsx @@ -34,8 +34,7 @@ import {useInteractionState} from '#/components/hooks/useInteractionState' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import { GalleryBleed, - POST_EMBED_NO_CONTENT_OFFSET, - POST_META_NO_CONTENT_OFFSET, + maybeApplyGalleryOffsetStyles, } from '#/components/images/Gallery' import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe' import {PostAlerts} from '#/components/moderation/PostAlerts' @@ -304,7 +303,11 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({ postHref={postHref} style={[ a.pb_xs, - !richText?.text && POST_META_NO_CONTENT_OFFSET, + maybeApplyGalleryOffsetStyles('meta', { + post, + modui: moderation.ui('contentList'), + additionalCauses: additionalPostAlerts, + }), ]} /> @@ -335,7 +338,11 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({ {post.embed && ( {post.embed ? ( + style={maybeApplyGalleryOffsetStyles('embed', { + post, + modui: moderation.ui('contentList'), + additionalCauses: [], + })}> { + const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri) + const rootPostUri = bsky.dangerousIsType( + post.record, + AppBskyFeedPost.isRecord, + ) + ? post.record?.reply?.root?.uri || post.uri + : undefined + const isControlledByViewer = + rootPostUri && new AtUri(rootPostUri).host === currentAccount?.did + return isControlledByViewer && isPostHiddenByThreadgate + ? [ + { + type: 'reply-hidden', + source: {type: 'user', did: currentAccount?.did}, + priority: 6, + }, + ] + : [] + }, [post, currentAccount?.did, threadgateHiddenReplies]) + return ( void post: AppBskyFeedDefs.PostView - threadgateRecord?: AppBskyFeedThreadgate.Record + additionalPostAlerts?: AppModerationCause[] }): React.ReactNode => { - const {currentAccount} = useSession() const [limitLines, setLimitLines] = useState( () => countLines(richText.text) >= MAX_POST_LINES, ) - const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({ - threadgateRecord, - }) - const additionalPostAlerts: AppModerationCause[] = useMemo(() => { - const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri) - const rootPostUri = bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) - ? post.record?.reply?.root?.uri || post.uri - : undefined - const isControlledByViewer = - rootPostUri && new AtUri(rootPostUri).host === currentAccount?.did - return isControlledByViewer && isPostHiddenByThreadgate - ? [ - { - type: 'reply-hidden', - source: {type: 'user', did: currentAccount?.did}, - priority: 6, - }, - ] - : [] - }, [post, currentAccount?.did, threadgateHiddenReplies]) const record = useMemo( () => @@ -503,7 +507,15 @@ let PostContent = ({ ) : undefined} {record && } {postEmbed ? ( - +