diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c563f034..579d126df 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,7 +44,7 @@ jobs: paths: - ./node_modules - e2e-test: + e2e-build: macos: xcode: "11.2.1" @@ -80,11 +80,73 @@ jobs: yarn global add detox-cli yarn + - run: + name: Rebuild Detox framework cache + command: | + detox clean-framework-cache + detox build-framework-cache + - run: name: Build command: | detox build --configuration ios.sim.release + - persist_to_workspace: + root: . + paths: + - ios/build/Build/Products/Release-iphonesimulator/RocketChatRN.app + + - save_cache: + name: Save NPM cache + key: node-v1-mac-{{ checksum "yarn.lock" }} + paths: + - node_modules + + e2e-test: + macos: + xcode: "11.2.1" + + environment: + BASH_ENV: "~/.nvm/nvm.sh" + + steps: + - checkout + + - attach_workspace: + at: . + + - restore_cache: + name: Restore NPM cache + key: node-v1-mac-{{ checksum "yarn.lock" }} + + - run: + name: Install Node 8 + command: | + curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash + source ~/.nvm/nvm.sh + # https://github.com/creationix/nvm/issues/1394 + set +e + nvm install 8 + + - run: + name: Install appleSimUtils + command: | + brew update + brew tap wix/brew + brew install wix/brew/applesimutils + + - run: + name: Install NPM modules + command: | + yarn global add detox-cli + yarn + + - run: + name: Rebuild Detox framework cache + command: | + detox clean-framework-cache + detox build-framework-cache + - run: name: Test command: | @@ -96,9 +158,6 @@ jobs: paths: - node_modules - - store_artifacts: - path: /tmp/screenshots - android-build: <<: *defaults docker: @@ -359,9 +418,12 @@ workflows: type: approval requires: - lint-testunit - - e2e-test: + - e2e-build: requires: - e2e-hold + - e2e-test: + requires: + - e2e-build - ios-build: requires: diff --git a/README.md b/README.md index c55fa7c96..392aa0700 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ Readme will guide you on how to config. | Report message | ✅ | | Theming | ✅ | | Settings -> Review the App | ✅ | -| Settings -> Default Browser | ❌ | +| Settings -> Default Browser | ✅ | | Admin panel | ✅ | | Reply message from notification | ✅ | | Unread counter banner on message list | ✅ | diff --git a/__mocks__/rn-user-defaults.js b/__mocks__/rn-user-defaults.js new file mode 100644 index 000000000..71f26ca30 --- /dev/null +++ b/__mocks__/rn-user-defaults.js @@ -0,0 +1,4 @@ +export default { + set: () => '', + get: () => '' +}; diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index ed7afaf9e..758bf7345 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -1,5 +1,3638 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Storyshots Markdown list Markdown 1`] = ` + + + + Short Text + + + + + This is Rocket.Chat + + + + + Long Text + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + + + Line Break Text + + + + + a + + + + + + + b + + + + + + + c + + + + + + + + + + + d + + + + + + + + + + + + + + + e + + + + + Edited + + + + + This is edited + + + ( + edited + ) + + + + + Preview + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + + + a b c d e + + + @rocket.cat @name1 @all @here @unknown #general #unknown + + + Testing: 😃 👍 :marioparty: + + + + Mentions + + + + + rocket.cat + + + + + + name1 + + + + + + all + + + + + + here + + + + + + @unknown + + + + + Mentions with Real Name + + + + + Rocket Cat + + + + + + Name + + + + + + all + + + + + + here + + + + + + @unknown + + + + + Hashtag + + + + + test-channel + + + + + + #unknown + + + + + Emoji + + + + + Unicode: 😃😇👍 + + + + + Shortnames: + + + 😂 + + + 👍 + + + + + Custom emojis: + + + + + + + + + + + + + + + + + + + + 😃 + + + 👍 + + + + + + + + + + + Block Quote + + + + + + + + This is block quote + + + + + + + this is a normal line + + + + + Links + + + + + + Markdown link + + + + : + + + [description](url) + + + + + + Formatted Link + + + + : + + + <url|description> + + + + + Image + + + + + + + + Headers + + + + + Header 1 + + + + + Header 2 + + + + + Header 3 + + + + + Header 4 + + + + + Header 5 + + + + + Header 6 + + + + + Inline Code + + + + + This is + + + inline code + + + + + Code Block + + + + + Inline + + + code + + + has + + + back-ticks around + + + it. + + + + Code block + + + + + Lists + + + + + + • + + + + + + Open Source + + + + + + + + • + + + + + + Rocket.Chat + + + + + + ◦ + + + + + + nodejs + + + + + + + + ◦ + + + + + + ReactNative + + + + + + + + + Numbered Lists + + + + + + 1. + + + + + + Open Source + + + + + + + + 2. + + + + + + Rocket.Chat + + + + + + + Emphasis + + + + + Strong emphasis, aka bold, with + + + asterisks + + + or + + + underscores + + + + + Table + + + + + + + + + + + First Header + + + + + + + Second Header + + + + + + + + + Content from cell 1 + + + + + + + Content from cell 2 + + + + + + + + + Content in the first column + + + + + + + Content in the second column + + + + + + + + + Click to see full table + + + + + +`; + exports[`Storyshots Message list message 1`] = ` - I - - - 'm fine - - - ! + I'm fine! @@ -13158,6 +16831,7 @@ exports[`Storyshots Message list message 1`] = ` } > - I - - - 'm fine - - - ! + I'm fine! @@ -13550,6 +17174,7 @@ exports[`Storyshots Message list message 1`] = ` } > - I - - - 'm fine - - - ! + I'm fine! @@ -13970,6 +17545,7 @@ exports[`Storyshots Message list message 1`] = ` } > - I - - - 'm fine - - - ! + I'm fine! @@ -14141,6 +17665,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -14242,6 +17767,7 @@ exports[`Storyshots Message list message 1`] = ` } > - I - - - 'm fine - - - ! + I'm fine! @@ -14410,6 +17884,7 @@ exports[`Storyshots Message list message 1`] = ` } > - I - - - 'm fine - - - ! + I'm fine! @@ -14625,6 +18048,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -14726,6 +18150,7 @@ exports[`Storyshots Message list message 1`] = ` } > - - I - - - 'm fine - - - ! - + I'm fine! @@ -15615,6 +19002,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -15647,64 +19035,23 @@ exports[`Storyshots Message list message 1`] = ` > - - I - - - 'm fine - - - ! - + I'm fine! @@ -15887,6 +19234,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -15919,64 +19267,23 @@ exports[`Storyshots Message list message 1`] = ` > - - I - - - 'm fine - - - ! - + I'm fine! @@ -16159,6 +19466,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -16191,64 +19499,23 @@ exports[`Storyshots Message list message 1`] = ` > - - I - - - 'm fine - - - ! - + I'm fine! @@ -16431,6 +19698,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -16463,32 +19731,23 @@ exports[`Storyshots Message list message 1`] = ` > - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. @@ -16671,6 +19930,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -16703,32 +19963,23 @@ exports[`Storyshots Message list message 1`] = ` > - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. @@ -16911,6 +20162,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -17074,6 +20326,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -17175,6 +20428,7 @@ exports[`Storyshots Message list message 1`] = ` } > - - I - - - 'm fine - - - ! - + I'm fine! @@ -17583,6 +20797,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -17615,32 +20830,23 @@ exports[`Storyshots Message list message 1`] = ` > - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. @@ -17740,6 +20946,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -17991,6 +21198,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -18023,64 +21231,23 @@ exports[`Storyshots Message list message 1`] = ` > - - I - - - 'm fine - - - ! - + I'm fine! @@ -18180,6 +21347,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -18212,48 +21380,23 @@ exports[`Storyshots Message list message 1`] = ` > - - Cool - - - ! - + Cool! @@ -18353,6 +21496,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -18385,32 +21529,23 @@ exports[`Storyshots Message list message 1`] = ` > - - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. @@ -18510,6 +21645,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -18673,6 +21809,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -18988,6 +22125,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -19305,6 +22443,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -19622,6 +22761,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -19961,6 +23101,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -20321,6 +23462,7 @@ exports[`Storyshots Message list message 1`] = ` resizeMode="cover" source={ Object { + "headers": Object {}, "priority": "high", "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&width=50&height=50&rc_token=79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz&rc_uid=y8bd77ptZswPj3EW8", } @@ -20422,6 +23564,7 @@ exports[`Storyshots Message list message 1`] = ` } > - I - - - \` - - - m an inline-style link + I\`m an inline-style link ( @@ -45,6 +46,7 @@ const Avatar = React.memo(({ style={avatarStyle} source={{ uri, + headers: RocketChatSettings.customHeaders, priority: FastImage.priority.high }} /> diff --git a/app/containers/MessageActions.js b/app/containers/MessageActions.js index ab3677fce..79f75c7c4 100644 --- a/app/containers/MessageActions.js +++ b/app/containers/MessageActions.js @@ -63,6 +63,12 @@ class MessageActions extends React.Component { this.EDIT_INDEX = this.options.length - 1; } + // Mark as unread + if (message.u && message.u._id !== user.id) { + this.options.push(I18n.t('Mark_unread')); + this.UNREAD_INDEX = this.options.length - 1; + } + // Permalink this.options.push(I18n.t('Permalink')); this.PERMALINK_INDEX = this.options.length - 1; @@ -243,6 +249,30 @@ class MessageActions extends React.Component { editInit(message); } + handleUnread = async() => { + const { message, room } = this.props; + const { id: messageId, ts } = message; + const { rid } = room; + try { + const db = database.active; + const result = await RocketChat.markAsUnread({ messageId }); + if (result.success) { + const subCollection = db.collections.get('subscriptions'); + const subRecord = await subCollection.find(rid); + await db.action(async() => { + try { + await subRecord.update(sub => sub.lastOpen = ts); + } catch { + // do nothing + } + }); + Navigation.navigate('RoomsListView'); + } + } catch (e) { + log(e); + } + } + handleCopy = async() => { const { message } = this.props; await Clipboard.setString(message.msg); @@ -349,6 +379,9 @@ class MessageActions extends React.Component { case this.EDIT_INDEX: this.handleEdit(); break; + case this.UNREAD_INDEX: + this.handleUnread(); + break; case this.PERMALINK_INDEX: this.handlePermalink(); break; diff --git a/app/containers/MessageBox/Mentions/index.js b/app/containers/MessageBox/Mentions/index.js index 8b3649a2a..37c30c30b 100644 --- a/app/containers/MessageBox/Mentions/index.js +++ b/app/containers/MessageBox/Mentions/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import { FlatList } from 'react-native'; +import { FlatList, View } from 'react-native'; import PropTypes from 'prop-types'; import equal from 'deep-equal'; @@ -12,15 +12,16 @@ const Mentions = React.memo(({ mentions, trackingType, theme }) => { return null; } return ( - } - keyExtractor={item => item.id || item.username || item.command || item} - keyboardShouldPersistTaps='always' - /> + + } + keyExtractor={item => item.id || item.username || item.command || item} + keyboardShouldPersistTaps='always' + /> + ); }, (prevProps, nextProps) => { if (prevProps.theme !== nextProps.theme) { diff --git a/app/containers/MessageBox/ReplyPreview.js b/app/containers/MessageBox/ReplyPreview.js index 54d872ebd..a755598be 100644 --- a/app/containers/MessageBox/ReplyPreview.js +++ b/app/containers/MessageBox/ReplyPreview.js @@ -42,7 +42,7 @@ const styles = StyleSheet.create({ }); const ReplyPreview = React.memo(({ - message, Message_TimeFormat, baseUrl, username, useMarkdown, replying, getCustomEmoji, close, theme + message, Message_TimeFormat, baseUrl, username, replying, getCustomEmoji, close, theme }) => { if (!replying) { return null; @@ -67,7 +67,6 @@ const ReplyPreview = React.memo(({ username={username} getCustomEmoji={getCustomEmoji} numberOfLines={1} - useMarkdown={useMarkdown} preview theme={theme} /> @@ -79,7 +78,6 @@ const ReplyPreview = React.memo(({ ReplyPreview.propTypes = { replying: PropTypes.bool, - useMarkdown: PropTypes.bool, message: PropTypes.object.isRequired, Message_TimeFormat: PropTypes.string.isRequired, close: PropTypes.func.isRequired, @@ -90,7 +88,6 @@ ReplyPreview.propTypes = { }; const mapStateToProps = state => ({ - useMarkdown: state.markdown.useMarkdown, Message_TimeFormat: state.settings.Message_TimeFormat, baseUrl: state.server.server }); diff --git a/app/containers/MessageBox/RightButtons.android.js b/app/containers/MessageBox/RightButtons.android.js index 716d9a258..f05c1ede8 100644 --- a/app/containers/MessageBox/RightButtons.android.js +++ b/app/containers/MessageBox/RightButtons.android.js @@ -4,17 +4,20 @@ import PropTypes from 'prop-types'; import { SendButton, AudioButton, FileButton } from './buttons'; const RightButtons = React.memo(({ - theme, showSend, submit, recordAudioMessage, showFileActions + theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled, showFileActions }) => { if (showSend) { return ; } - return ( - <> - - - - ); + if (recordAudioMessageEnabled) { + return ( + <> + + + + ); + } + return ; }); RightButtons.propTypes = { @@ -22,6 +25,7 @@ RightButtons.propTypes = { showSend: PropTypes.bool, submit: PropTypes.func.isRequired, recordAudioMessage: PropTypes.func.isRequired, + recordAudioMessageEnabled: PropTypes.bool, showFileActions: PropTypes.func.isRequired }; diff --git a/app/containers/MessageBox/RightButtons.ios.js b/app/containers/MessageBox/RightButtons.ios.js index d0b16da90..62fbca5ee 100644 --- a/app/containers/MessageBox/RightButtons.ios.js +++ b/app/containers/MessageBox/RightButtons.ios.js @@ -4,19 +4,23 @@ import PropTypes from 'prop-types'; import { SendButton, AudioButton } from './buttons'; const RightButtons = React.memo(({ - theme, showSend, submit, recordAudioMessage + theme, showSend, submit, recordAudioMessage, recordAudioMessageEnabled }) => { if (showSend) { return ; } - return ; + if (recordAudioMessageEnabled) { + return ; + } + return null; }); RightButtons.propTypes = { theme: PropTypes.string, showSend: PropTypes.bool, submit: PropTypes.func.isRequired, - recordAudioMessage: PropTypes.func.isRequired + recordAudioMessage: PropTypes.func.isRequired, + recordAudioMessageEnabled: PropTypes.bool }; export default RightButtons; diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js index 2c5f49686..14decbbf9 100644 --- a/app/containers/MessageBox/index.js +++ b/app/containers/MessageBox/index.js @@ -85,13 +85,15 @@ class MessageBox extends Component { replyWithMention: PropTypes.bool, FileUpload_MediaTypeWhiteList: PropTypes.string, FileUpload_MaxFileSize: PropTypes.number, + Message_AudioRecorderEnabled: PropTypes.bool, getCustomEmoji: PropTypes.func, editCancel: PropTypes.func.isRequired, editRequest: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, typing: PropTypes.func, theme: PropTypes.string, - replyCancel: PropTypes.func + replyCancel: PropTypes.func, + navigation: PropTypes.object } constructor(props) { @@ -139,7 +141,7 @@ class MessageBox extends Component { async componentDidMount() { const db = database.active; - const { rid, tmid } = this.props; + const { rid, tmid, navigation } = this.props; let msg; try { const threadsCollection = db.collections.get('threads'); @@ -177,6 +179,12 @@ class MessageBox extends Component { if (isTablet) { EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands); } + + this.didFocusListener = navigation.addListener('didFocus', () => { + if (this.tracking && this.tracking.resetTracking) { + this.tracking.resetTracking(); + } + }); } componentWillReceiveProps(nextProps) { @@ -257,6 +265,9 @@ class MessageBox extends Component { if (this.getSlashCommands && this.getSlashCommands.stop) { this.getSlashCommands.stop(); } + if (this.didFocusListener && this.didFocusListener.remove) { + this.didFocusListener.remove(); + } if (isTablet) { EventEmiter.removeListener(KEY_COMMAND, this.handleCommands); } @@ -781,7 +792,7 @@ class MessageBox extends Component { recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview } = this.state; const { - editing, message, replying, replyCancel, user, getCustomEmoji, theme + editing, message, replying, replyCancel, user, getCustomEmoji, theme, Message_AudioRecorderEnabled } = this.props; const isAndroidTablet = isTablet && isAndroid ? { @@ -842,6 +853,7 @@ class MessageBox extends Component { showSend={showSend} submit={this.submit} recordAudioMessage={this.recordAudioMessage} + recordAudioMessageEnabled={Message_AudioRecorderEnabled} showFileActions={this.showFileActions} /> @@ -864,6 +876,7 @@ class MessageBox extends Component { }} > this.tracking = ref} renderContent={this.renderContent} kbInputRef={this.component} kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null} @@ -891,7 +904,8 @@ const mapStateToProps = state => ({ threadsEnabled: state.settings.Threads_enabled, user: getUserSelector(state), FileUpload_MediaTypeWhiteList: state.settings.FileUpload_MediaTypeWhiteList, - FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize + FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize, + Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled }); const dispatchToProps = ({ diff --git a/app/containers/UIKit/MultiSelect/Chips.js b/app/containers/UIKit/MultiSelect/Chips.js index 330a04d8f..dae9c797e 100644 --- a/app/containers/UIKit/MultiSelect/Chips.js +++ b/app/containers/UIKit/MultiSelect/Chips.js @@ -20,7 +20,7 @@ const Chip = ({ item, onSelect, theme }) => ( > <> {item.imageUrl ? : null} - {textParser([item.text])} + {textParser([item.text])} diff --git a/app/containers/UIKit/MultiSelect/index.js b/app/containers/UIKit/MultiSelect/index.js index 47e6141b4..553e92c61 100644 --- a/app/containers/UIKit/MultiSelect/index.js +++ b/app/containers/UIKit/MultiSelect/index.js @@ -41,6 +41,12 @@ export const MultiSelect = React.memo(({ const [currentValue, setCurrentValue] = useState(''); const [showContent, setShowContent] = useState(false); + useEffect(() => { + if (values) { + select(values); + } + }, [values]); + useEffect(() => { setOpen(showContent); }, [showContent]); diff --git a/app/containers/UIKit/MultiSelect/styles.js b/app/containers/UIKit/MultiSelect/styles.js index f0034364a..bac42a168 100644 --- a/app/containers/UIKit/MultiSelect/styles.js +++ b/app/containers/UIKit/MultiSelect/styles.js @@ -34,6 +34,7 @@ export default StyleSheet.create({ }, item: { height: 48, + maxWidth: '85%', alignItems: 'center', flexDirection: 'row' }, @@ -59,7 +60,7 @@ export default StyleSheet.create({ chips: { flexDirection: 'row', flexWrap: 'wrap', - marginRight: 16 + marginRight: 50 }, chip: { flexDirection: 'row', @@ -72,6 +73,7 @@ export default StyleSheet.create({ }, chipText: { paddingHorizontal: 8, + flexShrink: 1, ...sharedStyles.textMedium, fontSize: 14 }, diff --git a/app/containers/markdown/AtMention.js b/app/containers/markdown/AtMention.js index 8980777e4..513dd8805 100644 --- a/app/containers/markdown/AtMention.js +++ b/app/containers/markdown/AtMention.js @@ -7,7 +7,7 @@ import { themes } from '../../constants/colors'; import styles from './styles'; const AtMention = React.memo(({ - mention, mentions, username, navToRoomInfo, preview, style = [], theme + mention, mentions, username, navToRoomInfo, style = [], useRealName, theme }) => { let mentionStyle = { ...styles.mention, color: themes[theme].buttonText }; if (mention === 'all' || mention === 'here') { @@ -27,22 +27,23 @@ const AtMention = React.memo(({ }; } + const user = mentions && mentions.length && mentions.find(m => m.username === mention); + const handlePress = () => { - const index = mentions.findIndex(m => m.username === mention); const navParam = { t: 'd', - rid: mentions[index]._id + rid: user && user._id }; navToRoomInfo(navParam); }; - if (mentions && mentions.length && mentions.findIndex(m => m.username === mention) !== -1) { + if (user) { return ( - {mention} + {useRealName && user.name ? user.name : user.username} ); } @@ -59,7 +60,7 @@ AtMention.propTypes = { username: PropTypes.string, navToRoomInfo: PropTypes.func, style: PropTypes.array, - preview: PropTypes.bool, + useRealName: PropTypes.bool, theme: PropTypes.string, mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) }; diff --git a/app/containers/markdown/Hashtag.js b/app/containers/markdown/Hashtag.js index 594192cad..655d3a737 100644 --- a/app/containers/markdown/Hashtag.js +++ b/app/containers/markdown/Hashtag.js @@ -7,7 +7,7 @@ import { themes } from '../../constants/colors'; import styles from './styles'; const Hashtag = React.memo(({ - hashtag, channels, navToRoomInfo, preview, style = [], theme + hashtag, channels, navToRoomInfo, style = [], theme }) => { const handlePress = () => { const index = channels.findIndex(channel => channel.name === hashtag); @@ -21,8 +21,8 @@ const Hashtag = React.memo(({ if (channels && channels.length && channels.findIndex(channel => channel.name === hashtag) !== -1) { return ( {hashtag} @@ -39,7 +39,6 @@ Hashtag.propTypes = { hashtag: PropTypes.string, navToRoomInfo: PropTypes.func, style: PropTypes.array, - preview: PropTypes.bool, theme: PropTypes.string, channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) }; diff --git a/app/containers/markdown/Link.js b/app/containers/markdown/Link.js index c974a0f25..1c1577110 100644 --- a/app/containers/markdown/Link.js +++ b/app/containers/markdown/Link.js @@ -10,7 +10,7 @@ import EventEmitter from '../../utils/events'; import I18n from '../../i18n'; const Link = React.memo(({ - children, link, preview, theme + children, link, theme }) => { const handlePress = () => { if (!link) { @@ -28,13 +28,9 @@ const Link = React.memo(({ // if you have a [](https://rocket.chat) render https://rocket.chat return ( { childLength !== 0 ? children : link } @@ -44,8 +40,7 @@ const Link = React.memo(({ Link.propTypes = { children: PropTypes.node, link: PropTypes.string, - theme: PropTypes.string, - preview: PropTypes.bool + theme: PropTypes.string }; export default Link; diff --git a/app/containers/markdown/index.js b/app/containers/markdown/index.js index 40af9e171..990b2fc66 100644 --- a/app/containers/markdown/index.js +++ b/app/containers/markdown/index.js @@ -3,6 +3,7 @@ import { Text, Image } from 'react-native'; import { Parser, Node } from 'commonmark'; import Renderer from 'commonmark-react-renderer'; import PropTypes from 'prop-types'; +import removeMarkdown from 'remove-markdown'; import shortnameToUnicode from '../../utils/shortnameToUnicode'; import I18n from '../../i18n'; @@ -18,6 +19,7 @@ import MarkdownEmoji from './Emoji'; import MarkdownTable from './Table'; import MarkdownTableRow from './TableRow'; import MarkdownTableCell from './TableCell'; +import mergeTextNodes from './mergeTextNodes'; import styles from './styles'; @@ -71,22 +73,23 @@ class Markdown extends PureComponent { tmid: PropTypes.string, isEdited: PropTypes.bool, numberOfLines: PropTypes.number, - useMarkdown: PropTypes.bool, customEmojis: PropTypes.bool, + useRealName: PropTypes.bool, channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), navToRoomInfo: PropTypes.func, preview: PropTypes.bool, theme: PropTypes.string, + testID: PropTypes.string, style: PropTypes.array }; constructor(props) { super(props); - this.renderer = this.createRenderer(props.preview); + this.renderer = this.createRenderer(); } - createRenderer = (preview = false) => new Renderer({ + createRenderer = () => new Renderer({ renderers: { text: this.renderText, @@ -119,7 +122,7 @@ class Markdown extends PureComponent { table_row: this.renderTableRow, table_cell: this.renderTableCell, - editedIndicator: preview ? () => null : this.renderEditedIndicator + editedIndicator: this.renderEditedIndicator }, renderParagraphsInLists: true }); @@ -141,19 +144,16 @@ class Markdown extends PureComponent { renderText = ({ context, literal }) => { const { - numberOfLines, preview, style = [] + numberOfLines, style = [] } = this.props; const defaultStyle = [ - this.isMessageContainsOnlyEmoji && !preview ? styles.textBig : {}, + this.isMessageContainsOnlyEmoji ? styles.textBig : {}, ...context.map(type => styles[type]) ]; return ( {literal} @@ -162,18 +162,16 @@ class Markdown extends PureComponent { } renderCodeInline = ({ literal }) => { - const { preview, theme, style = [] } = this.props; + const { theme, style = [] } = this.props; return ( @@ -183,18 +181,16 @@ class Markdown extends PureComponent { }; renderCodeBlock = ({ literal }) => { - const { preview, theme, style = [] } = this.props; + const { theme, style = [] } = this.props; return ( @@ -221,11 +217,10 @@ class Markdown extends PureComponent { }; renderLink = ({ children, href }) => { - const { preview, theme } = this.props; + const { theme } = this.props; return ( {children} @@ -235,14 +230,13 @@ class Markdown extends PureComponent { renderHashtag = ({ hashtag }) => { const { - channels, navToRoomInfo, style, preview, theme + channels, navToRoomInfo, style, theme } = this.props; return ( @@ -251,15 +245,15 @@ class Markdown extends PureComponent { renderAtMention = ({ mentionName }) => { const { - username, mentions, navToRoomInfo, preview, style, theme + username, mentions, navToRoomInfo, useRealName, style, theme } = this.props; return ( @@ -268,13 +262,13 @@ class Markdown extends PureComponent { renderEmoji = ({ emojiName, literal }) => { const { - getCustomEmoji, baseUrl, customEmojis = true, preview, style, theme + getCustomEmoji, baseUrl, customEmojis = true, style, theme } = this.props; return ( { - const { preview, theme } = this.props; - if (preview) { - return children; - } + const { theme } = this.props; return ( {children} @@ -367,7 +358,7 @@ class Markdown extends PureComponent { render() { const { - msg, useMarkdown = true, numberOfLines, preview = false, theme + msg, numberOfLines, preview = false, theme, style = [], testID } = this.props; if (!msg) { @@ -379,23 +370,22 @@ class Markdown extends PureComponent { // Ex: '[ ](https://open.rocket.chat/group/test?msg=abcdef) Test' // Return: 'Test' m = m.replace(/^\[([\s]]*)\]\(([^)]*)\)\s/, '').trim(); - m = shortnameToUnicode(m); if (preview) { - m = m.split('\n').reduce((lines, line) => `${ lines } ${ line }`, ''); - const ast = parser.parse(m); - return this.renderer.render(ast); + m = m.replace(/\n+/g, ' '); + m = shortnameToUnicode(m); + m = removeMarkdown(m); + return ( + + {m} + + ); } - if (!useMarkdown && !preview) { - return {m}; - } - - const ast = parser.parse(m); + let ast = parser.parse(m); + ast = mergeTextNodes(ast); this.isMessageContainsOnlyEmoji = isOnlyEmoji(m) && emojiCount(m) <= 3; - this.editedMessage(ast); - return this.renderer.render(ast); } } diff --git a/app/containers/markdown/mergeTextNodes.js b/app/containers/markdown/mergeTextNodes.js new file mode 100644 index 000000000..768395236 --- /dev/null +++ b/app/containers/markdown/mergeTextNodes.js @@ -0,0 +1,27 @@ +// TODO: should we add this to our commonmark fork instead? +// we loop through nodes and try to merge all texts +export default function mergeTextNodes(ast) { + // https://github.com/commonmark/commonmark.js/blob/master/lib/node.js#L268 + const walker = ast.walker(); + let event; + // eslint-disable-next-line no-cond-assign + while (event = walker.next()) { + const { entering, node } = event; + const { type } = node; + if (entering && type === 'text') { + while (node._next && node._next.type === 'text') { + const next = node._next; + node.literal += next.literal; + node._next = next._next; + if (node._next) { + node._next._prev = node; + } + if (node._parent._lastChild === next) { + node._parent._lastChild = node; + } + } + walker.resumeAt(node, false); + } + } + return ast; +} diff --git a/app/containers/message/Attachments.js b/app/containers/message/Attachments.js index dc1f06847..cf3f6106c 100644 --- a/app/containers/message/Attachments.js +++ b/app/containers/message/Attachments.js @@ -8,7 +8,7 @@ import Video from './Video'; import Reply from './Reply'; const Attachments = React.memo(({ - attachments, timeFormat, user, baseUrl, useMarkdown, showAttachment, getCustomEmoji, theme + attachments, timeFormat, user, baseUrl, showAttachment, getCustomEmoji, theme }) => { if (!attachments || attachments.length === 0) { return null; @@ -16,17 +16,17 @@ const Attachments = React.memo(({ return attachments.map((file, index) => { if (file.image_url) { - return ; + return ; } if (file.audio_url) { - return