Merge branch 'master' into PLT-11-email

Этот коммит содержится в:
=Corey Hulen
2015-09-23 12:55:58 -07:00
родитель 9e04909c0a 0170cfe604
Коммит 2b5b8f95ed
68 изменённых файлов: 1509 добавлений и 358 удалений

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

@@ -32,7 +32,7 @@ Please see the [features pages of the Mattermost website](http://www.mattermost.
- [Mattermost Forum](http://forum.mattermost.org/) - For technical questions and answers - [Mattermost Forum](http://forum.mattermost.org/) - For technical questions and answers
- [Issue Tracker](http://www.mattermost.org/filing-issues/) - For reporting bugs - [Issue Tracker](http://www.mattermost.org/filing-issues/) - For reporting bugs
- [Feature Ideas Forum](http://www.mattermost.org/feature-requests/) - For sharing ideas for future versions - [Feature Ideas Forum](http://www.mattermost.org/feature-requests/) - For sharing ideas for future versions
- [Contributuion Guidelines](http://www.mattermost.org/contribute-to-mattermost/) - For contributing code or feedback to the project - [Contribution Guidelines](http://www.mattermost.org/contribute-to-mattermost/) - For contributing code or feedback to the project
Follow us on Twitter at [@MattermostHQ](https://twitter.com/mattermosthq). Follow us on Twitter at [@MattermostHQ](https://twitter.com/mattermosthq).

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

@@ -160,7 +160,7 @@ func fireAndForgetNotifications(post *model.Post, teamId, siteURL string) {
channel = result.Data.(*model.Channel) channel = result.Data.(*model.Channel)
if channel.Type == model.CHANNEL_DIRECT { if channel.Type == model.CHANNEL_DIRECT {
bodyText = "You have one new message." bodyText = "You have one new message."
subjectText = "New Private Message" subjectText = "New Direct Message"
} else { } else {
bodyText = "You have one new mention." bodyText = "You have one new mention."
subjectText = "New Mention" subjectText = "New Mention"

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

@@ -171,9 +171,6 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
} }
user.MakeNonNil() user.MakeNonNil()
if len(user.Props["theme"]) == 0 {
user.AddProp("theme", utils.Cfg.TeamSettings.DefaultThemeColor)
}
if result := <-Srv.Store.User().Save(user); result.Err != nil { if result := <-Srv.Store.User().Save(user); result.Err != nil {
c.Err = result.Err c.Err = result.Err

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

@@ -33,25 +33,6 @@ git checkout -b <branch name>
1. Please add yourself to the Mattermost [approved contributor list](https://docs.google.com/spreadsheets/d/1NTCeG-iL_VS9bFqtmHSfwETo5f-8MQ7oMDE5IUYJi_Y/pubhtml?gid=0&single=true) prior to submitting by completing the [contributor license agreement](http://www.mattermost.org/mattermost-contributor-agreement/). 1. Please add yourself to the Mattermost [approved contributor list](https://docs.google.com/spreadsheets/d/1NTCeG-iL_VS9bFqtmHSfwETo5f-8MQ7oMDE5IUYJi_Y/pubhtml?gid=0&single=true) prior to submitting by completing the [contributor license agreement](http://www.mattermost.org/mattermost-contributor-agreement/).
For pull requests made by contributors not yet added to the approved contributor list, a reviewer may respond:
```
Thanks @[GITHUB_USERNAME] for the pull request!
Before we can review, we need to add you to the list of approved contributors for the Mattermost project.
**Please help complete the Mattermost [contribution license agreement](http://www.mattermost.org/mattermost-contributor-agreement/)?**
This is a standard procedure for many open source projects. You can view a list of past contributors who have completed the form [here](https://docs.google.com/spreadsheets/d/1NTCeG-iL_VS9bFqtmHSfwETo5f-8MQ7oMDE5IUYJi_Y/pubhtml?gid=0&single=true).
After completing the form, it should be processed within 24 hours and reviewers for your pull request will be able to proceed.
Please let us know if you have any questions.
We are very happy to have you join our growing community!
```
2. When you submit your pull request please include the Ticket ID at the beginning of your pull request comment, followed by a colon. 2. When you submit your pull request please include the Ticket ID at the beginning of your pull request comment, followed by a colon.
For example, for a ticket ID `PLT-394` start your comment with: `PLT-394:`. See [previously closed pull requests](https://github.com/mattermost/platform/pulls?q=is%3Apr+is%3Aclosed) for examples. For example, for a ticket ID `PLT-394` start your comment with: `PLT-394:`. See [previously closed pull requests](https://github.com/mattermost/platform/pulls?q=is%3Apr+is%3Aclosed) for examples.

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

@@ -47,6 +47,7 @@ type User struct {
AllowMarketing bool `json:"allow_marketing"` AllowMarketing bool `json:"allow_marketing"`
Props StringMap `json:"props"` Props StringMap `json:"props"`
NotifyProps StringMap `json:"notify_props"` NotifyProps StringMap `json:"notify_props"`
ThemeProps StringMap `json:"theme_props"`
LastPasswordUpdate int64 `json:"last_password_update"` LastPasswordUpdate int64 `json:"last_password_update"`
LastPictureUpdate int64 `json:"last_picture_update"` LastPictureUpdate int64 `json:"last_picture_update"`
FailedAttempts int `json:"failed_attempts"` FailedAttempts int `json:"failed_attempts"`
@@ -108,6 +109,10 @@ func (u *User) IsValid() *AppError {
return NewAppError("User.IsValid", "Invalid user, password and auth data cannot both be set", "user_id="+u.Id) return NewAppError("User.IsValid", "Invalid user, password and auth data cannot both be set", "user_id="+u.Id)
} }
if len(u.ThemeProps) > 2000 {
return NewAppError("User.IsValid", "Invalid theme", "user_id="+u.Id)
}
return nil return nil
} }

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

@@ -32,6 +32,7 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
table.ColMap("Roles").SetMaxSize(64) table.ColMap("Roles").SetMaxSize(64)
table.ColMap("Props").SetMaxSize(4000) table.ColMap("Props").SetMaxSize(4000)
table.ColMap("NotifyProps").SetMaxSize(2000) table.ColMap("NotifyProps").SetMaxSize(2000)
table.ColMap("ThemeProps").SetMaxSize(2000)
table.SetUniqueTogether("Email", "TeamId") table.SetUniqueTogether("Email", "TeamId")
table.SetUniqueTogether("Username", "TeamId") table.SetUniqueTogether("Username", "TeamId")
} }
@@ -40,6 +41,7 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
} }
func (us SqlUserStore) UpgradeSchemaIfNeeded() { func (us SqlUserStore) UpgradeSchemaIfNeeded() {
us.CreateColumnIfNotExists("Users", "ThemeProps", "varchar(2000)", "character varying(2000)", "{}")
} }
func (us SqlUserStore) CreateIndexesIfNotExists() { func (us SqlUserStore) CreateIndexesIfNotExists() {

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

@@ -55,7 +55,7 @@ export default class ChannelHeader extends React.Component {
if (!Utils.areStatesEqual(newState, this.state)) { if (!Utils.areStatesEqual(newState, this.state)) {
this.setState(newState); this.setState(newState);
} }
$('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover click', html: true, delay: {show: 500, hide: 500}}); $('.channel-header__info .description').popover({placement: 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
} }
onSocketChange(msg) { onSocketChange(msg) {
if (msg.action === 'new_user') { if (msg.action === 'new_user') {

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

@@ -12,6 +12,7 @@ var PostStore = require('../stores/post_store.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 Constants = require('../utils/constants.jsx');
export default class ChannelLoader extends React.Component { export default class ChannelLoader extends React.Component {
constructor(props) { constructor(props) {
@@ -68,33 +69,19 @@ export default class ChannelLoader extends React.Component {
/* Update CSS classes to match user theme */ /* Update CSS classes to match user theme */
var user = UserStore.getCurrentUser(); var user = UserStore.getCurrentUser();
if (user.props && user.props.theme) { if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
Utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';'); Utils.applyTheme(user.theme_props);
Utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';'); } else {
Utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';'); Utils.applyTheme(Constants.THEMES.default);
Utils.changeCss('.mention', 'background: ' + user.props.theme + ';');
Utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';');
Utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}');
Utils.changeCss('.search-item-container:hover', 'background: ' + Utils.changeOpacity(user.props.theme, 0.05) + ';');
}
if (user.props.theme !== '#000000' && user.props.theme !== '#585858') {
Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, -10) + ';');
Utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;');
} else if (user.props.theme === '#000000') {
Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +50) + ';');
$('.team__header').addClass('theme--black');
} else if (user.props.theme === '#585858') {
Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';');
$('.team__header').addClass('theme--gray');
} }
/* Setup global mouse events */ /* Setup global mouse events */
$('body').on('click.userpopover', function popOver(e) { $('body').on('click', function hidePopover(e) {
if ($(e.target).attr('data-toggle') !== 'popover' && $('[data-toggle="popover"]').each(function eachPopover() {
$(e.target).parents('.popover.in').length === 0) { if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
$('.user-popover').popover('hide'); $(this).popover('hide');
} }
});
}); });
$('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) { $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) {

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

@@ -23,6 +23,7 @@ export default class CreatePost extends React.Component {
this.lastTime = 0; this.lastTime = 0;
this.getCurrentDraft = this.getCurrentDraft.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
this.postMsgKeyPress = this.postMsgKeyPress.bind(this); this.postMsgKeyPress = this.postMsgKeyPress.bind(this);
this.handleUserInput = this.handleUserInput.bind(this); this.handleUserInput = this.handleUserInput.bind(this);
@@ -36,23 +37,15 @@ export default class CreatePost extends React.Component {
PostStore.clearDraftUploads(); PostStore.clearDraftUploads();
const draft = PostStore.getCurrentDraft(); const draft = this.getCurrentDraft();
let previews = [];
let messageText = '';
let uploadsInProgress = [];
if (draft && draft.previews && draft.message) {
previews = draft.previews;
messageText = draft.message;
uploadsInProgress = draft.uploadsInProgress;
}
this.state = { this.state = {
channelId: ChannelStore.getCurrentId(), channelId: ChannelStore.getCurrentId(),
messageText: messageText, messageText: draft.messageText,
uploadsInProgress: uploadsInProgress, uploadsInProgress: draft.uploadsInProgress,
previews: previews, previews: draft.previews,
submitting: false, submitting: false,
initialText: messageText initialText: draft.messageText
}; };
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
@@ -60,6 +53,24 @@ export default class CreatePost extends React.Component {
this.resizePostHolder(); this.resizePostHolder();
} }
} }
getCurrentDraft() {
const draft = PostStore.getCurrentDraft();
const safeDraft = {previews: [], messageText: '', uploadsInProgress: []};
if (draft) {
if (draft.message) {
safeDraft.messageText = draft.message;
}
if (draft.previews) {
safeDraft.previews = draft.previews;
}
if (draft.uploadsInProgress) {
safeDraft.uploadsInProgress = draft.uploadsInProgress;
}
}
return safeDraft;
}
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
@@ -253,18 +264,9 @@ export default class CreatePost extends React.Component {
onChange() { onChange() {
const channelId = ChannelStore.getCurrentId(); const channelId = ChannelStore.getCurrentId();
if (this.state.channelId !== channelId) { if (this.state.channelId !== channelId) {
let draft = PostStore.getCurrentDraft(); const draft = this.getCurrentDraft();
let previews = []; this.setState({channelId: channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress});
let messageText = '';
let uploadsInProgress = [];
if (draft && draft.previews && draft.message) {
previews = draft.previews;
messageText = draft.message;
uploadsInProgress = draft.uploadsInProgress;
}
this.setState({channelId: channelId, messageText: messageText, initialText: messageText, submitting: false, serverError: null, postError: null, previews: previews, uploadsInProgress: uploadsInProgress});
} }
} }
getFileCount(channelId) { getFileCount(channelId) {

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

@@ -10,12 +10,14 @@ export default class EmailVerify extends React.Component {
this.state = {}; this.state = {};
} }
handleResend() { handleResend() {
window.location.href = window.location.href + '&resend=true'; const newAddress = window.location.href.replace('&resend_success=true', '');
window.location.href = newAddress + '&resend=true';
} }
render() { render() {
var title = ''; var title = '';
var body = ''; var body = '';
var resend = ''; var resend = '';
var resendConfirm = '';
if (this.props.isVerified === 'true') { if (this.props.isVerified === 'true') {
title = global.window.config.SiteName + ' Email Verified'; title = global.window.config.SiteName + ' Email Verified';
body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>; body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>;
@@ -30,6 +32,9 @@ export default class EmailVerify extends React.Component {
Resend Email Resend Email
</button> </button>
); );
if (this.props.resendSuccess) {
resendConfirm = <div><br /><p className='alert alert-success'><i className='fa fa-check'></i>{' Verification email sent.'}</p></div>;
}
} }
return ( return (
@@ -41,6 +46,7 @@ export default class EmailVerify extends React.Component {
<div className='panel-body'> <div className='panel-body'>
{body} {body}
{resend} {resend}
{resendConfirm}
</div> </div>
</div> </div>
</div> </div>
@@ -51,10 +57,12 @@ export default class EmailVerify extends React.Component {
EmailVerify.defaultProps = { EmailVerify.defaultProps = {
isVerified: 'false', isVerified: 'false',
teamURL: '', teamURL: '',
userEmail: '' userEmail: '',
resendSuccess: 'false'
}; };
EmailVerify.propTypes = { EmailVerify.propTypes = {
isVerified: React.PropTypes.string, isVerified: React.PropTypes.string,
teamURL: React.PropTypes.string, teamURL: React.PropTypes.string,
userEmail: React.PropTypes.string userEmail: React.PropTypes.string,
resendSuccess: React.PropTypes.string
}; };

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

@@ -114,7 +114,7 @@ export default class MoreDirectChannels extends React.Component {
<span aria-hidden='true'>&times;</span> <span aria-hidden='true'>&times;</span>
<span className='sr-only'>Close</span> <span className='sr-only'>Close</span>
</button> </button>
<h4 className='modal-title'>More Private Messages</h4> <h4 className='modal-title'>More Direct Messages</h4>
</div> </div>
<div className='modal-body'> <div className='modal-body'>
<ul className='nav nav-pills nav-stacked'> <ul className='nav nav-pills nav-stacked'>

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

@@ -93,6 +93,7 @@ export default class NewChannelModal extends React.Component {
<span> <span>
<Modal <Modal
show={this.props.show} show={this.props.show}
bsSize='large'
onHide={this.props.onModalDismissed} onHide={this.props.onModalDismissed}
> >
<Modal.Header closeButton={true}> <Modal.Header closeButton={true}>
@@ -122,7 +123,7 @@ export default class NewChannelModal extends React.Component {
/> />
{displayNameError} {displayNameError}
<p className='input__help dark'> <p className='input__help dark'>
{'Channel URL: ' + prettyTeamURL + this.props.channelData.name + ' ('} {'URL: ' + prettyTeamURL + this.props.channelData.name + ' ('}
<a <a
href='#' href='#'
onClick={this.props.onChangeURLPressed} onClick={this.props.onChangeURLPressed}

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

@@ -1,6 +1,7 @@
// 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.
const Utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx'); var client = require('../utils/client.jsx');
export default class PasswordResetSendLink extends React.Component { export default class PasswordResetSendLink extends React.Component {
@@ -15,8 +16,8 @@ export default class PasswordResetSendLink extends React.Component {
e.preventDefault(); e.preventDefault();
var state = {}; var state = {};
var email = React.findDOMNode(this.refs.email).value.trim(); var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase();
if (!email) { if (!email || !Utils.isEmail(email)) {
state.error = 'Please enter a valid email address.'; state.error = 'Please enter a valid email address.';
this.setState(state); this.setState(state);
return; return;
@@ -67,7 +68,7 @@ export default class PasswordResetSendLink extends React.Component {
<p>{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}</p> <p>{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}</p>
<div className={formClass}> <div className={formClass}>
<input <input
type='text' type='email'
className='form-control' className='form-control'
name='email' name='email'
ref='email' ref='email'

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

@@ -65,7 +65,7 @@ export default class PopoverListMembers extends React.Component {
> >
{count} {count}
<span <span
className='glyphicon glyphicon-user' className='fa fa-user'
aria-hidden='true' aria-hidden='true'
/> />
</div> </div>

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

@@ -51,7 +51,7 @@ export default class Post extends React.Component {
var post = this.props.post; var post = this.props.post;
client.createPost(post, post.channel_id, client.createPost(post, post.channel_id,
function success(data) { (data) => {
AsyncClient.getPosts(); AsyncClient.getPosts();
var channel = ChannelStore.get(post.channel_id); var channel = ChannelStore.get(post.channel_id);
@@ -65,11 +65,11 @@ export default class Post extends React.Component {
post: data post: data
}); });
}, },
function error() { () => {
post.state = Constants.POST_FAILED; post.state = Constants.POST_FAILED;
PostStore.updatePendingPost(post); PostStore.updatePendingPost(post);
this.forceUpdate(); this.forceUpdate();
}.bind(this) }
); );
post.state = Constants.POST_LOADING; post.state = Constants.POST_LOADING;
@@ -81,8 +81,40 @@ export default class Post extends React.Component {
return true; return true;
} }
if (nextProps.sameRoot !== this.props.sameRoot) {
return true;
}
if (nextProps.sameUser !== this.props.sameUser) {
return true;
}
if (this.getCommentCount(nextProps) !== this.getCommentCount(this.props)) {
return true;
}
return false; return false;
} }
getCommentCount(props) {
const post = props.post;
const parentPost = props.parentPost;
const posts = props.posts;
let commentCount = 0;
let commentRootId;
if (parentPost) {
commentRootId = post.root_id;
} else {
commentRootId = post.id;
}
for (let postId in posts) {
if (posts[postId].root_id === commentRootId) {
commentCount += 1;
}
}
return commentCount;
}
render() { render() {
var post = this.props.post; var post = this.props.post;
var parentPost = this.props.parentPost; var parentPost = this.props.parentPost;
@@ -93,18 +125,7 @@ export default class Post extends React.Component {
type = 'Comment'; type = 'Comment';
} }
var commentCount = 0; const commentCount = this.getCommentCount(this.props);
var commentRootId;
if (parentPost) {
commentRootId = post.root_id;
} else {
commentRootId = post.id;
}
for (var postId in posts) {
if (posts[postId].root_id === commentRootId) {
commentCount += 1;
}
}
var rootUser; var rootUser;
if (this.props.sameRoot) { if (this.props.sameRoot) {

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

@@ -35,7 +35,6 @@ export default class PostBody extends React.Component {
parseEmojis() { parseEmojis() {
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
global.window.emojify.run(React.findDOMNode(this.refs.message_span));
} }
componentDidMount() { componentDidMount() {

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

@@ -326,8 +326,8 @@ export default class PostList extends React.Component {
<strong><UserProfile userId={teammate.id} /></strong> <strong><UserProfile userId={teammate.id} /></strong>
</div> </div>
<p className='channel-intro-text'> <p className='channel-intro-text'>
{'This is the start of your private message history with ' + teammateName + '.'}<br/> {'This is the start of your direct message history with ' + teammateName + '.'}<br/>
{'Private messages and files shared here are not shown to people outside this area.'} {'Direct messages and files shared here are not shown to people outside this area.'}
</p> </p>
<a <a
className='intro-links' className='intro-links'
@@ -346,7 +346,7 @@ export default class PostList extends React.Component {
return ( return (
<div className='channel-intro'> <div className='channel-intro'>
<p className='channel-intro-text'>{'This is the start of your private message history with this teammate. Private messages and files shared here are not shown to people outside this area.'}</p> <p className='channel-intro-text'>{'This is the start of your direct message history with this teammate. Direct messages and files shared here are not shown to people outside this area.'}</p>
</div> </div>
); );
} }

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

@@ -49,6 +49,7 @@ export default class PostListContainer extends React.Component {
for (let i = 0; i <= this.state.postLists.length - 1; i++) { for (let i = 0; i <= this.state.postLists.length - 1; i++) {
postListCtls.push( postListCtls.push(
<PostList <PostList
key={'postlistkey' + i}
channelId={postLists[i]} channelId={postLists[i]}
isActive={postLists[i] === channelId} isActive={postLists[i] === channelId}
/> />

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

@@ -228,7 +228,7 @@ export default class RegisterAppModal extends React.Component {
data-dismiss='modal' data-dismiss='modal'
aria-label='Close' aria-label='Close'
> >
<span aria-hidden='true'>{'x'}</span> <span aria-hidden='true'>{'×'}</span>
</button> </button>
<h4 <h4
className='modal-title' className='modal-title'

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

@@ -56,7 +56,6 @@ export default class RhsComment extends React.Component {
} }
parseEmojis() { parseEmojis() {
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
global.window.emojify.run(React.findDOMNode(this.refs.message_holder));
} }
componentDidMount() { componentDidMount() {
this.parseEmojis(); this.parseEmojis();
@@ -114,14 +113,7 @@ export default class RhsComment extends React.Component {
var ownerOptions; var ownerOptions;
if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) { if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) {
ownerOptions = ( ownerOptions = (
<div <div className='dropdown'>
className='dropdown'
onClick={
function scroll() {
$('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time').scrollTop() + 50);
}
}
>
<a <a
href='#' href='#'
className='dropdown-toggle theme' className='dropdown-toggle theme'

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

@@ -65,6 +65,7 @@ export default class RhsHeaderPost extends React.Component {
aria-label='Close' aria-label='Close'
onClick={this.handleClose} onClick={this.handleClose}
> >
<i className='fa fa-sign-out'/>
</button> </button>
</div> </div>
); );

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

@@ -20,7 +20,6 @@ export default class RhsRootPost extends React.Component {
} }
parseEmojis() { parseEmojis() {
twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE});
global.window.emojify.run(React.findDOMNode(this.refs.message_holder));
} }
componentDidMount() { componentDidMount() {
this.parseEmojis(); this.parseEmojis();
@@ -54,7 +53,7 @@ export default class RhsRootPost extends React.Component {
var channelName; var channelName;
if (channel) { if (channel) {
if (channel.type === 'D') { if (channel.type === 'D') {
channelName = 'Private Message'; channelName = 'Direct Message';
} else { } else {
channelName = channel.display_name; channelName = channel.display_name;
} }

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

@@ -50,6 +50,7 @@ export default class SearchResultsHeader extends React.Component {
title='Close' title='Close'
onClick={this.handleClose} onClick={this.handleClose}
> >
<i className='fa fa-sign-out'/>
</button> </button>
</div> </div>
); );

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

@@ -64,7 +64,7 @@ export default class SearchResultsItem extends React.Component {
if (channel) { if (channel) {
channelName = channel.display_name; channelName = channel.display_name;
if (channel.type === 'D') { if (channel.type === 'D') {
channelName = 'Private Message'; channelName = 'Direct Message';
} }
} }

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

@@ -566,7 +566,7 @@ export default class Sidebar extends React.Component {
{privateChannelItems} {privateChannelItems}
</ul> </ul>
<ul className='nav nav-pills nav-stacked'> <ul className='nav nav-pills nav-stacked'>
<li><h4>Private Messages</h4></li> <li><h4>Direct Messages</h4></li>
{directMessageItems} {directMessageItems}
{directMessageMore} {directMessageMore}
</ul> </ul>

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

@@ -6,6 +6,10 @@ var client = require('../utils/client.jsx');
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
export default class SidebarRightMenu extends React.Component { export default class SidebarRightMenu extends React.Component {
componentDidMount() {
$('.sidebar--left .dropdown-menu').perfectScrollbar();
}
constructor(props) { constructor(props) {
super(props); super(props);

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

@@ -1,7 +1,7 @@
// 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 utils = require('../utils/utils.jsx'); var Utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx'); var client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx');
@@ -30,13 +30,26 @@ export default class SignupUserComplete extends React.Component {
handleSubmit(e) { handleSubmit(e) {
e.preventDefault(); e.preventDefault();
const providedEmail = React.findDOMNode(this.refs.email).value.trim();
if (!providedEmail) {
this.setState({nameError: '', emailError: 'This field is required', passwordError: ''});
return;
}
if (!Utils.isEmail(providedEmail)) {
this.setState({nameError: '', emailError: 'Please enter a valid email address', passwordError: ''});
return;
}
this.state.user.email = providedEmail;
this.state.user.username = React.findDOMNode(this.refs.name).value.trim().toLowerCase(); this.state.user.username = React.findDOMNode(this.refs.name).value.trim().toLowerCase();
if (!this.state.user.username) { if (!this.state.user.username) {
this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''});
return; return;
} }
var usernameError = utils.isValidUsername(this.state.user.username); var usernameError = Utils.isValidUsername(this.state.user.username);
if (usernameError === 'Cannot use a reserved word as a username.') { if (usernameError === 'Cannot use a reserved word as a username.') {
this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''}); this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''});
return; return;
@@ -50,12 +63,6 @@ export default class SignupUserComplete extends React.Component {
return; return;
} }
this.state.user.email = React.findDOMNode(this.refs.email).value.trim();
if (!this.state.user.email) {
this.setState({nameError: '', emailError: 'This field is required', passwordError: ''});
return;
}
this.state.user.password = React.findDOMNode(this.refs.password).value.trim(); this.state.user.password = React.findDOMNode(this.refs.password).value.trim();
if (!this.state.user.password || this.state.user.password .length < 5) { if (!this.state.user.password || this.state.user.password .length < 5) {
this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''});

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

@@ -0,0 +1,108 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Constants = require('../../utils/constants.jsx');
export default class CustomThemeChooser extends React.Component {
constructor(props) {
super(props);
this.onPickerChange = this.onPickerChange.bind(this);
this.onInputChange = this.onInputChange.bind(this);
this.pasteBoxChange = this.pasteBoxChange.bind(this);
this.state = {};
}
componentDidMount() {
$('.color-picker').colorpicker().on('changeColor', this.onPickerChange);
}
onPickerChange(e) {
const theme = this.props.theme;
theme[e.target.id] = e.color.toHex();
theme.type = 'custom';
this.props.updateTheme(theme);
}
onInputChange(e) {
const theme = this.props.theme;
theme[e.target.parentNode.id] = e.target.value;
theme.type = 'custom';
this.props.updateTheme(theme);
}
pasteBoxChange(e) {
const text = e.target.value;
if (text.length === 0) {
return;
}
const colors = text.split(',');
const theme = {type: 'custom'};
let index = 0;
Constants.THEME_ELEMENTS.forEach((element) => {
if (index < colors.length) {
theme[element.id] = colors[index];
}
index++;
});
this.props.updateTheme(theme);
}
render() {
const theme = this.props.theme;
const elements = [];
let colors = '';
Constants.THEME_ELEMENTS.forEach((element) => {
elements.push(
<div className='col-sm-4 form-group'>
<label className='custom-label'>{element.uiName}</label>
<div
className='input-group color-picker'
id={element.id}
>
<input
className='form-control'
type='text'
defaultValue={theme[element.id]}
onChange={this.onInputChange}
/>
<span className='input-group-addon'><i></i></span>
</div>
</div>
);
colors += theme[element.id] + ',';
});
const pasteBox = (
<div className='col-sm-12'>
<label className='custom-label'>
{'Copy and paste to share theme colors:'}
</label>
<input
type='text'
className='form-control'
value={colors}
onChange={this.pasteBoxChange}
/>
</div>
);
return (
<div>
<div className='row form-group'>
{elements}
</div>
<div className='row'>
{pasteBox}
</div>
</div>
);
}
}
CustomThemeChooser.propTypes = {
theme: React.PropTypes.object.isRequired,
updateTheme: React.PropTypes.func.isRequired
};

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

@@ -0,0 +1,179 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
const UserStore = require('../../stores/user_store.jsx');
const Utils = require('../../utils/utils.jsx');
const Client = require('../../utils/client.jsx');
const Modal = ReactBootstrap.Modal;
const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx');
const Constants = require('../../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
export default class ImportThemeModal extends React.Component {
constructor(props) {
super(props);
this.updateShow = this.updateShow.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
this.state = {
inputError: '',
show: false
};
}
componentDidMount() {
UserStore.addImportModalListener(this.updateShow);
}
componentWillUnmount() {
UserStore.removeImportModalListener(this.updateShow);
}
updateShow(show) {
this.setState({show});
}
handleSubmit(e) {
e.preventDefault();
const text = React.findDOMNode(this.refs.input).value;
if (!this.isInputValid(text)) {
this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
return;
}
const colors = text.split(',');
const theme = {type: 'custom'};
theme.sidebarBg = colors[0];
theme.sidebarText = colors[5];
theme.sidebarUnreadText = colors[5];
theme.sidebarTextHoverBg = colors[4];
theme.sidebarTextHoverColor = colors[5];
theme.sidebarTextActiveBg = colors[2];
theme.sidebarTextActiveColor = colors[3];
theme.sidebarHeaderBg = colors[1];
theme.sidebarHeaderTextColor = colors[5];
theme.onlineIndicator = colors[6];
theme.mentionBj = colors[7];
theme.mentionColor = '#ffffff';
theme.centerChannelBg = '#ffffff';
theme.centerChannelColor = '#333333';
theme.linkColor = '#2389d7';
theme.buttonBg = '#26a970';
theme.buttonColor = '#ffffff';
let user = UserStore.getCurrentUser();
user.theme_props = theme;
Client.updateUser(user,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ME,
me: data
});
this.setState({show: false});
Utils.applyTheme(theme);
$('#user_settings').modal('show');
},
(err) => {
var state = this.getStateFromStores();
state.serverError = err;
this.setState(state);
}
);
}
isInputValid(text) {
if (text.length === 0) {
return false;
}
if (text.indexOf(' ') !== -1) {
return false;
}
if (text.length > 0 && text.indexOf(',') === -1) {
return false;
}
if (text.length > 0) {
const colors = text.split(',');
if (colors.length !== 8) {
return false;
}
for (let i = 0; i < colors.length; i++) {
if (colors[i].length !== 7 && colors[i].length !== 4) {
return false;
}
if (colors[i].charAt(0) !== '#') {
return false;
}
}
}
return true;
}
handleChange(e) {
if (this.isInputValid(e.target.value)) {
this.setState({inputError: null});
} else {
this.setState({inputError: 'Invalid format, please try copying and pasting in again.'});
}
}
render() {
return (
<span>
<Modal
show={this.state.show}
onHide={() => this.setState({show: false})}
>
<Modal.Header closeButton={true}>
<Modal.Title>{'Import Slack Theme'}</Modal.Title>
</Modal.Header>
<form
role='form'
className='form-horizontal'
>
<Modal.Body>
<p>
{'To import a theme, go to a Slack team and look for “”Preferences” -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'}
</p>
<div className='form-group less'>
<div className='col-sm-9'>
<input
ref='input'
type='text'
className='form-control'
onChange={this.handleChange}
/>
{this.state.inputError}
</div>
</div>
</Modal.Body>
<Modal.Footer>
<button
type='button'
className='btn btn-default'
onClick={() => this.setState({show: false})}
>
{'Cancel'}
</button>
<button
onClick={this.handleSubmit}
type='submit'
className='btn btn-primary'
tabIndex='3'
>
{'Submit'}
</button>
</Modal.Footer>
</form>
</Modal>
</span>
);
}
}

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

@@ -0,0 +1,55 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Utils = require('../../utils/utils.jsx');
var Constants = require('../../utils/constants.jsx');
export default class PremadeThemeChooser extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const theme = this.props.theme;
const premadeThemes = [];
for (const k in Constants.THEMES) {
if (Constants.THEMES.hasOwnProperty(k)) {
const premadeTheme = $.extend(true, {}, Constants.THEMES[k]);
let activeClass = '';
if (premadeTheme.type === theme.type) {
activeClass = 'active';
}
premadeThemes.push(
<div className='col-sm-3 premade-themes'>
<div
className={activeClass}
onClick={() => this.props.updateTheme(premadeTheme)}
>
<label>
<img
className='img-responsive'
src={'/static/images/themes/' + premadeTheme.type.toLowerCase() + '.png'}
/>
<div className='theme-label'>{Utils.toTitleCase(premadeTheme.type)}</div>
</label>
</div>
</div>
);
}
}
return (
<div className='row'>
{premadeThemes}
</div>
);
}
}
PremadeThemeChooser.propTypes = {
theme: React.PropTypes.object.isRequired,
updateTheme: React.PropTypes.func.isRequired
};

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

@@ -2,64 +2,32 @@
// See License.txt for license information. // See License.txt for license information.
var UserStore = require('../../stores/user_store.jsx'); var UserStore = require('../../stores/user_store.jsx');
var SettingItemMin = require('../setting_item_min.jsx');
var SettingItemMax = require('../setting_item_max.jsx');
var Client = require('../../utils/client.jsx'); var Client = require('../../utils/client.jsx');
var Utils = require('../../utils/utils.jsx'); var Utils = require('../../utils/utils.jsx');
var ThemeColors = ['#2389d7', '#008a17', '#dc4fad', '#ac193d', '#0072c6', '#d24726', '#ff8f32', '#82ba00', '#03b3b2', '#008299', '#4617b4', '#8c0095', '#004b8b', '#004b8b', '#570000', '#380000', '#585858', '#000000']; const CustomThemeChooser = require('./custom_theme_chooser.jsx');
const PremadeThemeChooser = require('./premade_theme_chooser.jsx');
const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx');
const Constants = require('../../utils/constants.jsx');
const ActionTypes = Constants.ActionTypes;
export default class UserSettingsAppearance extends React.Component { export default class UserSettingsAppearance extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.onChange = this.onChange.bind(this);
this.submitTheme = this.submitTheme.bind(this); this.submitTheme = this.submitTheme.bind(this);
this.updateTheme = this.updateTheme.bind(this); this.updateTheme = this.updateTheme.bind(this);
this.handleClose = this.handleClose.bind(this); this.handleClose = this.handleClose.bind(this);
this.handleImportModal = this.handleImportModal.bind(this);
this.state = this.getStateFromStores(); this.state = this.getStateFromStores();
}
getStateFromStores() {
var user = UserStore.getCurrentUser();
var theme = '#2389d7';
if (ThemeColors != null) {
theme = ThemeColors[0];
}
if (user.props && user.props.theme) {
theme = user.props.theme;
}
return {theme: theme.toLowerCase()}; this.originalTheme = this.state.theme;
}
submitTheme(e) {
e.preventDefault();
var user = UserStore.getCurrentUser();
if (!user.props) {
user.props = {};
}
user.props.theme = this.state.theme;
Client.updateUser(user,
function success() {
this.props.updateSection('');
window.location.reload();
}.bind(this),
function fail(err) {
var state = this.getStateFromStores();
state.serverError = err;
this.setState(state);
}.bind(this)
);
}
updateTheme(e) {
var hex = Utils.rgb2hex(e.target.style.backgroundColor);
this.setState({theme: hex.toLowerCase()});
}
handleClose() {
this.setState({serverError: null});
this.props.updateTab('general');
} }
componentDidMount() { componentDidMount() {
UserStore.addChangeListener(this.onChange);
if (this.props.activeSection === 'theme') { if (this.props.activeSection === 'theme') {
$(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border');
} }
@@ -72,8 +40,81 @@ export default class UserSettingsAppearance extends React.Component {
} }
} }
componentWillUnmount() { componentWillUnmount() {
UserStore.removeChangeListener(this.onChange);
$('#user_settings').off('hidden.bs.modal', this.handleClose); $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection(''); }
getStateFromStores() {
const user = UserStore.getCurrentUser();
let theme = null;
if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) {
theme = user.theme_props;
} else {
theme = $.extend(true, {}, Constants.THEMES.default);
}
let type = 'premade';
if (theme.type === 'custom') {
type = 'custom';
}
return {theme, type};
}
onChange() {
const newState = this.getStateFromStores();
if (!Utils.areStatesEqual(this.state, newState)) {
this.setState(newState);
}
}
submitTheme(e) {
e.preventDefault();
var user = UserStore.getCurrentUser();
user.theme_props = this.state.theme;
Client.updateUser(user,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ME,
me: data
});
$('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateTab('general');
$('#user_settings').modal('hide');
},
(err) => {
var state = this.getStateFromStores();
state.serverError = err;
this.setState(state);
}
);
}
updateTheme(theme) {
this.setState({theme});
Utils.applyTheme(theme);
}
updateType(type) {
this.setState({type});
}
handleClose() {
const state = this.getStateFromStores();
state.serverError = null;
Utils.applyTheme(state.theme);
this.setState(state);
$('.ps-container.modal-body').scrollTop(0);
$('.ps-container.modal-body').perfectScrollbar('update');
$('#user_settings').modal('hide');
}
handleImportModal() {
$('#user_settings').modal('hide');
AppDispatcher.handleViewAction({
type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL,
value: true
});
} }
render() { render() {
var serverError; var serverError;
@@ -81,67 +122,73 @@ export default class UserSettingsAppearance extends React.Component {
serverError = this.state.serverError; serverError = this.state.serverError;
} }
var themeSection; const displayCustom = this.state.type === 'custom';
var self = this;
if (ThemeColors != null) { let custom;
if (this.props.activeSection === 'theme') { let premade;
var themeButtons = []; if (displayCustom) {
custom = (
for (var i = 0; i < ThemeColors.length; i++) { <CustomThemeChooser
themeButtons.push( theme={this.state.theme}
<button updateTheme={this.updateTheme}
key={ThemeColors[i] + 'key' + i} />
ref={ThemeColors[i]} );
type='button' } else {
className='btn btn-lg color-btn' premade = (
style={{backgroundColor: ThemeColors[i]}} <PremadeThemeChooser
onClick={this.updateTheme} theme={this.state.theme}
/> updateTheme={this.updateTheme}
); />
} );
var inputs = [];
inputs.push(
<li
key='themeColorSetting'
className='setting-list-item'
>
<div
className='btn-group'
data-toggle='buttons-radio'
>
{themeButtons}
</div>
</li>
);
themeSection = (
<SettingItemMax
title='Theme Color'
inputs={inputs}
submit={this.submitTheme}
serverError={serverError}
updateSection={function updateSection(e) {
self.props.updateSection('');
e.preventDefault();
}}
/>
);
} else {
themeSection = (
<SettingItemMin
title='Theme Color'
describe={this.state.theme}
updateSection={function updateSection() {
self.props.updateSection('theme');
}}
/>
);
}
} }
const themeUI = (
<div className='section-max appearance-section'>
<div className='col-sm-12'>
<div className='radio'>
<label>
<input type='radio'
checked={!displayCustom}
onChange={this.updateType.bind(this, 'premade')}
>
{'Theme Colors'}
</input>
</label>
<br/>
</div>
{premade}
<div className='radio'>
<label>
<input type='radio'
checked={displayCustom}
onChange={this.updateType.bind(this, 'custom')}
>
{'Custom Theme'}
</input>
</label>
<br/>
</div>
{custom}
<hr />
{serverError}
<a
className='btn btn-sm btn-primary'
href='#'
onClick={this.submitTheme}
>
{'Submit'}
</a>
<a
className='btn btn-sm theme'
href='#'
onClick={this.handleClose}
>
{'Cancel'}
</a>
</div>
</div>
);
return ( return (
<div> <div>
<div className='modal-header'> <div className='modal-header'>
@@ -151,21 +198,28 @@ export default class UserSettingsAppearance extends React.Component {
data-dismiss='modal' data-dismiss='modal'
aria-label='Close' aria-label='Close'
> >
<span aria-hidden='true'>&times;</span> <span aria-hidden='true'>{'×'}</span>
</button> </button>
<h4 <h4
className='modal-title' className='modal-title'
ref='title' ref='title'
> >
<i className='modal-back'></i>Appearance Settings <i className='modal-back'></i>{'Appearance Settings'}
</h4> </h4>
</div> </div>
<div className='user-settings'> <div className='user-settings'>
<h3 className='tab-header'>Appearance Settings</h3> <h3 className='tab-header'>{'Appearance Settings'}</h3>
<div className='divider-dark first'/> <div className='divider-dark first'/>
{themeSection} {themeUI}
<div className='divider-dark'/> <div className='divider-dark'/>
</div> </div>
<br/>
<a
className='theme'
onClick={this.handleImportModal}
>
{'Import from Slack'}
</a>
</div> </div>
); );
} }
@@ -176,6 +230,5 @@ UserSettingsAppearance.defaultProps = {
}; };
UserSettingsAppearance.propTypes = { UserSettingsAppearance.propTypes = {
activeSection: React.PropTypes.string, activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func updateTab: React.PropTypes.func
}; };

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

@@ -64,7 +64,7 @@ export default class DeveloperTab extends React.Component {
data-dismiss='modal' data-dismiss='modal'
aria-label='Close' aria-label='Close'
> >
<span aria-hidden='true'>{'x'}</span> <span aria-hidden='true'>{'×'}</span>
</button> </button>
<h4 <h4
className='modal-title' className='modal-title'

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

@@ -60,7 +60,7 @@ export default class UserSettingsModal extends React.Component {
data-dismiss='modal' data-dismiss='modal'
aria-label='Close' aria-label='Close'
> >
<span aria-hidden='true'>{'x'}</span> <span aria-hidden='true'>{'×'}</span>
</button> </button>
<h4 <h4
className='modal-title' className='modal-title'

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

@@ -241,7 +241,7 @@ export default class NotificationsTab extends React.Component {
checked={notifyActive[1]} checked={notifyActive[1]}
onChange={this.handleNotifyRadio.bind(this, 'mention')} onChange={this.handleNotifyRadio.bind(this, 'mention')}
> >
Only for mentions and private messages Only for mentions and direct messages
</input> </input>
</label> </label>
<br/> <br/>
@@ -277,7 +277,7 @@ export default class NotificationsTab extends React.Component {
} else { } else {
let describe = ''; let describe = '';
if (this.state.notifyLevel === 'mention') { if (this.state.notifyLevel === 'mention') {
describe = 'Only for mentions and private messages'; describe = 'Only for mentions and direct messages';
} else if (this.state.notifyLevel === 'none') { } else if (this.state.notifyLevel === 'none') {
describe = 'Never'; describe = 'Never';
} else { } else {
@@ -414,7 +414,7 @@ export default class NotificationsTab extends React.Component {
</label> </label>
<br/> <br/>
</div> </div>
<div><br/>{'Email notifications are sent for mentions and private messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}</div> <div><br/>{'Email notifications are sent for mentions and direct messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}</div>
</div> </div>
); );

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

@@ -4,13 +4,14 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"autolinker": "0.18.1", "autolinker": "0.18.1",
"babel-runtime": "5.8.24",
"bootstrap-colorpicker": "2.2.0",
"flux": "2.1.1", "flux": "2.1.1",
"keymirror": "0.1.1", "keymirror": "0.1.1",
"marked": "0.3.5",
"object-assign": "3.0.0", "object-assign": "3.0.0",
"react-zeroclipboard-mixin": "0.1.0", "react-zeroclipboard-mixin": "0.1.0",
"twemoji": "1.4.1", "twemoji": "1.4.1"
"babel-runtime": "5.8.24",
"marked": "0.3.5"
}, },
"devDependencies": { "devDependencies": {
"browserify": "11.0.1", "browserify": "11.0.1",
@@ -28,8 +29,15 @@
}, },
"browserify": { "browserify": {
"transform": [ "transform": [
["babelify", { "optional": ["runtime"] }], [
"envify" "babelify",
{
"optional": [
"runtime"
]
}
],
"envify"
] ]
}, },
"jest": { "jest": {

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

@@ -34,6 +34,7 @@ var ActivityLogModal = require('../components/activity_log_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx'); var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
var RegisterAppModal = require('../components/register_app_modal.jsx'); var RegisterAppModal = require('../components/register_app_modal.jsx');
var ImportThemeModal = require('../components/user_settings/import_theme_modal.jsx');
var Constants = require('../utils/constants.jsx'); var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
@@ -84,6 +85,11 @@ function setupChannelPage(props) {
document.getElementById('user_settings_modal') document.getElementById('user_settings_modal')
); );
React.render(
<ImportThemeModal />,
document.getElementById('import_theme_modal')
);
React.render( React.render(
<TeamSettingsModal teamDisplayName={props.TeamDisplayName} />, <TeamSettingsModal teamDisplayName={props.TeamDisplayName} />,
document.getElementById('team_settings_modal') document.getElementById('team_settings_modal')

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

@@ -9,6 +9,7 @@ global.window.setupVerifyPage = function setupVerifyPage(props) {
isVerified={props.IsVerified} isVerified={props.IsVerified}
teamURL={props.TeamURL} teamURL={props.TeamURL}
userEmail={props.UserEmail} userEmail={props.UserEmail}
resendSuccess={props.ResendSuccess}
/>, />,
document.getElementById('verify') document.getElementById('verify')
); );

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

@@ -14,6 +14,7 @@ var CHANGE_EVENT_SESSIONS = 'change_sessions';
var CHANGE_EVENT_AUDITS = 'change_audits'; var CHANGE_EVENT_AUDITS = 'change_audits';
var CHANGE_EVENT_TEAMS = 'change_teams'; var CHANGE_EVENT_TEAMS = 'change_teams';
var CHANGE_EVENT_STATUSES = 'change_statuses'; var CHANGE_EVENT_STATUSES = 'change_statuses';
var TOGGLE_IMPORT_MODAL_EVENT = 'toggle_import_modal';
class UserStoreClass extends EventEmitter { class UserStoreClass extends EventEmitter {
constructor() { constructor() {
@@ -34,6 +35,9 @@ class UserStoreClass extends EventEmitter {
this.emitStatusesChange = this.emitStatusesChange.bind(this); this.emitStatusesChange = this.emitStatusesChange.bind(this);
this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this); this.addStatusesChangeListener = this.addStatusesChangeListener.bind(this);
this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this); this.removeStatusesChangeListener = this.removeStatusesChangeListener.bind(this);
this.emitToggleImportModal = this.emitToggleImportModal.bind(this);
this.addImportModalListener = this.addImportModalListener.bind(this);
this.removeImportModalListener = this.removeImportModalListener.bind(this);
this.setCurrentId = this.setCurrentId.bind(this); this.setCurrentId = this.setCurrentId.bind(this);
this.getCurrentId = this.getCurrentId.bind(this); this.getCurrentId = this.getCurrentId.bind(this);
this.getCurrentUser = this.getCurrentUser.bind(this); this.getCurrentUser = this.getCurrentUser.bind(this);
@@ -114,6 +118,15 @@ class UserStoreClass extends EventEmitter {
removeStatusesChangeListener(callback) { removeStatusesChangeListener(callback) {
this.removeListener(CHANGE_EVENT_STATUSES, callback); this.removeListener(CHANGE_EVENT_STATUSES, callback);
} }
emitToggleImportModal(value) {
this.emit(TOGGLE_IMPORT_MODAL_EVENT, value);
}
addImportModalListener(callback) {
this.on(TOGGLE_IMPORT_MODAL_EVENT, callback);
}
removeImportModalListener(callback) {
this.removeListener(TOGGLE_IMPORT_MODAL_EVENT, callback);
}
setCurrentId(id) { setCurrentId(id) {
this.gCurrentId = id; this.gCurrentId = id;
if (id == null) { if (id == null) {
@@ -321,6 +334,9 @@ UserStore.dispatchToken = AppDispatcher.register(function registry(payload) {
UserStore.pSetStatuses(action.statuses); UserStore.pSetStatuses(action.statuses);
UserStore.emitStatusesChange(); UserStore.emitStatusesChange();
break; break;
case ActionTypes.TOGGLE_IMPORT_THEME_MODAL:
UserStore.emitToggleImportModal(action.value);
break;
default: default:
} }

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

@@ -36,7 +36,9 @@ module.exports = {
RECIEVED_CONFIG: null, RECIEVED_CONFIG: null,
RECIEVED_LOGS: null RECIEVED_LOGS: null,
TOGGLE_IMPORT_THEME_MODAL: null
}), }),
PayloadSources: keyMirror({ PayloadSources: keyMirror({
@@ -110,5 +112,138 @@ module.exports = {
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>",
COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>" COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>",
THEMES: {
default: {
type: 'Mattermost',
sidebarBg: '#fafafa',
sidebarText: '#999999',
sidebarUnreadText: '#333333',
sidebarTextHoverBg: '#e6f2fa',
sidebarTextHoverColor: '#999999',
sidebarTextActiveBg: '#e1e1e1',
sidebarTextActiveColor: '#111111',
sidebarHeaderBg: '#2389d7',
sidebarHeaderTextColor: '#ffffff',
onlineIndicator: '#7DBE00',
mentionBj: '#2389d7',
mentionColor: '#ffffff',
centerChannelBg: '#ffffff',
centerChannelColor: '#333333',
linkColor: '#2389d7',
buttonBg: '#2389d7',
buttonColor: '#FFFFFF'
},
slack: {
type: 'Slack',
sidebarBg: '#4D394B',
sidebarText: '#ab9ba9',
sidebarUnreadText: '#FFFFFF',
sidebarTextHoverBg: '#3e313c',
sidebarTextHoverColor: '#ab9ba9',
sidebarTextActiveBg: '#4c9689',
sidebarTextActiveColor: '#FFFFFF',
sidebarHeaderBg: '#4D394B',
sidebarHeaderTextColor: '#FFFFFF',
onlineIndicator: '#4c9689',
mentionBj: '#eb4d5c',
mentionColor: '#FFFFFF',
centerChannelBg: '#FFFFFF',
centerChannelColor: '#333333',
linkColor: '#2389d7',
buttonBg: '#26a970',
buttonColor: '#FFFFFF'
},
dark: {
type: 'Dark',
sidebarBg: '#1B2C3E',
sidebarText: '#bbbbbb',
sidebarUnreadText: '#fff',
sidebarTextHoverBg: '#4A5664',
sidebarTextHoverColor: '#bbbbbb',
sidebarTextActiveBg: '#39769C',
sidebarTextActiveColor: '#FFFFFF',
sidebarHeaderBg: '#1B2C3E',
sidebarHeaderTextColor: '#FFFFFF',
onlineIndicator: '#4c9689',
mentionBj: '#B74A4A',
mentionColor: '#FFFFFF',
centerChannelBg: '#2F3E4E',
centerChannelColor: '#DDDDDD',
linkColor: '#A4FFEB',
buttonBg: '#2B9C99',
buttonColor: '#FFFFFF'
}
},
THEME_ELEMENTS: [
{
id: 'sidebarBg',
uiName: 'Sidebar BG'
},
{
id: 'sidebarText',
uiName: 'Sidebar text color'
},
{
id: 'sidebarHeaderBg',
uiName: 'Sidebar Header BG'
},
{
id: 'sidebarHeaderTextColor',
uiName: 'Sidebar Header text color'
},
{
id: 'sidebarUnreadText',
uiName: 'Sidebar unread text color'
},
{
id: 'sidebarTextHoverBg',
uiName: 'Sidebar text hover BG'
},
{
id: 'sidebarTextHoverColor',
uiName: 'Sidebar text hover color'
},
{
id: 'sidebarTextActiveBg',
uiName: 'Sidebar text active BG'
},
{
id: 'sidebarTextActiveColor',
uiName: 'Sidebar text active color'
},
{
id: 'onlineIndicator',
uiName: 'Online indicator'
},
{
id: 'mentionBj',
uiName: 'Mention jewel BG'
},
{
id: 'mentionColor',
uiName: 'Mention jewel text color'
},
{
id: 'centerChannelBg',
uiName: 'Center channel BG'
},
{
id: 'centerChannelColor',
uiName: 'Center channel text color'
},
{
id: 'linkColor',
uiName: 'Link color'
},
{
id: 'buttonBg',
uiName: 'Button BG'
},
{
id: 'buttonColor',
uiName: 'Button Color'
}
]
}; };

159
web/react/utils/emoticons.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,159 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
const emoticonPatterns = {
smile: /:-?\)/g, // :)
open_mouth: /:o/gi, // :o
scream: /:-o/gi, // :-o
smirk: /[:;]-?]/g, // :]
grinning: /[:;]-?d/gi, // :D
stuck_out_tongue_closed_eyes: /x-d/gi, // x-d
stuck_out_tongue_winking_eye: /[:;]-?p/gi, // ;p
rage: /:-?[\[@]/g, // :@
frowning: /:-?\(/g, // :(
sob: /:[']-?\(|:&#x27;\(/g, // :`(
kissing_heart: /:-?\*/g, // :*
wink: /;-?\)/g, // ;)
pensive: /:-?\//g, // :/
confounded: /:-?s/gi, // :s
flushed: /:-?\|/g, // :|
relaxed: /:-?\$/g, // :$
mask: /:-x/gi, // :-x
heart: /<3|&lt;3/g, // <3
broken_heart: /<\/3|&lt;&#x2F;3/g, // </3
thumbsup: /:\+1:/g, // :+1:
thumbsdown: /:\-1:/g // :-1:
};
function initializeEmoticonMap() {
const emoticonNames =
('+1,-1,100,1234,8ball,a,ab,abc,abcd,accept,aerial_tramway,airplane,alarm_clock,alien,ambulance,anchor,angel,' +
'anger,angry,anguished,ant,apple,aquarius,aries,arrow_backward,arrow_double_down,arrow_double_up,arrow_down,' +
'arrow_down_small,arrow_forward,arrow_heading_down,arrow_heading_up,arrow_left,arrow_lower_left,' +
'arrow_lower_right,arrow_right,arrow_right_hook,arrow_up,arrow_up_down,arrow_up_small,arrow_upper_left,' +
'arrow_upper_right,arrows_clockwise,arrows_counterclockwise,art,articulated_lorry,astonished,atm,b,baby,' +
'baby_bottle,baby_chick,baby_symbol,back,baggage_claim,balloon,ballot_box_with_check,bamboo,banana,bangbang,' +
'bank,bar_chart,barber,baseball,basketball,bath,bathtub,battery,bear,bee,beer,beers,beetle,beginner,bell,bento,' +
'bicyclist,bike,bikini,bird,birthday,black_circle,black_joker,black_medium_small_square,black_medium_square,' +
'black_nib,black_small_square,black_square,black_square_button,blossom,blowfish,blue_book,blue_car,blue_heart,' +
'blush,boar,boat,bomb,book,bookmark,bookmark_tabs,books,boom,boot,bouquet,bow,bowling,bowtie,boy,bread,' +
'bride_with_veil,bridge_at_night,briefcase,broken_heart,bug,bulb,bullettrain_front,bullettrain_side,bus,busstop,' +
'bust_in_silhouette,busts_in_silhouette,cactus,cake,calendar,calling,camel,camera,cancer,candy,capital_abcd,' +
'capricorn,car,card_index,carousel_horse,cat,cat2,cd,chart,chart_with_downwards_trend,chart_with_upwards_trend,' +
'checkered_flag,cherries,cherry_blossom,chestnut,chicken,children_crossing,chocolate_bar,christmas_tree,church,' +
'cinema,circus_tent,city_sunrise,city_sunset,cl,clap,clapper,clipboard,clock1,clock10,clock1030,clock11,' +
'clock1130,clock12,clock1230,clock130,clock2,clock230,clock3,clock330,clock4,clock430,clock5,clock530,clock6,' +
'clock630,clock7,clock730,clock8,clock830,clock9,clock930,closed_book,closed_lock_with_key,closed_umbrella,cloud,' +
'clubs,cn,cocktail,coffee,cold_sweat,collision,computer,confetti_ball,confounded,confused,congratulations,' +
'construction,construction_worker,convenience_store,cookie,cool,cop,copyright,corn,couple,couple_with_heart,' +
'couplekiss,cow,cow2,credit_card,crescent_moon,crocodile,crossed_flags,crown,cry,crying_cat_face,crystal_ball,' +
'cupid,curly_loop,currency_exchange,curry,custard,customs,cyclone,dancer,dancers,dango,dart,dash,date,de,' +
'deciduous_tree,department_store,diamond_shape_with_a_dot_inside,diamonds,disappointed,disappointed_relieved,' +
'dizzy,dizzy_face,do_not_litter,dog,dog2,dollar,dolls,dolphin,donut,door,doughnut,dragon,dragon_face,dress,' +
'dromedary_camel,droplet,dvd,e-mail,ear,ear_of_rice,earth_africa,earth_americas,earth_asia,egg,eggplant,eight,' +
'eight_pointed_black_star,eight_spoked_asterisk,electric_plug,elephant,email,end,envelope,es,euro,' +
'european_castle,european_post_office,evergreen_tree,exclamation,expressionless,eyeglasses,eyes,facepunch,' +
'factory,fallen_leaf,family,fast_forward,fax,fearful,feelsgood,feet,ferris_wheel,file_folder,finnadie,fire,' +
'fire_engine,fireworks,first_quarter_moon,first_quarter_moon_with_face,fish,fish_cake,fishing_pole_and_fish,fist,' +
'five,flags,flashlight,floppy_disk,flower_playing_cards,flushed,foggy,football,fork_and_knife,fountain,four,' +
'four_leaf_clover,fr,free,fried_shrimp,fries,frog,frowning,fu,fuelpump,full_moon,full_moon_with_face,game_die,gb,' +
'gem,gemini,ghost,gift,gift_heart,girl,globe_with_meridians,goat,goberserk,godmode,golf,grapes,green_apple,' +
'green_book,green_heart,grey_exclamation,grey_question,grimacing,grin,grinning,guardsman,guitar,gun,haircut,' +
'hamburger,hammer,hamster,hand,handbag,hankey,hash,hatched_chick,hatching_chick,headphones,hear_no_evil,heart,' +
'heart_decoration,heart_eyes,heart_eyes_cat,heartbeat,heartpulse,hearts,heavy_check_mark,heavy_division_sign,' +
'heavy_dollar_sign,heavy_exclamation_mark,heavy_minus_sign,heavy_multiplication_x,heavy_plus_sign,helicopter,' +
'herb,hibiscus,high_brightness,high_heel,hocho,honey_pot,honeybee,horse,horse_racing,hospital,hotel,hotsprings,' +
'hourglass,hourglass_flowing_sand,house,house_with_garden,hurtrealbad,hushed,ice_cream,icecream,id,' +
'ideograph_advantage,imp,inbox_tray,incoming_envelope,information_desk_person,information_source,innocent,' +
'interrobang,iphone,it,izakaya_lantern,jack_o_lantern,japan,japanese_castle,japanese_goblin,japanese_ogre,jeans,' +
'joy,joy_cat,jp,key,keycap_ten,kimono,kiss,kissing,kissing_cat,kissing_closed_eyes,kissing_face,kissing_heart,' +
'kissing_smiling_eyes,koala,koko,kr,large_blue_circle,large_blue_diamond,large_orange_diamond,last_quarter_moon,' +
'last_quarter_moon_with_face,laughing,leaves,ledger,left_luggage,left_right_arrow,leftwards_arrow_with_hook,' +
'lemon,leo,leopard,libra,light_rail,link,lips,lipstick,lock,lock_with_ink_pen,lollipop,loop,loudspeaker,' +
'love_hotel,love_letter,low_brightness,m,mag,mag_right,mahjong,mailbox,mailbox_closed,mailbox_with_mail,' +
'mailbox_with_no_mail,man,man_with_gua_pi_mao,man_with_turban,mans_shoe,maple_leaf,mask,massage,meat_on_bone,' +
'mega,melon,memo,mens,metal,metro,microphone,microscope,milky_way,minibus,minidisc,mobile_phone_off,' +
'money_with_wings,moneybag,monkey,monkey_face,monorail,mortar_board,mount_fuji,mountain_bicyclist,' +
'mountain_cableway,mountain_railway,mouse,mouse2,movie_camera,moyai,muscle,mushroom,musical_keyboard,' +
'musical_note,musical_score,mute,nail_care,name_badge,neckbeard,necktie,negative_squared_cross_mark,' +
'neutral_face,new,new_moon,new_moon_with_face,newspaper,ng,nine,no_bell,no_bicycles,no_entry,no_entry_sign,' +
'no_good,no_mobile_phones,no_mouth,no_pedestrians,no_smoking,non-potable_water,nose,notebook,' +
'notebook_with_decorative_cover,notes,nut_and_bolt,o,o2,ocean,octocat,octopus,oden,office,ok,ok_hand,' +
'ok_woman,older_man,older_woman,on,oncoming_automobile,oncoming_bus,oncoming_police_car,oncoming_taxi,one,' +
'open_file_folder,open_hands,open_mouth,ophiuchus,orange_book,outbox_tray,ox,package,page_facing_up,' +
'page_with_curl,pager,palm_tree,panda_face,paperclip,parking,part_alternation_mark,partly_sunny,' +
'passport_control,paw_prints,peach,pear,pencil,pencil2,penguin,pensive,performing_arts,persevere,' +
'person_frowning,person_with_blond_hair,person_with_pouting_face,phone,pig,pig2,pig_nose,pill,pineapple,pisces,' +
'pizza,plus1,point_down,point_left,point_right,point_up,point_up_2,police_car,poodle,poop,post_office,' +
'postal_horn,postbox,potable_water,pouch,poultry_leg,pound,pouting_cat,pray,princess,punch,purple_heart,purse,' +
'pushpin,put_litter_in_its_place,question,rabbit,rabbit2,racehorse,radio,radio_button,rage,rage1,rage2,rage3,' +
'rage4,railway_car,rainbow,raised_hand,raised_hands,raising_hand,ram,ramen,rat,recycle,red_car,red_circle,' +
'registered,relaxed,relieved,repeat,repeat_one,restroom,revolving_hearts,rewind,ribbon,rice,rice_ball,' +
'rice_cracker,rice_scene,ring,rocket,roller_coaster,rooster,rose,rotating_light,round_pushpin,rowboat,ru,' +
'rugby_football,runner,running,running_shirt_with_sash,sa,sagittarius,sailboat,sake,sandal,santa,satellite,' +
'satisfied,saxophone,school,school_satchel,scissors,scorpius,scream,scream_cat,scroll,seat,secret,see_no_evil,' +
'seedling,seven,shaved_ice,sheep,shell,ship,shipit,shirt,shit,shoe,shower,signal_strength,six,six_pointed_star,' +
'ski,skull,sleeping,sleepy,slot_machine,small_blue_diamond,small_orange_diamond,small_red_triangle,' +
'small_red_triangle_down,smile,smile_cat,smiley,smiley_cat,smiling_imp,smirk,smirk_cat,smoking,snail,snake,' +
'snowboarder,snowflake,snowman,sob,soccer,soon,sos,sound,space_invader,spades,spaghetti,sparkle,sparkler,' +
'sparkles,sparkling_heart,speak_no_evil,speaker,speech_balloon,speedboat,squirrel,star,star2,stars,station,' +
'statue_of_liberty,steam_locomotive,stew,straight_ruler,strawberry,stuck_out_tongue,stuck_out_tongue_closed_eyes,' +
'stuck_out_tongue_winking_eye,sun_with_face,sunflower,sunglasses,sunny,sunrise,sunrise_over_mountains,surfer,' +
'sushi,suspect,suspension_railway,sweat,sweat_drops,sweat_smile,sweet_potato,swimmer,symbols,syringe,tada,' +
'tanabata_tree,tangerine,taurus,taxi,tea,telephone,telephone_receiver,telescope,tennis,tent,thought_balloon,' +
'three,thumbsdown,thumbsup,ticket,tiger,tiger2,tired_face,tm,toilet,tokyo_tower,tomato,tongue,top,tophat,' +
'tractor,traffic_light,train,train2,tram,triangular_flag_on_post,triangular_ruler,trident,triumph,trolleybus,' +
'trollface,trophy,tropical_drink,tropical_fish,truck,trumpet,tshirt,tulip,turtle,tv,twisted_rightwards_arrows,' +
'two,two_hearts,two_men_holding_hands,two_women_holding_hands,u5272,u5408,u55b6,u6307,u6708,u6709,u6e80,u7121,' +
'u7533,u7981,u7a7a,uk,umbrella,unamused,underage,unlock,up,us,v,vertical_traffic_light,vhs,vibration_mode,' +
'video_camera,video_game,violin,virgo,volcano,vs,walking,waning_crescent_moon,waning_gibbous_moon,warning,watch,' +
'water_buffalo,watermelon,wave,wavy_dash,waxing_crescent_moon,waxing_gibbous_moon,wc,weary,wedding,whale,whale2,' +
'wheelchair,white_check_mark,white_circle,white_flower,white_large_square,white_medium_small_square,' +
'white_medium_square,white_small_square,white_square_button,wind_chime,wine_glass,wink,wolf,woman,' +
'womans_clothes,womans_hat,womens,worried,wrench,x,yellow_heart,yen,yum,zap,zero,zzz').split(',');
// use a map to help make lookups faster instead of having to use indexOf on an array
const out = new Map();
for (let i = 0; i < emoticonNames.length; i++) {
out[emoticonNames[i]] = true;
}
return out;
}
const emoticonMap = initializeEmoticonMap();
export function handleEmoticons(text, tokens) {
let output = text;
function replaceEmoticonWithToken(match, name) {
if (emoticonMap[name]) {
const index = tokens.size;
const alias = `MM_EMOTICON${index}`;
tokens.set(alias, {
value: `<img align="absmiddle" alt=${match} class="emoji" src=${getImagePathForEmoticon(name)} title=${match} />`,
originalText: match
});
return alias;
}
return match;
}
output = output.replace(/:([a-zA-Z0-9_-]+):/g, replaceEmoticonWithToken);
$.each(emoticonPatterns, (name, pattern) => {
// this might look a bit funny, but since the name isn't contained in the actual match
// like with the named emoticons, we need to add it in manually
output = output.replace(pattern, (match) => replaceEmoticonWithToken(match, name));
});
return output;
}
function getImagePathForEmoticon(name) {
return `/static/images/emoji/${name}.png`;
}

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

@@ -1,9 +1,25 @@
// 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.
const TextFormatting = require('./text_formatting.jsx');
const marked = require('marked'); const marked = require('marked');
export class MattermostMarkdownRenderer extends marked.Renderer { export class MattermostMarkdownRenderer extends marked.Renderer {
constructor(options, formattingOptions = {}) {
super(options);
this.heading = this.heading.bind(this);
this.text = this.text.bind(this);
this.formattingOptions = formattingOptions;
}
heading(text, level, raw) {
const id = `${this.options.headerPrefix}${raw.toLowerCase().replace(/[^\w]+/g, '-')}`;
return `<h${level} id="${id}" class="markdown__heading">${text}</h${level}>`;
}
link(href, title, text) { link(href, title, text) {
let outHref = href; let outHref = href;
@@ -11,7 +27,7 @@ export class MattermostMarkdownRenderer extends marked.Renderer {
outHref = `http://${outHref}`; outHref = `http://${outHref}`;
} }
let output = '<a class="theme" href="' + outHref + '"'; let output = '<a class="theme markdown__link" href="' + outHref + '"';
if (title) { if (title) {
output += ' title="' + title + '"'; output += ' title="' + title + '"';
} }
@@ -19,4 +35,12 @@ export class MattermostMarkdownRenderer extends marked.Renderer {
return output; return output;
} }
table(header, body) {
return `<table class="markdown__table"><thead>${header}</thead><tbody>${body}</tbody></table>`;
}
text(text) {
return TextFormatting.doFormatText(text, this.formattingOptions);
}
} }

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

@@ -3,41 +3,58 @@
const Autolinker = require('autolinker'); const Autolinker = require('autolinker');
const Constants = require('./constants.jsx'); const Constants = require('./constants.jsx');
const Emoticons = require('./emoticons.jsx');
const Markdown = require('./markdown.jsx'); const Markdown = require('./markdown.jsx');
const UserStore = require('../stores/user_store.jsx'); const UserStore = require('../stores/user_store.jsx');
const Utils = require('./utils.jsx'); const Utils = require('./utils.jsx');
const marked = require('marked'); const marked = require('marked');
const markdownRenderer = new Markdown.MattermostMarkdownRenderer();
// Performs formatting of user posts including highlighting mentions and search terms and converting urls, hashtags, and // Performs formatting of user posts including highlighting mentions and search terms and converting urls, hashtags, and
// @mentions to links by taking a user's message and returning a string of formatted html. Also takes a number of options // @mentions to links by taking a user's message and returning a string of formatted html. Also takes a number of options
// as part of the second parameter: // as part of the second parameter:
// - searchTerm - If specified, this word is highlighted in the resulting html. Defaults to nothing. // - searchTerm - If specified, this word is highlighted in the resulting html. Defaults to nothing.
// - mentionHighlight - Specifies whether or not to highlight mentions of the current user. Defaults to true. // - mentionHighlight - Specifies whether or not to highlight mentions of the current user. Defaults to true.
// - singleline - Specifies whether or not to remove newlines. Defaults to false. // - singleline - Specifies whether or not to remove newlines. Defaults to false.
// - emoticons - Enables emoticon parsing. Defaults to true.
// - markdown - Enables markdown parsing. Defaults to true. // - markdown - Enables markdown parsing. Defaults to true.
export function formatText(text, options = {}) { export function formatText(text, options = {}) {
if (!('markdown' in options)) { let output;
options.markdown = true;
if (!('markdown' in options) || options.markdown) {
// the markdown renderer will call doFormatText as necessary so just call marked
output = marked(text, {
renderer: new Markdown.MattermostMarkdownRenderer(null, options),
sanitize: true
});
} else {
output = sanitizeHtml(text);
output = doFormatText(output, options);
} }
// wait until marked can sanitize the html so that we don't break markdown block quotes // replace newlines with spaces if necessary
let output; if (options.singleline) {
if (!options.markdown) { output = replaceNewlines(output);
output = sanitizeHtml(text);
} else {
output = text;
} }
return output;
}
// Performs most of the actual formatting work for formatText. Not intended to be called normally.
export function doFormatText(text, options) {
let output = text;
const tokens = new Map(); const tokens = new Map();
// replace important words and phrases with tokens // replace important words and phrases with tokens
output = autolinkUrls(output, tokens, !!options.markdown); output = autolinkUrls(output, tokens);
output = autolinkAtMentions(output, tokens); output = autolinkAtMentions(output, tokens);
output = autolinkHashtags(output, tokens); output = autolinkHashtags(output, tokens);
if (!('emoticons' in options) || options.emoticon) {
output = Emoticons.handleEmoticons(output, tokens);
}
if (options.searchTerm) { if (options.searchTerm) {
output = highlightSearchTerm(output, tokens, options.searchTerm); output = highlightSearchTerm(output, tokens, options.searchTerm);
} }
@@ -46,22 +63,9 @@ export function formatText(text, options = {}) {
output = highlightCurrentMentions(output, tokens); output = highlightCurrentMentions(output, tokens);
} }
// perform markdown parsing while we have an html-free input string
if (options.markdown) {
output = marked(output, {
renderer: markdownRenderer,
sanitize: true
});
}
// reinsert tokens with formatted versions of the important words and phrases // reinsert tokens with formatted versions of the important words and phrases
output = replaceTokens(output, tokens); output = replaceTokens(output, tokens);
// replace newlines with html line breaks
if (options.singleline) {
output = replaceNewlines(output);
}
return output; return output;
} }
@@ -78,7 +82,7 @@ export function sanitizeHtml(text) {
return output; return output;
} }
function autolinkUrls(text, tokens, markdown) { function autolinkUrls(text, tokens) {
function replaceUrlWithToken(autolinker, match) { function replaceUrlWithToken(autolinker, match) {
const linkText = match.getMatchedText(); const linkText = match.getMatchedText();
let url = linkText; let url = linkText;
@@ -108,30 +112,7 @@ function autolinkUrls(text, tokens, markdown) {
replaceFn: replaceUrlWithToken replaceFn: replaceUrlWithToken
}); });
let output = text; return autolinker.link(text);
// temporarily replace markdown links if markdown is enabled so that we don't accidentally parse them twice
const markdownLinkTokens = new Map();
if (markdown) {
function replaceMarkdownLinkWithToken(markdownLink) {
const index = markdownLinkTokens.size;
const alias = `MM_MARKDOWNLINK${index}`;
markdownLinkTokens.set(alias, {value: markdownLink});
return alias;
}
output = output.replace(/\]\([^\)]*\)/g, replaceMarkdownLinkWithToken);
}
output = autolinker.link(output);
if (markdown) {
output = replaceTokens(output, markdownLinkTokens);
}
return output;
} }
function autolinkAtMentions(text, tokens) { function autolinkAtMentions(text, tokens) {
@@ -241,7 +222,7 @@ function autolinkHashtags(text, tokens) {
return prefix + alias; return prefix + alias;
} }
return output.replace(/(^|\W)(#[a-zA-Z0-9.\-_]+)\b/g, replaceHashtagWithToken); return output.replace(/(^|\W)(#[a-zA-Z][a-zA-Z0-9.\-_]*)\b/g, replaceHashtagWithToken);
} }
function highlightSearchTerm(text, tokens, searchTerm) { function highlightSearchTerm(text, tokens, searchTerm) {

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

@@ -542,7 +542,119 @@ export function toTitleCase(str) {
return str.replace(/\w\S*/g, doTitleCase); return str.replace(/\w\S*/g, doTitleCase);
} }
export function changeCss(className, classValue) { export function applyTheme(theme) {
if (theme.sidebarBg) {
changeCss('.sidebar--left', 'background:' + theme.sidebarBg, 1);
changeCss('@media(max-width: 768px){.search-bar__container', 'background:' + theme.sidebarBg, 1);
}
if (theme.sidebarText) {
changeCss('.sidebar--left .nav li>a, .sidebar--right', 'color:' + theme.sidebarText, 1);
changeCss('.sidebar--left .nav li>h4, .sidebar--left .add-channel-btn', 'color:' + changeOpacity(theme.sidebarText, 0.8), 1);
changeCss('.sidebar--left .add-channel-btn:hover, .sidebar--left .add-channel-btn:focus', 'color:' + theme.sidebarText, 1);
changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 1);
changeCss('.sidebar--right .sidebar-right__body', 'border-color:' + changeOpacity(theme.sidebarText, 0.2), 2);
changeCss('.sidebar--left .status path', 'fill:' + changeOpacity(theme.sidebarText, 0.5), 1);
}
if (theme.sidebarUnreadText) {
changeCss('.sidebar--left .nav li>a.unread-title', 'color:' + theme.sidebarUnreadText + '!important;', 1);
}
if (theme.sidebarTextHoverBg) {
changeCss('.sidebar--left .nav li>a:hover, .sidebar--left .nav li>a:focus', 'background:' + theme.sidebarTextHoverBg, 1);
}
if (theme.sidebarTextHoverColor) {
changeCss('.sidebar--left .nav li>a:hover, .sidebar--left .nav li>a:focus', 'color:' + theme.sidebarTextHoverColor, 2);
}
if (theme.sidebarTextActiveBg) {
changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'background:' + theme.sidebarTextActiveBg, 1);
}
if (theme.sidebarTextActiveColor) {
changeCss('.sidebar--left .nav li.active a, .sidebar--left .nav li.active a:hover, .sidebar--left .nav li.active a:focus', 'color:' + theme.sidebarTextActiveColor, 2);
}
if (theme.sidebarHeaderBg) {
changeCss('.sidebar--left .team__header, .sidebar--menu .team__header', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('.modal .modal-header', 'background:' + theme.sidebarHeaderBg, 1);
changeCss('#navbar .navbar-default', 'background:' + theme.sidebarHeaderBg, 1);
}
if (theme.sidebarHeaderTextColor) {
changeCss('.sidebar--left .team__header .header__info, .sidebar--menu .team__header .header__info', 'color:' + theme.sidebarHeaderTextColor, 1);
changeCss('.sidebar--left .team__header .user__name, .sidebar--menu .team__header .user__name', 'color:' + changeOpacity(theme.sidebarHeaderTextColor, 0.8), 1);
changeCss('.sidebar--left .team__header:hover .user__name, .sidebar--menu .team__header:hover .user__name', 'color:' + theme.sidebarHeaderTextColor, 1);
changeCss('.modal .modal-header .modal-title, .modal .modal-header .modal-title .name, .modal .modal-header button.close', 'color:' + theme.sidebarHeaderTextColor, 1);
changeCss('#navbar .navbar-default .navbar-brand .heading, ', 'color:' + theme.sidebarHeaderTextColor, 1);
changeCss('#navbar .navbar-default .navbar-toggle .icon-bar, ', 'background:' + theme.sidebarHeaderTextColor, 1);
}
if (theme.onlineIndicator) {
changeCss('.sidebar--left .status .online--icon', 'fill:' + theme.onlineIndicator, 1);
}
if (theme.mentionBj) {
changeCss('.sidebar--left .nav-pills__unread-indicator', 'background:' + theme.mentionBj, 1);
changeCss('.sidebar--left .badge', 'background:' + theme.mentionBj, 1);
}
if (theme.mentionColor) {
changeCss('.sidebar--left .nav-pills__unread-indicator', 'color:' + theme.mentionColor, 2);
changeCss('.sidebar--left .badge', 'color:' + theme.mentionColor, 2);
}
if (theme.centerChannelBg) {
changeCss('.app__content', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-create', 'background:' + theme.centerChannelBg, 1);
changeCss('.search-bar__container .search__form .search-bar', 'background:' + theme.centerChannelBg, 1);
changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1);
changeCss('.sidebar--right', 'background:' + theme.centerChannelBg, 1);
}
if (theme.centerChannelColor) {
changeCss('.app__content', 'color:' + theme.centerChannelColor, 2);
changeCss('#post-create', 'color:' + theme.centerChannelColor, 2);
changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1);
changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
changeCss('.channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
changeCss('.custom-textarea', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2) + '!important; color: ' + theme.centerChannelColor, 1);
changeCss('.search-bar__container .search__form .search-bar', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: ' + theme.centerChannelColor, 2);
changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
changeCss('.date-separator .separator__text, .new-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
changeCss('.date-separator .separator__hr, .new-separator .separator__hr, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.channel-intro', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('.post:hover, .sidebar--right .sidebar--right__header', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.date-separator.hovered--before:after, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.post.current--user:hover .post-body ', 'background: none;', 1);
changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2);
}
if (theme.linkColor) {
changeCss('a, a:focus, a:hover', 'color:' + theme.linkColor, 1);
changeCss('.post .comment-icon__container', 'fill:' + theme.linkColor, 1);
}
if (theme.buttonBg) {
changeCss('.btn.btn-primary', 'background:' + theme.buttonBg, 1);
changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background:' + changeColor(theme.buttonBg, -0.25), 1);
}
if (theme.buttonColor) {
changeCss('.btn.btn-primary', 'color:' + theme.buttonColor, 2);
}
}
export function changeCss(className, classValue, classRepeat) {
// we need invisible container to store additional css definitions // we need invisible container to store additional css definitions
var cssMainContainer = $('#css-modifier-container'); var cssMainContainer = $('#css-modifier-container');
if (cssMainContainer.length === 0) { if (cssMainContainer.length === 0) {
@@ -552,9 +664,9 @@ export function changeCss(className, classValue) {
} }
// and we need one div for each class // and we need one div for each class
var classContainer = cssMainContainer.find('div[data-class="' + className + '"]'); var classContainer = cssMainContainer.find('div[data-class="' + className + classRepeat + '"]');
if (classContainer.length === 0) { if (classContainer.length === 0) {
classContainer = $('<div data-class="' + className + '"></div>'); classContainer = $('<div data-class="' + className + classRepeat + '"></div>');
classContainer.appendTo(cssMainContainer); classContainer.appendTo(cssMainContainer);
} }
@@ -760,57 +872,47 @@ Image.prototype.load = function imageLoad(url, progressCallback) {
Image.prototype.completedPercentage = 0; Image.prototype.completedPercentage = 0;
export function changeColor(colourIn, amt) { export function changeColor(colourIn, amt) {
var usePound = false; var hex = colourIn;
var col = colourIn; var lum = amt;
if (col[0] === '#') { // validate hex string
col = col.slice(1); hex = String(hex).replace(/[^0-9a-f]/gi, '');
usePound = true; if (hex.length < 6) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
lum = lum || 0;
// convert to decimal and change luminosity
var rgb = '#';
var c;
var i;
for (i = 0; i < 3; i++) {
c = parseInt(hex.substr(i * 2, 2), 16);
c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
rgb += ('00' + c).substr(c.length);
} }
var num = parseInt(col, 16); return rgb;
var r = (num >> 16) + amt;
if (r > 255) {
r = 255;
} else if (r < 0) {
r = 0;
}
var b = ((num >> 8) & 0x00FF) + amt;
if (b > 255) {
b = 255;
} else if (b < 0) {
b = 0;
}
var g = (num & 0x0000FF) + amt;
if (g > 255) {
g = 255;
} else if (g < 0) {
g = 0;
}
var pound = '#';
if (!usePound) {
pound = '';
}
return pound + String('000000' + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
} }
export function changeOpacity(oldColor, opacity) { export function changeOpacity(oldColor, opacity) {
var col = oldColor; var color = oldColor;
if (col[0] === '#') { if (color[0] === '#') {
col = col.slice(1); color = color.slice(1);
} }
var r = parseInt(col.substring(0, 2), 16); if (color.length === 3) {
var g = parseInt(col.substring(2, 4), 16); const tempColor = color;
var b = parseInt(col.substring(4, 6), 16); color = '';
color += tempColor[0] + tempColor[0];
color += tempColor[1] + tempColor[1];
color += tempColor[2] + tempColor[2];
}
var r = parseInt(color.substring(0, 2), 16);
var g = parseInt(color.substring(2, 4), 16);
var b = parseInt(color.substring(4, 6), 16);
return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')'; return 'rgba(' + r + ',' + g + ',' + b + ',' + opacity + ')';
} }
@@ -955,7 +1057,8 @@ export function getTeamURLFromAddressBar() {
export function getShortenedTeamURL() { export function getShortenedTeamURL() {
const teamURL = getTeamURLFromAddressBar(); const teamURL = getTeamURLFromAddressBar();
if (teamURL.length > 24) { if (teamURL.length > 35) {
return teamURL.substring(0, 10) + '...' + teamURL.substring(teamURL.length - 12, teamURL.length) + '/'; return teamURL.substring(0, 10) + '...' + teamURL.substring(teamURL.length - 12, teamURL.length) + '/';
} }
return teamURL + '/';
} }

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

@@ -40,14 +40,11 @@ b, strong {
a { a {
word-break: break-word; word-break: break-word;
}
a.theme {
color: $primary-color; color: $primary-color;
} }
div.theme { a:focus, a:hover {
background-color: $primary-color; color: $primary-color;
} }
.tooltip { .tooltip {

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

@@ -0,0 +1,251 @@
/*!
* Bootstrap Colorpicker
* http://mjolnic.github.io/bootstrap-colorpicker/
*
* Originally written by (c) 2012 Stefan Petre
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
*/
.colorpicker-saturation {
float: left;
width: 100px;
height: 100px;
cursor: crosshair;
background-image: url("../images/bootstrap-colorpicker/saturation.png");
}
.colorpicker-saturation i {
position: absolute;
top: 0;
left: 0;
display: block;
width: 5px;
height: 5px;
margin: -4px 0 0 -4px;
border: 1px solid #000;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.colorpicker-saturation i b {
display: block;
width: 5px;
height: 5px;
border: 1px solid #fff;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.colorpicker-hue,
.colorpicker-alpha {
float: left;
width: 15px;
height: 100px;
margin-bottom: 4px;
margin-left: 4px;
cursor: row-resize;
}
.colorpicker-hue i,
.colorpicker-alpha i {
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: 1px;
margin-top: -1px;
background: #000;
border-top: 1px solid #fff;
}
.colorpicker-hue {
background-image: url("../images/bootstrap-colorpicker/hue.png");
}
.colorpicker-alpha {
display: none;
background-image: url("../images/bootstrap-colorpicker/alpha.png");
}
.colorpicker-saturation,
.colorpicker-hue,
.colorpicker-alpha {
background-size: contain;
}
.colorpicker {
top: 0;
left: 0;
z-index: 2500;
min-width: 130px;
padding: 4px;
margin-top: 1px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
*zoom: 1;
}
.colorpicker:before,
.colorpicker:after {
display: table;
line-height: 0;
content: "";
}
.colorpicker:after {
clear: both;
}
.colorpicker:before {
position: absolute;
top: -7px;
left: 6px;
display: inline-block;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-left: 7px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.2);
content: '';
}
.colorpicker:after {
position: absolute;
top: -6px;
left: 7px;
display: inline-block;
border-right: 6px solid transparent;
border-bottom: 6px solid #ffffff;
border-left: 6px solid transparent;
content: '';
}
.colorpicker div {
position: relative;
}
.colorpicker.colorpicker-with-alpha {
min-width: 140px;
}
.colorpicker.colorpicker-with-alpha .colorpicker-alpha {
display: block;
}
.colorpicker-color {
height: 10px;
margin-top: 5px;
clear: both;
background-image: url("../images/bootstrap-colorpicker/alpha.png");
background-position: 0 100%;
}
.colorpicker-color div {
height: 10px;
}
.colorpicker-selectors {
display: none;
height: 10px;
margin-top: 5px;
clear: both;
}
.colorpicker-selectors i {
float: left;
width: 10px;
height: 10px;
cursor: pointer;
}
.colorpicker-selectors i + i {
margin-left: 3px;
}
.colorpicker-element .input-group-addon i,
.colorpicker-element .add-on i {
display: inline-block;
width: 16px;
height: 16px;
vertical-align: text-top;
cursor: pointer;
}
.colorpicker.colorpicker-inline {
position: relative;
z-index: auto;
display: inline-block;
float: none;
}
.colorpicker.colorpicker-horizontal {
width: 110px;
height: auto;
min-width: 110px;
}
.colorpicker.colorpicker-horizontal .colorpicker-saturation {
margin-bottom: 4px;
}
.colorpicker.colorpicker-horizontal .colorpicker-color {
width: 100px;
}
.colorpicker.colorpicker-horizontal .colorpicker-hue,
.colorpicker.colorpicker-horizontal .colorpicker-alpha {
float: left;
width: 100px;
height: 15px;
margin-bottom: 4px;
margin-left: 0;
cursor: col-resize;
}
.colorpicker.colorpicker-horizontal .colorpicker-hue i,
.colorpicker.colorpicker-horizontal .colorpicker-alpha i {
position: absolute;
top: 0;
left: 0;
display: block;
width: 1px;
height: 15px;
margin-top: 0;
background: #ffffff;
border: none;
}
.colorpicker.colorpicker-horizontal .colorpicker-hue {
background-image: url("../images/bootstrap-colorpicker/hue-horizontal.png");
}
.colorpicker.colorpicker-horizontal .colorpicker-alpha {
background-image: url("../images/bootstrap-colorpicker/alpha-horizontal.png");
}
.colorpicker.colorpicker-hidden {
display: none;
}
.colorpicker.colorpicker-visible {
display: block;
}
.colorpicker-inline.colorpicker-visible {
display: inline-block;
}
.colorpicker-right:before {
right: 6px;
left: auto;
}
.colorpicker-right:after {
right: 7px;
left: auto;
}

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

@@ -18,6 +18,7 @@
.input__help { .input__help {
color: #777; color: #777;
margin: 10px 0 0 10px; margin: 10px 0 0 10px;
word-break: break-word;
&.dark { &.dark {
color: #222; color: #222;
} }

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

@@ -231,6 +231,10 @@
width: 45px; width: 45px;
color: #999; color: #999;
cursor: pointer; cursor: pointer;
.fa {
margin-left: 3px;
font-size: 16px;
}
} }
&.alt { &.alt {
margin: 0; margin: 0;

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

@@ -8,6 +8,16 @@
@include opacity(0.7); @include opacity(0.7);
} }
} }
a, a:focus, a:hover {
color: #2389D7;
}
.btn.btn-primary {
background: #4285f4;
&:hover, &:focus, &:active {
background: $primary-color--hover;
color: #fff;
}
}
.info__label { .info__label {
font-weight: 600; font-weight: 600;
text-align: right; text-align: right;

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

@@ -275,7 +275,7 @@ body.ios {
&.current--user { &.current--user {
.post-body { .post-body {
@include border-radius(4px); @include border-radius(4px);
background: #f5f5f5; background: rgba(#000, 0.05);
} }
} }
&.post--comment { &.post--comment {

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

@@ -107,4 +107,5 @@
.search-highlight.theme, .search-highlight { .search-highlight.theme, .search-highlight {
background-color: #FFF2BB; background-color: #FFF2BB;
color: #333;
} }

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

@@ -79,6 +79,36 @@
} }
} }
.appearance-section {
.premade-themes {
.theme-label {
font-weight: 400;
margin-top: 5px;
}
img {
border: 3px solid transparent;
}
.active {
img {
border-color: $primary-color;
}
}
}
.custom-label {
font-weight: normal;
font-size: 13px;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 0;
}
.radio {
label {
font-weight: 600;
}
}
}
.section-title { .section-title {
margin-bottom: 5px; margin-bottom: 5px;
font-weight: 600; font-weight: 600;

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

@@ -16,6 +16,12 @@
overflow-y: auto; overflow-y: auto;
max-width: 200px; max-width: 200px;
width: 200px; width: 200px;
a {
color: #262626 !important;
&:hover, &:focus {
background: #f5f5f5 !important;
}
}
} }
.search__form { .search__form {
margin: 0; margin: 0;

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

@@ -51,9 +51,10 @@
margin: 11px 0 0 0; margin: 11px 0 0 0;
width: 22px; width: 22px;
height: 22px; height: 22px;
background: url("../images/closeSidebar.png");
@include background-size(100% 100%);
opacity: 0.5; opacity: 0.5;
font-size: 22px;
line-height: 0;
background: none;
float: right; float: right;
outline: none; outline: none;
border: none; border: none;
@@ -61,11 +62,15 @@
&:hover, &:active { &:hover, &:active {
opacity: 0.8; opacity: 0.8;
} }
i {
position: relative;
top: -2px;
}
} }
.sidebar--right__header { .sidebar--right__header {
font-size: 1em; font-size: 1em;
text-transform: uppercase; text-transform: uppercase;
color: #444; color: inherit;
height: 44px; height: 44px;
padding: 0 1em; padding: 0 1em;
line-height: 44px; line-height: 44px;

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

@@ -10,6 +10,7 @@
@import "partials/perfect-scrollbar"; @import "partials/perfect-scrollbar";
@import "partials/font-awesome"; @import "partials/font-awesome";
@import "partials/base"; @import "partials/base";
@import "partials/colorpicker";
// Channel Css // Channel Css
@import "partials/headers"; @import "partials/headers";

9
web/static/css/bootstrap-colorpicker.min.css поставляемый Исполняемый файл
Просмотреть файл

@@ -0,0 +1,9 @@
/*!
* Bootstrap Colorpicker
* http://mjolnic.github.io/bootstrap-colorpicker/
*
* Originally written by (c) 2012 Stefan Petre
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
*/.colorpicker-saturation{float:left;width:100px;height:100px;cursor:crosshair;background-image:url("../images/bootstrap-colorpicker/saturation.png")}.colorpicker-saturation i{position:absolute;top:0;left:0;display:block;width:5px;height:5px;margin:-4px 0 0 -4px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-saturation i b{display:block;width:5px;height:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-hue,.colorpicker-alpha{float:left;width:15px;height:100px;margin-bottom:4px;margin-left:4px;cursor:row-resize}.colorpicker-hue i,.colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:100%;height:1px;margin-top:-1px;background:#000;border-top:1px solid #fff}.colorpicker-hue{background-image:url("../images/bootstrap-colorpicker/hue.png")}.colorpicker-alpha{display:none;background-image:url("../images/bootstrap-colorpicker/alpha.png")}.colorpicker-saturation,.colorpicker-hue,.colorpicker-alpha{background-size:contain}.colorpicker{top:0;left:0;z-index:2500;min-width:130px;padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1}.colorpicker:before,.colorpicker:after{display:table;line-height:0;content:""}.colorpicker:after{clear:both}.colorpicker:before{position:absolute;top:-7px;left:6px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.colorpicker:after{position:absolute;top:-6px;left:7px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url("../images/bootstrap-colorpicker/alpha.png");background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-selectors{display:none;height:10px;margin-top:5px;clear:both}.colorpicker-selectors i{float:left;width:10px;height:10px;cursor:pointer}.colorpicker-selectors i+i{margin-left:3px}.colorpicker-element .input-group-addon i,.colorpicker-element .add-on i{display:inline-block;width:16px;height:16px;vertical-align:text-top;cursor:pointer}.colorpicker.colorpicker-inline{position:relative;z-index:auto;display:inline-block;float:none}.colorpicker.colorpicker-horizontal{width:110px;height:auto;min-width:110px}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-hue,.colorpicker.colorpicker-horizontal .colorpicker-alpha{float:left;width:100px;height:15px;margin-bottom:4px;margin-left:0;cursor:col-resize}.colorpicker.colorpicker-horizontal .colorpicker-hue i,.colorpicker.colorpicker-horizontal .colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:1px;height:15px;margin-top:0;background:#fff;border:0}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url("../images/bootstrap-colorpicker/hue-horizontal.png")}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url("../images/bootstrap-colorpicker/alpha-horizontal.png")}.colorpicker.colorpicker-hidden{display:none}.colorpicker.colorpicker-visible{display:block}.colorpicker-inline.colorpicker-visible{display:inline-block}.colorpicker-right:before{right:6px;left:auto}.colorpicker-right:after{right:7px;left:auto}

Двоичные данные
web/static/images/bootstrap-colorpicker/alpha-horizontal.png Исполняемый файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.5 KiB

Двоичные данные
web/static/images/bootstrap-colorpicker/alpha.png Исполняемый файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.2 KiB

Двоичные данные
web/static/images/bootstrap-colorpicker/hue-horizontal.png Исполняемый файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.8 KiB

Двоичные данные
web/static/images/bootstrap-colorpicker/hue.png Исполняемый файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.9 KiB

Двоичные данные
web/static/images/bootstrap-colorpicker/saturation.png Исполняемый файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 8.6 KiB

Двоичные данные
web/static/images/themes/dark.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 74 KiB

Двоичные данные
web/static/images/themes/mattermost.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 65 KiB

Двоичные данные
web/static/images/themes/slack.png Обычный файл

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 67 KiB

1
web/static/js/bootstrap-colorpicker.min.js поставляемый Исполняемый файл

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

4
web/static/js/emojify.min.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

@@ -29,6 +29,7 @@
<div id="edit_mention_tab"></div> <div id="edit_mention_tab"></div>
<div id="get_link_modal"></div> <div id="get_link_modal"></div>
<div id="user_settings_modal"></div> <div id="user_settings_modal"></div>
<div id="import_theme_modal"></div>
<div id="team_settings_modal"></div> <div id="team_settings_modal"></div>
<div id="invite_member_modal"></div> <div id="invite_member_modal"></div>
<div id="edit_channel_modal"></div> <div id="edit_channel_modal"></div>

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

@@ -25,10 +25,12 @@
<link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css"> <link rel="stylesheet" href="/static/css/bootstrap-3.3.5.min.css">
<link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/bootstrap-colorpicker.min.css" rel="stylesheet">
<script src="/static/js/react-with-addons-0.13.3.js"></script> <script src="/static/js/react-with-addons-0.13.3.js"></script>
<script src="/static/js/jquery-1.11.1.js"></script> <script src="/static/js/jquery-1.11.1.js"></script>
<script src="/static/js/bootstrap-3.3.5.js"></script> <script src="/static/js/bootstrap-3.3.5.js"></script>
<script src="/static/js/bootstrap-colorpicker.min.js"></script>
<script src="/static/js/react-bootstrap-0.25.1.js"></script> <script src="/static/js/react-bootstrap-0.25.1.js"></script>
<link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon"> <link id="favicon" rel="icon" href="/static/images/favicon.ico" type="image/x-icon">
@@ -40,11 +42,6 @@
<script src="/static/js/jquery-dragster/jquery.dragster.js"></script> <script src="/static/js/jquery-dragster/jquery.dragster.js"></script>
<script src="/static/js/emojify.min.js"></script>
<script>
emojify.setConfig({img_dir: '/static/images/emoji'});
</script>
<style id="antiClickjack">body{display:none !important;}</style> <style id="antiClickjack">body{display:none !important;}</style>
<script src="/static/js/bundle.js"></script> <script src="/static/js/bundle.js"></script>
<script type="text/javascript"> <script type="text/javascript">

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

@@ -355,6 +355,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) {
func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
resend := r.URL.Query().Get("resend") resend := r.URL.Query().Get("resend")
resendSuccess := r.URL.Query().Get("resend_success")
name := r.URL.Query().Get("teamname") name := r.URL.Query().Get("teamname")
email := r.URL.Query().Get("email") email := r.URL.Query().Get("email")
hashedId := r.URL.Query().Get("hid") hashedId := r.URL.Query().Get("hid")
@@ -375,7 +376,9 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
} else { } else {
user := result.Data.(*model.User) user := result.Data.(*model.User)
api.FireAndForgetVerifyEmail(user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team)) api.FireAndForgetVerifyEmail(user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team))
http.Redirect(w, r, "/", http.StatusFound)
newAddress := strings.Replace(r.URL.String(), "&resend=true", "&resend_success=true", -1)
http.Redirect(w, r, newAddress, http.StatusFound)
return return
} }
} }
@@ -400,6 +403,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) {
page.Props["IsVerified"] = isVerified page.Props["IsVerified"] = isVerified
page.Props["TeamURL"] = c.GetTeamURLFromTeam(team) page.Props["TeamURL"] = c.GetTeamURLFromTeam(team)
page.Props["UserEmail"] = email page.Props["UserEmail"] = email
page.Props["ResendSuccess"] = resendSuccess
page.Render(c, w) page.Render(c, w)
} }