mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Posting using formData
This commit is contained in:
@@ -18,28 +18,29 @@ const client = async ({
|
||||
query?: {
|
||||
[key: string]: string | number | boolean
|
||||
}
|
||||
body?: object
|
||||
body?: FormData
|
||||
}): Promise<any> => {
|
||||
const state: RootState['instanceInfo'] = store.getState().instanceInfo
|
||||
|
||||
let response
|
||||
try {
|
||||
response = await ky(endpoint, {
|
||||
method: method,
|
||||
prefixUrl: `https://${state[instance]}/api/${version}`,
|
||||
searchParams: query,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers,
|
||||
...(instance === 'local' && {
|
||||
Authorization: `Bearer ${state.localToken}`
|
||||
})
|
||||
},
|
||||
...(body && { json: body })
|
||||
})
|
||||
} catch (error) {
|
||||
return Promise.reject('ky error: ' + error)
|
||||
}
|
||||
// try {
|
||||
response = await ky(endpoint, {
|
||||
method: method,
|
||||
prefixUrl: `https://${state[instance]}/api/${version}`,
|
||||
searchParams: query,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers,
|
||||
...(instance === 'local' && {
|
||||
Authorization: `Bearer ${state.localToken}`
|
||||
})
|
||||
},
|
||||
...(body && { body: body }),
|
||||
throwHttpErrors: false
|
||||
})
|
||||
// } catch (error) {
|
||||
// return Promise.reject('ky error: ' + error.json())
|
||||
// }
|
||||
|
||||
if (response.ok) {
|
||||
return Promise.resolve({
|
||||
@@ -47,8 +48,9 @@ const client = async ({
|
||||
body: await response.json()
|
||||
})
|
||||
} else {
|
||||
console.error(response.status + ': ' + response.statusText)
|
||||
return Promise.reject({ body: response.statusText })
|
||||
const errorResponse = await response.json()
|
||||
console.error(response.status + ': ' + errorResponse.error)
|
||||
return Promise.reject({ body: errorResponse.error })
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -31,6 +31,27 @@ export type PostState = {
|
||||
}
|
||||
| undefined
|
||||
emojis: mastodon.Emoji[] | undefined
|
||||
poll: {
|
||||
active: boolean
|
||||
total: number
|
||||
options: {
|
||||
'1': string
|
||||
'2': string
|
||||
'3': string
|
||||
'4': string
|
||||
[key: string]: string
|
||||
}
|
||||
multiple: boolean
|
||||
expire:
|
||||
| '300'
|
||||
| '1800'
|
||||
| '3600'
|
||||
| '21600'
|
||||
| '86400'
|
||||
| '259200'
|
||||
| '604800'
|
||||
| string
|
||||
}
|
||||
}
|
||||
|
||||
export type PostAction =
|
||||
@@ -54,6 +75,10 @@ export type PostAction =
|
||||
type: 'emojis'
|
||||
payload: PostState['emojis']
|
||||
}
|
||||
| {
|
||||
type: 'poll'
|
||||
payload: PostState['poll']
|
||||
}
|
||||
|
||||
const postInitialState: PostState = {
|
||||
text: {
|
||||
@@ -64,7 +89,19 @@ const postInitialState: PostState = {
|
||||
selection: { start: 0, end: 0 },
|
||||
overlay: null,
|
||||
tag: undefined,
|
||||
emojis: undefined
|
||||
emojis: undefined,
|
||||
poll: {
|
||||
active: false,
|
||||
total: 2,
|
||||
options: {
|
||||
'1': '',
|
||||
'2': '',
|
||||
'3': '',
|
||||
'4': ''
|
||||
},
|
||||
multiple: false,
|
||||
expire: '86400'
|
||||
}
|
||||
}
|
||||
const postReducer = (state: PostState, action: PostAction): PostState => {
|
||||
switch (action.type) {
|
||||
@@ -78,6 +115,8 @@ const postReducer = (state: PostState, action: PostAction): PostState => {
|
||||
return { ...state, tag: action.payload }
|
||||
case 'emojis':
|
||||
return { ...state, emojis: action.payload }
|
||||
case 'poll':
|
||||
return { ...state, poll: action.payload }
|
||||
default:
|
||||
throw new Error('Unexpected action')
|
||||
}
|
||||
@@ -106,6 +145,72 @@ const PostToot: React.FC = () => {
|
||||
|
||||
const [postState, postDispatch] = useReducer(postReducer, postInitialState)
|
||||
|
||||
const tootPost = async () => {
|
||||
if (postState.text.count < 0) {
|
||||
Alert.alert('字数超限', '', [
|
||||
{
|
||||
text: '返回继续编辑'
|
||||
}
|
||||
])
|
||||
} else {
|
||||
const formData = new FormData()
|
||||
formData.append('status', postState.text.raw)
|
||||
if (postState.poll.active) {
|
||||
Object.values(postState.poll.options)
|
||||
.filter(e => e.length)
|
||||
.forEach(e => {
|
||||
console.log(e)
|
||||
formData.append('poll[options][]', e)
|
||||
})
|
||||
formData.append('poll[expires_in]', postState.poll.expire)
|
||||
formData.append('poll[multiple]', postState.poll.multiple.toString())
|
||||
}
|
||||
console.log(formData)
|
||||
|
||||
client({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
endpoint: 'statuses',
|
||||
headers: {
|
||||
'Idempotency-Key': Date.now().toString() + Math.random().toString()
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
.then(
|
||||
res => {
|
||||
if (res.body.id) {
|
||||
Alert.alert('发布成功', '', [
|
||||
{
|
||||
text: '好的',
|
||||
onPress: () => navigation.goBack()
|
||||
}
|
||||
])
|
||||
} else {
|
||||
Alert.alert('发布失败', '', [
|
||||
{
|
||||
text: '返回重试'
|
||||
}
|
||||
])
|
||||
}
|
||||
},
|
||||
error => {
|
||||
Alert.alert('发布失败', error.body, [
|
||||
{
|
||||
text: '返回重试'
|
||||
}
|
||||
])
|
||||
}
|
||||
)
|
||||
.catch(() => {
|
||||
Alert.alert('发布失败', '', [
|
||||
{
|
||||
text: '返回重试'
|
||||
}
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
|
||||
<SafeAreaView
|
||||
@@ -134,42 +239,7 @@ const PostToot: React.FC = () => {
|
||||
),
|
||||
headerCenter: () => <></>,
|
||||
headerRight: () => (
|
||||
<Pressable
|
||||
onPress={async () => {
|
||||
if (postState.text.count < 0) {
|
||||
Alert.alert('字数超限', '', [
|
||||
{
|
||||
text: '返回继续编辑'
|
||||
}
|
||||
])
|
||||
} else {
|
||||
const res = await client({
|
||||
method: 'post',
|
||||
instance: 'local',
|
||||
endpoint: 'statuses',
|
||||
headers: {
|
||||
'Idempotency-Key':
|
||||
Date.now().toString() + Math.random().toString()
|
||||
},
|
||||
query: { status: postState.text.raw }
|
||||
})
|
||||
if (res.body.id) {
|
||||
Alert.alert('发布成功', '', [
|
||||
{
|
||||
text: '好的',
|
||||
onPress: () => navigation.goBack()
|
||||
}
|
||||
])
|
||||
} else {
|
||||
Alert.alert('发布失败', '', [
|
||||
{
|
||||
text: '返回重试'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Pressable onPress={async () => tootPost()}>
|
||||
<Text>发嘟嘟</Text>
|
||||
</Pressable>
|
||||
)
|
||||
@@ -184,5 +254,6 @@ const PostToot: React.FC = () => {
|
||||
</KeyboardAvoidingView>
|
||||
)
|
||||
}
|
||||
;(PostMain as any).whyDidYouRender = true
|
||||
|
||||
export default PostToot
|
||||
|
@@ -13,15 +13,16 @@ import {
|
||||
TextInput,
|
||||
View
|
||||
} from 'react-native'
|
||||
import { useQuery } from 'react-query'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
import { debounce, differenceWith, isEqual } from 'lodash'
|
||||
|
||||
import Autolinker from 'src/modules/autolinker'
|
||||
import PostSuggestions from './PostSuggestions'
|
||||
import PostEmojis from './PostEmojis'
|
||||
import { useQuery } from 'react-query'
|
||||
import PostPoll from './PostPoll'
|
||||
import PostSuggestions from './PostSuggestions'
|
||||
import { emojisFetch } from 'src/stacks/common/emojisFetch'
|
||||
import { PostAction, PostState } from '../PostToot'
|
||||
import { PostAction, PostState } from 'src/stacks/Shared/PostToot'
|
||||
|
||||
export interface Props {
|
||||
postState: PostState
|
||||
@@ -169,8 +170,13 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
>
|
||||
<Text>{postState.text.formatted}</Text>
|
||||
</TextInput>
|
||||
{postState.poll.active && (
|
||||
<View style={styles.poll}>
|
||||
<PostPoll postState={postState} postDispatch={postDispatch} />
|
||||
</View>
|
||||
)}
|
||||
{postState.overlay === 'suggestions' ? (
|
||||
<View style={[styles.suggestions]}>
|
||||
<View style={styles.suggestions}>
|
||||
<PostSuggestions
|
||||
onChangeText={onChangeText}
|
||||
postState={postState}
|
||||
@@ -181,7 +187,7 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
<></>
|
||||
)}
|
||||
{postState.overlay === 'emojis' ? (
|
||||
<View style={[styles.emojis]}>
|
||||
<View style={styles.emojis}>
|
||||
<PostEmojis
|
||||
onChangeText={onChangeText}
|
||||
postState={postState}
|
||||
@@ -193,7 +199,16 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
)}
|
||||
<Pressable style={styles.additions} onPress={() => Keyboard.dismiss()}>
|
||||
<Feather name='paperclip' size={24} />
|
||||
<Feather name='bar-chart-2' size={24} />
|
||||
<Feather
|
||||
name='bar-chart-2'
|
||||
size={24}
|
||||
onPress={() =>
|
||||
postDispatch({
|
||||
type: 'poll',
|
||||
payload: { ...postState.poll, active: !postState.poll.active }
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Feather name='eye-off' size={24} />
|
||||
<Feather
|
||||
name='smile'
|
||||
@@ -211,6 +226,10 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
)
|
||||
}
|
||||
|
||||
// (PostSuggestions as any).whyDidYouRender = true,
|
||||
// (PostPoll as any).whyDidYouRender = true,
|
||||
// (PostEmojis as any).whyDidYouRender = true
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
flex: 1
|
||||
@@ -218,6 +237,10 @@ const styles = StyleSheet.create({
|
||||
textInput: {
|
||||
backgroundColor: 'gray'
|
||||
},
|
||||
poll: {
|
||||
flex: 1,
|
||||
height: 100
|
||||
},
|
||||
suggestions: {
|
||||
flex: 1,
|
||||
backgroundColor: 'lightyellow'
|
||||
|
144
src/stacks/Shared/PostToot/PostPoll.tsx
Normal file
144
src/stacks/Shared/PostToot/PostPoll.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import React, { Dispatch, useState } from 'react'
|
||||
import {
|
||||
ActionSheetIOS,
|
||||
Pressable,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
View
|
||||
} from 'react-native'
|
||||
import { Feather } from '@expo/vector-icons'
|
||||
|
||||
import { PostAction, PostState } from '../PostToot'
|
||||
|
||||
export interface Props {
|
||||
postState: PostState
|
||||
postDispatch: Dispatch<PostAction>
|
||||
}
|
||||
|
||||
const PostPoll: React.FC<Props> = ({ postState, postDispatch }) => {
|
||||
const expireMapping: { [key: string]: string } = {
|
||||
'300': '5分钟',
|
||||
'1800': '30分钟',
|
||||
'3600': '1小时',
|
||||
'21600': '6小时',
|
||||
'86400': '1天',
|
||||
'259200': '3天',
|
||||
'604800': '7天'
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
{[...Array(postState.poll.total)].map((e, i) => (
|
||||
<View key={i} style={styles.option}>
|
||||
{postState.poll.multiple ? (
|
||||
<Feather name='square' size={20} />
|
||||
) : (
|
||||
<Feather name='circle' size={20} />
|
||||
)}
|
||||
<TextInput
|
||||
style={styles.textInput}
|
||||
maxLength={50}
|
||||
value={postState.poll.options[i]}
|
||||
onChangeText={e =>
|
||||
postDispatch({
|
||||
type: 'poll',
|
||||
payload: {
|
||||
...postState.poll,
|
||||
options: { ...postState.poll.options, [i]: e }
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
<View style={styles.totalControl}>
|
||||
<Feather
|
||||
name='minus'
|
||||
size={20}
|
||||
color={postState.poll.total > 2 ? 'black' : 'grey'}
|
||||
onPress={() =>
|
||||
postState.poll.total > 2 &&
|
||||
postDispatch({
|
||||
type: 'poll',
|
||||
payload: { ...postState.poll, total: postState.poll.total - 1 }
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Feather
|
||||
name='plus'
|
||||
size={20}
|
||||
color={postState.poll.total < 4 ? 'black' : 'grey'}
|
||||
onPress={() =>
|
||||
postState.poll.total < 4 &&
|
||||
postDispatch({
|
||||
type: 'poll',
|
||||
payload: { ...postState.poll, total: postState.poll.total + 1 }
|
||||
})
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
options: ['单选', '多选', '取消'],
|
||||
cancelButtonIndex: 2
|
||||
},
|
||||
index =>
|
||||
index < 2 &&
|
||||
postDispatch({
|
||||
type: 'poll',
|
||||
payload: { ...postState.poll, multiple: index === 1 }
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text>{postState.poll.multiple ? '多选' : '单选'}</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={() =>
|
||||
ActionSheetIOS.showActionSheetWithOptions(
|
||||
{
|
||||
options: [...Object.values(expireMapping), '取消'],
|
||||
cancelButtonIndex: 7
|
||||
},
|
||||
index =>
|
||||
index < 7 &&
|
||||
postDispatch({
|
||||
type: 'poll',
|
||||
payload: {
|
||||
...postState.poll,
|
||||
expire: Object.keys(expireMapping)[index]
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text>{expireMapping[postState.poll.expire]}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
flex: 1,
|
||||
backgroundColor: 'green'
|
||||
},
|
||||
option: {
|
||||
height: 30,
|
||||
margin: 5,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
textInput: {
|
||||
flex: 1,
|
||||
backgroundColor: 'white'
|
||||
},
|
||||
totalControl: {
|
||||
alignSelf: 'flex-end',
|
||||
flexDirection: 'row'
|
||||
}
|
||||
})
|
||||
|
||||
export default PostPoll
|
Reference in New Issue
Block a user