diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 4020bcb..58f462b 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -10,5 +10,6 @@ module.exports = { plugins: ['react-refresh'], rules: { 'react-refresh/only-export-components': 'warn', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], }, } diff --git a/package-lock.json b/package-lock.json index dc33612..2315587 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@atproto/api": "^0.5.2", "classnames": "^2.3.2", "fast-memoize": "^2.5.2", + "immutable": "^4.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-embed": "^3.7.0", @@ -2137,6 +2138,11 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.2.tgz", + "integrity": "sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", diff --git a/package.json b/package.json index 851c7ae..5c2f4a2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@atproto/api": "^0.5.2", "classnames": "^2.3.2", "fast-memoize": "^2.5.2", + "immutable": "^4.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-embed": "^3.7.0", diff --git a/src/App.tsx b/src/App.tsx index 4725483..e7e03cc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import classNames from 'classnames' -import { FormEvent, useEffect, useMemo, useReducer, useState } from 'react' +import { Set } from 'immutable' +import { Context, FormEvent, createContext, useEffect, useMemo, useReducer, useState } from 'react' import { Tooltip } from 'react-tooltip' import './App.css' import FriendlyError from './components/FriendlyError' @@ -26,11 +27,14 @@ const cleanHandle = (handle: string, service: string) => { return handle.toLowerCase().trim().replace(/^@/, '') } +export const Filter: Context<[Set, (value: Set) => void]> = createContext([Set(), (_) => { }]) + function App() { const [isLoading, setIsLoading] = useState(false) const [profileHandle, setProfileHandle] = useState('') const [profile, setProfile] = useState() const [service, setService] = useState(DEFAULT_SERVICE) + const filterState = useState(Set()) const [collection, setCollection] = useState({ name: 'posts', id: 'app.bsky.feed.post', @@ -53,6 +57,7 @@ function App() { setError(undefined) if (!cursor) { setIsLoading(true) + filterState[1](Set()) } return fetchPosts({ @@ -137,7 +142,7 @@ function App() { }, [cursor, load]) return ( - <> +

@@ -254,7 +259,7 @@ function App() { )} /> - + ) } diff --git a/src/components/Post.css b/src/components/Post.css index 87e4fb2..204892d 100644 --- a/src/components/Post.css +++ b/src/components/Post.css @@ -227,4 +227,9 @@ .Post--ellipsis span { padding-left: 56px; + cursor: pointer; +} + +.Post--ellipsis span:hover { + text-decoration: underline; } diff --git a/src/components/Post.tsx b/src/components/Post.tsx index c76df52..7473aea 100644 --- a/src/components/Post.tsx +++ b/src/components/Post.tsx @@ -7,9 +7,10 @@ import { AtUri, } from '@atproto/api' import classNames from 'classnames' -import { useEffect, useMemo, useState } from 'react' +import { createContext, useContext, useEffect, useMemo, useState } from 'react' import { renderToString } from 'react-dom/server' import { ReactEmbed } from 'react-embed' +import { Filter } from '../App' import { Profile, fetchPost, fetchProfile, getBlobURL } from '../utils/api' import { WEB_APP } from '../utils/constants' import { getRelativeDateString } from '../utils/datetime' @@ -19,6 +20,8 @@ import RichText from './RichText' import Spinner from './Spinner' import User from './User' +const ReplyContext = createContext<[boolean, (value: boolean) => void]>([true, () => { }]); + function ExternalEmbed({ service, did, @@ -111,21 +114,17 @@ function Post({ depth?: number }) { const atUri = useMemo(() => new AtUri(uri), [uri]) - const [profile, setProfile] = useState() - const [profileError, setProfileError] = useState() - const [embeddedPost, setEmbeddedPost] = useState<{ uri: string record: AppBskyFeedPost.Record }>() - const [embeddedPostError, setEmbeddedPostError] = useState() - const [parentPost, setParentPost] = useState() - const [parentPostError, setParentPostError] = useState() + const [hideReplies, setHideReplies] = useContext(ReplyContext); + const [filter, setFilter] = useContext(Filter); const profileImage = useMemo(() => { if (!profile) { @@ -172,6 +171,10 @@ function Post({ ) }, [isEmbedded, post.reply, service]) + useEffect(() => { + post.reply && parentPost && !filter.contains(post.reply.parent.uri) && setFilter(filter.add(post.reply.parent.uri)) + }, [post.reply, parentPost, filter, setFilter]) + useEffect( () => fetchProfile(service, atUri.hostname, setProfile, setProfileError), [atUri.hostname, service], @@ -202,7 +205,7 @@ function Post({ }, [isEmbedded, post.embed, service]) const postNode = - depth > 1 && parentPost ? null : ( + hideReplies && depth > 1 && parentPost ? null : (

@@ -218,9 +221,8 @@ function Post({ )} @@ -347,11 +349,11 @@ function Post({ return ( <> {postNode} -
- + {hideReplies &&
+ setHideReplies(false)}> {depth - 2} {depth > 3 ? 'replies' : 'reply'} hidden -
+
} ) } else { @@ -359,4 +361,11 @@ function Post({ } } -export default Post +function WithContext(props: Parameters[0]) { + const replyState = useState(true); + return + + +} + +export default WithContext diff --git a/src/components/Record.tsx b/src/components/Record.tsx index e7930bc..416508c 100644 --- a/src/components/Record.tsx +++ b/src/components/Record.tsx @@ -7,7 +7,8 @@ import { AppBskyActorProfile as profile, AppBskyFeedRepost as repost, } from '@atproto/api' -import { useEffect, useState } from 'react' +import { useContext, useEffect, useState } from 'react' +import { Filter } from '../App' import { Profile, fetchPost, fetchProfile } from '../utils/api' import FriendlyError from './FriendlyError' import Post from './Post' @@ -23,6 +24,7 @@ export function Record({ }) { const [value, setValue] = useState() const [error, setError] = useState('') + const [filter] = useContext(Filter) useEffect(() => { if ( post.isRecord(record.value) || @@ -45,7 +47,7 @@ export function Record({ }, [service, record]) if (post.isRecord(record.value)) { - return + return filter.contains(record.uri) ? null : } if (