Basic editing works

This commit is contained in:
Zhiyuan Zheng 2022-04-30 17:44:39 +02:00
parent 95ec76f411
commit d4f91a5756
17 changed files with 220 additions and 71 deletions

View File

@ -302,7 +302,7 @@ const ParseHTML = React.memo(
/>
)
},
() => true
(prev, next) => prev.content === next.content
)
export default ParseHTML

View File

@ -72,7 +72,7 @@ const TimelineContent = React.memo(
</>
)
},
() => true
(prev, next) => prev.status.content === next.status.content
)
export default TimelineContent

View File

@ -167,11 +167,23 @@
},
"status": {
"heading": "About toot",
"edit": {
"function": "Edit toot",
"button": "Edit this toot"
},
"delete": {
"function": "Delete toot",
"button": "Delete this toot"
"button": "Delete this toot",
"alert": {
"title": "Confirm deleting toot?",
"message": "Are you sure to delete this toot? All boosts and favourites will be cleared, including all replies.",
"buttons": {
"confirm": "Confirm deleting",
"cancel": "$t(common:buttons.cancel)"
}
}
},
"edit": {
"deleteEdit": {
"function": "Delete toot",
"button": "Delete and re-draft",
"alert": {

View File

@ -16,6 +16,7 @@
"default": "Toot",
"conversation": "Toot DM",
"reply": "Toot reply",
"deleteEdit": "Toot",
"edit": "Toot"
},
"alert": {

View File

@ -160,7 +160,7 @@
"function": "툿 삭제",
"button": "이 툿 삭제"
},
"edit": {
"deleteEdit": {
"function": "툿 삭제",
"button": "삭제하고 다시 쓰기",
"alert": {

View File

@ -162,7 +162,7 @@
"function": "Xóa tút",
"button": "Xóa tút này"
},
"edit": {
"deleteEdit": {
"function": "Xóa tút",
"button": "Xóa và viết lại",
"alert": {

View File

@ -162,7 +162,7 @@
"function": "删除",
"button": "删除此条嘟文"
},
"edit": {
"deleteEdit": {
"function": "删除",
"button": "删除并重新编辑此条嘟文",
"alert": {

View File

@ -14,6 +14,8 @@ import { useTheme } from '@utils/styles/ThemeManager'
import apiInstance from '@api/instance'
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
import { RootStackParamList } from '@utils/navigation/navigators'
import { useSelector } from 'react-redux'
import { checkInstanceFeature } from '@utils/slices/instancesSlice'
export interface Props {
navigation: NativeStackNavigationProp<RootStackParamList, 'Screen-Actions'>
@ -59,22 +61,89 @@ const ActionsStatus: React.FC<Props> = ({
}
})
const canEditPost = useSelector(checkInstanceFeature('edit_post'))
return (
<MenuContainer>
<MenuHeader heading={t('shared.header.actions.status.heading')} />
{canEditPost ? (
<MenuRow
onPress={async () => {
analytics('timeline_shared_headeractions_status_edit_press', {
page: queryKey && queryKey[1].page
})
let replyToStatus: Mastodon.Status | undefined = undefined
if (status.in_reply_to_id) {
replyToStatus = await apiInstance<Mastodon.Status>({
method: 'get',
url: `statuses/${status.in_reply_to_id}`
}).then(res => res.body)
}
apiInstance<{
id: Mastodon.Status['id']
text: NonNullable<Mastodon.Status['text']>
spoiler_text: Mastodon.Status['spoiler_text']
}>({
method: 'get',
url: `statuses/${status.id}/source`
}).then(res => {
dismiss()
navigation.navigate('Screen-Compose', {
type: 'edit',
incomingStatus: {
...status,
text: res.body.text,
spoiler_text: res.body.spoiler_text
},
...(replyToStatus && { replyToStatus }),
queryKey,
rootQueryKey
})
})
}}
iconFront='Edit3'
title={t('shared.header.actions.status.edit.button')}
/>
) : null}
<MenuRow
onPress={() => {
analytics('timeline_shared_headeractions_status_delete_press', {
page: queryKey && queryKey[1].page
})
dismiss()
mutation.mutate({
type: 'deleteItem',
source: 'statuses',
queryKey,
rootQueryKey,
id: status.id
})
Alert.alert(
t('shared.header.actions.status.delete.alert.title'),
t('shared.header.actions.status.delete.alert.message'),
[
{
text: t(
'shared.header.actions.status.delete.alert.buttons.cancel'
),
style: 'cancel'
},
{
text: t(
'shared.header.actions.status.delete.alert.buttons.confirm'
),
style: 'destructive',
onPress: async () => {
analytics(
'timeline_shared_headeractions_status_delete_confirm',
{
page: queryKey && queryKey[1].page
}
)
dismiss()
mutation.mutate({
type: 'deleteItem',
source: 'statuses',
queryKey,
rootQueryKey,
id: status.id
})
}
}
]
)
}}
iconFront='Trash'
title={t('shared.header.actions.status.delete.button')}
@ -85,18 +154,18 @@ const ActionsStatus: React.FC<Props> = ({
page: queryKey && queryKey[1].page
})
Alert.alert(
t('shared.header.actions.status.edit.alert.title'),
t('shared.header.actions.status.edit.alert.message'),
t('shared.header.actions.status.deleteEdit.alert.title'),
t('shared.header.actions.status.deleteEdit.alert.message'),
[
{
text: t(
'shared.header.actions.status.edit.alert.buttons.cancel'
'shared.header.actions.status.deleteEdit.alert.buttons.cancel'
),
style: 'cancel'
},
{
text: t(
'shared.header.actions.status.edit.alert.buttons.confirm'
'shared.header.actions.status.deleteEdit.alert.buttons.confirm'
),
style: 'destructive',
onPress: async () => {
@ -106,7 +175,7 @@ const ActionsStatus: React.FC<Props> = ({
page: queryKey && queryKey[1].page
}
)
let replyToStatus: Mastodon.Status
let replyToStatus: Mastodon.Status | undefined = undefined
if (status.in_reply_to_id) {
replyToStatus = await apiInstance<Mastodon.Status>({
method: 'get',
@ -122,10 +191,9 @@ const ActionsStatus: React.FC<Props> = ({
})
.then(res => {
dismiss()
// @ts-ignore
navigation.navigate('Screen-Compose', {
type: 'edit',
incomingStatus: res.body,
type: 'deleteEdit',
incomingStatus: res.body as Mastodon.Status,
...(replyToStatus && { replyToStatus }),
queryKey
})
@ -136,7 +204,7 @@ const ActionsStatus: React.FC<Props> = ({
)
}}
iconFront='Edit'
title={t('shared.header.actions.status.edit.button')}
title={t('shared.header.actions.status.deleteEdit.button')}
/>
<MenuRow
onPress={() => {

View File

@ -1,11 +1,14 @@
import analytics from '@components/analytics'
import { HeaderCenter, HeaderLeft, HeaderRight } from '@components/Header'
import { HeaderLeft, HeaderRight } from '@components/Header'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import haptics from '@root/components/haptics'
import formatText from '@screens/Compose/formatText'
import ComposeRoot from '@screens/Compose/Root'
import { RootStackScreenProps } from '@utils/navigation/navigators'
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
import {
QueryKeyTimeline,
useTimelineMutation
} from '@utils/queryHooks/timeline'
import { updateStoreReview } from '@utils/slices/contextsSlice'
import {
getInstanceAccount,
@ -133,6 +136,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
useEffect(() => {
switch (params?.type) {
case 'edit':
case 'deleteEdit':
if (params.incomingStatus.spoiler_text) {
formatText({
textInput: 'spoiler',
@ -268,6 +272,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
}
return false
}, [totalTextCount, composeState.attachments.uploads, composeState.text.raw])
const mutateTimeline = useTimelineMutation({ onMutate: true })
const headerRight = useCallback(
() => (
<HeaderRight
@ -282,7 +287,7 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
composeDispatch({ type: 'posting', payload: true })
composePost(params, composeState)
.then(() => {
.then(res => {
haptics('Success')
if (
Platform.OS === 'ios' &&
@ -300,6 +305,15 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
switch (params?.type) {
case 'edit':
console.log('firing mutation')
mutateTimeline.mutate({
type: 'editItem',
queryKey: params.queryKey,
rootQueryKey: params.rootQueryKey,
status: res.body
})
break
case 'deleteEdit':
case 'reply':
if (params?.queryKey && params.queryKey[1].page === 'Toot') {
queryClient.invalidateQueries(params.queryKey)

View File

@ -39,6 +39,7 @@ const composeParseState = (
): ComposeState => {
switch (params.type) {
case 'edit':
case 'deleteEdit':
return {
...composeInitialState,
dirty: true,

View File

@ -51,8 +51,11 @@ const composePost = async (
formData.append('visibility', composeState.visibility)
return apiInstance<Mastodon.Status>({
method: 'post',
url: 'statuses',
method: params?.type === 'edit' ? 'put' : 'post',
url:
params?.type === 'edit'
? `statuses/${params.incomingStatus.id}`
: 'statuses',
headers: {
'Idempotency-Key': await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
@ -67,7 +70,9 @@ const composePost = async (
composeState.attachments.sensitive +
composeState.attachments.uploads.map(upload => upload.remote?.id) +
composeState.visibility +
(params?.type === 'edit' ? Math.random() : '')
(params?.type === 'edit' || params?.type === 'deleteEdit'
? Math.random()
: '')
)
},
body: formData

View File

@ -26,31 +26,20 @@ export type RootStackParamList = {
type: 'edit'
incomingStatus: Mastodon.Status
replyToStatus?: Mastodon.Status
queryKey?: [
'Timeline',
{
page: App.Pages
hashtag?: Mastodon.Tag['name']
list?: Mastodon.List['id']
toot?: Mastodon.Status['id']
account?: Mastodon.Account['id']
}
]
queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
}
| {
type: 'deleteEdit'
incomingStatus: Mastodon.Status
replyToStatus?: Mastodon.Status
queryKey?: QueryKeyTimeline
}
| {
type: 'reply'
incomingStatus: Mastodon.Status
accts: Mastodon.Account['acct'][]
queryKey?: [
'Timeline',
{
page: App.Pages
hashtag?: Mastodon.Tag['name']
list?: Mastodon.List['id']
toot?: Mastodon.Status['id']
account?: Mastodon.Account['id']
}
]
queryKey?: QueryKeyTimeline
}
| {
type: 'conversation'

View File

@ -13,6 +13,7 @@ import {
useMutation
} from 'react-query'
import deleteItem from './timeline/deleteItem'
import editItem from './timeline/editItem'
import updateStatusProperty from './timeline/updateStatusProperty'
export type QueryKeyTimeline = [
@ -303,13 +304,21 @@ export type MutationVarsTimelineUpdateAccountProperty = {
}
}
export type MutationVarsTimelineEditItem = {
// This is for editing status
type: 'editItem'
queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
status: Mastodon.Status
}
export type MutationVarsTimelineDeleteItem = {
// This is for deleting status and conversation
type: 'deleteItem'
source: 'statuses' | 'conversations'
queryKey?: QueryKeyTimeline
rootQueryKey?: QueryKeyTimeline
id: Mastodon.Conversation['id']
id: Mastodon.Status['id']
}
export type MutationVarsTimelineDomainBlock = {
@ -322,6 +331,7 @@ export type MutationVarsTimelineDomainBlock = {
export type MutationVarsTimeline =
| MutationVarsTimelineUpdateStatusProperty
| MutationVarsTimelineUpdateAccountProperty
| MutationVarsTimelineEditItem
| MutationVarsTimelineDeleteItem
| MutationVarsTimelineDomainBlock
@ -371,6 +381,8 @@ const mutationFunction = async (params: MutationVarsTimeline) => {
}
})
}
case 'editItem':
return { body: params.status }
case 'deleteItem':
return apiInstance<Mastodon.Conversation>({
method: 'delete',
@ -423,6 +435,9 @@ const useTimelineMutation = ({
case 'updateStatusProperty':
updateStatusProperty(params)
break
case 'editItem':
editItem(params)
break
case 'deleteItem':
deleteItem(params)
break

View File

@ -6,11 +6,7 @@ const deleteItem = ({
queryKey,
rootQueryKey,
id
}: {
queryKey?: MutationVarsTimelineDeleteItem['queryKey']
rootQueryKey?: MutationVarsTimelineDeleteItem['rootQueryKey']
id: MutationVarsTimelineDeleteItem['id']
}) => {
}: MutationVarsTimelineDeleteItem) => {
queryKey &&
queryClient.setQueryData<InfiniteData<any> | undefined>(queryKey, old => {
if (old) {

View File

@ -0,0 +1,52 @@
import queryClient from '@helpers/queryClient'
import { InfiniteData } from 'react-query'
import { MutationVarsTimelineEditItem } from '../timeline'
const editItem = ({
queryKey,
rootQueryKey,
status
}: MutationVarsTimelineEditItem) => {
console.log('START')
queryKey &&
queryClient.setQueryData<InfiniteData<any> | undefined>(queryKey, old => {
if (old) {
old.pages = old.pages.map(page => {
page.body = page.body.map((item: Mastodon.Status) => {
if (item.id === status.id) {
console.log('found queryKey', queryKey)
console.log('new content', status.content)
item = status
}
return item
})
return page
})
return old
}
})
rootQueryKey &&
queryClient.setQueryData<InfiniteData<any> | undefined>(
rootQueryKey,
old => {
if (old) {
old.pages = old.pages.map(page => {
page.body = page.body.map((item: Mastodon.Status) => {
if (item.id === status.id) {
console.log('found rootQueryKey', queryKey)
console.log('new content', status.content)
item = status
}
return item
})
return page
})
return old
}
}
)
console.log('EDN')
}
export default editItem

View File

@ -14,13 +14,7 @@ const updateStatusProperty = ({
id,
reblog,
payload
}: {
queryKey: MutationVarsTimelineUpdateStatusProperty['queryKey']
rootQueryKey?: MutationVarsTimelineUpdateStatusProperty['rootQueryKey']
id: MutationVarsTimelineUpdateStatusProperty['id']
reblog?: MutationVarsTimelineUpdateStatusProperty['reblog']
payload: MutationVarsTimelineUpdateStatusProperty['payload']
}) => {
}: MutationVarsTimelineUpdateStatusProperty) => {
queryClient.setQueryData<InfiniteData<TimelineData> | undefined>(
queryKey,
old => {

View File

@ -344,14 +344,16 @@ export const getInstanceVersion = ({ instances: { instances } }: RootState) =>
instances[findInstanceActive(instances)]?.version
export const checkInstanceFeature =
(feature: string) =>
({ instances: { instances } }: RootState) => {
return features
.filter(f => f.feature === feature)
.filter(
f =>
parseFloat(instances[findInstanceActive(instances)]?.version) >=
f.version
)
({ instances: { instances } }: RootState): Boolean => {
return (
features
.filter(f => f.feature === feature)
.filter(
f =>
parseFloat(instances[findInstanceActive(instances)]?.version) >=
f.version
).length > 0
)
}
/* Get Instance Configuration */