Merge branch 'master' into PLT-12

Этот коммит содержится в:
=Corey Hulen
2015-09-03 13:39:22 -07:00
родитель 48ca3a4007 4f0c06114b
Коммит 05d95d80a8
47 изменённых файлов: 436 добавлений и 259 удалений

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

@@ -38,7 +38,7 @@ travis:
@cd web/react/ && npm install @cd web/react/ && npm install
@echo Checking for style guide compliance @echo Checking for style guide compliance
cd web/react && $(ESLINT) components/* dispatcher/* pages/* stores/* utils/* cd web/react && $(ESLINT) --quiet components/* dispatcher/* pages/* stores/* utils/*
@$(GO) build $(GOFLAGS) ./... @$(GO) build $(GOFLAGS) ./...
@$(GO) install $(GOFLAGS) -a ./... @$(GO) install $(GOFLAGS) -a ./...

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

@@ -54,7 +54,7 @@ There are multiple ways to install Mattermost depending on your needs.
#### Development Install #### Development Install
- [Developer Machine Setup](scripts/README_DEV.md) - Setup your local machine development environment using Docker on Mac OSX or Ubuntu. - [Developer Machine Setup](doc/install/dev-setup.md) - Setup your local machine development environment using Docker on Mac OSX or Ubuntu.
#### Production Deployment #### Production Deployment

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

@@ -4,7 +4,9 @@
"jsx": true, "jsx": true,
"blockBindings": true, "blockBindings": true,
"modules": true, "modules": true,
"classes": true "classes": true,
"arrowFunctions": true,
"defaultParams": true,
}, },
"plugins": [ "plugins": [
"react" "react"
@@ -21,36 +23,36 @@
"rules": { "rules": {
"comma-dangle": [2, "never"], "comma-dangle": [2, "never"],
"no-cond-assign": [2, "except-parens"], "no-cond-assign": [2, "except-parens"],
"no-console": 1, "no-console": 2,
"no-constant-condition": 1, "no-constant-condition": 2,
"no-debugger": 1, "no-debugger": 2,
"no-dupe-args": 2, "no-dupe-args": 2,
"no-dupe-keys": 2, "no-dupe-keys": 2,
"no-duplicate-case": 2, "no-duplicate-case": 2,
"no-empty": 1, "no-empty": 2,
"no-ex-assign": 1, "no-ex-assign": 2,
"no-extra-semi": 2, "no-extra-semi": 2,
"no-func-assign": 1, "no-func-assign": 2,
"no-inner-declarations": 0, "no-inner-declarations": 0,
"no-invalid-regexp": 2, "no-invalid-regexp": 2,
"no-irregular-whitespace": 2, "no-irregular-whitespace": 2,
"no-unexpected-multiline": 2,
"no-unreachable": 2, "no-unreachable": 2,
"valid-typeof": 2, "valid-typeof": 2,
"no-unexpected-multiline": 2,
"block-scoped-var": 1, "block-scoped-var": 2,
"complexity": [0, 8], "complexity": [1, 8],
"consistent-return": 2, "consistent-return": 2,
"curly": [2, "all"], "curly": [2, "all"],
"dot-notation": 2,
"dot-location": [2, "object"], "dot-location": [2, "object"],
"dot-notation": 2,
"eqeqeq": [2, "smart"], "eqeqeq": [2, "smart"],
"guard-for-in": 1, "guard-for-in": 2,
"no-alert": 1, "no-alert": 2,
"no-array-constructor": 2, "no-array-constructor": 2,
"no-caller": 2, "no-caller": 2,
"no-div-regex": 1, "no-div-regex": 2,
"no-else-return": 1, "no-else-return": 2,
"no-eval": 2, "no-eval": 2,
"no-extend-native": 2, "no-extend-native": 2,
"no-extra-bind": 2, "no-extra-bind": 2,
@@ -58,7 +60,7 @@
"no-implied-eval": 2, "no-implied-eval": 2,
"no-iterator": 2, "no-iterator": 2,
"no-labels": 2, "no-labels": 2,
"no-lone-blocks": 1, "no-lone-blocks": 2,
"no-loop-func": 2, "no-loop-func": 2,
"no-multi-spaces": [2, { "exceptions": { "Property": false } }], "no-multi-spaces": [2, { "exceptions": { "Property": false } }],
"no-multi-str": 0, "no-multi-str": 0,
@@ -78,10 +80,11 @@
"no-self-compare": 2, "no-self-compare": 2,
"no-sequences": 2, "no-sequences": 2,
"no-throw-literal": 2, "no-throw-literal": 2,
"no-unused-expressions": 2,
"no-undef-init": 2, "no-undef-init": 2,
"no-unused-expressions": 2,
"no-useless-concat": 1,
"no-void": 2, "no-void": 2,
"no-warning-comments": 0, "no-warning-comments": 1,
"no-with": 2, "no-with": 2,
"radix": 2, "radix": 2,
"vars-on-top": 0, "vars-on-top": 0,
@@ -135,14 +138,33 @@
"space-unary-ops": [2, { "words": true, "nonwords": false }], "space-unary-ops": [2, { "words": true, "nonwords": false }],
"wrap-regex": 2, "wrap-regex": 2,
// ES6 stuff
"arrow-parens": [2, "always"],
"arrow-spacing": [2, { "before": true, "after": true }],
"constructor-super": 2,
"generator-star-spacing": [2, {"before": false, "after": true}],
"no-class-assign": 2,
"no-const-assign": 2,
"no-dupe-class-members": 2,
"no-this-before-super": 2,
"no-var": 0,
"object-shorthand": [1, "always"],
"prefer-arrow-callback": 1,
"prefer-const": 1,
"prefer-spread": 2,
"prefer-reflect": 1,
"prefer-template": 0,
"require-yield": 2,
// React Specific // React Specific
"react/display-name": [2, { "acceptTranspilerName": true }], "react/display-name": [2, { "acceptTranspilerName": true }],
"react/jsx-boolean-value": [2, "always"], "react/jsx-boolean-value": [2, "always"],
"react/jsx-closing-bracket-location": [2, { "location": "tag-aligned" }],
"react/jsx-curly-spacing": [2, "never"], "react/jsx-curly-spacing": [2, "never"],
"react/jsx-indent-props": [2, 4],
"react/jsx-max-props-per-line": [2, { "maximum": 1 }], "react/jsx-max-props-per-line": [2, { "maximum": 1 }],
// SOON "react/jsx-indent-props": [2, 4],
"react/jsx-no-duplicate-props": [2, { "ignoreCase": false }], "react/jsx-no-duplicate-props": [2, { "ignoreCase": false }],
"react/jsx-no-literals": 0, "react/jsx-no-literals": 1,
"react/jsx-no-undef": 2, "react/jsx-no-undef": 2,
"react/jsx-quotes": [2, "single", "avoid-escape"], "react/jsx-quotes": [2, "single", "avoid-escape"],
"react/jsx-uses-react": 2, "react/jsx-uses-react": 2,
@@ -151,10 +173,11 @@
"react/no-did-mount-set-state": 2, "react/no-did-mount-set-state": 2,
"react/no-did-update-set-state": 2, "react/no-did-update-set-state": 2,
"react/no-multi-comp": 2, "react/no-multi-comp": 2,
"react/no-set-state": 0,
"react/no-unknown-property": 2, "react/no-unknown-property": 2,
"react/prop-types": 2, "react/prop-types": 2,
"react/sort-comp": 0,
"react/self-closing-comp": 2, "react/self-closing-comp": 2,
"react/sort-comp": 0,
"react/wrap-multilines": 2 "react/wrap-multilines": 2
} }
} }

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

@@ -34,27 +34,27 @@ export default class CommandList extends React.Component {
return ( return (
<div <div
className='modal fade' className='modal fade'
ref='modal' ref='modal'
id='channel_info' id='channel_info'
tabIndex='-1' tabIndex='-1'
role='dialog' role='dialog'
aria-hidden='true' aria-hidden='true'
> >
<div className='modal-dialog'> <div className='modal-dialog'>
<div className='modal-content'> <div className='modal-content'>
<div className='modal-header'> <div className='modal-header'>
<button <button
type='button' type='button'
className='close' className='close'
data-dismiss='modal' data-dismiss='modal'
aria-label='Close' aria-label='Close'
> >
<span aria-hidden='true'>&times;</span> <span aria-hidden='true'>&times;</span>
</button> </button>
<h4 <h4
className='modal-title' className='modal-title'
id='myModalLabel' id='myModalLabel'
> >
<span className='name'>{channel.display_name}</span> <span className='name'>{channel.display_name}</span>
</h4> </h4>
@@ -75,9 +75,9 @@ export default class CommandList extends React.Component {
</div> </div>
<div className='modal-footer'> <div className='modal-footer'>
<button <button
type='button' type='button'
className='btn btn-default' className='btn btn-default'
data-dismiss='modal' data-dismiss='modal'
>Close</button> >Close</button>
</div> </div>
</div> </div>

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

@@ -146,8 +146,8 @@ export default class ChannelInviteModal extends React.Component {
aria-hidden='true' aria-hidden='true'
> >
<div <div
className='modal-dialog' className='modal-dialog'
role='document' role='document'
> >
<div className='modal-content'> <div className='modal-content'>
<div className='modal-header'> <div className='modal-header'>

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

@@ -162,6 +162,13 @@ export default class ChannelNotifications extends React.Component {
e.preventDefault(); e.preventDefault();
}.bind(this); }.bind(this);
let curChannel = ChannelStore.get(this.state.channelId);
let extraInfo = (<span>These settings will override the global notification settings</span>);
if (curChannel && curChannel.display_name) {
extraInfo = (<span>These settings will override the global notification settings for the <b>{curChannel.display_name}</b> channel</span>);
}
return ( return (
<SettingItemMax <SettingItemMax
title='Send desktop notifications' title='Send desktop notifications'
@@ -169,6 +176,7 @@ export default class ChannelNotifications extends React.Component {
submit={this.handleUpdate} submit={this.handleUpdate}
server_error={serverError} server_error={serverError}
updateSection={handleUpdateSection} updateSection={handleUpdateSection}
extraInfo={extraInfo}
/> />
); );
} }

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

@@ -117,7 +117,6 @@ export default class CreatePost extends React.Component {
Client.createPost(post, channel, Client.createPost(post, channel,
function handlePostSuccess(data) { function handlePostSuccess(data) {
this.resizePostHolder();
AsyncClient.getPosts(); AsyncClient.getPosts();
let member = ChannelStore.getMember(channel.id); let member = ChannelStore.getMember(channel.id);
@@ -129,7 +128,7 @@ export default class CreatePost extends React.Component {
type: ActionTypes.RECIEVED_POST, type: ActionTypes.RECIEVED_POST,
post: data post: data
}); });
}.bind(this), },
function handlePostError(err) { function handlePostError(err) {
let state = {}; let state = {};
@@ -149,9 +148,6 @@ export default class CreatePost extends React.Component {
); );
} }
} }
componentDidUpdate() {
this.resizePostHolder();
}
postMsgKeyPress(e) { postMsgKeyPress(e) {
if (e.which === 13 && !e.shiftKey && !e.altKey) { if (e.which === 13 && !e.shiftKey && !e.altKey) {
e.preventDefault(); e.preventDefault();
@@ -166,7 +162,6 @@ export default class CreatePost extends React.Component {
} }
} }
handleUserInput(messageText) { handleUserInput(messageText) {
this.resizePostHolder();
this.setState({messageText: messageText}); this.setState({messageText: messageText});
let draft = PostStore.getCurrentDraft(); let draft = PostStore.getCurrentDraft();
@@ -296,7 +291,8 @@ export default class CreatePost extends React.Component {
<FilePreview <FilePreview
files={this.state.previews} files={this.state.previews}
onRemove={this.removePreview} onRemove={this.removePreview}
uploadsInProgress={this.state.uploadsInProgress} /> uploadsInProgress={this.state.uploadsInProgress}
/>
); );
} }
@@ -317,11 +313,13 @@ export default class CreatePost extends React.Component {
<Textbox <Textbox
onUserInput={this.handleUserInput} onUserInput={this.handleUserInput}
onKeyPress={this.postMsgKeyPress} onKeyPress={this.postMsgKeyPress}
onHeightChange={this.resizePostHolder}
messageText={this.state.messageText} messageText={this.state.messageText}
createMessage='Write a message...' createMessage='Write a message...'
channelId={this.state.channelId} channelId={this.state.channelId}
id='post_textbox' id='post_textbox'
ref='textbox' /> ref='textbox'
/>
<FileUpload <FileUpload
ref='fileUpload' ref='fileUpload'
getFileCount={this.getFileCount} getFileCount={this.getFileCount}
@@ -329,7 +327,8 @@ export default class CreatePost extends React.Component {
onFileUpload={this.handleFileUploadComplete} onFileUpload={this.handleFileUploadComplete}
onUploadError={this.handleUploadError} onUploadError={this.handleUploadError}
postType='post' postType='post'
channelId='' /> channelId=''
/>
</div> </div>
<div className={postFooterClassName}> <div className={postFooterClassName}>
{postError} {postError}

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

@@ -88,7 +88,8 @@ export default class EditPostModal extends React.Component {
id='edit_post' id='edit_post'
role='dialog' role='dialog'
tabIndex='-1' tabIndex='-1'
aria-hidden='true' > aria-hidden='true'
>
<div className='modal-dialog modal-push-down'> <div className='modal-dialog modal-push-down'>
<div className='modal-content'> <div className='modal-content'>
<div className='modal-header'> <div className='modal-header'>
@@ -97,7 +98,8 @@ export default class EditPostModal extends React.Component {
className='close' className='close'
data-dismiss='modal' data-dismiss='modal'
aria-label='Close' aria-label='Close'
onClick={this.handleEditClose}> onClick={this.handleEditClose}
>
<span aria-hidden='true'>&times;</span> <span aria-hidden='true'>&times;</span>
</button> </button>
<h4 className='modal-title'>Edit {this.state.title}</h4> <h4 className='modal-title'>Edit {this.state.title}</h4>
@@ -110,20 +112,22 @@ export default class EditPostModal extends React.Component {
createMessage='Edit the post...' createMessage='Edit the post...'
id='edit_textbox' id='edit_textbox'
ref='editbox' ref='editbox'
/> />
{error} {error}
</div> </div>
<div className='modal-footer'> <div className='modal-footer'>
<button <button
type='button' type='button'
className='btn btn-default' className='btn btn-default'
data-dismiss='modal' > data-dismiss='modal'
>
Cancel Cancel
</button> </button>
<button <button
type='button' type='button'
className='btn btn-primary' className='btn btn-primary'
onClick={this.handleEdit}> onClick={this.handleEdit}
>
Save Save
</button> </button>
</div> </div>

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

@@ -24,12 +24,14 @@ export default class EmailVerify extends React.Component {
} else { } else {
title = config.SiteName + ' Email Not Verified'; title = config.SiteName + ' Email Not Verified';
body = <p>Please verify your email address. Check your inbox for an email.</p>; body = <p>Please verify your email address. Check your inbox for an email.</p>;
resend = (<button resend = (
onClick={this.handleResend} <button
className='btn btn-primary' onClick={this.handleResend}
> className='btn btn-primary'
Resend Email >
</button>); Resend Email
</button>
);
} }
return ( return (

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

@@ -69,7 +69,8 @@ export default class ErrorBar extends React.Component {
<a <a
href='#' href='#'
className='error-bar__close' className='error-bar__close'
onClick={this.handleClose}> onClick={this.handleClose}
>
&times; &times;
</a> </a>
</div> </div>

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

@@ -101,10 +101,13 @@ export default class FileAttachment extends React.Component {
var thumbnail; var thumbnail;
if (type === 'image') { if (type === 'image') {
thumbnail = (<div thumbnail = (
ref={filename} <div
className='post__load' ref={filename}
style={{backgroundImage: 'url(/static/images/load.gif)'}} />); className='post__load'
style={{backgroundImage: 'url(/static/images/load.gif)'}}
/>
);
} else { } else {
thumbnail = <div className={'file-icon ' + utils.getIconClassName(type)}/>; thumbnail = <div className={'file-icon ' + utils.getIconClassName(type)}/>;
} }
@@ -135,20 +138,23 @@ export default class FileAttachment extends React.Component {
return ( return (
<div <div
className='post-image__column' className='post-image__column'
key={filename}> key={filename}
>
<a className='post-image__thumbnail' <a className='post-image__thumbnail'
href='#' href='#'
onClick={this.props.handleImageClick} onClick={this.props.handleImageClick}
data-img-id={this.props.index} data-img-id={this.props.index}
data-toggle='modal' data-toggle='modal'
data-target={'#' + this.props.modalId} > data-target={'#' + this.props.modalId}
>
{thumbnail} {thumbnail}
</a> </a>
<div className='post-image__details'> <div className='post-image__details'>
<div <div
data-toggle='tooltip' data-toggle='tooltip'
title={filenameString} title={filenameString}
className='post-image__name' > className='post-image__name'
>
{trimmedFilename} {trimmedFilename}
</div> </div>
<div> <div>

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

@@ -28,7 +28,8 @@ export default class FileAttachmentList extends React.Component {
filename={filenames[i]} filename={filenames[i]}
index={i} index={i}
modalId={modalId} modalId={modalId}
handleImageClick={this.handleImageClick} /> handleImageClick={this.handleImageClick}
/>
); );
} }
@@ -42,7 +43,8 @@ export default class FileAttachmentList extends React.Component {
userId={this.props.userId} userId={this.props.userId}
modalId={modalId} modalId={modalId}
startId={this.state.startImgId} startId={this.state.startImgId}
filenames={filenames} /> filenames={filenames}
/>
</div> </div>
); );
} }

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

@@ -77,7 +77,10 @@ export default class FindTeam extends React.Component {
</div> </div>
<button <button
className='btn btn-md btn-primary' className='btn btn-md btn-primary'
type='submit'>Send</button> type='submit'
>
Send
</button>
</form> </form>
</div> </div>
); );

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

@@ -23,14 +23,16 @@ export default class MemberList extends React.Component {
return ( return (
<div className='member-list-holder'> <div className='member-list-holder'>
{members.map(function mymembers(member) { {members.map(function mymembers(member) {
return (<MemberListItem return (
key={member.id} <MemberListItem
member={member} key={member.id}
isAdmin={this.props.isAdmin} member={member}
handleInvite={this.props.handleInvite} isAdmin={this.props.isAdmin}
handleRemove={this.props.handleRemove} handleInvite={this.props.handleInvite}
handleMakeAdmin={this.props.handleMakeAdmin} handleRemove={this.props.handleRemove}
/>); handleMakeAdmin={this.props.handleMakeAdmin}
/>
);
}, this)} }, this)}
{message} {message}
</div> </div>

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

@@ -36,8 +36,10 @@ export default class MemberListItem extends React.Component {
invite = ( invite = (
<a <a
onClick={this.handleInvite} onClick={this.handleInvite}
className='btn btn-sm btn-primary member-invite'> className='btn btn-sm btn-primary member-invite'
<i className='glyphicon glyphicon-envelope'/> Add >
<i className='glyphicon glyphicon-envelope'/>
{' Add'}
</a> </a>
); );
} else if (isAdmin && !isMemberAdmin && (member.id !== UserStore.getCurrentId())) { } else if (isAdmin && !isMemberAdmin && (member.id !== UserStore.getCurrentId())) {
@@ -50,7 +52,9 @@ export default class MemberListItem extends React.Component {
<a <a
href='' href=''
role='menuitem' role='menuitem'
onClick={self.handleMakeAdmin}>Make Admin onClick={self.handleMakeAdmin}
>
Make Admin
</a> </a>
</li>); </li>);
} }
@@ -62,7 +66,9 @@ export default class MemberListItem extends React.Component {
<a <a
href='' href=''
role='menuitem' role='menuitem'
onClick={self.handleRemove}>Remove Member onClick={self.handleRemove}
>
Remove Member
</a> </a>
</li>); </li>);
} }
@@ -75,14 +81,16 @@ export default class MemberListItem extends React.Component {
type='button' type='button'
id='channel_header_dropdown' id='channel_header_dropdown'
data-toggle='dropdown' data-toggle='dropdown'
aria-expanded='true' > aria-expanded='true'
>
<span className='text-capitalize'>{member.roles || 'Member'} </span> <span className='text-capitalize'>{member.roles || 'Member'} </span>
<span className='caret'></span> <span className='caret'></span>
</a> </a>
<ul <ul
className='dropdown-menu member-menu' className='dropdown-menu member-menu'
role='menu' role='menu'
aria-labelledby='channel_header_dropdown'> aria-labelledby='channel_header_dropdown'
>
{makeAdminOption} {makeAdminOption}
{handleRemoveOption} {handleRemoveOption}
</ul> </ul>
@@ -98,7 +106,8 @@ export default class MemberListItem extends React.Component {
className='post-profile-img pull-left' className='post-profile-img pull-left'
src={'/api/v1/users/' + member.id + '/image?time=' + timestamp} src={'/api/v1/users/' + member.id + '/image?time=' + timestamp}
height='36' height='36'
width='36' /> width='36'
/>
<span className='member-name'>{member.username}</span> <span className='member-name'>{member.username}</span>
<span className='member-email'>{member.email}</span> <span className='member-email'>{member.email}</span>
{invite} {invite}

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

@@ -19,11 +19,14 @@ export default class Mention extends React.Component {
if (this.props.id === 'allmention' || this.props.id === 'channelmention') { if (this.props.id === 'allmention' || this.props.id === 'channelmention') {
icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>;
} else if (this.props.id != null) { } else if (this.props.id != null) {
icon = (<span><img icon = (
className='mention-img' <span>
src={'/api/v1/users/' + this.props.id + '/image?time=' + timestamp} <img
/> className='mention-img'
</span>); src={'/api/v1/users/' + this.props.id + '/image?time=' + timestamp}
/>
</span>
);
} else { } else {
icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>;
} }

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

@@ -216,7 +216,8 @@ export default class MentionList extends React.Component {
listId={index} listId={index}
isFocused={isFocused} isFocused={isFocused}
handleMouseEnter={this.handleMouseEnter.bind(this, index)} handleMouseEnter={this.handleMouseEnter.bind(this, index)}
handleClick={this.handleClick} /> handleClick={this.handleClick}
/>
); );
index++; index++;
} }
@@ -240,11 +241,13 @@ export default class MentionList extends React.Component {
return ( return (
<div <div
className='mentions--top' className='mentions--top'
style={style}> style={style}
>
<div <div
ref='mentionlist' ref='mentionlist'
className='mentions-box' className='mentions-box'
id='mentionsbox'> id='mentionsbox'
>
{mentions} {mentions}
</div> </div>
</div> </div>

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

@@ -86,15 +86,21 @@ export default class MoreChannels extends React.Component {
{channels.map(function cMap(channel, index) { {channels.map(function cMap(channel, index) {
var joinButton; var joinButton;
if (self.state.joiningChannel === index) { if (self.state.joiningChannel === index) {
joinButton = (<img joinButton = (
className='join-channel-loading-gif' <img
src='/static/images/load.gif' className='join-channel-loading-gif'
/>); src='/static/images/load.gif'
/>
);
} else { } else {
joinButton = (<button joinButton = (
onClick={self.handleJoin.bind(self, channel, index)} <button
className='btn btn-primary'>Join onClick={self.handleJoin.bind(self, channel, index)}
</button>); className='btn btn-primary'
>
Join
</button>
);
} }
return ( return (
@@ -152,7 +158,9 @@ export default class MoreChannels extends React.Component {
data-channeltype={this.state.channelType} data-channeltype={this.state.channelType}
type='button' type='button'
className='btn btn-primary channel-create-btn' className='btn btn-primary channel-create-btn'
onClick={this.handleNewChannel}>Create New Channel onClick={this.handleNewChannel}
>
Create New Channel
</button> </button>
</div> </div>
<div className='modal-body'> <div className='modal-body'>

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

@@ -61,15 +61,17 @@ export default class NavbarDropdown extends React.Component {
if (currentUser != null) { if (currentUser != null) {
isAdmin = currentUser.roles.indexOf('admin') > -1; isAdmin = currentUser.roles.indexOf('admin') > -1;
inviteLink = (<li> inviteLink = (
<a <li>
href='#' <a
data-toggle='modal' href='#'
data-target='#invite_member' data-toggle='modal'
> data-target='#invite_member'
Invite New Member >
</a> Invite New Member
</li>); </a>
</li>
);
if (this.props.teamType === 'O') { if (this.props.teamType === 'O') {
teamLink = ( teamLink = (
@@ -89,15 +91,17 @@ export default class NavbarDropdown extends React.Component {
} }
if (isAdmin) { if (isAdmin) {
manageLink = (<li> manageLink = (
<a <li>
href='#' <a
data-toggle='modal' href='#'
data-target='#team_members' data-toggle='modal'
> data-target='#team_members'
Manage Team >
</a> Manage Team
</li>); </a>
</li>
);
teamSettings = (<li> teamSettings = (<li>
<a <a
href='#' href='#'
@@ -111,11 +115,13 @@ export default class NavbarDropdown extends React.Component {
var teams = []; var teams = [];
teams.push(<li teams.push(
className='divider' <li
key='div' className='divider'
> key='div'
</li>); >
</li>
);
if (this.state.teams.length > 1 && this.state.currentTeam) { if (this.state.teams.length > 1 && this.state.currentTeam) {
var curTeamName = this.state.currentTeam.name; var curTeamName = this.state.currentTeam.name;
this.state.teams.forEach(function listTeams(teamName) { this.state.teams.forEach(function listTeams(teamName) {

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

@@ -134,14 +134,16 @@ export default class NewChannelModal extends React.Component {
ref='modal' ref='modal'
tabIndex='-1' tabIndex='-1'
role='dialog' role='dialog'
aria-hidden='true'> aria-hidden='true'
>
<div className='modal-dialog'> <div className='modal-dialog'>
<div className='modal-content'> <div className='modal-content'>
<div className='modal-header'> <div className='modal-header'>
<button <button
type='button' type='button'
className='close' className='close'
data-dismiss='modal'> data-dismiss='modal'
>
<span aria-hidden='true'>&times;</span> <span aria-hidden='true'>&times;</span>
<span className='sr-only'>Cancel</span> <span className='sr-only'>Cancel</span>
</button> </button>
@@ -157,7 +159,8 @@ export default class NewChannelModal extends React.Component {
ref='display_name' ref='display_name'
className='form-control' className='form-control'
placeholder='Enter display name' placeholder='Enter display name'
maxLength='22' /> maxLength='22'
/>
{displayNameError} {displayNameError}
</div> </div>
<div className={nameClass}> <div className={nameClass}>
@@ -167,7 +170,8 @@ export default class NewChannelModal extends React.Component {
className='form-control' className='form-control'
ref='channel_name' ref='channel_name'
placeholder="lowercase alphanumeric's only" placeholder="lowercase alphanumeric's only"
maxLength='22' /> maxLength='22'
/>
{nameError} {nameError}
</div> </div>
<div className='form-group'> <div className='form-group'>
@@ -177,7 +181,8 @@ export default class NewChannelModal extends React.Component {
ref='channel_desc' ref='channel_desc'
rows='3' rows='3'
placeholder='Description' placeholder='Description'
maxLength='1024' /> maxLength='1024'
/>
</div> </div>
{serverError} {serverError}
</div> </div>
@@ -185,13 +190,15 @@ export default class NewChannelModal extends React.Component {
<button <button
type='button' type='button'
className='btn btn-default' className='btn btn-default'
data-dismiss='modal' > data-dismiss='modal'
>
Cancel Cancel
</button> </button>
<button <button
onClick={this.handleSubmit} onClick={this.handleSubmit}
type='submit' type='submit'
className='btn btn-primary' > className='btn btn-primary'
>
Create New {channelTerm} Create New {channelTerm}
</button> </button>
</div> </div>

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

@@ -143,7 +143,8 @@ export default class Post extends React.Component {
className='post-profile-img' className='post-profile-img'
src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp} src={'/api/v1/users/' + post.user_id + '/image?time=' + timestamp}
height='36' height='36'
width='36' /> width='36'
/>
</div> </div>
); );
} }
@@ -152,7 +153,8 @@ export default class Post extends React.Component {
<div> <div>
<div <div
id={post.id} id={post.id}
className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss} > className={'post ' + sameUserClass + ' ' + rootUser + ' ' + postType + ' ' + currentUserCss}
>
{profilePic} {profilePic}
<div className='post__content'> <div className='post__content'>
<PostHeader <PostHeader
@@ -161,21 +163,24 @@ export default class Post extends React.Component {
sameRoot={this.props.sameRoot} sameRoot={this.props.sameRoot}
commentCount={commentCount} commentCount={commentCount}
handleCommentClick={this.handleCommentClick} handleCommentClick={this.handleCommentClick}
isLastComment={this.props.isLastComment} /> isLastComment={this.props.isLastComment}
/>
<PostBody <PostBody
post={post} post={post}
sameRoot={this.props.sameRoot} sameRoot={this.props.sameRoot}
parentPost={parentPost} parentPost={parentPost}
posts={posts} posts={posts}
handleCommentClick={this.handleCommentClick} handleCommentClick={this.handleCommentClick}
retryPost={this.retryPost} /> retryPost={this.retryPost}
/>
<PostInfo <PostInfo
ref='info' ref='info'
post={post} post={post}
sameRoot={this.props.sameRoot} sameRoot={this.props.sameRoot}
commentCount={commentCount} commentCount={commentCount}
handleCommentClick={this.handleCommentClick} handleCommentClick={this.handleCommentClick}
allowReply='true' /> allowReply='true'
/>
</div> </div>
</div> </div>
</div> </div>

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

@@ -5,14 +5,26 @@ const FileAttachmentList = require('./file_attachment_list.jsx');
const UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
const Utils = require('../utils/utils.jsx'); const Utils = require('../utils/utils.jsx');
const Constants = require('../utils/constants.jsx'); const Constants = require('../utils/constants.jsx');
const twemoji = require('twemoji');
export default class PostBody extends React.Component { export default class PostBody extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.parseEmojis = this.parseEmojis.bind(this);
const linkData = Utils.extractLinks(this.props.post.message); const linkData = Utils.extractLinks(this.props.post.message);
this.state = {links: linkData.links, message: linkData.text}; this.state = {links: linkData.links, message: linkData.text};
} }
parseEmojis() {
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
}
componentDidMount() {
this.parseEmojis();
}
componentDidUpdate() {
this.parseEmojis();
}
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const linkData = Utils.extractLinks(nextProps.post.message); const linkData = Utils.extractLinks(nextProps.post.message);
this.setState({links: linkData.links, message: linkData.text}); this.setState({links: linkData.links, message: linkData.text});

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

@@ -559,7 +559,7 @@ export default class PostList extends React.Component {
> >
<hr <hr
className='separator__hr' className='separator__hr'
/> />
<div className='separator__text'>New Messages</div> <div className='separator__text'>New Messages</div>
</div> </div>
); );

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

@@ -12,12 +12,14 @@ var FileAttachmentList = require('./file_attachment_list.jsx');
var Client = require('../utils/client.jsx'); var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx'); var AsyncClient = require('../utils/async_client.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
var twemoji = require('twemoji');
export default class RhsComment extends React.Component { export default class RhsComment extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.retryComment = this.retryComment.bind(this); this.retryComment = this.retryComment.bind(this);
this.parseEmojis = this.parseEmojis.bind(this);
this.state = {}; this.state = {};
} }
@@ -51,6 +53,12 @@ export default class RhsComment extends React.Component {
PostStore.updatePendingPost(post); PostStore.updatePendingPost(post);
this.forceUpdate(); this.forceUpdate();
} }
parseEmojis() {
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
}
componentDidMount() {
this.parseEmojis();
}
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
if (!Utils.areStatesEqual(nextProps.post, this.props.post)) { if (!Utils.areStatesEqual(nextProps.post, this.props.post)) {
return true; return true;
@@ -58,6 +66,9 @@ export default class RhsComment extends React.Component {
return false; return false;
} }
componentDidUpdate() {
this.parseEmojis();
}
render() { render() {
var post = this.props.post; var post = this.props.post;
@@ -161,7 +172,8 @@ export default class RhsComment extends React.Component {
filenames={post.filenames} filenames={post.filenames}
modalId={'rhs_comment_view_image_modal_' + post.id} modalId={'rhs_comment_view_image_modal_' + post.id}
channelId={post.channel_id} channelId={post.channel_id}
userId={post.user_id} /> userId={post.user_id}
/>
); );
} }

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

@@ -6,12 +6,23 @@ var UserProfile = require('./user_profile.jsx');
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
var FileAttachmentList = require('./file_attachment_list.jsx'); var FileAttachmentList = require('./file_attachment_list.jsx');
var twemoji = require('twemoji');
var Constants = require('../utils/constants.jsx');
export default class RhsRootPost extends React.Component { export default class RhsRootPost extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.parseEmojis = this.parseEmojis.bind(this);
this.state = {}; this.state = {};
} }
parseEmojis() {
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
}
componentDidMount() {
this.parseEmojis();
}
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
if (!utils.areStatesEqual(nextProps.post, this.props.post)) { if (!utils.areStatesEqual(nextProps.post, this.props.post)) {
return true; return true;
@@ -19,6 +30,9 @@ export default class RhsRootPost extends React.Component {
return false; return false;
} }
componentDidUpdate() {
this.parseEmojis();
}
render() { render() {
var post = this.props.post; var post = this.props.post;
var message = utils.textToJsx(post.message); var message = utils.textToJsx(post.message);
@@ -99,7 +113,8 @@ export default class RhsRootPost extends React.Component {
filenames={post.filenames} filenames={post.filenames}
modalId={'rhs_view_image_modal_' + post.id} modalId={'rhs_view_image_modal_' + post.id}
channelId={post.channel_id} channelId={post.channel_id}
userId={post.user_id} /> userId={post.user_id}
/>
); );
} }

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

@@ -18,7 +18,6 @@ export default class RhsThread extends React.Component {
this.onChange = this.onChange.bind(this); this.onChange = this.onChange.bind(this);
this.onChangeAll = this.onChangeAll.bind(this); this.onChangeAll = this.onChangeAll.bind(this);
this.onTimeChange = this.onTimeChange.bind(this);
this.state = this.getStateFromStores(); this.state = this.getStateFromStores();
} }
@@ -44,7 +43,6 @@ export default class RhsThread extends React.Component {
componentDidMount() { componentDidMount() {
PostStore.addSelectedPostChangeListener(this.onChange); PostStore.addSelectedPostChangeListener(this.onChange);
PostStore.addChangeListener(this.onChangeAll); PostStore.addChangeListener(this.onChangeAll);
UserStore.addStatusesChangeListener(this.onTimeChange);
this.resize(); this.resize();
$(window).resize(function resize() { $(window).resize(function resize() {
this.resize(); this.resize();
@@ -58,7 +56,6 @@ export default class RhsThread extends React.Component {
componentWillUnmount() { componentWillUnmount() {
PostStore.removeSelectedPostChangeListener(this.onChange); PostStore.removeSelectedPostChangeListener(this.onChange);
PostStore.removeChangeListener(this.onChangeAll); PostStore.removeChangeListener(this.onChangeAll);
UserStore.removeStatusesChangeListener(this.onTimeChange);
} }
onChange() { onChange() {
var newState = this.getStateFromStores(); var newState = this.getStateFromStores();
@@ -96,14 +93,6 @@ export default class RhsThread extends React.Component {
this.setState(newState); this.setState(newState);
} }
} }
onTimeChange() {
for (var id in this.state.postList.posts) {
if (!this.refs[id]) {
continue;
}
this.refs[id].forceUpdate();
}
}
resize() { resize() {
var height = $(window).height() - $('#error_bar').outerHeight() - 100; var height = $(window).height() - $('#error_bar').outerHeight() - 100;
$('.post-right__scroll').css('height', height + 'px'); $('.post-right__scroll').css('height', height + 'px');
@@ -173,8 +162,7 @@ export default class RhsThread extends React.Component {
return ( return (
<div className='post-right__container'> <div className='post-right__container'>
<FileUploadOverlay <FileUploadOverlay overlayType='right' />
overlayType='right' />
<div className='search-bar__container sidebar--right__search-header'>{searchForm}</div> <div className='search-bar__container sidebar--right__search-header'>{searchForm}</div>
<div className='sidebar-right__body'> <div className='sidebar-right__body'>
<RhsHeaderPost <RhsHeaderPost

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

@@ -116,19 +116,22 @@ export default class SearchBar extends React.Component {
<div> <div>
<div <div
className='sidebar__collapse' className='sidebar__collapse'
onClick={this.handleClose} > onClick={this.handleClose}
>
<span className='fa fa-angle-left'></span> <span className='fa fa-angle-left'></span>
</div> </div>
<span <span
className='search__clear' className='search__clear'
onClick={this.clearFocus}> onClick={this.clearFocus}
>
Cancel Cancel
</span> </span>
<form <form
role='form' role='form'
className='search__form relative-div' className='search__form relative-div'
onSubmit={this.handleSubmit}> onSubmit={this.handleSubmit}
<span className='glyphicon glyphicon-search sidebar__search-icon'></span> >
<span className='glyphicon glyphicon-search sidebar__search-icon' />
<input <input
type='text' type='text'
ref='search' ref='search'
@@ -136,7 +139,8 @@ export default class SearchBar extends React.Component {
placeholder='Search' placeholder='Search'
value={this.state.searchTerm} value={this.state.searchTerm}
onFocus={this.handleUserFocus} onFocus={this.handleUserFocus}
onChange={this.handleUserInput} /> onChange={this.handleUserInput}
/>
{isSearching} {isSearching}
</form> </form>
</div> </div>

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

@@ -15,16 +15,20 @@ export default class SettingItemMax extends React.Component {
var extraInfo = null; var extraInfo = null;
if (this.props.extraInfo) { if (this.props.extraInfo) {
extraInfo = this.props.extraInfo; extraInfo = (<div className='setting-list__hint'>{this.props.extraInfo}</div>);
} }
var submit = ''; var submit = '';
if (this.props.submit) { if (this.props.submit) {
submit = (<a submit = (
className='btn btn-sm btn-primary' <a
href='#' className='btn btn-sm btn-primary'
onClick={this.props.submit}> href='#'
Submit</a>); onClick={this.props.submit}
>
Submit
</a>
);
} }
var inputs = this.props.inputs; var inputs = this.props.inputs;
@@ -46,7 +50,8 @@ export default class SettingItemMax extends React.Component {
<a <a
className='btn btn-sm theme' className='btn btn-sm theme'
href='#' href='#'
onClick={this.props.updateSection} > onClick={this.props.updateSection}
>
Cancel Cancel
</a> </a>
</li> </li>

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

@@ -98,12 +98,13 @@ export default class SettingPicture extends React.Component {
<li className='setting-list-item'> <li className='setting-list-item'>
{serverError} {serverError}
{clientError} {clientError}
<span className='btn btn-sm btn-primary btn-file sel-btn' <span className='btn btn-sm btn-primary btn-file sel-btn'>
>Select<input Select
ref='input' <input
accept='.jpg,.png,.bmp' ref='input'
type='file' accept='.jpg,.png,.bmp'
onChange={this.props.pictureChange} type='file'
onChange={this.props.pictureChange}
/> />
</span> </span>
{confirmButton} {confirmButton}

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

@@ -2,7 +2,6 @@
// See License.txt for license information. // See License.txt for license information.
export default class SettingsUpload extends React.Component { export default class SettingsUpload extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -70,14 +69,19 @@ export default class SettingsUpload extends React.Component {
<li className='col-xs-offset-3 col-xs-8'> <li className='col-xs-offset-3 col-xs-8'>
<ul className='setting-list'> <ul className='setting-list'>
<li className='setting-list-item'> <li className='setting-list-item'>
<span className='btn btn-sm btn-primary btn-file sel-btn'>Select file<input <span className='btn btn-sm btn-primary btn-file sel-btn'>
ref='uploadinput' Select file
accept={this.props.fileTypesAccepted} <input
type='file' ref='uploadinput'
onChange={this.onFileSelect}/></span> accept={this.props.fileTypesAccepted}
type='file'
onChange={this.onFileSelect}
/>
</span>
<a <a
className={'btn btn-sm btn-primary'} className={'btn btn-sm btn-primary'}
onClick={this.doSubmit}> onClick={this.doSubmit}
>
Import Import
</a> </a>
<div className='file-status file-name hide'></div> <div className='file-status file-name hide'></div>

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

@@ -29,10 +29,12 @@ export default class SidebarHeader extends React.Component {
} }
if (me.last_picture_update) { if (me.last_picture_update) {
profilePicture = (<img profilePicture = (
className='user__picture' <img
src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} className='user__picture'
/>); src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at}
/>
);
} }
return ( return (

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

@@ -71,10 +71,12 @@ export default class SidebarRight extends React.Component {
if (this.state.search_visible) { if (this.state.search_visible) {
content = <SearchResults isMentionSearch={this.state.is_mention_search} />; content = <SearchResults isMentionSearch={this.state.is_mention_search} />;
} else if (this.state.post_right_visible) { } else if (this.state.post_right_visible) {
content = (<RhsThread content = (
fromSearch={this.state.from_search} <RhsThread
isMentionSearch={this.state.is_mention_search} fromSearch={this.state.from_search}
/>); isMentionSearch={this.state.is_mention_search}
/>
);
} }
return ( return (

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

@@ -68,7 +68,8 @@ export default class SidebarRightMenu extends React.Component {
href='#' href='#'
data-toggle='modal' data-toggle='modal'
data-target='#team_members' data-target='#team_members'
><i className='glyphicon glyphicon-wrench'></i>Manage Team</a> >
<i className='glyphicon glyphicon-wrench'></i>Manage Team</a>
</li> </li>
); );
} }

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

@@ -44,52 +44,66 @@ export default class SignupTeamComplete extends React.Component {
} }
render() { render() {
if (this.state.wizard === 'welcome') { if (this.state.wizard === 'welcome') {
return (<WelcomePage return (
state={this.state} <WelcomePage
updateParent={this.updateParent} state={this.state}
/>); updateParent={this.updateParent}
/>
);
} }
if (this.state.wizard === 'team_display_name') { if (this.state.wizard === 'team_display_name') {
return (<TeamDisplayNamePage return (
state={this.state} <TeamDisplayNamePage
updateParent={this.updateParent} state={this.state}
/>); updateParent={this.updateParent}
/>
);
} }
if (this.state.wizard === 'team_url') { if (this.state.wizard === 'team_url') {
return (<TeamURLPage return (
state={this.state} <TeamURLPage
updateParent={this.updateParent} state={this.state}
/>); updateParent={this.updateParent}
/>
);
} }
if (this.state.wizard === 'allowed_domains') { if (this.state.wizard === 'allowed_domains') {
return (<AllowedDomainsPage return (
state={this.state} <AllowedDomainsPage
updateParent={this.updateParent} state={this.state}
/>); updateParent={this.updateParent}
/>
);
} }
if (this.state.wizard === 'send_invites') { if (this.state.wizard === 'send_invites') {
return (<SendInivtesPage return (
state={this.state} <SendInivtesPage
updateParent={this.updateParent} state={this.state}
/>); updateParent={this.updateParent}
/>
);
} }
if (this.state.wizard === 'username') { if (this.state.wizard === 'username') {
return (<UsernamePage return (
state={this.state} <UsernamePage
updateParent={this.updateParent} state={this.state}
/>); updateParent={this.updateParent}
/>
);
} }
if (this.state.wizard === 'password') { if (this.state.wizard === 'password') {
return (<PasswordPage return (
state={this.state} <PasswordPage
updateParent={this.updateParent} state={this.state}
/>); updateParent={this.updateParent}
/>
);
} }
return (<div>You've already completed the signup process for this invitation or this invitation has expired.</div>); return (<div>You've already completed the signup process for this invitation or this invitation has expired.</div>);

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

@@ -46,7 +46,8 @@ export default class TeamImportTab extends React.Component {
title='Import from Slack' title='Import from Slack'
submit={this.doImportSlack} submit={this.doImportSlack}
helpText={uploadHelpText} helpText={uploadHelpText}
fileTypesAccepted='.zip'/> fileTypesAccepted='.zip'
/>
); );
var messageSection; var messageSection;
@@ -62,14 +63,30 @@ export default class TeamImportTab extends React.Component {
break; break;
case 'done': case 'done':
messageSection = ( messageSection = (
<p className='confirm-import alert alert-success'><i className='fa fa-check'></i> Import successful: <a href={this.state.link} <p className='confirm-import alert alert-success'>
download='MattermostImportSummary.txt'>View Summary</a></p> <i className='fa fa-check' />
); {' Import successful: '}
<a
href={this.state.link}
download='MattermostImportSummary.txt'
>
{'View Summary'}
</a>
</p>
);
break; break;
case 'fail': case 'fail':
messageSection = ( messageSection = (
<p className='confirm-import alert alert-warning'><i className='fa fa-warning'></i> Import failure: <a href={this.state.link} <p className='confirm-import alert alert-warning'>
download='MattermostImportSummary.txt'>View Summary</a></p> <i className='fa fa-warning' />
{' Import failure: '}
<a
href={this.state.link}
download='MattermostImportSummary.txt'
>
{'View Summary'}
</a>
</p>
); );
break; break;
} }

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

@@ -22,7 +22,7 @@ export default class ChooseAuthPage extends React.Component {
this.props.updatePage('service', Constants.GITLAB_SERVICE); this.props.updatePage('service', Constants.GITLAB_SERVICE);
}.bind(this) }.bind(this)
} }
> >
<span className='icon' /> <span className='icon' />
<span>Create new {strings.Team} with GitLab Account</span> <span>Create new {strings.Team} with GitLab Account</span>
</a> </a>
@@ -40,7 +40,7 @@ export default class ChooseAuthPage extends React.Component {
this.props.updatePage('email', ''); this.props.updatePage('email', '');
}.bind(this) }.bind(this)
} }
> >
<span className='fa fa-envelope' /> <span className='fa fa-envelope' />
<span>Create new {strings.Team} with email address</span> <span>Create new {strings.Team} with email address</span>
</a> </a>

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

@@ -52,8 +52,8 @@ export default class TeamSignupDisplayNamePage extends React.Component {
<form> <form>
<img <img
className='signup-team-logo' className='signup-team-logo'
src='/static/images/logo.png' /> src='/static/images/logo.png'
/>
<h2>{utils.toTitleCase(strings.Team) + ' Name'}</h2> <h2>{utils.toTitleCase(strings.Team) + ' Name'}</h2>
<div className={nameDivClass}> <div className={nameDivClass}>
<div className='row'> <div className='row'>
@@ -66,22 +66,27 @@ export default class TeamSignupDisplayNamePage extends React.Component {
maxLength='128' maxLength='128'
defaultValue={this.props.state.team.display_name} defaultValue={this.props.state.team.display_name}
autoFocus={true} autoFocus={true}
onFocus={this.handleFocus} /> onFocus={this.handleFocus}
/>
</div> </div>
</div> </div>
{nameError} {nameError}
</div> </div>
<div>{'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'}</div> <div>
{'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'}
</div>
<button <button
type='submit' type='submit'
className='btn btn-primary margin--extra' className='btn btn-primary margin--extra'
onClick={this.submitNext} > onClick={this.submitNext}
>
Next<i className='glyphicon glyphicon-chevron-right'></i> Next<i className='glyphicon glyphicon-chevron-right'></i>
</button> </button>
<div className='margin--extra'> <div className='margin--extra'>
<a <a
href='#' href='#'
onClick={this.submitBack}> onClick={this.submitBack}
>
Back to previous step Back to previous step
</a> </a>
</div> </div>

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

@@ -121,7 +121,6 @@ export default class Textbox extends React.Component {
} }
this.addedMention = false; this.addedMention = false;
this.refs.commands.getSuggestedCommands(nextProps.messageText); this.refs.commands.getSuggestedCommands(nextProps.messageText);
this.resize();
} }
updateMentionTab(mentionText) { updateMentionTab(mentionText) {
// using setTimeout so dispatch isn't called during an in progress dispatch // using setTimeout so dispatch isn't called during an in progress dispatch
@@ -135,7 +134,6 @@ export default class Textbox extends React.Component {
} }
handleChange() { handleChange() {
this.props.onUserInput(React.findDOMNode(this.refs.message).value); this.props.onUserInput(React.findDOMNode(this.refs.message).value);
this.resize();
} }
handleKeyPress(e) { handleKeyPress(e) {
const text = React.findDOMNode(this.refs.message).value; const text = React.findDOMNode(this.refs.message).value;
@@ -244,6 +242,8 @@ export default class Textbox extends React.Component {
const e = React.findDOMNode(this.refs.message); const e = React.findDOMNode(this.refs.message);
const w = React.findDOMNode(this.refs.wrapper); const w = React.findDOMNode(this.refs.wrapper);
let prevHeight = $(e).height();
const lht = parseInt($(e).css('lineHeight'), 10); const lht = parseInt($(e).css('lineHeight'), 10);
const lines = e.scrollHeight / lht; const lines = e.scrollHeight / lht;
let mod = 15; let mod = 15;
@@ -259,6 +259,10 @@ export default class Textbox extends React.Component {
$(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167); $(e).css({height: 'auto', 'overflow-y': 'scroll'}).height(167);
$(w).css({height: 'auto'}).height(167); $(w).css({height: 'auto'}).height(167);
} }
if (prevHeight !== $(e).height()) {
this.props.onHeightChange();
}
} }
handleFocus() { handleFocus() {
const elm = React.findDOMNode(this.refs.message); const elm = React.findDOMNode(this.refs.message);
@@ -316,5 +320,6 @@ Textbox.propTypes = {
messageText: React.PropTypes.string.isRequired, messageText: React.PropTypes.string.isRequired,
onUserInput: React.PropTypes.func.isRequired, onUserInput: React.PropTypes.func.isRequired,
onKeyPress: React.PropTypes.func.isRequired, onKeyPress: React.PropTypes.func.isRequired,
onHeightChange: React.PropTypes.func.isRequired,
createMessage: React.PropTypes.string.isRequired createMessage: React.PropTypes.string.isRequired
}; };

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

@@ -1,7 +1,6 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx'); var SettingItemMax = require('./setting_item_max.jsx');
@@ -69,11 +68,9 @@ function getNotificationsStateFromStores() {
} }
} }
var curChannel = ChannelStore.getCurrent().display_name;
return {notifyLevel: desktop, enableEmail: email, soundNeeded: soundNeeded, enableSound: sound, return {notifyLevel: desktop, enableEmail: email, soundNeeded: soundNeeded, enableSound: sound,
usernameKey: usernameKey, mentionKey: mentionKey, customKeys: customKeys, customKeysChecked: customKeys.length > 0, usernameKey: usernameKey, mentionKey: mentionKey, customKeys: customKeys, customKeysChecked: customKeys.length > 0,
firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey, curChannel: curChannel}; firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey};
} }
export default class NotificationsTab extends React.Component { export default class NotificationsTab extends React.Component {
@@ -147,12 +144,10 @@ export default class NotificationsTab extends React.Component {
} }
componentDidMount() { componentDidMount() {
UserStore.addChangeListener(this.onListenerChange); UserStore.addChangeListener(this.onListenerChange);
ChannelStore.addChangeListener(this.onListenerChange);
$('#user_settings').on('hidden.bs.modal', this.handleClose); $('#user_settings').on('hidden.bs.modal', this.handleClose);
} }
componentWillUnmount() { componentWillUnmount() {
UserStore.removeChangeListener(this.onListenerChange); UserStore.removeChangeListener(this.onListenerChange);
ChannelStore.removeChangeListener(this.onListenerChange);
$('#user_settings').off('hidden.bs.modal', this.handleClose); $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection(''); this.props.updateSection('');
} }
@@ -271,12 +266,6 @@ export default class NotificationsTab extends React.Component {
e.preventDefault(); e.preventDefault();
}.bind(this); }.bind(this);
let extraInfo = (
<div className='setting-list__hint'>
These settings will override the global notification settings for the <b>{this.state.curChannel}</b> channel
</div>
);
desktopSection = ( desktopSection = (
<SettingItemMax <SettingItemMax
title='Send desktop notifications' title='Send desktop notifications'
@@ -284,7 +273,6 @@ export default class NotificationsTab extends React.Component {
submit={this.handleSubmit} submit={this.handleSubmit}
server_error={serverError} server_error={serverError}
updateSection={handleUpdateDesktopSection} updateSection={handleUpdateDesktopSection}
extraInfo={extraInfo}
/> />
); );
} else { } else {

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

@@ -307,7 +307,8 @@ export default class ViewImageModal extends React.Component {
ref='previewArrowLeft' ref='previewArrowLeft'
className='modal-prev-bar' className='modal-prev-bar'
href='#' href='#'
onClick={this.handlePrev}> onClick={this.handlePrev}
>
<i className='image-control image-prev'/> <i className='image-control image-prev'/>
</a> </a>
); );
@@ -317,7 +318,8 @@ export default class ViewImageModal extends React.Component {
ref='previewArrowRight' ref='previewArrowRight'
className='modal-next-bar' className='modal-next-bar'
href='#' href='#'
onClick={this.handleNext}> onClick={this.handleNext}
>
<i className='image-control image-next'/> <i className='image-control image-next'/>
</a> </a>
); );
@@ -351,7 +353,7 @@ export default class ViewImageModal extends React.Component {
ref='imageFooter' ref='imageFooter'
className='modal-button-bar' className='modal-button-bar'
> >
<span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span> <span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span>
<div className='image-links'> <div className='image-links'>
{publicLink} {publicLink}
<a <a

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

@@ -8,7 +8,8 @@
"keymirror": "^0.1.1", "keymirror": "^0.1.1",
"object-assign": "^3.0.0", "object-assign": "^3.0.0",
"react": "^0.13.3", "react": "^0.13.3",
"react-zeroclipboard-mixin": "^0.1.0" "react-zeroclipboard-mixin": "^0.1.0",
"twemoji": "^1.4.1"
}, },
"devDependencies": { "devDependencies": {
"browserify": "^11.0.1", "browserify": "^11.0.1",
@@ -16,12 +17,12 @@
"babelify": "^6.1.3", "babelify": "^6.1.3",
"uglify-js": "^2.4.24", "uglify-js": "^2.4.24",
"watchify": "^3.3.1", "watchify": "^3.3.1",
"eslint": "^1.1.0", "eslint": "^1.3.1",
"eslint-plugin-react": "^3.2.3" "eslint-plugin-react": "^3.3.1"
}, },
"scripts": { "scripts": {
"start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx", "start": "watchify --extension=jsx -o ../static/js/bundle.js -v -d ./**/*.jsx",
"build": "NODE_ENV=production browserify ./**/*.jsx | uglifyjs > ../static/js/bundle.min.js", "build": "NODE_ENV=production browserify ./**/*.jsx | uglifyjs -c -m --screw-ie8 > ../static/js/bundle.min.js",
"test": "jest" "test": "jest"
}, },
"browserify": { "browserify": {

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

@@ -73,7 +73,8 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) {
React.render( React.render(
<Sidebar <Sidebar
teamDisplayName={teamName} teamDisplayName={teamName}
teamType={teamType} />, teamType={teamType}
/>,
document.getElementById('sidebar-left') document.getElementById('sidebar-left')
); );
@@ -190,7 +191,8 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) {
React.render( React.render(
<SidebarRightMenu <SidebarRightMenu
teamDisplayName={teamName} teamDisplayName={teamName}
teamType={teamType} />, teamType={teamType}
/>,
document.getElementById('sidebar-menu') document.getElementById('sidebar-menu')
); );
@@ -226,7 +228,8 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) {
React.render( React.render(
<FileUploadOverlay <FileUploadOverlay
overlayType='center' />, overlayType='center'
/>,
document.getElementById('file_upload_overlay') document.getElementById('file_upload_overlay')
); );
} }

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

