diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index f4e290ca8d..b5f2d84d4f 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -190,7 +190,7 @@ export const ComposePost = ({
// TODO: Move more state here.
const [composerState, dispatch] = useReducer(
composerReducer,
- {initImageUris},
+ {initImageUris, initQuoteUri: initQuote?.uri},
createComposerState,
)
@@ -337,6 +337,7 @@ export const ComposePost = ({
const onNewLink = useCallback(
(uri: string) => {
+ dispatch({type: 'embed_add_uri', uri})
if (extLink != null) return
setExtLink({uri, isLoading: true})
},
@@ -582,6 +583,7 @@ export const ComposePost = ({
const onSelectGif = useCallback(
(gif: Gif) => {
+ dispatch({type: 'embed_add_gif', gif})
setExtLink({
uri: `${gif.media_formats.gif.url}?hh=${gif.media_formats.gif.dims[1]}&ww=${gif.media_formats.gif.dims[0]}`,
isLoading: true,
@@ -600,6 +602,7 @@ export const ComposePost = ({
const handleChangeGifAltText = useCallback(
(altText: string) => {
+ dispatch({type: 'embed_update_gif', alt: altText})
setExtLink(ext =>
ext && ext.meta
? {
@@ -770,6 +773,11 @@ export const ComposePost = ({
link={extLink}
gif={extGif}
onRemove={() => {
+ if (extGif) {
+ dispatch({type: 'embed_remove_gif'})
+ } else {
+ dispatch({type: 'embed_remove_link'})
+ }
setExtLink(undefined)
setExtGif(undefined)
}}
@@ -818,7 +826,12 @@ export const ComposePost = ({
{quote.uri !== initQuote?.uri && (
- setQuote(undefined)} />
+ {
+ dispatch({type: 'embed_remove_quote'})
+ setQuote(undefined)
+ }}
+ />
)}
) : null}
diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts
index a23a5d8c86..769a0521d4 100644
--- a/src/view/com/composer/state/composer.ts
+++ b/src/view/com/composer/state/composer.ts
@@ -1,17 +1,14 @@
import {ImagePickerAsset} from 'expo-image-picker'
+import {isBskyPostUrl} from '#/lib/strings/url-helpers'
import {ComposerImage, createInitialImages} from '#/state/gallery'
+import {Gif} from '#/state/queries/tenor'
import {ComposerOpts} from '#/state/shell/composer'
import {createVideoState, VideoAction, videoReducer, VideoState} from './video'
-type PostRecord = {
- uri: string
-}
-
type ImagesMedia = {
type: 'images'
images: ComposerImage[]
- labels: string[]
}
type VideoMedia = {
@@ -19,16 +16,30 @@ type VideoMedia = {
video: VideoState
}
-type ComposerEmbed = {
- // TODO: Other record types.
- record: PostRecord | undefined
- // TODO: Other media types.
- media: ImagesMedia | VideoMedia | undefined
+type GifMedia = {
+ type: 'gif'
+ gif: Gif
+ alt: string
+}
+
+type Link = {
+ type: 'link'
+ uri: string
+}
+
+// This structure doesn't exactly correspond to the data model.
+// Instead, it maps to how the UI is organized, and how we present a post.
+type EmbedDraft = {
+ // We'll always submit quote and actual media (images, video, gifs) chosen by the user.
+ quote: Link | undefined
+ media: ImagesMedia | VideoMedia | GifMedia | undefined
+ // This field may end up ignored if we have more important things to display than a link card:
+ link: Link | undefined
}
export type ComposerState = {
// TODO: Other draft data.
- embed: ComposerEmbed
+ embed: EmbedDraft
}
export type ComposerAction =
@@ -42,6 +53,12 @@ export type ComposerAction =
}
| {type: 'embed_remove_video'}
| {type: 'embed_update_video'; videoAction: VideoAction}
+ | {type: 'embed_add_uri'; uri: string}
+ | {type: 'embed_remove_quote'}
+ | {type: 'embed_remove_link'}
+ | {type: 'embed_add_gif'; gif: Gif}
+ | {type: 'embed_update_gif'; alt: string}
+ | {type: 'embed_remove_gif'}
const MAX_IMAGES = 4
@@ -60,7 +77,6 @@ export function composerReducer(
nextMedia = {
type: 'images',
images: action.images.slice(0, MAX_IMAGES),
- labels: [],
}
} else if (prevMedia.type === 'images') {
nextMedia = {
@@ -171,6 +187,102 @@ export function composerReducer(
},
}
}
+ case 'embed_add_uri': {
+ const prevQuote = state.embed.quote
+ const prevLink = state.embed.link
+ let nextQuote = prevQuote
+ let nextLink = prevLink
+ if (isBskyPostUrl(action.uri)) {
+ if (!prevQuote) {
+ nextQuote = {
+ type: 'link',
+ uri: action.uri,
+ }
+ }
+ } else {
+ if (!prevLink) {
+ nextLink = {
+ type: 'link',
+ uri: action.uri,
+ }
+ }
+ }
+ return {
+ ...state,
+ embed: {
+ ...state.embed,
+ quote: nextQuote,
+ link: nextLink,
+ },
+ }
+ }
+ case 'embed_remove_link': {
+ return {
+ ...state,
+ embed: {
+ ...state.embed,
+ link: undefined,
+ },
+ }
+ }
+ case 'embed_remove_quote': {
+ return {
+ ...state,
+ embed: {
+ ...state.embed,
+ quote: undefined,
+ },
+ }
+ }
+ case 'embed_add_gif': {
+ const prevMedia = state.embed.media
+ let nextMedia = prevMedia
+ if (!prevMedia) {
+ nextMedia = {
+ type: 'gif',
+ gif: action.gif,
+ alt: '',
+ }
+ }
+ return {
+ ...state,
+ embed: {
+ ...state.embed,
+ media: nextMedia,
+ },
+ }
+ }
+ case 'embed_update_gif': {
+ const prevMedia = state.embed.media
+ let nextMedia = prevMedia
+ if (prevMedia?.type === 'gif') {
+ nextMedia = {
+ ...prevMedia,
+ alt: action.alt,
+ }
+ }
+ return {
+ ...state,
+ embed: {
+ ...state.embed,
+ media: nextMedia,
+ },
+ }
+ }
+ case 'embed_remove_gif': {
+ const prevMedia = state.embed.media
+ let nextMedia = prevMedia
+ if (prevMedia?.type === 'gif') {
+ nextMedia = undefined
+ }
+ return {
+ ...state,
+ embed: {
+ ...state.embed,
+ media: nextMedia,
+ },
+ }
+ }
default:
return state
}
@@ -178,22 +290,31 @@ export function composerReducer(
export function createComposerState({
initImageUris,
+ initQuoteUri,
}: {
initImageUris: ComposerOpts['imageUris']
+ initQuoteUri: string | undefined
}): ComposerState {
let media: ImagesMedia | undefined
if (initImageUris?.length) {
media = {
type: 'images',
images: createInitialImages(initImageUris),
- labels: [],
}
}
- // TODO: initial video.
+ let quote: Link | undefined
+ if (initQuoteUri) {
+ quote = {
+ type: 'link',
+ uri: initQuoteUri,
+ }
+ }
+ // TODO: Other initial content.
return {
embed: {
- record: undefined,
+ quote,
media,
+ link: undefined,
},
}
}