PLT-6714: add /settings command (#6716)

* add /settings command

* update receiver name
Этот коммит содержится в:
Chris
2017-06-23 15:47:24 -07:00
коммит произвёл Corey Hulen
родитель bf12e7e32c
Коммит 4df36a504c
11 изменённых файлов: 131 добавлений и 40 удалений

13
api/command_settings_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,13 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"testing"
)
func TestSettingsCommand(t *testing.T) {
th := Setup().InitBasic()
th.BasicClient.Must(th.BasicClient.Command(th.BasicChannel.Id, "/settings"))
}

42
app/command_settings.go Обычный файл
Просмотреть файл

@@ -0,0 +1,42 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"github.com/mattermost/platform/model"
goi18n "github.com/nicksnyder/go-i18n/i18n"
)
type SettingsProvider struct {
}
const (
CMD_SETTINGS = "settings"
)
func init() {
RegisterCommandProvider(&SettingsProvider{})
}
func (settings *SettingsProvider) GetTrigger() string {
return CMD_SETTINGS
}
func (settings *SettingsProvider) GetCommand(T goi18n.TranslateFunc) *model.Command {
return &model.Command{
Trigger: CMD_SETTINGS,
AutoComplete: true,
AutoCompleteDesc: T("api.command_settings.desc"),
AutoCompleteHint: "",
DisplayName: T("api.command_settings.name"),
}
}
func (settings *SettingsProvider) DoCommand(args *model.CommandArgs, message string) *model.CommandResponse {
// This command is handled client-side and shouldn't hit the server.
return &model.CommandResponse{
Text: args.T("api.command_settings.unsupported.app_error"),
ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
}
}

Просмотреть файл