@@ -8,7 +8,8 @@ function setupLoginPage(teamDisplayName, teamName, authServices) {
<Login <Login
teamDisplayName={teamDisplayName} teamDisplayName={teamDisplayName}
teamName={teamName} teamName={teamName}
authServices={authServices} />, authServices={authServices}
/>,
document.getElementById('login') document.getElementById('login')
); );
} }

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

@@ -8,7 +8,8 @@ function setupSignupTeamCompletePage(email, data, hash) {
<SignupTeamComplete <SignupTeamComplete
email={email} email={email}
hash={hash} hash={hash}
data={data}/>, data={data}
/>,
document.getElementById('signup-team-complete') document.getElementById('signup-team-complete')
); );
} }

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

@@ -12,7 +12,8 @@ function setupSignupUserCompletePage(email, name, uiName, id, data, hash, authSe
email={email} email={email}
hash={hash} hash={hash}
data={data} data={data}
authServices={authServices} />, authServices={authServices}
/>,
document.getElementById('signup-user-complete') document.getElementById('signup-user-complete')
); );
} }

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

@@ -8,7 +8,8 @@ global.window.setupVerifyPage = function setupVerifyPage(isVerified, teamURL, us
<EmailVerify <EmailVerify
isVerified={isVerified} isVerified={isVerified}
teamURL={teamURL} teamURL={teamURL}
userEmail={userEmail} />, userEmail={userEmail}
/>,
document.getElementById('verify') document.getElementById('verify')
); );
}; };

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

@@ -103,6 +103,7 @@ module.exports = {
MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
MAX_DMS: 20, MAX_DMS: 20,
MAX_POST_LEN: 4000, MAX_POST_LEN: 4000,
EMOJI_SIZE: 16,
ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>", ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>",
OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>", OFFLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path fill='#cccccc' d='M6.002,7.143C5.645,7.363,5.167,7.52,4.502,7.52c-2.493,0-2.5-2.02-2.5-2.02S1.029,5.607,0.775,6.004C0.41,6.577,0.15,7.716,0.049,8.545c-0.025,0.145-0.057,0.537-0.05,0.598c0.162,1.295,2.237,2.321,4.375,2.357c0.043,0.001,0.085,0.001,0.127,0.001c0.043,0,0.084,0,0.127-0.001c1.879-0.023,3.793-0.879,4.263-2h-2.89L6.002,7.143L6.002,7.143z M4.501,5.488c1.372,0,2.483-1.117,2.483-2.494c0-1.378-1.111-2.495-2.483-2.495c-1.371,0-2.481,1.117-2.481,2.495C2.02,4.371,3.13,5.488,4.501,5.488z M7.002,6.5v2h5v-2H7.002z'/></g></g></svg>",
MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>", MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>",