Этот коммит содержится в:
=Corey Hulen
2015-10-16 09:12:30 -07:00
родитель 6a1755d2e3 9c045f0d4b
Коммит 8b2d7f391a
37 изменённых файлов: 642 добавлений и 165 удалений

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

@@ -16,6 +16,7 @@ const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
const KeyCodes = Constants.KeyCodes;
export default class CreatePost extends React.Component {
constructor(props) {
@@ -35,6 +36,7 @@ export default class CreatePost extends React.Component {
this.removePreview = this.removePreview.bind(this);
this.onChange = this.onChange.bind(this);
this.getFileCount = this.getFileCount.bind(this);
this.handleArrowUp = this.handleArrowUp.bind(this);
PostStore.clearDraftUploads();
@@ -172,7 +174,7 @@ export default class CreatePost extends React.Component {
}
}
postMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) {
if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) {
e.preventDefault();
ReactDOM.findDOMNode(this.refs.textbox).blur();
this.handleSubmit(e);
@@ -292,6 +294,27 @@ export default class CreatePost extends React.Component {
const draft = PostStore.getDraft(channelId);
return draft.previews.length + draft.uploadsInProgress.length;
}
handleArrowUp(e) {
if (e.keyCode === KeyCodes.UP && this.state.messageText === '') {
e.preventDefault();
const channelId = ChannelStore.getCurrentId();
const lastPost = PostStore.getCurrentUsersLatestPost(channelId);
if (!lastPost) {
return;
}
var type = (lastPost.root_id && lastPost.root_id.length > 0) ? 'Comment' : 'Post';
AppDispatcher.handleViewAction({
type: ActionTypes.RECIEVED_EDIT_POST,
refocusId: '#post_textbox',
title: type,
message: lastPost.message,
postId: lastPost.id,
channelId: lastPost.channel_id
});
}
}
render() {
let serverError = null;
if (this.state.serverError) {
@@ -336,6 +359,7 @@ export default class CreatePost extends React.Component {
<Textbox
onUserInput={this.handleUserInput}
onKeyPress={this.postMsgKeyPress}
onKeyDown={this.handleArrowUp}
onHeightChange={this.resizePostHolder}
messageText={this.state.messageText}
createMessage='Write a message...'

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

@@ -5,6 +5,7 @@ var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Textbox = require('./textbox.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
var PostStore = require('../stores/post_store.jsx');
export default class EditPostModal extends React.Component {
constructor() {
@@ -14,6 +15,7 @@ export default class EditPostModal extends React.Component {
this.handleEditInput = this.handleEditInput.bind(this);
this.handleEditKeyPress = this.handleEditKeyPress.bind(this);
this.handleUserInput = this.handleUserInput.bind(this);
this.handleEditPostEvent = this.handleEditPostEvent.bind(this);
this.state = {editText: '', title: '', post_id: '', channel_id: '', comments: 0, refocusId: ''};
}
@@ -35,16 +37,15 @@ export default class EditPostModal extends React.Component {
Client.updatePost(updatedPost,
function success() {
AsyncClient.getPosts(this.state.channel_id);
AsyncClient.getPosts(updatedPost.channel_id);
window.scrollTo(0, 0);
}.bind(this),
},
function error(err) {
AsyncClient.dispatchError(err, 'updatePost');
}
);
$('#edit_post').modal('hide');
$(this.state.refocusId).focus();
}
handleEditInput(editMessage) {
this.setState({editText: editMessage});
@@ -59,6 +60,18 @@ export default class EditPostModal extends React.Component {
handleUserInput(e) {
this.setState({editText: e.target.value});
}
handleEditPostEvent(options) {
this.setState({
editText: options.message || '',
title: options.title || '',
post_id: options.postId || '',
channel_id: options.channelId || '',
comments: options.comments || 0,
refocusId: options.refocusId || ''
});
$(React.findDOMNode(this.refs.modal)).modal('show');
}
componentDidMount() {
var self = this;
@@ -68,12 +81,29 @@ export default class EditPostModal extends React.Component {
$(ReactDOM.findDOMNode(this.refs.modal)).on('show.bs.modal', function onShow(e) {
var button = e.relatedTarget;
if (!button) {
return;
}
self.setState({editText: $(button).attr('data-message'), title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments'), refocusId: $(button).attr('data-refoucsid')});
});
$(ReactDOM.findDOMNode(this.refs.modal)).on('shown.bs.modal', function onShown() {
self.refs.editbox.resize();
$('#edit_textbox').get(0).focus();
});
$(React.findDOMNode(this.refs.modal)).on('hide.bs.modal', function onShown() {
if (self.state.refocusId !== '') {
setTimeout(() => {
$(self.state.refocusId).get(0).focus();
});
}
});
PostStore.addEditPostListener(this.handleEditPostEvent);
}
componentWillUnmount() {
PostStore.removeEditPostListener(this.handleEditPostEvent);
}
render() {
var error = (<div className='form-group'><br /></div>);

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

@@ -11,7 +11,24 @@ var AboutBuildModal = require('./about_build_modal.jsx');
var Constants = require('../utils/constants.jsx');
function getStateFromStores() {
return {teams: UserStore.getTeams()};
let teams = [];
let teamsObject = UserStore.getTeams();
for (let teamId in teamsObject) {
if (teamsObject.hasOwnProperty(teamId)) {
teams.push(teamsObject[teamId]);
}
}
teams.sort(function sortByDisplayName(teamA, teamB) {
let teamADisplayName = teamA.display_name.toLowerCase();
let teamBDisplayName = teamB.display_name.toLowerCase();
if (teamADisplayName < teamBDisplayName) {
return -1;
} else if (teamADisplayName > teamBDisplayName) {
return 1;
}
return 0;
});
return {teams};
}
export default class NavbarDropdown extends React.Component {
@@ -154,9 +171,9 @@ export default class NavbarDropdown extends React.Component {
</li>
);
this.state.teams.forEach((teamName) => {
if (teamName !== this.props.teamName) {
teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>{'Switch to ' + teamName}</a></li>);
this.state.teams.forEach((team) => {
if (team.name !== this.props.teamName) {
teams.push(<li key={team.name}><a href={Utils.getWindowLocationOrigin() + '/' + team.name}>{'Switch to ' + team.display_name}</a></li>);
}
});
}

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

@@ -4,6 +4,7 @@
var PostStore = require('../stores/post_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var PreferenceStore = require('../stores/preference_store.jsx');
var UserProfile = require('./user_profile.jsx');
var AsyncClient = require('../utils/async_client.jsx');
var Post = require('./post.jsx');
@@ -105,6 +106,7 @@ export default class PostList extends React.Component {
PostStore.clearUnseenDeletedPosts(this.props.channelId);
PostStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onTimeChange);
PreferenceStore.addChangeListener(this.onTimeChange);
SocketStore.addChangeListener(this.onSocketChange);
const postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));
@@ -156,6 +158,7 @@ export default class PostList extends React.Component {
PostStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onTimeChange);
SocketStore.removeChangeListener(this.onSocketChange);
PreferenceStore.removeChangeListener(this.onTimeChange);
$('body').off('click.userpopover');
$(window).off('resize');
var postHolder = $(ReactDOM.findDOMNode(this.refs.postlist));

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

@@ -3,6 +3,7 @@
var PostStore = require('../stores/post_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var PreferenceStore = require('../stores/preference_store.jsx');
var utils = require('../utils/utils.jsx');
var SearchBox = require('./search_bar.jsx');
var CreateComment = require('./create_comment.jsx');
@@ -18,6 +19,7 @@ export default class RhsThread extends React.Component {
this.onChange = this.onChange.bind(this);
this.onChangeAll = this.onChangeAll.bind(this);
this.forceUpdateInfo = this.forceUpdateInfo.bind(this);
this.state = this.getStateFromStores();
}
@@ -43,6 +45,7 @@ export default class RhsThread extends React.Component {
componentDidMount() {
PostStore.addSelectedPostChangeListener(this.onChange);
PostStore.addChangeListener(this.onChangeAll);
PreferenceStore.addChangeListener(this.forceUpdateInfo);
this.resize();
$(window).resize(function resize() {
this.resize();
@@ -57,6 +60,16 @@ export default class RhsThread extends React.Component {
componentWillUnmount() {
PostStore.removeSelectedPostChangeListener(this.onChange);
PostStore.removeChangeListener(this.onChangeAll);
PreferenceStore.removeChangeListener(this.forceUpdateInfo);
}
forceUpdateInfo() {
if (this.state.postList) {
for (var postId in this.state.postList.posts) {
if (this.refs[postId]) {
this.refs[postId].forceUpdate();
}
}
}
}
onChange() {
var newState = this.getStateFromStores();
@@ -174,6 +187,7 @@ export default class RhsThread extends React.Component {
/>
<div className='post-right__scroll'>
<RootPost
ref={rootPost.id}
post={rootPost}
commentCount={postsArray.length}
/>

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

@@ -132,11 +132,7 @@ export default class Sidebar extends React.Component {
SocketStore.addChangeListener(this.onSocketChange);
PreferenceStore.addChangeListener(this.onChange);
AsyncClient.getDirectChannelPreferences();
if ($(window).width() > 768) {
$('.nav-pills__container').perfectScrollbar();
}
$('.nav-pills__container').perfectScrollbar();
this.updateTitle();
this.updateUnreadIndicators();

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

@@ -9,6 +9,7 @@ const ErrorStore = require('../stores/error_store.jsx');
const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
const KeyCodes = Constants.KeyCodes;
export default class Textbox extends React.Component {
constructor(props) {
@@ -148,8 +149,10 @@ export default class Textbox extends React.Component {
this.doProcessMentions = true;
}
if (e.keyCode === 8) {
if (e.keyCode === KeyCodes.BACKSPACE) {
this.handleBackspace(e);
} else if (this.props.onKeyDown) {
this.props.onKeyDown(e);
}
}
@@ -318,5 +321,6 @@ Textbox.propTypes = {
onUserInput: React.PropTypes.func.isRequired,
onKeyPress: React.PropTypes.func.isRequired,
onHeightChange: React.PropTypes.func,
createMessage: React.PropTypes.string.isRequired
createMessage: React.PropTypes.string.isRequired,
onKeyDown: React.PropTypes.func
};

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

@@ -9,6 +9,7 @@ var GeneralTab = require('./user_settings_general.jsx');
var AppearanceTab = require('./user_settings_appearance.jsx');
var DeveloperTab = require('./user_settings_developer.jsx');
var IntegrationsTab = require('./user_settings_integrations.jsx');
var DisplayTab = require('./user_settings_display.jsx');
export default class UserSettings extends React.Component {
constructor(props) {
@@ -98,6 +99,17 @@ export default class UserSettings extends React.Component {
/>
</div>
);
} else if (this.props.activeTab === 'display') {
return (
<div>
<DisplayTab
user={this.state.user}
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
updateTab={this.props.updateTab}
/>
</div>
);
}
return <div/>;

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

@@ -0,0 +1,168 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import { savePreferences } from '../../utils/client.jsx';
import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import Constants from '../../utils/constants.jsx';
import PreferenceStore from '../../stores/preference_store.jsx';
function getDisplayStateFromStores() {
const militaryTime = PreferenceStore.getPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', {value: 'false'});
return {militaryTime: militaryTime.value};
}
export default class UserSettingsDisplay extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleClockRadio = this.handleClockRadio.bind(this);
this.updateSection = this.updateSection.bind(this);
this.handleClose = this.handleClose.bind(this);
this.state = getDisplayStateFromStores();
}
handleSubmit() {
const preference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', this.state.militaryTime);
savePreferences([preference],
() => {
PreferenceStore.emitChange();
this.updateSection('');
},
(err) => {
this.setState({serverError: err.message});
}
);
}
handleClockRadio(militaryTime) {
this.setState({militaryTime: militaryTime});
}
updateSection(section) {
this.setState(getDisplayStateFromStores());
this.props.updateSection(section);
}
handleClose() {
this.updateSection('');
}
componentDidMount() {
$('#user_settings').on('hidden.bs.modal', this.handleClose);
}
componentWillUnmount() {
$('#user_settings').off('hidden.bs.modal', this.handleClose);
}
render() {
const serverError = this.state.serverError || null;
let clockSection;
if (this.props.activeSection === 'clock') {
let clockFormat = [false, false];
if (this.state.militaryTime === 'true') {
clockFormat[1] = true;
} else {
clockFormat[0] = true;
}
const handleUpdateClockSection = (e) => {
this.updateSection('');
e.preventDefault();
};
const inputs = [
<div key='userDisplayClockOptions'>
<div className='radio'>
<label>
<input
type='radio'
checked={clockFormat[0]}
onChange={this.handleClockRadio.bind(this, 'false')}
>
12-hour clock (example: 4:00 PM)
</input>
</label>
<br/>
</div>
<div className='radio'>
<label>
<input
type='radio'
checked={clockFormat[1]}
onChange={this.handleClockRadio.bind(this, 'true')}
>
24-hour clock (example: 16:00)
</input>
</label>
<br/>
</div>
<div><br/>{'Select how you prefer time displayed.'}</div>
</div>
];
clockSection = (
<SettingItemMax
title='Clock Display'
inputs={inputs}
submit={this.handleSubmit}
server_error={serverError}
updateSection={handleUpdateClockSection}
/>
);
} else {
let describe = '';
if (this.state.militaryTime === 'true') {
describe = '24-hour clock (example: 16:00)';
} else {
describe = '12-hour clock (example: 4:00 PM)';
}
const handleUpdateClockSection = () => {
this.props.updateSection('clock');
};
clockSection = (
<SettingItemMin
title='Clock Display'
describe={describe}
updateSection={handleUpdateClockSection}
/>
);
}
return (
<div>
<div className='modal-header'>
<button
type='button'
className='close'
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>{'×'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
<i className='modal-back'></i>
{'Display Settings'}
</h4>
</div>
<div className='user-settings'>
<h3 className='tab-header'>{'Display Settings'}</h3>
<div className='divider-dark first'/>
{clockSection}
<div className='divider-dark'/>
</div>
</div>
);
}
}
UserSettingsDisplay.propTypes = {
user: React.PropTypes.object,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
activeSection: React.PropTypes.string
};

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

@@ -41,6 +41,7 @@ export default class UserSettingsModal extends React.Component {
if (global.window.mm_config.EnableIncomingWebhooks === 'true') {
tabs.push({name: 'integrations', uiName: 'Integrations', icon: 'glyphicon glyphicon-transfer'});
}
tabs.push({name: 'display', uiName: 'Display', icon: 'glyphicon glyphicon-eye-open'});
return (
<div