diff --git a/src/components/Parse/HTML.tsx b/src/components/Parse/HTML.tsx
index 518305dd..cb9cb39d 100644
--- a/src/components/Parse/HTML.tsx
+++ b/src/components/Parse/HTML.tsx
@@ -302,7 +302,7 @@ const ParseHTML = React.memo(
/>
)
},
- () => true
+ (prev, next) => prev.content === next.content
)
export default ParseHTML
diff --git a/src/components/Timeline/Shared/Content.tsx b/src/components/Timeline/Shared/Content.tsx
index 576f3247..31c1ccbb 100644
--- a/src/components/Timeline/Shared/Content.tsx
+++ b/src/components/Timeline/Shared/Content.tsx
@@ -72,7 +72,7 @@ const TimelineContent = React.memo(
>
)
},
- () => true
+ (prev, next) => prev.status.content === next.status.content
)
export default TimelineContent
diff --git a/src/i18n/en/components/timeline.json b/src/i18n/en/components/timeline.json
index cf4b5596..8ee94ff5 100644
--- a/src/i18n/en/components/timeline.json
+++ b/src/i18n/en/components/timeline.json
@@ -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": {
diff --git a/src/i18n/en/screens/compose.json b/src/i18n/en/screens/compose.json
index a0e41ae1..15eb4537 100644
--- a/src/i18n/en/screens/compose.json
+++ b/src/i18n/en/screens/compose.json
@@ -16,6 +16,7 @@
"default": "Toot",
"conversation": "Toot DM",
"reply": "Toot reply",
+ "deleteEdit": "Toot",
"edit": "Toot"
},
"alert": {
diff --git a/src/i18n/ko/components/timeline.json b/src/i18n/ko/components/timeline.json
index f78c80a4..4a0d1729 100644
--- a/src/i18n/ko/components/timeline.json
+++ b/src/i18n/ko/components/timeline.json
@@ -160,7 +160,7 @@
"function": "툿 삭제",
"button": "이 툿 삭제"
},
- "edit": {
+ "deleteEdit": {
"function": "툿 삭제",
"button": "삭제하고 다시 쓰기",
"alert": {
diff --git a/src/i18n/vi/components/timeline.json b/src/i18n/vi/components/timeline.json
index a1eb9053..d3e84f49 100644
--- a/src/i18n/vi/components/timeline.json
+++ b/src/i18n/vi/components/timeline.json
@@ -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": {
diff --git a/src/i18n/zh-Hans/components/timeline.json b/src/i18n/zh-Hans/components/timeline.json
index dd2756d6..d39a21c3 100644
--- a/src/i18n/zh-Hans/components/timeline.json
+++ b/src/i18n/zh-Hans/components/timeline.json
@@ -162,7 +162,7 @@
"function": "删除",
"button": "删除此条嘟文"
},
- "edit": {
+ "deleteEdit": {
"function": "删除",
"button": "删除并重新编辑此条嘟文",
"alert": {
diff --git a/src/screens/Actions/Status.tsx b/src/screens/Actions/Status.tsx
index d509c549..da423c41 100644
--- a/src/screens/Actions/Status.tsx
+++ b/src/screens/Actions/Status.tsx
@@ -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
@@ -59,22 +61,89 @@ const ActionsStatus: React.FC = ({
}
})
+ const canEditPost = useSelector(checkInstanceFeature('edit_post'))
+
return (
+ {canEditPost ? (
+ {
+ 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({
+ method: 'get',
+ url: `statuses/${status.in_reply_to_id}`
+ }).then(res => res.body)
+ }
+ apiInstance<{
+ id: Mastodon.Status['id']
+ text: NonNullable
+ 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}
{
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 = ({
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 = ({
page: queryKey && queryKey[1].page
}
)
- let replyToStatus: Mastodon.Status
+ let replyToStatus: Mastodon.Status | undefined = undefined
if (status.in_reply_to_id) {
replyToStatus = await apiInstance({
method: 'get',
@@ -122,10 +191,9 @@ const ActionsStatus: React.FC = ({
})
.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 = ({
)
}}
iconFront='Edit'
- title={t('shared.header.actions.status.edit.button')}
+ title={t('shared.header.actions.status.deleteEdit.button')}
/>
{
diff --git a/src/screens/Compose.tsx b/src/screens/Compose.tsx
index 92b23b8c..dab9093d 100644
--- a/src/screens/Compose.tsx
+++ b/src/screens/Compose.tsx
@@ -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> = ({
useEffect(() => {
switch (params?.type) {
case 'edit':
+ case 'deleteEdit':
if (params.incomingStatus.spoiler_text) {
formatText({
textInput: 'spoiler',
@@ -268,6 +272,7 @@ const ScreenCompose: React.FC> = ({
}
return false
}, [totalTextCount, composeState.attachments.uploads, composeState.text.raw])
+ const mutateTimeline = useTimelineMutation({ onMutate: true })
const headerRight = useCallback(
() => (
> = ({
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> = ({
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)
diff --git a/src/screens/Compose/utils/parseState.ts b/src/screens/Compose/utils/parseState.ts
index ae874d18..285206de 100644
--- a/src/screens/Compose/utils/parseState.ts
+++ b/src/screens/Compose/utils/parseState.ts
@@ -39,6 +39,7 @@ const composeParseState = (
): ComposeState => {
switch (params.type) {
case 'edit':
+ case 'deleteEdit':
return {
...composeInitialState,
dirty: true,
diff --git a/src/screens/Compose/utils/post.ts b/src/screens/Compose/utils/post.ts
index 9be1334c..d1ee13c4 100644
--- a/src/screens/Compose/utils/post.ts
+++ b/src/screens/Compose/utils/post.ts
@@ -51,8 +51,11 @@ const composePost = async (
formData.append('visibility', composeState.visibility)
return apiInstance({
- 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
diff --git a/src/utils/navigation/navigators.ts b/src/utils/navigation/navigators.ts
index b4956ac7..ef8e7a5b 100644
--- a/src/utils/navigation/navigators.ts
+++ b/src/utils/navigation/navigators.ts
@@ -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'
diff --git a/src/utils/queryHooks/timeline.ts b/src/utils/queryHooks/timeline.ts
index 384db03e..b2956faa 100644
--- a/src/utils/queryHooks/timeline.ts
+++ b/src/utils/queryHooks/timeline.ts
@@ -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({
method: 'delete',
@@ -423,6 +435,9 @@ const useTimelineMutation = ({
case 'updateStatusProperty':
updateStatusProperty(params)
break
+ case 'editItem':
+ editItem(params)
+ break
case 'deleteItem':
deleteItem(params)
break
diff --git a/src/utils/queryHooks/timeline/deleteItem.ts b/src/utils/queryHooks/timeline/deleteItem.ts
index fa2ddaa3..d2911bf8 100644
--- a/src/utils/queryHooks/timeline/deleteItem.ts
+++ b/src/utils/queryHooks/timeline/deleteItem.ts
@@ -6,11 +6,7 @@ const deleteItem = ({
queryKey,
rootQueryKey,
id
-}: {
- queryKey?: MutationVarsTimelineDeleteItem['queryKey']
- rootQueryKey?: MutationVarsTimelineDeleteItem['rootQueryKey']
- id: MutationVarsTimelineDeleteItem['id']
-}) => {
+}: MutationVarsTimelineDeleteItem) => {
queryKey &&
queryClient.setQueryData | undefined>(queryKey, old => {
if (old) {
diff --git a/src/utils/queryHooks/timeline/editItem.ts b/src/utils/queryHooks/timeline/editItem.ts
new file mode 100644
index 00000000..0ea5a818
--- /dev/null
+++ b/src/utils/queryHooks/timeline/editItem.ts
@@ -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 | 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 | 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
diff --git a/src/utils/queryHooks/timeline/updateStatusProperty.ts b/src/utils/queryHooks/timeline/updateStatusProperty.ts
index 893aa52d..2a697404 100644
--- a/src/utils/queryHooks/timeline/updateStatusProperty.ts
+++ b/src/utils/queryHooks/timeline/updateStatusProperty.ts
@@ -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 | undefined>(
queryKey,
old => {
diff --git a/src/utils/slices/instancesSlice.ts b/src/utils/slices/instancesSlice.ts
index 5d62ee10..605c036a 100644
--- a/src/utils/slices/instancesSlice.ts
+++ b/src/utils/slices/instancesSlice.ts
@@ -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 */