Move remaining composer state into reducer (#5623)
Co-authored-by: Mary <git@mary.my.id> Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
+17
-28
@@ -4,7 +4,6 @@ import {
|
|||||||
AppBskyEmbedRecord,
|
AppBskyEmbedRecord,
|
||||||
AppBskyEmbedRecordWithMedia,
|
AppBskyEmbedRecordWithMedia,
|
||||||
AppBskyEmbedVideo,
|
AppBskyEmbedVideo,
|
||||||
AppBskyFeedPostgate,
|
|
||||||
AtUri,
|
AtUri,
|
||||||
BlobRef,
|
BlobRef,
|
||||||
BskyAgent,
|
BskyAgent,
|
||||||
@@ -17,7 +16,7 @@ import {QueryClient} from '@tanstack/react-query'
|
|||||||
import {isNetworkError} from '#/lib/strings/errors'
|
import {isNetworkError} from '#/lib/strings/errors'
|
||||||
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {ComposerImage, compressImage} from '#/state/gallery'
|
import {compressImage} from '#/state/gallery'
|
||||||
import {writePostgateRecord} from '#/state/queries/postgate'
|
import {writePostgateRecord} from '#/state/queries/postgate'
|
||||||
import {
|
import {
|
||||||
fetchResolveGifQuery,
|
fetchResolveGifQuery,
|
||||||
@@ -25,32 +24,18 @@ import {
|
|||||||
} from '#/state/queries/resolve-link'
|
} from '#/state/queries/resolve-link'
|
||||||
import {
|
import {
|
||||||
createThreadgateRecord,
|
createThreadgateRecord,
|
||||||
ThreadgateAllowUISetting,
|
|
||||||
threadgateAllowUISettingToAllowRecordValue,
|
threadgateAllowUISettingToAllowRecordValue,
|
||||||
writeThreadgateRecord,
|
writeThreadgateRecord,
|
||||||
} from '#/state/queries/threadgate'
|
} from '#/state/queries/threadgate'
|
||||||
import {ComposerState, EmbedDraft} from '#/view/com/composer/state/composer'
|
import {ComposerDraft, EmbedDraft} from '#/view/com/composer/state/composer'
|
||||||
import {createGIFDescription} from '../gif-alt-text'
|
import {createGIFDescription} from '../gif-alt-text'
|
||||||
import {LinkMeta} from '../link-meta/link-meta'
|
|
||||||
import {uploadBlob} from './upload-blob'
|
import {uploadBlob} from './upload-blob'
|
||||||
|
|
||||||
export {uploadBlob}
|
export {uploadBlob}
|
||||||
|
|
||||||
export interface ExternalEmbedDraft {
|
|
||||||
uri: string
|
|
||||||
isLoading: boolean
|
|
||||||
meta?: LinkMeta
|
|
||||||
embed?: AppBskyEmbedRecord.Main
|
|
||||||
localThumb?: ComposerImage
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PostOpts {
|
interface PostOpts {
|
||||||
composerState: ComposerState // TODO: Not used yet.
|
draft: ComposerDraft
|
||||||
rawText: string
|
|
||||||
replyTo?: string
|
replyTo?: string
|
||||||
labels?: string[]
|
|
||||||
threadgate: ThreadgateAllowUISetting[]
|
|
||||||
postgate: AppBskyFeedPostgate.Record
|
|
||||||
onStateChange?: (state: string) => void
|
onStateChange?: (state: string) => void
|
||||||
langs?: string[]
|
langs?: string[]
|
||||||
}
|
}
|
||||||
@@ -60,8 +45,12 @@ export async function post(
|
|||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
opts: PostOpts,
|
opts: PostOpts,
|
||||||
) {
|
) {
|
||||||
|
const draft = opts.draft
|
||||||
let reply
|
let reply
|
||||||
let rt = new RichText({text: opts.rawText.trimEnd()}, {cleanNewlines: true})
|
let rt = new RichText(
|
||||||
|
{text: draft.richtext.text.trimEnd()},
|
||||||
|
{cleanNewlines: true},
|
||||||
|
)
|
||||||
|
|
||||||
opts.onStateChange?.('Processing...')
|
opts.onStateChange?.('Processing...')
|
||||||
|
|
||||||
@@ -73,7 +62,7 @@ export async function post(
|
|||||||
const embed = await resolveEmbed(
|
const embed = await resolveEmbed(
|
||||||
agent,
|
agent,
|
||||||
queryClient,
|
queryClient,
|
||||||
opts.composerState,
|
draft,
|
||||||
opts.onStateChange,
|
opts.onStateChange,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,10 +87,10 @@ export async function post(
|
|||||||
|
|
||||||
// set labels
|
// set labels
|
||||||
let labels: ComAtprotoLabelDefs.SelfLabels | undefined
|
let labels: ComAtprotoLabelDefs.SelfLabels | undefined
|
||||||
if (opts.labels?.length) {
|
if (draft.labels.length) {
|
||||||
labels = {
|
labels = {
|
||||||
$type: 'com.atproto.label.defs#selfLabels',
|
$type: 'com.atproto.label.defs#selfLabels',
|
||||||
values: opts.labels.map(val => ({val})),
|
values: draft.labels.map(val => ({val})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +124,7 @@ export async function post(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.threadgate.some(tg => tg.type !== 'everybody')) {
|
if (draft.threadgate.some(tg => tg.type !== 'everybody')) {
|
||||||
try {
|
try {
|
||||||
// TODO: this needs to be batch-created with the post!
|
// TODO: this needs to be batch-created with the post!
|
||||||
await writeThreadgateRecord({
|
await writeThreadgateRecord({
|
||||||
@@ -143,7 +132,7 @@ export async function post(
|
|||||||
postUri: res.uri,
|
postUri: res.uri,
|
||||||
threadgate: createThreadgateRecord({
|
threadgate: createThreadgateRecord({
|
||||||
post: res.uri,
|
post: res.uri,
|
||||||
allow: threadgateAllowUISettingToAllowRecordValue(opts.threadgate),
|
allow: threadgateAllowUISettingToAllowRecordValue(draft.threadgate),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -158,8 +147,8 @@ export async function post(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
opts.postgate.embeddingRules?.length ||
|
draft.postgate.embeddingRules?.length ||
|
||||||
opts.postgate.detachedEmbeddingUris?.length
|
draft.postgate.detachedEmbeddingUris?.length
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// TODO: this needs to be batch-created with the post!
|
// TODO: this needs to be batch-created with the post!
|
||||||
@@ -167,7 +156,7 @@ export async function post(
|
|||||||
agent,
|
agent,
|
||||||
postUri: res.uri,
|
postUri: res.uri,
|
||||||
postgate: {
|
postgate: {
|
||||||
...opts.postgate,
|
...draft.postgate,
|
||||||
post: res.uri,
|
post: res.uri,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -188,7 +177,7 @@ export async function post(
|
|||||||
async function resolveEmbed(
|
async function resolveEmbed(
|
||||||
agent: BskyAgent,
|
agent: BskyAgent,
|
||||||
queryClient: QueryClient,
|
queryClient: QueryClient,
|
||||||
draft: ComposerState,
|
draft: ComposerDraft,
|
||||||
onStateChange: ((state: string) => void) | undefined,
|
onStateChange: ((state: string) => void) | undefined,
|
||||||
): Promise<
|
): Promise<
|
||||||
| AppBskyEmbedImages.Main
|
| AppBskyEmbedImages.Main
|
||||||
|
|||||||
+68
-25
@@ -1,17 +1,20 @@
|
|||||||
import {AppBskyActorDefs, ComAtprotoRepoStrongRef} from '@atproto/api'
|
import {
|
||||||
|
AppBskyActorDefs,
|
||||||
|
AppBskyFeedPost,
|
||||||
|
AppBskyGraphStarterpack,
|
||||||
|
ComAtprotoRepoStrongRef,
|
||||||
|
} from '@atproto/api'
|
||||||
import {AtUri} from '@atproto/api'
|
import {AtUri} from '@atproto/api'
|
||||||
import {BskyAgent} from '@atproto/api'
|
import {BskyAgent} from '@atproto/api'
|
||||||
|
|
||||||
import {POST_IMG_MAX} from '#/lib/constants'
|
import {POST_IMG_MAX} from '#/lib/constants'
|
||||||
import {
|
|
||||||
getFeedAsEmbed,
|
|
||||||
getListAsEmbed,
|
|
||||||
getPostAsQuote,
|
|
||||||
getStarterPackAsEmbed,
|
|
||||||
} from '#/lib/link-meta/bsky'
|
|
||||||
import {getLinkMeta} from '#/lib/link-meta/link-meta'
|
import {getLinkMeta} from '#/lib/link-meta/link-meta'
|
||||||
import {resolveShortLink} from '#/lib/link-meta/resolve-short-link'
|
import {resolveShortLink} from '#/lib/link-meta/resolve-short-link'
|
||||||
import {downloadAndResize} from '#/lib/media/manip'
|
import {downloadAndResize} from '#/lib/media/manip'
|
||||||
|
import {
|
||||||
|
createStarterPackUri,
|
||||||
|
parseStarterPackUri,
|
||||||
|
} from '#/lib/strings/starter-pack'
|
||||||
import {
|
import {
|
||||||
isBskyCustomFeedUrl,
|
isBskyCustomFeedUrl,
|
||||||
isBskyListUrl,
|
isBskyListUrl,
|
||||||
@@ -24,6 +27,7 @@ import {ComposerImage} from '#/state/gallery'
|
|||||||
import {createComposerImage} from '#/state/gallery'
|
import {createComposerImage} from '#/state/gallery'
|
||||||
import {Gif} from '#/state/queries/tenor'
|
import {Gif} from '#/state/queries/tenor'
|
||||||
import {createGIFDescription} from '../gif-alt-text'
|
import {createGIFDescription} from '../gif-alt-text'
|
||||||
|
import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers'
|
||||||
|
|
||||||
type ResolvedExternalLink = {
|
type ResolvedExternalLink = {
|
||||||
type: 'external'
|
type: 'external'
|
||||||
@@ -60,6 +64,12 @@ export type ResolvedLink =
|
|||||||
| ResolvedPostRecord
|
| ResolvedPostRecord
|
||||||
| ResolvedOtherRecord
|
| ResolvedOtherRecord
|
||||||
|
|
||||||
|
class EmbeddingDisabledError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super('Embedding is disabled for this record')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function resolveLink(
|
export async function resolveLink(
|
||||||
agent: BskyAgent,
|
agent: BskyAgent,
|
||||||
uri: string,
|
uri: string,
|
||||||
@@ -68,55 +78,88 @@ export async function resolveLink(
|
|||||||
uri = await resolveShortLink(uri)
|
uri = await resolveShortLink(uri)
|
||||||
}
|
}
|
||||||
if (isBskyPostUrl(uri)) {
|
if (isBskyPostUrl(uri)) {
|
||||||
// TODO: Remove this abstraction.
|
uri = convertBskyAppUrlIfNeeded(uri)
|
||||||
// TODO: Nice error messages (e.g. EmbeddingDisabledError).
|
const [_0, user, _1, rkey] = uri.split('/').filter(Boolean)
|
||||||
const result = await getPostAsQuote(getPost, uri)
|
const recordUri = makeRecordUri(user, 'app.bsky.feed.post', rkey)
|
||||||
|
const post = await getPost({uri: recordUri})
|
||||||
|
if (post.viewer?.embeddingDisabled) {
|
||||||
|
throw new EmbeddingDisabledError()
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
type: 'record',
|
type: 'record',
|
||||||
record: {
|
record: {
|
||||||
cid: result.cid,
|
cid: post.cid,
|
||||||
uri: result.uri,
|
uri: post.uri,
|
||||||
},
|
},
|
||||||
kind: 'post',
|
kind: 'post',
|
||||||
meta: result,
|
meta: {
|
||||||
|
text: AppBskyFeedPost.isRecord(post.record) ? post.record.text : '',
|
||||||
|
indexedAt: post.indexedAt,
|
||||||
|
author: post.author,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isBskyCustomFeedUrl(uri)) {
|
if (isBskyCustomFeedUrl(uri)) {
|
||||||
// TODO: Remove this abstraction.
|
uri = convertBskyAppUrlIfNeeded(uri)
|
||||||
const result = await getFeedAsEmbed(agent, fetchDid, uri)
|
const [_0, handleOrDid, _1, rkey] = uri.split('/').filter(Boolean)
|
||||||
|
const did = await fetchDid(handleOrDid)
|
||||||
|
const feed = makeRecordUri(did, 'app.bsky.feed.generator', rkey)
|
||||||
|
const res = await agent.app.bsky.feed.getFeedGenerator({feed})
|
||||||
return {
|
return {
|
||||||
type: 'record',
|
type: 'record',
|
||||||
record: result.embed!.record,
|
record: {
|
||||||
|
uri: res.data.view.uri,
|
||||||
|
cid: res.data.view.cid,
|
||||||
|
},
|
||||||
kind: 'other',
|
kind: 'other',
|
||||||
meta: {
|
meta: {
|
||||||
// TODO: Include hydrated content instead.
|
// TODO: Include hydrated content instead.
|
||||||
title: result.meta!.title!,
|
title: res.data.view.displayName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isBskyListUrl(uri)) {
|
if (isBskyListUrl(uri)) {
|
||||||
// TODO: Remove this abstraction.
|
uri = convertBskyAppUrlIfNeeded(uri)
|
||||||
const result = await getListAsEmbed(agent, fetchDid, uri)
|
const [_0, handleOrDid, _1, rkey] = uri.split('/').filter(Boolean)
|
||||||
|
const did = await fetchDid(handleOrDid)
|
||||||
|
const list = makeRecordUri(did, 'app.bsky.graph.list', rkey)
|
||||||
|
const res = await agent.app.bsky.graph.getList({list})
|
||||||
return {
|
return {
|
||||||
type: 'record',
|
type: 'record',
|
||||||
record: result.embed!.record,
|
record: {
|
||||||
|
uri: res.data.list.uri,
|
||||||
|
cid: res.data.list.cid,
|
||||||
|
},
|
||||||
kind: 'other',
|
kind: 'other',
|
||||||
meta: {
|
meta: {
|
||||||
// TODO: Include hydrated content instead.
|
// TODO: Include hydrated content instead.
|
||||||
title: result.meta!.title!,
|
title: res.data.list.name,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isBskyStartUrl(uri) || isBskyStarterPackUrl(uri)) {
|
if (isBskyStartUrl(uri) || isBskyStarterPackUrl(uri)) {
|
||||||
// TODO: Remove this abstraction.
|
const parsed = parseStarterPackUri(uri)
|
||||||
const result = await getStarterPackAsEmbed(agent, fetchDid, uri)
|
if (!parsed) {
|
||||||
|
throw new Error(
|
||||||
|
'Unexpectedly called getStarterPackAsEmbed with a non-starterpack url',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const did = await fetchDid(parsed.name)
|
||||||
|
const starterPack = createStarterPackUri({did, rkey: parsed.rkey})
|
||||||
|
const res = await agent.app.bsky.graph.getStarterPack({starterPack})
|
||||||
|
const record = res.data.starterPack.record
|
||||||
return {
|
return {
|
||||||
type: 'record',
|
type: 'record',
|
||||||
record: result.embed!.record,
|
record: {
|
||||||
|
uri: res.data.starterPack.uri,
|
||||||
|
cid: res.data.starterPack.cid,
|
||||||
|
},
|
||||||
kind: 'other',
|
kind: 'other',
|
||||||
meta: {
|
meta: {
|
||||||
// TODO: Include hydrated content instead.
|
// TODO: Include hydrated content instead.
|
||||||
title: result.meta!.title!,
|
title: AppBskyGraphStarterpack.isRecord(record)
|
||||||
|
? record.name
|
||||||
|
: 'Starter Pack',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,225 +0,0 @@
|
|||||||
import {AppBskyFeedPost, AppBskyGraphStarterpack, BskyAgent} from '@atproto/api'
|
|
||||||
|
|
||||||
import {useFetchDid} from '#/state/queries/handle'
|
|
||||||
import {useGetPost} from '#/state/queries/post'
|
|
||||||
import * as apilib from 'lib/api/index'
|
|
||||||
import {
|
|
||||||
createStarterPackUri,
|
|
||||||
parseStarterPackUri,
|
|
||||||
} from 'lib/strings/starter-pack'
|
|
||||||
import {ComposerOptsQuote} from 'state/shell/composer'
|
|
||||||
// import {match as matchRoute} from 'view/routes'
|
|
||||||
import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers'
|
|
||||||
import {LikelyType, LinkMeta} from './link-meta'
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// import {Home} from 'view/screens/Home'
|
|
||||||
// import {Search} from 'view/screens/Search'
|
|
||||||
// import {Notifications} from 'view/screens/Notifications'
|
|
||||||
// import {PostThread} from 'view/screens/PostThread'
|
|
||||||
// import {PostUpvotedBy} from 'view/screens/PostUpvotedBy'
|
|
||||||
// import {PostRepostedBy} from 'view/screens/PostRepostedBy'
|
|
||||||
// import {Profile} from 'view/screens/Profile'
|
|
||||||
// import {ProfileFollowers} from 'view/screens/ProfileFollowers'
|
|
||||||
// import {ProfileFollows} from 'view/screens/ProfileFollows'
|
|
||||||
|
|
||||||
// NOTE
|
|
||||||
// this is a hack around the lack of hosted social metadata
|
|
||||||
// remove once that's implemented
|
|
||||||
// -prf
|
|
||||||
export async function extractBskyMeta(
|
|
||||||
agent: BskyAgent,
|
|
||||||
url: string,
|
|
||||||
): Promise<LinkMeta> {
|
|
||||||
url = convertBskyAppUrlIfNeeded(url)
|
|
||||||
// const route = matchRoute(url)
|
|
||||||
let meta: LinkMeta = {
|
|
||||||
likelyType: LikelyType.AtpData,
|
|
||||||
url,
|
|
||||||
// title: route.defaultTitle,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (route.Com === Home) {
|
|
||||||
// meta = {
|
|
||||||
// ...meta,
|
|
||||||
// title: 'Bluesky',
|
|
||||||
// description: 'A new kind of social network',
|
|
||||||
// }
|
|
||||||
// } else if (route.Com === Search) {
|
|
||||||
// meta = {
|
|
||||||
// ...meta,
|
|
||||||
// title: 'Search - Bluesky',
|
|
||||||
// description: 'A new kind of social network',
|
|
||||||
// }
|
|
||||||
// } else if (route.Com === Notifications) {
|
|
||||||
// meta = {
|
|
||||||
// ...meta,
|
|
||||||
// title: 'Notifications - Bluesky',
|
|
||||||
// description: 'A new kind of social network',
|
|
||||||
// }
|
|
||||||
// } else if (
|
|
||||||
// route.Com === PostThread ||
|
|
||||||
// route.Com === PostUpvotedBy ||
|
|
||||||
// route.Com === PostRepostedBy
|
|
||||||
// ) {
|
|
||||||
// // post and post-related screens
|
|
||||||
// const threadUri = makeRecordUri(
|
|
||||||
// route.params.name,
|
|
||||||
// 'app.bsky.feed.post',
|
|
||||||
// route.params.rkey,
|
|
||||||
// )
|
|
||||||
// const threadView = new PostThreadViewModel(store, {
|
|
||||||
// uri: threadUri,
|
|
||||||
// depth: 0,
|
|
||||||
// })
|
|
||||||
// await threadView.setup().catch(_err => undefined)
|
|
||||||
// const title = [
|
|
||||||
// route.Com === PostUpvotedBy
|
|
||||||
// ? 'Likes on a post by'
|
|
||||||
// : route.Com === PostRepostedBy
|
|
||||||
// ? 'Reposts of a post by'
|
|
||||||
// : 'Post by',
|
|
||||||
// threadView.thread?.post.author.displayName ||
|
|
||||||
// threadView.thread?.post.author.handle ||
|
|
||||||
// 'a bluesky user',
|
|
||||||
// ].join(' ')
|
|
||||||
// meta = {
|
|
||||||
// ...meta,
|
|
||||||
// title,
|
|
||||||
// description: threadView.thread?.postRecord?.text,
|
|
||||||
// }
|
|
||||||
// } else if (
|
|
||||||
// route.Com === Profile ||
|
|
||||||
// route.Com === ProfileFollowers ||
|
|
||||||
// route.Com === ProfileFollows
|
|
||||||
// ) {
|
|
||||||
// // profile and profile-related screens
|
|
||||||
// const profile = await store.profiles.getProfile(route.params.name)
|
|
||||||
// if (profile?.data) {
|
|
||||||
// meta = {
|
|
||||||
// ...meta,
|
|
||||||
// title: profile.data.displayName || profile.data.handle,
|
|
||||||
// description: profile.data.description,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EmbeddingDisabledError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super('Embedding is disabled for this record')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export async function getPostAsQuote(
|
|
||||||
getPost: ReturnType<typeof useGetPost>,
|
|
||||||
url: string,
|
|
||||||
): Promise<ComposerOptsQuote> {
|
|
||||||
url = convertBskyAppUrlIfNeeded(url)
|
|
||||||
const [_0, user, _1, rkey] = url.split('/').filter(Boolean)
|
|
||||||
const uri = makeRecordUri(user, 'app.bsky.feed.post', rkey)
|
|
||||||
const post = await getPost({uri: uri})
|
|
||||||
if (post.viewer?.embeddingDisabled) {
|
|
||||||
throw new EmbeddingDisabledError()
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
uri: post.uri,
|
|
||||||
cid: post.cid,
|
|
||||||
text: AppBskyFeedPost.isRecord(post.record) ? post.record.text : '',
|
|
||||||
indexedAt: post.indexedAt,
|
|
||||||
author: post.author,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getFeedAsEmbed(
|
|
||||||
agent: BskyAgent,
|
|
||||||
fetchDid: ReturnType<typeof useFetchDid>,
|
|
||||||
url: string,
|
|
||||||
): Promise<apilib.ExternalEmbedDraft> {
|
|
||||||
url = convertBskyAppUrlIfNeeded(url)
|
|
||||||
const [_0, handleOrDid, _1, rkey] = url.split('/').filter(Boolean)
|
|
||||||
const did = await fetchDid(handleOrDid)
|
|
||||||
const feed = makeRecordUri(did, 'app.bsky.feed.generator', rkey)
|
|
||||||
const res = await agent.app.bsky.feed.getFeedGenerator({feed})
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
uri: feed,
|
|
||||||
meta: {
|
|
||||||
url: feed,
|
|
||||||
likelyType: LikelyType.AtpData,
|
|
||||||
title: res.data.view.displayName,
|
|
||||||
},
|
|
||||||
embed: {
|
|
||||||
$type: 'app.bsky.embed.record',
|
|
||||||
record: {
|
|
||||||
uri: res.data.view.uri,
|
|
||||||
cid: res.data.view.cid,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getListAsEmbed(
|
|
||||||
agent: BskyAgent,
|
|
||||||
fetchDid: ReturnType<typeof useFetchDid>,
|
|
||||||
url: string,
|
|
||||||
): Promise<apilib.ExternalEmbedDraft> {
|
|
||||||
url = convertBskyAppUrlIfNeeded(url)
|
|
||||||
const [_0, handleOrDid, _1, rkey] = url.split('/').filter(Boolean)
|
|
||||||
const did = await fetchDid(handleOrDid)
|
|
||||||
const list = makeRecordUri(did, 'app.bsky.graph.list', rkey)
|
|
||||||
const res = await agent.app.bsky.graph.getList({list})
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
uri: list,
|
|
||||||
meta: {
|
|
||||||
url: list,
|
|
||||||
likelyType: LikelyType.AtpData,
|
|
||||||
title: res.data.list.name,
|
|
||||||
},
|
|
||||||
embed: {
|
|
||||||
$type: 'app.bsky.embed.record',
|
|
||||||
record: {
|
|
||||||
uri: res.data.list.uri,
|
|
||||||
cid: res.data.list.cid,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStarterPackAsEmbed(
|
|
||||||
agent: BskyAgent,
|
|
||||||
fetchDid: ReturnType<typeof useFetchDid>,
|
|
||||||
url: string,
|
|
||||||
): Promise<apilib.ExternalEmbedDraft> {
|
|
||||||
const parsed = parseStarterPackUri(url)
|
|
||||||
if (!parsed) {
|
|
||||||
throw new Error(
|
|
||||||
'Unexepectedly called getStarterPackAsEmbed with a non-starterpack url',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const did = await fetchDid(parsed.name)
|
|
||||||
const starterPack = createStarterPackUri({did, rkey: parsed.rkey})
|
|
||||||
const res = await agent.app.bsky.graph.getStarterPack({starterPack})
|
|
||||||
const record = res.data.starterPack.record
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
uri: starterPack,
|
|
||||||
meta: {
|
|
||||||
url: starterPack,
|
|
||||||
likelyType: LikelyType.AtpData,
|
|
||||||
// Validation here should never fail
|
|
||||||
title: AppBskyGraphStarterpack.isRecord(record)
|
|
||||||
? record.name
|
|
||||||
: 'Starter Pack',
|
|
||||||
},
|
|
||||||
embed: {
|
|
||||||
$type: 'app.bsky.embed.record',
|
|
||||||
record: {
|
|
||||||
uri: res.data.starterPack.uri,
|
|
||||||
cid: res.data.starterPack.cid,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import {BskyAgent} from '@atproto/api'
|
import {BskyAgent} from '@atproto/api'
|
||||||
|
|
||||||
import {LINK_META_PROXY} from 'lib/constants'
|
import {LINK_META_PROXY} from '#/lib/constants'
|
||||||
import {getGiphyMetaUri} from 'lib/strings/embed-player'
|
import {getGiphyMetaUri} from '#/lib/strings/embed-player'
|
||||||
import {parseStarterPackUri} from 'lib/strings/starter-pack'
|
import {parseStarterPackUri} from '#/lib/strings/starter-pack'
|
||||||
import {isBskyAppUrl} from '../strings/url-helpers'
|
import {isBskyAppUrl} from '../strings/url-helpers'
|
||||||
import {extractBskyMeta} from './bsky'
|
|
||||||
|
|
||||||
export enum LikelyType {
|
export enum LikelyType {
|
||||||
HTML,
|
HTML,
|
||||||
@@ -31,7 +30,10 @@ export async function getLinkMeta(
|
|||||||
timeout = 15e3,
|
timeout = 15e3,
|
||||||
): Promise<LinkMeta> {
|
): Promise<LinkMeta> {
|
||||||
if (isBskyAppUrl(url) && !parseStarterPackUri(url)) {
|
if (isBskyAppUrl(url) && !parseStarterPackUri(url)) {
|
||||||
return extractBskyMeta(agent, url)
|
return {
|
||||||
|
likelyType: LikelyType.AtpData,
|
||||||
|
url,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let urlp
|
let urlp
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import {
|
|||||||
AppBskyFeedGetPostThread,
|
AppBskyFeedGetPostThread,
|
||||||
BskyAgent,
|
BskyAgent,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {RichText} from '@atproto/api'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
@@ -57,7 +56,6 @@ import {usePalette} from '#/lib/hooks/usePalette'
|
|||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
import {logEvent} from '#/lib/statsig/statsig'
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {insertMentionAt} from '#/lib/strings/mention-manip'
|
|
||||||
import {shortenLinks} from '#/lib/strings/rich-text-manip'
|
import {shortenLinks} from '#/lib/strings/rich-text-manip'
|
||||||
import {colors, s} from '#/lib/styles'
|
import {colors, s} from '#/lib/styles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
@@ -73,11 +71,8 @@ import {
|
|||||||
useLanguagePrefs,
|
useLanguagePrefs,
|
||||||
useLanguagePrefsApi,
|
useLanguagePrefsApi,
|
||||||
} from '#/state/preferences/languages'
|
} from '#/state/preferences/languages'
|
||||||
import {createPostgateRecord} from '#/state/queries/postgate/util'
|
|
||||||
import {useProfileQuery} from '#/state/queries/profile'
|
import {useProfileQuery} from '#/state/queries/profile'
|
||||||
import {Gif} from '#/state/queries/tenor'
|
import {Gif} from '#/state/queries/tenor'
|
||||||
import {ThreadgateAllowUISetting} from '#/state/queries/threadgate'
|
|
||||||
import {threadgateViewToAllowUISetting} from '#/state/queries/threadgate/util'
|
|
||||||
import {useAgent, useSession} from '#/state/session'
|
import {useAgent, useSession} from '#/state/session'
|
||||||
import {useComposerControls} from '#/state/shell/composer'
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
import {ComposerOpts} from '#/state/shell/composer'
|
import {ComposerOpts} from '#/state/shell/composer'
|
||||||
@@ -168,35 +163,40 @@ export const ComposePost = ({
|
|||||||
const [isProcessing, setIsProcessing] = useState(false)
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
const [processingState, setProcessingState] = useState('')
|
const [processingState, setProcessingState] = useState('')
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const [richtext, setRichText] = useState(
|
|
||||||
new RichText({
|
const [draft, dispatch] = useReducer(
|
||||||
text: initText
|
composerReducer,
|
||||||
? initText
|
{initImageUris, initQuoteUri: initQuote?.uri, initText, initMention},
|
||||||
: initMention
|
createComposerState,
|
||||||
? insertMentionAt(
|
|
||||||
`@${initMention}`,
|
|
||||||
initMention.length + 1,
|
|
||||||
`${initMention}`,
|
|
||||||
) // insert mention if passed in
|
|
||||||
: '',
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
const richtext = draft.richtext
|
||||||
|
let quote: string | undefined
|
||||||
|
if (draft.embed.quote) {
|
||||||
|
quote = draft.embed.quote.uri
|
||||||
|
}
|
||||||
|
let images = NO_IMAGES
|
||||||
|
if (draft.embed.media?.type === 'images') {
|
||||||
|
images = draft.embed.media.images
|
||||||
|
}
|
||||||
|
let videoState: VideoState | NoVideoState = NO_VIDEO
|
||||||
|
if (draft.embed.media?.type === 'video') {
|
||||||
|
videoState = draft.embed.media.video
|
||||||
|
}
|
||||||
|
let extGif: Gif | undefined
|
||||||
|
let extGifAlt: string | undefined
|
||||||
|
if (draft.embed.media?.type === 'gif') {
|
||||||
|
extGif = draft.embed.media.gif
|
||||||
|
extGifAlt = draft.embed.media.alt
|
||||||
|
}
|
||||||
|
let extLink: string | undefined
|
||||||
|
if (draft.embed.link) {
|
||||||
|
extLink = draft.embed.link.uri
|
||||||
|
}
|
||||||
|
|
||||||
const graphemeLength = useMemo(() => {
|
const graphemeLength = useMemo(() => {
|
||||||
return shortenLinks(richtext).graphemeLength
|
return shortenLinks(richtext).graphemeLength
|
||||||
}, [richtext])
|
}, [richtext])
|
||||||
|
|
||||||
// TODO: Move more state here.
|
|
||||||
const [composerState, dispatch] = useReducer(
|
|
||||||
composerReducer,
|
|
||||||
{initImageUris, initQuoteUri: initQuote?.uri},
|
|
||||||
createComposerState,
|
|
||||||
)
|
|
||||||
|
|
||||||
let videoState: VideoState | NoVideoState = NO_VIDEO
|
|
||||||
if (composerState.embed.media?.type === 'video') {
|
|
||||||
videoState = composerState.embed.media.video
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectVideo = React.useCallback(
|
const selectVideo = React.useCallback(
|
||||||
(asset: ImagePickerAsset) => {
|
(asset: ImagePickerAsset) => {
|
||||||
const abortController = new AbortController()
|
const abortController = new AbortController()
|
||||||
@@ -241,35 +241,8 @@ export const ComposePost = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const hasVideo = Boolean(videoState.asset || videoState.video)
|
const hasVideo = Boolean(videoState.asset || videoState.video)
|
||||||
|
|
||||||
const [publishOnUpload, setPublishOnUpload] = useState(false)
|
const [publishOnUpload, setPublishOnUpload] = useState(false)
|
||||||
|
|
||||||
const [labels, setLabels] = useState<string[]>([])
|
|
||||||
const [threadgateAllowUISettings, onChangeThreadgateAllowUISettings] =
|
|
||||||
useState<ThreadgateAllowUISetting[]>(
|
|
||||||
threadgateViewToAllowUISetting(undefined),
|
|
||||||
)
|
|
||||||
const [postgate, setPostgate] = useState(createPostgateRecord({post: ''}))
|
|
||||||
|
|
||||||
let quote: string | undefined
|
|
||||||
if (composerState.embed.quote) {
|
|
||||||
quote = composerState.embed.quote.uri
|
|
||||||
}
|
|
||||||
let images = NO_IMAGES
|
|
||||||
if (composerState.embed.media?.type === 'images') {
|
|
||||||
images = composerState.embed.media.images
|
|
||||||
}
|
|
||||||
let extGif: Gif | undefined
|
|
||||||
let extGifAlt: string | undefined
|
|
||||||
if (composerState.embed.media?.type === 'gif') {
|
|
||||||
extGif = composerState.embed.media.gif
|
|
||||||
extGifAlt = composerState.embed.media.alt
|
|
||||||
}
|
|
||||||
let extLink: string | undefined
|
|
||||||
if (composerState.embed.link) {
|
|
||||||
extLink = composerState.embed.link.uri
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
closeComposer()
|
closeComposer()
|
||||||
}, [closeComposer])
|
}, [closeComposer])
|
||||||
@@ -419,12 +392,8 @@ export const ComposePost = ({
|
|||||||
try {
|
try {
|
||||||
postUri = (
|
postUri = (
|
||||||
await apilib.post(agent, queryClient, {
|
await apilib.post(agent, queryClient, {
|
||||||
composerState, // TODO: move more state here.
|
draft: draft,
|
||||||
rawText: richtext.text,
|
|
||||||
replyTo: replyTo?.uri,
|
replyTo: replyTo?.uri,
|
||||||
labels,
|
|
||||||
threadgate: threadgateAllowUISettings,
|
|
||||||
postgate,
|
|
||||||
onStateChange: setProcessingState,
|
onStateChange: setProcessingState,
|
||||||
langs: toPostLanguages(langPrefs.postLanguage),
|
langs: toPostLanguages(langPrefs.postLanguage),
|
||||||
})
|
})
|
||||||
@@ -497,24 +466,21 @@ export const ComposePost = ({
|
|||||||
[
|
[
|
||||||
_,
|
_,
|
||||||
agent,
|
agent,
|
||||||
composerState,
|
draft,
|
||||||
extLink,
|
extLink,
|
||||||
images,
|
images,
|
||||||
graphemeLength,
|
graphemeLength,
|
||||||
isAltTextRequiredAndMissing,
|
isAltTextRequiredAndMissing,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
labels,
|
|
||||||
langPrefs.postLanguage,
|
langPrefs.postLanguage,
|
||||||
onClose,
|
onClose,
|
||||||
onPost,
|
onPost,
|
||||||
postgate,
|
|
||||||
quote,
|
quote,
|
||||||
initQuote,
|
initQuote,
|
||||||
initQuoteCount,
|
initQuoteCount,
|
||||||
replyTo,
|
replyTo,
|
||||||
richtext.text,
|
richtext.text,
|
||||||
setLangPrefs,
|
setLangPrefs,
|
||||||
threadgateAllowUISettings,
|
|
||||||
videoState.asset,
|
videoState.asset,
|
||||||
videoState.status,
|
videoState.status,
|
||||||
queryClient,
|
queryClient,
|
||||||
@@ -615,8 +581,10 @@ export const ComposePost = ({
|
|||||||
) : (
|
) : (
|
||||||
<View style={[styles.postBtnWrapper]}>
|
<View style={[styles.postBtnWrapper]}>
|
||||||
<LabelsBtn
|
<LabelsBtn
|
||||||
labels={labels}
|
labels={draft.labels}
|
||||||
onChange={setLabels}
|
onChange={nextLabels => {
|
||||||
|
dispatch({type: 'update_labels', labels: nextLabels})
|
||||||
|
}}
|
||||||
hasMedia={hasMedia || Boolean(extLink)}
|
hasMedia={hasMedia || Boolean(extLink)}
|
||||||
/>
|
/>
|
||||||
{canPost ? (
|
{canPost ? (
|
||||||
@@ -698,7 +666,9 @@ export const ComposePost = ({
|
|||||||
richtext={richtext}
|
richtext={richtext}
|
||||||
placeholder={selectTextInputPlaceholder}
|
placeholder={selectTextInputPlaceholder}
|
||||||
autoFocus
|
autoFocus
|
||||||
setRichText={setRichText}
|
setRichText={rt => {
|
||||||
|
dispatch({type: 'update_richtext', richtext: rt})
|
||||||
|
}}
|
||||||
onPhotoPasted={onPhotoPasted}
|
onPhotoPasted={onPhotoPasted}
|
||||||
onPressPublish={() => onPressPublish()}
|
onPressPublish={() => onPressPublish()}
|
||||||
onNewLink={onNewLink}
|
onNewLink={onNewLink}
|
||||||
@@ -734,7 +704,7 @@ export const ComposePost = ({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!composerState.embed.media && extLink && (
|
{!draft.embed.media && extLink && (
|
||||||
<View style={a.relative} key={extLink}>
|
<View style={a.relative} key={extLink}>
|
||||||
<ExternalEmbedLink
|
<ExternalEmbedLink
|
||||||
uri={extLink}
|
uri={extLink}
|
||||||
@@ -815,12 +785,17 @@ export const ComposePost = ({
|
|||||||
|
|
||||||
{replyTo ? null : (
|
{replyTo ? null : (
|
||||||
<ThreadgateBtn
|
<ThreadgateBtn
|
||||||
postgate={postgate}
|
postgate={draft.postgate}
|
||||||
onChangePostgate={setPostgate}
|
onChangePostgate={nextPostgate => {
|
||||||
threadgateAllowUISettings={threadgateAllowUISettings}
|
dispatch({type: 'update_postgate', postgate: nextPostgate})
|
||||||
onChangeThreadgateAllowUISettings={
|
}}
|
||||||
onChangeThreadgateAllowUISettings
|
threadgateAllowUISettings={draft.threadgate}
|
||||||
}
|
onChangeThreadgateAllowUISettings={nextThreadgate => {
|
||||||
|
dispatch({
|
||||||
|
type: 'update_threadgate',
|
||||||
|
threadgate: nextThreadgate,
|
||||||
|
})
|
||||||
|
}}
|
||||||
style={bottomBarAnimatedStyle}
|
style={bottomBarAnimatedStyle}
|
||||||
Portal={Portal.Portal}
|
Portal={Portal.Portal}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import {ImagePickerAsset} from 'expo-image-picker'
|
import {ImagePickerAsset} from 'expo-image-picker'
|
||||||
|
import {AppBskyFeedPostgate, RichText} from '@atproto/api'
|
||||||
|
|
||||||
|
import {insertMentionAt} from '#/lib/strings/mention-manip'
|
||||||
import {
|
import {
|
||||||
isBskyPostUrl,
|
isBskyPostUrl,
|
||||||
postUriToRelativePath,
|
postUriToRelativePath,
|
||||||
toBskyAppUrl,
|
toBskyAppUrl,
|
||||||
} from '#/lib/strings/url-helpers'
|
} from '#/lib/strings/url-helpers'
|
||||||
import {ComposerImage, createInitialImages} from '#/state/gallery'
|
import {ComposerImage, createInitialImages} from '#/state/gallery'
|
||||||
|
import {createPostgateRecord} from '#/state/queries/postgate/util'
|
||||||
import {Gif} from '#/state/queries/tenor'
|
import {Gif} from '#/state/queries/tenor'
|
||||||
|
import {threadgateViewToAllowUISetting} from '#/state/queries/threadgate'
|
||||||
|
import {ThreadgateAllowUISetting} from '#/state/queries/threadgate'
|
||||||
import {ComposerOpts} from '#/state/shell/composer'
|
import {ComposerOpts} from '#/state/shell/composer'
|
||||||
import {createVideoState, VideoAction, videoReducer, VideoState} from './video'
|
import {createVideoState, VideoAction, videoReducer, VideoState} from './video'
|
||||||
|
|
||||||
@@ -41,12 +46,19 @@ export type EmbedDraft = {
|
|||||||
link: Link | undefined
|
link: Link | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComposerState = {
|
export type ComposerDraft = {
|
||||||
// TODO: Other draft data.
|
richtext: RichText
|
||||||
|
labels: string[]
|
||||||
|
postgate: AppBskyFeedPostgate.Record
|
||||||
|
threadgate: ThreadgateAllowUISetting[]
|
||||||
embed: EmbedDraft
|
embed: EmbedDraft
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComposerAction =
|
export type ComposerAction =
|
||||||
|
| {type: 'update_richtext'; richtext: RichText}
|
||||||
|
| {type: 'update_labels'; labels: string[]}
|
||||||
|
| {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record}
|
||||||
|
| {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]}
|
||||||
| {type: 'embed_add_images'; images: ComposerImage[]}
|
| {type: 'embed_add_images'; images: ComposerImage[]}
|
||||||
| {type: 'embed_update_image'; image: ComposerImage}
|
| {type: 'embed_update_image'; image: ComposerImage}
|
||||||
| {type: 'embed_remove_image'; image: ComposerImage}
|
| {type: 'embed_remove_image'; image: ComposerImage}
|
||||||
@@ -67,10 +79,34 @@ export type ComposerAction =
|
|||||||
export const MAX_IMAGES = 4
|
export const MAX_IMAGES = 4
|
||||||
|
|
||||||
export function composerReducer(
|
export function composerReducer(
|
||||||
state: ComposerState,
|
state: ComposerDraft,
|
||||||
action: ComposerAction,
|
action: ComposerAction,
|
||||||
): ComposerState {
|
): ComposerDraft {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case 'update_richtext': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
richtext: action.richtext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'update_labels': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
labels: action.labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'update_postgate': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
postgate: action.postgate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'update_threadgate': {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
threadgate: action.threadgate,
|
||||||
|
}
|
||||||
|
}
|
||||||
case 'embed_add_images': {
|
case 'embed_add_images': {
|
||||||
if (action.images.length === 0) {
|
if (action.images.length === 0) {
|
||||||
return state
|
return state
|
||||||
@@ -293,12 +329,16 @@ export function composerReducer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createComposerState({
|
export function createComposerState({
|
||||||
|
initText,
|
||||||
|
initMention,
|
||||||
initImageUris,
|
initImageUris,
|
||||||
initQuoteUri,
|
initQuoteUri,
|
||||||
}: {
|
}: {
|
||||||
|
initText: string | undefined
|
||||||
|
initMention: string | undefined
|
||||||
initImageUris: ComposerOpts['imageUris']
|
initImageUris: ComposerOpts['imageUris']
|
||||||
initQuoteUri: string | undefined
|
initQuoteUri: string | undefined
|
||||||
}): ComposerState {
|
}): ComposerDraft {
|
||||||
let media: ImagesMedia | undefined
|
let media: ImagesMedia | undefined
|
||||||
if (initImageUris?.length) {
|
if (initImageUris?.length) {
|
||||||
media = {
|
media = {
|
||||||
@@ -317,7 +357,22 @@ export function createComposerState({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const initRichText = new RichText({
|
||||||
|
text: initText
|
||||||
|
? initText
|
||||||
|
: initMention
|
||||||
|
? insertMentionAt(
|
||||||
|
`@${initMention}`,
|
||||||
|
initMention.length + 1,
|
||||||
|
`${initMention}`,
|
||||||
|
)
|
||||||
|
: '',
|
||||||
|
})
|
||||||
return {
|
return {
|
||||||
|
richtext: initRichText,
|
||||||
|
labels: [],
|
||||||
|
postgate: createPostgateRecord({post: ''}),
|
||||||
|
threadgate: threadgateViewToAllowUISetting(undefined),
|
||||||
embed: {
|
embed: {
|
||||||
quote,
|
quote,
|
||||||
media,
|
media,
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export interface TextInputRef {
|
|||||||
interface TextInputProps extends ComponentProps<typeof RNTextInput> {
|
interface TextInputProps extends ComponentProps<typeof RNTextInput> {
|
||||||
richtext: RichText
|
richtext: RichText
|
||||||
placeholder: string
|
placeholder: string
|
||||||
setRichText: (v: RichText | ((v: RichText) => RichText)) => void
|
setRichText: (v: RichText) => void
|
||||||
onPhotoPasted: (uri: string) => void
|
onPhotoPasted: (uri: string) => void
|
||||||
onPressPublish: (richtext: RichText) => Promise<void>
|
onPressPublish: (richtext: RichText) => Promise<void>
|
||||||
onNewLink: (uri: string) => void
|
onNewLink: (uri: string) => void
|
||||||
|
|||||||
Reference in New Issue
Block a user