about summary refs log tree commit diff
path: root/src/view/com/composer/state/composer.ts
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-11-01 03:45:55 +0000
committerGitHub <noreply@github.com>2024-11-01 03:45:55 +0000
commit7a08d61d889328ff5e3b8ba61faab71a5568df2f (patch)
tree105fd6ef552581048349ca1e756b71daebf7fc20 /src/view/com/composer/state/composer.ts
parent01c9ac0e13e959bae9ab221cd0a724a70c222772 (diff)
downloadvoidsky-7a08d61d889328ff5e3b8ba61faab71a5568df2f.tar.zst
Thread composer UI (#6050)
* Basic adding of posts

* Switch active post on focus

* Conditionally show plus button

* Insert posts midthread

* Track active/inactive post

* Delete posts in a thread

* Focus after deletion

* Tweak empty post detection

* Mix height for active only

* Move toolbar with post on web

* Fix footer positioning

* Post All button

* Fix reply to positioning

* Improve memoization

* Improve memoization for clearVideo

* Remove unnecessary argument

* Add some manual memoization to fix re-renders

* Scroll to bottom on add new

* Fix opacity on Android

* Add backdrop

* Fix videos

* Check alt for video too

* Clear pending publish on error

* Fork alt message by type

* Separate placeholder for next posts

* Limit hitslop to avoid clashes
Diffstat (limited to 'src/view/com/composer/state/composer.ts')
-rw-r--r--src/view/com/composer/state/composer.ts81
1 files changed, 80 insertions, 1 deletions
diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts
index 4c94d5eac..27bed6d44 100644
--- a/src/view/com/composer/state/composer.ts
+++ b/src/view/com/composer/state/composer.ts
@@ -85,7 +85,9 @@ export type ThreadDraft = {
 
 export type ComposerState = {
   thread: ThreadDraft
-  activePostIndex: number // TODO: Add actions to update this.
+  activePostIndex: number
+  mutableNeedsFocusActive: boolean
+  mutableNeedsScrollToBottom: boolean
 }
 
 export type ComposerAction =
@@ -96,6 +98,17 @@ export type ComposerAction =
       postId: string
       postAction: PostAction
     }
+  | {
+      type: 'add_post'
+    }
+  | {
+      type: 'remove_post'
+      postId: string
+    }
+  | {
+      type: 'focus_post'
+      postId: string
+    }
 
 export const MAX_IMAGES = 4
 
@@ -142,6 +155,69 @@ export function composerReducer(
         },
       }
     }
+    case 'add_post': {
+      const activePostIndex = state.activePostIndex
+      const isAtTheEnd = activePostIndex === state.thread.posts.length - 1
+      const nextPosts = [...state.thread.posts]
+      nextPosts.splice(activePostIndex + 1, 0, {
+        id: nanoid(),
+        richtext: new RichText({text: ''}),
+        shortenedGraphemeLength: 0,
+        labels: [],
+        embed: {
+          quote: undefined,
+          media: undefined,
+          link: undefined,
+        },
+      })
+      return {
+        ...state,
+        mutableNeedsScrollToBottom: isAtTheEnd,
+        thread: {
+          ...state.thread,
+          posts: nextPosts,
+        },
+      }
+    }
+    case 'remove_post': {
+      if (state.thread.posts.length < 2) {
+        return state
+      }
+      let nextActivePostIndex = state.activePostIndex
+      const indexToRemove = state.thread.posts.findIndex(
+        p => p.id === action.postId,
+      )
+      let nextPosts = [...state.thread.posts]
+      if (indexToRemove !== -1) {
+        const postToRemove = state.thread.posts[indexToRemove]
+        if (postToRemove.embed.media?.type === 'video') {
+          postToRemove.embed.media.video.abortController.abort()
+        }
+        nextPosts.splice(indexToRemove, 1)
+        nextActivePostIndex = Math.max(0, indexToRemove - 1)
+      }
+      return {
+        ...state,
+        activePostIndex: nextActivePostIndex,
+        mutableNeedsFocusActive: true,
+        thread: {
+          ...state.thread,
+          posts: nextPosts,
+        },
+      }
+    }
+    case 'focus_post': {
+      const nextActivePostIndex = state.thread.posts.findIndex(
+        p => p.id === action.postId,
+      )
+      if (nextActivePostIndex === -1) {
+        return state
+      }
+      return {
+        ...state,
+        activePostIndex: nextActivePostIndex,
+      }
+    }
   }
 }
 
@@ -275,6 +351,7 @@ function postReducer(state: PostDraft, action: PostAction): PostDraft {
       const prevMedia = state.embed.media
       let nextMedia = prevMedia
       if (prevMedia?.type === 'video') {
+        prevMedia.video.abortController.abort()
         nextMedia = undefined
       }
       let nextLabels = state.labels
@@ -436,6 +513,8 @@ export function createComposerState({
   })
   return {
     activePostIndex: 0,
+    mutableNeedsFocusActive: false,
+    mutableNeedsScrollToBottom: false,
     thread: {
       posts: [
         {