forked from Ivasoft/mattermost-mobile
MM-34842 global threads options (#5630)
* fixes MM-37294 MM-37296 MM-37297 * Added conditions to check for post & thread existence * Update app/mm-redux/selectors/entities/threads.ts Co-authored-by: Kyriakos Z. <3829551+koox00@users.noreply.github.com> * type fix * Never disabling Mark All as unread * Added follow/unfollow message for not yet thread posts * Test case fix for mark all as unread enabled all the time * Removed hardcoded condition * Fixed MM-37509 * Updated snapshot for sidebar * Global thread actions init * Added options * Update post_options.js * Test cases fix * Added border bottom for each thread option * Update test case * Reverting snapshot * Updated snapshot * Moved options to screens & removed redundants translations * Reusing post_option for thread_option * Component name changed to PostOption from ThreadOption * Snapshot updated * Removed factory * Update app/screens/thread_options/index.ts Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * Update app/screens/thread_options/index.ts Co-authored-by: Elias Nahum <nahumhbl@gmail.com> Co-authored-by: Kyriakos Z. <3829551+koox00@users.noreply.github.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
committed by
GitHub
parent
5e0c75d772
commit
856d8bd05f
@@ -5,9 +5,14 @@ import React from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {Alert, FlatList} from 'react-native';
|
||||
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
import {THREAD} from '@constants/screen';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
|
||||
import ThreadList from './thread_list';
|
||||
|
||||
import type {ActionResult} from '@mm-redux/types/actions';
|
||||
import type {Post} from '@mm-redux/types/posts';
|
||||
import type {Team} from '@mm-redux/types/teams';
|
||||
import type {Theme} from '@mm-redux/types/theme';
|
||||
import type {ThreadsState, UserThread} from '@mm-redux/types/threads';
|
||||
@@ -16,10 +21,12 @@ import type {$ID} from '@mm-redux/types/utilities';
|
||||
|
||||
type Props = {
|
||||
actions: {
|
||||
getPostThread: (postId: string) => void;
|
||||
getThreads: (userId: $ID<UserProfile>, teamId: $ID<Team>, before?: $ID<UserThread>, after?: $ID<UserThread>, perPage?: number, deleted?: boolean, unread?: boolean) => Promise<ActionResult>;
|
||||
handleViewingGlobalThreadsAll: () => void;
|
||||
handleViewingGlobalThreadsUnreads: () => void;
|
||||
markAllThreadsInTeamRead: (userId: $ID<UserProfile>, teamId: $ID<Team>) => void;
|
||||
selectPost: (postId: string) => void;
|
||||
};
|
||||
allThreadIds: $ID<UserThread>[];
|
||||
intl: typeof intlShape;
|
||||
@@ -119,6 +126,23 @@ function GlobalThreadsList({actions, allThreadIds, intl, teamId, theme, threadCo
|
||||
);
|
||||
};
|
||||
|
||||
const goToThread = React.useCallback((post: Post) => {
|
||||
actions.getPostThread(post.id);
|
||||
actions.selectPost(post.id);
|
||||
const passProps = {
|
||||
channelId: post.channel_id,
|
||||
rootId: post.id,
|
||||
};
|
||||
goToScreen(THREAD, '', passProps);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
EventEmitter.on('goToThread', goToThread);
|
||||
return () => {
|
||||
EventEmitter.off('goToThread', goToThread);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ThreadList
|
||||
haveUnreads={haveUnreads}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators, Dispatch} from 'redux';
|
||||
|
||||
import {getPostThread} from '@actions/views/post';
|
||||
import {handleViewingGlobalThreadsAll, handleViewingGlobalThreadsUnreads} from '@actions/views/threads';
|
||||
import {selectPost} from '@mm-redux/actions/posts';
|
||||
import {getThreads, markAllThreadsInTeamRead} from '@mm-redux/actions/threads';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/common';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
@@ -32,10 +34,12 @@ function mapStateToProps(state: GlobalState) {
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getPostThread,
|
||||
getThreads,
|
||||
handleViewingGlobalThreadsAll,
|
||||
handleViewingGlobalThreadsUnreads,
|
||||
markAllThreadsInTeamRead,
|
||||
selectPost,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
exports[`Global Thread Item Should render thread item with unread messages dot 1`] = `
|
||||
<TouchableHighlight
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
testID="thread_item.post1.item"
|
||||
underlayColor="rgba(28,88,217,0.08)"
|
||||
@@ -185,6 +186,7 @@ exports[`Global Thread Item Should render thread item with unread messages dot 1
|
||||
|
||||
exports[`Global Thread Item Should show unread mentions count 1`] = `
|
||||
<TouchableHighlight
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
testID="thread_item.post1.item"
|
||||
underlayColor="rgba(28,88,217,0.08)"
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators, Dispatch} from 'redux';
|
||||
|
||||
import {getPost, getPostThread} from '@actions/views/post';
|
||||
import {selectPost} from '@mm-redux/actions/posts';
|
||||
import {getPost} from '@actions/views/post';
|
||||
import {getChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getPost as getPostSelector} from '@mm-redux/selectors/entities/posts';
|
||||
import {getThread} from '@mm-redux/selectors/entities/threads';
|
||||
@@ -30,8 +29,6 @@ function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
getPost,
|
||||
getPostThread,
|
||||
selectPost,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
|
||||
import * as navigationActions from '@actions/navigation';
|
||||
import {THREAD} from '@constants/screen';
|
||||
import {Preferences} from '@mm-redux/constants';
|
||||
import {Channel} from '@mm-redux/types/channels';
|
||||
import {Post} from '@mm-redux/types/posts';
|
||||
import {UserThread} from '@mm-redux/types/threads';
|
||||
import {UserProfile} from '@mm-redux/types/users';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {intl} from '@test/intl-test-helper';
|
||||
|
||||
import {ThreadItem} from './thread_item';
|
||||
@@ -99,7 +98,7 @@ describe('Global Thread Item', () => {
|
||||
});
|
||||
|
||||
test('Should goto threads when pressed on thread item', () => {
|
||||
const goToScreen = jest.spyOn(navigationActions, 'goToScreen');
|
||||
EventEmitter.emit = jest.fn();
|
||||
const wrapper = shallow(
|
||||
<ThreadItem
|
||||
{...baseProps}
|
||||
@@ -108,6 +107,6 @@ describe('Global Thread Item', () => {
|
||||
const threadItem = wrapper.find({testID: `${testIDPrefix}.item`});
|
||||
expect(threadItem.exists()).toBeTruthy();
|
||||
threadItem.simulate('press');
|
||||
expect(goToScreen).toHaveBeenCalledWith(THREAD, expect.anything(), expect.anything());
|
||||
expect(EventEmitter.emit).toHaveBeenCalledWith('goToThread', expect.anything());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,29 +3,28 @@
|
||||
|
||||
import React from 'react';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {View, Text, TouchableHighlight} from 'react-native';
|
||||
import {Keyboard, Text, TouchableHighlight, View} from 'react-native';
|
||||
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
import {showModalOverCurrentContext} from '@actions/navigation';
|
||||
import FriendlyDate from '@components/friendly_date';
|
||||
import RemoveMarkdown from '@components/remove_markdown';
|
||||
import {GLOBAL_THREADS, THREAD} from '@constants/screen';
|
||||
import {GLOBAL_THREADS} from '@constants/screen';
|
||||
import {Posts, Preferences} from '@mm-redux/constants';
|
||||
import {Channel} from '@mm-redux/types/channels';
|
||||
import {Post} from '@mm-redux/types/posts';
|
||||
import {UserThread} from '@mm-redux/types/threads';
|
||||
import {UserProfile} from '@mm-redux/types/users';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {displayUsername} from '@mm-redux/utils/user_utils';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import ThreadFooter from '../thread_footer';
|
||||
|
||||
import type {Channel} from '@mm-redux/types/channels';
|
||||
import type {Post} from '@mm-redux/types/posts';
|
||||
import type {Theme} from '@mm-redux/types/theme';
|
||||
import type {UserThread} from '@mm-redux/types/threads';
|
||||
import type {UserProfile} from '@mm-redux/types/users';
|
||||
|
||||
export type DispatchProps = {
|
||||
actions: {
|
||||
getPost: (postId: string) => void;
|
||||
getPostThread: (postId: string) => void;
|
||||
selectPost: (postId: string) => void;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,13 +65,18 @@ function ThreadItem({actions, channel, intl, post, threadId, testID, theme, thre
|
||||
const threadStarterName = displayUsername(threadStarter, Preferences.DISPLAY_PREFER_FULL_NAME);
|
||||
|
||||
const showThread = () => {
|
||||
actions.getPostThread(postItem.id);
|
||||
actions.selectPost(postItem.id);
|
||||
EventEmitter.emit('goToThread', postItem);
|
||||
};
|
||||
|
||||
const showThreadOptions = () => {
|
||||
const screen = 'GlobalThreadOptions';
|
||||
const passProps = {
|
||||
channelId: postItem.channel_id,
|
||||
rootId: postItem.id,
|
||||
rootId: post.id,
|
||||
};
|
||||
goToScreen(THREAD, '', passProps);
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
showModalOverCurrentContext(screen, passProps);
|
||||
});
|
||||
};
|
||||
|
||||
const testIDPrefix = `${testID}.${postItem?.id}`;
|
||||
@@ -134,6 +138,7 @@ function ThreadItem({actions, channel, intl, post, threadId, testID, theme, thre
|
||||
return (
|
||||
<TouchableHighlight
|
||||
underlayColor={changeOpacity(theme.buttonBg, 0.08)}
|
||||
onLongPress={showThreadOptions}
|
||||
onPress={showThread}
|
||||
testID={`${testIDPrefix}.item`}
|
||||
>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import {updateThreadLastViewedAt} from '@actions/views/threads';
|
||||
import {Client4} from '@client/rest';
|
||||
import {WebsocketEvents} from '@constants';
|
||||
import {THREAD} from '@constants/screen';
|
||||
import {GLOBAL_THREADS, THREAD} from '@constants/screen';
|
||||
import {analytics} from '@init/analytics';
|
||||
import {PostTypes, ChannelTypes, FileTypes, IntegrationTypes} from '@mm-redux/action_types';
|
||||
import {handleFollowChanged, updateThreadRead} from '@mm-redux/actions/threads';
|
||||
@@ -444,8 +444,8 @@ export function setUnreadPost(userId: string, postId: string, location: string)
|
||||
return {};
|
||||
}
|
||||
const collapsedThreadsEnabled = isCollapsedThreadsEnabled(state);
|
||||
const isUnreadFromThreadScreen = collapsedThreadsEnabled && location === THREAD;
|
||||
if (isUnreadFromThreadScreen) {
|
||||
const isUnreadFromThread = collapsedThreadsEnabled && (location === THREAD || location === GLOBAL_THREADS);
|
||||
if (isUnreadFromThread) {
|
||||
const currentTeamId = getThreadTeamId(state, postId);
|
||||
const threadId = post.root_id || post.id;
|
||||
const actions: GenericAction[] = [];
|
||||
|
||||
@@ -105,6 +105,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
||||
case 'Gallery':
|
||||
screen = require('@screens/gallery').default;
|
||||
break;
|
||||
case 'GlobalThreadOptions':
|
||||
screen = require('@screens/thread_options').default;
|
||||
break;
|
||||
case 'InteractiveDialog':
|
||||
screen = require('@screens/interactive_dialog').default;
|
||||
break;
|
||||
|
||||
@@ -200,7 +200,7 @@ class Option extends React.PureComponent<OptionProps> {
|
||||
|
||||
return (
|
||||
<PostOption
|
||||
icon={{uri: binding.icon}}
|
||||
icon={{uri: binding.icon!}}
|
||||
text={binding.label}
|
||||
onPress={this.onPress}
|
||||
theme={theme}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {
|
||||
Text,
|
||||
Platform,
|
||||
TouchableHighlight,
|
||||
TouchableNativeFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {isValidUrl} from '@utils/url';
|
||||
|
||||
export default class PostOption extends PureComponent {
|
||||
static propTypes = {
|
||||
testID: PropTypes.string,
|
||||
destructive: PropTypes.bool,
|
||||
icon: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.object,
|
||||
]).isRequired,
|
||||
onPress: PropTypes.func.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleOnPress = preventDoubleTap(() => {
|
||||
this.props.onPress();
|
||||
}, 500);
|
||||
|
||||
render() {
|
||||
const {destructive, icon, testID, text, theme} = this.props;
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const Touchable = Platform.select({
|
||||
ios: TouchableHighlight,
|
||||
android: TouchableNativeFeedback,
|
||||
});
|
||||
|
||||
const touchableProps = Platform.select({
|
||||
ios: {
|
||||
underlayColor: 'rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
android: {
|
||||
background: TouchableNativeFeedback.Ripple( //eslint-disable-line new-cap
|
||||
'rgba(0, 0, 0, 0.1)',
|
||||
false,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const imageStyle = [style.icon, destructive ? style.destructive : null];
|
||||
let image;
|
||||
let iconStyle = [style.iconContainer];
|
||||
if (typeof icon === 'object') {
|
||||
if (icon.uri) {
|
||||
imageStyle.push({width: 24, height: 24});
|
||||
image = isValidUrl(icon.uri) && (
|
||||
<FastImage
|
||||
source={icon}
|
||||
style={imageStyle}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
iconStyle = [style.noIconContainer];
|
||||
}
|
||||
} else {
|
||||
image = (
|
||||
<CompassIcon
|
||||
name={icon}
|
||||
size={24}
|
||||
style={[style.icon, destructive ? style.destructive : null]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
testID={testID}
|
||||
style={style.container}
|
||||
>
|
||||
<Touchable
|
||||
onPress={this.handleOnPress}
|
||||
{...touchableProps}
|
||||
style={style.row}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<View style={iconStyle}>
|
||||
{image}
|
||||
</View>
|
||||
<View style={style.textContainer}>
|
||||
<Text style={[style.text, destructive ? style.destructive : null]}>
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Touchable>
|
||||
<View style={style.footer}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
container: {
|
||||
height: 51,
|
||||
width: '100%',
|
||||
},
|
||||
destructive: {
|
||||
color: '#D0021B',
|
||||
},
|
||||
row: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
iconContainer: {
|
||||
alignItems: 'center',
|
||||
height: 50,
|
||||
justifyContent: 'center',
|
||||
width: 60,
|
||||
},
|
||||
noIconContainer: {
|
||||
height: 50,
|
||||
width: 18,
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
},
|
||||
textContainer: {
|
||||
justifyContent: 'center',
|
||||
flex: 1,
|
||||
height: 50,
|
||||
marginRight: 5,
|
||||
},
|
||||
text: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 16,
|
||||
lineHeight: 19,
|
||||
opacity: 0.9,
|
||||
letterSpacing: -0.45,
|
||||
},
|
||||
footer: {
|
||||
marginHorizontal: 17,
|
||||
borderBottomWidth: 0.5,
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
},
|
||||
};
|
||||
});
|
||||
153
app/screens/post_options/post_option.tsx
Normal file
153
app/screens/post_options/post_option.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Text,
|
||||
Platform,
|
||||
TouchableHighlight,
|
||||
TouchableNativeFeedback,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {Theme} from '@mm-redux/types/theme';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {isValidUrl} from '@utils/url';
|
||||
|
||||
type Props = {
|
||||
testID?: string;
|
||||
destructive?: boolean;
|
||||
icon: string | {
|
||||
uri: string;
|
||||
};
|
||||
onPress: () => void;
|
||||
text: string;
|
||||
theme: Theme;
|
||||
};
|
||||
|
||||
function PostOption({destructive, icon, onPress, testID, text, theme}: Props) {
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const handleOnPress = React.useCallback(preventDoubleTap(onPress, 500), []);
|
||||
|
||||
let Touchable: React.ElementType;
|
||||
if (Platform.OS === 'android') {
|
||||
Touchable = TouchableNativeFeedback;
|
||||
} else {
|
||||
Touchable = TouchableHighlight;
|
||||
}
|
||||
|
||||
const touchableProps = Platform.select({
|
||||
ios: {
|
||||
underlayColor: 'rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
android: {
|
||||
background: TouchableNativeFeedback.Ripple( //eslint-disable-line new-cap
|
||||
'rgba(0, 0, 0, 0.1)',
|
||||
false,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const imageStyle = [style.icon, destructive ? style.destructive : null];
|
||||
let image;
|
||||
let iconStyle = [style.iconContainer];
|
||||
if (typeof icon === 'object') {
|
||||
if (icon.uri) {
|
||||
imageStyle.push({width: 24, height: 24});
|
||||
image = isValidUrl(icon.uri) && (
|
||||
<FastImage
|
||||
source={icon}
|
||||
style={imageStyle}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
iconStyle = [style.noIconContainer];
|
||||
}
|
||||
} else {
|
||||
image = (
|
||||
<CompassIcon
|
||||
name={icon}
|
||||
size={24}
|
||||
style={[style.icon, destructive ? style.destructive : null]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
testID={testID}
|
||||
style={style.container}
|
||||
>
|
||||
<Touchable
|
||||
onPress={handleOnPress}
|
||||
{...touchableProps}
|
||||
style={style.row}
|
||||
>
|
||||
<View style={style.row}>
|
||||
<View style={iconStyle}>
|
||||
{image}
|
||||
</View>
|
||||
<View style={style.textContainer}>
|
||||
<Text style={[style.text, destructive ? style.destructive : null]}>
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Touchable>
|
||||
<View style={style.footer}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default PostOption;
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
height: 51,
|
||||
width: '100%',
|
||||
},
|
||||
destructive: {
|
||||
color: '#D0021B',
|
||||
},
|
||||
row: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
iconContainer: {
|
||||
alignItems: 'center',
|
||||
height: 50,
|
||||
justifyContent: 'center',
|
||||
width: 60,
|
||||
},
|
||||
noIconContainer: {
|
||||
height: 50,
|
||||
width: 18,
|
||||
},
|
||||
icon: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
},
|
||||
textContainer: {
|
||||
justifyContent: 'center',
|
||||
flex: 1,
|
||||
height: 50,
|
||||
marginRight: 5,
|
||||
},
|
||||
text: {
|
||||
color: theme.centerChannelColor,
|
||||
fontSize: 16,
|
||||
lineHeight: 19,
|
||||
opacity: 0.9,
|
||||
letterSpacing: -0.45,
|
||||
},
|
||||
footer: {
|
||||
marginHorizontal: 17,
|
||||
borderBottomWidth: 0.5,
|
||||
borderBottomColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,278 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ThreadOptions should match snapshot, showing all possible options 1`] = `
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
testID="global_threads.item.options"
|
||||
>
|
||||
<Connect(SlideUpPanel)
|
||||
allowStayMiddle={false}
|
||||
initialPosition={400}
|
||||
marginFromTop={200}
|
||||
onRequestClose={[Function]}
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="THREAD ACTIONS"
|
||||
id="global_threads.options.title"
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.65)",
|
||||
"fontSize": 12,
|
||||
"paddingBottom": 8,
|
||||
"paddingLeft": 16,
|
||||
"paddingTop": 16,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<PostOption
|
||||
destructive={false}
|
||||
icon="reply-outline"
|
||||
onPress={[Function]}
|
||||
testID="global_threads.options.reply.action"
|
||||
text="Reply"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<PostOption
|
||||
destructive={false}
|
||||
icon="message-minus-outline"
|
||||
onPress={[Function]}
|
||||
testID="global_threads.options.unfollow.action"
|
||||
text="Unfollow Thread"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<PostOption
|
||||
destructive={false}
|
||||
icon="globe"
|
||||
onPress={[Function]}
|
||||
testID="global_threads.options.open_in_channel.action"
|
||||
text="Open in Channel"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<PostOption
|
||||
destructive={false}
|
||||
icon="mark-as-unread"
|
||||
onPress={[Function]}
|
||||
testID="global_threads.options.mark_as_read.action"
|
||||
text="Mark as Read"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<PostOption
|
||||
destructive={false}
|
||||
icon="bookmark-outline"
|
||||
onPress={[Function]}
|
||||
testID="global_threads.options.unflag.action"
|
||||
text="Unsave"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<PostOption
|
||||
destructive={false}
|
||||
icon="link-variant"
|
||||
onPress={[Function]}
|
||||
testID="global_threads.options.permalink.action"
|
||||
text="Copy Link"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Connect(SlideUpPanel)>
|
||||
</View>
|
||||
`;
|
||||
53
app/screens/thread_options/index.ts
Normal file
53
app/screens/thread_options/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators, Dispatch} from 'redux';
|
||||
|
||||
import {showPermalink} from '@actions/views/permalink';
|
||||
import {
|
||||
flagPost,
|
||||
setUnreadPost,
|
||||
unflagPost,
|
||||
} from '@mm-redux/actions/posts';
|
||||
import {setThreadFollow, updateThreadRead} from '@mm-redux/actions/threads';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/common';
|
||||
import {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
import {getMyPreferences, getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeam, getCurrentTeamUrl} from '@mm-redux/selectors/entities/teams';
|
||||
import {getThread} from '@mm-redux/selectors/entities/threads';
|
||||
import {isPostFlagged} from '@mm-redux/utils/post_utils';
|
||||
import {getDimensions} from '@selectors/device';
|
||||
|
||||
import ThreadOptions, {OwnProps} from './thread_options';
|
||||
|
||||
import type {GlobalState} from '@mm-redux/types/store';
|
||||
|
||||
export function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
const myPreferences = getMyPreferences(state);
|
||||
return {
|
||||
...getDimensions(state),
|
||||
currentTeamName: getCurrentTeam(state)?.name,
|
||||
currentTeamUrl: getCurrentTeamUrl(state),
|
||||
currentUserId: getCurrentUserId(state),
|
||||
isFlagged: isPostFlagged(ownProps.rootId, myPreferences),
|
||||
post: getPost(state, ownProps.rootId),
|
||||
theme: getTheme(state),
|
||||
thread: getThread(state, ownProps.rootId),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
flagPost,
|
||||
setThreadFollow,
|
||||
setUnreadPost,
|
||||
showPermalink,
|
||||
unflagPost,
|
||||
updateThreadRead,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ThreadOptions);
|
||||
81
app/screens/thread_options/thread_options.test.tsx
Normal file
81
app/screens/thread_options/thread_options.test.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import {intl} from '@test/intl-test-helper';
|
||||
|
||||
import {ThreadOptions} from './thread_options';
|
||||
|
||||
import type {Post} from '@mm-redux/types/posts';
|
||||
import type {UserThread} from '@mm-redux/types/threads';
|
||||
|
||||
describe('ThreadOptions', () => {
|
||||
const actions = {
|
||||
flagPost: jest.fn(),
|
||||
setThreadFollow: jest.fn(),
|
||||
setUnreadPost: jest.fn(),
|
||||
showPermalink: jest.fn(),
|
||||
unflagPost: jest.fn(),
|
||||
updateThreadRead: jest.fn(),
|
||||
};
|
||||
|
||||
const post = {
|
||||
id: 'post_id',
|
||||
message: 'message',
|
||||
is_pinned: false,
|
||||
channel_id: 'channel_id',
|
||||
} as Post;
|
||||
|
||||
const thread = {
|
||||
id: 'post_id',
|
||||
unread_replies: 4,
|
||||
} as UserThread;
|
||||
|
||||
const baseProps = {
|
||||
actions,
|
||||
currentTeamName: 'current team name',
|
||||
currentTeamUrl: 'http://localhost:8065/team-name',
|
||||
currentUserId: 'user1',
|
||||
deviceHeight: 600,
|
||||
isFlagged: true,
|
||||
intl,
|
||||
post,
|
||||
rootId: 'post_id',
|
||||
theme: Preferences.THEMES.denim,
|
||||
thread,
|
||||
};
|
||||
|
||||
function getWrapper(props = {}) {
|
||||
return shallow(
|
||||
<ThreadOptions
|
||||
{...baseProps}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
test('should match snapshot, showing all possible options', () => {
|
||||
const wrapper = getWrapper();
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
expect(wrapper.findWhere((node) => node.key() === 'flagged')).toMatchObject({});
|
||||
expect(wrapper.findWhere((node) => node.key() === 'mark_as_read')).toMatchObject({});
|
||||
});
|
||||
|
||||
test('should show unflag option', () => {
|
||||
const wrapper = getWrapper({isFlagged: false});
|
||||
expect(wrapper.findWhere((node) => node.key() === 'unflag')).toMatchObject({});
|
||||
});
|
||||
|
||||
test('should show unflag option', () => {
|
||||
const wrapper = getWrapper({
|
||||
thread: {
|
||||
...thread,
|
||||
unread_replies: 0,
|
||||
},
|
||||
});
|
||||
expect(wrapper.findWhere((node) => node.key() === 'mark_as_unread')).toMatchObject({});
|
||||
});
|
||||
});
|
||||
285
app/screens/thread_options/thread_options.tsx
Normal file
285
app/screens/thread_options/thread_options.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
|
||||
import React from 'react';
|
||||
import {intlShape, injectIntl} from 'react-intl';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {dismissModal} from '@actions/navigation';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SlideUpPanel from '@components/slide_up_panel';
|
||||
import {BOTTOM_MARGIN} from '@components/slide_up_panel/slide_up_panel';
|
||||
import {GLOBAL_THREADS} from '@constants/screen';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import ThreadOption from '@screens/post_options/post_option';
|
||||
import {OPTION_HEIGHT, getInitialPosition} from '@screens/post_options/post_options_utils';
|
||||
import {t} from '@utils/i18n';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import type {Post} from '@mm-redux/types/posts';
|
||||
import type {Theme} from '@mm-redux/types/theme';
|
||||
import type {UserThread} from '@mm-redux/types/threads';
|
||||
import type {UserProfile} from '@mm-redux/types/users';
|
||||
import type {$ID} from '@mm-redux/types/utilities';
|
||||
|
||||
export type StateProps = {
|
||||
currentTeamName: string;
|
||||
currentTeamUrl: string;
|
||||
currentUserId: $ID<UserProfile>;
|
||||
deviceHeight: number;
|
||||
isFlagged: boolean;
|
||||
post: Post;
|
||||
theme: Theme;
|
||||
thread: UserThread;
|
||||
};
|
||||
|
||||
export type DispatchProps = {
|
||||
actions: {
|
||||
flagPost: (postId: $ID<Post>) => void;
|
||||
setThreadFollow: (currentUserId: $ID<UserProfile>, threadId: $ID<UserThread>, newState: boolean) => void;
|
||||
setUnreadPost: (currentUserId: $ID<UserProfile>, postId: $ID<Post>, location: string) => void;
|
||||
showPermalink: (currentUserId: $ID<UserProfile>, teamName: string, postId: $ID<Post>) => void;
|
||||
unflagPost: (postId: $ID<Post>) => void;
|
||||
updateThreadRead: (currentUserId: $ID<UserProfile>, threadId: $ID<UserThread>, timestamp: number) => void;
|
||||
};
|
||||
};
|
||||
|
||||
export type OwnProps = {
|
||||
rootId: $ID<Post>;
|
||||
};
|
||||
|
||||
type Props = StateProps & DispatchProps & OwnProps & {
|
||||
intl: typeof intlShape;
|
||||
};
|
||||
|
||||
function ThreadOptions({actions, currentTeamName, currentTeamUrl, currentUserId, deviceHeight, intl, isFlagged, post, theme, thread}: Props) {
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const slideUpPanelRef = React.useRef<any>();
|
||||
|
||||
const close = async (cb?: () => void) => {
|
||||
await dismissModal();
|
||||
|
||||
if (typeof cb === 'function') {
|
||||
requestAnimationFrame(cb);
|
||||
}
|
||||
};
|
||||
|
||||
const closeWithAnimation = (cb?: () => void) => {
|
||||
if (slideUpPanelRef.current) {
|
||||
slideUpPanelRef.current.closeWithAnimation(cb);
|
||||
} else {
|
||||
close(cb);
|
||||
}
|
||||
};
|
||||
|
||||
const getOption = (key: string, icon: string, message: Record<string, any>, onPress: () => void, destructive = false) => {
|
||||
const testID = `global_threads.options.${key}.action`;
|
||||
return (
|
||||
<ThreadOption
|
||||
testID={testID}
|
||||
key={key}
|
||||
icon={icon}
|
||||
text={intl.formatMessage(message)}
|
||||
onPress={onPress}
|
||||
destructive={destructive}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
//
|
||||
// Option: Reply
|
||||
//
|
||||
const handleReply = () => {
|
||||
closeWithAnimation(() => {
|
||||
EventEmitter.emit('goToThread', post);
|
||||
});
|
||||
};
|
||||
|
||||
const getReplyOption = () => {
|
||||
const key = 'reply';
|
||||
const icon = 'reply-outline';
|
||||
const message = {id: t('mobile.post_info.reply'), defaultMessage: 'Reply'};
|
||||
const onPress = handleReply;
|
||||
return getOption(key, icon, message, onPress);
|
||||
};
|
||||
|
||||
//
|
||||
// Option: Unfollow thread
|
||||
//
|
||||
const handleUnfollowThread = () => {
|
||||
closeWithAnimation(() => {
|
||||
actions.setThreadFollow(currentUserId, thread.id, false);
|
||||
});
|
||||
};
|
||||
|
||||
const getUnfollowThread = () => {
|
||||
const key = 'unfollow';
|
||||
const icon = 'message-minus-outline';
|
||||
const message = {id: t('global_threads.options.unfollow'), defaultMessage: 'Unfollow Thread'};
|
||||
const onPress = handleUnfollowThread;
|
||||
return getOption(key, icon, message, onPress);
|
||||
};
|
||||
|
||||
//
|
||||
// Option: Open in Channel
|
||||
//
|
||||
const handleOpenInChannel = () => {
|
||||
closeWithAnimation(() => {
|
||||
actions.showPermalink(intl, currentTeamName, post.id);
|
||||
});
|
||||
};
|
||||
|
||||
const getOpenInChannel = () => {
|
||||
const key = 'open_in_channel';
|
||||
const icon = 'globe';
|
||||
const message = {id: t('global_threads.options.open_in_channel'), defaultMessage: 'Open in Channel'};
|
||||
const onPress = handleOpenInChannel;
|
||||
return getOption(key, icon, message, onPress);
|
||||
};
|
||||
|
||||
//
|
||||
// Option: Mark as Read
|
||||
//
|
||||
const handleMarkAsRead = () => {
|
||||
closeWithAnimation(() => {
|
||||
actions.updateThreadRead(
|
||||
currentUserId,
|
||||
post.id,
|
||||
Date.now(),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleMarkAsUnread = () => {
|
||||
closeWithAnimation(() => {
|
||||
actions.setUnreadPost(currentUserId, post.id, GLOBAL_THREADS);
|
||||
});
|
||||
};
|
||||
|
||||
const getMarkAsUnreadOption = () => {
|
||||
const icon = 'mark-as-unread';
|
||||
let key;
|
||||
let message;
|
||||
let onPress;
|
||||
if (thread.unread_replies) {
|
||||
key = 'mark_as_read';
|
||||
message = {id: t('global_threads.options.mark_as_read'), defaultMessage: 'Mark as Read'};
|
||||
onPress = handleMarkAsRead;
|
||||
} else {
|
||||
key = 'mark_as_unread';
|
||||
message = {id: t('mobile.post_info.mark_unread'), defaultMessage: 'Mark as Unread'};
|
||||
onPress = handleMarkAsUnread;
|
||||
}
|
||||
return getOption(key, icon, message, onPress);
|
||||
};
|
||||
|
||||
//
|
||||
// Option: Flag/Unflag
|
||||
//
|
||||
const handleFlagPost = () => {
|
||||
closeWithAnimation(() => {
|
||||
actions.flagPost(post.id);
|
||||
});
|
||||
};
|
||||
|
||||
const handleUnflagPost = () => {
|
||||
closeWithAnimation(() => {
|
||||
actions.unflagPost(post.id);
|
||||
});
|
||||
};
|
||||
|
||||
const getFlagOption = () => {
|
||||
let key;
|
||||
let message;
|
||||
let onPress;
|
||||
const icon = 'bookmark-outline';
|
||||
|
||||
if (isFlagged) {
|
||||
key = 'unflag';
|
||||
message = {id: t('mobile.post_info.unflag'), defaultMessage: 'Unsave'};
|
||||
onPress = handleUnflagPost;
|
||||
} else {
|
||||
key = 'flagged';
|
||||
message = {id: t('mobile.post_info.flag'), defaultMessage: 'Save'};
|
||||
onPress = handleFlagPost;
|
||||
}
|
||||
|
||||
return getOption(key, icon, message, onPress);
|
||||
};
|
||||
|
||||
//
|
||||
// Option: Copy Link
|
||||
//
|
||||
const handleCopyPermalink = () => {
|
||||
closeWithAnimation(() => {
|
||||
const permalink = `${currentTeamUrl}/pl/${post.id}`;
|
||||
Clipboard.setString(permalink);
|
||||
});
|
||||
};
|
||||
|
||||
const getCopyPermalink = () => {
|
||||
const key = 'permalink';
|
||||
const icon = 'link-variant';
|
||||
const message = {id: t('get_post_link_modal.title'), defaultMessage: 'Copy Link'};
|
||||
const onPress = handleCopyPermalink;
|
||||
return getOption(key, icon, message, onPress);
|
||||
};
|
||||
|
||||
const options = [
|
||||
getReplyOption(),
|
||||
getUnfollowThread(),
|
||||
getOpenInChannel(),
|
||||
getMarkAsUnreadOption(),
|
||||
getFlagOption(),
|
||||
getCopyPermalink(),
|
||||
].filter((option) => option !== null);
|
||||
|
||||
const marginFromTop = deviceHeight - BOTTOM_MARGIN - ((options.length + 2) * OPTION_HEIGHT);
|
||||
const initialPosition = getInitialPosition(deviceHeight, marginFromTop);
|
||||
|
||||
return (
|
||||
<View
|
||||
testID='global_threads.item.options'
|
||||
style={style.container}
|
||||
>
|
||||
<SlideUpPanel
|
||||
allowStayMiddle={false}
|
||||
marginFromTop={marginFromTop > 0 ? marginFromTop : 0}
|
||||
onRequestClose={close}
|
||||
initialPosition={initialPosition}
|
||||
key={marginFromTop}
|
||||
ref={slideUpPanelRef}
|
||||
theme={theme}
|
||||
>
|
||||
<FormattedText
|
||||
id='global_threads.options.title'
|
||||
defaultMessage='THREAD ACTIONS'
|
||||
style={style.title}
|
||||
/>
|
||||
{options}
|
||||
</SlideUpPanel>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export {ThreadOptions};
|
||||
export default injectIntl(ThreadOptions);
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
title: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.65),
|
||||
fontSize: 12,
|
||||
paddingLeft: 16,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 8,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -199,6 +199,10 @@
|
||||
"global_threads.markAllRead.markRead": "Mark read",
|
||||
"global_threads.markAllRead.message": "This will clear any unread status for all of your threads shown here",
|
||||
"global_threads.markAllRead.title": "Are you sure you want to mark all threads as read?",
|
||||
"global_threads.options.mark_as_read": "Mark as Read",
|
||||
"global_threads.options.open_in_channel": "Open in Channel",
|
||||
"global_threads.options.title": "THREAD ACTIONS",
|
||||
"global_threads.options.unfollow": "Unfollow Thread",
|
||||
"global_threads.unreads": "Unreads",
|
||||
"integrations.add": "Add",
|
||||
"intro_messages.anyMember": " Any member can join and read this channel.",
|
||||
|
||||
Reference in New Issue
Block a user