61004b887b
* WIP * Sorting working * Rough handling of hidden/muted * Better muted/hidden sorting and handling * Clarify some naming * Fix parents * Handle first reply under highlighted/composer * WIP RaW * WIP optimistic * Optimistic WIP * Little cleanup, inserting dupes * Re-org * Add in new optimistic insert logic * Update types * Sorta working linear view optimistic state * Simple working version, no pref for OP * Working optimistic reply insertions, preference for OP * Ensure deletes are coming through * WIP scroll handling * WIP scroll tweaks * Clean up scrolling * Clean up onPostSuccess * Add annotations * Fix highlighted post calc * WIP kill me * Update APIs * Nvm don't kill me * Fix optimistic insert * Handle read more cases in tree view * Basically working read more * Handle linear view * Reorg * More reorg * Split up thread post components * New reply tree layout * Fix up traversal metadata * Tighten some spacing * Use indent ya idiot * Some linear mode cleanup * Fix lines on read more items * Vibe coding to success * Almost there with read mores * Update APIs * Bump sdk * Update import * Checkpoint new traversal * Checkpoint cleanup * Checkpoint, need to fix blocked posts * Checkpoint: think we're good, needs more cleanup * Clean it up * Two passes only * Set to default params, update comment * Fix render bug on native * Checkpoint parent rendering, can opt for slower handling here * Clean up parent handling, reply handling * Fix read more extra space * Fix read more in linear view * Fix hidden reply handling, seen count, before/after calc * Update naming * Rename Slice to ThreadItem * Add basic post and anchor skeletons * Refactor client-side hidden * WIP hidden fetching * Update types * Clean up query a bit * Scrolling still broken * Ok maybe fix scrolling * Checkpoint move state into meta query * Don't load remote hidden items unless needed * skeleton view * Reset hidden items when params change * Split up traversal and avoid multiple passes * Clean up * Checkpoint: handling exhausted replies * Clean up traversal functions further * Clean up pagination * Limit optimistic reply depth * Handle optimistic insert in hidden replies * Share root query key for easier cache extraction * Make blurred posts not look like ass * Fix double deleted item * Make optimistic deleted state not look like crap in tree view * Fix parents traversal 4 real * Rename tree post * Make optimistic deletions of linear posts not look bad * Rename linear post components * Handle tombstone views * Rename read more component * Add moreParents handling * Align interaction states of read more * Fix read more on FF * Tree view skeleton * Reply composer skele * Remove hack for showing more replies * Checkpoint: sort change scrolling fixed * Checkpoint: learned new things, reset to base * Feature gate * Rename * Replace show more * Update settings screen * Update pkg and endpoint * Remove console * Eureka * Cleanup last commit * No tests atm * Remove scroll provider * Clean up callbacks, better error state * Remove todo * Remove todo * Remove todos * Format * Ok I think scrolling is solid * Add back mobile compose input * Ok need to compute headerHeight every time * Update comments * Ok button up web too * Threads v2 tweaks (#8467) * fix error screen collapsing * use personx icon for blocked posts * Remove height/width * Revert unused Header change * Clarify code * Relate consts to theme values * Remove debug code * Typo * Fix debounce of threads prefs * Update metadata comments, dev mode * Missed a spot * Clean up todo * Fix up no-unauthenticated posts * Truncate parents if no-unauth * Update getBranch docs * Remove debug code * Expand fetching in some cases * Clear scroll need for root post to fix jump bug * Fix reply composer skeleton state * Remove uneeded initialized value * Add profile shadow cache * Some metrics * prettier tweak * eslint ignore * Fix optimistic insertion * Typo * Rename, comment * Remove wait * Counter naming * Replies seen counter for moderated sub-trees * Remove borders on skeleton * Align tombstone with optimistic deletion state * Fix optimistic deletion for thread * Add tree view icon * Rename * Cleanup * Update settings copy * Header menu open metric * Bump package * Better reply prompt (#8474) * restyle reply prompt * hide bottom bar border for cleaner look * use new border hiding hook in DMs * create `transparentifyColor` function * adjust padding * fix padding in immersive lpayer * Apply suggestions from code review Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Integrate post-source (cherry picked from commit fe053e9b38395a4fcb30a4367bc800f64ea84fe9) --------- Co-authored-by: Samuel Newman <mozzius@protonmail.com> Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
177 lines
5.1 KiB
TypeScript
177 lines
5.1 KiB
TypeScript
import {useEffect, useMemo, useState} from 'react'
|
|
import {
|
|
AppBskyEmbedRecord,
|
|
AppBskyEmbedRecordWithMedia,
|
|
type AppBskyFeedDefs,
|
|
} from '@atproto/api'
|
|
import {type QueryClient} from '@tanstack/react-query'
|
|
import EventEmitter from 'eventemitter3'
|
|
|
|
import {batchedUpdates} from '#/lib/batchedUpdates'
|
|
import {findAllPostsInQueryData as findAllPostsInExploreFeedPreviewsQueryData} from '#/state/queries/explore-feed-previews'
|
|
import {findAllPostsInQueryData as findAllPostsInNotifsQueryData} from '#/state/queries/notifications/feed'
|
|
import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/queries/post-feed'
|
|
import {findAllPostsInQueryData as findAllPostsInQuoteQueryData} from '#/state/queries/post-quotes'
|
|
import {findAllPostsInQueryData as findAllPostsInThreadQueryData} from '#/state/queries/post-thread'
|
|
import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts'
|
|
import {findAllPostsInQueryData as findAllPostsInThreadV2QueryData} from '#/state/queries/usePostThread/queryCache'
|
|
import {useProfileShadow} from './profile-shadow'
|
|
import {castAsShadow, type Shadow} from './types'
|
|
export type {Shadow} from './types'
|
|
|
|
export interface PostShadow {
|
|
likeUri: string | undefined
|
|
repostUri: string | undefined
|
|
isDeleted: boolean
|
|
embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined
|
|
pinned: boolean
|
|
}
|
|
|
|
export const POST_TOMBSTONE = Symbol('PostTombstone')
|
|
|
|
const emitter = new EventEmitter()
|
|
const shadows: WeakMap<
|
|
AppBskyFeedDefs.PostView,
|
|
Partial<PostShadow>
|
|
> = new WeakMap()
|
|
|
|
export function usePostShadow(
|
|
post: AppBskyFeedDefs.PostView,
|
|
): Shadow<AppBskyFeedDefs.PostView> | typeof POST_TOMBSTONE {
|
|
const [shadow, setShadow] = useState(() => shadows.get(post))
|
|
const [prevPost, setPrevPost] = useState(post)
|
|
if (post !== prevPost) {
|
|
setPrevPost(post)
|
|
setShadow(shadows.get(post))
|
|
}
|
|
|
|
const authorShadow = useProfileShadow(post.author)
|
|
const wasMuted = !!authorShadow.viewer?.muted
|
|
const wasBlocked = !!authorShadow.viewer?.blocking
|
|
|
|
useEffect(() => {
|
|
function onUpdate() {
|
|
setShadow(shadows.get(post))
|
|
}
|
|
emitter.addListener(post.uri, onUpdate)
|
|
return () => {
|
|
emitter.removeListener(post.uri, onUpdate)
|
|
}
|
|
}, [post, setShadow])
|
|
|
|
return useMemo(() => {
|
|
if (wasMuted || wasBlocked) {
|
|
return POST_TOMBSTONE
|
|
}
|
|
if (shadow) {
|
|
return mergeShadow(post, shadow)
|
|
} else {
|
|
return castAsShadow(post)
|
|
}
|
|
}, [post, shadow, wasMuted, wasBlocked])
|
|
}
|
|
|
|
export function mergeShadow(
|
|
post: AppBskyFeedDefs.PostView,
|
|
shadow: Partial<PostShadow>,
|
|
): Shadow<AppBskyFeedDefs.PostView> | typeof POST_TOMBSTONE {
|
|
if (shadow.isDeleted) {
|
|
return POST_TOMBSTONE
|
|
}
|
|
|
|
let likeCount = post.likeCount ?? 0
|
|
if ('likeUri' in shadow) {
|
|
const wasLiked = !!post.viewer?.like
|
|
const isLiked = !!shadow.likeUri
|
|
if (wasLiked && !isLiked) {
|
|
likeCount--
|
|
} else if (!wasLiked && isLiked) {
|
|
likeCount++
|
|
}
|
|
likeCount = Math.max(0, likeCount)
|
|
}
|
|
|
|
let repostCount = post.repostCount ?? 0
|
|
if ('repostUri' in shadow) {
|
|
const wasReposted = !!post.viewer?.repost
|
|
const isReposted = !!shadow.repostUri
|
|
if (wasReposted && !isReposted) {
|
|
repostCount--
|
|
} else if (!wasReposted && isReposted) {
|
|
repostCount++
|
|
}
|
|
repostCount = Math.max(0, repostCount)
|
|
}
|
|
|
|
let embed: typeof post.embed
|
|
if ('embed' in shadow) {
|
|
if (
|
|
(AppBskyEmbedRecord.isView(post.embed) &&
|
|
AppBskyEmbedRecord.isView(shadow.embed)) ||
|
|
(AppBskyEmbedRecordWithMedia.isView(post.embed) &&
|
|
AppBskyEmbedRecordWithMedia.isView(shadow.embed))
|
|
) {
|
|
embed = shadow.embed
|
|
}
|
|
}
|
|
|
|
return castAsShadow({
|
|
...post,
|
|
embed: embed || post.embed,
|
|
likeCount: likeCount,
|
|
repostCount: repostCount,
|
|
viewer: {
|
|
...(post.viewer || {}),
|
|
like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like,
|
|
repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost,
|
|
pinned: 'pinned' in shadow ? shadow.pinned : post.viewer?.pinned,
|
|
},
|
|
})
|
|
}
|
|
|
|
export function updatePostShadow(
|
|
queryClient: QueryClient,
|
|
uri: string,
|
|
value: Partial<PostShadow>,
|
|
) {
|
|
const cachedPosts = findPostsInCache(queryClient, uri)
|
|
for (let post of cachedPosts) {
|
|
shadows.set(post, {...shadows.get(post), ...value})
|
|
}
|
|
batchedUpdates(() => {
|
|
emitter.emit(uri)
|
|
})
|
|
}
|
|
|
|
function* findPostsInCache(
|
|
queryClient: QueryClient,
|
|
uri: string,
|
|
): Generator<AppBskyFeedDefs.PostView, void> {
|
|
for (let post of findAllPostsInFeedQueryData(queryClient, uri)) {
|
|
yield post
|
|
}
|
|
for (let post of findAllPostsInNotifsQueryData(queryClient, uri)) {
|
|
yield post
|
|
}
|
|
for (let node of findAllPostsInThreadQueryData(queryClient, uri)) {
|
|
if (node.type === 'post') {
|
|
yield node.post
|
|
}
|
|
}
|
|
for (let post of findAllPostsInThreadV2QueryData(queryClient, uri)) {
|
|
yield post
|
|
}
|
|
for (let post of findAllPostsInSearchQueryData(queryClient, uri)) {
|
|
yield post
|
|
}
|
|
for (let post of findAllPostsInQuoteQueryData(queryClient, uri)) {
|
|
yield post
|
|
}
|
|
for (let post of findAllPostsInExploreFeedPreviewsQueryData(
|
|
queryClient,
|
|
uri,
|
|
)) {
|
|
yield post
|
|
}
|
|
}
|