From b9360217e661def5612f94abb318f571c89ca85c Mon Sep 17 00:00:00 2001 From: Youssef Muhamad Date: Fri, 6 Mar 2020 11:19:03 -0300 Subject: [PATCH] [NEW] Filter system messages per room (#1815) Co-authored-by: Djorkaeff Alexandre Co-authored-by: Diego Mello --- app/containers/UIKit/MultiSelect/Chips.js | 2 +- app/containers/UIKit/MultiSelect/index.js | 6 ++ app/containers/UIKit/MultiSelect/styles.js | 4 +- app/i18n/locales/en.js | 17 ++++ app/i18n/locales/pt-BR.js | 17 ++++ app/lib/database/model/Subscription.js | 2 + app/lib/database/model/migrations.js | 11 +++ app/lib/database/schema/app.js | 5 +- .../helpers/mergeSubscriptionsRooms.js | 1 + app/utils/messageTypes.js | 42 +++++++++ app/views/RoomInfoEditView/SwitchContainer.js | 87 ++++++++++--------- app/views/RoomInfoEditView/index.js | 72 +++++++++++++-- app/views/RoomInfoEditView/styles.js | 9 ++ app/views/RoomView/List.js | 27 +++++- app/views/RoomView/index.js | 6 +- 15 files changed, 250 insertions(+), 58 deletions(-) create mode 100644 app/utils/messageTypes.js 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/i18n/locales/en.js b/app/i18n/locales/en.js index d5cf54295..f632e7b8d 100644 --- a/app/i18n/locales/en.js +++ b/app/i18n/locales/en.js @@ -210,6 +210,21 @@ export default { Has_joined_the_channel: 'Has joined the channel', Has_joined_the_conversation: 'Has joined the conversation', Has_left_the_channel: 'Has left the channel', + Hide_System_Messages: 'Hide System Messages', + Hide_type_messages: 'Hide "{{type}}" messages', + Message_HideType_uj: 'User Join', + Message_HideType_ul: 'User Leave', + Message_HideType_ru: 'User Removed', + Message_HideType_au: 'User Added', + Message_HideType_mute_unmute: 'User Muted / Unmuted', + Message_HideType_r: 'Room Name Changed', + Message_HideType_ut: 'User Joined Conversation', + Message_HideType_wm: 'Welcome', + Message_HideType_rm: 'Message Removed', + Message_HideType_subscription_role_added: 'Was Set Role', + Message_HideType_subscription_role_removed: 'Role No Longer Defined', + Message_HideType_room_archived: 'Room Archived', + Message_HideType_room_unarchived: 'Room Unarchived', In_app: 'In-app', IN_APP_AND_DESKTOP: 'IN-APP AND DESKTOP', In_App_and_Desktop_Alert_info: 'Displays a banner at the top of the screen when app is open, and displays a notification on desktop', @@ -295,6 +310,7 @@ export default { Only_authorized_users_can_write_new_messages: 'Only authorized users can write new messages', Open_emoji_selector: 'Open emoji selector', Open_Source_Communication: 'Open Source Communication', + Overwrites_the_server_configuration_and_use_room_config: 'Overwrites the server configuration and use room config', Password: 'Password', Permalink_copied_to_clipboard: 'Permalink copied to clipboard!', Pin: 'Pin', @@ -454,6 +470,7 @@ export default { Username_is_empty: 'Username is empty', Username: 'Username', Username_or_email: 'Username or email', + Uses_server_configuration: 'Uses server configuration', Validating: 'Validating', Verify_email_title: 'Registration Succeeded!', Verify_email_desc: 'We have sent you an email to confirm your registration. If you do not receive an email shortly, please come back and try again.', diff --git a/app/i18n/locales/pt-BR.js b/app/i18n/locales/pt-BR.js index a10a770c2..bee747f8d 100644 --- a/app/i18n/locales/pt-BR.js +++ b/app/i18n/locales/pt-BR.js @@ -201,6 +201,21 @@ export default { Has_joined_the_channel: 'Entrou no canal', Has_joined_the_conversation: 'Entrou na conversa', Has_left_the_channel: 'Saiu da conversa', + Hide_System_Messages: 'Esconder mensagens do sistema', + Hide_type_messages: 'Esconder mensagens de "{{type}}"', + Message_HideType_uj: 'Utilizador Entrou', + Message_HideType_ul: 'Utilizador Saiu', + Message_HideType_ru: 'Utilizador Removido', + Message_HideType_au: 'Utilizador adicionado', + Message_HideType_mute_unmute: 'Utilizador Silenciado', + Message_HideType_r: 'Nome da sala alterado', + Message_HideType_ut: 'Utilizador adicionado ao bate-papo', + Message_HideType_wm: 'Bem Vindo', + Message_HideType_rm: 'Mensagem Removida', + Message_HideType_subscription_role_added: 'Papel atribuído', + Message_HideType_subscription_role_removed: 'Papel removido', + Message_HideType_room_archived: 'Sala arquivada', + Message_HideType_room_unarchived: 'Sala desarquivada', In_app: 'No app', Invisible: 'Invisível', Invite: 'Convidar', @@ -271,6 +286,7 @@ export default { Only_authorized_users_can_write_new_messages: 'Somente usuários autorizados podem escrever novas mensagens', Open_emoji_selector: 'Abrir seletor de emoji', Open_Source_Communication: 'Comunicação Open Source', + Overwrites_the_server_configuration_and_use_room_config: 'Substituir a configuração do servidor e usar a configuração da sala', Password: 'Senha', Permalink_copied_to_clipboard: 'Link-permanente copiado para a área de transferência!', Pin: 'Fixar', @@ -409,6 +425,7 @@ export default { Username_is_empty: 'Usuário está vazio', Username: 'Usuário', Username_or_email: 'Usuário ou email', + Uses_server_configuration: 'Usar configuração do servidor', Verify_email_title: 'Registrado com sucesso!', Verify_email_desc: 'Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.', Video_call: 'Chamada de vídeo', diff --git a/app/lib/database/model/Subscription.js b/app/lib/database/model/Subscription.js index 630c640b3..d40193c52 100644 --- a/app/lib/database/model/Subscription.js +++ b/app/lib/database/model/Subscription.js @@ -89,4 +89,6 @@ export default class Subscription extends Model { @children('thread_messages') threadMessages; @field('hide_unread_status') hideUnreadStatus; + + @json('sys_mes', sanitizer) sysMes; } diff --git a/app/lib/database/model/migrations.js b/app/lib/database/model/migrations.js index ced859bda..14313585e 100644 --- a/app/lib/database/model/migrations.js +++ b/app/lib/database/model/migrations.js @@ -51,6 +51,17 @@ export default schemaMigrations({ ] }) ] + }, + { + toVersion: 6, + steps: [ + addColumns({ + table: 'subscriptions', + columns: [ + { name: 'sys_mes', type: 'string', isOptional: true } + ] + }) + ] } ] }); diff --git a/app/lib/database/schema/app.js b/app/lib/database/schema/app.js index de98df29b..20741b573 100644 --- a/app/lib/database/schema/app.js +++ b/app/lib/database/schema/app.js @@ -1,7 +1,7 @@ import { appSchema, tableSchema } from '@nozbe/watermelondb'; export default appSchema({ - version: 5, + version: 6, tables: [ tableSchema({ name: 'subscriptions', @@ -39,7 +39,8 @@ export default appSchema({ { name: 'jitsi_timeout', type: 'number', isOptional: true }, { name: 'auto_translate', type: 'boolean', isOptional: true }, { name: 'auto_translate_language', type: 'string' }, - { name: 'hide_unread_status', type: 'boolean', isOptional: true } + { name: 'hide_unread_status', type: 'boolean', isOptional: true }, + { name: 'sys_mes', type: 'string', isOptional: true } ] }), tableSchema({ diff --git a/app/lib/methods/helpers/mergeSubscriptionsRooms.js b/app/lib/methods/helpers/mergeSubscriptionsRooms.js index 02a7a6612..6af040f5b 100644 --- a/app/lib/methods/helpers/mergeSubscriptionsRooms.js +++ b/app/lib/methods/helpers/mergeSubscriptionsRooms.js @@ -32,6 +32,7 @@ export const merge = (subscription, room) => { } else { subscription.muted = []; } + subscription.sysMes = room.sysMes; } if (!subscription.name) { diff --git a/app/utils/messageTypes.js b/app/utils/messageTypes.js new file mode 100644 index 000000000..f874a437a --- /dev/null +++ b/app/utils/messageTypes.js @@ -0,0 +1,42 @@ +export const MessageTypeValues = [ + { + value: 'uj', + text: 'Message_HideType_uj' + }, { + value: 'ul', + text: 'Message_HideType_ul' + }, { + value: 'ru', + text: 'Message_HideType_ru' + }, { + value: 'au', + text: 'Message_HideType_au' + }, { + value: 'mute_unmute', + text: 'Message_HideType_mute_unmute' + }, { + value: 'r', + text: 'Message_HideType_r' + }, { + value: 'ut', + text: 'Message_HideType_ut' + }, { + value: 'wm', + text: 'Message_HideType_wm' + }, { + value: 'rm', + text: 'Message_HideType_rm' + }, { + value: 'subscription_role_added', + text: 'Message_HideType_subscription_role_added' + }, { + value: 'subscription_role_removed', + text: 'Message_HideType_subscription_role_removed' + }, { + value: 'room_archived', + text: 'Message_HideType_room_archived' + }, { + value: 'room_unarchived', + text: 'Message_HideType_room_unarchived' + } +]; diff --git a/app/views/RoomInfoEditView/SwitchContainer.js b/app/views/RoomInfoEditView/SwitchContainer.js index 5b877552d..de1aed98f 100644 --- a/app/views/RoomInfoEditView/SwitchContainer.js +++ b/app/views/RoomInfoEditView/SwitchContainer.js @@ -5,45 +5,50 @@ import PropTypes from 'prop-types'; import styles from './styles'; import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; -export default class SwitchContainer extends React.PureComponent { - static propTypes = { - value: PropTypes.bool, - disabled: PropTypes.bool, - leftLabelPrimary: PropTypes.string, - leftLabelSecondary: PropTypes.string, - rightLabelPrimary: PropTypes.string, - rightLabelSecondary: PropTypes.string, - onValueChange: PropTypes.func, - theme: PropTypes.string, - testID: PropTypes.string - } +const SwitchContainer = React.memo(({ + children, value, disabled, onValueChange, leftLabelPrimary, leftLabelSecondary, rightLabelPrimary, rightLabelSecondary, theme, testID, labelContainerStyle, leftLabelStyle +}) => ( + <> + + {leftLabelPrimary && ( + + {leftLabelPrimary} + {leftLabelSecondary} + + )} + + {rightLabelPrimary && ( + + {rightLabelPrimary} + {rightLabelSecondary} + + )} + + {children} + + +)); - render() { - const { - value, disabled, onValueChange, leftLabelPrimary, leftLabelSecondary, rightLabelPrimary, rightLabelSecondary, theme, testID - } = this.props; - return ( - [ - - - {leftLabelPrimary} - {leftLabelSecondary} - - - - {rightLabelPrimary} - {rightLabelSecondary} - - , - - ] - ); - } -} +SwitchContainer.propTypes = { + value: PropTypes.bool, + disabled: PropTypes.bool, + leftLabelPrimary: PropTypes.string, + leftLabelSecondary: PropTypes.string, + rightLabelPrimary: PropTypes.string, + rightLabelSecondary: PropTypes.string, + onValueChange: PropTypes.func, + theme: PropTypes.string, + testID: PropTypes.string, + labelContainerStyle: PropTypes.object, + leftLabelStyle: PropTypes.object, + children: PropTypes.any +}; + +export default SwitchContainer; diff --git a/app/views/RoomInfoEditView/index.js b/app/views/RoomInfoEditView/index.js index 58e8f1248..fe217ec1a 100644 --- a/app/views/RoomInfoEditView/index.js +++ b/app/views/RoomInfoEditView/index.js @@ -6,6 +6,9 @@ import { import { connect } from 'react-redux'; import { SafeAreaView } from 'react-navigation'; import equal from 'deep-equal'; +import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; +import isEqual from 'lodash/isEqual'; +import semver from 'semver'; import database from '../../lib/database'; import { deleteRoomInit as deleteRoomInitAction } from '../../actions/room'; @@ -27,6 +30,8 @@ import StatusBar from '../../containers/StatusBar'; import { themedHeader } from '../../utils/navigation'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; +import { MultiSelect } from '../../containers/UIKit/MultiSelect'; +import { MessageTypeValues } from '../../utils/messageTypes'; const PERMISSION_SET_READONLY = 'set-readonly'; const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly'; @@ -52,6 +57,7 @@ class RoomInfoEditView extends React.Component { static propTypes = { navigation: PropTypes.object, deleteRoomInit: PropTypes.func, + serverVersion: PropTypes.string, theme: PropTypes.string }; @@ -70,7 +76,9 @@ class RoomInfoEditView extends React.Component { t: false, ro: false, reactWhenReadOnly: false, - archived: false + archived: false, + systemMessages: [], + enableSysMes: false }; this.loadRoom(); } @@ -117,7 +125,7 @@ class RoomInfoEditView extends React.Component { init = (room) => { const { - name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired + name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, sysMes } = room; // fake password just to user knows about it this.randomValue = random(15); @@ -131,7 +139,9 @@ class RoomInfoEditView extends React.Component { ro, reactWhenReadOnly, joinCode: joinCodeRequired ? this.randomValue : '', - archived: room.archived + archived: room.archived, + systemMessages: sysMes, + enableSysMes: sysMes && sysMes.length > 0 }); } @@ -148,7 +158,7 @@ class RoomInfoEditView extends React.Component { formIsChanged = () => { const { - room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode + room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages, enableSysMes } = this.state; const { joinCodeRequired } = room; return !(room.name === name @@ -159,13 +169,15 @@ class RoomInfoEditView extends React.Component { && room.t === 'p' === t && room.ro === ro && room.reactWhenReadOnly === reactWhenReadOnly + && isEqual(room.sysMes, systemMessages) + && enableSysMes === (room.sysMes && room.sysMes.length > 0) ); } submit = async() => { Keyboard.dismiss(); const { - room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode + room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages } = this.state; this.setState({ saving: true }); @@ -210,6 +222,10 @@ class RoomInfoEditView extends React.Component { params.reactWhenReadOnly = reactWhenReadOnly; } + if (!isEqual(room.sysMes, systemMessages)) { + params.systemMessages = systemMessages; + } + // Join Code if (this.randomValue !== joinCode) { params.joinCode = joinCode; @@ -298,12 +314,34 @@ class RoomInfoEditView extends React.Component { return (permissions[PERMISSION_ARCHIVE] || permissions[PERMISSION_UNARCHIVE]); }; + renderSystemMessages = () => { + const { systemMessages, enableSysMes } = this.state; + const { theme } = this.props; + + if (!enableSysMes) { + return null; + } + + return ( + ({ value: m.value, text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) } }))} + onChange={({ value }) => this.setState({ systemMessages: value })} + placeholder={{ text: I18n.t('Hide_System_Messages') }} + value={systemMessages} + context={BLOCK_CONTEXT.FORM} + multiselect + theme={theme} + /> + ); + } + render() { const { - name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived + name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes } = this.state; - const { theme } = this.props; + const { serverVersion, theme } = this.props; const { dangerColor } = themes[theme]; + return ( this.setState(({ systemMessages }) => ({ enableSysMes: value, systemMessages: value ? systemMessages : [] }))} + labelContainerStyle={styles.hideSystemMessages} + leftLabelStyle={styles.systemMessagesLabel} + > + {this.renderSystemMessages()} + + ) : null} ({ + serverVersion: state.server.version +}); + const mapDispatchToProps = dispatch => ({ deleteRoomInit: (rid, t) => dispatch(deleteRoomInitAction(rid, t)) }); -export default connect(null, mapDispatchToProps)(withTheme(RoomInfoEditView)); +export default connect(mapStateToProps, mapDispatchToProps)(withTheme(RoomInfoEditView)); diff --git a/app/views/RoomInfoEditView/styles.js b/app/views/RoomInfoEditView/styles.js index b63ae9987..bf857940f 100644 --- a/app/views/RoomInfoEditView/styles.js +++ b/app/views/RoomInfoEditView/styles.js @@ -63,5 +63,14 @@ export default StyleSheet.create({ broadcast: { ...sharedStyles.textAlignCenter, ...sharedStyles.textSemibold + }, + hideSystemMessages: { + alignItems: 'flex-start' + }, + systemMessagesLabel: { + textAlign: 'left' + }, + switchMargin: { + marginBottom: 16 } }); diff --git a/app/views/RoomView/List.js b/app/views/RoomView/List.js index 238fb319d..5ca13644b 100644 --- a/app/views/RoomView/List.js +++ b/app/views/RoomView/List.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import orderBy from 'lodash/orderBy'; import { Q } from '@nozbe/watermelondb'; import moment from 'moment'; +import isEqual from 'lodash/isEqual'; import styles from './styles'; import database from '../../lib/database'; @@ -62,9 +63,15 @@ class List extends React.Component { // eslint-disable-next-line react/sort-comp async init() { - const { rid, tmid, hideSystemMessages = [] } = this.props; + const { rid, tmid } = this.props; const db = database.active; + // handle servers with version < 3.0.0 + let { hideSystemMessages = [] } = this.props; + if (!Array.isArray(hideSystemMessages)) { + hideSystemMessages = []; + } + if (tmid) { try { this.thread = await db.collections @@ -103,6 +110,12 @@ class List extends React.Component { } } + // eslint-disable-next-line react/sort-comp + reload = () => { + this.unsubscribeMessages(); + this.init(); + } + // this.state.loading works for this.onEndReached and RoomView.init static getDerivedStateFromProps(props, state) { if (props.loading !== state.loading) { @@ -115,7 +128,7 @@ class List extends React.Component { shouldComponentUpdate(nextProps, nextState) { const { loading, end, refreshing } = this.state; - const { theme } = this.props; + const { hideSystemMessages, theme } = this.props; if (theme !== nextProps.theme) { return true; } @@ -128,9 +141,19 @@ class List extends React.Component { if (refreshing !== nextState.refreshing) { return true; } + if (!isEqual(hideSystemMessages, nextProps.hideSystemMessages)) { + return true; + } return false; } + componentDidUpdate(prevProps) { + const { hideSystemMessages } = this.props; + if (!isEqual(hideSystemMessages, prevProps.hideSystemMessages)) { + this.reload(); + } + } + componentWillUnmount() { this.unsubscribeMessages(); if (this.interaction && this.interaction.cancel) { diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index 1da342dc2..2dd13f2d9 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -69,7 +69,7 @@ const stateAttrsUpdate = [ 'reacting', 'showAnnouncementModal' ]; -const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'muted', 'jitsiTimeout', 'announcement']; +const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'muted', 'jitsiTimeout', 'announcement', 'sysMes']; class RoomView extends React.Component { static navigationOptions = ({ navigation, screenProps }) => { @@ -961,7 +961,7 @@ class RoomView extends React.Component { const { user, baseUrl, theme, navigation, Hide_System_Messages } = this.props; - const { rid, t } = room; + const { rid, t, sysMes } = room; return ( {this.renderAnnouncementModal()} {this.renderFooter()}