Merge branch 'master' into mark-as-unread
This commit is contained in:
commit
c017913911
34 changed files with 387 additions and 184 deletions
23
.circleci/config.yml
Normal file
23
.circleci/config.yml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
version: 2.1
|
||||
|
||||
|
||||
jobs:
|
||||
test:
|
||||
working_directory: ~/mattermost-mobile
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
steps:
|
||||
- checkout
|
||||
- run: |
|
||||
echo assets/base/config.json
|
||||
cat assets/base/config.json
|
||||
# Avoid installing pods
|
||||
touch .podinstall
|
||||
# Run tests
|
||||
make test || exit 1
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
pr-test:
|
||||
jobs:
|
||||
- test
|
||||
|
|
@ -123,7 +123,7 @@ android {
|
|||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
|
||||
versionCode 232
|
||||
versionCode 233
|
||||
versionName "1.24.0"
|
||||
multiDexEnabled = true
|
||||
ndk {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
|
|||
|
||||
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
|
||||
import Emoji from 'app/components/emoji';
|
||||
import {BuiltInEmojis} from 'app/utils/emojis';
|
||||
import {getEmojiByName} from 'app/utils/emoji_utils';
|
||||
import {makeStyleSheetFromTheme} from 'app/utils/theme';
|
||||
|
||||
const EMOJI_REGEX = /(^|\s|^\+|^-)(:([^:\s]*))$/i;
|
||||
|
|
@ -138,10 +140,16 @@ export default class EmojiSuggestion extends Component {
|
|||
// We are going to set a double : on iOS to prevent the auto correct from taking over and replacing it
|
||||
// with the wrong value, this is a hack but I could not found another way to solve it
|
||||
let completedDraft;
|
||||
let prefix = ':';
|
||||
if (Platform.OS === 'ios') {
|
||||
completedDraft = emojiPart.replace(EMOJI_REGEX_WITHOUT_PREFIX, `::${emoji}: `);
|
||||
prefix = '::';
|
||||
}
|
||||
|
||||
const emojiData = getEmojiByName(emoji);
|
||||
if (emojiData?.filename && !BuiltInEmojis.includes(emojiData.filename)) {
|
||||
completedDraft = emojiPart.replace(EMOJI_REGEX_WITHOUT_PREFIX, String.fromCodePoint(parseInt(emojiData.filename, 16)));
|
||||
} else {
|
||||
completedDraft = emojiPart.replace(EMOJI_REGEX_WITHOUT_PREFIX, `:${emoji}: `);
|
||||
completedDraft = emojiPart.replace(EMOJI_REGEX_WITHOUT_PREFIX, `${prefix}${emoji}: `);
|
||||
}
|
||||
|
||||
if (value.length > cursorPosition) {
|
||||
|
|
@ -150,7 +158,7 @@ export default class EmojiSuggestion extends Component {
|
|||
|
||||
onChangeText(completedDraft);
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
if (Platform.OS === 'ios' && (!emojiData?.filename || BuiltInEmojis.includes(emojiData?.filename))) {
|
||||
// This is the second part of the hack were we replace the double : with just one
|
||||
// after the auto correct vanished
|
||||
setTimeout(() => {
|
||||
|
|
@ -178,6 +186,7 @@ export default class EmojiSuggestion extends Component {
|
|||
<View style={style.emoji}>
|
||||
<Emoji
|
||||
emojiName={item}
|
||||
textStyle={style.emojiText}
|
||||
size={20}
|
||||
/>
|
||||
</View>
|
||||
|
|
@ -225,6 +234,10 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => {
|
|||
fontSize: 13,
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
emojiText: {
|
||||
color: '#000',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
listView: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export default class Emoji extends React.PureComponent {
|
|||
literal: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
textStyle: CustomPropTypes.Style,
|
||||
unicode: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -116,6 +117,19 @@ export default class Emoji extends React.PureComponent {
|
|||
// force a new image to be rendered when the size changes
|
||||
const key = Platform.OS === 'android' ? (height + '-' + width) : null;
|
||||
|
||||
if (this.props.unicode && !this.props.imageUrl) {
|
||||
const codeArray = this.props.unicode.split('-');
|
||||
const code = codeArray.reduce((acc, c) => {
|
||||
return acc + String.fromCodePoint(parseInt(c, 16));
|
||||
}, '');
|
||||
|
||||
return (
|
||||
<Text style={[this.props.textStyle, {fontSize: size}]}>
|
||||
{code}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (!imageUrl) {
|
||||
return (
|
||||
<Image
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
|||
import {Client4} from 'mattermost-redux/client';
|
||||
import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
|
||||
|
||||
import {EmojiIndicesByAlias, Emojis} from 'app/utils/emojis';
|
||||
import {BuiltInEmojis, EmojiIndicesByAlias, Emojis} from 'app/utils/emojis';
|
||||
|
||||
import Emoji from './emoji';
|
||||
|
||||
|
|
@ -19,11 +19,15 @@ function mapStateToProps(state, ownProps) {
|
|||
const customEmojis = getCustomEmojisByName(state);
|
||||
|
||||
let imageUrl = '';
|
||||
let unicode;
|
||||
let isCustomEmoji = false;
|
||||
let displayTextOnly = false;
|
||||
if (EmojiIndicesByAlias.has(emojiName)) {
|
||||
if (EmojiIndicesByAlias.has(emojiName) || BuiltInEmojis.includes(emojiName)) {
|
||||
const emoji = Emojis[EmojiIndicesByAlias.get(emojiName)];
|
||||
imageUrl = Client4.getSystemEmojiImageUrl(emoji.filename);
|
||||
unicode = emoji.filename;
|
||||
if (BuiltInEmojis.includes(emojiName)) {
|
||||
imageUrl = Client4.getSystemEmojiImageUrl(emoji.filename);
|
||||
}
|
||||
} else if (customEmojis.has(emojiName)) {
|
||||
const emoji = customEmojis.get(emojiName);
|
||||
imageUrl = Client4.getCustomEmojiImageUrl(emoji.id);
|
||||
|
|
@ -33,7 +37,6 @@ function mapStateToProps(state, ownProps) {
|
|||
config.EnableCustomEmoji !== 'true' ||
|
||||
config.ExperimentalEnablePostMetadata === 'true' ||
|
||||
getCurrentUserId(state) === '' ||
|
||||
!isMinimumServerVersion(Client4.getServerVersion(), 4, 7) ||
|
||||
isMinimumServerVersion(Client4.getServerVersion(), 5, 12);
|
||||
}
|
||||
|
||||
|
|
@ -41,6 +44,7 @@ function mapStateToProps(state, ownProps) {
|
|||
imageUrl,
|
||||
isCustomEmoji,
|
||||
displayTextOnly,
|
||||
unicode,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ exports[`components/emoji_picker/EmojiPicker should match snapshot 1`] = `
|
|||
disableVirtualization={false}
|
||||
getItemLayout={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
initialNumToRender={50}
|
||||
keyExtractor={[Function]}
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardShouldPersistTaps="always"
|
||||
|
|
@ -86,7 +86,7 @@ exports[`components/emoji_picker/EmojiPicker should match snapshot 1`] = `
|
|||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollToIndexFailed={[Function]}
|
||||
pageSize={30}
|
||||
pageSize={50}
|
||||
removeClippedSubviews={false}
|
||||
renderItem={[Function]}
|
||||
renderSectionHeader={[Function]}
|
||||
|
|
|
|||
|
|
@ -2,61 +2,20 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
FlatList,
|
||||
SectionList,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import SearchBar from 'app/components/search_bar';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme} from 'app/utils/theme';
|
||||
|
||||
import EmojiPickerBase, {getStyleSheetFromTheme, SECTION_MARGIN} from './emoji_picker_base';
|
||||
import EmojiPickerBase, {getStyleSheetFromTheme} from './emoji_picker_base';
|
||||
|
||||
export default class EmojiPicker extends EmojiPickerBase {
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {deviceWidth, theme} = this.props;
|
||||
const {emojis, filteredEmojis, searchTerm} = this.state;
|
||||
const {theme} = this.props;
|
||||
const {searchTerm} = this.state;
|
||||
const styles = getStyleSheetFromTheme(theme);
|
||||
|
||||
let listComponent;
|
||||
if (searchTerm) {
|
||||
listComponent = (
|
||||
<FlatList
|
||||
keyboardShouldPersistTaps='always'
|
||||
style={styles.flatList}
|
||||
data={filteredEmojis}
|
||||
keyExtractor={this.flatListKeyExtractor}
|
||||
renderItem={this.flatListRenderItem}
|
||||
pageSize={10}
|
||||
initialListSize={10}
|
||||
removeClippedSubviews={true}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
listComponent = (
|
||||
<SectionList
|
||||
ref={this.attachSectionList}
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[styles.sectionList, {width: deviceWidth - (SECTION_MARGIN * 2)}]}
|
||||
sections={emojis}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
renderItem={this.renderItem}
|
||||
keyboardShouldPersistTaps='always'
|
||||
getItemLayout={this.sectionListGetItemLayout}
|
||||
removeClippedSubviews={true}
|
||||
onScroll={this.onScroll}
|
||||
onScrollToIndexFailed={this.handleScrollToSectionFailed}
|
||||
onMomentumScrollEnd={this.onMomentumScrollEnd}
|
||||
pageSize={30}
|
||||
ListFooterComponent={this.renderFooter}
|
||||
onEndReached={this.loadMoreCustomEmojis}
|
||||
onEndReachedThreshold={1}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const searchBarInput = {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
color: theme.centerChannelColor,
|
||||
|
|
@ -87,7 +46,7 @@ export default class EmojiPicker extends EmojiPickerBase {
|
|||
/>
|
||||
</View>
|
||||
<View style={styles.container}>
|
||||
{listComponent}
|
||||
{this.renderListComponent(2)}
|
||||
{!searchTerm &&
|
||||
<View style={styles.bottomContentWrapper}>
|
||||
<View style={styles.bottomContent}>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import {
|
||||
FlatList,
|
||||
KeyboardAvoidingView,
|
||||
SectionList,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {KeyboardTrackingView} from 'react-native-keyboard-tracking-view';
|
||||
|
|
@ -16,58 +14,17 @@ import SearchBar from 'app/components/search_bar';
|
|||
import {DeviceTypes} from 'app/constants';
|
||||
import {changeOpacity, getKeyboardAppearanceFromTheme} from 'app/utils/theme';
|
||||
|
||||
import EmojiPickerBase, {getStyleSheetFromTheme, SECTION_MARGIN} from './emoji_picker_base';
|
||||
|
||||
const SCROLLVIEW_NATIVE_ID = 'emojiPicker';
|
||||
import EmojiPickerBase, {getStyleSheetFromTheme, SCROLLVIEW_NATIVE_ID} from './emoji_picker_base';
|
||||
|
||||
export default class EmojiPicker extends EmojiPickerBase {
|
||||
render() {
|
||||
const {formatMessage} = this.context.intl;
|
||||
const {deviceWidth, isLandscape, theme} = this.props;
|
||||
const {emojis, filteredEmojis, searchTerm} = this.state;
|
||||
const {isLandscape, theme} = this.props;
|
||||
const {searchTerm} = this.state;
|
||||
const styles = getStyleSheetFromTheme(theme);
|
||||
|
||||
const shorten = DeviceTypes.IS_IPHONE_WITH_INSETS && isLandscape ? 6 : 2;
|
||||
|
||||
let listComponent;
|
||||
if (searchTerm) {
|
||||
listComponent = (
|
||||
<FlatList
|
||||
data={filteredEmojis}
|
||||
initialListSize={10}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={this.flatListKeyExtractor}
|
||||
nativeID={SCROLLVIEW_NATIVE_ID}
|
||||
pageSize={10}
|
||||
renderItem={this.flatListRenderItem}
|
||||
style={styles.flatList}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
listComponent = (
|
||||
<SectionList
|
||||
getItemLayout={this.sectionListGetItemLayout}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyboardDismissMode='interactive'
|
||||
ListFooterComponent={this.renderFooter}
|
||||
nativeID={SCROLLVIEW_NATIVE_ID}
|
||||
onEndReached={this.loadMoreCustomEmojis}
|
||||
onEndReachedThreshold={0}
|
||||
onMomentumScrollEnd={this.onMomentumScrollEnd}
|
||||
onScroll={this.onScroll}
|
||||
onScrollToIndexFailed={this.handleScrollToSectionFailed}
|
||||
pageSize={30}
|
||||
ref={this.attachSectionList}
|
||||
removeClippedSubviews={false}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
sections={emojis}
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[styles.sectionList, {width: deviceWidth - (SECTION_MARGIN * shorten)}]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let keyboardOffset = DeviceTypes.IS_IPHONE_WITH_INSETS ? 50 : 30;
|
||||
if (isLandscape) {
|
||||
keyboardOffset = DeviceTypes.IS_IPHONE_WITH_INSETS ? 0 : 10;
|
||||
|
|
@ -111,7 +68,7 @@ export default class EmojiPicker extends EmojiPickerBase {
|
|||
/>
|
||||
</View>
|
||||
<View style={[styles.container]}>
|
||||
{listComponent}
|
||||
{this.renderListComponent(shorten)}
|
||||
{!searchTerm &&
|
||||
<KeyboardTrackingView
|
||||
ref={this.keyboardTracker}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import PropTypes from 'prop-types';
|
|||
import {intlShape} from 'react-intl';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
Platform,
|
||||
SectionList,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
|
|
@ -28,10 +30,11 @@ import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone
|
|||
import EmojiPickerRow from './emoji_picker_row';
|
||||
|
||||
const EMOJI_SIZE = 30;
|
||||
const EMOJI_GUTTER = 7.5;
|
||||
const EMOJI_GUTTER = 7;
|
||||
const EMOJIS_PER_PAGE = 200;
|
||||
const SECTION_HEADER_HEIGHT = 28;
|
||||
export const SECTION_MARGIN = 15;
|
||||
const SECTION_MARGIN = 15;
|
||||
export const SCROLLVIEW_NATIVE_ID = 'emojiPicker';
|
||||
|
||||
export function filterEmojiSearchInput(searchText) {
|
||||
return searchText.toLowerCase().replace(/^:|:$/g, '');
|
||||
|
|
@ -69,7 +72,7 @@ export default class EmojiPicker extends PureComponent {
|
|||
|
||||
this.sectionListGetItemLayout = sectionListGetItemLayout({
|
||||
getItemHeight: () => {
|
||||
return EMOJI_SIZE + (EMOJI_GUTTER * 2);
|
||||
return (EMOJI_SIZE + 5) + (EMOJI_GUTTER * 2);
|
||||
},
|
||||
getSectionHeaderHeight: () => SECTION_HEADER_HEIGHT,
|
||||
});
|
||||
|
|
@ -198,10 +201,6 @@ export default class EmojiPicker extends PureComponent {
|
|||
});
|
||||
};
|
||||
|
||||
filterEmojiAliases = (aliases, searchTerm) => {
|
||||
return aliases.findIndex((alias) => alias.includes(searchTerm)) !== -1;
|
||||
};
|
||||
|
||||
searchEmojis = (searchTerm) => {
|
||||
const {emojis, fuse} = this.props;
|
||||
const searchTermLowerCase = searchTerm.toLowerCase();
|
||||
|
|
@ -232,6 +231,54 @@ export default class EmojiPicker extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
renderListComponent = (shorten) => {
|
||||
const {deviceWidth, theme} = this.props;
|
||||
const {emojis, filteredEmojis, searchTerm} = this.state;
|
||||
const styles = getStyleSheetFromTheme(theme);
|
||||
|
||||
let listComponent;
|
||||
if (searchTerm) {
|
||||
listComponent = (
|
||||
<FlatList
|
||||
data={filteredEmojis}
|
||||
initialListSize={10}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyExtractor={this.flatListKeyExtractor}
|
||||
nativeID={SCROLLVIEW_NATIVE_ID}
|
||||
pageSize={10}
|
||||
renderItem={this.flatListRenderItem}
|
||||
style={styles.flatList}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
listComponent = (
|
||||
<SectionList
|
||||
getItemLayout={this.sectionListGetItemLayout}
|
||||
initialNumToRender={50}
|
||||
keyboardShouldPersistTaps='always'
|
||||
keyboardDismissMode='interactive'
|
||||
ListFooterComponent={this.renderFooter}
|
||||
nativeID={SCROLLVIEW_NATIVE_ID}
|
||||
onEndReached={this.loadMoreCustomEmojis}
|
||||
onEndReachedThreshold={Platform.OS === 'ios' ? 0 : 1}
|
||||
onMomentumScrollEnd={this.onMomentumScrollEnd}
|
||||
onScroll={this.onScroll}
|
||||
onScrollToIndexFailed={this.handleScrollToSectionFailed}
|
||||
pageSize={50}
|
||||
ref={this.attachSectionList}
|
||||
removeClippedSubviews={false}
|
||||
renderItem={this.renderItem}
|
||||
renderSectionHeader={this.renderSectionHeader}
|
||||
sections={emojis}
|
||||
showsVerticalScrollIndicator={false}
|
||||
style={[styles.sectionList, {width: deviceWidth - (SECTION_MARGIN * shorten)}]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return listComponent;
|
||||
};
|
||||
|
||||
flatListKeyExtractor = (item) => item;
|
||||
|
||||
flatListRenderItem = ({item}) => {
|
||||
|
|
@ -271,7 +318,7 @@ export default class EmojiPicker extends PureComponent {
|
|||
}
|
||||
|
||||
this.props.actions.incrementEmojiPickerPage();
|
||||
}
|
||||
};
|
||||
|
||||
onScroll = (e) => {
|
||||
if (this.state.jumpToSection) {
|
||||
|
|
@ -324,7 +371,7 @@ export default class EmojiPicker extends PureComponent {
|
|||
this.scrollToSection(index);
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderSectionHeader = ({section}) => {
|
||||
const {theme} = this.props;
|
||||
|
|
@ -351,7 +398,7 @@ export default class EmojiPicker extends PureComponent {
|
|||
if (isCustomSection && this.props.customEmojiPage === 0) {
|
||||
this.loadMoreCustomEmojis();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderSectionIcons = () => {
|
||||
const {theme} = this.props;
|
||||
|
|
@ -393,7 +440,7 @@ export default class EmojiPicker extends PureComponent {
|
|||
<ActivityIndicator/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ export default class EmojiPickerRow extends Component {
|
|||
renderEmojis = (emoji, index, emojis) => {
|
||||
const {emojiGutter, emojiSize} = this.props;
|
||||
|
||||
const size = emojiSize + 5;
|
||||
const style = [
|
||||
styles.emoji,
|
||||
{
|
||||
width: emojiSize,
|
||||
height: emojiSize,
|
||||
width: size,
|
||||
height: size,
|
||||
marginHorizontal: emojiGutter,
|
||||
},
|
||||
];
|
||||
|
|
@ -60,6 +61,7 @@ export default class EmojiPickerRow extends Component {
|
|||
>
|
||||
<Emoji
|
||||
emojiName={emoji.name}
|
||||
textStyle={styles.emojiText}
|
||||
size={emojiSize}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
|
@ -79,7 +81,7 @@ export default class EmojiPickerRow extends Component {
|
|||
|
||||
const styles = StyleSheet.create({
|
||||
columnStyle: {
|
||||
alignSelf: 'stretch',
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
|
|
@ -88,6 +90,10 @@ const styles = StyleSheet.create({
|
|||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
emojiText: {
|
||||
color: '#000',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
emojiLeft: {
|
||||
marginLeft: 0,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {getCustomEmojis, searchCustomEmojis} from 'mattermost-redux/actions/emoj
|
|||
|
||||
import {incrementEmojiPickerPage} from 'app/actions/views/emoji';
|
||||
import {getDimensions, isLandscape} from 'app/selectors/device';
|
||||
import {CategoryNames, Emojis, EmojiIndicesByAlias, EmojiIndicesByCategory} from 'app/utils/emojis';
|
||||
import {BuiltInEmojis, CategoryNames, Emojis, EmojiIndicesByAlias, EmojiIndicesByCategory} from 'app/utils/emojis';
|
||||
import {t} from 'app/utils/i18n';
|
||||
|
||||
import EmojiPicker from './emoji_picker';
|
||||
|
|
@ -96,6 +96,11 @@ const getEmojisBySection = createSelector(
|
|||
});
|
||||
|
||||
const customEmojiItems = [];
|
||||
BuiltInEmojis.forEach((emoji) => {
|
||||
customEmojiItems.push({
|
||||
name: emoji,
|
||||
});
|
||||
});
|
||||
|
||||
for (const [key] of customEmojis) {
|
||||
customEmojiItems.push({
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import PostBody from './post_body';
|
|||
|
||||
const POST_TIMEOUT = 20000;
|
||||
|
||||
function makeMapStateToProps() {
|
||||
export function makeMapStateToProps() {
|
||||
const memoizeHasEmojisOnly = memoizeResult((message, customEmojis) => hasEmojisOnly(message, customEmojis));
|
||||
const getReactionsForPost = makeGetReactionsForPost();
|
||||
|
||||
|
|
@ -61,9 +61,10 @@ function makeMapStateToProps() {
|
|||
const roles = getCurrentUserId(state) ? getCurrentUserRoles(state) : '';
|
||||
const isAdmin = checkIsAdmin(roles);
|
||||
const isSystemAdmin = checkIsSystemAdmin(roles);
|
||||
const channelIsArchived = channel?.delete_at !== 0; //eslint-disable-line camelcase
|
||||
let canDelete = false;
|
||||
|
||||
if (post && !ownProps.channelIsArchived) {
|
||||
if (post && !channelIsArchived) {
|
||||
canDelete = canDeletePost(state, config, license, currentTeamId, currentChannelId, currentUserId, post, isAdmin, isSystemAdmin);
|
||||
}
|
||||
|
||||
|
|
|
|||
116
app/components/post_body/index.test.js
Normal file
116
app/components/post_body/index.test.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {getChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import * as PostUtils from 'mattermost-redux/utils/post_utils';
|
||||
|
||||
import {makeMapStateToProps} from './index.js';
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/channels', () => {
|
||||
const channels = require.requireActual('mattermost-redux/selectors/entities/channels');
|
||||
|
||||
return {
|
||||
...channels,
|
||||
getChannel: jest.fn(),
|
||||
canManageChannelMembers: jest.fn(),
|
||||
getCurrentChannelId: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/preferences', () => {
|
||||
const preferences = require.requireActual('mattermost-redux/selectors/entities/preferences');
|
||||
return {
|
||||
...preferences,
|
||||
getTheme: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/general', () => {
|
||||
const general = require.requireActual('mattermost-redux/selectors/entities/general');
|
||||
return {
|
||||
...general,
|
||||
getConfig: jest.fn(),
|
||||
getLicense: jest.fn().mockReturnValue({}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/users', () => {
|
||||
const users = require.requireActual('mattermost-redux/selectors/entities/users');
|
||||
return {
|
||||
...users,
|
||||
getCurrentUserId: jest.fn(),
|
||||
getCurrentUserRoles: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/teams', () => {
|
||||
const teams = require.requireActual('mattermost-redux/selectors/entities/teams');
|
||||
return {
|
||||
...teams,
|
||||
getCurrentTeamId: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/emojis', () => {
|
||||
const emojis = require.requireActual('mattermost-redux/selectors/entities/emojis');
|
||||
return {
|
||||
...emojis,
|
||||
getCustomEmojisByName: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/posts', () => {
|
||||
const posts = require.requireActual('mattermost-redux/selectors/entities/posts');
|
||||
return {
|
||||
...posts,
|
||||
makeGetReactionsForPost: () => jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('app/selectors/device', () => ({
|
||||
getDimensions: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('makeMapStateToProps', () => {
|
||||
const defaultState = {
|
||||
entities: {
|
||||
general: {
|
||||
serverVersion: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
const defaultOwnProps = {
|
||||
post: {},
|
||||
};
|
||||
|
||||
test('should not call canDeletePost if post is not defined', () => {
|
||||
const canDeletePost = jest.spyOn(PostUtils, 'canDeletePost');
|
||||
const mapStateToProps = makeMapStateToProps();
|
||||
const ownProps = {
|
||||
post: '',
|
||||
};
|
||||
|
||||
const props = mapStateToProps(defaultState, ownProps);
|
||||
expect(props.canDelete).toBe(false);
|
||||
expect(canDeletePost).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not call canDeletePost if post is defined and channel is archived', () => {
|
||||
const canDeletePost = jest.spyOn(PostUtils, 'canDeletePost');
|
||||
const mapStateToProps = makeMapStateToProps();
|
||||
|
||||
getChannel.mockReturnValueOnce({delete_at: 1}); //eslint-disable-line camelcase
|
||||
const props = mapStateToProps(defaultState, defaultOwnProps);
|
||||
expect(props.canDelete).toBe(false);
|
||||
expect(canDeletePost).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call canDeletePost if post is defined and channel is not archived', () => {
|
||||
const canDeletePost = jest.spyOn(PostUtils, 'canDeletePost');
|
||||
const mapStateToProps = makeMapStateToProps();
|
||||
|
||||
getChannel.mockReturnValue({delete_at: 0}); //eslint-disable-line camelcase
|
||||
mapStateToProps(defaultState, defaultOwnProps);
|
||||
expect(canDeletePost).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -299,7 +299,7 @@ export default class PostList extends PureComponent {
|
|||
|
||||
scrollToBottom = () => {
|
||||
setTimeout(() => {
|
||||
if (this.flatListRef?.current) {
|
||||
if (this.flatListRef.current) {
|
||||
this.flatListRef.current.scrollToOffset({offset: 0, animated: true});
|
||||
}
|
||||
}, 250);
|
||||
|
|
@ -316,7 +316,7 @@ export default class PostList extends PureComponent {
|
|||
|
||||
scrollToIndex = (index) => {
|
||||
this.animationFrameInitialIndex = requestAnimationFrame(() => {
|
||||
if (this.flatListRef?.current && index > 0 && index <= this.getItemCount()) {
|
||||
if (this.flatListRef.current && index > 0 && index <= this.getItemCount()) {
|
||||
this.flatListScrollToIndex(index);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ describe('PostList', () => {
|
|||
const indexInRange = baseProps.postIds.length;
|
||||
const indexOutOfRange = [-1, indexInRange + 1];
|
||||
|
||||
instance.flatListRef = {};
|
||||
instance.flatListRef = {
|
||||
current: null,
|
||||
};
|
||||
instance.scrollToIndex(indexInRange);
|
||||
expect(flatListScrollToIndex).not.toHaveBeenCalled();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ import assert from 'assert';
|
|||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
|
||||
import Fade from 'app/components/fade';
|
||||
import SendButton from 'app/components/send_button';
|
||||
import PasteableTextInput from 'app/components/pasteable_text_input';
|
||||
import EventEmitter from 'mattermost-redux/utils/event_emitter';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
|
||||
import PostTextbox from './post_textbox.ios';
|
||||
|
||||
|
|
@ -62,6 +63,7 @@ describe('PostTextBox', () => {
|
|||
cursorPositionEvent: '',
|
||||
valueEvent: '',
|
||||
isLandscape: false,
|
||||
screenId: 'NavigationScreen1',
|
||||
};
|
||||
|
||||
test('should match, full snapshot', () => {
|
||||
|
|
@ -343,6 +345,7 @@ describe('PostTextBox', () => {
|
|||
test('should show error dialog if error occured', () => {
|
||||
jest.spyOn(Alert, 'alert').mockReturnValue(null);
|
||||
const wrapper = shallowWithIntl(<PostTextbox {...baseProps}/>);
|
||||
EphemeralStore.addNavigationComponentId('NavigationScreen1');
|
||||
wrapper.find(PasteableTextInput).first().simulate('paste', {error: 'some error'}, []);
|
||||
expect(Alert.alert).toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -350,6 +353,7 @@ describe('PostTextBox', () => {
|
|||
test('should show file max warning and not uploading', () => {
|
||||
jest.spyOn(EventEmitter, 'emit').mockReturnValue(null);
|
||||
const wrapper = shallowWithIntl(<PostTextbox {...baseProps}/>);
|
||||
EphemeralStore.addNavigationComponentId('NavigationScreen1');
|
||||
wrapper.find(PasteableTextInput).first().simulate('paste', null, [
|
||||
{
|
||||
fileSize: 1000,
|
||||
|
|
@ -414,6 +418,7 @@ describe('PostTextBox', () => {
|
|||
|
||||
test('should upload images', () => {
|
||||
const wrapper = shallowWithIntl(<PostTextbox {...baseProps}/>);
|
||||
EphemeralStore.addNavigationComponentId('NavigationScreen1');
|
||||
wrapper.find(PasteableTextInput).first().simulate('paste', null, [
|
||||
{
|
||||
fileSize: 1000,
|
||||
|
|
@ -424,6 +429,20 @@ describe('PostTextBox', () => {
|
|||
]);
|
||||
expect(baseProps.actions.initUploadFiles).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should NOT upload images when not the top most screen', () => {
|
||||
const wrapper = shallowWithIntl(<PostTextbox {...baseProps}/>);
|
||||
EphemeralStore.addNavigationComponentId('NavigationScreen2');
|
||||
wrapper.find(PasteableTextInput).first().simulate('paste', null, [
|
||||
{
|
||||
fileSize: 1000,
|
||||
fileName: 'fileName.png',
|
||||
type: 'images/png',
|
||||
url: 'path/to/image',
|
||||
},
|
||||
]);
|
||||
expect(baseProps.actions.initUploadFiles).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -24,11 +24,12 @@ import AttachmentButton from 'app/components/attachment_button';
|
|||
import Fade from 'app/components/fade';
|
||||
import FormattedMarkdownText from 'app/components/formatted_markdown_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import SendButton from 'app/components/send_button';
|
||||
import PasteableTextInput from 'app/components/pasteable_text_input';
|
||||
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
import SendButton from 'app/components/send_button';
|
||||
import {INSERT_TO_COMMENT, INSERT_TO_DRAFT, IS_REACTION_REGEX, MAX_CONTENT_HEIGHT, MAX_FILE_COUNT} from 'app/constants/post_textbox';
|
||||
import {NOTIFY_ALL_MEMBERS} from 'app/constants/view';
|
||||
import EphemeralStore from 'app/store/ephemeral_store';
|
||||
import {t} from 'app/utils/i18n';
|
||||
import {confirmOutOfOfficeDisabled} from 'app/utils/status';
|
||||
import {
|
||||
|
|
@ -36,7 +37,6 @@ import {
|
|||
makeStyleSheetFromTheme,
|
||||
getKeyboardAppearanceFromTheme,
|
||||
} from 'app/utils/theme';
|
||||
import {paddingHorizontal as padding} from 'app/components/safe_area_view/iphone_x_spacing';
|
||||
|
||||
const {RNTextInputReset} = NativeModules;
|
||||
|
||||
|
|
@ -83,6 +83,7 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
isTimezoneEnabled: PropTypes.bool,
|
||||
currentChannel: PropTypes.object,
|
||||
isLandscape: PropTypes.bool.isRequired,
|
||||
screenId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -412,19 +413,19 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
this.props.actions.initUploadFiles(images, this.props.rootId);
|
||||
};
|
||||
|
||||
isFileLoading() {
|
||||
isFileLoading = () => {
|
||||
const {files} = this.props;
|
||||
|
||||
return files.some((file) => file.loading);
|
||||
}
|
||||
};
|
||||
|
||||
isSendButtonVisible() {
|
||||
isSendButtonVisible = () => {
|
||||
return this.canSend() || this.isFileLoading();
|
||||
}
|
||||
};
|
||||
|
||||
isSendButtonEnabled() {
|
||||
isSendButtonEnabled = () => {
|
||||
return this.canSend() && !this.isFileLoading() && !this.state.sendingMessage;
|
||||
}
|
||||
};
|
||||
|
||||
sendMessage = () => {
|
||||
const {value} = this.state;
|
||||
|
|
@ -445,7 +446,7 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
textContainsAtAllAtChannel = (text) => {
|
||||
const textWithoutCode = text.replace(/(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)| *(`{3,}|~{3,})[ .]*(\S+)? *\n([\s\S]*?\s*)\3 *(?:\n+|$)/g, '');
|
||||
return (/\B@(all|channel)\b/i).test(textWithoutCode);
|
||||
}
|
||||
};
|
||||
|
||||
showSendToAllOrChannelAlert = (currentMembersCount) => {
|
||||
const {intl} = this.context;
|
||||
|
|
@ -505,7 +506,7 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
doSubmitMessage = () => {
|
||||
const {actions, currentUserId, channelId, files, rootId} = this.props;
|
||||
|
|
@ -562,7 +563,7 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
}
|
||||
|
||||
EventEmitter.emit('scroll-to-bottom');
|
||||
}
|
||||
};
|
||||
|
||||
getStatusFromSlashCommand = (message) => {
|
||||
const tokens = message.split(' ');
|
||||
|
|
@ -704,29 +705,31 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
handlePasteImages = (error, images) => {
|
||||
if (error) {
|
||||
this.showPasteImageErrorDialog();
|
||||
return;
|
||||
}
|
||||
if (this.props.screenId === EphemeralStore.getNavigationTopComponentId()) {
|
||||
if (error) {
|
||||
this.showPasteImageErrorDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
const {maxFileSize, files} = this.props;
|
||||
const availableCount = MAX_FILE_COUNT - files.length;
|
||||
if (images.length > availableCount) {
|
||||
this.onShowFileMaxWarning();
|
||||
return;
|
||||
}
|
||||
const {maxFileSize, files} = this.props;
|
||||
const availableCount = MAX_FILE_COUNT - files.length;
|
||||
if (images.length > availableCount) {
|
||||
this.onShowFileMaxWarning();
|
||||
return;
|
||||
}
|
||||
|
||||
const largeImage = images.find((image) => image.fileSize > maxFileSize);
|
||||
if (largeImage) {
|
||||
this.onShowFileSizeWarning(largeImage.fileName);
|
||||
return;
|
||||
}
|
||||
const largeImage = images.find((image) => image.fileSize > maxFileSize);
|
||||
if (largeImage) {
|
||||
this.onShowFileSizeWarning(largeImage.fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleUploadFiles(images);
|
||||
}
|
||||
this.handleUploadFiles(images);
|
||||
}
|
||||
};
|
||||
|
||||
renderDeactivatedChannel = () => {
|
||||
const {intl} = this.context;
|
||||
|
|
@ -740,7 +743,7 @@ export default class PostTextBoxBase extends PureComponent {
|
|||
})}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderTextBox = () => {
|
||||
const {intl} = this.context;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
Platform,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
} from 'react-native';
|
||||
|
|
@ -46,6 +47,7 @@ export default class Reaction extends PureComponent {
|
|||
<Emoji
|
||||
emojiName={emojiName}
|
||||
size={20}
|
||||
textStyle={{color: 'black', fontWeight: 'bold'}}
|
||||
padding={5}
|
||||
/>
|
||||
<Text style={styles.count}>{count}</Text>
|
||||
|
|
@ -73,8 +75,12 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
|||
marginRight: 6,
|
||||
marginBottom: 5,
|
||||
marginTop: 10,
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 6,
|
||||
...Platform.select({
|
||||
android: {
|
||||
paddingBottom: 2,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -108,12 +108,8 @@ function loadTranslation(locale) {
|
|||
}
|
||||
}
|
||||
|
||||
let momentLocale = DEFAULT_LOCALE;
|
||||
|
||||
function setMomentLocale(locale) {
|
||||
if (momentLocale !== locale) {
|
||||
momentLocale = moment.locale(locale);
|
||||
}
|
||||
export function resetMomentLocale() {
|
||||
moment.locale(DEFAULT_LOCALE);
|
||||
}
|
||||
|
||||
export function getTranslations(locale) {
|
||||
|
|
@ -121,8 +117,6 @@ export function getTranslations(locale) {
|
|||
loadTranslation(locale);
|
||||
}
|
||||
|
||||
setMomentLocale(locale.toLowerCase());
|
||||
|
||||
return TRANSLATIONS[locale] || TRANSLATIONS[DEFAULT_LOCALE];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {selectDefaultChannel} from 'app/actions/views/channel';
|
|||
import {showOverlay} from 'app/actions/navigation';
|
||||
import {loadConfigAndLicense, setDeepLinkURL, startDataCleanup} from 'app/actions/views/root';
|
||||
import {NavigationTypes, ViewTypes} from 'app/constants';
|
||||
import {getTranslations} from 'app/i18n';
|
||||
import {getTranslations, resetMomentLocale} from 'app/i18n';
|
||||
import mattermostManaged from 'app/mattermost_managed';
|
||||
import PushNotifications from 'app/push_notifications';
|
||||
import {getCurrentLocale} from 'app/selectors/i18n';
|
||||
|
|
@ -145,6 +145,7 @@ class GlobalEventHandler {
|
|||
this.store.dispatch(setServerVersion(''));
|
||||
deleteFileCache();
|
||||
removeAppCredentials();
|
||||
resetMomentLocale();
|
||||
|
||||
PushNotifications.clearNotifications();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import thunk from 'redux-thunk';
|
|||
|
||||
import intitialState from 'app/initial_state';
|
||||
import PushNotification from 'app/push_notifications';
|
||||
import * as I18n from 'app/i18n';
|
||||
|
||||
import GlobalEventHandler from './global_event_handler';
|
||||
|
||||
|
|
@ -35,11 +36,13 @@ GlobalEventHandler.store = store;
|
|||
|
||||
// TODO: Add Android test as part of https://mattermost.atlassian.net/browse/MM-17110
|
||||
describe('GlobalEventHandler', () => {
|
||||
it('should clear notifications on logout', async () => {
|
||||
it('should clear notifications and reset moment locale on logout', async () => {
|
||||
const clearNotifications = jest.spyOn(PushNotification, 'clearNotifications');
|
||||
const resetMomentLocale = jest.spyOn(I18n, 'resetMomentLocale');
|
||||
|
||||
await GlobalEventHandler.onLogout();
|
||||
expect(clearNotifications).toHaveBeenCalled();
|
||||
expect(resetMomentLocale).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('should call onAppStateChange after configuration', () => {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export default class ChannelAndroid extends ChannelBase {
|
|||
</View>
|
||||
<PostTextbox
|
||||
ref={this.postTextbox}
|
||||
screenId={this.props.componentId}
|
||||
/>
|
||||
</KeyboardLayout>
|
||||
<ChannelLoader
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ export default class ChannelIOS extends ChannelBase {
|
|||
cursorPositionEvent={CHANNEL_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
valueEvent={CHANNEL_POST_TEXTBOX_VALUE_CHANGE}
|
||||
ref={this.postTextbox}
|
||||
screenId={this.props.componentId}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
</React.Fragment>
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
defaultMessage: "We couldn't archive the channel {displayName}. Please check your connection and try again.",
|
||||
},
|
||||
{
|
||||
displayName: channel.display_name,
|
||||
displayName: channel.display_name.trim(),
|
||||
}
|
||||
);
|
||||
if (result.error.server_error_id === 'api.channel.delete_channel.deleted.app_error') {
|
||||
|
|
@ -290,7 +290,7 @@ export default class ChannelInfo extends PureComponent {
|
|||
message,
|
||||
{
|
||||
term: term.toLowerCase(),
|
||||
name: channel.display_name,
|
||||
name: channel.display_name.trim(),
|
||||
}
|
||||
),
|
||||
[{
|
||||
|
|
|
|||
|
|
@ -60,9 +60,7 @@ function channelInfoRow(props) {
|
|||
value={detail}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (rightArrow) {
|
||||
} else if (rightArrow) {
|
||||
actionElement = (
|
||||
<Icon
|
||||
name='angle-right'
|
||||
|
|
|
|||
|
|
@ -44,10 +44,11 @@ export default class ThreadAndroid extends ThreadBase {
|
|||
|
||||
postTextBox = (
|
||||
<PostTextbox
|
||||
channelIsArchived={channelIsArchived}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
channelIsArchived={channelIsArchived}
|
||||
onCloseChannel={this.onCloseChannel}
|
||||
rootId={rootId}
|
||||
screenId={this.props.componentId}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -72,12 +72,13 @@ export default class ThreadIOS extends ThreadBase {
|
|||
accessoriesContainerID={ACCESSORIES_CONTAINER_NATIVE_ID}
|
||||
>
|
||||
<PostTextbox
|
||||
ref={this.postTextbox}
|
||||
channelIsArchived={channelIsArchived}
|
||||
rootId={rootId}
|
||||
channelId={channelId}
|
||||
onCloseChannel={this.onCloseChannel}
|
||||
channelIsArchived={channelIsArchived}
|
||||
cursorPositionEvent={THREAD_POST_TEXTBOX_CURSOR_CHANGE}
|
||||
onCloseChannel={this.onCloseChannel}
|
||||
ref={this.postTextbox}
|
||||
rootId={rootId}
|
||||
screenId={this.props.componentId}
|
||||
valueEvent={THREAD_POST_TEXTBOX_VALUE_CHANGE}
|
||||
/>
|
||||
</KeyboardTrackingView>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import emojiRegex from 'emoji-regex';
|
||||
|
||||
import {EmojiIndicesByAlias} from './emojis';
|
||||
import {Emojis, EmojiIndicesByAlias} from './emojis';
|
||||
|
||||
const RE_NAMED_EMOJI = /(:([a-zA-Z0-9_-]+):)/g;
|
||||
|
||||
|
|
@ -105,3 +105,11 @@ export function doesMatchNamedEmoji(emojiName) {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getEmojiByName(emojiName) {
|
||||
if (EmojiIndicesByAlias.has(emojiName)) {
|
||||
return Emojis[EmojiIndicesByAlias.get(emojiName)];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -2836,7 +2836,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
|
@ -2897,7 +2897,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 232;
|
||||
CURRENT_PROJECT_VERSION = 233;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>232</string>
|
||||
<string>233</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.24.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>232</string>
|
||||
<string>233</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@
|
|||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>232</string>
|
||||
<string>233</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.24.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>232</string>
|
||||
<string>233</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
|
|||
Loading…
Reference in a new issue