From faebd555e859a3a2042782dab90d1e8bd66a5411 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Mon, 6 Jun 2022 22:49:43 +0200 Subject: [PATCH] New context menu largely working --- ios/Podfile.lock | 6 + package.json | 3 +- src/components/Timeline/Default.tsx | 169 ++++++----- .../Timeline/Shared/ContextMenu.tsx | 65 ++++ .../Timeline/Shared/ContextMenu/account.ts | 165 ++++++++++ .../Timeline/Shared/ContextMenu/instance.ts | 108 +++++++ .../Timeline/Shared/ContextMenu/share.ts | 40 +++ .../Timeline/Shared/ContextMenu/status.ts | 286 ++++++++++++++++++ .../Timeline/Shared/HeaderDefault.tsx | 64 ++-- src/i18n/en/_all.ts | 1 + src/i18n/en/components/contextMenu.json | 70 +++++ src/i18n/en/components/timeline.json | 88 ------ src/i18n/i18n.ts | 4 +- src/screens/Actions.tsx | 33 -- yarn.lock | 4 + 15 files changed, 872 insertions(+), 234 deletions(-) create mode 100644 src/components/Timeline/Shared/ContextMenu.tsx create mode 100644 src/components/Timeline/Shared/ContextMenu/account.ts create mode 100644 src/components/Timeline/Shared/ContextMenu/instance.ts create mode 100644 src/components/Timeline/Shared/ContextMenu/share.ts create mode 100644 src/components/Timeline/Shared/ContextMenu/status.ts create mode 100644 src/i18n/en/components/contextMenu.json diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5235561c..885ea5d8 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -412,6 +412,8 @@ PODS: - React-Core - react-native-cameraroll (4.1.2): - React-Core + - react-native-context-menu-view (1.5.4): + - React - react-native-netinfo (9.0.0): - React-Core - react-native-pager-view (5.4.11): @@ -646,6 +648,7 @@ DEPENDENCIES: - "react-native-blur (from `../node_modules/@react-native-community/blur`)" - react-native-blurhash (from `../node_modules/react-native-blurhash`) - "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)" + - react-native-context-menu-view (from `../node_modules/react-native-context-menu-view`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-pager-view (from `../node_modules/react-native-pager-view`) - "react-native-paste-input (from `../node_modules/@mattermost/react-native-paste-input`)" @@ -804,6 +807,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-blurhash" react-native-cameraroll: :path: "../node_modules/@react-native-community/cameraroll" + react-native-context-menu-view: + :path: "../node_modules/react-native-context-menu-view" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-pager-view: @@ -928,6 +933,7 @@ SPEC CHECKSUMS: react-native-blur: cad4d93b364f91e7b7931b3fa935455487e5c33c react-native-blurhash: add4df9a937b4e021a24bc67a0714f13e0bd40b7 react-native-cameraroll: 2957f2bce63ae896a848fbe0d5352c1bd4d20866 + react-native-context-menu-view: b0beca02aad4bd9f9d7d932bf437e0a03baa69ef react-native-netinfo: 5b664b2945a8f02102b296f0f812bddd6827ed9c react-native-pager-view: 7f00d63688f7df9fad86dfb0154814419cc5eb8d react-native-paste-input: efbf0b08fa1673f0e3131da6ea01678c1bb8003e diff --git a/package.json b/package.json index 5f66e806..582be92e 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "react-native-animated-spinkit": "1.5.2", "react-native-base64": "^0.2.1", "react-native-blurhash": "1.1.10", + "react-native-context-menu-view": "xmflsct/react-native-context-menu-view", "react-native-fast-image": "8.5.11", "react-native-feather": "1.1.2", "react-native-flash-message": "0.2.1", @@ -151,4 +152,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/Timeline/Default.tsx b/src/components/Timeline/Default.tsx index 9d863144..6c84aec3 100644 --- a/src/components/Timeline/Default.tsx +++ b/src/components/Timeline/Default.tsx @@ -18,6 +18,7 @@ import { uniqBy } from 'lodash' import React, { useCallback } from 'react' import { Pressable, View } from 'react-native' import { useSelector } from 'react-redux' +import TimelineContextMenu from './Shared/ContextMenu' import TimelineFeedback from './Shared/Feedback' import TimelineFiltered, { shouldFilter } from './Shared/Filtered' import TimelineFullConversation from './Shared/FullConversation' @@ -73,96 +74,108 @@ const TimelineDefault: React.FC = ({ }, []) return ( - - {item.reblog ? ( - - ) : item._pinned ? ( - - ) : null} - - - - - - - {}} > - {typeof actualStatus.content === 'string' && - actualStatus.content.length > 0 ? ( - + ) : item._pinned ? ( + + ) : null} + + + + - ) : null} - {queryKey && actualStatus.poll ? ( - + + + {typeof actualStatus.content === 'string' && + actualStatus.content.length > 0 ? ( + + ) : null} + {queryKey && actualStatus.poll ? ( + + ) : null} + {!disableDetails && + Array.isArray(actualStatus.media_attachments) && + actualStatus.media_attachments.length ? ( + + ) : null} + {!disableDetails && actualStatus.card ? ( + + ) : null} + {!disableDetails ? ( + + ) : null} + + + + + {queryKey && !disableDetails ? ( + d?.id !== instanceAccount?.id), + d => d?.id + ).map(d => d?.acct)} reblog={item.reblog ? true : false} - sameAccount={ownAccount} /> ) : null} - {!disableDetails && - Array.isArray(actualStatus.media_attachments) && - actualStatus.media_attachments.length ? ( - - ) : null} - {!disableDetails && actualStatus.card ? ( - - ) : null} - {!disableDetails ? ( - - ) : null} - - - - - {queryKey && !disableDetails ? ( - d?.id !== instanceAccount?.id), - d => d?.id - ).map(d => d?.acct)} - reblog={item.reblog ? true : false} - /> - ) : null} - + + ) } diff --git a/src/components/Timeline/Shared/ContextMenu.tsx b/src/components/Timeline/Shared/ContextMenu.tsx new file mode 100644 index 00000000..d3769c84 --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu.tsx @@ -0,0 +1,65 @@ +import { QueryKeyTimeline } from '@utils/queryHooks/timeline' +import { createContext } from 'react' +import ContextMenu, { ContextMenuAction } from 'react-native-context-menu-view' +import contextMenuAccount from './ContextMenu/account' +import contextMenuInstance from './ContextMenu/instance' +import contextMenuShare from './ContextMenu/share' +import contextMenuStatus from './ContextMenu/status' + +export interface Props { + status: Mastodon.Status + queryKey?: QueryKeyTimeline + rootQueryKey?: QueryKeyTimeline +} + +export const ContextMenuContext = createContext([]) + +const TimelineContextMenu: React.FC = ({ + children, + status, + queryKey, + rootQueryKey +}) => { + if (!queryKey) { + return <>{children} + } + + const menuItems: ContextMenuAction[] = [] + + const shareOnPress = contextMenuShare({ menuItems, status }) + const statusOnPress = contextMenuStatus({ + menuItems, + status, + queryKey, + rootQueryKey + }) + const accountOnPress = contextMenuAccount({ + menuItems, + status, + queryKey, + rootQueryKey + }) + const instanceOnPress = contextMenuInstance({ + menuItems, + status, + queryKey, + rootQueryKey + }) + + return ( + + { + shareOnPress(id) + statusOnPress(id) + accountOnPress(id) + instanceOnPress(id) + }} + children={children} + /> + + ) +} + +export default TimelineContextMenu diff --git a/src/components/Timeline/Shared/ContextMenu/account.ts b/src/components/Timeline/Shared/ContextMenu/account.ts new file mode 100644 index 00000000..76e6436b --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu/account.ts @@ -0,0 +1,165 @@ +import analytics from '@components/analytics' +import { displayMessage } from '@components/Message' +import { + MutationVarsTimelineUpdateAccountProperty, + QueryKeyTimeline, + useTimelineMutation +} from '@utils/queryHooks/timeline' +import { getInstanceAccount } from '@utils/slices/instancesSlice' +import { useTheme } from '@utils/styles/ThemeManager' +import { useTranslation } from 'react-i18next' +import { Platform } from 'react-native' +import { ContextMenuAction } from 'react-native-context-menu-view' +import { useQueryClient } from 'react-query' +import { useSelector } from 'react-redux' + +export interface Props { + menuItems: ContextMenuAction[] + status: Mastodon.Status + queryKey: QueryKeyTimeline + rootQueryKey?: QueryKeyTimeline +} + +const contextMenuAccount = ({ + menuItems, + status, + queryKey, + rootQueryKey +}: Props) => { + const { theme } = useTheme() + const { t } = useTranslation('componentContextMenu') + + const queryClient = useQueryClient() + const mutateion = useTimelineMutation({ + onSuccess: (_, params) => { + const theParams = params as MutationVarsTimelineUpdateAccountProperty + displayMessage({ + theme, + type: 'success', + message: t('common:message.success.message', { + function: t(`account.${theParams.payload.property}.action`) + }) + }) + }, + onError: (err: any, params) => { + const theParams = params as MutationVarsTimelineUpdateAccountProperty + displayMessage({ + theme, + type: 'error', + message: t('common:message.error.message', { + function: t(`account.${theParams.payload.property}.action`) + }), + ...(err.status && + typeof err.status === 'number' && + err.data && + err.data.error && + typeof err.data.error === 'string' && { + description: err.data.error + }) + }) + }, + onSettled: () => { + queryKey && queryClient.invalidateQueries(queryKey) + rootQueryKey && queryClient.invalidateQueries(rootQueryKey) + } + }) + + const instanceAccount = useSelector( + getInstanceAccount, + (prev, next) => prev.id === next.id + ) + const ownAccount = instanceAccount?.id === status.account.id + + if (!ownAccount) { + switch (Platform.OS) { + case 'ios': + menuItems.push({ + id: 'account', + title: t('account.title'), + inlineChildren: true, + actions: [ + { + id: 'account-mute', + title: t('account.mute.action'), + systemIcon: 'eye.slash' + }, + { + id: 'account-block', + title: t('account.block.action'), + systemIcon: 'xmark.circle', + destructive: true + }, + { + id: 'account-reports', + title: t('account.reports.action'), + systemIcon: 'flag', + destructive: true + } + ] + }) + break + default: + menuItems.push( + { + id: 'account-mute', + title: t('account.mute.action'), + systemIcon: 'eye.slash' + }, + { + id: 'account-block', + title: t('account.block.action'), + systemIcon: 'xmark.circle', + destructive: true + }, + { + id: 'account-reports', + title: t('account.reports.action'), + systemIcon: 'flag', + destructive: true + } + ) + break + } + } + + return (id: string) => { + const url = status.url || status.uri + switch (id) { + case 'account-mute': + analytics('timeline_shared_headeractions_account_mute_press', { + page: queryKey && queryKey[1].page + }) + mutateion.mutate({ + type: 'updateAccountProperty', + queryKey, + id: status.account.id, + payload: { property: 'mute' } + }) + break + case 'account-block': + analytics('timeline_shared_headeractions_account_block_press', { + page: queryKey && queryKey[1].page + }) + mutateion.mutate({ + type: 'updateAccountProperty', + queryKey, + id: status.account.id, + payload: { property: 'block' } + }) + break + case 'account-report': + analytics('timeline_shared_headeractions_account_reports_press', { + page: queryKey && queryKey[1].page + }) + mutateion.mutate({ + type: 'updateAccountProperty', + queryKey, + id: status.account.id, + payload: { property: 'reports' } + }) + break + } + } +} + +export default contextMenuAccount diff --git a/src/components/Timeline/Shared/ContextMenu/instance.ts b/src/components/Timeline/Shared/ContextMenu/instance.ts new file mode 100644 index 00000000..008b1b80 --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu/instance.ts @@ -0,0 +1,108 @@ +import analytics from '@components/analytics' +import { displayMessage } from '@components/Message' +import { + QueryKeyTimeline, + useTimelineMutation +} from '@utils/queryHooks/timeline' +import { getInstanceUrl } from '@utils/slices/instancesSlice' +import { useTheme } from '@utils/styles/ThemeManager' +import { useTranslation } from 'react-i18next' +import { Alert, Platform } from 'react-native' +import { ContextMenuAction } from 'react-native-context-menu-view' +import { useQueryClient } from 'react-query' +import { useSelector } from 'react-redux' + +export interface Props { + menuItems: ContextMenuAction[] + status: Mastodon.Status + queryKey: QueryKeyTimeline + rootQueryKey?: QueryKeyTimeline +} + +const contextMenuInstance = ({ + menuItems, + status, + queryKey, + rootQueryKey +}: Props) => { + const { t } = useTranslation('componentContextMenu') + const { theme } = useTheme() + + const currentInstance = useSelector(getInstanceUrl) + const instance = status.uri && status.uri.split(new RegExp(/\/\/(.*?)\//))[1] + + const queryClient = useQueryClient() + const mutation = useTimelineMutation({ + onSettled: () => { + displayMessage({ + theme, + type: 'success', + message: t('common:message.success.message', { + function: t(`instance.block.action`, { instance }) + }) + }) + queryClient.invalidateQueries(queryKey) + rootQueryKey && queryClient.invalidateQueries(rootQueryKey) + } + }) + + if (currentInstance !== instance && instance) { + switch (Platform.OS) { + case 'ios': + menuItems.push({ + id: 'instance', + title: t('instance.title'), + actions: [ + { + id: 'instance-block', + title: t('instance.block.action', { instance }), + destructive: true + } + ] + }) + break + default: + menuItems.push({ + id: 'instance-block', + title: t('instance.block.action', { instance }), + destructive: true + }) + break + } + } + + return (id: string) => { + switch (id) { + case 'instance-block': + analytics('timeline_shared_headeractions_domain_block_press', { + page: queryKey[1].page + }) + Alert.alert( + t('instance.block.alert.title', { instance }), + t('instance.block.alert.message'), + [ + { + text: t('instance.block.alert.buttons.confirm'), + style: 'destructive', + onPress: () => { + analytics( + 'timeline_shared_headeractions_domain_block_confirm', + { page: queryKey && queryKey[1].page } + ) + mutation.mutate({ + type: 'domainBlock', + queryKey, + domain: instance + }) + } + }, + { + text: t('common:buttons.cancel') + } + ] + ) + } + } +} + +export default contextMenuInstance diff --git a/src/components/Timeline/Shared/ContextMenu/share.ts b/src/components/Timeline/Shared/ContextMenu/share.ts new file mode 100644 index 00000000..68e4213f --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu/share.ts @@ -0,0 +1,40 @@ +import analytics from '@components/analytics' +import { useTranslation } from 'react-i18next' +import { Platform, Share } from 'react-native' +import { ContextMenuAction } from 'react-native-context-menu-view' + +export interface Props { + menuItems: ContextMenuAction[] + status: Mastodon.Status +} + +const contextMenuShare = ({ menuItems, status }: Props) => { + const { t } = useTranslation('componentContextMenu') + + if (status.visibility !== 'direct') { + menuItems.push({ + id: 'share', + title: t(`share.status.action`), + systemIcon: 'square.and.arrow.up' + }) + } + + return (id: string) => { + const url = status.url || status.uri + switch (id) { + case 'share': + analytics('timeline_shared_headeractions_share_press') + switch (Platform.OS) { + case 'ios': + Share.share({ url }) + break + case 'android': + Share.share({ message: url }) + break + } + break + } + } +} + +export default contextMenuShare diff --git a/src/components/Timeline/Shared/ContextMenu/status.ts b/src/components/Timeline/Shared/ContextMenu/status.ts new file mode 100644 index 00000000..858c3522 --- /dev/null +++ b/src/components/Timeline/Shared/ContextMenu/status.ts @@ -0,0 +1,286 @@ +import apiInstance from '@api/instance' +import analytics from '@components/analytics' +import { displayMessage } from '@components/Message' +import { useNavigation } from '@react-navigation/native' +import { NativeStackNavigationProp } from '@react-navigation/native-stack' +import { RootStackParamList } from '@utils/navigation/navigators' +import { + MutationVarsTimelineUpdateStatusProperty, + QueryKeyTimeline, + useTimelineMutation +} from '@utils/queryHooks/timeline' +import { + checkInstanceFeature, + getInstanceAccount +} from '@utils/slices/instancesSlice' +import { useTheme } from '@utils/styles/ThemeManager' +import { useTranslation } from 'react-i18next' +import { Alert, Platform } from 'react-native' +import { ContextMenuAction } from 'react-native-context-menu-view' +import { useQueryClient } from 'react-query' +import { useSelector } from 'react-redux' + +export interface Props { + menuItems: ContextMenuAction[] + status: Mastodon.Status + queryKey: QueryKeyTimeline + rootQueryKey?: QueryKeyTimeline +} + +const contextMenuStatus = ({ + menuItems, + status, + queryKey, + rootQueryKey +}: Props) => { + const navigation = + useNavigation< + NativeStackNavigationProp + >() + const { theme } = useTheme() + const { t } = useTranslation('componentContextMenu') + + const queryClient = useQueryClient() + const mutation = useTimelineMutation({ + onMutate: true, + onError: (err: any, params, oldData) => { + const theFunction = (params as MutationVarsTimelineUpdateStatusProperty) + .payload + ? (params as MutationVarsTimelineUpdateStatusProperty).payload.property + : 'delete' + displayMessage({ + theme, + type: 'error', + message: t('common:message.error.message', { + function: t(`status.${theFunction}.action`) + }), + ...(err.status && + typeof err.status === 'number' && + err.data && + err.data.error && + typeof err.data.error === 'string' && { + description: err.data.error + }) + }) + queryClient.setQueryData(queryKey, oldData) + } + }) + + const instanceAccount = useSelector( + getInstanceAccount, + (prev, next) => prev.id === next.id + ) + const ownAccount = instanceAccount?.id === status.account.id + + if (ownAccount) { + const accountMenuItems: ContextMenuAction[] = [ + { + id: 'status-delete', + title: t('status.delete.action'), + systemIcon: 'trash', + destructive: true + }, + { + id: 'status-delete-edit', + title: t('status.deleteEdit.action'), + systemIcon: 'pencil.and.outline', + destructive: true + }, + { + id: 'status-mute', + title: t('status.mute.action-muted', { + context: status.muted.toString() + }), + systemIcon: status.muted ? 'speaker' : 'speaker.slash' + } + ] + + const canEditPost = useSelector(checkInstanceFeature('edit_post')) + if (canEditPost) { + accountMenuItems.unshift({ + id: 'status-edit', + title: t('status.edit.action'), + systemIcon: 'square.and.pencil' + }) + } + + if (status.visibility === 'public' || status.visibility === 'unlisted') { + accountMenuItems.push({ + id: 'status-pin', + title: t('status.pin.action-pinned', { + context: status.pinned.toString() + }), + systemIcon: status.pinned ? 'pin.slash' : 'pin' + }) + } + + switch (Platform.OS) { + case 'ios': + menuItems.push({ + id: 'status', + title: t('status.title'), + inlineChildren: true, + actions: accountMenuItems + }) + break + default: + menuItems.push(...accountMenuItems) + break + } + } + + return async (id: string) => { + switch (id) { + case 'status-delete': + analytics('timeline_shared_headeractions_status_delete_press', { + page: queryKey && queryKey[1].page + }) + Alert.alert( + t('status.delete.alert.title'), + t('status.delete.alert.message'), + [ + { + text: t('status.delete.alert.buttons.confirm'), + style: 'destructive', + onPress: async () => { + analytics( + 'timeline_shared_headeractions_status_delete_confirm', + { + page: queryKey && queryKey[1].page + } + ) + mutation.mutate({ + type: 'deleteItem', + source: 'statuses', + queryKey, + rootQueryKey, + id: status.id + }) + } + }, + { + text: t('common:buttons.cancel') + } + ] + ) + break + case 'status-delete-edit': + analytics('timeline_shared_headeractions_status_deleteedit_press', { + page: queryKey && queryKey[1].page + }) + Alert.alert( + t('status.deleteEdit.alert.title'), + t('status.deleteEdit.alert.message'), + [ + { + text: t('status.deleteEdit.alert.buttons.confirm'), + style: 'destructive', + onPress: async () => { + analytics( + 'timeline_shared_headeractions_status_deleteedit_confirm', + { + 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) + } + mutation + .mutateAsync({ + type: 'deleteItem', + source: 'statuses', + queryKey, + id: status.id + }) + .then(res => { + navigation.navigate('Screen-Compose', { + type: 'deleteEdit', + incomingStatus: res.body as Mastodon.Status, + ...(replyToStatus && { replyToStatus }), + queryKey + }) + }) + } + }, + { + text: t('common:buttons.cancel') + } + ] + ) + break + case 'status-mute': + analytics('timeline_shared_headeractions_status_mute_press', { + page: queryKey && queryKey[1].page + }) + mutation.mutate({ + type: 'updateStatusProperty', + queryKey, + rootQueryKey, + id: status.id, + payload: { + property: 'muted', + currentValue: status.muted, + propertyCount: undefined, + countValue: undefined + } + }) + break + case 'status-edit': + 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 => { + navigation.navigate('Screen-Compose', { + type: 'edit', + incomingStatus: { + ...status, + text: res.body.text, + spoiler_text: res.body.spoiler_text + }, + ...(replyToStatus && { replyToStatus }), + queryKey, + rootQueryKey + }) + }) + break + case 'status-pin': + // Also note that reblogs cannot be pinned. + analytics('timeline_shared_headeractions_status_pin_press', { + page: queryKey && queryKey[1].page + }) + mutation.mutate({ + type: 'updateStatusProperty', + queryKey, + rootQueryKey, + id: status.id, + payload: { + property: 'pinned', + currentValue: status.pinned, + propertyCount: undefined, + countValue: undefined + } + }) + break + } + } +} + +export default contextMenuStatus diff --git a/src/components/Timeline/Shared/HeaderDefault.tsx b/src/components/Timeline/Shared/HeaderDefault.tsx index b4db437b..ebfc2135 100644 --- a/src/components/Timeline/Shared/HeaderDefault.tsx +++ b/src/components/Timeline/Shared/HeaderDefault.tsx @@ -1,13 +1,13 @@ import Icon from '@components/Icon' -import { useNavigation } from '@react-navigation/native' -import { StackNavigationProp } from '@react-navigation/stack' -import { RootStackParamList } from '@utils/navigation/navigators' import { QueryKeyTimeline } from '@utils/queryHooks/timeline' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' -import React from 'react' +import React, { useContext, useRef } from 'react' import { useTranslation } from 'react-i18next' import { Pressable, View } from 'react-native' +import ContextMenu from 'react-native-context-menu-view' +import { TouchableWithoutFeedback } from 'react-native-gesture-handler' +import { ContextMenuContext } from './ContextMenu' import HeaderSharedAccount from './HeaderShared/Account' import HeaderSharedApplication from './HeaderShared/Application' import HeaderSharedCreated from './HeaderShared/Created' @@ -16,24 +16,19 @@ import HeaderSharedVisibility from './HeaderShared/Visibility' export interface Props { queryKey?: QueryKeyTimeline - rootQueryKey?: QueryKeyTimeline status: Mastodon.Status highlighted: boolean } -const TimelineHeaderDefault = ({ - queryKey, - rootQueryKey, - status, - highlighted -}: Props) => { - const { t } = useTranslation('componentTimeline') - const navigation = useNavigation>() +const TimelineHeaderDefault = ({ queryKey, status, highlighted }: Props) => { + const { t } = useTranslation('componentContextMenu') const { colors } = useTheme() + const contextMenuItems = useContext(ContextMenuContext) + return ( - + - navigation.navigate('Screen-Actions', { - queryKey, - rootQueryKey, - status, - type: 'status' - }) - } - children={ - - } - /> + > + { + // console.log('index', index) + // console.log('name', name) + // // shareOnPress(name) + // // statusOnPress(name) + // accountOnPress(name) + // // instanceOnPress(name) + // }} + children={ + + } + /> + ) : null} ) diff --git a/src/i18n/en/_all.ts b/src/i18n/en/_all.ts index fa81be2e..5f2a7a82 100644 --- a/src/i18n/en/_all.ts +++ b/src/i18n/en/_all.ts @@ -8,6 +8,7 @@ export default { screenImageViewer: require('./screens/imageViewer'), screenTabs: require('./screens/tabs'), + componentContextMenu: require('./components/contextMenu'), componentEmojis: require('./components/emojis'), componentInstance: require('./components/instance'), componentMediaSelector: require('./components/mediaSelector'), diff --git a/src/i18n/en/components/contextMenu.json b/src/i18n/en/components/contextMenu.json new file mode 100644 index 00000000..538c7a4f --- /dev/null +++ b/src/i18n/en/components/contextMenu.json @@ -0,0 +1,70 @@ +{ + "accessibilityHint": "Actions for this toot, such as its posted user, toot itself", + "account": { + "title": "User actions", + "mute": { + "action": "Mute user" + }, + "block": { + "action": "Block user" + }, + "reports": { + "action": "Report user" + } + }, + "instance": { + "title": "Instance action", + "block": { + "action": "Block instance {{instance}}", + "alert": { + "title": "Confirm blocking instance {{instance}} ?", + "message": "Mostly you can mute or block certain user.\n\nAfter blocking instance, all its content including followers from this instance will be removed!", + "buttons": { + "confirm": "Confirm" + } + } + } + }, + "share": { + "status": { + "action": "Share toot" + }, + "account": { + "action": "Share user" + } + }, + "status": { + "title": "Toot actions", + "edit": { + "action": "Edit toot" + }, + "delete": { + "action": "Delete toot", + "alert": { + "title": "Confirm deleting?", + "message": "All boosts and favourites will be cleared, including all replies.", + "buttons": { + "confirm": "Confirm" + } + } + }, + "deleteEdit": { + "action": "Delete toot and repost", + "alert": { + "title": "Confirm deleting and repost?", + "message": "All boosts and favourites will be cleared, including all replies.", + "buttons": { + "confirm": "Confirm" + } + } + }, + "mute": { + "action-muted_false": "Mute toot and replies", + "action-muted_true": "Unmute toot and replies" + }, + "pin": { + "action-pinned_false": "Pin toot", + "action-pinned_true": "Unpin toot" + } + } +} \ No newline at end of file diff --git a/src/i18n/en/components/timeline.json b/src/i18n/en/components/timeline.json index caeb15ed..6a3341a1 100644 --- a/src/i18n/en/components/timeline.json +++ b/src/i18n/en/components/timeline.json @@ -123,94 +123,6 @@ "delete": { "function": "Delete direct message" } - }, - "actions": { - "accessibilityHint": "Actions for this toot, such as its posted user, toot itself", - "account": { - "heading": "About user", - "mute": { - "function": "Mute user", - "button": "Mute @{{acct}}" - }, - "block": { - "function": "Block user", - "button": "Block @{{acct}}" - }, - "reports": { - "function": "Report user", - "button": "Report @{{acct}}" - } - }, - "domain": { - "heading": "About instance", - "block": { - "function": "Block instance", - "button": "Block instance {{domain}}" - }, - "alert": { - "title": "Confirm blocking {{domain}} ?", - "message": "Mostly you can mute or block certain user.\n\nAfter blocking instance, all its content including followers from this instance will be removed!", - "buttons": { - "confirm": "Confirm blocking", - "cancel": "$t(common:buttons.cancel)" - } - } - }, - "share": { - "status": { - "heading": "Share toot", - "button": "Share link to this toot" - }, - "account": { - "heading": "Share user", - "button": "Share link to this user" - } - }, - "status": { - "heading": "About toot", - "edit": { - "function": "Edit toot", - "button": "Edit this toot" - }, - "delete": { - "function": "Delete 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)" - } - } - }, - "deleteEdit": { - "function": "Delete toot", - "button": "Delete and re-draft", - "alert": { - "title": "Confirm deleting toot?", - "message": "Are you sure to delete and re-draft this toot? All boosts and favourites will be cleared, including all replies.", - "buttons": { - "confirm": "Confirm deleting", - "cancel": "$t(common:buttons.cancel)" - } - } - }, - "mute": { - "function": "Mute toot", - "button": { - "positive": "Mute this toot and replies", - "negative": "Unmute this toot and replies" - } - }, - "pin": { - "function": "Pin", - "button": { - "positive": "Pin this toot", - "negative": "Unpin this toot" - } - } - } } }, "poll": { diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts index 5e951716..37ace006 100644 --- a/src/i18n/i18n.ts +++ b/src/i18n/i18n.ts @@ -60,8 +60,8 @@ i18n.use(initReactI18next).init({ returnEmptyString: false, saveMissing: true, - missingKeyHandler: (ns, key) => { - console.log('i18n missing: ' + ns + ' : ' + key) + missingKeyHandler: (_, ns, key) => { + console.log('i18n missing', ns, key) }, interpolation: { diff --git a/src/screens/Actions.tsx b/src/screens/Actions.tsx index a633cda1..c1cf3d7e 100644 --- a/src/screens/Actions.tsx +++ b/src/screens/Actions.tsx @@ -49,7 +49,6 @@ const ScreenActions = ({ let sameAccount = false switch (params.type) { case 'status': - console.log('media length', params.status.media_attachments.length) sameAccount = instanceAccount?.id === params.status.account.id break case 'account': @@ -117,38 +116,6 @@ const ScreenActions = ({ dismiss={dismiss} /> ) : null} - {sameAccount && params.status ? ( - - ) : null} - {!sameDomain && statusDomain ? ( - - ) : null} - {params.status.visibility !== 'direct' ? ( - - ) : null} -