Skip to content

Commit

Permalink
deduplicate threads and enable expanding
Browse files Browse the repository at this point in the history
  • Loading branch information
bskyviewer committed Aug 11, 2023
1 parent 338ab3d commit 743c6e9
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 20 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ module.exports = {
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': 'warn',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
},
}
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 8 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -26,11 +27,14 @@ const cleanHandle = (handle: string, service: string) => {
return handle.toLowerCase().trim().replace(/^@/, '')
}

export const Filter: Context<[Set<string>, (value: Set<string>) => void]> = createContext([Set(), (_) => { }])

function App() {
const [isLoading, setIsLoading] = useState(false)
const [profileHandle, setProfileHandle] = useState('')
const [profile, setProfile] = useState<Profile>()
const [service, setService] = useState(DEFAULT_SERVICE)
const filterState = useState(Set<string>())
const [collection, setCollection] = useState({
name: 'posts',
id: 'app.bsky.feed.post',
Expand All @@ -53,6 +57,7 @@ function App() {
setError(undefined)
if (!cursor) {
setIsLoading(true)
filterState[1](Set())
}

return fetchPosts({
Expand Down Expand Up @@ -137,7 +142,7 @@ function App() {
}, [cursor, load])

return (
<>
<Filter.Provider value={filterState}>
<header className="App__header">
<p className="App__credits">
<a href="https://github.com/bskyviewer/bskyviewer.github.io">
Expand Down Expand Up @@ -254,7 +259,7 @@ function App() {
)}
/>
<Tooltip id="profile" opacity={1} style={{ zIndex: 100 }} />
</>
</Filter.Provider>
)
}

Expand Down
5 changes: 5 additions & 0 deletions src/components/Post.css
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,9 @@

.Post--ellipsis span {
padding-left: 56px;
cursor: pointer;
}

.Post--ellipsis span:hover {
text-decoration: underline;
}
39 changes: 24 additions & 15 deletions src/components/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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,
Expand Down Expand Up @@ -111,21 +114,17 @@ function Post({
depth?: number
}) {
const atUri = useMemo(() => new AtUri(uri), [uri])

const [profile, setProfile] = useState<Profile>()

const [profileError, setProfileError] = useState<string>()

const [embeddedPost, setEmbeddedPost] = useState<{
uri: string
record: AppBskyFeedPost.Record
}>()

const [embeddedPostError, setEmbeddedPostError] = useState<string>()

const [parentPost, setParentPost] = useState<AppBskyFeedPost.Record>()

const [parentPostError, setParentPostError] = useState<string>()
const [hideReplies, setHideReplies] = useContext(ReplyContext);
const [filter, setFilter] = useContext(Filter);

const profileImage = useMemo(() => {
if (!profile) {
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -202,7 +205,7 @@ function Post({
}, [isEmbedded, post.embed, service])

const postNode =
depth > 1 && parentPost ? null : (
hideReplies && depth > 1 && parentPost ? null : (
<article
className={classNames('Post', isEmbedded && 'Post--embed', className)}
>
Expand All @@ -218,9 +221,8 @@ function Post({
)}
<a
className="Post__author-name"
href={`${WEB_APP}/profile/${
profile ? profile.handle : atUri.hostname
}`}
href={`${WEB_APP}/profile/${profile ? profile.handle : atUri.hostname
}`}
data-tooltip-id="profile"
data-tooltip-html={profileHtml}
>
Expand Down Expand Up @@ -347,16 +349,23 @@ function Post({
return (
<>
{postNode}
<div className="Post--ellipsis">
<span>
{hideReplies && <div className="Post--ellipsis">
<span onClick={() => setHideReplies(false)}>
{depth - 2} {depth > 3 ? 'replies' : 'reply'} hidden
</span>
</div>
</div>}
</>
)
} else {
return postNode
}
}

export default Post
function WithContext(props: Parameters<typeof Post>[0]) {
const replyState = useState(true);
return <ReplyContext.Provider value={replyState}>
<Post {...props} />
</ReplyContext.Provider>
}

export default WithContext
6 changes: 4 additions & 2 deletions src/components/Record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -23,6 +24,7 @@ export function Record({
}) {
const [value, setValue] = useState<post.Record | Profile>()
const [error, setError] = useState('')
const [filter] = useContext(Filter)
useEffect(() => {
if (
post.isRecord(record.value) ||
Expand All @@ -45,7 +47,7 @@ export function Record({
}, [service, record])

if (post.isRecord(record.value)) {
return <Post service={service} uri={record.uri} post={record.value} />
return filter.contains(record.uri) ? null : <Post service={service} uri={record.uri} post={record.value} />
}

if (
Expand Down

0 comments on commit 743c6e9

Please sign in to comment.