[NEW] Slash commands (#886)
* setup database * added getSlashCommands to loginSucess * added slash command first prototype * added preview feture for commands that have preview enabled * address requested changes * added preview options for other types of files too * address changes * done requested changes * undone un-nessary changes * done suggested changes * fixed lint * done requested changes * fixed lint * fix e2e
This commit is contained in:
parent
d68eb01b82
commit
82afb63327
|
@ -0,0 +1,47 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { TouchableOpacity, ActivityIndicator } from 'react-native';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
import { COLOR_PRIMARY } from '../../constants/colors';
|
||||||
|
|
||||||
|
export default class CommandPreview extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
onPress: PropTypes.func,
|
||||||
|
item: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { loading: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onPress, item } = this.props;
|
||||||
|
const { loading } = this.state;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.commandPreview}
|
||||||
|
onPress={() => onPress(item)}
|
||||||
|
testID={`command-preview-item${ item.id }`}
|
||||||
|
>
|
||||||
|
{item.type === 'image'
|
||||||
|
? (
|
||||||
|
<FastImage
|
||||||
|
style={styles.commandPreviewImage}
|
||||||
|
source={{ uri: item.value }}
|
||||||
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
|
onLoadStart={() => this.setState({ loading: true })}
|
||||||
|
onLoad={() => this.setState({ loading: false })}
|
||||||
|
>
|
||||||
|
{ loading ? <ActivityIndicator /> : null }
|
||||||
|
</FastImage>
|
||||||
|
)
|
||||||
|
: <CustomIcon name='file-generic' size={36} color={COLOR_PRIMARY} />
|
||||||
|
}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
View, TextInput, FlatList, Text, TouchableOpacity, Alert
|
View, TextInput, FlatList, Text, TouchableOpacity, Alert, ScrollView
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { emojify } from 'react-emojione';
|
import { emojify } from 'react-emojione';
|
||||||
|
@ -32,9 +32,12 @@ import { COLOR_TEXT_DESCRIPTION } from '../../constants/colors';
|
||||||
import LeftButtons from './LeftButtons';
|
import LeftButtons from './LeftButtons';
|
||||||
import RightButtons from './RightButtons';
|
import RightButtons from './RightButtons';
|
||||||
import { isAndroid } from '../../utils/deviceInfo';
|
import { isAndroid } from '../../utils/deviceInfo';
|
||||||
|
import CommandPreview from './CommandPreview';
|
||||||
|
|
||||||
const MENTIONS_TRACKING_TYPE_USERS = '@';
|
const MENTIONS_TRACKING_TYPE_USERS = '@';
|
||||||
const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
|
const MENTIONS_TRACKING_TYPE_EMOJIS = ':';
|
||||||
|
const MENTIONS_TRACKING_TYPE_COMMANDS = '/';
|
||||||
|
const MENTIONS_COUNT_TO_DISPLAY = 4;
|
||||||
|
|
||||||
const onlyUnique = function onlyUnique(value, index, self) {
|
const onlyUnique = function onlyUnique(value, index, self) {
|
||||||
return self.indexOf(({ _id }) => value._id === _id) === index;
|
return self.indexOf(({ _id }) => value._id === _id) === index;
|
||||||
|
@ -93,8 +96,11 @@ class MessageBox extends Component {
|
||||||
trackingType: '',
|
trackingType: '',
|
||||||
file: {
|
file: {
|
||||||
isVisible: false
|
isVisible: false
|
||||||
}
|
},
|
||||||
|
commandPreview: []
|
||||||
};
|
};
|
||||||
|
this.showCommandPreview = false;
|
||||||
|
this.commands = [];
|
||||||
this.users = [];
|
this.users = [];
|
||||||
this.rooms = [];
|
this.rooms = [];
|
||||||
this.emojis = [];
|
this.emojis = [];
|
||||||
|
@ -147,7 +153,7 @@ class MessageBox extends Component {
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const {
|
const {
|
||||||
showEmojiKeyboard, showSend, recording, mentions, file
|
showEmojiKeyboard, showSend, recording, mentions, file, commandPreview
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
roomType, replying, editing, isFocused
|
roomType, replying, editing, isFocused
|
||||||
|
@ -176,6 +182,9 @@ class MessageBox extends Component {
|
||||||
if (!equal(nextState.mentions, mentions)) {
|
if (!equal(nextState.mentions, mentions)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!equal(nextState.commandPreview, commandPreview)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (!equal(nextState.file, file)) {
|
if (!equal(nextState.file, file)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -187,20 +196,36 @@ class MessageBox extends Component {
|
||||||
this.setShowSend(!isTextEmpty);
|
this.setShowSend(!isTextEmpty);
|
||||||
this.handleTyping(!isTextEmpty);
|
this.handleTyping(!isTextEmpty);
|
||||||
this.setInput(text);
|
this.setInput(text);
|
||||||
|
// matches if their is text that stats with '/' and group the command and params so we can use it "/command params"
|
||||||
|
const slashCommand = text.match(/^\/([a-z0-9._-]+) (.+)/im);
|
||||||
|
if (slashCommand) {
|
||||||
|
const [, name, params] = slashCommand;
|
||||||
|
const command = database.objects('slashCommand').filtered('command == $0', name);
|
||||||
|
if (command && command[0] && command[0].providesPreview) {
|
||||||
|
return this.setCommandPreview(name, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isTextEmpty) {
|
if (!isTextEmpty) {
|
||||||
const { start, end } = this.component._lastNativeSelection;
|
const { start, end } = this.component._lastNativeSelection;
|
||||||
const cursor = Math.max(start, end);
|
const cursor = Math.max(start, end);
|
||||||
const lastNativeText = this.component._lastNativeText;
|
const lastNativeText = this.component._lastNativeText;
|
||||||
const regexp = /(#|@|:)([a-z0-9._-]+)$/im;
|
// matches if text either starts with '/' or have (@,#,:) then it groups whatever comes next of mention type
|
||||||
|
const regexp = /(#|@|:|^\/)([a-z0-9._-]+)$/im;
|
||||||
const result = lastNativeText.substr(0, cursor).match(regexp);
|
const result = lastNativeText.substr(0, cursor).match(regexp);
|
||||||
|
this.showCommandPreview = false;
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
const slash = lastNativeText.match(/^\/$/); // matches only '/' in input
|
||||||
|
if (slash) {
|
||||||
|
return this.identifyMentionKeyword('', MENTIONS_TRACKING_TYPE_COMMANDS);
|
||||||
|
}
|
||||||
return this.stopTrackingMention();
|
return this.stopTrackingMention();
|
||||||
}
|
}
|
||||||
const [, lastChar, name] = result;
|
const [, lastChar, name] = result;
|
||||||
this.identifyMentionKeyword(name, lastChar);
|
this.identifyMentionKeyword(name, lastChar);
|
||||||
} else {
|
} else {
|
||||||
this.stopTrackingMention();
|
this.stopTrackingMention();
|
||||||
|
this.showCommandPreview = false;
|
||||||
}
|
}
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
|
@ -220,13 +245,32 @@ class MessageBox extends Component {
|
||||||
const result = msg.substr(0, cursor).replace(regexp, '');
|
const result = msg.substr(0, cursor).replace(regexp, '');
|
||||||
const mentionName = trackingType === MENTIONS_TRACKING_TYPE_EMOJIS
|
const mentionName = trackingType === MENTIONS_TRACKING_TYPE_EMOJIS
|
||||||
? `${ item.name || item }:`
|
? `${ item.name || item }:`
|
||||||
: (item.username || item.name);
|
: (item.username || item.name || item.command);
|
||||||
const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
|
const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`;
|
||||||
|
if ((trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) && item.providesPreview) {
|
||||||
|
this.showCommandPreview = true;
|
||||||
|
}
|
||||||
this.setInput(text);
|
this.setInput(text);
|
||||||
this.focus();
|
this.focus();
|
||||||
requestAnimationFrame(() => this.stopTrackingMention());
|
requestAnimationFrame(() => this.stopTrackingMention());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPressCommandPreview = (item) => {
|
||||||
|
const { rid } = this.props;
|
||||||
|
const { text } = this;
|
||||||
|
const command = text.substr(0, text.indexOf(' ')).slice(1);
|
||||||
|
const params = text.substr(text.indexOf(' ') + 1) || 'params';
|
||||||
|
this.showCommandPreview = false;
|
||||||
|
this.setState({ commandPreview: [] });
|
||||||
|
this.stopTrackingMention();
|
||||||
|
this.clearInput();
|
||||||
|
try {
|
||||||
|
RocketChat.executeCommandPreview(command, params, rid, item);
|
||||||
|
} catch (e) {
|
||||||
|
log('onPressCommandPreview', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onEmojiSelected = (keyboardId, params) => {
|
onEmojiSelected = (keyboardId, params) => {
|
||||||
const { text } = this;
|
const { text } = this;
|
||||||
const { emoji } = params;
|
const { emoji } = params;
|
||||||
|
@ -301,7 +345,7 @@ class MessageBox extends Component {
|
||||||
console.warn('spotlight canceled');
|
console.warn('spotlight canceled');
|
||||||
} finally {
|
} finally {
|
||||||
delete this.oldPromise;
|
delete this.oldPromise;
|
||||||
this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword).slice();
|
this.users = database.objects('users').filtered('username CONTAINS[c] $0', keyword).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
this.getFixedMentions(keyword);
|
this.getFixedMentions(keyword);
|
||||||
this.setState({ mentions: this.users });
|
this.setState({ mentions: this.users });
|
||||||
}
|
}
|
||||||
|
@ -351,13 +395,18 @@ class MessageBox extends Component {
|
||||||
|
|
||||||
getEmojis = (keyword) => {
|
getEmojis = (keyword) => {
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
this.customEmojis = database.objects('customEmojis').filtered('name CONTAINS[c] $0', keyword).slice(0, 4);
|
this.customEmojis = database.objects('customEmojis').filtered('name CONTAINS[c] $0', keyword).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
this.emojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, 4);
|
this.emojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
const mergedEmojis = [...this.customEmojis, ...this.emojis];
|
const mergedEmojis = [...this.customEmojis, ...this.emojis].slice(0, MENTIONS_COUNT_TO_DISPLAY);
|
||||||
this.setState({ mentions: mergedEmojis });
|
this.setState({ mentions: mergedEmojis });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSlashCommands = (keyword) => {
|
||||||
|
this.commands = database.objects('slashCommand').filtered('command CONTAINS[c] $0', keyword);
|
||||||
|
this.setState({ mentions: this.commands });
|
||||||
|
}
|
||||||
|
|
||||||
focus = () => {
|
focus = () => {
|
||||||
if (this.component && this.component.focus) {
|
if (this.component && this.component.focus) {
|
||||||
this.component.focus();
|
this.component.focus();
|
||||||
|
@ -385,6 +434,18 @@ class MessageBox extends Component {
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCommandPreview = async(command, params) => {
|
||||||
|
const { rid } = this.props;
|
||||||
|
try {
|
||||||
|
const { preview } = await RocketChat.getCommandPreview(command, rid, params);
|
||||||
|
this.showCommandPreview = true;
|
||||||
|
this.setState({ commandPreview: preview.items });
|
||||||
|
} catch (e) {
|
||||||
|
this.showCommandPreview = false;
|
||||||
|
log('command Preview', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setInput = (text) => {
|
setInput = (text) => {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
if (this.component && this.component.setNativeProps) {
|
if (this.component && this.component.setNativeProps) {
|
||||||
|
@ -505,7 +566,7 @@ class MessageBox extends Component {
|
||||||
|
|
||||||
submit = async() => {
|
submit = async() => {
|
||||||
const {
|
const {
|
||||||
message: editingMessage, editRequest, onSubmit
|
message: editingMessage, editRequest, onSubmit, rid: roomId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const message = this.text;
|
const message = this.text;
|
||||||
|
|
||||||
|
@ -521,6 +582,22 @@ class MessageBox extends Component {
|
||||||
editing, replying
|
editing, replying
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
// Slash command
|
||||||
|
|
||||||
|
if (message[0] === MENTIONS_TRACKING_TYPE_COMMANDS) {
|
||||||
|
const command = message.replace(/ .*/, '').slice(1);
|
||||||
|
const slashCommand = database.objects('slashCommand').filtered('command CONTAINS[c] $0', command);
|
||||||
|
if (slashCommand.length > 0) {
|
||||||
|
try {
|
||||||
|
const messageWithoutCommand = message.substr(message.indexOf(' ') + 1);
|
||||||
|
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand);
|
||||||
|
} catch (e) {
|
||||||
|
log('slashCommand', e);
|
||||||
|
}
|
||||||
|
this.clearInput();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Edit
|
// Edit
|
||||||
if (editing) {
|
if (editing) {
|
||||||
const { _id, rid } = editingMessage;
|
const { _id, rid } = editingMessage;
|
||||||
|
@ -561,6 +638,8 @@ class MessageBox extends Component {
|
||||||
this.getUsers(keyword);
|
this.getUsers(keyword);
|
||||||
} else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
|
} else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) {
|
||||||
this.getEmojis(keyword);
|
this.getEmojis(keyword);
|
||||||
|
} else if (type === MENTIONS_TRACKING_TYPE_COMMANDS) {
|
||||||
|
this.getSlashCommands(keyword);
|
||||||
} else {
|
} else {
|
||||||
this.getRooms(keyword);
|
this.getRooms(keyword);
|
||||||
}
|
}
|
||||||
|
@ -579,15 +658,16 @@ class MessageBox extends Component {
|
||||||
if (!trackingType) {
|
if (!trackingType) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
mentions: [],
|
mentions: [],
|
||||||
trackingType: ''
|
trackingType: '',
|
||||||
|
commandPreview: []
|
||||||
});
|
});
|
||||||
this.users = [];
|
this.users = [];
|
||||||
this.rooms = [];
|
this.rooms = [];
|
||||||
this.customEmojis = [];
|
this.customEmojis = [];
|
||||||
this.emojis = [];
|
this.emojis = [];
|
||||||
|
this.commands = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFixedMentionItem = item => (
|
renderFixedMentionItem = item => (
|
||||||
|
@ -623,31 +703,55 @@ class MessageBox extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMentionItem = (item) => {
|
renderMentionItem = ({ item }) => {
|
||||||
const { trackingType } = this.state;
|
const { trackingType } = this.state;
|
||||||
const { baseUrl, user } = this.props;
|
const { baseUrl, user } = this.props;
|
||||||
|
|
||||||
if (item.username === 'all' || item.username === 'here') {
|
if (item.username === 'all' || item.username === 'here') {
|
||||||
return this.renderFixedMentionItem(item);
|
return this.renderFixedMentionItem(item);
|
||||||
}
|
}
|
||||||
|
const defineTestID = (type) => {
|
||||||
|
switch (type) {
|
||||||
|
case MENTIONS_TRACKING_TYPE_EMOJIS:
|
||||||
|
return `mention-item-${ item.name || item }`;
|
||||||
|
case MENTIONS_TRACKING_TYPE_COMMANDS:
|
||||||
|
return `mention-item-${ item.command || item }`;
|
||||||
|
default:
|
||||||
|
return `mention-item-${ item.username || item.name || item }`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testID = defineTestID(trackingType);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.mentionItem}
|
style={styles.mentionItem}
|
||||||
onPress={() => this.onPressMention(item)}
|
onPress={() => this.onPressMention(item)}
|
||||||
testID={`mention-item-${ trackingType === MENTIONS_TRACKING_TYPE_EMOJIS ? item.name || item : item.username || item.name }`}
|
testID={testID}
|
||||||
>
|
>
|
||||||
{trackingType === MENTIONS_TRACKING_TYPE_EMOJIS
|
|
||||||
? (
|
{(() => {
|
||||||
|
switch (trackingType) {
|
||||||
|
case MENTIONS_TRACKING_TYPE_EMOJIS:
|
||||||
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{this.renderMentionEmoji(item)}
|
{this.renderMentionEmoji(item)}
|
||||||
<Text key='mention-item-name' style={styles.mentionText}>:{ item.name || item }:</Text>
|
<Text key='mention-item-name' style={styles.mentionText}>:{ item.name || item }:</Text>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
: (
|
case MENTIONS_TRACKING_TYPE_COMMANDS:
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Text key='mention-item-command' style={styles.slash}>/</Text>
|
||||||
|
<Text key='mention-item-param'>{ item.command}</Text>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Avatar
|
<Avatar
|
||||||
key='mention-item-avatar'
|
key='mention-item-avatar'
|
||||||
style={{ margin: 8 }}
|
style={styles.avatar}
|
||||||
text={item.username || item.name}
|
text={item.username || item.name}
|
||||||
size={30}
|
size={30}
|
||||||
type={item.username ? 'd' : 'c'}
|
type={item.username ? 'd' : 'c'}
|
||||||
|
@ -655,9 +759,11 @@ class MessageBox extends Component {
|
||||||
userId={user.id}
|
userId={user.id}
|
||||||
token={user.token}
|
token={user.token}
|
||||||
/>
|
/>
|
||||||
<Text key='mention-item-name' style={styles.mentionText}>{ item.username || item.name }</Text>
|
<Text key='mention-item-name' style={styles.mentionText}>{ item.username || item.name || item }</Text>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
|
}
|
||||||
|
})()
|
||||||
}
|
}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
@ -669,17 +775,45 @@ class MessageBox extends Component {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View testID='messagebox-container'>
|
<ScrollView
|
||||||
|
testID='messagebox-container'
|
||||||
|
style={styles.scrollViewMention}
|
||||||
|
keyboardShouldPersistTaps='always'
|
||||||
|
>
|
||||||
<FlatList
|
<FlatList
|
||||||
style={styles.mentionList}
|
style={styles.mentionList}
|
||||||
data={mentions}
|
data={mentions}
|
||||||
renderItem={({ item }) => this.renderMentionItem(item)}
|
renderItem={this.renderMentionItem}
|
||||||
keyExtractor={item => item._id || item.username || item}
|
keyExtractor={item => item._id || item.username || item.command || item}
|
||||||
keyboardShouldPersistTaps='always'
|
keyboardShouldPersistTaps='always'
|
||||||
/>
|
/>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderCommandPreviewItem = ({ item }) => (
|
||||||
|
<CommandPreview item={item} onPress={this.onPressCommandPreview} />
|
||||||
|
);
|
||||||
|
|
||||||
|
renderCommandPreview = () => {
|
||||||
|
const { commandPreview } = this.state;
|
||||||
|
if (!this.showCommandPreview) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View key='commandbox-container' testID='commandbox-container'>
|
||||||
|
<FlatList
|
||||||
|
style={styles.mentionList}
|
||||||
|
data={commandPreview}
|
||||||
|
renderItem={this.renderCommandPreviewItem}
|
||||||
|
keyExtractor={item => item.id}
|
||||||
|
keyboardShouldPersistTaps='always'
|
||||||
|
horizontal
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
renderReplyPreview = () => {
|
renderReplyPreview = () => {
|
||||||
const {
|
const {
|
||||||
|
@ -700,6 +834,7 @@ class MessageBox extends Component {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
{this.renderCommandPreview()}
|
||||||
{this.renderMentions()}
|
{this.renderMentions()}
|
||||||
<View style={styles.composer} key='messagebox'>
|
<View style={styles.composer} key='messagebox'>
|
||||||
{this.renderReplyPreview()}
|
{this.renderReplyPreview()}
|
||||||
|
|
|
@ -3,10 +3,11 @@ import { StyleSheet } from 'react-native';
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
import { isIOS } from '../../utils/deviceInfo';
|
||||||
import sharedStyles from '../../views/Styles';
|
import sharedStyles from '../../views/Styles';
|
||||||
import {
|
import {
|
||||||
COLOR_BORDER, COLOR_SEPARATOR, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE
|
COLOR_BORDER, COLOR_SEPARATOR, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_PRIMARY
|
||||||
} from '../../constants/colors';
|
} from '../../constants/colors';
|
||||||
|
|
||||||
const MENTION_HEIGHT = 50;
|
const MENTION_HEIGHT = 50;
|
||||||
|
const SCROLLVIEW_MENTION_HEIGHT = 4 * MENTION_HEIGHT;
|
||||||
|
|
||||||
export default StyleSheet.create({
|
export default StyleSheet.create({
|
||||||
textBox: {
|
textBox: {
|
||||||
|
@ -100,5 +101,35 @@ export default StyleSheet.create({
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0
|
right: 0
|
||||||
|
},
|
||||||
|
slash: {
|
||||||
|
color: COLOR_PRIMARY,
|
||||||
|
backgroundColor: COLOR_BORDER,
|
||||||
|
height: 30,
|
||||||
|
width: 30,
|
||||||
|
padding: 5,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
marginHorizontal: 10,
|
||||||
|
borderRadius: 2
|
||||||
|
},
|
||||||
|
commandPreviewImage: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: 3,
|
||||||
|
width: 120,
|
||||||
|
height: 80,
|
||||||
|
borderRadius: 4
|
||||||
|
},
|
||||||
|
commandPreview: {
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
||||||
|
height: 100,
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
margin: 8
|
||||||
|
},
|
||||||
|
scrollViewMention: {
|
||||||
|
maxHeight: SCROLLVIEW_MENTION_HEIGHT
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { InteractionManager } from 'react-native';
|
||||||
|
|
||||||
|
import database from '../realm';
|
||||||
|
import log from '../../utils/log';
|
||||||
|
|
||||||
|
export default async function() {
|
||||||
|
try {
|
||||||
|
// RC 0.60.2
|
||||||
|
const result = await this.sdk.get('commands.list');
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
return log('getSlashCommand fetch', result);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { commands } = result;
|
||||||
|
|
||||||
|
if (commands && commands.length) {
|
||||||
|
InteractionManager.runAfterInteractions(() => {
|
||||||
|
database.write(() => commands.forEach((command) => {
|
||||||
|
try {
|
||||||
|
database.create('slashCommand', command, true);
|
||||||
|
} catch (e) {
|
||||||
|
log('get_slash_command', e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('err_get_slash_command', e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -273,6 +273,18 @@ const frequentlyUsedEmojiSchema = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const slashCommandSchema = {
|
||||||
|
name: 'slashCommand',
|
||||||
|
primaryKey: 'command',
|
||||||
|
properties: {
|
||||||
|
command: 'string',
|
||||||
|
params: { type: 'string', optional: true },
|
||||||
|
description: { type: 'string', optional: true },
|
||||||
|
clientOnly: { type: 'bool', optional: true },
|
||||||
|
providesPreview: { type: 'bool', optional: true }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const customEmojisSchema = {
|
const customEmojisSchema = {
|
||||||
name: 'customEmojis',
|
name: 'customEmojis',
|
||||||
primaryKey: '_id',
|
primaryKey: '_id',
|
||||||
|
@ -347,7 +359,8 @@ const schema = [
|
||||||
customEmojisSchema,
|
customEmojisSchema,
|
||||||
messagesReactionsSchema,
|
messagesReactionsSchema,
|
||||||
rolesSchema,
|
rolesSchema,
|
||||||
uploadsSchema
|
uploadsSchema,
|
||||||
|
slashCommandSchema
|
||||||
];
|
];
|
||||||
|
|
||||||
const inMemorySchema = [usersTypingSchema, activeUsersSchema];
|
const inMemorySchema = [usersTypingSchema, activeUsersSchema];
|
||||||
|
|
|
@ -25,6 +25,7 @@ import getSettings from './methods/getSettings';
|
||||||
import getRooms from './methods/getRooms';
|
import getRooms from './methods/getRooms';
|
||||||
import getPermissions from './methods/getPermissions';
|
import getPermissions from './methods/getPermissions';
|
||||||
import getCustomEmoji from './methods/getCustomEmojis';
|
import getCustomEmoji from './methods/getCustomEmojis';
|
||||||
|
import getSlashCommands from './methods/getSlashCommands';
|
||||||
import getRoles from './methods/getRoles';
|
import getRoles from './methods/getRoles';
|
||||||
import canOpenRoom from './methods/canOpenRoom';
|
import canOpenRoom from './methods/canOpenRoom';
|
||||||
|
|
||||||
|
@ -153,6 +154,7 @@ const RocketChat = {
|
||||||
this.getPermissions();
|
this.getPermissions();
|
||||||
this.getCustomEmoji();
|
this.getCustomEmoji();
|
||||||
this.getRoles();
|
this.getRoles();
|
||||||
|
this.getSlashCommands();
|
||||||
this.registerPushToken().catch(e => console.log(e));
|
this.registerPushToken().catch(e => console.log(e));
|
||||||
this.getUserPresence();
|
this.getUserPresence();
|
||||||
},
|
},
|
||||||
|
@ -467,6 +469,7 @@ const RocketChat = {
|
||||||
getSettings,
|
getSettings,
|
||||||
getPermissions,
|
getPermissions,
|
||||||
getCustomEmoji,
|
getCustomEmoji,
|
||||||
|
getSlashCommands,
|
||||||
getRoles,
|
getRoles,
|
||||||
parseSettings: settings => settings.reduce((ret, item) => {
|
parseSettings: settings => settings.reduce((ret, item) => {
|
||||||
ret[item._id] = item[defaultSettings[item._id].type];
|
ret[item._id] = item[defaultSettings[item._id].type];
|
||||||
|
@ -803,6 +806,24 @@ const RocketChat = {
|
||||||
rid, updatedSince
|
rid, updatedSince
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
runSlashCommand(command, roomId, params) {
|
||||||
|
// RC 0.60.2
|
||||||
|
return this.sdk.post('commands.run', {
|
||||||
|
command, roomId, params
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getCommandPreview(command, roomId, params) {
|
||||||
|
// RC 0.65.0
|
||||||
|
return this.sdk.get('commands.preview', {
|
||||||
|
command, roomId, params
|
||||||
|
});
|
||||||
|
},
|
||||||
|
executeCommandPreview(command, params, roomId, previewItem) {
|
||||||
|
// RC 0.65.0
|
||||||
|
return this.sdk.post('commands.preview', {
|
||||||
|
command, params, roomId, previewItem
|
||||||
|
});
|
||||||
|
},
|
||||||
async getUserPresence() {
|
async getUserPresence() {
|
||||||
const serverVersion = reduxStore.getState().server.version;
|
const serverVersion = reduxStore.getState().server.version;
|
||||||
|
|
||||||
|
|
|
@ -158,6 +158,31 @@ describe('Room screen', () => {
|
||||||
await expect(element(by.id('messagebox-input'))).toHaveText('#general ');
|
await expect(element(by.id('messagebox-input'))).toHaveText('#general ');
|
||||||
await element(by.id('messagebox-input')).clearText();
|
await element(by.id('messagebox-input')).clearText();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show and tap on slash command autocomplete and send slash command', async() => {
|
||||||
|
await element(by.id('messagebox-input')).tap();
|
||||||
|
await element(by.id('messagebox-input')).typeText('/');
|
||||||
|
await waitFor(element(by.id('messagebox-container'))).toBeVisible().withTimeout(10000);
|
||||||
|
await expect(element(by.id('messagebox-container'))).toBeVisible();
|
||||||
|
await element(by.id('mention-item-shrug')).tap();
|
||||||
|
await expect(element(by.id('messagebox-input'))).toHaveText('/shrug ');
|
||||||
|
await element(by.id('messagebox-input')).typeText('joy'); // workaround for number keyboard
|
||||||
|
await element(by.id('messagebox-send-message')).tap();
|
||||||
|
await waitFor(element(by.text(`joy ¯\_(ツ)_/¯`))).toBeVisible().withTimeout(60000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show command Preview', async() => {
|
||||||
|
await element(by.id('messagebox-input')).tap();
|
||||||
|
await element(by.id('messagebox-input')).replaceText('/giphy');
|
||||||
|
await waitFor(element(by.id('messagebox-container'))).toBeVisible().withTimeout(10000);
|
||||||
|
await expect(element(by.id('messagebox-container'))).toBeVisible();
|
||||||
|
await element(by.id('mention-item-giphy')).tap();
|
||||||
|
await expect(element(by.id('messagebox-input'))).toHaveText('/giphy ');
|
||||||
|
await element(by.id('messagebox-input')).typeText('no'); // workaround for number keyboard
|
||||||
|
await waitFor(element(by.id('commandbox-container'))).toBeVisible().withTimeout(10000);
|
||||||
|
await expect(element(by.id('commandbox-container'))).toBeVisible();
|
||||||
|
await element(by.id('messagebox-input')).clearText();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Message', async() => {
|
describe('Message', async() => {
|
||||||
|
|
Loading…
Reference in New Issue