about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/api/index.ts23
-rw-r--r--src/view/com/composer/Composer.tsx61
-rw-r--r--src/view/com/composer/photos/Gallery.tsx4
-rw-r--r--src/view/com/composer/state/composer.ts96
4 files changed, 126 insertions, 58 deletions
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index f727aa4ca..46dbd1e66 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -30,14 +30,18 @@ import {
   createThreadgateRecord,
   threadgateAllowUISettingToAllowRecordValue,
 } from '#/state/queries/threadgate'
-import {ComposerDraft, EmbedDraft} from '#/view/com/composer/state/composer'
+import {
+  EmbedDraft,
+  PostDraft,
+  ThreadDraft,
+} from '#/view/com/composer/state/composer'
 import {createGIFDescription} from '../gif-alt-text'
 import {uploadBlob} from './upload-blob'
 
 export {uploadBlob}
 
 interface PostOpts {
-  draft: ComposerDraft
+  thread: ThreadDraft
   replyTo?: string
   onStateChange?: (state: string) => void
   langs?: string[]
@@ -48,7 +52,8 @@ export async function post(
   queryClient: QueryClient,
   opts: PostOpts,
 ) {
-  const draft = opts.draft
+  const thread = opts.thread
+  const draft = thread.posts[0] // TODO: Support threads.
 
   opts.onStateChange?.(t`Processing...`)
   // NB -- Do not await anything here to avoid waterfalls!
@@ -111,11 +116,11 @@ export async function post(
   }
 
   // Create threadgate record
-  if (draft.threadgate.some(tg => tg.type !== 'everybody')) {
+  if (thread.threadgate.some(tg => tg.type !== 'everybody')) {
     const record = createThreadgateRecord({
       createdAt: date,
       post: uri,
-      allow: threadgateAllowUISettingToAllowRecordValue(draft.threadgate),
+      allow: threadgateAllowUISettingToAllowRecordValue(thread.threadgate),
     })
 
     writes.push({
@@ -128,11 +133,11 @@ export async function post(
 
   // Create postgate record
   if (
-    draft.postgate.embeddingRules?.length ||
-    draft.postgate.detachedEmbeddingUris?.length
+    thread.postgate.embeddingRules?.length ||
+    thread.postgate.detachedEmbeddingUris?.length
   ) {
     const record: AppBskyFeedPostgate.Record = {
-      ...draft.postgate,
+      ...thread.postgate,
       $type: 'app.bsky.feed.postgate',
       createdAt: date,
       post: uri,
@@ -198,7 +203,7 @@ async function resolveReply(agent: BskyAgent, replyTo: string) {
 async function resolveEmbed(
   agent: BskyAgent,
   queryClient: QueryClient,
-  draft: ComposerDraft,
+  draft: PostDraft,
   onStateChange: ((state: string) => void) | undefined,
 ): Promise<
   | AppBskyEmbedImages.Main
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index 8fdc62bc3..214f3605e 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -114,11 +114,13 @@ import {Text as NewText} from '#/components/Typography'
 import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet'
 import {
   ComposerAction,
-  ComposerDraft,
   composerReducer,
   createComposerState,
   EmbedDraft,
   MAX_IMAGES,
+  PostAction,
+  PostDraft,
+  ThreadDraft,
 } from './state/composer'
 import {NO_VIDEO, NoVideoState, processVideo, VideoState} from './state/video'
 
@@ -161,11 +163,21 @@ export const ComposePost = ({
   const [publishingStage, setPublishingStage] = useState('')
   const [error, setError] = useState('')
 
-  const [draft, dispatch] = useReducer(
+  const [composerState, composerDispatch] = useReducer(
     composerReducer,
     {initImageUris, initQuoteUri: initQuote?.uri, initText, initMention},
     createComposerState,
   )
+
+  // TODO: Display drafts for other posts in the thread.
+  const draft = composerState.thread.posts[composerState.activePostIndex]
+  const dispatch = useCallback((postAction: PostAction) => {
+    composerDispatch({
+      type: 'update_post',
+      postAction,
+    })
+  }, [])
+
   const richtext = draft.richtext
   let quote: string | undefined
   if (draft.embed.quote) {
@@ -207,7 +219,7 @@ export const ComposePost = ({
         _,
       )
     },
-    [_, agent, currentDid],
+    [_, agent, currentDid, dispatch],
   )
 
   // Whenever we receive an initial video uri, we should immediately run compression if necessary
@@ -333,7 +345,7 @@ export const ComposePost = ({
       try {
         postUri = (
           await apilib.post(agent, queryClient, {
-            draft: draft,
+            thread: composerState.thread,
             replyTo: replyTo?.uri,
             onStateChange: setPublishingStage,
             langs: toPostLanguages(langPrefs.postLanguage),
@@ -409,7 +421,7 @@ export const ComposePost = ({
     [
       _,
       agent,
-      draft,
+      composerState.thread,
       extLink,
       images,
       canPost,
@@ -504,8 +516,9 @@ export const ComposePost = ({
 
           <ComposerPills
             isReply={!!replyTo}
-            draft={draft}
-            dispatch={dispatch}
+            post={draft}
+            thread={composerState.thread}
+            dispatch={composerDispatch}
             bottomBarAnimatedStyle={bottomBarAnimatedStyle}
           />
 
@@ -543,8 +556,8 @@ function ComposerPost({
   onError,
   onPublish,
 }: {
-  draft: ComposerDraft
-  dispatch: (action: ComposerAction) => void
+  draft: PostDraft
+  dispatch: (action: PostAction) => void
   textInput: React.Ref<TextInputRef>
   isReply: boolean
   canRemoveQuote: boolean
@@ -736,7 +749,7 @@ function ComposerEmbeds({
   canRemoveQuote,
 }: {
   embed: EmbedDraft
-  dispatch: (action: ComposerAction) => void
+  dispatch: (action: PostAction) => void
   clearVideo: () => void
   canRemoveQuote: boolean
 }) {
@@ -850,19 +863,21 @@ function ComposerEmbeds({
 
 function ComposerPills({
   isReply,
-  draft,
+  thread,
+  post,
   dispatch,
   bottomBarAnimatedStyle,
 }: {
   isReply: boolean
-  draft: ComposerDraft
+  thread: ThreadDraft
+  post: PostDraft
   dispatch: (action: ComposerAction) => void
   bottomBarAnimatedStyle: StyleProp<ViewStyle>
 }) {
   const t = useTheme()
-  const media = draft.embed.media
+  const media = post.embed.media
   const hasMedia = media?.type === 'images' || media?.type === 'video'
-  const hasLink = !!draft.embed.link
+  const hasLink = !!post.embed.link
 
   // Don't render anything if no pills are going to be displayed
   if (isReply && !hasMedia && !hasLink) {
@@ -879,11 +894,11 @@ function ComposerPills({
         showsHorizontalScrollIndicator={false}>
         {isReply ? null : (
           <ThreadgateBtn
-            postgate={draft.postgate}
+            postgate={thread.postgate}
             onChangePostgate={nextPostgate => {
               dispatch({type: 'update_postgate', postgate: nextPostgate})
             }}
-            threadgateAllowUISettings={draft.threadgate}
+            threadgateAllowUISettings={thread.threadgate}
             onChangeThreadgateAllowUISettings={nextThreadgate => {
               dispatch({
                 type: 'update_threadgate',
@@ -895,9 +910,15 @@ function ComposerPills({
         )}
         {hasMedia || hasLink ? (
           <LabelsBtn
-            labels={draft.labels}
+            labels={post.labels}
             onChange={nextLabels => {
-              dispatch({type: 'update_labels', labels: nextLabels})
+              dispatch({
+                type: 'update_post',
+                postAction: {
+                  type: 'update_labels',
+                  labels: nextLabels,
+                },
+              })
             }}
           />
         ) : null}
@@ -914,8 +935,8 @@ function ComposerFooter({
   onError,
   onSelectVideo,
 }: {
-  draft: ComposerDraft
-  dispatch: (action: ComposerAction) => void
+  draft: PostDraft
+  dispatch: (action: PostAction) => void
   graphemeLength: number
   onEmojiButtonPress: () => void
   onError: (error: string) => void
diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx
index 5ff7042bc..e65c5407a 100644
--- a/src/view/com/composer/photos/Gallery.tsx
+++ b/src/view/com/composer/photos/Gallery.tsx
@@ -21,7 +21,7 @@ import {ComposerImage, cropImage} from '#/state/gallery'
 import {Text} from '#/view/com/util/text/Text'
 import {useTheme} from '#/alf'
 import * as Dialog from '#/components/Dialog'
-import {ComposerAction} from '../state/composer'
+import {PostAction} from '../state/composer'
 import {EditImageDialog} from './EditImageDialog'
 import {ImageAltTextDialog} from './ImageAltTextDialog'
 
@@ -29,7 +29,7 @@ const IMAGE_GAP = 8
 
 interface GalleryProps {
   images: ComposerImage[]
-  dispatch: (action: ComposerAction) => void
+  dispatch: (action: PostAction) => void
 }
 
 export let Gallery = (props: GalleryProps): React.ReactNode => {
diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts
index 3c1edb6bd..958718eaf 100644
--- a/src/view/com/composer/state/composer.ts
+++ b/src/view/com/composer/state/composer.ts
@@ -47,19 +47,15 @@ export type EmbedDraft = {
   link: Link | undefined
 }
 
-export type ComposerDraft = {
+export type PostDraft = {
   richtext: RichText
   labels: SelfLabel[]
-  postgate: AppBskyFeedPostgate.Record
-  threadgate: ThreadgateAllowUISetting[]
   embed: EmbedDraft
 }
 
-export type ComposerAction =
+export type PostAction =
   | {type: 'update_richtext'; richtext: RichText}
   | {type: 'update_labels'; labels: SelfLabel[]}
-  | {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record}
-  | {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]}
   | {type: 'embed_add_images'; images: ComposerImage[]}
   | {type: 'embed_update_image'; image: ComposerImage}
   | {type: 'embed_remove_image'; image: ComposerImage}
@@ -77,35 +73,76 @@ export type ComposerAction =
   | {type: 'embed_update_gif'; alt: string}
   | {type: 'embed_remove_gif'}
 
+export type ThreadDraft = {
+  posts: PostDraft[]
+  postgate: AppBskyFeedPostgate.Record
+  threadgate: ThreadgateAllowUISetting[]
+}
+
+export type ComposerState = {
+  thread: ThreadDraft
+  activePostIndex: number // TODO: Add actions to update this.
+}
+
+export type ComposerAction =
+  | {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record}
+  | {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]}
+  | {type: 'update_post'; postAction: PostAction}
+
 export const MAX_IMAGES = 4
 
 export function composerReducer(
-  state: ComposerDraft,
+  state: ComposerState,
   action: ComposerAction,
-): ComposerDraft {
+): ComposerState {
   switch (action.type) {
-    case 'update_richtext': {
+    case 'update_postgate': {
       return {
         ...state,
-        richtext: action.richtext,
+        thread: {
+          ...state.thread,
+          postgate: action.postgate,
+        },
       }
     }
-    case 'update_labels': {
+    case 'update_threadgate': {
       return {
         ...state,
-        labels: action.labels,
+        thread: {
+          ...state.thread,
+          threadgate: action.threadgate,
+        },
       }
     }
-    case 'update_postgate': {
+    case 'update_post': {
+      const nextPosts = [...state.thread.posts]
+      nextPosts[state.activePostIndex] = postReducer(
+        state.thread.posts[state.activePostIndex],
+        action.postAction,
+      )
       return {
         ...state,
-        postgate: action.postgate,
+        thread: {
+          ...state.thread,
+          posts: nextPosts,
+        },
       }
     }
-    case 'update_threadgate': {
+  }
+}
+
+function postReducer(state: PostDraft, action: PostAction): PostDraft {
+  switch (action.type) {
+    case 'update_richtext': {
       return {
         ...state,
-        threadgate: action.threadgate,
+        richtext: action.richtext,
+      }
+    }
+    case 'update_labels': {
+      return {
+        ...state,
+        labels: action.labels,
       }
     }
     case 'embed_add_images': {
@@ -339,8 +376,6 @@ export function composerReducer(
         },
       }
     }
-    default:
-      return state
   }
 }
 
@@ -354,7 +389,7 @@ export function createComposerState({
   initMention: string | undefined
   initImageUris: ComposerOpts['imageUris']
   initQuoteUri: string | undefined
-}): ComposerDraft {
+}): ComposerState {
   let media: ImagesMedia | undefined
   if (initImageUris?.length) {
     media = {
@@ -385,14 +420,21 @@ export function createComposerState({
       : '',
   })
   return {
-    richtext: initRichText,
-    labels: [],
-    postgate: createPostgateRecord({post: ''}),
-    threadgate: threadgateViewToAllowUISetting(undefined),
-    embed: {
-      quote,
-      media,
-      link: undefined,
+    activePostIndex: 0,
+    thread: {
+      posts: [
+        {
+          richtext: initRichText,
+          labels: [],
+          embed: {
+            quote,
+            media,
+            link: undefined,
+          },
+        },
+      ],
+      postgate: createPostgateRecord({post: ''}),
+      threadgate: threadgateViewToAllowUISetting(undefined),
     },
   }
 }