@@ -659,6 +659,18 @@
"id": "api.command_online.success", "id": "api.command_online.success",
"translation": "You are now online" "translation": "You are now online"
}, },
{
"id": "api.command_settings.desc",
"translation": "Open the Account Settings dialog"
},
{
"id": "api.command_settings.name",
"translation": "settings"
},
{
"id": "api.command_settings.unsupported.app_error",
"translation": "The settings command is not supported on your device"
},
{ {
"id": "api.command_shortcuts.browser.channel_next", "id": "api.command_shortcuts.browser.channel_next",
"translation": "{{.ChannelNextCmd}}: Next channel in your history\n" "translation": "{{.ChannelNextCmd}}: Next channel in your history\n"

Просмотреть файл

@@ -9,6 +9,8 @@ import ChannelStore from 'stores/channel_store.jsx';
import * as ChannelUtils from 'utils/channel_utils.jsx'; import * as ChannelUtils from 'utils/channel_utils.jsx';
import PreferenceStore from 'stores/preference_store.jsx'; import PreferenceStore from 'stores/preference_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {loadProfilesForSidebar, loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions.jsx'; import {loadProfilesForSidebar, loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions.jsx';
import {trackEvent} from 'actions/diagnostics_actions.jsx'; import {trackEvent} from 'actions/diagnostics_actions.jsx';
@@ -66,9 +68,15 @@ export function goToChannel(channel) {
export function executeCommand(message, args, success, error) { export function executeCommand(message, args, success, error) {
let msg = message; let msg = message;
msg = msg.substring(0, msg.indexOf(' ')).toLowerCase() + msg.substring(msg.indexOf(' '), msg.length); let cmdLength = msg.indexOf(' ');
if (cmdLength < 0) {
cmdLength = msg.length;
}
const cmd = msg.substring(0, cmdLength).toLowerCase();
msg = cmd + msg.substring(cmdLength, msg.length);
if (message.indexOf('/shortcuts') !== -1) { switch (cmd) {
case '/shortcuts':
if (UserAgent.isMobile()) { if (UserAgent.isMobile()) {
const err = {message: Utils.localizeMessage('create_post.shortcutsNotSupported', 'Keyboard shortcuts are not supported on your device')}; const err = {message: Utils.localizeMessage('create_post.shortcutsNotSupported', 'Keyboard shortcuts are not supported on your device')};
error(err); error(err);
@@ -78,7 +86,12 @@ export function executeCommand(message, args, success, error) {
} else if (message.indexOf('mac') !== -1) { } else if (message.indexOf('mac') !== -1) {
msg = '/shortcuts'; msg = '/shortcuts';
} }
break;
case '/settings':
GlobalActions.showAccountSettingsModal();
return;
} }
Client.executeCommand(msg, args, success, Client.executeCommand(msg, args, success,
(err) => { (err) => {
AsyncClient.dispatchError(err, 'executeCommand'); AsyncClient.dispatchError(err, 'executeCommand');
@@ -86,7 +99,8 @@ export function executeCommand(message, args, success, error) {
if (error) { if (error) {
error(err); error(err);
} }
}); }
);
} }
export function setChannelAsRead(channelIdParam) { export function setChannelAsRead(channelIdParam) {

Просмотреть файл

@@ -197,6 +197,13 @@ export function emitUserCommentedEvent(post) {
}); });
} }
export function showAccountSettingsModal() {
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_ACCOUNT_SETTINGS_MODAL,
value: true
});
}
export function showDeletePostModal(post, commentCount = 0) { export function showDeletePostModal(post, commentCount = 0) {
AppDispatcher.handleViewAction({ AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_DELETE_POST_MODAL, type: ActionTypes.TOGGLE_DELETE_POST_MODAL,

Просмотреть файл

@@ -33,6 +33,7 @@ import store from 'stores/redux_store.jsx';
import {getPost} from 'mattermost-redux/selectors/entities/posts'; import {getPost} from 'mattermost-redux/selectors/entities/posts';
// Modals // Modals
import UserSettingsModal from 'components/user_settings/user_settings_modal.jsx';
import GetPostLinkModal from 'components/get_post_link_modal.jsx'; import GetPostLinkModal from 'components/get_post_link_modal.jsx';
import GetPublicLinkModal from 'components/get_public_link_modal.jsx'; import GetPublicLinkModal from 'components/get_public_link_modal.jsx';
import GetTeamInviteLinkModal from 'components/get_team_invite_link_modal.jsx'; import GetTeamInviteLinkModal from 'components/get_team_invite_link_modal.jsx';
@@ -218,6 +219,7 @@ export default class NeedsTeam extends React.Component {
<WebrtcSidebar/> <WebrtcSidebar/>
{content} {content}
<UserSettingsModal/>
<GetPostLinkModal/> <GetPostLinkModal/>
<GetPublicLinkModal/> <GetPublicLinkModal/>
<GetTeamInviteLinkModal/> <GetTeamInviteLinkModal/>

Просмотреть файл

@@ -13,7 +13,6 @@ import WebrtcStore from 'stores/webrtc_store.jsx';
import AboutBuildModal from './about_build_modal.jsx'; import AboutBuildModal from './about_build_modal.jsx';
import SidebarHeaderDropdownButton from './sidebar_header_dropdown_button.jsx'; import SidebarHeaderDropdownButton from './sidebar_header_dropdown_button.jsx';
import TeamMembersModal from './team_members_modal.jsx'; import TeamMembersModal from './team_members_modal.jsx';
import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import AddUsersToTeam from 'components/add_users_to_team'; import AddUsersToTeam from 'components/add_users_to_team';
import {Constants, WebrtcActionTypes} from 'utils/constants.jsx'; import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';
@@ -45,7 +44,7 @@ export default class SidebarHeaderDropdown extends React.Component {
this.handleAboutModal = this.handleAboutModal.bind(this); this.handleAboutModal = this.handleAboutModal.bind(this);
this.aboutModalDismissed = this.aboutModalDismissed.bind(this); this.aboutModalDismissed = this.aboutModalDismissed.bind(this);
this.toggleAccountSettingsModal = this.toggleAccountSettingsModal.bind(this); this.showAccountSettingsModal = this.showAccountSettingsModal.bind(this);
this.showAddUsersToTeamModal = this.showAddUsersToTeamModal.bind(this); this.showAddUsersToTeamModal = this.showAddUsersToTeamModal.bind(this);
this.hideAddUsersToTeamModal = this.hideAddUsersToTeamModal.bind(this); this.hideAddUsersToTeamModal = this.hideAddUsersToTeamModal.bind(this);
this.showInviteMemberModal = this.showInviteMemberModal.bind(this); this.showInviteMemberModal = this.showInviteMemberModal.bind(this);
@@ -54,7 +53,6 @@ export default class SidebarHeaderDropdown extends React.Component {
this.hideTeamMembersModal = this.hideTeamMembersModal.bind(this); this.hideTeamMembersModal = this.hideTeamMembersModal.bind(this);
this.onTeamChange = this.onTeamChange.bind(this); this.onTeamChange = this.onTeamChange.bind(this);
this.openAccountSettings = this.openAccountSettings.bind(this);
this.renderCustomEmojiLink = this.renderCustomEmojiLink.bind(this); this.renderCustomEmojiLink = this.renderCustomEmojiLink.bind(this);
@@ -66,7 +64,6 @@ export default class SidebarHeaderDropdown extends React.Component {
showAboutModal: false, showAboutModal: false,
showDropdown: false, showDropdown: false,
showTeamMembersModal: false, showTeamMembersModal: false,
showUserSettingsModal: false,
showAddUsersToTeamModal: false showAddUsersToTeamModal: false
}; };
} }
@@ -104,13 +101,12 @@ export default class SidebarHeaderDropdown extends React.Component {
this.setState({showAboutModal: false}); this.setState({showAboutModal: false});
} }
toggleAccountSettingsModal(e) { showAccountSettingsModal(e) {
e.preventDefault(); e.preventDefault();
this.setState({ this.setState({showDropdown: false});
showUserSettingsModal: !this.state.showUserSettingsModal,
showDropdown: false GlobalActions.showAccountSettingsModal();
});
} }
showAddUsersToTeamModal(e) { showAddUsersToTeamModal(e) {
@@ -160,7 +156,6 @@ export default class SidebarHeaderDropdown extends React.Component {
componentDidMount() { componentDidMount() {
TeamStore.addChangeListener(this.onTeamChange); TeamStore.addChangeListener(this.onTeamChange);
document.addEventListener('keydown', this.openAccountSettings);
} }
onTeamChange() { onTeamChange() {
@@ -174,13 +169,6 @@ export default class SidebarHeaderDropdown extends React.Component {
componentWillUnmount() { componentWillUnmount() {
$(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); $(ReactDOM.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown');
TeamStore.removeChangeListener(this.onTeamChange); TeamStore.removeChangeListener(this.onTeamChange);
document.removeEventListener('keydown', this.openAccountSettings);
}
openAccountSettings(e) {
if (Utils.cmdOrCtrlPressed(e) && e.shiftKey && e.keyCode === Constants.KeyCodes.A) {
this.toggleAccountSettingsModal(e);
}
} }
renderCustomEmojiLink() { renderCustomEmojiLink() {
@@ -527,7 +515,7 @@ export default class SidebarHeaderDropdown extends React.Component {
<a <a
id='accountSettings' id='accountSettings'
href='#' href='#'
onClick={this.toggleAccountSettingsModal} onClick={this.showAccountSettingsModal}
> >
<FormattedMessage <FormattedMessage
id='navbar_dropdown.accountSettings' id='navbar_dropdown.accountSettings'
@@ -634,10 +622,6 @@ export default class SidebarHeaderDropdown extends React.Component {
{about} {about}
{logoutDivider} {logoutDivider}
{logout} {logout}
<UserSettingsModal
show={this.state.showUserSettingsModal}
onModalDismissed={() => this.setState({showUserSettingsModal: false})}
/>
{teamMembersModal} {teamMembersModal}
<AboutBuildModal <AboutBuildModal
show={this.state.showAboutModal} show={this.state.showAboutModal}

Просмотреть файл

@@ -4,7 +4,6 @@
import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import TeamMembersModal from './team_members_modal.jsx'; import TeamMembersModal from './team_members_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx'; import ToggleModalButton from './toggle_modal_button.jsx';
import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import AboutBuildModal from './about_build_modal.jsx'; import AboutBuildModal from './about_build_modal.jsx';
import AddUsersToTeam from 'components/add_users_to_team'; import AddUsersToTeam from 'components/add_users_to_team';
@@ -46,7 +45,6 @@ export default class SidebarRightMenu extends React.Component {
this.getFlagged = this.getFlagged.bind(this); this.getFlagged = this.getFlagged.bind(this);
const state = this.getStateFromStores(); const state = this.getStateFromStores();
state.showUserSettingsModal = false;
state.showAboutModal = false; state.showAboutModal = false;
state.showAddUsersToTeamModal = false; state.showAddUsersToTeamModal = false;
@@ -503,7 +501,7 @@ export default class SidebarRightMenu extends React.Component {
<li> <li>
<a <a
href='#' href='#'
onClick={() => this.setState({showUserSettingsModal: true})} onClick={() => GlobalActions.showAccountSettingsModal()}
> >
<i className='icon fa fa-cog'/> <i className='icon fa fa-cog'/>
<FormattedMessage <FormattedMessage
@@ -555,10 +553,6 @@ export default class SidebarRightMenu extends React.Component {
</li> </li>
</ul> </ul>
</div> </div>
<UserSettingsModal
show={this.state.showUserSettingsModal}
onModalDismissed={() => this.setState({showUserSettingsModal: false})}
/>
<AboutBuildModal <AboutBuildModal
show={this.state.showAboutModal} show={this.state.showAboutModal}
onModalDismissed={this.aboutModalDismissed} onModalDismissed={this.aboutModalDismissed}

Просмотреть файл

@@ -7,8 +7,10 @@ import ConfirmModal from '../confirm_modal.jsx';
import UserSettings from './user_settings.jsx'; import UserSettings from './user_settings.jsx';
import SettingsSidebar from '../settings_sidebar.jsx'; import SettingsSidebar from '../settings_sidebar.jsx';
import ModalStore from 'stores/modal_store.jsx';
import UserStore from 'stores/user_store.jsx'; import UserStore from 'stores/user_store.jsx';
import * as Utils from 'utils/utils.jsx'; import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
import {Modal} from 'react-bootstrap'; import {Modal} from 'react-bootstrap';
@@ -49,8 +51,6 @@ const holders = defineMessages({
} }
}); });
import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
class UserSettingsModal extends React.Component { class UserSettingsModal extends React.Component {
@@ -62,6 +62,8 @@ class UserSettingsModal extends React.Component {
this.handleCollapse = this.handleCollapse.bind(this); this.handleCollapse = this.handleCollapse.bind(this);
this.handleConfirm = this.handleConfirm.bind(this); this.handleConfirm = this.handleConfirm.bind(this);
this.handleCancelConfirmation = this.handleCancelConfirmation.bind(this); this.handleCancelConfirmation = this.handleCancelConfirmation.bind(this);
this.handleToggle = this.handleToggle.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.closeModal = this.closeModal.bind(this); this.closeModal = this.closeModal.bind(this);
this.collapseModal = this.collapseModal.bind(this); this.collapseModal = this.collapseModal.bind(this);
@@ -75,7 +77,8 @@ class UserSettingsModal extends React.Component {
active_section: '', active_section: '',
showConfirmModal: false, showConfirmModal: false,
enforceFocus: true, enforceFocus: true,
currentUser: UserStore.getCurrentUser() currentUser: UserStore.getCurrentUser(),
show: false
}; };
this.requireConfirm = false; this.requireConfirm = false;
@@ -91,10 +94,14 @@ class UserSettingsModal extends React.Component {
componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;
UserStore.addChangeListener(this.onUserChanged); UserStore.addChangeListener(this.onUserChanged);
ModalStore.addModalListener(Constants.ActionTypes.TOGGLE_ACCOUNT_SETTINGS_MODAL, this.handleToggle);
document.addEventListener('keydown', this.handleKeyDown);
} }
componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
ModalStore.removeModalListener(Constants.ActionTypes.TOGGLE_ACCOUNT_SETTINGS_MODAL, this.handleToggle);
document.removeEventListener('keydown', this.handleKeyDown);
} }
componentDidUpdate() { componentDidUpdate() {
@@ -104,6 +111,20 @@ class UserSettingsModal extends React.Component {
} }
} }
handleKeyDown(e) {
if (Utils.cmdOrCtrlPressed(e) && e.shiftKey && e.keyCode === Constants.KeyCodes.A) {
this.setState({
show: true
});
}
}
handleToggle(value) {
this.setState({
show: value
});
}
// Called when the close button is pressed on the main modal // Called when the close button is pressed on the main modal
handleHide() { handleHide() {
if (this.requireConfirm) { if (this.requireConfirm) {
@@ -113,7 +134,9 @@ class UserSettingsModal extends React.Component {
return; return;
} }
this.props.onModalDismissed(); this.setState({
show: false
});
} }
// called after the dialog is fully hidden and faded out // called after the dialog is fully hidden and faded out
@@ -225,7 +248,7 @@ class UserSettingsModal extends React.Component {
return ( return (
<Modal <Modal
dialogClassName='settings-modal' dialogClassName='settings-modal'
show={this.props.show} show={this.state.show}
onHide={this.handleHide} onHide={this.handleHide}
onExited={this.handleHidden} onExited={this.handleHidden}
enforceFocus={this.state.enforceFocus} enforceFocus={this.state.enforceFocus}
@@ -280,9 +303,7 @@ class UserSettingsModal extends React.Component {
} }
UserSettingsModal.propTypes = { UserSettingsModal.propTypes = {
intl: intlShape.isRequired, intl: intlShape.isRequired
show: PropTypes.bool.isRequired,
onModalDismissed: PropTypes.func.isRequired
}; };
export default injectIntl(UserSettingsModal); export default injectIntl(UserSettingsModal);

Просмотреть файл

@@ -31,6 +31,7 @@ class ModalStoreClass extends EventEmitter {
const {type, value, ...args} = payload.action; //eslint-disable-line no-use-before-define const {type, value, ...args} = payload.action; //eslint-disable-line no-use-before-define
switch (type) { switch (type) {
case ActionTypes.TOGGLE_ACCOUNT_SETTINGS_MODAL:
case ActionTypes.TOGGLE_IMPORT_THEME_MODAL: case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL: case ActionTypes.TOGGLE_INVITE_MEMBER_MODAL:
case ActionTypes.TOGGLE_LEAVE_TEAM_MODAL: case ActionTypes.TOGGLE_LEAVE_TEAM_MODAL:

Просмотреть файл

@@ -166,6 +166,7 @@ export const ActionTypes = keyMirror({
USER_TYPING: null, USER_TYPING: null,
TOGGLE_ACCOUNT_SETTINGS_MODAL: null,
TOGGLE_IMPORT_THEME_MODAL: null, TOGGLE_IMPORT_THEME_MODAL: null,
TOGGLE_INVITE_MEMBER_MODAL: null, TOGGLE_INVITE_MEMBER_MODAL: null,
TOGGLE_LEAVE_TEAM_MODAL: null, TOGGLE_LEAVE_TEAM_MODAL: null,