diff --git a/app/actions/views/emoji.js b/app/actions/views/emoji.js
index c2feff4e1..1ef09e266 100644
--- a/app/actions/views/emoji.js
+++ b/app/actions/views/emoji.js
@@ -32,3 +32,13 @@ export function addRecentEmoji(emoji) {
emoji
};
}
+
+export function incrementEmojiPickerPage() {
+ return async (dispatch) => {
+ dispatch({
+ type: ViewTypes.INCREMENT_EMOJI_PICKER_PAGE
+ });
+
+ return {data: true};
+ };
+}
diff --git a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js
index 605e14fbd..7fe3d82c0 100644
--- a/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js
+++ b/app/components/autocomplete/emoji_suggestion/emoji_suggestion.js
@@ -10,6 +10,8 @@ import {
View
} from 'react-native';
+import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
+
import AutocompleteDivider from 'app/components/autocomplete/autocomplete_divider';
import Emoji from 'app/components/emoji';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
@@ -20,7 +22,8 @@ const EMOJI_REGEX_WITHOUT_PREFIX = /\B(:([^:\s]*))$/i;
export default class EmojiSuggestion extends Component {
static propTypes = {
actions: PropTypes.shape({
- addReactionToLatestPost: PropTypes.func.isRequired
+ addReactionToLatestPost: PropTypes.func.isRequired,
+ autocompleteCustomEmojis: PropTypes.func.isRequired
}).isRequired,
cursorPosition: PropTypes.number,
emojis: PropTypes.array.isRequired,
@@ -30,7 +33,8 @@ export default class EmojiSuggestion extends Component {
onChangeText: PropTypes.func.isRequired,
onResultCountChange: PropTypes.func.isRequired,
rootId: PropTypes.string,
- value: PropTypes.string
+ value: PropTypes.string,
+ serverVersion: PropTypes.string
};
static defaultProps = {
@@ -43,6 +47,12 @@ export default class EmojiSuggestion extends Component {
dataSource: []
};
+ constructor(props) {
+ super(props);
+
+ this.matchTerm = '';
+ }
+
componentWillReceiveProps(nextProps) {
if (nextProps.isSearch) {
return;
@@ -54,7 +64,6 @@ export default class EmojiSuggestion extends Component {
if (!match || this.state.emojiComplete) {
this.setState({
active: false,
- matchTerm: null,
emojiComplete: false
});
@@ -63,18 +72,33 @@ export default class EmojiSuggestion extends Component {
return;
}
- const matchTerm = match[3];
+ const oldMatchTerm = this.matchTerm;
+ this.matchTerm = match[3] || '';
- const matchTermChanged = matchTerm !== this.state.matchTerm;
- if (matchTermChanged) {
- this.setState({
- matchTerm
- });
+ // If we're server version 4.7 or higher
+ if (isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
+ if (this.matchTerm !== oldMatchTerm && this.matchTerm.length) {
+ this.props.actions.autocompleteCustomEmojis(this.matchTerm);
+ return;
+ }
+
+ if (this.props.emojis !== nextProps.emojis) {
+ this.handleFuzzySearch(this.matchTerm, nextProps);
+ } else if (!this.matchTerm.length) {
+ const initialEmojis = [...nextProps.emojis];
+ initialEmojis.splice(0, 300);
+ const data = initialEmojis.sort();
+
+ this.setEmojiData(data);
+ }
+
+ return;
}
- if (matchTermChanged) {
- this.handleFuzzySearch(matchTerm, nextProps);
- } else if (!matchTerm.length) {
+ // If we're server version 4.6 or lower
+ if (this.matchTerm !== oldMatchTerm) {
+ this.handleFuzzySearch(this.matchTerm, nextProps);
+ } else if (!this.matchTerm.length) {
const initialEmojis = [...nextProps.emojis];
initialEmojis.splice(0, 300);
const data = initialEmojis.sort();
diff --git a/app/components/autocomplete/emoji_suggestion/index.js b/app/components/autocomplete/emoji_suggestion/index.js
index 1150988e2..bde447f3e 100644
--- a/app/components/autocomplete/emoji_suggestion/index.js
+++ b/app/components/autocomplete/emoji_suggestion/index.js
@@ -6,6 +6,8 @@ import {createSelector} from 'reselect';
import {bindActionCreators} from 'redux';
import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
+import {autocompleteCustomEmojis} from 'mattermost-redux/actions/emojis';
+import {Client4} from 'mattermost-redux/client';
import {addReactionToLatestPost} from 'app/actions/views/emoji';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
@@ -43,14 +45,16 @@ function mapStateToProps(state) {
return {
fuse,
emojis,
- theme: getTheme(state)
+ theme: getTheme(state),
+ serverVersion: state.entities.general.serverVersion || Client4.getServerVersion()
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
- addReactionToLatestPost
+ addReactionToLatestPost,
+ autocompleteCustomEmojis
}, dispatch)
};
}
diff --git a/app/components/emoji/emoji.js b/app/components/emoji/emoji.js
index dbe29106e..dffc606d9 100644
--- a/app/components/emoji/emoji.js
+++ b/app/components/emoji/emoji.js
@@ -13,9 +13,6 @@ import {
import FastImage from 'react-native-fast-image';
import CustomPropTypes from 'app/constants/custom_prop_types';
-import {EmojiIndicesByAlias, Emojis} from 'app/utils/emojis';
-
-import {Client4} from 'mattermost-redux/client';
const scaleEmojiBasedOnDevice = (size) => {
if (Platform.OS === 'ios') {
@@ -26,8 +23,26 @@ const scaleEmojiBasedOnDevice = (size) => {
export default class Emoji extends React.PureComponent {
static propTypes = {
- customEmojis: PropTypes.object,
+
+ /*
+ * Emoji text name.
+ */
emojiName: PropTypes.string.isRequired,
+
+ /*
+ * Image URL for the emoji.
+ */
+ imageUrl: PropTypes.string.isRequired,
+
+ /*
+ * Set if this is a custom emoji.
+ */
+ isCustomEmoji: PropTypes.bool.isRequired,
+
+ /*
+ * Set to render only the text and no image.
+ */
+ displayTextOnly: PropTypes.bool,
literal: PropTypes.string,
size: PropTypes.number,
textStyle: CustomPropTypes.Style,
@@ -36,14 +51,15 @@ export default class Emoji extends React.PureComponent {
static defaultProps = {
customEmojis: new Map(),
- literal: ''
+ literal: '',
+ imageUrl: '',
+ isCustomEmoji: false
};
constructor(props) {
super(props);
this.state = {
- ...this.getImageUrl(props),
originalWidth: 0,
originalHeight: 0
};
@@ -51,24 +67,22 @@ export default class Emoji extends React.PureComponent {
componentWillMount() {
this.mounted = true;
- if (this.state.imageUrl && this.state.isCustomEmoji) {
- this.updateImageHeight(this.state.imageUrl);
+ if (!this.props.displayTextOnly && this.props.imageUrl && this.props.isCustomEmoji) {
+ this.updateImageHeight(this.props.imageUrl);
}
}
componentWillReceiveProps(nextProps) {
- if (nextProps.customEmojis !== this.props.customEmojis || nextProps.emojiName !== this.props.emojiName) {
+ if (nextProps.emojiName !== this.props.emojiName) {
this.setState({
- ...this.getImageUrl(nextProps),
originalWidth: 0,
originalHeight: 0
});
}
- }
- componentWillUpdate(nextProps, nextState) {
- if (nextState.imageUrl !== this.state.imageUrl && nextState.imageUrl && nextState.isCustomEmoji) {
- this.updateImageHeight(nextState.imageUrl);
+ if (!nextProps.displayTextOnly && nextProps.imageUrl && nextProps.isCustomEmoji &&
+ nextProps.imageUrl !== this.props.imageUrl) {
+ this.updateImageHeight(nextProps.imageUrl);
}
}
@@ -76,28 +90,6 @@ export default class Emoji extends React.PureComponent {
this.mounted = false;
}
- getImageUrl = (props = this.props) => {
- const emojiName = props.emojiName;
-
- let imageUrl = '';
- let isCustomEmoji = false;
- if (EmojiIndicesByAlias.has(emojiName)) {
- const emoji = Emojis[EmojiIndicesByAlias.get(emojiName)];
-
- imageUrl = Client4.getSystemEmojiImageUrl(emoji.filename);
- } else if (props.customEmojis.has(emojiName)) {
- const emoji = props.customEmojis.get(emojiName);
-
- imageUrl = Client4.getCustomEmojiImageUrl(emoji.id);
- isCustomEmoji = true;
- }
-
- return {
- imageUrl,
- isCustomEmoji
- };
- }
-
updateImageHeight = (imageUrl) => {
Image.getSize(imageUrl, (originalWidth, originalHeight) => {
if (this.mounted) {
@@ -113,7 +105,9 @@ export default class Emoji extends React.PureComponent {
const {
literal,
textStyle,
- token
+ token,
+ imageUrl,
+ displayTextOnly
} = this.props;
let size = this.props.size;
@@ -124,19 +118,12 @@ export default class Emoji extends React.PureComponent {
size = scaleEmojiBasedOnDevice(fontSize);
}
- if (!this.state.imageUrl) {
+ if (displayTextOnly) {
return {literal};
}
- let ImageComponent;
- if (Platform.OS === 'android') {
- ImageComponent = Image;
- } else {
- ImageComponent = FastImage;
- }
-
const source = {
- uri: this.state.imageUrl,
+ uri: imageUrl,
headers: {
Authorization: `Bearer ${token}`
}
@@ -170,6 +157,22 @@ 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;
+ let ImageComponent;
+ if (Platform.OS === 'android') {
+ ImageComponent = Image;
+ } else {
+ ImageComponent = FastImage;
+ }
+
+ if (!imageUrl) {
+ return (
+
+ );
+ }
+
return (
{
@@ -139,13 +162,22 @@ export default class EmojiPicker extends PureComponent {
};
changeSearchTerm = (text) => {
- this.setState({
+ const nextState = {
searchTerm: text
- });
+ };
+
+ if (!text) {
+ nextState.currentSectionIndex = 0;
+ }
+
+ this.setState(nextState);
clearTimeout(this.searchTermTimeout);
const timeout = text ? 100 : 0;
- this.searchTermTimeout = setTimeout(() => {
+ this.searchTermTimeout = setTimeout(async () => {
+ if (isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
+ await this.props.actions.searchCustomEmojis(text);
+ }
const filteredEmojis = this.searchEmojis(text);
this.setState({
filteredEmojis
@@ -155,6 +187,7 @@ export default class EmojiPicker extends PureComponent {
cancelSearch = () => {
this.setState({
+ currentSectionIndex: 0,
filteredEmojis: [],
searchTerm: ''
});
@@ -214,6 +247,26 @@ export default class EmojiPicker extends PureComponent {
);
};
+ loadMoreCustomEmojis = async () => {
+ if (!this.props.customEmojisEnabled || !isMinimumServerVersion(this.props.serverVersion, 4, 7)) {
+ return;
+ }
+
+ const {data} = await this.props.actions.getCustomEmojis(this.props.customEmojiPage, EMOJIS_PER_PAGE);
+ this.setState({loadingMore: false});
+
+ if (!data) {
+ return;
+ }
+
+ if (data.length < EMOJIS_PER_PAGE) {
+ this.setState({missingPages: false});
+ return;
+ }
+
+ this.props.actions.incrementEmojiPickerPage();
+ }
+
onScroll = (e) => {
if (this.state.jumpToSection) {
return;
@@ -285,9 +338,13 @@ export default class EmojiPicker extends PureComponent {
);
};
- handleSectionIconPress = (index) => {
+ handleSectionIconPress = (index, isCustomSection = false) => {
this.scrollToSectionTries = 0;
this.scrollToSection(index);
+
+ if (isCustomSection && this.props.customEmojiPage === 0) {
+ this.loadMoreCustomEmojis();
+ }
}
renderSectionIcons = () => {
@@ -295,7 +352,7 @@ export default class EmojiPicker extends PureComponent {
const styles = getStyleSheetFromTheme(theme);
return this.state.emojis.map((section, index) => {
- const onPress = () => this.handleSectionIconPress(index);
+ const onPress = () => this.handleSectionIconPress(index, section.key === 'custom');
return (
{
+ if (!this.state.missingPages) {
+ return null;
+ }
+
+ const {theme} = this.props;
+
+ const styles = getStyleSheetFromTheme(theme);
+ return (
+
+
+
+ );
+ }
+
render() {
const {deviceWidth, isLandscape, theme} = this.props;
const {emojis, filteredEmojis, searchTerm} = this.state;
@@ -353,6 +425,9 @@ export default class EmojiPicker extends PureComponent {
onScrollToIndexFailed={this.handleScrollToSectionFailed}
onMomentumScrollEnd={this.onMomentumScrollEnd}
pageSize={30}
+ ListFooterComponent={this.renderFooter}
+ onEndReached={this.loadMoreCustomEmojis}
+ onEndReachedThreshold={Platform.OS === 'ios' ? 0 : 1}
/>
);
}
@@ -511,6 +586,10 @@ const getStyleSheetFromTheme = makeStyleSheetFromTheme((theme) => {
},
wrapper: {
flex: 1
+ },
+ loading: {
+ flex: 1,
+ alignItems: 'center'
}
};
});
diff --git a/app/components/emoji_picker/emoji_picker_row.js b/app/components/emoji_picker/emoji_picker_row.js
index 6974b55f2..a18d4fa2a 100644
--- a/app/components/emoji_picker/emoji_picker_row.js
+++ b/app/components/emoji_picker/emoji_picker_row.js
@@ -8,6 +8,7 @@ import {
TouchableOpacity,
View
} from 'react-native';
+import shallowEqual from 'shallow-equals';
import Emoji from 'app/components/emoji';
@@ -20,7 +21,7 @@ export default class EmojiPickerRow extends Component {
}
shouldComponentUpdate(nextProps) {
- return this.props.items.length !== nextProps.items.length;
+ return !shallowEqual(this.props.items, nextProps.items);
}
renderEmojis = (emoji, index, emojis) => {
@@ -92,4 +93,4 @@ const styles = StyleSheet.create({
emojiRight: {
marginRight: 0
}
-});
\ No newline at end of file
+});
diff --git a/app/components/emoji_picker/index.js b/app/components/emoji_picker/index.js
index 872723b35..7cae05a01 100644
--- a/app/components/emoji_picker/index.js
+++ b/app/components/emoji_picker/index.js
@@ -3,11 +3,16 @@
import {connect} from 'react-redux';
import {createSelector} from 'reselect';
+import {bindActionCreators} from 'redux';
-import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
-
-import {getDimensions, isLandscape} from 'app/selectors/device';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis';
+import {getConfig} from 'mattermost-redux/selectors/entities/general';
+import {getCustomEmojis, searchCustomEmojis} from 'mattermost-redux/actions/emojis';
+import {Client4} from 'mattermost-redux/client';
+
+import {incrementEmojiPickerPage} from 'app/actions/views/emoji';
+import {getDimensions, isLandscape} from 'app/selectors/device';
import {CategoryNames, Emojis, EmojiIndicesByAlias, EmojiIndicesByCategory} from 'app/utils/emojis';
import EmojiPicker from './emoji_picker';
@@ -152,8 +157,21 @@ function mapStateToProps(state) {
emojisBySection,
deviceWidth,
isLandscape: isLandscape(state),
- theme: getTheme(state)
+ theme: getTheme(state),
+ customEmojisEnabled: getConfig(state).EnableCustomEmoji === 'true',
+ customEmojiPage: state.views.emoji.emojiPickerCustomPage,
+ serverVersion: state.entities.general.serverVersion || Client4.getServerVersion()
};
}
-export default connect(mapStateToProps)(EmojiPicker);
+function mapDispatchToProps(dispatch) {
+ return {
+ actions: bindActionCreators({
+ getCustomEmojis,
+ incrementEmojiPickerPage,
+ searchCustomEmojis
+ }, dispatch)
+ };
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(EmojiPicker);
diff --git a/app/constants/view.js b/app/constants/view.js
index b69d0b071..925a954aa 100644
--- a/app/constants/view.js
+++ b/app/constants/view.js
@@ -62,7 +62,9 @@ const ViewTypes = keyMirror({
ADD_RECENT_EMOJI: null,
EXTENSION_SELECTED_TEAM_ID: null,
- ANNOUNCEMENT_BANNER: null
+ ANNOUNCEMENT_BANNER: null,
+
+ INCREMENT_EMOJI_PICKER_PAGE: null
});
export default {
diff --git a/app/mattermost.js b/app/mattermost.js
index 5345ac694..a138b81de 100644
--- a/app/mattermost.js
+++ b/app/mattermost.js
@@ -22,6 +22,7 @@ import semver from 'semver';
import {General} from 'mattermost-redux/constants';
import {setAppState, setDeviceToken, setServerVersion} from 'mattermost-redux/actions/general';
import {markChannelAsRead} from 'mattermost-redux/actions/channels';
+import {setSystemEmojis} from 'mattermost-redux/actions/emojis';
import {logError} from 'mattermost-redux/actions/errors';
import {loadMe, logout} from 'mattermost-redux/actions/users';
import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
@@ -56,6 +57,7 @@ import {init as initAnalytics} from 'app/utils/segment';
import {captureException, initializeSentry, LOGGER_JAVASCRIPT, LOGGER_NATIVE} from 'app/utils/sentry';
import tracker from 'app/utils/time_tracker';
import {stripTrailingSlashes} from 'app/utils/url';
+import {EmojiIndicesByAlias} from 'app/utils/emojis';
import LocalConfig from 'assets/config';
@@ -90,6 +92,7 @@ export default class Mattermost {
setJSExceptionHandler(this.errorHandler, false);
setNativeExceptionHandler(this.nativeErrorHandler, false);
+ setSystemEmojis(EmojiIndicesByAlias);
}
errorHandler = (e, isFatal) => {
diff --git a/app/reducers/views/emoji.js b/app/reducers/views/emoji.js
new file mode 100644
index 000000000..17e5b93e2
--- /dev/null
+++ b/app/reducers/views/emoji.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2018-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {combineReducers} from 'redux';
+import {UserTypes} from 'mattermost-redux/action_types';
+
+import {ViewTypes} from 'app/constants';
+
+function emojiPickerCustomPage(state = 0, action) {
+ switch (action.type) {
+ case ViewTypes.INCREMENT_EMOJI_PICKER_PAGE:
+ return state + 1;
+ case UserTypes.LOGOUT_SUCCESS:
+ return 0;
+ default:
+ return state;
+ }
+}
+
+export default combineReducers({
+ emojiPickerCustomPage
+});
+
diff --git a/app/reducers/views/index.js b/app/reducers/views/index.js
index 5c812c603..10146f083 100644
--- a/app/reducers/views/index.js
+++ b/app/reducers/views/index.js
@@ -16,6 +16,7 @@ import search from './search';
import selectServer from './select_server';
import team from './team';
import thread from './thread';
+import emoji from './emoji';
export default combineReducers({
announcement,
@@ -30,5 +31,6 @@ export default combineReducers({
search,
selectServer,
team,
- thread
+ thread,
+ emoji
});
diff --git a/app/screens/channel_info/channel_info.js b/app/screens/channel_info/channel_info.js
index 6830edfbe..b25e8fee8 100644
--- a/app/screens/channel_info/channel_info.js
+++ b/app/screens/channel_info/channel_info.js
@@ -43,7 +43,8 @@ class ChannelInfo extends PureComponent {
getChannelStats: PropTypes.func.isRequired,
leaveChannel: PropTypes.func.isRequired,
favoriteChannel: PropTypes.func.isRequired,
- unfavoriteChannel: PropTypes.func.isRequired
+ unfavoriteChannel: PropTypes.func.isRequired,
+ getCustomEmojisInText: PropTypes.func.isRequired
})
};
@@ -57,6 +58,7 @@ class ChannelInfo extends PureComponent {
componentDidMount() {
this.props.actions.getChannelStats(this.props.currentChannel.id);
+ this.props.actions.getCustomEmojisInText(this.props.currentChannel.header);
}
componentWillReceiveProps(nextProps) {
diff --git a/app/screens/channel_info/index.js b/app/screens/channel_info/index.js
index 37c6216a5..2932f7339 100644
--- a/app/screens/channel_info/index.js
+++ b/app/screens/channel_info/index.js
@@ -11,6 +11,7 @@ import {
} from 'app/actions/views/channel';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
+import {getCustomEmojisInText} from 'mattermost-redux/actions/emojis';
import {favoriteChannel, getChannelStats, deleteChannel, unfavoriteChannel} from 'mattermost-redux/actions/channels';
import {General} from 'mattermost-redux/constants';
import {
@@ -68,7 +69,8 @@ function mapDispatchToProps(dispatch) {
getChannelStats,
leaveChannel,
favoriteChannel,
- unfavoriteChannel
+ unfavoriteChannel,
+ getCustomEmojisInText
}, dispatch)
};
}
diff --git a/app/screens/select_server/index.js b/app/screens/select_server/index.js
index f3031b53f..c28623883 100644
--- a/app/screens/select_server/index.js
+++ b/app/screens/select_server/index.js
@@ -4,7 +4,7 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
-import {getPing, resetPing} from 'mattermost-redux/actions/general';
+import {getPing, resetPing, setServerVersion} from 'mattermost-redux/actions/general';
import {setLastUpgradeCheck} from 'app/actions/views/client_upgrade';
import {loadConfigAndLicense} from 'app/actions/views/root';
@@ -37,7 +37,8 @@ function mapDispatchToProps(dispatch) {
handleServerUrlChanged,
loadConfigAndLicense,
resetPing,
- setLastUpgradeCheck
+ setLastUpgradeCheck,
+ setServerVersion
}, dispatch)
};
}
diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js
index 3949ec69f..e1477267b 100644
--- a/app/screens/select_server/select_server.js
+++ b/app/screens/select_server/select_server.js
@@ -40,7 +40,8 @@ class SelectServer extends PureComponent {
handleServerUrlChanged: PropTypes.func.isRequired,
loadConfigAndLicense: PropTypes.func.isRequired,
resetPing: PropTypes.func.isRequired,
- setLastUpgradeCheck: PropTypes.func.isRequired
+ setLastUpgradeCheck: PropTypes.func.isRequired,
+ setServerVersion: PropTypes.func.isRequired
}).isRequired,
allowOtherServers: PropTypes.bool,
config: PropTypes.object,
@@ -214,7 +215,8 @@ class SelectServer extends PureComponent {
const {
getPing,
handleServerUrlChanged,
- loadConfigAndLicense
+ loadConfigAndLicense,
+ setServerVersion
} = this.props.actions;
this.setState({
@@ -245,6 +247,7 @@ class SelectServer extends PureComponent {
if (!result.error) {
loadConfigAndLicense();
+ setServerVersion(Client4.getServerVersion());
}
this.setState({
diff --git a/app/store/index.js b/app/store/index.js
index 2eab2cbd5..8dd2c3175 100644
--- a/app/store/index.js
+++ b/app/store/index.js
@@ -71,7 +71,7 @@ export default function configureAppStore(initialState) {
};
},
null,
- {whitelist: ['views']} // Only run this filter the views state (or any other entry that ends up being named views)
+ {whitelist: ['views']} // Only run this filter on the views state (or any other entry that ends up being named views)
);
const emojiBlackList = {nonExistentEmoji: true};
diff --git a/yarn.lock b/yarn.lock
index 1fbb3dfe8..83ff5b6aa 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1684,7 +1684,13 @@ colors@0.5.x:
version "0.5.1"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774"
-combined-stream@^1.0.5, combined-stream@~1.0.5:
+combined-stream@^1.0.5:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
+ dependencies:
+ delayed-stream "~1.0.0"
+
+combined-stream@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
dependencies:
@@ -4018,7 +4024,7 @@ makeerror@1.0.x:
mattermost-redux@mattermost/mattermost-redux:
version "1.2.0"
- resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/17237100e4ba15c73e158973044282d1dcb5562e"
+ resolved "https://codeload.github.com/mattermost/mattermost-redux/tar.gz/624b545605695ade03aa0604ef14ba44c4f06d9f"
dependencies:
deep-equal "1.0.1"
form-data "2.3.1"