diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts
index 7b27b239..c6b7c0dd 100644
--- a/src/state/models/feed-view.ts
+++ b/src/state/models/feed-view.ts
@@ -5,6 +5,13 @@ import {AtUri} from '../../third-party/uri'
import {RootStoreModel} from './root-store'
import * as apilib from '../lib/api'
import {cleanError} from '../../lib/strings'
+import {isObj, hasProp} from '../lib/type-guards'
+
+type FeedItem = GetTimeline.FeedItem | GetAuthorFeed.FeedItem
+type FeedItemWithThreadMeta = FeedItem & {
+ _isThreadParent?: boolean
+ _isThreadChild?: boolean
+}
export class FeedItemMyStateModel {
repost?: string
@@ -19,6 +26,8 @@ export class FeedItemMyStateModel {
export class FeedItemModel implements GetTimeline.FeedItem {
// ui state
_reactKey: string = ''
+ _isThreadParent: boolean = false
+ _isThreadChild: boolean = false
// data
uri: string = ''
@@ -46,11 +55,13 @@ export class FeedItemModel implements GetTimeline.FeedItem {
constructor(
public rootStore: RootStoreModel,
reactKey: string,
- v: GetTimeline.FeedItem | GetAuthorFeed.FeedItem,
+ v: FeedItemWithThreadMeta,
) {
makeAutoObservable(this, {rootStore: false})
this._reactKey = reactKey
this.copy(v)
+ this._isThreadParent = v._isThreadParent || false
+ this._isThreadChild = v._isThreadChild || false
}
copy(v: GetTimeline.FeedItem | GetAuthorFeed.FeedItem) {
@@ -197,7 +208,9 @@ export class FeedModel {
}
get nonReplyFeed() {
- return this.feed.filter(post => !post.record.reply)
+ return this.feed.filter(
+ post => !post.record.reply || post._isThreadParent || post._isThreadChild,
+ )
}
setHasNewLatest(v: boolean) {
@@ -391,17 +404,18 @@ export class FeedModel {
this.loadMoreCursor = res.data.cursor
this.hasMore = !!this.loadMoreCursor
let counter = this.feed.length
- for (const item of res.data.feed) {
- // HACK
- // deduplicate posts on the home feed
- // (should be done on the server)
- // -prf
- if (this.feedType === 'home') {
- if (this.feed.find(item2 => item2.uri === item.uri)) {
- continue
- }
- }
+ // HACK 1
+ // rearrange the posts to represent threads
+ // (should be done on the server)
+ // -prf
+ // HACK 2
+ // deduplicate posts on the home feed
+ // (should be done on the server)
+ // -prf
+ const reorgedFeed = preprocessFeed(res.data.feed, this.feedType === 'home')
+
+ for (const item of reorgedFeed) {
this._append(counter++, item)
}
}
@@ -465,3 +479,50 @@ export class FeedModel {
}
}
}
+
+function preprocessFeed(
+ feed: FeedItem[],
+ dedup: boolean,
+): FeedItemWithThreadMeta[] {
+ const reorg: FeedItemWithThreadMeta[] = []
+ for (let i = feed.length - 1; i >= 0; i--) {
+ const item = feed[i] as FeedItemWithThreadMeta
+
+ if (dedup) {
+ if (reorg.find(item2 => item2.uri === item.uri)) {
+ continue
+ }
+ }
+
+ const selfReplyUri = getSelfReplyUri(item)
+ if (selfReplyUri) {
+ const parentIndex = reorg.findIndex(item2 => item2.uri === selfReplyUri)
+ if (parentIndex !== -1 && !reorg[parentIndex]._isThreadParent) {
+ reorg[parentIndex]._isThreadParent = true
+ item._isThreadChild = true
+ reorg.splice(parentIndex + 1, 0, item)
+ continue
+ }
+ }
+ reorg.unshift(item)
+ }
+ return reorg
+}
+
+function getSelfReplyUri(
+ item: GetTimeline.FeedItem | GetAuthorFeed.FeedItem,
+): string | undefined {
+ if (
+ isObj(item.record) &&
+ hasProp(item.record, 'reply') &&
+ isObj(item.record.reply) &&
+ hasProp(item.record.reply, 'parent') &&
+ isObj(item.record.reply.parent) &&
+ hasProp(item.record.reply.parent, 'uri') &&
+ typeof item.record.reply.parent.uri === 'string'
+ ) {
+ if (new AtUri(item.record.reply.parent.uri).host === item.author.did) {
+ return item.record.reply.parent.uri
+ }
+ }
+}
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index 6deb0420..4d7b307b 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -15,6 +15,8 @@ import {UserAvatar} from '../util/UserAvatar'
import {s, colors} from '../../lib/styles'
import {useStores} from '../../../state'
+const TOP_REPLY_LINE_LENGTH = 12
+
export const FeedItem = observer(function FeedItem({
item,
}: {
@@ -74,8 +76,22 @@ export const FeedItem = observer(function FeedItem({
return
}
+ const outerStyles = [
+ styles.outer,
+ item._isThreadChild ? styles.outerNoTop : undefined,
+ item._isThreadParent ? styles.outerNoBottom : undefined,
+ ]
return (
-
+
+ {item._isThreadChild && }
+ {item._isThreadParent && (
+
+ )}
{item.repostedBy && (
-
+
-
- {replyHref !== '' && (
+ {!item._isThreadChild ? (
+
+ ) : undefined}
+ {!item._isThreadChild && replyHref !== '' && (
Replying to
@@ -165,6 +186,35 @@ const styles = StyleSheet.create({
backgroundColor: colors.white,
padding: 10,
},
+ outerNoTop: {
+ marginTop: 1,
+ borderTopLeftRadius: 0,
+ borderTopRightRadius: 0,
+ },
+ outerNoBottom: {
+ marginBottom: 0,
+ borderBottomLeftRadius: 0,
+ borderBottomRightRadius: 0,
+ },
+ topReplyLine: {
+ position: 'absolute',
+ left: 34,
+ top: -1 * TOP_REPLY_LINE_LENGTH + 10,
+ height: TOP_REPLY_LINE_LENGTH,
+ borderLeftWidth: 2,
+ borderLeftColor: colors.gray2,
+ },
+ bottomReplyLine: {
+ position: 'absolute',
+ left: 34,
+ top: 70,
+ bottom: 0,
+ borderLeftWidth: 2,
+ borderLeftColor: colors.gray2,
+ },
+ bottomReplyLineSmallAvi: {
+ top: 50,
+ },
includeReason: {
flexDirection: 'row',
paddingLeft: 60,