diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index f727aa4cac..46dbd1e66a 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 8fdc62bc3b..214f3605ec 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 = ({
@@ -543,8 +556,8 @@ function ComposerPost({
onError,
onPublish,
}: {
- draft: ComposerDraft
- dispatch: (action: ComposerAction) => void
+ draft: PostDraft
+ dispatch: (action: PostAction) => void
textInput: React.Ref
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
}) {
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 : (
{
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 ? (
{
- 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 5ff7042bc1..e65c5407ac 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 3c1edb6bd8..958718eafa 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,12 +73,65 @@ 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_postgate': {
+ return {
+ ...state,
+ thread: {
+ ...state.thread,
+ postgate: action.postgate,
+ },
+ }
+ }
+ case 'update_threadgate': {
+ return {
+ ...state,
+ thread: {
+ ...state.thread,
+ threadgate: action.threadgate,
+ },
+ }
+ }
+ case 'update_post': {
+ const nextPosts = [...state.thread.posts]
+ nextPosts[state.activePostIndex] = postReducer(
+ state.thread.posts[state.activePostIndex],
+ action.postAction,
+ )
+ return {
+ ...state,
+ thread: {
+ ...state.thread,
+ posts: nextPosts,
+ },
+ }
+ }
+ }
+}
+
+function postReducer(state: PostDraft, action: PostAction): PostDraft {
switch (action.type) {
case 'update_richtext': {
return {
@@ -96,18 +145,6 @@ export function composerReducer(
labels: action.labels,
}
}
- case 'update_postgate': {
- return {
- ...state,
- postgate: action.postgate,
- }
- }
- case 'update_threadgate': {
- return {
- ...state,
- threadgate: action.threadgate,
- }
- }
case 'embed_add_images': {
if (action.images.length === 0) {
return state
@@ -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),
},
}
}