about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2022-11-16 16:16:43 -0600
committerPaul Frazee <pfrazee@gmail.com>2022-11-16 16:16:43 -0600
commit41ae87e770f23ebe5ad02dbc68a497e0fb2f2a3d (patch)
tree0bba2afc4eaa19abe64fafeaf2e6ac50861cae15 /src
parentbd1a4b198e3682f96354d83f1e1efccf31813f82 (diff)
downloadvoidsky-41ae87e770f23ebe5ad02dbc68a497e0fb2f2a3d.tar.zst
Add post deletion
Diffstat (limited to 'src')
-rw-r--r--src/state/models/feed-view.ts8
-rw-r--r--src/state/models/post-thread-view.ts7
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx22
-rw-r--r--src/view/com/post/Post.tsx27
-rw-r--r--src/view/com/posts/FeedItem.tsx28
-rw-r--r--src/view/com/util/DropdownBtn.tsx23
-rw-r--r--src/view/com/util/PostMeta.tsx6
-rw-r--r--src/view/index.ts2
8 files changed, 114 insertions, 9 deletions
diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts
index 67a927a7a..436b47038 100644
--- a/src/state/models/feed-view.ts
+++ b/src/state/models/feed-view.ts
@@ -1,6 +1,7 @@
 import {makeAutoObservable, runInAction} from 'mobx'
 import * as GetTimeline from '../../third-party/api/src/client/types/app/bsky/feed/getTimeline'
 import * as GetAuthorFeed from '../../third-party/api/src/client/types/app/bsky/feed/getAuthorFeed'
+import {AtUri} from '../../third-party/uri'
 import {RootStoreModel} from './root-store'
 import * as apilib from '../lib/api'
 import {cleanError} from '../../view/lib/strings'
@@ -135,6 +136,13 @@ export class FeedItemModel implements GetTimeline.FeedItem {
       })
     }
   }
+
+  async delete() {
+    await this.rootStore.api.app.bsky.feed.post.delete({
+      did: this.author.did,
+      rkey: new AtUri(this.uri).rkey,
+    })
+  }
 }
 
 export class FeedModel {
diff --git a/src/state/models/post-thread-view.ts b/src/state/models/post-thread-view.ts
index 58fee1619..0334c8442 100644
--- a/src/state/models/post-thread-view.ts
+++ b/src/state/models/post-thread-view.ts
@@ -175,6 +175,13 @@ export class PostThreadViewPostModel implements GetPostThread.Post {
       })
     }
   }
