From 84a2a0fab8efd4615a70d9155d5c57f6480c75eb Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 15 Feb 2018 17:38:45 -0500 Subject: [PATCH] ABC-123 Add handling for loading custom emojis asynchronously (#1406) * Add async handling of custom emoji to posts and channel info * Add server request to emoji autocomplete * Add custom emoji paging and server search to emoji picker * Fix android * Update mattermost-redux and revert temp change * Backwards compatibility fixes * Updates per feedback * Set server version when user is not logged in * Reset section index when search is cleared or cancelled * Some small fixes and upgrade mattermost-redux * Minor sorting changes --- app/actions/views/emoji.js | 10 ++ .../emoji_suggestion/emoji_suggestion.js | 48 ++++++-- .../autocomplete/emoji_suggestion/index.js | 8 +- app/components/emoji/emoji.js | 95 ++++++++-------- app/components/emoji/index.js | 32 +++++- app/components/emoji_picker/emoji_picker.js | 103 ++++++++++++++++-- .../emoji_picker/emoji_picker_row.js | 5 +- app/components/emoji_picker/index.js | 28 ++++- app/constants/view.js | 4 +- app/mattermost.js | 3 + app/reducers/views/emoji.js | 23 ++++ app/reducers/views/index.js | 4 +- app/screens/channel_info/channel_info.js | 4 +- app/screens/channel_info/index.js | 4 +- app/screens/select_server/index.js | 5 +- app/screens/select_server/select_server.js | 7 +- app/store/index.js | 2 +- yarn.lock | 10 +- 18 files changed, 303 insertions(+), 92 deletions(-) create mode 100644 app/reducers/views/emoji.js 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"