From dda30e407aca321f369f27078076d3e05fd28b19 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Mon, 21 Sep 2015 16:03:18 -0700 Subject: Added confirmation when users request another verification email; users are no longer redirected after requesting another verification email --- web/react/components/email_verify.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'web/react') diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 92123956f..4d4d489bb 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -10,12 +10,14 @@ export default class EmailVerify extends React.Component { this.state = {}; } handleResend() { - window.location.href = window.location.href + '&resend=true'; + const newAddress = window.location.href.replace('?resend_success=true', '').replace('&resend_success=true', ''); + window.location.href = newAddress + '&resend=true'; } render() { var title = ''; var body = ''; var resend = ''; + let resendConfirm = ''; if (this.props.isVerified === 'true') { title = global.window.config.SiteName + ' Email Verified'; body =

Your email has been verified! Click -1) { + resendConfirm =


{' Verification email sent.'}

; + } } return ( @@ -41,6 +46,7 @@ export default class EmailVerify extends React.Component {
{body} {resend} + {resendConfirm}
-- cgit v1.2.3-1-g7c22 From b4be8eb554d4de1de5b4928a37cb72c34d565487 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Mon, 21 Sep 2015 17:03:08 -0700 Subject: Minor changes to how resend status is passed --- web/react/components/email_verify.jsx | 12 +++++++----- web/react/pages/verify.jsx | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'web/react') diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 4d4d489bb..8d3f15525 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -10,14 +10,14 @@ export default class EmailVerify extends React.Component { this.state = {}; } handleResend() { - const newAddress = window.location.href.replace('?resend_success=true', '').replace('&resend_success=true', ''); + const newAddress = window.location.href.replace('&resend_success=true', ''); window.location.href = newAddress + '&resend=true'; } render() { var title = ''; var body = ''; var resend = ''; - let resendConfirm = ''; + var resendConfirm = ''; if (this.props.isVerified === 'true') { title = global.window.config.SiteName + ' Email Verified'; body =

Your email has been verified! Click -1) { + if (this.props.resendSuccess) { resendConfirm =


{' Verification email sent.'}

; } } @@ -57,10 +57,12 @@ export default class EmailVerify extends React.Component { EmailVerify.defaultProps = { isVerified: 'false', teamURL: '', - userEmail: '' + userEmail: '', + resendSuccess: 'false' }; EmailVerify.propTypes = { isVerified: React.PropTypes.string, teamURL: React.PropTypes.string, - userEmail: React.PropTypes.string + userEmail: React.PropTypes.string, + resendSuccess: React.PropTypes.string }; diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx index e48471bbd..16a9846e5 100644 --- a/web/react/pages/verify.jsx +++ b/web/react/pages/verify.jsx @@ -9,6 +9,7 @@ global.window.setupVerifyPage = function setupVerifyPage(props) { isVerified={props.IsVerified} teamURL={props.TeamURL} userEmail={props.UserEmail} + resendSuccess={props.ResendSuccess} />, document.getElementById('verify') ); -- cgit v1.2.3-1-g7c22 From 38cc1485749c60c32ee9ef8a01da74140588cc3a Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Tue, 22 Sep 2015 11:21:28 +0500 Subject: UI Modifications for multiple tickets --- web/react/components/channel_loader.jsx | 11 ++++++----- web/react/components/new_channel_modal.jsx | 3 ++- web/react/components/sidebar_right_menu.jsx | 4 ++++ web/react/utils/utils.jsx | 3 ++- 4 files changed, 14 insertions(+), 7 deletions(-) (limited to 'web/react') diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index ce6f60f87..20ea34e15 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -90,11 +90,12 @@ export default class ChannelLoader extends React.Component { } /* Setup global mouse events */ - $('body').on('click.userpopover', function popOver(e) { - if ($(e.target).attr('data-toggle') !== 'popover' && - $(e.target).parents('.popover.in').length === 0) { - $('.user-popover').popover('hide'); - } + $('body').on('click', function hidePopover(e) { + $('[data-toggle="popover"]').each(function eachPopover() { + if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) { + $(this).popover('hide'); + } + }); }); $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) { diff --git a/web/react/components/new_channel_modal.jsx b/web/react/components/new_channel_modal.jsx index 1488a1431..99d5b49e9 100644 --- a/web/react/components/new_channel_modal.jsx +++ b/web/react/components/new_channel_modal.jsx @@ -93,6 +93,7 @@ export default class NewChannelModal extends React.Component { @@ -122,7 +123,7 @@ export default class NewChannelModal extends React.Component { /> {displayNameError}

- {'Channel URL: ' + prettyTeamURL + this.props.channelData.name + ' ('} + {'URL: ' + prettyTeamURL + this.props.channelData.name + ' ('} 24) { + if (teamURL.length > 35) { return teamURL.substring(0, 10) + '...' + teamURL.substring(teamURL.length - 12, teamURL.length) + '/'; } + return teamURL + '/'; } -- cgit v1.2.3-1-g7c22 From 302372cfa0c1f86f78336a5b77e879eac32d27cb Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Tue, 22 Sep 2015 21:01:55 +0500 Subject: Removing click event for channel description popover --- web/react/components/channel_header.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web/react') diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 8d23ec646..b81936b57 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -55,7 +55,7 @@ export default class ChannelHeader extends React.Component { if (!Utils.areStatesEqual(newState, this.state)) { 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) { if (msg.action === 'new_user') { -- cgit v1.2.3-1-g7c22 From 09e8874b8ec4c56464b734056175a184196276ec Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 22 Sep 2015 13:27:10 -0400 Subject: Removed click handler from RHS [...] menu --- web/react/components/rhs_comment.jsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'web/react') diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx index fe31ac381..8d1054e86 100644 --- a/web/react/components/rhs_comment.jsx +++ b/web/react/components/rhs_comment.jsx @@ -114,14 +114,7 @@ export default class RhsComment extends React.Component { var ownerOptions; if (isOwner && post.state !== Constants.POST_FAILED && post.state !== Constants.POST_LOADING) { ownerOptions = ( -

+
Date: Tue, 22 Sep 2015 13:53:43 -0400 Subject: Restored changes which remove emojify and switch to our own version --- web/react/components/post_body.jsx | 1 - web/react/components/rhs_comment.jsx | 1 - web/react/components/rhs_root_post.jsx | 1 - web/react/utils/emoticons.jsx | 159 +++++++++++++++++++++++++++++++++ web/react/utils/text_formatting.jsx | 6 ++ 5 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 web/react/utils/emoticons.jsx (limited to 'web/react') diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index e0682e997..dbbcdc409 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -35,7 +35,6 @@ export default class PostBody extends React.Component { parseEmojis() { twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); - global.window.emojify.run(React.findDOMNode(this.refs.message_span)); } componentDidMount() { diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx index fe31ac381..8cc2d309b 100644 --- a/web/react/components/rhs_comment.jsx +++ b/web/react/components/rhs_comment.jsx @@ -56,7 +56,6 @@ export default class RhsComment extends React.Component { } parseEmojis() { twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); - global.window.emojify.run(React.findDOMNode(this.refs.message_holder)); } componentDidMount() { this.parseEmojis(); diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx index 2ea697c5b..86620a499 100644 --- a/web/react/components/rhs_root_post.jsx +++ b/web/react/components/rhs_root_post.jsx @@ -20,7 +20,6 @@ export default class RhsRootPost extends React.Component { } parseEmojis() { twemoji.parse(React.findDOMNode(this), {size: Constants.EMOJI_SIZE}); - global.window.emojify.run(React.findDOMNode(this.refs.message_holder)); } componentDidMount() { this.parseEmojis(); diff --git a/web/react/utils/emoticons.jsx b/web/react/utils/emoticons.jsx new file mode 100644 index 000000000..7210201ff --- /dev/null +++ b/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: /:['’]-?\(|:'\(/g, // :`( + kissing_heart: /:-?\*/g, // :* + wink: /;-?\)/g, // ;) + pensive: /:-?\//g, // :/ + confounded: /:-?s/gi, // :s + flushed: /:-?\|/g, // :| + relaxed: /:-?\$/g, // :$ + mask: /:-x/gi, // :-x + heart: /<3|<3/g, // <3 + broken_heart: /<\/3|</3/g, // `, + 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`; +} diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index 4e390f708..be82f7b9c 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -3,6 +3,7 @@ const Autolinker = require('autolinker'); const Constants = require('./constants.jsx'); +const Emoticons = require('./emoticons.jsx'); const Markdown = require('./markdown.jsx'); const UserStore = require('../stores/user_store.jsx'); const Utils = require('./utils.jsx'); @@ -17,6 +18,7 @@ const markdownRenderer = new Markdown.MattermostMarkdownRenderer(); // - 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. // - 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. export function formatText(text, options = {}) { if (!('markdown' in options)) { @@ -34,6 +36,10 @@ export function formatText(text, options = {}) { const tokens = new Map(); // replace important words and phrases with tokens + if (!('emoticons' in options) || options.emoticon) { + output = Emoticons.handleEmoticons(output, tokens); + } + output = autolinkUrls(output, tokens, !!options.markdown); output = autolinkAtMentions(output, tokens); output = autolinkHashtags(output, tokens); -- cgit v1.2.3-1-g7c22 From 1709532ae6f48df0c152afb6a58597cd07d6df49 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 22 Sep 2015 14:14:54 -0400 Subject: Moved emoticon parsing to happen after url parsing so that we don't have smilies accidentally occuring in urls --- web/react/utils/text_formatting.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'web/react') diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index be82f7b9c..940bd0485 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -36,14 +36,14 @@ export function formatText(text, options = {}) { const tokens = new Map(); // replace important words and phrases with tokens - if (!('emoticons' in options) || options.emoticon) { - output = Emoticons.handleEmoticons(output, tokens); - } - output = autolinkUrls(output, tokens, !!options.markdown); output = autolinkAtMentions(output, tokens); output = autolinkHashtags(output, tokens); + if (!('emoticons' in options) || options.emoticon) { + output = Emoticons.handleEmoticons(output, tokens); + } + if (options.searchTerm) { output = highlightSearchTerm(output, tokens, options.searchTerm); } -- cgit v1.2.3-1-g7c22 From d025da5da9b6b6bf3c0003191f07a8184e545f8a Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 22 Sep 2015 14:28:43 -0400 Subject: Changed hashtag regex to only accept hashtags that begin with a letter --- web/react/utils/text_formatting.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web/react') diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index 940bd0485..089fdd4f9 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -247,7 +247,7 @@ function autolinkHashtags(text, tokens) { 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) { -- cgit v1.2.3-1-g7c22 From 1afa1d37c77b9508a4256e32c4e1fea27113033f Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 22 Sep 2015 14:30:50 -0400 Subject: Moved text formatting code to only occur inside of markdown text nodes --- web/react/utils/markdown.jsx | 13 +++++++++++++ web/react/utils/text_formatting.jsx | 39 +++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 19 deletions(-) (limited to 'web/react') diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index 96da54217..0a876a3e3 100644 --- a/web/react/utils/markdown.jsx +++ b/web/react/utils/markdown.jsx @@ -1,9 +1,18 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +const TextFormatting = require('./text_formatting.jsx'); + const marked = require('marked'); export class MattermostMarkdownRenderer extends marked.Renderer { + constructor(options, formattingOptions = {}) { + super(options); + + this.text = this.text.bind(this); + + this.formattingOptions = formattingOptions; + } link(href, title, text) { let outHref = href; @@ -19,4 +28,8 @@ export class MattermostMarkdownRenderer extends marked.Renderer { return output; } + + text(text) { + return TextFormatting.doFormatText(text, this.formattingOptions); + } } diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index 089fdd4f9..41dc98084 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -10,8 +10,6 @@ const Utils = require('./utils.jsx'); 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 // @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: @@ -25,14 +23,30 @@ export function formatText(text, options = {}) { options.markdown = true; } - // wait until marked can sanitize the html so that we don't break markdown block quotes let output; - if (!options.markdown) { - output = sanitizeHtml(text); + + if (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 = text; + output = sanitizeHtml(text); + output = doFormatText(output, options); } + // replace newlines with spaces if necessary + if (options.singleline) { + output = replaceNewlines(output); + } + + return output; +} + +export function doFormatText(text, options) { + let output = text; + const tokens = new Map(); // replace important words and phrases with tokens @@ -52,22 +66,9 @@ export function formatText(text, options = {}) { 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 output = replaceTokens(output, tokens); - // replace newlines with html line breaks - if (options.singleline) { - output = replaceNewlines(output); - } - return output; } -- cgit v1.2.3-1-g7c22 From 55f11ba8719d133103bc1d4779bb8cb66d591b58 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Tue, 22 Sep 2015 14:34:59 -0400 Subject: Removed extra handling of markdown links when doing autolinking --- web/react/utils/text_formatting.jsx | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) (limited to 'web/react') diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index 41dc98084..56bf49c3f 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -19,13 +19,9 @@ const marked = require('marked'); // - emoticons - Enables emoticon parsing. Defaults to true. // - markdown - Enables markdown parsing. Defaults to true. export function formatText(text, options = {}) { - if (!('markdown' in options)) { - options.markdown = true; - } - let output; - if (options.markdown) { + 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), @@ -44,13 +40,14 @@ export function formatText(text, options = {}) { 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(); // replace important words and phrases with tokens - output = autolinkUrls(output, tokens, !!options.markdown); + output = autolinkUrls(output, tokens); output = autolinkAtMentions(output, tokens); output = autolinkHashtags(output, tokens); @@ -85,7 +82,7 @@ export function sanitizeHtml(text) { return output; } -function autolinkUrls(text, tokens, markdown) { +function autolinkUrls(text, tokens) { function replaceUrlWithToken(autolinker, match) { const linkText = match.getMatchedText(); let url = linkText; @@ -115,30 +112,7 @@ function autolinkUrls(text, tokens, markdown) { replaceFn: replaceUrlWithToken }); - let output = 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; + return autolinker.link(text); } function autolinkAtMentions(text, tokens) { -- cgit v1.2.3-1-g7c22 From f1700e765b979b0a3d3fba478dc468f61d5d1226 Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Mon, 21 Sep 2015 18:41:22 -0700 Subject: Small change to remove react warning --- web/react/components/post_list_container.jsx | 1 + 1 file changed, 1 insertion(+) (limited to 'web/react') diff --git a/web/react/components/post_list_container.jsx b/web/react/components/post_list_container.jsx index 0815ac883..e59d85d41 100644 --- a/web/react/components/post_list_container.jsx +++ b/web/react/components/post_list_container.jsx @@ -49,6 +49,7 @@ export default class PostListContainer extends React.Component { for (let i = 0; i <= this.state.postLists.length - 1; i++) { postListCtls.push( -- cgit v1.2.3-1-g7c22 From bb557b30d7d806dfa14bb0a643d320f7451d875e Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Tue, 22 Sep 2015 15:23:50 -0700 Subject: Post drafts are now displayed when they contain only files and no text --- web/react/components/create_post.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'web/react') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index d9e67836d..6ab4af880 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -40,7 +40,7 @@ export default class CreatePost extends React.Component { let previews = []; let messageText = ''; let uploadsInProgress = []; - if (draft && draft.previews && draft.message) { + if (draft && (draft.message || (draft.previews && draft.previews.length))) { previews = draft.previews; messageText = draft.message; uploadsInProgress = draft.uploadsInProgress; @@ -258,7 +258,7 @@ export default class CreatePost extends React.Component { let previews = []; let messageText = ''; let uploadsInProgress = []; - if (draft && draft.previews && draft.message) { + if (draft && (draft.message || (draft.previews && draft.previews.length))) { previews = draft.previews; messageText = draft.message; uploadsInProgress = draft.uploadsInProgress; -- cgit v1.2.3-1-g7c22 From 2270a2bb455e0f16ca5d9f886d08f360b272f72c Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Tue, 22 Sep 2015 16:00:44 -0700 Subject: Functionalized draft checking --- web/react/components/create_post.jsx | 50 +++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 24 deletions(-) (limited to 'web/react') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 6ab4af880..abad60154 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -23,6 +23,7 @@ export default class CreatePost extends React.Component { this.lastTime = 0; + this.getCurrentDraft = this.getCurrentDraft.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.postMsgKeyPress = this.postMsgKeyPress.bind(this); this.handleUserInput = this.handleUserInput.bind(this); @@ -36,23 +37,15 @@ export default class CreatePost extends React.Component { PostStore.clearDraftUploads(); - const draft = PostStore.getCurrentDraft(); - let previews = []; - let messageText = ''; - let uploadsInProgress = []; - if (draft && (draft.message || (draft.previews && draft.previews.length))) { - previews = draft.previews; - messageText = draft.message; - uploadsInProgress = draft.uploadsInProgress; - } + const draft = this.getCurrentDraft(); this.state = { channelId: ChannelStore.getCurrentId(), - messageText: messageText, - uploadsInProgress: uploadsInProgress, - previews: previews, + messageText: draft.messageText, + uploadsInProgress: draft.uploadsInProgress, + previews: draft.previews, submitting: false, - initialText: messageText + initialText: draft.messageText }; } componentDidUpdate(prevProps, prevState) { @@ -60,6 +53,24 @@ export default class CreatePost extends React.Component { 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) { e.preventDefault(); @@ -253,18 +264,9 @@ export default class CreatePost extends React.Component { onChange() { const channelId = ChannelStore.getCurrentId(); if (this.state.channelId !== channelId) { - let draft = PostStore.getCurrentDraft(); - - let previews = []; - let messageText = ''; - let uploadsInProgress = []; - if (draft && (draft.message || (draft.previews && draft.previews.length))) { - previews = draft.previews; - messageText = draft.message; - uploadsInProgress = draft.uploadsInProgress; - } + const draft = this.getCurrentDraft(); - this.setState({channelId: channelId, messageText: messageText, initialText: messageText, submitting: false, serverError: null, postError: null, previews: previews, uploadsInProgress: uploadsInProgress}); + this.setState({channelId: channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress}); } } getFileCount(channelId) { -- cgit v1.2.3-1-g7c22 From 3ab7d3e24dba94fa31b88f255602bf6211aa7d7b Mon Sep 17 00:00:00 2001 From: Reed Garmsen Date: Tue, 22 Sep 2015 16:38:46 -0700 Subject: Adds a check for email validity client-side when joining a team using a public link --- web/react/components/signup_user_complete.jsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'web/react') diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 19c3b2d22..e77bde861 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -1,7 +1,7 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // 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 UserStore = require('../stores/user_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); @@ -31,13 +31,26 @@ export default class SignupUserComplete extends React.Component { handleSubmit(e) { 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(); if (!this.state.user.username) { this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); 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.') { this.setState({nameError: 'This username is reserved, please choose a new one.', emailError: '', passwordError: '', serverError: ''}); return; @@ -51,12 +64,6 @@ export default class SignupUserComplete extends React.Component { 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(); if (!this.state.user.password || this.state.user.password .length < 5) { this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); -- cgit v1.2.3-1-g7c22 From 284b3bb64c4edd3f2d336d35a6df9495d99cdf55 Mon Sep 17 00:00:00 2001 From: Chengwei Yang Date: Wed, 23 Sep 2015 16:57:48 +0800 Subject: PLT-168: check email used to reset password strictly Signed-off-by: Chengwei Yang --- web/react/components/password_reset_send_link.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'web/react') diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx index 1e6cc3607..37d4a58cb 100644 --- a/web/react/components/password_reset_send_link.jsx +++ b/web/react/components/password_reset_send_link.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +const Utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); export default class PasswordResetSendLink extends React.Component { @@ -15,8 +16,8 @@ export default class PasswordResetSendLink extends React.Component { e.preventDefault(); var state = {}; - var email = React.findDOMNode(this.refs.email).value.trim(); - if (!email) { + var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); + if (!email || !Utils.isEmail(email)) { state.error = 'Please enter a valid email address.'; this.setState(state); return; @@ -67,7 +68,7 @@ export default class PasswordResetSendLink extends React.Component {

{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}

Date: Tue, 22 Sep 2015 15:23:39 -0400 Subject: Added CSS classes to markdown headings, links, and tables --- web/react/utils/markdown.jsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'web/react') diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index 0a876a3e3..347024e1a 100644 --- a/web/react/utils/markdown.jsx +++ b/web/react/utils/markdown.jsx @@ -9,10 +9,17 @@ 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 `${text}`; + } + link(href, title, text) { let outHref = href; @@ -20,7 +27,7 @@ export class MattermostMarkdownRenderer extends marked.Renderer { outHref = `http://${outHref}`; } - let output = '${header}${body}`; + } + text(text) { return TextFormatting.doFormatText(text, this.formattingOptions); } -- cgit v1.2.3-1-g7c22 From e4a15076f458be1416de25b2c45578975b914de5 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 23 Sep 2015 10:12:40 -0400 Subject: Implement UI theme colors. --- web/react/components/channel_loader.jsx | 24 +- web/react/components/popover_list_members.jsx | 2 +- web/react/components/register_app_modal.jsx | 2 +- web/react/components/rhs_header_post.jsx | 1 + web/react/components/search_results_header.jsx | 1 + .../user_settings/custom_theme_chooser.jsx | 108 +++++++++ .../user_settings/import_theme_modal.jsx | 179 ++++++++++++++ .../user_settings/premade_theme_chooser.jsx | 55 +++++ .../user_settings/user_settings_appearance.jsx | 261 +++++++++++++-------- .../user_settings/user_settings_developer.jsx | 2 +- .../user_settings/user_settings_modal.jsx | 2 +- web/react/package.json | 18 +- web/react/pages/channel.jsx | 6 + web/react/stores/user_store.jsx | 16 ++ web/react/utils/constants.jsx | 139 ++++++++++- web/react/utils/utils.jsx | 188 +++++++++++---- 16 files changed, 827 insertions(+), 177 deletions(-) create mode 100644 web/react/components/user_settings/custom_theme_chooser.jsx create mode 100644 web/react/components/user_settings/import_theme_modal.jsx create mode 100644 web/react/components/user_settings/premade_theme_chooser.jsx (limited to 'web/react') diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index ce6f60f87..1261b957b 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -12,6 +12,7 @@ var PostStore = require('../stores/post_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var Utils = require('../utils/utils.jsx'); +var Constants = require('../utils/constants.jsx'); export default class ChannelLoader extends React.Component { constructor(props) { @@ -68,25 +69,10 @@ export default class ChannelLoader extends React.Component { /* Update CSS classes to match user theme */ var user = UserStore.getCurrentUser(); - if (user.props && user.props.theme) { - Utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';'); - Utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';'); - Utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';'); - 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'); + if ($.isPlainObject(user.theme_props) && !$.isEmptyObject(user.theme_props)) { + Utils.applyTheme(user.theme_props); + } else { + Utils.applyTheme(Constants.THEMES.default); } /* Setup global mouse events */ diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx index ec873dd00..a2ca8b00f 100644 --- a/web/react/components/popover_list_members.jsx +++ b/web/react/components/popover_list_members.jsx @@ -65,7 +65,7 @@ export default class PopoverListMembers extends React.Component { > {count}
diff --git a/web/react/components/register_app_modal.jsx b/web/react/components/register_app_modal.jsx index 3dd5c094e..473ff3f91 100644 --- a/web/react/components/register_app_modal.jsx +++ b/web/react/components/register_app_modal.jsx @@ -228,7 +228,7 @@ export default class RegisterAppModal extends React.Component { data-dismiss='modal' aria-label='Close' > - +

+

); diff --git a/web/react/components/search_results_header.jsx b/web/react/components/search_results_header.jsx index 694f0c55d..4e8a3ef10 100644 --- a/web/react/components/search_results_header.jsx +++ b/web/react/components/search_results_header.jsx @@ -50,6 +50,7 @@ export default class SearchResultsHeader extends React.Component { title='Close' onClick={this.handleClose} > +
); diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx new file mode 100644 index 000000000..44630a318 --- /dev/null +++ b/web/react/components/user_settings/custom_theme_chooser.jsx @@ -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( +
+ +
+ + +
+
+ ); + + colors += theme[element.id] + ','; + }); + + const pasteBox = ( +
+ + +
+ ); + + return ( +
+
+ {elements} +
+
+ {pasteBox} +
+
+ ); + } +} + +CustomThemeChooser.propTypes = { + theme: React.PropTypes.object.isRequired, + updateTheme: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx new file mode 100644 index 000000000..48be83afe --- /dev/null +++ b/web/react/components/user_settings/import_theme_modal.jsx @@ -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 ( + + this.setState({show: false})} + > + + {'Import Slack Theme'} + +
+ +

+ {'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:'} +

+
+
+ + {this.state.inputError} +
+
+
+ + + + +
+
+
+ ); + } +} diff --git a/web/react/components/user_settings/premade_theme_chooser.jsx b/web/react/components/user_settings/premade_theme_chooser.jsx new file mode 100644 index 000000000..e36503053 --- /dev/null +++ b/web/react/components/user_settings/premade_theme_chooser.jsx @@ -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( +
+
this.props.updateTheme(premadeTheme)} + > + +
+
+ ); + } + } + + return ( +
+ {premadeThemes} +
+ ); + } +} + +PremadeThemeChooser.propTypes = { + theme: React.PropTypes.object.isRequired, + updateTheme: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/user_settings/user_settings_appearance.jsx b/web/react/components/user_settings/user_settings_appearance.jsx index aec3b319d..7617f04d1 100644 --- a/web/react/components/user_settings/user_settings_appearance.jsx +++ b/web/react/components/user_settings/user_settings_appearance.jsx @@ -2,78 +2,119 @@ // See License.txt for license information. 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 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 { constructor(props) { super(props); + this.onChange = this.onChange.bind(this); this.submitTheme = this.submitTheme.bind(this); this.updateTheme = this.updateTheme.bind(this); this.handleClose = this.handleClose.bind(this); + this.handleImportModal = this.handleImportModal.bind(this); this.state = this.getStateFromStores(); + + this.originalTheme = this.state.theme; + } + componentDidMount() { + UserStore.addChangeListener(this.onChange); + + if (this.props.activeSection === 'theme') { + $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); + } + $('#user_settings').on('hidden.bs.modal', this.handleClose); + } + componentDidUpdate() { + if (this.props.activeSection === 'theme') { + $('.color-btn').removeClass('active-border'); + $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); + } + } + componentWillUnmount() { + UserStore.removeChangeListener(this.onChange); + $('#user_settings').off('hidden.bs.modal', this.handleClose); } getStateFromStores() { - var user = UserStore.getCurrentUser(); - var theme = '#2389d7'; - if (ThemeColors != null) { - theme = ThemeColors[0]; + 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); } - if (user.props && user.props.theme) { - theme = user.props.theme; + + let type = 'premade'; + if (theme.type === 'custom') { + type = 'custom'; } - return {theme: theme.toLowerCase()}; + 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(); - if (!user.props) { - user.props = {}; - } - user.props.theme = this.state.theme; + user.theme_props = this.state.theme; Client.updateUser(user, - function success() { - this.props.updateSection(''); - window.location.reload(); - }.bind(this), - function fail(err) { + (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); - }.bind(this) + } ); } - updateTheme(e) { - var hex = Utils.rgb2hex(e.target.style.backgroundColor); - this.setState({theme: hex.toLowerCase()}); + updateTheme(theme) { + this.setState({theme}); + Utils.applyTheme(theme); } - handleClose() { - this.setState({serverError: null}); - this.props.updateTab('general'); + updateType(type) { + this.setState({type}); } - componentDidMount() { - if (this.props.activeSection === 'theme') { - $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); - } - $('#user_settings').on('hidden.bs.modal', this.handleClose); - } - componentDidUpdate() { - if (this.props.activeSection === 'theme') { - $('.color-btn').removeClass('active-border'); - $(React.findDOMNode(this.refs[this.state.theme])).addClass('active-border'); - } + 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'); } - componentWillUnmount() { - $('#user_settings').off('hidden.bs.modal', this.handleClose); - this.props.updateSection(''); + handleImportModal() { + $('#user_settings').modal('hide'); + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_IMPORT_THEME_MODAL, + value: true + }); } render() { var serverError; @@ -81,67 +122,73 @@ export default class UserSettingsAppearance extends React.Component { serverError = this.state.serverError; } - var themeSection; - var self = this; - - if (ThemeColors != null) { - if (this.props.activeSection === 'theme') { - var themeButtons = []; - - for (var i = 0; i < ThemeColors.length; i++) { - themeButtons.push( -

- Appearance Settings + {'Appearance Settings'}

-

Appearance Settings

+

{'Appearance Settings'}

- {themeSection} + {themeUI}
+
+ + {'Import from Slack'} +
); } @@ -176,6 +230,5 @@ UserSettingsAppearance.defaultProps = { }; UserSettingsAppearance.propTypes = { activeSection: React.PropTypes.string, - updateSection: React.PropTypes.func, updateTab: React.PropTypes.func }; diff --git a/web/react/components/user_settings/user_settings_developer.jsx b/web/react/components/user_settings/user_settings_developer.jsx index 1694aaa79..d9fb43902 100644 --- a/web/react/components/user_settings/user_settings_developer.jsx +++ b/web/react/components/user_settings/user_settings_developer.jsx @@ -64,7 +64,7 @@ export default class DeveloperTab extends React.Component { data-dismiss='modal' aria-label='Close' > - +

- +

, + document.getElementById('import_theme_modal') + ); + React.render( , document.getElementById('team_settings_modal') diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index f75c1d4c3..8842263fa 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -14,6 +14,7 @@ var CHANGE_EVENT_SESSIONS = 'change_sessions'; var CHANGE_EVENT_AUDITS = 'change_audits'; var CHANGE_EVENT_TEAMS = 'change_teams'; var CHANGE_EVENT_STATUSES = 'change_statuses'; +var TOGGLE_IMPORT_MODAL_EVENT = 'toggle_import_modal'; class UserStoreClass extends EventEmitter { constructor() { @@ -34,6 +35,9 @@ class UserStoreClass extends EventEmitter { this.emitStatusesChange = this.emitStatusesChange.bind(this); this.addStatusesChangeListener = this.addStatusesChangeListener.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.getCurrentId = this.getCurrentId.bind(this); this.getCurrentUser = this.getCurrentUser.bind(this); @@ -114,6 +118,15 @@ class UserStoreClass extends EventEmitter { removeStatusesChangeListener(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) { this.gCurrentId = id; if (id == null) { @@ -321,6 +334,9 @@ UserStore.dispatchToken = AppDispatcher.register(function registry(payload) { UserStore.pSetStatuses(action.statuses); UserStore.emitStatusesChange(); break; + case ActionTypes.TOGGLE_IMPORT_THEME_MODAL: + UserStore.emitToggleImportModal(action.value); + break; default: } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 03e4635b5..41e9e9ca6 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -36,7 +36,9 @@ module.exports = { RECIEVED_CONFIG: null, - RECIEVED_LOGS: null + RECIEVED_LOGS: null, + + TOGGLE_IMPORT_THEME_MODAL: null }), PayloadSources: keyMirror({ @@ -110,5 +112,138 @@ module.exports = { ONLINE_ICON_SVG: "", OFFLINE_ICON_SVG: "", MENU_ICON: " ", - COMMENT_ICON: " " + COMMENT_ICON: " ", + 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' + } + ] }; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 074591489..a8860eb94 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -542,7 +542,119 @@ export function toTitleCase(str) { 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 var cssMainContainer = $('#css-modifier-container'); if (cssMainContainer.length === 0) { @@ -552,9 +664,9 @@ export function changeCss(className, classValue) { } // 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) { - classContainer = $('
'); + classContainer = $('
'); classContainer.appendTo(cssMainContainer); } @@ -760,57 +872,47 @@ Image.prototype.load = function imageLoad(url, progressCallback) { Image.prototype.completedPercentage = 0; export function changeColor(colourIn, amt) { - var usePound = false; - var col = colourIn; + var hex = colourIn; + var lum = amt; - if (col[0] === '#') { - col = col.slice(1); - usePound = true; + // validate hex string + hex = String(hex).replace(/[^0-9a-f]/gi, ''); + if (hex.length < 6) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; } + lum = lum || 0; - var num = parseInt(col, 16); - - 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; + // 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 g = (num & 0x0000FF) + amt; - - if (g > 255) { - g = 255; - } else if (g < 0) { - g = 0; - } + return rgb; +} - var pound = '#'; - if (!usePound) { - pound = ''; +export function changeOpacity(oldColor, opacity) { + var color = oldColor; + if (color[0] === '#') { + color = color.slice(1); } - return pound + String('000000' + (g | (b << 8) | (r << 16)).toString(16)).slice(-6); -} + if (color.length === 3) { + const tempColor = color; + color = ''; -export function changeOpacity(oldColor, opacity) { - var col = oldColor; - if (col[0] === '#') { - col = col.slice(1); + color += tempColor[0] + tempColor[0]; + color += tempColor[1] + tempColor[1]; + color += tempColor[2] + tempColor[2]; } - var r = parseInt(col.substring(0, 2), 16); - var g = parseInt(col.substring(2, 4), 16); - var b = parseInt(col.substring(4, 6), 16); + 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 + ')'; } -- cgit v1.2.3-1-g7c22 From f7a32fa38a5c9f33d7fbb3d73d240626211173cc Mon Sep 17 00:00:00 2001 From: Chengwei Yang Date: Wed, 23 Sep 2015 22:37:40 +0800 Subject: PLT-126: Change 'Private Messages' to 'Direct Messages' Signed-off-by: Chengwei Yang --- web/react/components/more_direct_channels.jsx | 2 +- web/react/components/post_list.jsx | 6 +++--- web/react/components/rhs_root_post.jsx | 2 +- web/react/components/search_results_item.jsx | 2 +- web/react/components/sidebar.jsx | 2 +- web/react/components/user_settings/user_settings_notifications.jsx | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) (limited to 'web/react') diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx index f27b09ecc..b7bce9b34 100644 --- a/web/react/components/more_direct_channels.jsx +++ b/web/react/components/more_direct_channels.jsx @@ -114,7 +114,7 @@ export default class MoreDirectChannels extends React.Component { Close -

More Private Messages

+

More Direct Messages

    diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 703e548fb..218922b67 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -326,8 +326,8 @@ export default class PostList extends React.Component {

- {'This is the start of your private message history with ' + teammateName + '.'}
- {'Private messages and files shared here are not shown to people outside this area.'} + {'This is the start of your direct message history with ' + teammateName + '.'}
+ {'Direct messages and files shared here are not shown to people outside this area.'}

-

{'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.'}

+

{'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.'}

); } diff --git a/web/react/components/rhs_root_post.jsx b/web/react/components/rhs_root_post.jsx index 86620a499..e661bdce1 100644 --- a/web/react/components/rhs_root_post.jsx +++ b/web/react/components/rhs_root_post.jsx @@ -53,7 +53,7 @@ export default class RhsRootPost extends React.Component { var channelName; if (channel) { if (channel.type === 'D') { - channelName = 'Private Message'; + channelName = 'Direct Message'; } else { channelName = channel.display_name; } diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx index 0e951f5c6..32b521560 100644 --- a/web/react/components/search_results_item.jsx +++ b/web/react/components/search_results_item.jsx @@ -64,7 +64,7 @@ export default class SearchResultsItem extends React.Component { if (channel) { channelName = channel.display_name; if (channel.type === 'D') { - channelName = 'Private Message'; + channelName = 'Direct Message'; } } diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 87007edcc..14664ed4d 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -566,7 +566,7 @@ export default class Sidebar extends React.Component { {privateChannelItems}
    -
  • Private Messages

  • +
  • Direct Messages

  • {directMessageItems} {directMessageMore}
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx index fde4970ce..8d364cde7 100644 --- a/web/react/components/user_settings/user_settings_notifications.jsx +++ b/web/react/components/user_settings/user_settings_notifications.jsx @@ -241,7 +241,7 @@ export default class NotificationsTab extends React.Component { checked={notifyActive[1]} onChange={this.handleNotifyRadio.bind(this, 'mention')} > - Only for mentions and private messages + Only for mentions and direct messages
@@ -277,7 +277,7 @@ export default class NotificationsTab extends React.Component { } else { let describe = ''; 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') { describe = 'Never'; } else { @@ -414,7 +414,7 @@ export default class NotificationsTab extends React.Component {
-

{'Email notifications are sent for mentions and private messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}
+

{'Email notifications are sent for mentions and direct messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}
); -- cgit v1.2.3-1-g7c22 From 845229d9836412c2592fdcd9da0e7b1b9fc76a15 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 23 Sep 2015 12:25:01 -0400 Subject: Fix post not rendering properly in some cases. --- web/react/components/post.jsx | 51 ++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 15 deletions(-) (limited to 'web/react') diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index d3c6befd0..9127f00de 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -51,7 +51,7 @@ export default class Post extends React.Component { var post = this.props.post; client.createPost(post, post.channel_id, - function success(data) { + (data) => { AsyncClient.getPosts(); var channel = ChannelStore.get(post.channel_id); @@ -65,11 +65,11 @@ export default class Post extends React.Component { post: data }); }, - function error() { + () => { post.state = Constants.POST_FAILED; PostStore.updatePendingPost(post); this.forceUpdate(); - }.bind(this) + } ); post.state = Constants.POST_LOADING; @@ -81,31 +81,52 @@ export default class Post extends React.Component { return true; } - return false; - } - render() { - var post = this.props.post; - var parentPost = this.props.parentPost; - var posts = this.props.posts; + if (nextProps.sameRoot !== this.props.sameRoot) { + return true; + } - var type = 'Post'; - if (post.root_id && post.root_id.length > 0) { - type = 'Comment'; + if (nextProps.sameUser !== this.props.sameUser) { + return true; + } + + if (this.getCommentCount(nextProps) !== this.getCommentCount(this.props)) { + return true; } - var commentCount = 0; - var commentRootId; + 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 (var postId in posts) { + for (let postId in posts) { if (posts[postId].root_id === commentRootId) { commentCount += 1; } } + return commentCount; + } + render() { + var post = this.props.post; + var parentPost = this.props.parentPost; + var posts = this.props.posts; + + var type = 'Post'; + if (post.root_id && post.root_id.length > 0) { + type = 'Comment'; + } + + const commentCount = this.getCommentCount(this.props); + var rootUser; if (this.props.sameRoot) { rootUser = 'same--root'; -- cgit v1.2.3-1-g7c22 From e79e66c0a41dd4aaf0bf2873689120253c9339a7 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 23 Sep 2015 15:29:39 -0400 Subject: Fix theme images 404ing. --- web/react/components/user_settings/premade_theme_chooser.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web/react') diff --git a/web/react/components/user_settings/premade_theme_chooser.jsx b/web/react/components/user_settings/premade_theme_chooser.jsx index e36503053..e6aa2f5b9 100644 --- a/web/react/components/user_settings/premade_theme_chooser.jsx +++ b/web/react/components/user_settings/premade_theme_chooser.jsx @@ -31,7 +31,7 @@ export default class PremadeThemeChooser extends React.Component { -- cgit v1.2.3-1-g7c22