+
+  async delete() {
+    await this.rootStore.api.app.bsky.feed.post.delete({
+      did: this.author.did,
+      rkey: new AtUri(this.uri).rkey,
+    })
+  }
 }
 
 export class PostThreadViewModel {
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index c63364ecb..0ee52660b 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -1,4 +1,4 @@
-import React, {useMemo} from 'react'
+import React, {useMemo, useState} from 'react'
 import {observer} from 'mobx-react-lite'
 import {StyleSheet, Text, View} from 'react-native'
 import Svg, {Line} from 'react-native-svg'
@@ -9,6 +9,7 @@ import {PostThreadViewPostModel} from '../../../state/models/post-thread-view'
 import {Link} from '../util/Link'
 import {RichText} from '../util/RichText'
 import {PostDropdownBtn} from '../util/DropdownBtn'
+import Toast from '../util/Toast'
 import {UserAvatar} from '../util/UserAvatar'
 import {s, colors} from '../../lib/styles'
 import {ago, pluralize} from '../../lib/strings'
@@ -28,6 +29,7 @@ export const PostThreadItem = observer(function PostThreadItem({
   onPostReply: () => void
 }) {
   const store = useStores()
+  const [deleted, setDeleted] = useState(false)
   const record = item.record as unknown as PostType.Record
   const hasEngagement =
     item.upvoteCount || item.downvoteCount || item.repostCount
@@ -76,6 +78,22 @@ export const PostThreadItem = observer(function PostThreadItem({
       .toggleDownvote()
       .catch(e => console.error('Failed to toggle downvote', record, e))
   }
+  const onDeletePost = () => {
+    item.delete().then(
+      () => {
+        setDeleted(true)
+        Toast.show('Post deleted', {
+          position: Toast.positions.TOP,
+        })
+      },
+      e => {
+        console.error(e)
+        Toast.show('Failed to delete post, please try again', {
+          position: Toast.positions.TOP,
+        })
+      },
+    )
+  }
 
   if (item._isHighlightedPost) {
     return (
@@ -250,6 +268,8 @@ export const PostThreadItem = observer(function PostThreadItem({
               authorHandle={item.author.handle}
               authorDisplayName={item.author.displayName}
               timestamp={item.indexedAt}
+              isAuthor={item.author.did === store.me.did}
+              onDeletePost={onDeletePost}
             />
             {item.replyingToAuthor &&
               item.replyingToAuthor !== item.author.handle && (
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index 83bf8bed8..7d2edff4c 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -10,14 +10,15 @@ import {UserInfoText} from '../util/UserInfoText'
 import {PostMeta} from '../util/PostMeta'
 import {PostCtrls} from '../util/PostCtrls'
 import {RichText} from '../util/RichText'
+import Toast from '../util/Toast'
 import {UserAvatar} from '../util/UserAvatar'
 import {useStores} from '../../../state'
 import {s, colors} from '../../lib/styles'
-import {ago} from '../../lib/strings'
 
 export const Post = observer(function Post({uri}: {uri: string}) {
   const store = useStores()
   const [view, setView] = useState<PostThreadViewModel | undefined>()
+  const [deleted, setDeleted] = useState(false)
 
   useEffect(() => {
     if (view?.params.uri === uri) {
@@ -28,6 +29,12 @@ export const Post = observer(function Post({uri}: {uri: string}) {
     newView.setup().catch(err => console.error('Failed to fetch post', err))
   }, [uri, view?.params.uri, store])
 
+  // deleted
+  // =
+  if (deleted) {
+    return <View />
+  }
+
   // loading
   // =
   if (!view || view.isLoading || view.params.uri !== uri) {
@@ -83,6 +90,22 @@ export const Post = observer(function Post({uri}: {uri: string}) {
       .toggleDownvote()
       .catch(e => console.error('Failed to toggle downvote', record, e))
   }
+  const onDeletePost = () => {
+    item.delete().then(
+      () => {
+        setDeleted(true)
+        Toast.show('Post deleted', {
+          position: Toast.positions.TOP,
+        })
+      },
+      e => {
+        console.error(e)
+        Toast.show('Failed to delete post, please try again', {
+          position: Toast.positions.TOP,
+        })
+      },
+    )
+  }
 
   return (
     <Link style={styles.outer} href={itemHref} title={itemTitle}>
@@ -102,6 +125,8 @@ export const Post = observer(function Post({uri}: {uri: string}) {
             authorHandle={item.author.handle}
             authorDisplayName={item.author.displayName}
             timestamp={item.indexedAt}
+            isAuthor={item.author.did === store.me.did}
+            onDeletePost={onDeletePost}
           />
           {replyHref !== '' && (
             <View style={[s.flexRow, s.mb2, {alignItems: 'center'}]}>
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index d1e891d62..e4c54b32a 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -1,16 +1,16 @@
-import React, {useMemo} from 'react'
+import React, {useMemo, useState} from 'react'
 import {observer} from 'mobx-react-lite'
 import {StyleSheet, Text, View} from 'react-native'
 import {AtUri} from '../../../third-party/uri'
 import * as PostType from '../../../third-party/api/src/client/types/app/bsky/feed/post'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {FeedItemModel} from '../../../state/models/feed-view'
-import {SharePostModel} from '../../../state/models/shell-ui'
 import {Link} from '../util/Link'
 import {UserInfoText} from '../util/UserInfoText'
 import {PostMeta} from '../util/PostMeta'
 import {PostCtrls} from '../util/PostCtrls'
 import {RichText} from '../util/RichText'
+import Toast from '../util/Toast'
 import {UserAvatar} from '../util/UserAvatar'
 import {s, colors} from '../../lib/styles'
 import {useStores} from '../../../state'
@@ -21,6 +21,7 @@ export const FeedItem = observer(function FeedItem({
   item: FeedItemModel
 }) {
   const store = useStores()
+  const [deleted, setDeleted] = useState(false)
   const record = item.record as unknown as PostType.Record
   const itemHref = useMemo(() => {
     const urip = new AtUri(item.uri)
@@ -57,8 +58,25 @@ export const FeedItem = observer(function FeedItem({
       .toggleDownvote()
       .catch(e => console.error('Failed to toggle downvote', record, e))
   }
-  const onPressShare = (uri: string) => {
-    store.shell.openModal(new SharePostModel(uri))
+  const onDeletePost = () => {
+    item.delete().then(
+      () => {
+        setDeleted(true)
+        Toast.show('Post deleted', {
+          position: Toast.positions.TOP,
+        })
+      },
+      e => {
+        console.error(e)
+        Toast.show('Failed to delete post, please try again', {
+          position: Toast.positions.TOP,
+        })
+      },
+    )
+  }
+
+  if (deleted) {
+    return <View />
   }
 
   return (
@@ -107,6 +125,8 @@ export const FeedItem = observer(function FeedItem({
             authorHandle={item.author.handle}
             authorDisplayName={item.author.displayName}
             timestamp={item.indexedAt}
+            isAuthor={item.author.did === store.me.did}
+            onDeletePost={onDeletePost}
           />
           {replyHref !== '' && (
             <View style={[s.flexRow, s.mb5, {alignItems: 'center'}]}>
diff --git a/src/view/com/util/DropdownBtn.tsx b/src/view/com/util/DropdownBtn.tsx
index 960293320..bef193f1d 100644
--- a/src/view/com/util/DropdownBtn.tsx
+++ b/src/view/com/util/DropdownBtn.tsx
@@ -13,7 +13,7 @@ import RootSiblings from 'react-native-root-siblings'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {colors} from '../../lib/styles'
 import {useStores} from '../../../state'
-import {SharePostModel} from '../../../state/models/shell-ui'
+import {SharePostModel, ConfirmModel} from '../../../state/models/shell-ui'
 
 export interface DropdownItem {
   icon?: IconProp
@@ -69,11 +69,15 @@ export function PostDropdownBtn({
   children,
   itemHref,
   itemTitle,
+  isAuthor,
+  onDeletePost,
 }: {
   style?: StyleProp<ViewStyle>
   children?: React.ReactNode
   itemHref: string
   itemTitle: string
+  isAuthor: boolean
+  onDeletePost: () => void
 }) {
   const store = useStores()
 
@@ -92,7 +96,22 @@ export function PostDropdownBtn({
         store.shell.openModal(new SharePostModel(itemHref))
       },
     },
-  ]
+    isAuthor
+      ? {
+          icon: ['far', 'trash-can'],
+          label: 'Delete post',
+          onPress() {
+            store.shell.openModal(
+              new ConfirmModel(
+                'Delete this post?',
+                'Are you sure? This can not be undone.',
+                onDeletePost,
+              ),
+            )
+          },
+        }
+      : undefined,
+  ].filter(Boolean) as DropdownItem[]
 
   return (
     <DropdownBtn style={style} items={dropdownItems} menuWidth={200}>
diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx
index 95dfcbd64..840de8709 100644
--- a/src/view/com/util/PostMeta.tsx
+++ b/src/view/com/util/PostMeta.tsx
@@ -13,6 +13,8 @@ interface PostMetaOpts {
   authorHandle: string
   authorDisplayName: string | undefined
   timestamp: string
+  isAuthor: boolean
+  onDeletePost: () => void
 }
 
 export function PostMeta(opts: PostMetaOpts) {
@@ -48,7 +50,9 @@ export function PostMeta(opts: PostMetaOpts) {
       <PostDropdownBtn
         style={styles.metaItem}
         itemHref={opts.itemHref}
-        itemTitle={opts.itemTitle}>
+        itemTitle={opts.itemTitle}
+        isAuthor={opts.isAuthor}
+        onDeletePost={opts.onDeletePost}>
         <FontAwesomeIcon icon="ellipsis-h" size={14} style={[s.mt2, s.mr5]} />
       </PostDropdownBtn>
     </View>
diff --git a/src/view/index.ts b/src/view/index.ts
index 78361e75b..69078dae9 100644
--- a/src/view/index.ts
+++ b/src/view/index.ts
@@ -52,6 +52,7 @@ import {faUserCheck} from '@fortawesome/free-solid-svg-icons/faUserCheck'
 import {faUserPlus} from '@fortawesome/free-solid-svg-icons/faUserPlus'
 import {faUserXmark} from '@fortawesome/free-solid-svg-icons/faUserXmark'
 import {faTicket} from '@fortawesome/free-solid-svg-icons/faTicket'
+import {faTrashCan} from '@fortawesome/free-regular-svg-icons/faTrashCan'
 import {faX} from '@fortawesome/free-solid-svg-icons/faX'
 
 export function setup() {
@@ -108,6 +109,7 @@ export function setup() {
     faUserPlus,
     faUserXmark,
     faTicket,
+    faTrashCan,
     faX,
   )
 }