mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Basic reply and re-edit done
This commit is contained in:
6
src/@types/mastodon.d.ts
vendored
6
src/@types/mastodon.d.ts
vendored
@ -299,9 +299,9 @@ declare namespace Mastodon {
|
|||||||
url?: string
|
url?: string
|
||||||
in_reply_to_id?: string
|
in_reply_to_id?: string
|
||||||
in_reply_to_account_id?: string
|
in_reply_to_account_id?: string
|
||||||
reblog: Status
|
reblog?: Status
|
||||||
poll: Poll
|
poll?: Poll
|
||||||
card: Card
|
card?: Card
|
||||||
language?: string
|
language?: string
|
||||||
text?: string
|
text?: string
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import client from 'src/api/client'
|
|||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
import { toast } from 'src/components/toast'
|
import { toast } from 'src/components/toast'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import getCurrentTab from 'src/utils/getCurrentTab'
|
||||||
|
|
||||||
const fireMutation = async ({
|
const fireMutation = async ({
|
||||||
id,
|
id,
|
||||||
@ -47,6 +49,7 @@ export interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
|
const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
|
||||||
|
const navigation = useNavigation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const iconColor = theme.secondary
|
const iconColor = theme.secondary
|
||||||
const iconColorAction = (state: boolean) =>
|
const iconColorAction = (state: boolean) =>
|
||||||
@ -85,7 +88,15 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const onPressReply = useCallback(() => {}, [])
|
const onPressReply = useCallback(() => {
|
||||||
|
navigation.navigate(getCurrentTab(navigation), {
|
||||||
|
screen: 'Screen-Shared-Compose',
|
||||||
|
params: {
|
||||||
|
type: 'reply',
|
||||||
|
incomingStatus: status
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
const onPressReblog = useCallback(
|
const onPressReblog = useCallback(
|
||||||
() =>
|
() =>
|
||||||
mutateAction({
|
mutateAction({
|
||||||
|
@ -41,7 +41,7 @@ const TimelineHeaderDefault: React.FC<Props> = ({ queryKey, status }) => {
|
|||||||
|
|
||||||
const onPressAction = useCallback(() => setBottomSheetVisible(true), [])
|
const onPressAction = useCallback(() => setBottomSheetVisible(true), [])
|
||||||
const onPressApplication = useCallback(() => {
|
const onPressApplication = useCallback(() => {
|
||||||
navigation.navigate('Webview', {
|
navigation.navigate('Screen-Shared-Webview', {
|
||||||
uri: status.application!.website
|
uri: status.application!.website
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Alert } from 'react-native'
|
||||||
import { useMutation, useQueryCache } from 'react-query'
|
import { useMutation, useQueryCache } from 'react-query'
|
||||||
import client from 'src/api/client'
|
import client from 'src/api/client'
|
||||||
import { MenuContainer, MenuHeader, MenuRow } from 'src/components/Menu'
|
import { MenuContainer, MenuHeader, MenuRow } from 'src/components/Menu'
|
||||||
import { toast } from 'src/components/toast'
|
import { toast } from 'src/components/toast'
|
||||||
|
import getCurrentTab from 'src/utils/getCurrentTab'
|
||||||
|
|
||||||
const fireMutation = async ({
|
const fireMutation = async ({
|
||||||
id,
|
id,
|
||||||
@ -62,6 +65,7 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
|||||||
status,
|
status,
|
||||||
setBottomSheetVisible
|
setBottomSheetVisible
|
||||||
}) => {
|
}) => {
|
||||||
|
const navigation = useNavigation()
|
||||||
const queryCache = useQueryCache()
|
const queryCache = useQueryCache()
|
||||||
const [mutateAction] = useMutation(fireMutation, {
|
const [mutateAction] = useMutation(fireMutation, {
|
||||||
onMutate: ({ id, type, stateKey, prevState }) => {
|
onMutate: ({ id, type, stateKey, prevState }) => {
|
||||||
@ -119,10 +123,38 @@ const HeaderDefaultActionsStatus: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
console.warn('功能未开发')
|
Alert.alert(
|
||||||
|
'确认删除嘟嘟?',
|
||||||
|
'你确定要删除这条嘟文并重新编辑它吗?所有相关的转嘟和喜欢都会被清除,回复将会失去关联。',
|
||||||
|
[
|
||||||
|
{ text: '取消', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: '删除并重新编辑',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: async () => {
|
||||||
|
await client({
|
||||||
|
method: 'delete',
|
||||||
|
instance: 'local',
|
||||||
|
url: `statuses/${status.id}`
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
queryCache.invalidateQueries(queryKey)
|
||||||
|
setBottomSheetVisible(false)
|
||||||
|
navigation.navigate(getCurrentTab(navigation), {
|
||||||
|
screen: 'Screen-Shared-Compose',
|
||||||
|
params: { type: 'edit', incomingStatus: res.body }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast({ type: 'error', content: '删除失败' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
iconFront='trash'
|
iconFront='trash'
|
||||||
title='删除并重发'
|
title='删除并重新编辑'
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
@ -19,10 +19,11 @@ import { getLocalAccountPreferences } from 'src/utils/slices/instancesSlice'
|
|||||||
import { HeaderLeft, HeaderRight } from 'src/components/Header'
|
import { HeaderLeft, HeaderRight } from 'src/components/Header'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
|
import formatText from './Compose/formatText'
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator()
|
const Stack = createNativeStackNavigator()
|
||||||
|
|
||||||
export type PostState = {
|
export type ComposeState = {
|
||||||
spoiler: {
|
spoiler: {
|
||||||
active: boolean
|
active: boolean
|
||||||
count: number
|
count: number
|
||||||
@ -70,36 +71,37 @@ export type PostState = {
|
|||||||
attachments: { sensitive: boolean; uploads: Mastodon.Attachment[] }
|
attachments: { sensitive: boolean; uploads: Mastodon.Attachment[] }
|
||||||
attachmentUploadProgress: { progress: number; aspect?: number } | undefined
|
attachmentUploadProgress: { progress: number; aspect?: number } | undefined
|
||||||
visibility: 'public' | 'unlisted' | 'private' | 'direct'
|
visibility: 'public' | 'unlisted' | 'private' | 'direct'
|
||||||
|
replyToStatus?: Mastodon.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PostAction =
|
export type PostAction =
|
||||||
| {
|
| {
|
||||||
type: 'spoiler'
|
type: 'spoiler'
|
||||||
payload: Partial<PostState['spoiler']>
|
payload: Partial<ComposeState['spoiler']>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'text'
|
type: 'text'
|
||||||
payload: Partial<PostState['text']>
|
payload: Partial<ComposeState['text']>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'tag'
|
type: 'tag'
|
||||||
payload: PostState['tag']
|
payload: ComposeState['tag']
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'emoji'
|
type: 'emoji'
|
||||||
payload: PostState['emoji']
|
payload: ComposeState['emoji']
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'poll'
|
type: 'poll'
|
||||||
payload: PostState['poll']
|
payload: ComposeState['poll']
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'attachments'
|
type: 'attachments'
|
||||||
payload: Partial<PostState['attachments']>
|
payload: Partial<ComposeState['attachments']>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'attachmentUploadProgress'
|
type: 'attachmentUploadProgress'
|
||||||
payload: PostState['attachmentUploadProgress']
|
payload: ComposeState['attachmentUploadProgress']
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'attachmentEdit'
|
type: 'attachmentEdit'
|
||||||
@ -107,10 +109,10 @@ export type PostAction =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'visibility'
|
type: 'visibility'
|
||||||
payload: PostState['visibility']
|
payload: ComposeState['visibility']
|
||||||
}
|
}
|
||||||
|
|
||||||
const postInitialState: PostState = {
|
const composeInitialState: ComposeState = {
|
||||||
spoiler: {
|
spoiler: {
|
||||||
active: false,
|
active: false,
|
||||||
count: 0,
|
count: 0,
|
||||||
@ -143,9 +145,75 @@ const postInitialState: PostState = {
|
|||||||
visibility:
|
visibility:
|
||||||
getLocalAccountPreferences(store.getState())[
|
getLocalAccountPreferences(store.getState())[
|
||||||
'posting:default:visibility'
|
'posting:default:visibility'
|
||||||
] || 'public'
|
] || 'public',
|
||||||
|
replyToStatus: undefined
|
||||||
}
|
}
|
||||||
const postReducer = (state: PostState, action: PostAction): PostState => {
|
const composeExistingState = ({
|
||||||
|
type,
|
||||||
|
incomingStatus
|
||||||
|
}: {
|
||||||
|
type: 'reply' | 'edit'
|
||||||
|
incomingStatus: Mastodon.Status
|
||||||
|
}): ComposeState => {
|
||||||
|
switch (type) {
|
||||||
|
case 'edit':
|
||||||
|
return {
|
||||||
|
...composeInitialState,
|
||||||
|
...(incomingStatus.spoiler_text?.length && {
|
||||||
|
spoiler: {
|
||||||
|
active: true,
|
||||||
|
count: incomingStatus.spoiler_text.length,
|
||||||
|
raw: incomingStatus.spoiler_text,
|
||||||
|
formatted: incomingStatus.spoiler_text,
|
||||||
|
selection: { start: 0, end: 0 }
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
text: {
|
||||||
|
count: incomingStatus.text!.length,
|
||||||
|
raw: incomingStatus.text!,
|
||||||
|
formatted: undefined,
|
||||||
|
selection: { start: 0, end: 0 }
|
||||||
|
},
|
||||||
|
...(incomingStatus.poll && {
|
||||||
|
poll: {
|
||||||
|
active: true,
|
||||||
|
total: incomingStatus.poll.options.length,
|
||||||
|
options: {
|
||||||
|
'0': incomingStatus.poll.options[0].title || undefined,
|
||||||
|
'1': incomingStatus.poll.options[1].title || undefined,
|
||||||
|
'2': incomingStatus.poll.options[2].title || undefined,
|
||||||
|
'3': incomingStatus.poll.options[3].title || undefined
|
||||||
|
},
|
||||||
|
multiple: incomingStatus.poll.multiple,
|
||||||
|
expire: '86400' // !!!
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
...(incomingStatus.media_attachments && {
|
||||||
|
attachments: {
|
||||||
|
sensitive: incomingStatus.sensitive,
|
||||||
|
uploads: incomingStatus.media_attachments
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
visibility: incomingStatus.visibility
|
||||||
|
}
|
||||||
|
case 'reply':
|
||||||
|
const replyPlaceholder = `@${
|
||||||
|
incomingStatus.reblog
|
||||||
|
? incomingStatus.reblog.account.acct
|
||||||
|
: incomingStatus.account.acct
|
||||||
|
} `
|
||||||
|
return {
|
||||||
|
...composeInitialState,
|
||||||
|
text: {
|
||||||
|
count: replyPlaceholder.length,
|
||||||
|
raw: replyPlaceholder,
|
||||||
|
formatted: undefined,
|
||||||
|
selection: { start: 0, end: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const postReducer = (state: ComposeState, action: PostAction): ComposeState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'spoiler':
|
case 'spoiler':
|
||||||
return { ...state, spoiler: { ...state.spoiler, ...action.payload } }
|
return { ...state, spoiler: { ...state.spoiler, ...action.payload } }
|
||||||
@ -181,11 +249,20 @@ const postReducer = (state: PostState, action: PostAction): PostState => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Compose: React.FC = () => {
|
export interface Props {
|
||||||
const { theme } = useTheme()
|
route: {
|
||||||
const navigation = useNavigation()
|
params:
|
||||||
|
| {
|
||||||
|
type?: 'reply' | 'edit'
|
||||||
|
incomingStatus: Mastodon.Status
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const Compose: React.FC<Props> = ({ route: { params } }) => {
|
||||||
|
const navigation = useNavigation()
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const [hasKeyboard, setHasKeyboard] = useState(false)
|
const [hasKeyboard, setHasKeyboard] = useState(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -205,11 +282,53 @@ const Compose: React.FC = () => {
|
|||||||
setHasKeyboard(false)
|
setHasKeyboard(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [postState, postDispatch] = useReducer(postReducer, postInitialState)
|
const [composeState, composeDispatch] = useReducer(
|
||||||
|
postReducer,
|
||||||
|
params?.type && params?.incomingStatus
|
||||||
|
? composeExistingState({
|
||||||
|
type: params.type,
|
||||||
|
incomingStatus: params.incomingStatus
|
||||||
|
})
|
||||||
|
: composeInitialState
|
||||||
|
)
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
switch (params?.type) {
|
||||||
|
case 'edit':
|
||||||
|
if (params.incomingStatus.spoiler_text) {
|
||||||
|
formatText({
|
||||||
|
origin: 'spoiler',
|
||||||
|
composeDispatch,
|
||||||
|
content: params.incomingStatus.spoiler_text,
|
||||||
|
disableDebounce: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
formatText({
|
||||||
|
origin: 'text',
|
||||||
|
composeDispatch,
|
||||||
|
content: params.incomingStatus.text!,
|
||||||
|
disableDebounce: true
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'reply':
|
||||||
|
formatText({
|
||||||
|
origin: 'text',
|
||||||
|
composeDispatch,
|
||||||
|
content: `@${
|
||||||
|
params.incomingStatus.reblog
|
||||||
|
? params.incomingStatus.reblog.account.acct
|
||||||
|
: params.incomingStatus.account.acct
|
||||||
|
} `,
|
||||||
|
disableDebounce: true
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}, [params?.type])
|
||||||
|
|
||||||
const tootPost = async () => {
|
const tootPost = async () => {
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
if (postState.text.count < 0) {
|
if (composeState.text.count < 0) {
|
||||||
Alert.alert('字数超限', '', [
|
Alert.alert('字数超限', '', [
|
||||||
{
|
{
|
||||||
text: '返回继续编辑'
|
text: '返回继续编辑'
|
||||||
@ -218,28 +337,31 @@ const Compose: React.FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
|
|
||||||
if (postState.spoiler.active) {
|
if (composeState.spoiler.active) {
|
||||||
formData.append('spoiler_text', postState.spoiler.raw)
|
formData.append('spoiler_text', composeState.spoiler.raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
formData.append('status', postState.text.raw)
|
formData.append('status', composeState.text.raw)
|
||||||
|
|
||||||
if (postState.poll.active) {
|
if (composeState.poll.active) {
|
||||||
Object.values(postState.poll.options)
|
Object.values(composeState.poll.options)
|
||||||
.filter(e => e?.length)
|
.filter(e => e?.length)
|
||||||
.forEach(e => formData.append('poll[options][]', e!))
|
.forEach(e => formData.append('poll[options][]', e!))
|
||||||
formData.append('poll[expires_in]', postState.poll.expire)
|
formData.append('poll[expires_in]', composeState.poll.expire)
|
||||||
formData.append('poll[multiple]', postState.poll.multiple.toString())
|
formData.append('poll[multiple]', composeState.poll.multiple.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postState.attachments.uploads.length) {
|
if (composeState.attachments.uploads.length) {
|
||||||
formData.append('sensitive', postState.attachments.sensitive.toString())
|
formData.append(
|
||||||
postState.attachments.uploads.forEach(e =>
|
'sensitive',
|
||||||
|
composeState.attachments.sensitive.toString()
|
||||||
|
)
|
||||||
|
composeState.attachments.uploads.forEach(e =>
|
||||||
formData.append('media_ids[]', e!.id)
|
formData.append('media_ids[]', e!.id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
formData.append('visibility', postState.visibility)
|
formData.append('visibility', composeState.visibility)
|
||||||
|
|
||||||
client({
|
client({
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@ -247,17 +369,17 @@ const Compose: React.FC = () => {
|
|||||||
url: 'statuses',
|
url: 'statuses',
|
||||||
headers: {
|
headers: {
|
||||||
'Idempotency-Key': sha256(
|
'Idempotency-Key': sha256(
|
||||||
postState.spoiler.raw +
|
composeState.spoiler.raw +
|
||||||
postState.text.raw +
|
composeState.text.raw +
|
||||||
postState.poll.options['0'] +
|
composeState.poll.options['0'] +
|
||||||
postState.poll.options['1'] +
|
composeState.poll.options['1'] +
|
||||||
postState.poll.options['2'] +
|
composeState.poll.options['2'] +
|
||||||
postState.poll.options['3'] +
|
composeState.poll.options['3'] +
|
||||||
postState.poll.multiple +
|
composeState.poll.multiple +
|
||||||
postState.poll.expire +
|
composeState.poll.expire +
|
||||||
postState.attachments.sensitive +
|
composeState.attachments.sensitive +
|
||||||
postState.attachments.uploads.map(upload => upload.id) +
|
composeState.attachments.uploads.map(upload => upload.id) +
|
||||||
postState.visibility
|
composeState.visibility
|
||||||
).toString()
|
).toString()
|
||||||
},
|
},
|
||||||
body: formData
|
body: formData
|
||||||
@ -305,8 +427,8 @@ const Compose: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalTextCount =
|
const totalTextCount =
|
||||||
(postState.spoiler.active ? postState.spoiler.count : 0) +
|
(composeState.spoiler.active ? composeState.spoiler.count : 0) +
|
||||||
postState.text.count
|
composeState.text.count
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
||||||
@ -354,14 +476,17 @@ const Compose: React.FC = () => {
|
|||||||
onPress={async () => tootPost()}
|
onPress={async () => tootPost()}
|
||||||
text='发嘟嘟'
|
text='发嘟嘟'
|
||||||
disabled={
|
disabled={
|
||||||
postState.text.raw.length < 1 || totalTextCount > 500
|
composeState.text.raw.length < 1 || totalTextCount > 500
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{() => (
|
{() => (
|
||||||
<ComposeRoot postState={postState} postDispatch={postDispatch} />
|
<ComposeRoot
|
||||||
|
composeState={composeState}
|
||||||
|
composeDispatch={composeDispatch}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
@ -377,4 +502,4 @@ const styles = StyleSheet.create({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Compose
|
export default React.memo(Compose, () => true)
|
||||||
|
@ -10,24 +10,24 @@ import {
|
|||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
import addAttachments from './addAttachments'
|
import addAttachments from './addAttachments'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
textInputRef: React.RefObject<TextInput>
|
textInputRef: React.RefObject<TextInput>
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComposeActions: React.FC<Props> = ({
|
const ComposeActions: React.FC<Props> = ({
|
||||||
textInputRef,
|
textInputRef,
|
||||||
postState,
|
composeState,
|
||||||
postDispatch
|
composeDispatch
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const getVisibilityIcon = () => {
|
const getVisibilityIcon = () => {
|
||||||
switch (postState.visibility) {
|
switch (composeState.visibility) {
|
||||||
case 'public':
|
case 'public':
|
||||||
return 'globe'
|
return 'globe'
|
||||||
case 'unlisted':
|
case 'unlisted':
|
||||||
@ -40,88 +40,88 @@ const ComposeActions: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const attachmentColor = useMemo(() => {
|
const attachmentColor = useMemo(() => {
|
||||||
if (postState.poll.active) return theme.disabled
|
if (composeState.poll.active) return theme.disabled
|
||||||
if (postState.attachmentUploadProgress) return theme.primary
|
if (composeState.attachmentUploadProgress) return theme.primary
|
||||||
|
|
||||||
if (postState.attachments.uploads.length) {
|
if (composeState.attachments.uploads.length) {
|
||||||
return theme.primary
|
return theme.primary
|
||||||
} else {
|
} else {
|
||||||
return theme.secondary
|
return theme.secondary
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
postState.poll.active,
|
composeState.poll.active,
|
||||||
postState.attachments.uploads,
|
composeState.attachments.uploads,
|
||||||
postState.attachmentUploadProgress
|
composeState.attachmentUploadProgress
|
||||||
])
|
])
|
||||||
const attachmentOnPress = useCallback(async () => {
|
const attachmentOnPress = useCallback(async () => {
|
||||||
if (postState.poll.active) return
|
if (composeState.poll.active) return
|
||||||
if (postState.attachmentUploadProgress) return
|
if (composeState.attachmentUploadProgress) return
|
||||||
|
|
||||||
if (!postState.attachments.uploads.length) {
|
if (!composeState.attachments.uploads.length) {
|
||||||
return await addAttachments({ postState, postDispatch })
|
return await addAttachments({ composeState, composeDispatch })
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
postState.poll.active,
|
composeState.poll.active,
|
||||||
postState.attachments.uploads,
|
composeState.attachments.uploads,
|
||||||
postState.attachmentUploadProgress
|
composeState.attachmentUploadProgress
|
||||||
])
|
])
|
||||||
|
|
||||||
const pollColor = useMemo(() => {
|
const pollColor = useMemo(() => {
|
||||||
if (postState.attachments.uploads.length) return theme.disabled
|
if (composeState.attachments.uploads.length) return theme.disabled
|
||||||
if (postState.attachmentUploadProgress) return theme.disabled
|
if (composeState.attachmentUploadProgress) return theme.disabled
|
||||||
|
|
||||||
if (postState.poll.active) {
|
if (composeState.poll.active) {
|
||||||
return theme.primary
|
return theme.primary
|
||||||
} else {
|
} else {
|
||||||
return theme.secondary
|
return theme.secondary
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
postState.poll.active,
|
composeState.poll.active,
|
||||||
postState.attachments.uploads,
|
composeState.attachments.uploads,
|
||||||
postState.attachmentUploadProgress
|
composeState.attachmentUploadProgress
|
||||||
])
|
])
|
||||||
const pollOnPress = useCallback(() => {
|
const pollOnPress = useCallback(() => {
|
||||||
if (
|
if (
|
||||||
!postState.attachments.uploads.length &&
|
!composeState.attachments.uploads.length &&
|
||||||
!postState.attachmentUploadProgress
|
!composeState.attachmentUploadProgress
|
||||||
) {
|
) {
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
payload: { ...postState.poll, active: !postState.poll.active }
|
payload: { ...composeState.poll, active: !composeState.poll.active }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (postState.poll.active) {
|
if (composeState.poll.active) {
|
||||||
textInputRef.current?.focus()
|
textInputRef.current?.focus()
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
postState.poll.active,
|
composeState.poll.active,
|
||||||
postState.attachments.uploads,
|
composeState.attachments.uploads,
|
||||||
postState.attachmentUploadProgress
|
composeState.attachmentUploadProgress
|
||||||
])
|
])
|
||||||
|
|
||||||
const emojiColor = useMemo(() => {
|
const emojiColor = useMemo(() => {
|
||||||
if (!postState.emoji.emojis) return theme.disabled
|
if (!composeState.emoji.emojis) return theme.disabled
|
||||||
if (postState.emoji.active) {
|
if (composeState.emoji.active) {
|
||||||
return theme.primary
|
return theme.primary
|
||||||
} else {
|
} else {
|
||||||
return theme.secondary
|
return theme.secondary
|
||||||
}
|
}
|
||||||
}, [postState.emoji.active, postState.emoji.emojis])
|
}, [composeState.emoji.active, composeState.emoji.emojis])
|
||||||
const emojiOnPress = useCallback(() => {
|
const emojiOnPress = useCallback(() => {
|
||||||
if (postState.emoji.emojis) {
|
if (composeState.emoji.emojis) {
|
||||||
if (postState.emoji.active) {
|
if (composeState.emoji.active) {
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'emoji',
|
type: 'emoji',
|
||||||
payload: { ...postState.emoji, active: false }
|
payload: { ...composeState.emoji, active: false }
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'emoji',
|
type: 'emoji',
|
||||||
payload: { ...postState.emoji, active: true }
|
payload: { ...composeState.emoji, active: true }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [postState.emoji.active, postState.emoji.emojis])
|
}, [composeState.emoji.active, composeState.emoji.emojis])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
@ -156,16 +156,16 @@ const ComposeActions: React.FC<Props> = ({
|
|||||||
buttonIndex => {
|
buttonIndex => {
|
||||||
switch (buttonIndex) {
|
switch (buttonIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
postDispatch({ type: 'visibility', payload: 'public' })
|
composeDispatch({ type: 'visibility', payload: 'public' })
|
||||||
break
|
break
|
||||||
case 1:
|
case 1:
|
||||||
postDispatch({ type: 'visibility', payload: 'unlisted' })
|
composeDispatch({ type: 'visibility', payload: 'unlisted' })
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
postDispatch({ type: 'visibility', payload: 'private' })
|
composeDispatch({ type: 'visibility', payload: 'private' })
|
||||||
break
|
break
|
||||||
case 3:
|
case 3:
|
||||||
postDispatch({ type: 'visibility', payload: 'direct' })
|
composeDispatch({ type: 'visibility', payload: 'direct' })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,11 +175,11 @@ const ComposeActions: React.FC<Props> = ({
|
|||||||
<Feather
|
<Feather
|
||||||
name='alert-triangle'
|
name='alert-triangle'
|
||||||
size={24}
|
size={24}
|
||||||
color={postState.spoiler.active ? theme.primary : theme.secondary}
|
color={composeState.spoiler.active ? theme.primary : theme.secondary}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'spoiler',
|
type: 'spoiler',
|
||||||
payload: { active: !postState.spoiler.active }
|
payload: { active: !composeState.spoiler.active }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
View
|
View
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
@ -20,11 +20,11 @@ import { Feather } from '@expo/vector-icons'
|
|||||||
const DEFAULT_HEIGHT = 200
|
const DEFAULT_HEIGHT = 200
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComposeAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
const ComposeAttachments: React.FC<Props> = ({ composeState, composeDispatch }) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
|
||||||
@ -73,10 +73,10 @@ const ComposeAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
<ButtonRound
|
<ButtonRound
|
||||||
icon='x'
|
icon='x'
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'attachments',
|
type: 'attachments',
|
||||||
payload: {
|
payload: {
|
||||||
uploads: postState.attachments.uploads.filter(
|
uploads: composeState.attachments.uploads.filter(
|
||||||
e => e.id !== item.id
|
e => e.id !== item.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -89,7 +89,7 @@ const ComposeAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
onPress={() =>
|
onPress={() =>
|
||||||
navigation.navigate('Screen-Shared-Compose-EditAttachment', {
|
navigation.navigate('Screen-Shared-Compose-EditAttachment', {
|
||||||
attachment: item,
|
attachment: item,
|
||||||
postDispatch
|
composeDispatch
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
styles={styles.edit}
|
styles={styles.edit}
|
||||||
@ -104,15 +104,15 @@ const ComposeAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
return (
|
return (
|
||||||
<ShimmerPlaceholder
|
<ShimmerPlaceholder
|
||||||
style={styles.progressContainer}
|
style={styles.progressContainer}
|
||||||
visible={postState.attachmentUploadProgress === undefined}
|
visible={composeState.attachmentUploadProgress === undefined}
|
||||||
width={
|
width={
|
||||||
(postState.attachmentUploadProgress?.aspect || 3 / 2) * DEFAULT_HEIGHT
|
(composeState.attachmentUploadProgress?.aspect || 3 / 2) * DEFAULT_HEIGHT
|
||||||
}
|
}
|
||||||
height={200}
|
height={200}
|
||||||
>
|
>
|
||||||
{postState.attachments.uploads.length > 0 &&
|
{composeState.attachments.uploads.length > 0 &&
|
||||||
postState.attachments.uploads[0].type === 'image' &&
|
composeState.attachments.uploads[0].type === 'image' &&
|
||||||
postState.attachments.uploads.length < 4 && (
|
composeState.attachments.uploads.length < 4 && (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{
|
style={{
|
||||||
width: DEFAULT_HEIGHT,
|
width: DEFAULT_HEIGHT,
|
||||||
@ -120,13 +120,13 @@ const ComposeAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
backgroundColor: theme.border
|
backgroundColor: theme.border
|
||||||
}}
|
}}
|
||||||
onPress={async () =>
|
onPress={async () =>
|
||||||
await addAttachments({ postState, postDispatch })
|
await addAttachments({ composeState, composeDispatch })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ButtonRound
|
<ButtonRound
|
||||||
icon='upload-cloud'
|
icon='upload-cloud'
|
||||||
onPress={async () =>
|
onPress={async () =>
|
||||||
await addAttachments({ postState, postDispatch })
|
await addAttachments({ composeState, composeDispatch })
|
||||||
}
|
}
|
||||||
styles={{
|
styles={{
|
||||||
top:
|
top:
|
||||||
@ -144,21 +144,21 @@ const ComposeAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
)}
|
)}
|
||||||
</ShimmerPlaceholder>
|
</ShimmerPlaceholder>
|
||||||
)
|
)
|
||||||
}, [postState.attachmentUploadProgress, postState.attachments.uploads])
|
}, [composeState.attachmentUploadProgress, composeState.attachments.uploads])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<Pressable
|
<Pressable
|
||||||
style={styles.sensitive}
|
style={styles.sensitive}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'attachments',
|
type: 'attachments',
|
||||||
payload: { sensitive: !postState.attachments.sensitive }
|
payload: { sensitive: !composeState.attachments.sensitive }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Feather
|
<Feather
|
||||||
name={postState.attachments.sensitive ? 'check-circle' : 'circle'}
|
name={composeState.attachments.sensitive ? 'check-circle' : 'circle'}
|
||||||
size={StyleConstants.Font.Size.L}
|
size={StyleConstants.Font.Size.L}
|
||||||
color={theme.primary}
|
color={theme.primary}
|
||||||
/>
|
/>
|
||||||
@ -169,8 +169,8 @@ const ComposeAttachments: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
<View style={styles.imageContainer}>
|
<View style={styles.imageContainer}>
|
||||||
<FlatList
|
<FlatList
|
||||||
horizontal
|
horizontal
|
||||||
extraData={postState.attachments.uploads.length}
|
extraData={composeState.attachments.uploads.length}
|
||||||
data={postState.attachments.uploads}
|
data={composeState.attachments.uploads}
|
||||||
renderItem={renderAttachment}
|
renderItem={renderAttachment}
|
||||||
ListFooterComponent={listFooter}
|
ListFooterComponent={listFooter}
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
|
@ -36,14 +36,14 @@ export interface Props {
|
|||||||
route: {
|
route: {
|
||||||
params: {
|
params: {
|
||||||
attachment: Mastodon.Attachment & { local_url: string }
|
attachment: Mastodon.Attachment & { local_url: string }
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComposeEditAttachment: React.FC<Props> = ({
|
const ComposeEditAttachment: React.FC<Props> = ({
|
||||||
route: {
|
route: {
|
||||||
params: { attachment, postDispatch }
|
params: { attachment, composeDispatch }
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
@ -72,7 +72,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (needUpdate) {
|
if (needUpdate) {
|
||||||
postDispatch({ type: 'attachmentEdit', payload: attachment })
|
composeDispatch({ type: 'attachmentEdit', payload: attachment })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -11,19 +11,19 @@ import {
|
|||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
|
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
import updateText from './updateText'
|
import updateText from './updateText'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
textInputRef: React.RefObject<TextInput>
|
textInputRef: React.RefObject<TextInput>
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComposeEmojis: React.FC<Props> = ({
|
const ComposeEmojis: React.FC<Props> = ({
|
||||||
textInputRef,
|
textInputRef,
|
||||||
postState,
|
composeState,
|
||||||
postDispatch
|
composeDispatch
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ const ComposeEmojis: React.FC<Props> = ({
|
|||||||
<SectionList
|
<SectionList
|
||||||
horizontal
|
horizontal
|
||||||
keyboardShouldPersistTaps='handled'
|
keyboardShouldPersistTaps='handled'
|
||||||
sections={postState.emoji.emojis!}
|
sections={composeState.emoji.emojis!}
|
||||||
keyExtractor={item => item.shortcode}
|
keyExtractor={item => item.shortcode}
|
||||||
renderSectionHeader={({ section: { title } }) => (
|
renderSectionHeader={({ section: { title } }) => (
|
||||||
<Text style={[styles.group, { color: theme.secondary }]}>
|
<Text style={[styles.group, { color: theme.secondary }]}>
|
||||||
@ -51,14 +51,14 @@ const ComposeEmojis: React.FC<Props> = ({
|
|||||||
origin: textInputRef.current?.isFocused()
|
origin: textInputRef.current?.isFocused()
|
||||||
? 'text'
|
? 'text'
|
||||||
: 'spoiler',
|
: 'spoiler',
|
||||||
postState,
|
composeState,
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
newText: `:${emoji.shortcode}:`,
|
newText: `:${emoji.shortcode}:`,
|
||||||
type: 'emoji'
|
type: 'emoji'
|
||||||
})
|
})
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'emoji',
|
type: 'emoji',
|
||||||
payload: { ...postState.emoji, active: false }
|
payload: { ...composeState.emoji, active: false }
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -2,18 +2,18 @@ import React, { Dispatch, useEffect, useState } from 'react'
|
|||||||
import { ActionSheetIOS, StyleSheet, TextInput, View } from 'react-native'
|
import { ActionSheetIOS, StyleSheet, TextInput, View } from 'react-native'
|
||||||
import { Feather } from '@expo/vector-icons'
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
import { ButtonRow } from 'src/components/Button'
|
import { ButtonRow } from 'src/components/Button'
|
||||||
import { MenuContainer, MenuRow } from 'src/components/Menu'
|
import { MenuContainer, MenuRow } from 'src/components/Menu'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComposePoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
const ComposePoll: React.FC<Props> = ({ composeState, composeDispatch }) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const expireMapping: { [key: string]: string } = {
|
const expireMapping: { [key: string]: string } = {
|
||||||
@ -34,21 +34,21 @@ const ComposePoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
return (
|
return (
|
||||||
<View style={[styles.base, { borderColor: theme.border }]}>
|
<View style={[styles.base, { borderColor: theme.border }]}>
|
||||||
<View style={styles.options}>
|
<View style={styles.options}>
|
||||||
{[...Array(postState.poll.total)].map((e, i) => {
|
{[...Array(composeState.poll.total)].map((e, i) => {
|
||||||
const restOptions = Object.keys(postState.poll.options).filter(
|
const restOptions = Object.keys(composeState.poll.options).filter(
|
||||||
o => parseInt(o) !== i && parseInt(o) < postState.poll.total
|
o => parseInt(o) !== i && parseInt(o) < composeState.poll.total
|
||||||
)
|
)
|
||||||
let hasConflict = false
|
let hasConflict = false
|
||||||
restOptions.forEach(o => {
|
restOptions.forEach(o => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (postState.poll.options[o] === postState.poll.options[i]) {
|
if (composeState.poll.options[o] === composeState.poll.options[i]) {
|
||||||
hasConflict = true
|
hasConflict = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
<View key={i} style={styles.option}>
|
<View key={i} style={styles.option}>
|
||||||
<Feather
|
<Feather
|
||||||
name={postState.poll.multiple ? 'square' : 'circle'}
|
name={composeState.poll.multiple ? 'square' : 'circle'}
|
||||||
size={StyleConstants.Font.Size.L}
|
size={StyleConstants.Font.Size.L}
|
||||||
color={theme.secondary}
|
color={theme.secondary}
|
||||||
/>
|
/>
|
||||||
@ -65,13 +65,13 @@ const ComposePoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
placeholderTextColor={theme.secondary}
|
placeholderTextColor={theme.secondary}
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
value={postState.poll.options[i]}
|
value={composeState.poll.options[i]}
|
||||||
onChangeText={e =>
|
onChangeText={e =>
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
payload: {
|
payload: {
|
||||||
...postState.poll,
|
...composeState.poll,
|
||||||
options: { ...postState.poll.options, [i]: e }
|
options: { ...composeState.poll.options, [i]: e }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -84,34 +84,34 @@ const ComposePoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
<View style={styles.firstButton}>
|
<View style={styles.firstButton}>
|
||||||
<ButtonRow
|
<ButtonRow
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
postState.poll.total > 2 &&
|
composeState.poll.total > 2 &&
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
payload: { ...postState.poll, total: postState.poll.total - 1 }
|
payload: { ...composeState.poll, total: composeState.poll.total - 1 }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
icon='minus'
|
icon='minus'
|
||||||
disabled={!(postState.poll.total > 2)}
|
disabled={!(composeState.poll.total > 2)}
|
||||||
buttonSize='S'
|
buttonSize='S'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<ButtonRow
|
<ButtonRow
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
postState.poll.total < 4 &&
|
composeState.poll.total < 4 &&
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
payload: { ...postState.poll, total: postState.poll.total + 1 }
|
payload: { ...composeState.poll, total: composeState.poll.total + 1 }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
icon='plus'
|
icon='plus'
|
||||||
disabled={!(postState.poll.total < 4)}
|
disabled={!(composeState.poll.total < 4)}
|
||||||
buttonSize='S'
|
buttonSize='S'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<MenuContainer>
|
<MenuContainer>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title='可选项'
|
title='可选项'
|
||||||
content={postState.poll.multiple ? '多选' : '单选'}
|
content={composeState.poll.multiple ? '多选' : '单选'}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
ActionSheetIOS.showActionSheetWithOptions(
|
ActionSheetIOS.showActionSheetWithOptions(
|
||||||
{
|
{
|
||||||
@ -120,9 +120,9 @@ const ComposePoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
},
|
},
|
||||||
index =>
|
index =>
|
||||||
index < 2 &&
|
index < 2 &&
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
payload: { ...postState.poll, multiple: index === 1 }
|
payload: { ...composeState.poll, multiple: index === 1 }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ const ComposePoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title='有效期'
|
title='有效期'
|
||||||
content={expireMapping[postState.poll.expire]}
|
content={expireMapping[composeState.poll.expire]}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
ActionSheetIOS.showActionSheetWithOptions(
|
ActionSheetIOS.showActionSheetWithOptions(
|
||||||
{
|
{
|
||||||
@ -139,10 +139,10 @@ const ComposePoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
},
|
},
|
||||||
index =>
|
index =>
|
||||||
index < 7 &&
|
index < 7 &&
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
payload: {
|
payload: {
|
||||||
...postState.poll,
|
...composeState.poll,
|
||||||
expire: Object.keys(expireMapping)[index]
|
expire: Object.keys(expireMapping)[index]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -17,7 +17,7 @@ import { emojisFetch } from 'src/utils/fetches/emojisFetch'
|
|||||||
import { searchFetch } from 'src/utils/fetches/searchFetch'
|
import { searchFetch } from 'src/utils/fetches/searchFetch'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
import ComposeActions from './Actions'
|
import ComposeActions from './Actions'
|
||||||
import ComposeAttachments from './Attachments'
|
import ComposeAttachments from './Attachments'
|
||||||
import ComposeEmojis from './Emojis'
|
import ComposeEmojis from './Emojis'
|
||||||
@ -28,26 +28,26 @@ import updateText from './updateText'
|
|||||||
import * as Permissions from 'expo-permissions'
|
import * as Permissions from 'expo-permissions'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => {
|
const ComposeRoot: React.FC<Props> = ({ composeState, composeDispatch }) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const { isFetching, isSuccess, data, refetch } = useQuery(
|
const { isFetching, isSuccess, data, refetch } = useQuery(
|
||||||
[
|
[
|
||||||
'Search',
|
'Search',
|
||||||
{ type: postState.tag?.type, term: postState.tag?.text.substring(1) }
|
{ type: composeState.tag?.type, term: composeState.tag?.text.substring(1) }
|
||||||
],
|
],
|
||||||
searchFetch,
|
searchFetch,
|
||||||
{ enabled: false }
|
{ enabled: false }
|
||||||
)
|
)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (postState.tag?.text) {
|
if (composeState.tag?.text) {
|
||||||
refetch()
|
refetch()
|
||||||
}
|
}
|
||||||
}, [postState.tag?.text])
|
}, [composeState.tag?.text])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
@ -71,9 +71,9 @@ const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'),
|
groupBy(sortBy(emojisData, ['category', 'shortcode']), 'category'),
|
||||||
(value, key) => sortedEmojis.push({ title: key, data: value })
|
(value, key) => sortedEmojis.push({ title: key, data: value })
|
||||||
)
|
)
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'emoji',
|
type: 'emoji',
|
||||||
payload: { ...postState.emoji, emojis: sortedEmojis }
|
payload: { ...composeState.emoji, emojis: sortedEmojis }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [emojisData])
|
}, [emojisData])
|
||||||
@ -89,60 +89,60 @@ const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<ProgressViewIOS
|
<ProgressViewIOS
|
||||||
progress={postState.attachmentUploadProgress?.progress || 0}
|
progress={composeState.attachmentUploadProgress?.progress || 0}
|
||||||
progressViewStyle='bar'
|
progressViewStyle='bar'
|
||||||
/>
|
/>
|
||||||
<FlatList
|
<FlatList
|
||||||
keyboardShouldPersistTaps='handled'
|
keyboardShouldPersistTaps='handled'
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<>
|
<>
|
||||||
{postState.spoiler.active ? (
|
{composeState.spoiler.active ? (
|
||||||
<ComposeSpoilerInput
|
<ComposeSpoilerInput
|
||||||
postState={postState}
|
composeState={composeState}
|
||||||
postDispatch={postDispatch}
|
composeDispatch={composeDispatch}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<ComposeTextInput
|
<ComposeTextInput
|
||||||
postState={postState}
|
composeState={composeState}
|
||||||
postDispatch={postDispatch}
|
composeDispatch={composeDispatch}
|
||||||
textInputRef={textInputRef}
|
textInputRef={textInputRef}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
<>
|
<>
|
||||||
{postState.emoji.active && (
|
{composeState.emoji.active && (
|
||||||
<View style={styles.emojis}>
|
<View style={styles.emojis}>
|
||||||
<ComposeEmojis
|
<ComposeEmojis
|
||||||
textInputRef={textInputRef}
|
textInputRef={textInputRef}
|
||||||
postState={postState}
|
composeState={composeState}
|
||||||
postDispatch={postDispatch}
|
composeDispatch={composeDispatch}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(postState.attachments.uploads.length > 0 ||
|
{(composeState.attachments.uploads.length > 0 ||
|
||||||
postState.attachmentUploadProgress) && (
|
composeState.attachmentUploadProgress) && (
|
||||||
<View style={styles.attachments}>
|
<View style={styles.attachments}>
|
||||||
<ComposeAttachments
|
<ComposeAttachments
|
||||||
postState={postState}
|
composeState={composeState}
|
||||||
postDispatch={postDispatch}
|
composeDispatch={composeDispatch}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{postState.poll.active && (
|
{composeState.poll.active && (
|
||||||
<View style={styles.poll}>
|
<View style={styles.poll}>
|
||||||
<ComposePoll
|
<ComposePoll
|
||||||
postState={postState}
|
composeState={composeState}
|
||||||
postDispatch={postDispatch}
|
composeDispatch={composeDispatch}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
ListEmptyComponent={listEmpty}
|
ListEmptyComponent={listEmpty}
|
||||||
data={postState.tag && isSuccess ? data[postState.tag.type] : []}
|
data={composeState.tag && isSuccess ? data[composeState.tag.type] : []}
|
||||||
renderItem={({ item, index }) => (
|
renderItem={({ item, index }) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
key={index}
|
key={index}
|
||||||
@ -152,18 +152,18 @@ const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
: 'spoiler'
|
: 'spoiler'
|
||||||
updateText({
|
updateText({
|
||||||
origin: focusedInput,
|
origin: focusedInput,
|
||||||
postState: {
|
composeState: {
|
||||||
...postState,
|
...composeState,
|
||||||
[focusedInput]: {
|
[focusedInput]: {
|
||||||
...postState[focusedInput],
|
...composeState[focusedInput],
|
||||||
selection: {
|
selection: {
|
||||||
start: postState.tag!.offset,
|
start: composeState.tag!.offset,
|
||||||
end:
|
end:
|
||||||
postState.tag!.offset + postState.tag!.text.length + 1
|
composeState.tag!.offset + composeState.tag!.text.length + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
newText: item.acct ? `@${item.acct}` : `#${item.name}`,
|
newText: item.acct ? `@${item.acct}` : `#${item.name}`,
|
||||||
type: 'suggestion'
|
type: 'suggestion'
|
||||||
})
|
})
|
||||||
@ -225,8 +225,8 @@ const ComposeRoot: React.FC<Props> = ({ postState, postDispatch }) => {
|
|||||||
/>
|
/>
|
||||||
<ComposeActions
|
<ComposeActions
|
||||||
textInputRef={textInputRef}
|
textInputRef={textInputRef}
|
||||||
postState={postState}
|
composeState={composeState}
|
||||||
postDispatch={postDispatch}
|
composeDispatch={composeDispatch}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -2,18 +2,18 @@ import React, { Dispatch, RefObject } from 'react'
|
|||||||
import { StyleSheet, Text, TextInput } from 'react-native'
|
import { StyleSheet, Text, TextInput } from 'react-native'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
import formatText from './formatText'
|
import formatText from './formatText'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
// textInputRef: RefObject<TextInput>
|
// textInputRef: RefObject<TextInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComposeSpoilerInput: React.FC<Props> = ({
|
const ComposeSpoilerInput: React.FC<Props> = ({
|
||||||
postState,
|
composeState,
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
// textInputRef
|
// textInputRef
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@ -37,7 +37,7 @@ const ComposeSpoilerInput: React.FC<Props> = ({
|
|||||||
onChangeText={content =>
|
onChangeText={content =>
|
||||||
formatText({
|
formatText({
|
||||||
origin: 'spoiler',
|
origin: 'spoiler',
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
content
|
content
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ const ComposeSpoilerInput: React.FC<Props> = ({
|
|||||||
selection: { start, end }
|
selection: { start, end }
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'spoiler',
|
type: 'spoiler',
|
||||||
payload: { selection: { start, end } }
|
payload: { selection: { start, end } }
|
||||||
})
|
})
|
||||||
@ -54,7 +54,7 @@ const ComposeSpoilerInput: React.FC<Props> = ({
|
|||||||
// ref={textInputRef}
|
// ref={textInputRef}
|
||||||
scrollEnabled
|
scrollEnabled
|
||||||
>
|
>
|
||||||
<Text>{postState.spoiler.formatted}</Text>
|
<Text>{composeState.spoiler.formatted}</Text>
|
||||||
</TextInput>
|
</TextInput>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -73,5 +73,5 @@ const styles = StyleSheet.create({
|
|||||||
export default React.memo(
|
export default React.memo(
|
||||||
ComposeSpoilerInput,
|
ComposeSpoilerInput,
|
||||||
(prev, next) =>
|
(prev, next) =>
|
||||||
prev.postState.spoiler.formatted === next.postState.spoiler.formatted
|
prev.composeState.spoiler.formatted === next.composeState.spoiler.formatted
|
||||||
)
|
)
|
||||||
|
@ -2,18 +2,18 @@ import React, { Dispatch, RefObject } from 'react'
|
|||||||
import { StyleSheet, Text, TextInput } from 'react-native'
|
import { StyleSheet, Text, TextInput } from 'react-native'
|
||||||
import { StyleConstants } from 'src/utils/styles/constants'
|
import { StyleConstants } from 'src/utils/styles/constants'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
import formatText from './formatText'
|
import formatText from './formatText'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
textInputRef: RefObject<TextInput>
|
textInputRef: RefObject<TextInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComposeTextInput: React.FC<Props> = ({
|
const ComposeTextInput: React.FC<Props> = ({
|
||||||
postState,
|
composeState,
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
textInputRef
|
textInputRef
|
||||||
}) => {
|
}) => {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@ -37,7 +37,7 @@ const ComposeTextInput: React.FC<Props> = ({
|
|||||||
onChangeText={content =>
|
onChangeText={content =>
|
||||||
formatText({
|
formatText({
|
||||||
origin: 'text',
|
origin: 'text',
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
content
|
content
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -46,12 +46,12 @@ const ComposeTextInput: React.FC<Props> = ({
|
|||||||
selection: { start, end }
|
selection: { start, end }
|
||||||
}
|
}
|
||||||
}) => {
|
}) => {
|
||||||
postDispatch({ type: 'text', payload: { selection: { start, end } } })
|
composeDispatch({ type: 'text', payload: { selection: { start, end } } })
|
||||||
}}
|
}}
|
||||||
ref={textInputRef}
|
ref={textInputRef}
|
||||||
scrollEnabled
|
scrollEnabled
|
||||||
>
|
>
|
||||||
<Text>{postState.text.formatted}</Text>
|
<Text>{composeState.text.formatted}</Text>
|
||||||
</TextInput>
|
</TextInput>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -70,5 +70,5 @@ const styles = StyleSheet.create({
|
|||||||
export default React.memo(
|
export default React.memo(
|
||||||
ComposeTextInput,
|
ComposeTextInput,
|
||||||
(prev, next) =>
|
(prev, next) =>
|
||||||
prev.postState.text.formatted === next.postState.text.formatted
|
prev.composeState.text.formatted === next.composeState.text.formatted
|
||||||
)
|
)
|
||||||
|
@ -2,18 +2,18 @@ import { Dispatch } from 'react'
|
|||||||
import { ActionSheetIOS, Alert } from 'react-native'
|
import { ActionSheetIOS, Alert } from 'react-native'
|
||||||
import * as ImagePicker from 'expo-image-picker'
|
import * as ImagePicker from 'expo-image-picker'
|
||||||
|
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
import client from 'src/api/client'
|
import client from 'src/api/client'
|
||||||
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
|
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
|
||||||
|
|
||||||
const uploadAttachment = async ({
|
const uploadAttachment = async ({
|
||||||
result,
|
result,
|
||||||
postState,
|
composeState,
|
||||||
postDispatch
|
composeDispatch
|
||||||
}: {
|
}: {
|
||||||
result: NonNullable<ImageInfo>
|
result: NonNullable<ImageInfo>
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
}) => {
|
}) => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -30,7 +30,7 @@ const uploadAttachment = async ({
|
|||||||
url: 'media',
|
url: 'media',
|
||||||
body: formData,
|
body: formData,
|
||||||
onUploadProgress: p => {
|
onUploadProgress: p => {
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'attachmentUploadProgress',
|
type: 'attachmentUploadProgress',
|
||||||
payload: {
|
payload: {
|
||||||
progress: p.loaded / p.total,
|
progress: p.loaded / p.total,
|
||||||
@ -40,15 +40,15 @@ const uploadAttachment = async ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(({ body }: { body: Mastodon.Attachment & { local_url: string } }) => {
|
.then(({ body }: { body: Mastodon.Attachment & { local_url: string } }) => {
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'attachmentUploadProgress',
|
type: 'attachmentUploadProgress',
|
||||||
payload: undefined
|
payload: undefined
|
||||||
})
|
})
|
||||||
if (body.id) {
|
if (body.id) {
|
||||||
body.local_url = result.uri
|
body.local_url = result.uri
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'attachments',
|
type: 'attachments',
|
||||||
payload: { uploads: postState.attachments.uploads.concat([body]) }
|
payload: { uploads: composeState.attachments.uploads.concat([body]) }
|
||||||
})
|
})
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
} else {
|
} else {
|
||||||
@ -56,7 +56,7 @@ const uploadAttachment = async ({
|
|||||||
{
|
{
|
||||||
text: '返回重试',
|
text: '返回重试',
|
||||||
onPress: () =>
|
onPress: () =>
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'attachmentUploadProgress',
|
type: 'attachmentUploadProgress',
|
||||||
payload: undefined
|
payload: undefined
|
||||||
})
|
})
|
||||||
@ -70,7 +70,7 @@ const uploadAttachment = async ({
|
|||||||
{
|
{
|
||||||
text: '返回重试',
|
text: '返回重试',
|
||||||
onPress: () =>
|
onPress: () =>
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: 'attachmentUploadProgress',
|
type: 'attachmentUploadProgress',
|
||||||
payload: undefined
|
payload: undefined
|
||||||
})
|
})
|
||||||
@ -83,8 +83,8 @@ const uploadAttachment = async ({
|
|||||||
const addAttachments = async ({
|
const addAttachments = async ({
|
||||||
...params
|
...params
|
||||||
}: {
|
}: {
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
}): Promise<any> => {
|
}): Promise<any> => {
|
||||||
ActionSheetIOS.showActionSheetWithOptions(
|
ActionSheetIOS.showActionSheetWithOptions(
|
||||||
{
|
{
|
||||||
|
@ -4,11 +4,11 @@ import { Text } from 'react-native'
|
|||||||
import { RefetchOptions } from 'react-query/types/core/query'
|
import { RefetchOptions } from 'react-query/types/core/query'
|
||||||
import Autolinker from 'src/modules/autolinker'
|
import Autolinker from 'src/modules/autolinker'
|
||||||
import { useTheme } from 'src/utils/styles/ThemeManager'
|
import { useTheme } from 'src/utils/styles/ThemeManager'
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
|
|
||||||
export interface Params {
|
export interface Params {
|
||||||
origin: 'text' | 'spoiler'
|
origin: 'text' | 'spoiler'
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
content: string
|
content: string
|
||||||
refetch?: (options?: RefetchOptions | undefined) => Promise<any>
|
refetch?: (options?: RefetchOptions | undefined) => Promise<any>
|
||||||
disableDebounce?: boolean
|
disableDebounce?: boolean
|
||||||
@ -25,8 +25,8 @@ const TagText = ({ text }: { text: string }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const debouncedSuggestions = debounce(
|
const debouncedSuggestions = debounce(
|
||||||
(postDispatch, tag) => {
|
(composeDispatch, tag) => {
|
||||||
postDispatch({ type: 'tag', payload: tag })
|
composeDispatch({ type: 'tag', payload: tag })
|
||||||
},
|
},
|
||||||
500,
|
500,
|
||||||
{
|
{
|
||||||
@ -34,15 +34,15 @@ const debouncedSuggestions = debounce(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
let prevTags: PostState['tag'][] = []
|
let prevTags: ComposeState['tag'][] = []
|
||||||
|
|
||||||
const formatText = ({
|
const formatText = ({
|
||||||
origin,
|
origin,
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
content,
|
content,
|
||||||
disableDebounce = false
|
disableDebounce = false
|
||||||
}: Params) => {
|
}: Params) => {
|
||||||
const tags: PostState['tag'][] = []
|
const tags: ComposeState['tag'][] = []
|
||||||
Autolinker.link(content, {
|
Autolinker.link(content, {
|
||||||
email: false,
|
email: false,
|
||||||
phone: false,
|
phone: false,
|
||||||
@ -74,11 +74,11 @@ const formatText = ({
|
|||||||
const changedTag = differenceWith(tags, prevTags, isEqual)
|
const changedTag = differenceWith(tags, prevTags, isEqual)
|
||||||
if (changedTag.length && !disableDebounce) {
|
if (changedTag.length && !disableDebounce) {
|
||||||
if (changedTag[0]!.type !== 'url') {
|
if (changedTag[0]!.type !== 'url') {
|
||||||
debouncedSuggestions(postDispatch, changedTag[0])
|
debouncedSuggestions(composeDispatch, changedTag[0])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debouncedSuggestions.cancel()
|
debouncedSuggestions.cancel()
|
||||||
postDispatch({ type: 'tag', payload: undefined })
|
composeDispatch({ type: 'tag', payload: undefined })
|
||||||
}
|
}
|
||||||
prevTags = tags
|
prevTags = tags
|
||||||
let _content = content
|
let _content = content
|
||||||
@ -107,7 +107,7 @@ const formatText = ({
|
|||||||
children.push(_content)
|
children.push(_content)
|
||||||
contentLength = contentLength + _content.length
|
contentLength = contentLength + _content.length
|
||||||
|
|
||||||
postDispatch({
|
composeDispatch({
|
||||||
type: origin,
|
type: origin,
|
||||||
payload: {
|
payload: {
|
||||||
count: contentLength,
|
count: contentLength,
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
import { Dispatch } from 'react'
|
import { Dispatch } from 'react'
|
||||||
import { PostAction, PostState } from '../Compose'
|
import { PostAction, ComposeState } from '../Compose'
|
||||||
import formatText from './formatText'
|
import formatText from './formatText'
|
||||||
|
|
||||||
const updateText = ({
|
const updateText = ({
|
||||||
origin,
|
origin,
|
||||||
postState,
|
composeState,
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
newText,
|
newText,
|
||||||
type
|
type
|
||||||
}: {
|
}: {
|
||||||
origin: 'text' | 'spoiler'
|
origin: 'text' | 'spoiler'
|
||||||
postState: PostState
|
composeState: ComposeState
|
||||||
postDispatch: Dispatch<PostAction>
|
composeDispatch: Dispatch<PostAction>
|
||||||
newText: string
|
newText: string
|
||||||
type: 'emoji' | 'suggestion'
|
type: 'emoji' | 'suggestion'
|
||||||
}) => {
|
}) => {
|
||||||
if (postState[origin].raw.length) {
|
if (composeState[origin].raw.length) {
|
||||||
const contentFront = postState[origin].raw.slice(
|
const contentFront = composeState[origin].raw.slice(
|
||||||
0,
|
0,
|
||||||
postState[origin].selection.start
|
composeState[origin].selection.start
|
||||||
)
|
)
|
||||||
const contentRear = postState[origin].raw.slice(
|
const contentRear = composeState[origin].raw.slice(
|
||||||
postState[origin].selection.end
|
composeState[origin].selection.end
|
||||||
)
|
)
|
||||||
|
|
||||||
const whiteSpaceFront = /\s/g.test(contentFront.slice(-1))
|
const whiteSpaceFront = /\s/g.test(contentFront.slice(-1))
|
||||||
@ -33,14 +33,14 @@ const updateText = ({
|
|||||||
|
|
||||||
formatText({
|
formatText({
|
||||||
origin,
|
origin,
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
content: [contentFront, newTextWithSpace, contentRear].join(''),
|
content: [contentFront, newTextWithSpace, contentRear].join(''),
|
||||||
disableDebounce: true
|
disableDebounce: true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
formatText({
|
formatText({
|
||||||
origin,
|
origin,
|
||||||
postDispatch,
|
composeDispatch,
|
||||||
content: `${newText} `,
|
content: `${newText} `,
|
||||||
disableDebounce: true
|
disableDebounce: true
|
||||||
})
|
})
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
const getCurrentTab = (navigation: any) => {
|
const getCurrentTab = (navigation: any) => {
|
||||||
const {
|
const { length, [length - 1]: last } =
|
||||||
length,
|
navigation.dangerouslyGetState().history ||
|
||||||
[length - 1]: last
|
navigation.dangerouslyGetParent()?.dangerouslyGetState().history
|
||||||
} = navigation.dangerouslyGetState().history
|
|
||||||
return `Screen-${last.key.split(new RegExp(/Screen-(.*?)-/))[1]}`
|
return `Screen-${last.key.split(new RegExp(/Screen-(.*?)-/))[1]}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user