From 113741243bee612b9e65530e1827a0891d96474c Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sat, 24 Oct 2015 03:25:26 +0200 Subject: highlight code in markdown blocks --- web/react/package.json | 1 + web/react/utils/constants.jsx | 23 +++++++++++++ web/react/utils/markdown.jsx | 61 +++++++++++++++++++++++++++++++++ web/sass-files/sass/partials/_post.scss | 16 +++++++++ web/static/css/highlight | 1 + web/templates/head.html | 1 + 6 files changed, 103 insertions(+) create mode 120000 web/static/css/highlight (limited to 'web') diff --git a/web/react/package.json b/web/react/package.json index e6a662375..9af6f5880 100644 --- a/web/react/package.json +++ b/web/react/package.json @@ -6,6 +6,7 @@ "autolinker": "0.18.1", "babel-runtime": "5.8.24", "flux": "2.1.1", + "highlight.js": "^8.9.1", "keymirror": "0.1.1", "marked": "0.3.5", "object-assign": "3.0.0", diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 72773bf05..1a1d7f39f 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -313,5 +313,28 @@ module.exports = { ENTER: 13, ESCAPE: 27, SPACE: 32 + }, + HighlightedLanguages: { + diff: 'Diff', + apache: 'Apache', + makefile: 'Makefile', + http: 'HTTP', + json: 'JSON', + markdown: 'Markdown', + javascript: 'JavaScript', + css: 'CSS', + nginx: 'nginx', + objectivec: 'Objective-C', + python: 'Python', + xml: 'XML', + perl: 'Perl', + bash: 'Bash', + php: 'PHP', + coffeescript: 'CoffeeScript', + cs: 'C#', + cpp: 'C++', + sql: 'SQL', + go: 'Go', + ruby: 'Ruby' } }; diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index 7a4e70054..8cfcfdefd 100644 --- a/web/react/utils/markdown.jsx +++ b/web/react/utils/markdown.jsx @@ -6,6 +6,32 @@ const Utils = require('./utils.jsx'); const marked = require('marked'); +const highlightJs = require('highlight.js/lib/highlight.js'); +const highlightJsDiff = require('highlight.js/lib/languages/diff.js'); +const highlightJsApache = require('highlight.js/lib/languages/apache.js'); +const highlightJsMakefile = require('highlight.js/lib/languages/makefile.js'); +const highlightJsHttp = require('highlight.js/lib/languages/http.js'); +const highlightJsJson = require('highlight.js/lib/languages/json.js'); +const highlightJsMarkdown = require('highlight.js/lib/languages/markdown.js'); +const highlightJsJavascript = require('highlight.js/lib/languages/javascript.js'); +const highlightJsCss = require('highlight.js/lib/languages/css.js'); +const highlightJsNginx = require('highlight.js/lib/languages/nginx.js'); +const highlightJsObjectivec = require('highlight.js/lib/languages/objectivec.js'); +const highlightJsPython = require('highlight.js/lib/languages/python.js'); +const highlightJsXml = require('highlight.js/lib/languages/xml.js'); +const highlightJsPerl = require('highlight.js/lib/languages/perl.js'); +const highlightJsBash = require('highlight.js/lib/languages/bash.js'); +const highlightJsPhp = require('highlight.js/lib/languages/php.js'); +const highlightJsCoffeescript = require('highlight.js/lib/languages/coffeescript.js'); +const highlightJsCs = require('highlight.js/lib/languages/cs.js'); +const highlightJsCpp = require('highlight.js/lib/languages/cpp.js'); +const highlightJsSql = require('highlight.js/lib/languages/sql.js'); +const highlightJsGo = require('highlight.js/lib/languages/go.js'); +const highlightJsRuby = require('highlight.js/lib/languages/ruby.js'); + +const Constants = require('../utils/constants.jsx'); +const HighlightedLanguages = Constants.HighlightedLanguages; + export class MattermostMarkdownRenderer extends marked.Renderer { constructor(options, formattingOptions = {}) { super(options); @@ -15,6 +41,41 @@ export class MattermostMarkdownRenderer extends marked.Renderer { this.text = this.text.bind(this); this.formattingOptions = formattingOptions; + + highlightJs.registerLanguage('diff', highlightJsDiff); + highlightJs.registerLanguage('apache', highlightJsApache); + highlightJs.registerLanguage('makefile', highlightJsMakefile); + highlightJs.registerLanguage('http', highlightJsHttp); + highlightJs.registerLanguage('json', highlightJsJson); + highlightJs.registerLanguage('markdown', highlightJsMarkdown); + highlightJs.registerLanguage('javascript', highlightJsJavascript); + highlightJs.registerLanguage('css', highlightJsCss); + highlightJs.registerLanguage('nginx', highlightJsNginx); + highlightJs.registerLanguage('objectivec', highlightJsObjectivec); + highlightJs.registerLanguage('python', highlightJsPython); + highlightJs.registerLanguage('xml', highlightJsXml); + highlightJs.registerLanguage('perl', highlightJsPerl); + highlightJs.registerLanguage('bash', highlightJsBash); + highlightJs.registerLanguage('php', highlightJsPhp); + highlightJs.registerLanguage('coffeescript', highlightJsCoffeescript); + highlightJs.registerLanguage('cs', highlightJsCs); + highlightJs.registerLanguage('cpp', highlightJsCpp); + highlightJs.registerLanguage('sql', highlightJsSql); + highlightJs.registerLanguage('go', highlightJsGo); + highlightJs.registerLanguage('ruby', highlightJsRuby); + } + + code(code, language) { + if (!language || highlightJs.listLanguages().indexOf(language) < 0) { + let parsed = super.code(code, language); + return '' + parsed.substr(11, parsed.length - 17); + } + + let parsed = highlightJs.highlight(language, code); + return '
' + + '' + HighlightedLanguages[language] + '' + + '' + parsed.value + '' + + '
'; } br() { diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index f5fc1631f..3fac1fed9 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -466,6 +466,22 @@ body.ios { white-space: nowrap; cursor: pointer; } + .post-body--code { + font-size: .97em; + position:relative; + .post-body--code__language { + position: absolute; + right: 0; + background: #fff; + cursor: default; + padding: 0.3em 0.5em 0.1em; + border-bottom-left-radius: 4px; + @include opacity(.3); + } + code { + white-space: pre; + } + } } .create-reply-form-wrap { width: 100%; diff --git a/web/static/css/highlight b/web/static/css/highlight new file mode 120000 index 000000000..c774cf397 --- /dev/null +++ b/web/static/css/highlight @@ -0,0 +1 @@ +../../react/node_modules/highlight.js/styles/ \ No newline at end of file diff --git a/web/templates/head.html b/web/templates/head.html index 041831ed7..837cfb133 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -24,6 +24,7 @@ + -- cgit v1.2.3-1-g7c22 From 0f62befef06f7fc467571a87affdfa95fa1fbb81 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sat, 24 Oct 2015 15:50:20 +0200 Subject: code style theme chooser --- .../user_settings/code_theme_chooser.jsx | 55 +++++++++++++++++++++ .../user_settings/user_settings_appearance.jsx | 23 ++++++++- web/react/utils/constants.jsx | 7 +++ web/react/utils/utils.jsx | 26 ++++++++++ web/static/images/themes/code_themes/github.png | Bin 0 -> 9648 bytes web/static/images/themes/code_themes/monokai.png | Bin 0 -> 9303 bytes .../images/themes/code_themes/solarized_dark.png | Bin 0 -> 8172 bytes .../images/themes/code_themes/solarized_light.png | Bin 0 -> 8860 bytes web/templates/head.html | 2 +- 9 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 web/react/components/user_settings/code_theme_chooser.jsx create mode 100644 web/static/images/themes/code_themes/github.png create mode 100644 web/static/images/themes/code_themes/monokai.png create mode 100644 web/static/images/themes/code_themes/solarized_dark.png create mode 100644 web/static/images/themes/code_themes/solarized_light.png (limited to 'web') diff --git a/web/react/components/user_settings/code_theme_chooser.jsx b/web/react/components/user_settings/code_theme_chooser.jsx new file mode 100644 index 000000000..eef4b24ba --- /dev/null +++ b/web/react/components/user_settings/code_theme_chooser.jsx @@ -0,0 +1,55 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +var Constants = require('../../utils/constants.jsx'); + +export default class CodeThemeChooser extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + render() { + const theme = this.props.theme; + + const premadeThemes = []; + for (const k in Constants.CODE_THEMES) { + if (Constants.CODE_THEMES.hasOwnProperty(k)) { + let activeClass = ''; + if (k === theme.codeTheme) { + activeClass = 'active'; + } + + premadeThemes.push( +
+
this.props.updateTheme(k)} + > + +
+
+ ); + } + } + + return ( +
+ {premadeThemes} +
+ ); + } +} + +CodeThemeChooser.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 8c62a189d..e94894a1d 100644 --- a/web/react/components/user_settings/user_settings_appearance.jsx +++ b/web/react/components/user_settings/user_settings_appearance.jsx @@ -7,6 +7,7 @@ var Utils = require('../../utils/utils.jsx'); const CustomThemeChooser = require('./custom_theme_chooser.jsx'); const PremadeThemeChooser = require('./premade_theme_chooser.jsx'); +const CodeThemeChooser = require('./code_theme_chooser.jsx'); const AppDispatcher = require('../../dispatcher/app_dispatcher.jsx'); const Constants = require('../../utils/constants.jsx'); const ActionTypes = Constants.ActionTypes; @@ -18,12 +19,14 @@ export default class UserSettingsAppearance extends React.Component { this.onChange = this.onChange.bind(this); this.submitTheme = this.submitTheme.bind(this); this.updateTheme = this.updateTheme.bind(this); + this.updateCodeTheme = this.updateCodeTheme.bind(this); this.handleClose = this.handleClose.bind(this); this.handleImportModal = this.handleImportModal.bind(this); this.state = this.getStateFromStores(); this.originalTheme = this.state.theme; + this.originalCodeTheme = this.state.theme.codeTheme; } componentDidMount() { UserStore.addChangeListener(this.onChange); @@ -58,6 +61,10 @@ export default class UserSettingsAppearance extends React.Component { type = 'custom'; } + if (!theme.codeTheme) { + theme.codeTheme = Constants.DEFAULT_CODE_THEME; + } + return {theme, type}; } onChange() { @@ -93,6 +100,13 @@ export default class UserSettingsAppearance extends React.Component { ); } updateTheme(theme) { + theme.codeTheme = this.state.theme.codeTheme; + this.setState({theme}); + Utils.applyTheme(theme); + } + updateCodeTheme(codeTheme) { + var theme = this.state.theme; + theme.codeTheme = codeTheme; this.setState({theme}); Utils.applyTheme(theme); } @@ -102,6 +116,7 @@ export default class UserSettingsAppearance extends React.Component { handleClose() { const state = this.getStateFromStores(); state.serverError = null; + state.theme.codeTheme = this.originalCodeTheme; Utils.applyTheme(state.theme); @@ -170,7 +185,13 @@ export default class UserSettingsAppearance extends React.Component { {custom}
- {serverError} + {'Code Theme'} + +
+ {serverError} { + changeCss('code.hljs', 'visibility: visible'); + }); + } else { + changeCss('code.hljs', 'visibility: visible'); + } + }; + xmlHTTP.send(); + } +} + export function placeCaretAtEnd(el) { el.focus(); if (typeof window.getSelection != 'undefined' && typeof document.createRange != 'undefined') { diff --git a/web/static/images/themes/code_themes/github.png b/web/static/images/themes/code_themes/github.png new file mode 100644 index 000000000..d0538d6c0 Binary files /dev/null and b/web/static/images/themes/code_themes/github.png differ diff --git a/web/static/images/themes/code_themes/monokai.png b/web/static/images/themes/code_themes/monokai.png new file mode 100644 index 000000000..8f92d2a18 Binary files /dev/null and b/web/static/images/themes/code_themes/monokai.png differ diff --git a/web/static/images/themes/code_themes/solarized_dark.png b/web/static/images/themes/code_themes/solarized_dark.png new file mode 100644 index 000000000..76055c678 Binary files /dev/null and b/web/static/images/themes/code_themes/solarized_dark.png differ diff --git a/web/static/images/themes/code_themes/solarized_light.png b/web/static/images/themes/code_themes/solarized_light.png new file mode 100644 index 000000000..b9595c22d Binary files /dev/null and b/web/static/images/themes/code_themes/solarized_light.png differ diff --git a/web/templates/head.html b/web/templates/head.html index 837cfb133..fdc371af4 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -24,7 +24,7 @@ - + -- cgit v1.2.3-1-g7c22 From 9fd9fb1722edfa4aa2e77aa25abfe3e317160839 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sat, 24 Oct 2015 16:03:12 +0200 Subject: fix markup if code is of unknown language --- web/react/utils/markdown.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index 8cfcfdefd..f1d15615e 100644 --- a/web/react/utils/markdown.jsx +++ b/web/react/utils/markdown.jsx @@ -68,7 +68,7 @@ export class MattermostMarkdownRenderer extends marked.Renderer { code(code, language) { if (!language || highlightJs.listLanguages().indexOf(language) < 0) { let parsed = super.code(code, language); - return '' + parsed.substr(11, parsed.length - 17); + return '' + $(parsed).text() + ''; } let parsed = highlightJs.highlight(language, code); -- cgit v1.2.3-1-g7c22 From 51032ae11de9158ea6a4a4be6b73c621b1c75f2a Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sat, 24 Oct 2015 21:55:16 +0200 Subject: Add java and ini language Forgot to add those altough they are quite common --- web/react/utils/constants.jsx | 4 +++- web/react/utils/markdown.jsx | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 9c079bb87..c20d84f40 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -342,6 +342,8 @@ module.exports = { cpp: 'C++', sql: 'SQL', go: 'Go', - ruby: 'Ruby' + ruby: 'Ruby', + java: 'Java', + ini: 'ini' } }; diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index f1d15615e..01cc309b8 100644 --- a/web/react/utils/markdown.jsx +++ b/web/react/utils/markdown.jsx @@ -28,6 +28,8 @@ const highlightJsCpp = require('highlight.js/lib/languages/cpp.js'); const highlightJsSql = require('highlight.js/lib/languages/sql.js'); const highlightJsGo = require('highlight.js/lib/languages/go.js'); const highlightJsRuby = require('highlight.js/lib/languages/ruby.js'); +const highlightJsJava = require('highlight.js/lib/languages/java.js'); +const highlightJsIni = require('highlight.js/lib/languages/ini.js'); const Constants = require('../utils/constants.jsx'); const HighlightedLanguages = Constants.HighlightedLanguages; @@ -63,6 +65,8 @@ export class MattermostMarkdownRenderer extends marked.Renderer { highlightJs.registerLanguage('sql', highlightJsSql); highlightJs.registerLanguage('go', highlightJsGo); highlightJs.registerLanguage('ruby', highlightJsRuby); + highlightJs.registerLanguage('java', highlightJsJava); + highlightJs.registerLanguage('ini', highlightJsIni); } code(code, language) { -- cgit v1.2.3-1-g7c22 From 0c0453559974dcdd2a7fa6fb9c8d72fdbc4082c7 Mon Sep 17 00:00:00 2001 From: Pat Lathem Date: Sat, 24 Oct 2015 18:32:45 -0500 Subject: Remove trailing punctuation when parsing @username references --- web/react/utils/text_formatting.jsx | 60 +++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 13 deletions(-) (limited to 'web') diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index d79aeed68..9fd22e6b9 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -82,6 +82,7 @@ export function sanitizeHtml(text) { return output; } +// Convert URLs into tokens function autolinkUrls(text, tokens) { function replaceUrlWithToken(autolinker, match) { const linkText = match.getMatchedText(); @@ -123,27 +124,60 @@ function autolinkUrls(text, tokens) { } function autolinkAtMentions(text, tokens) { - let output = text; + // Return true if provided character is punctuation + function isPunctuation(character) { + const re = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]/g; + return re.test(character); + } - function replaceAtMentionWithToken(fullMatch, prefix, mention, username) { - const usernameLower = username.toLowerCase(); - if (Constants.SPECIAL_MENTIONS.indexOf(usernameLower) !== -1 || UserStore.getProfileByUsername(usernameLower)) { - const index = tokens.size; - const alias = `MM_ATMENTION${index}`; - - tokens.set(alias, { - value: `${mention}`, - originalText: mention - }); + // Test if provided text needs to be highlighted, special mention or current user + function mentionExists(u) { + return (Constants.SPECIAL_MENTIONS.indexOf(u) !== -1 || UserStore.getProfileByUsername(u)); + } + + function addToken(username, mention, extraText) { + const index = tokens.size; + const alias = `MM_ATMENTION${index}`; + + tokens.set(alias, { + value: `${mention}${extraText}`, + originalText: mention + }); + return alias; + } + function replaceAtMentionWithToken(fullMatch, prefix, mention, username) { + let usernameLower = username.toLowerCase(); + + if (mentionExists(usernameLower)) { + // Exact match + const alias = addToken(usernameLower, mention, ''); return prefix + alias; + } + + // Not an exact match, attempt to truncate any punctuation to see if we can find a user + const originalUsername = usernameLower; + + for (let c = usernameLower.length; c > 0; c--) { + if (isPunctuation(usernameLower[c-1])) { + usernameLower = usernameLower.substring(0, c); + + if (mentionExists(usernameLower)) { + const extraText = originalUsername.substr(c); + const alias = addToken(usernameLower, mention, extraText); + return prefix + alias; + } + } else { + // If the last character is not punctuation, no point in going any further + break; + } } return fullMatch; } - output = output.replace(/(^|\s)(@([a-z0-9.\-_]*[a-z0-9]))/gi, replaceAtMentionWithToken); - + let output = text; + output = output.replace(/(^|\s)(@([a-z0-9.\-_]*))/gi, replaceAtMentionWithToken); return output; } -- cgit v1.2.3-1-g7c22 From 80e0a8db1d70ca387c654d9ac6bded0fb1e352a6 Mon Sep 17 00:00:00 2001 From: Pat Lathem Date: Sat, 24 Oct 2015 18:54:01 -0500 Subject: Fix off by one error --- web/react/utils/text_formatting.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'web') diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index 9fd22e6b9..c49bdf916 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -148,23 +148,23 @@ function autolinkAtMentions(text, tokens) { function replaceAtMentionWithToken(fullMatch, prefix, mention, username) { let usernameLower = username.toLowerCase(); - + if (mentionExists(usernameLower)) { // Exact match const alias = addToken(usernameLower, mention, ''); return prefix + alias; - } + } // Not an exact match, attempt to truncate any punctuation to see if we can find a user const originalUsername = usernameLower; - + for (let c = usernameLower.length; c > 0; c--) { - if (isPunctuation(usernameLower[c-1])) { - usernameLower = usernameLower.substring(0, c); + if (isPunctuation(usernameLower[c - 1])) { + usernameLower = usernameLower.substring(0, c - 1); if (mentionExists(usernameLower)) { - const extraText = originalUsername.substr(c); - const alias = addToken(usernameLower, mention, extraText); + const extraText = originalUsername.substr(c - 1); + const alias = addToken(usernameLower, '@' + usernameLower, extraText); return prefix + alias; } } else { -- cgit v1.2.3-1-g7c22 From 3a588fbc18bb990e07e656a41d2f858fb9dc25e2 Mon Sep 17 00:00:00 2001 From: Pat Lathem Date: Sun, 25 Oct 2015 14:09:09 -0500 Subject: Fix highlighting of trailing punctuation for own username --- web/react/utils/text_formatting.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'web') diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index c49bdf916..e47aca39b 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -140,8 +140,9 @@ function autolinkAtMentions(text, tokens) { const alias = `MM_ATMENTION${index}`; tokens.set(alias, { - value: `${mention}${extraText}`, - originalText: mention + value: `${mention}`, + originalText: mention, + extraText }); return alias; } @@ -194,10 +195,9 @@ function highlightCurrentMentions(text, tokens) { const newAlias = `MM_SELFMENTION${index}`; newTokens.set(newAlias, { - value: `${alias}`, + value: `${alias}` + token.extraText, originalText: token.originalText }); - output = output.replace(alias, newAlias); } } -- cgit v1.2.3-1-g7c22 From 6afe95158c9cda38bb87ef193e77435f339b846b Mon Sep 17 00:00:00 2001 From: it33 Date: Sun, 25 Oct 2015 13:37:06 -0700 Subject: Update websocket error Getting this error when websockets is properly configured and connection is just slow --- web/react/utils/client.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index bc73f3c64..a93257dd2 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -34,7 +34,7 @@ function handleError(methodName, xhr, status, err) { if (oldError && oldError.connErrorCount) { errorCount += oldError.connErrorCount; - connectError = 'We cannot reach the Mattermost service. The service may be down or misconfigured. Please contact an administrator to make sure the WebSocket port is configured properly.'; + connectError = 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to check WebSocket port.'; } e = {message: connectError, connErrorCount: errorCount}; -- cgit v1.2.3-1-g7c22 From af62f4d57112d35ba9da37216eaed72c18923102 Mon Sep 17 00:00:00 2001 From: it33 Date: Sun, 25 Oct 2015 13:37:49 -0700 Subject: Update help --- web/react/stores/socket_store.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index 9410c1e9c..455b5b042 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -86,7 +86,7 @@ class SocketStoreClass extends EventEmitter { this.failCount = this.failCount + 1; - ErrorStore.storeLastError({connErrorCount: this.failCount, message: 'We cannot reach the Mattermost service. The service may be down or misconfigured. Please contact an administrator to make sure the WebSocket port is configured properly.'}); + ErrorStore.storeLastError({connErrorCount: this.failCount, message: 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to check WebSocket port.'}); ErrorStore.emitChange(); }; -- cgit v1.2.3-1-g7c22 From c6bbebbf6f5bc6fd1b4af222e7043cee0dd24f1f Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sun, 25 Oct 2015 23:55:50 +0100 Subject: Mattermost can not send message start with slash resolves #827 --- web/react/components/create_post.jsx | 115 +++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 51 deletions(-) (limited to 'web') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 8b5fc4162..055be112d 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -38,6 +38,7 @@ export default class CreatePost extends React.Component { this.getFileCount = this.getFileCount.bind(this); this.handleArrowUp = this.handleArrowUp.bind(this); this.handleResize = this.handleResize.bind(this); + this.sendMessage = this.sendMessage.bind(this); PostStore.clearDraftUploads(); @@ -122,6 +123,11 @@ export default class CreatePost extends React.Component { post.message, false, (data) => { + if (data.response === 'not implemented') { + this.sendMessage(post); + return; + } + PostStore.storeDraft(data.channel_id, null); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); @@ -130,63 +136,70 @@ export default class CreatePost extends React.Component { } }, (err) => { - const state = {}; - state.serverError = err.message; - state.submitting = false; - this.setState(state); - } - ); - } else { - post.channel_id = this.state.channelId; - post.filenames = this.state.previews; - - const time = Utils.getTimestamp(); - const userId = UserStore.getCurrentId(); - post.pending_post_id = `${userId}:${time}`; - post.user_id = userId; - post.create_at = time; - post.root_id = this.state.rootId; - post.parent_id = this.state.parentId; - - const channel = ChannelStore.get(this.state.channelId); - - PostStore.storePendingPost(post); - PostStore.storeDraft(channel.id, null); - this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); - - Client.createPost(post, channel, - (data) => { - AsyncClient.getPosts(); - - const member = ChannelStore.getMember(channel.id); - member.msg_count = channel.total_msg_count; - member.last_viewed_at = Date.now(); - ChannelStore.setChannelMember(member); - - AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_POST, - post: data - }); - }, - (err) => { - const state = {}; - - if (err.message === 'Invalid RootId parameter') { - if ($('#post_deleted').length > 0) { - $('#post_deleted').modal('show'); - } - PostStore.removePendingPost(post.pending_post_id); + if (err.sendMessage) { + this.sendMessage(post); } else { - post.state = Constants.POST_FAILED; - PostStore.updatePendingPost(post); + const state = {}; + state.serverError = err.message; + state.submitting = false; + this.setState(state); } - - state.submitting = false; - this.setState(state); } ); + } else { + this.sendMessage(post); } } + sendMessage(post) { + post.channel_id = this.state.channelId; + post.filenames = this.state.previews; + + const time = Utils.getTimestamp(); + const userId = UserStore.getCurrentId(); + post.pending_post_id = `${userId}:${time}`; + post.user_id = userId; + post.create_at = time; + post.root_id = this.state.rootId; + post.parent_id = this.state.parentId; + + const channel = ChannelStore.get(this.state.channelId); + + PostStore.storePendingPost(post); + PostStore.storeDraft(channel.id, null); + this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); + + Client.createPost(post, channel, + (data) => { + AsyncClient.getPosts(); + + const member = ChannelStore.getMember(channel.id); + member.msg_count = channel.total_msg_count; + member.last_viewed_at = Date.now(); + ChannelStore.setChannelMember(member); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_POST, + post: data + }); + }, + (err) => { + const state = {}; + + if (err.message === 'Invalid RootId parameter') { + if ($('#post_deleted').length > 0) { + $('#post_deleted').modal('show'); + } + PostStore.removePendingPost(post.pending_post_id); + } else { + post.state = Constants.POST_FAILED; + PostStore.updatePendingPost(post); + } + + state.submitting = false; + this.setState(state); + } + ); + } postMsgKeyPress(e) { if (e.which === KeyCodes.ENTER && !e.shiftKey && !e.altKey) { e.preventDefault(); -- cgit v1.2.3-1-g7c22 From 4cfde35256cecab12d317e0d829769143f4df2d0 Mon Sep 17 00:00:00 2001 From: Girish S Date: Mon, 26 Oct 2015 12:25:14 +0530 Subject: append * to search query if not present and highlight partial matches --- web/react/components/search_bar.jsx | 4 ++++ web/react/utils/text_formatting.jsx | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'web') diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx index e1d36ad7d..fae26f803 100644 --- a/web/react/components/search_bar.jsx +++ b/web/react/components/search_bar.jsx @@ -105,6 +105,10 @@ export default class SearchBar extends React.Component { performSearch(terms, isMentionSearch) { if (terms.length) { this.setState({isSearching: true}); + + if(terms.search(/\*\s*$/) == -1) // append * if not present + terms = terms + "*"; + client.search( terms, (data) => { diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index 5c2e68f1e..75f6cb714 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -243,10 +243,11 @@ function autolinkHashtags(text, tokens) { function highlightSearchTerm(text, tokens, searchTerm) { let output = text; + searchTerm = searchTerm.replace(/\*$/, ''); var newTokens = new Map(); for (const [alias, token] of tokens) { - if (token.originalText === searchTerm) { + if (token.originalText.indexOf(searchTerm) > -1) { const index = tokens.size + newTokens.size; const newAlias = `MM_SEARCHTERM${index}`; @@ -276,7 +277,7 @@ function highlightSearchTerm(text, tokens, searchTerm) { return prefix + alias; } - return output.replace(new RegExp(`(^|\\W)(${searchTerm})\\b`, 'gi'), replaceSearchTermWithToken); + return output.replace(new RegExp(`()(${searchTerm})`, 'gi'), replaceSearchTermWithToken); } function replaceTokens(text, tokens) { -- cgit v1.2.3-1-g7c22 From e9812655f6cb7e9e6c06bc1b2a462efc106f52f7 Mon Sep 17 00:00:00 2001 From: Girish S Date: Mon, 26 Oct 2015 13:00:26 +0530 Subject: made eslint happy --- web/react/components/search_bar.jsx | 9 ++++++--- web/react/utils/text_formatting.jsx | 5 ++--- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'web') diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx index fae26f803..0da43e8cd 100644 --- a/web/react/components/search_bar.jsx +++ b/web/react/components/search_bar.jsx @@ -106,11 +106,14 @@ export default class SearchBar extends React.Component { if (terms.length) { this.setState({isSearching: true}); - if(terms.search(/\*\s*$/) == -1) // append * if not present - terms = terms + "*"; + // append * if not present + let searchTerms = terms; + if (searchTerms.search(/\*\s*$/) === -1) { + searchTerms = searchTerms + '*'; + } client.search( - terms, + searchTerms, (data) => { this.setState({isSearching: false}); if (utils.isMobile()) { diff --git a/web/react/utils/text_formatting.jsx b/web/react/utils/text_formatting.jsx index 75f6cb714..204c37364 100644 --- a/web/react/utils/text_formatting.jsx +++ b/web/react/utils/text_formatting.jsx @@ -243,11 +243,10 @@ function autolinkHashtags(text, tokens) { function highlightSearchTerm(text, tokens, searchTerm) { let output = text; - searchTerm = searchTerm.replace(/\*$/, ''); var newTokens = new Map(); for (const [alias, token] of tokens) { - if (token.originalText.indexOf(searchTerm) > -1) { + if (token.originalText.indexOf(searchTerm.replace(/\*$/, '')) > -1) { const index = tokens.size + newTokens.size; const newAlias = `MM_SEARCHTERM${index}`; @@ -277,7 +276,7 @@ function highlightSearchTerm(text, tokens, searchTerm) { return prefix + alias; } - return output.replace(new RegExp(`()(${searchTerm})`, 'gi'), replaceSearchTermWithToken); + return output.replace(new RegExp(`()(${searchTerm})`, 'gi'), replaceSearchTermWithToken); } function replaceTokens(text, tokens) { -- cgit v1.2.3-1-g7c22 From 5d25e55254ce8060a69a0cfdbbfbd4babe77a860 Mon Sep 17 00:00:00 2001 From: Girish S Date: Mon, 26 Oct 2015 14:48:00 +0530 Subject: strips extra hiphens from channel url --- web/react/utils/utils.jsx | 1 + 1 file changed, 1 insertion(+) (limited to 'web') diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 67a9d6983..1f24cd634 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -20,6 +20,7 @@ export function isEmail(email) { export function cleanUpUrlable(input) { var cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-'); + cleaned = cleaned.replace(/-{2,}/, '-'); cleaned = cleaned.replace(/^\-+/, ''); cleaned = cleaned.replace(/\-+$/, ''); return cleaned; -- cgit v1.2.3-1-g7c22 From fda62fbbf576ead8aea3b4a39a167b7f2d218142 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 26 Oct 2015 12:05:09 -0400 Subject: Fix error message on leaving channel --- web/react/stores/socket_store.jsx | 2 +- web/react/utils/async_client.jsx | 4 ++-- web/react/utils/constants.jsx | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'web') diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index 9410c1e9c..d4b0e62db 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -160,7 +160,7 @@ function handleNewPostEvent(msg) { if (window.isActive) { AsyncClient.updateLastViewedAt(true); } - } else { + } else if (UserStore.getCurrentId() !== msg.user_id || post.type !== Constants.POST_TYPE_JOIN_LEAVE) { AsyncClient.getChannel(msg.channel_id); } diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index b1bc71d54..75dd35e3f 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -132,7 +132,7 @@ export function getChannel(id) { callTracker['getChannel' + id] = utils.getTimestamp(); client.getChannel(id, - function getChannelSuccess(data, textStatus, xhr) { + (data, textStatus, xhr) => { callTracker['getChannel' + id] = 0; if (xhr.status === 304 || !data) { @@ -145,7 +145,7 @@ export function getChannel(id) { member: data.member }); }, - function getChannelFailure(err) { + (err) => { callTracker['getChannel' + id] = 0; dispatchError(err, 'getChannel'); } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index c20d84f40..f31bf6740 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -98,6 +98,7 @@ module.exports = { POST_LOADING: 'loading', POST_FAILED: 'failed', POST_DELETED: 'deleted', + POST_TYPE_JOIN_LEAVE: 'join_leave', RESERVED_TEAM_NAMES: [ 'www', 'web', -- cgit v1.2.3-1-g7c22 From 46042d995bc553a10513c527aba106e1b92d04d4 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sun, 25 Oct 2015 02:00:55 +0100 Subject: PLT-703: Support multiple users being shown as typing underneath input boxes. --- web/react/components/create_post.jsx | 2 +- web/react/components/msg_typing.jsx | 49 ++++++++++++++++++++++++------------ web/react/utils/constants.jsx | 1 + 3 files changed, 35 insertions(+), 17 deletions(-) (limited to 'web') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 055be112d..b74f1871c 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -208,7 +208,7 @@ export default class CreatePost extends React.Component { } const t = Date.now(); - if ((t - this.lastTime) > 5000) { + if ((t - this.lastTime) > Constants.UPDATE_TYPING_MS) { SocketStore.sendMessage({channel_id: this.state.channelId, action: 'typing', props: {parent_id: ''}, state: {}}); this.lastTime = t; } diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx index 1bd23c55c..42ae4bcc4 100644 --- a/web/react/components/msg_typing.jsx +++ b/web/react/components/msg_typing.jsx @@ -11,11 +11,11 @@ export default class MsgTyping extends React.Component { constructor(props) { super(props); - this.timer = null; - this.lastTime = 0; - this.onChange = this.onChange.bind(this); + this.getTypingText = this.getTypingText.bind(this); + this.componentWillReceiveProps = this.componentWillReceiveProps.bind(this); + this.typingUsers = {}; this.state = { text: '' }; @@ -27,7 +27,7 @@ export default class MsgTyping extends React.Component { componentWillReceiveProps(newProps) { if (this.props.channelId !== newProps.channelId) { - this.setState({text: ''}); + this.setState({text: this.getTypingText()}); } } @@ -36,27 +36,44 @@ export default class MsgTyping extends React.Component { } onChange(msg) { + let username = 'Someone'; if (msg.action === SocketEvents.TYPING && this.props.channelId === msg.channel_id && this.props.parentId === msg.props.parent_id) { - this.lastTime = new Date().getTime(); - - var username = 'Someone'; if (UserStore.hasProfile(msg.user_id)) { username = UserStore.getProfile(msg.user_id).username; } - this.setState({text: username + ' is typing...'}); - - if (!this.timer) { - this.timer = setInterval(function myTimer() { - if ((new Date().getTime() - this.lastTime) > 8000) { - this.setState({text: ''}); - } - }.bind(this), 3000); + if (this.typingUsers[username]) { + clearTimeout(this.typingUsers[username]); } + + this.typingUsers[username] = setTimeout(function myTimer(user) { + delete this.typingUsers[user]; + this.setState({text: this.getTypingText()}); + }.bind(this, username), Constants.UPDATE_TYPING_MS); + + this.setState({text: this.getTypingText()}); } else if (msg.action === SocketEvents.POSTED && msg.channel_id === this.props.channelId) { - this.setState({text: ''}); + if (UserStore.hasProfile(msg.user_id)) { + username = UserStore.getProfile(msg.user_id).username; + } + clearTimeout(this.typingUsers[username]); + delete this.typingUsers[username]; + this.setState({text: this.getTypingText()}); + } + } + + getTypingText() { + let users = Object.keys(this.typingUsers); + switch (users.length) { + case 0: + return ''; + case 1: + return users[0] + ' is typing...'; + default: + const last = users.pop(); + return users.join(', ') + ' and ' + last + ' are typing...'; } } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index c20d84f40..cda04bf04 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -132,6 +132,7 @@ module.exports = { OFFLINE_ICON_SVG: "", MENU_ICON: " ", COMMENT_ICON: " ", + UPDATE_TYPING_MS: 5000, THEMES: { default: { type: 'Mattermost', -- cgit v1.2.3-1-g7c22 From b7aa4220d575d446830a0b0c9a3f753214272422 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Mon, 26 Oct 2015 02:24:46 +0100 Subject: use constant value to check if to send a typing event for comments --- web/react/components/create_comment.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 435c7d542..18936e808 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -147,7 +147,7 @@ export default class CreateComment extends React.Component { } const t = Date.now(); - if ((t - this.lastTime) > 5000) { + if ((t - this.lastTime) > Constants.UPDATE_TYPING_MS) { SocketStore.sendMessage({channel_id: this.props.channelId, action: 'typing', props: {parent_id: this.props.rootId}}); this.lastTime = t; } -- cgit v1.2.3-1-g7c22 From 9a4d648c9b9a6c78cd2a171b665bc5aa9ade3634 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Mon, 26 Oct 2015 17:44:29 +0100 Subject: rename getTypingText to updateTypingText and set component's state in it --- web/react/components/msg_typing.jsx | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'web') diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx index 42ae4bcc4..ccf8a2445 100644 --- a/web/react/components/msg_typing.jsx +++ b/web/react/components/msg_typing.jsx @@ -12,7 +12,7 @@ export default class MsgTyping extends React.Component { super(props); this.onChange = this.onChange.bind(this); - this.getTypingText = this.getTypingText.bind(this); + this.updateTypingText = this.updateTypingText.bind(this); this.componentWillReceiveProps = this.componentWillReceiveProps.bind(this); this.typingUsers = {}; @@ -27,7 +27,7 @@ export default class MsgTyping extends React.Component { componentWillReceiveProps(newProps) { if (this.props.channelId !== newProps.channelId) { - this.setState({text: this.getTypingText()}); + this.updateTypingText(); } } @@ -50,31 +50,37 @@ export default class MsgTyping extends React.Component { this.typingUsers[username] = setTimeout(function myTimer(user) { delete this.typingUsers[user]; - this.setState({text: this.getTypingText()}); + this.updateTypingText(); }.bind(this, username), Constants.UPDATE_TYPING_MS); - this.setState({text: this.getTypingText()}); + this.updateTypingText(); } else if (msg.action === SocketEvents.POSTED && msg.channel_id === this.props.channelId) { if (UserStore.hasProfile(msg.user_id)) { username = UserStore.getProfile(msg.user_id).username; } clearTimeout(this.typingUsers[username]); delete this.typingUsers[username]; - this.setState({text: this.getTypingText()}); + this.updateTypingText(); } } - getTypingText() { - let users = Object.keys(this.typingUsers); + updateTypingText() { + const users = Object.keys(this.typingUsers); + let text = ''; switch (users.length) { case 0: - return ''; + text = ''; + break; case 1: - return users[0] + ' is typing...'; + text = users[0] + ' is typing...'; + break; default: const last = users.pop(); - return users.join(', ') + ' and ' + last + ' are typing...'; + text = users.join(', ') + ' and ' + last + ' are typing...'; + break; } + + this.setState({text}); } render() { -- cgit v1.2.3-1-g7c22 From a9ec7215ade2b64f2d5164a12b47d92ed8981233 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sun, 25 Oct 2015 15:12:36 +0100 Subject: PLT-642: Browser tab alerts --- web/react/components/sidebar.jsx | 61 ++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 12 deletions(-) (limited to 'web') diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index ed2c84057..0d3577f2d 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -40,6 +40,9 @@ export default class Sidebar extends React.Component { this.hideMoreDirectChannelsModal = this.hideMoreDirectChannelsModal.bind(this); this.createChannelElement = this.createChannelElement.bind(this); + this.updateTitle = this.updateTitle.bind(this); + this.setUnreadCount = this.setUnreadCount.bind(this); + this.getUnreadCount = this.getUnreadCount.bind(this); this.isLeaving = new Map(); @@ -49,8 +52,44 @@ export default class Sidebar extends React.Component { state.loadingDMChannel = -1; state.windowWidth = Utils.windowWidth(); + this.unreadCount = this.setUnreadCount(); this.state = state; } + setUnreadCount() { + const channels = ChannelStore.getAll(); + const members = ChannelStore.getAllMembers(); + const unreadCount = {}; + + channels.forEach((ch) => { + const chMember = members[ch.id]; + let chMentionCount = chMember.mention_count; + let chUnreadCount = ch.total_msg_count - chMember.msg_count - chMentionCount; + + if (ch.type === 'D') { + chMentionCount = chUnreadCount; + chUnreadCount = 0; + } + + unreadCount[ch.id] = {msgs: chUnreadCount, mentions: chMentionCount}; + }); + + return unreadCount; + } + getUnreadCount(channelId) { + let mentions = 0; + let msgs = 0; + + if (channelId) { + return this.unreadCount[channelId] ? this.unreadCount[channelId] : {msgs, mentions}; + } + + Object.keys(this.unreadCount).forEach((chId) => { + msgs += this.unreadCount[chId].msgs; + mentions += this.unreadCount[chId].mentions; + }); + + return {msgs, mentions}; + } getStateFromStores() { const members = ChannelStore.getAllMembers(); var teamMemberMap = UserStore.getActiveOnlyProfiles(); @@ -192,7 +231,10 @@ export default class Sidebar extends React.Component { currentChannelName = Utils.getDirectTeammate(channel.id).username; } - document.title = currentChannelName + ' - ' + this.props.teamDisplayName + ' ' + currentSiteName; + const unread = this.getUnreadCount(); + const mentionTitle = unread.mentions > 0 ? '(' + unread.mentions + ') ' : ''; + const unreadTitle = unread.msgs > 0 ? '* ' : ''; + document.title = mentionTitle + unreadTitle + currentChannelName + ' - ' + this.props.teamDisplayName + ' ' + currentSiteName; } } onScroll() { @@ -273,6 +315,7 @@ export default class Sidebar extends React.Component { var members = this.state.members; var activeId = this.state.activeId; var channelMember = members[channel.id]; + var unreadCount = this.getUnreadCount(channel.id); var msgCount; var linkClass = ''; @@ -284,7 +327,7 @@ export default class Sidebar extends React.Component { var unread = false; if (channelMember) { - msgCount = channel.total_msg_count - channelMember.msg_count; + msgCount = unreadCount.msgs + unreadCount.mentions; unread = (msgCount > 0 && channelMember.notify_props.mark_unread !== 'mention') || channelMember.mention_count > 0; } @@ -301,16 +344,8 @@ export default class Sidebar extends React.Component { var badge = null; if (channelMember) { - if (channel.type === 'D') { - // direct message channels show badges for any number of unread posts - msgCount = channel.total_msg_count - channelMember.msg_count; - if (msgCount > 0) { - badge = {msgCount}; - this.badgesActive = true; - } - } else if (channelMember.mention_count > 0) { - // public and private channels only show badges for mentions - badge = {channelMember.mention_count}; + if (unreadCount.mentions) { + badge = {unreadCount.mentions}; this.badgesActive = true; } } else if (this.state.loadingDMChannel === index && channel.type === 'D') { @@ -434,6 +469,8 @@ export default class Sidebar extends React.Component { render() { this.badgesActive = false; + this.unreadCount = this.setUnreadCount(); + // keep track of the first and last unread channels so we can use them to set the unread indicators this.firstUnreadChannel = null; this.lastUnreadChannel = null; -- cgit v1.2.3-1-g7c22 From a88fe8ea1a9cc020cc0d1dc442d46fc0bed90cc9 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Mon, 26 Oct 2015 17:53:21 +0100 Subject: rename methods and varibles to a more meaningful name --- web/react/components/sidebar.jsx | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'web') diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 0d3577f2d..5cb6d168b 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -41,7 +41,7 @@ export default class Sidebar extends React.Component { this.createChannelElement = this.createChannelElement.bind(this); this.updateTitle = this.updateTitle.bind(this); - this.setUnreadCount = this.setUnreadCount.bind(this); + this.setUnreadCountPerChannel = this.setUnreadCountPerChannel.bind(this); this.getUnreadCount = this.getUnreadCount.bind(this); this.isLeaving = new Map(); @@ -51,14 +51,15 @@ export default class Sidebar extends React.Component { state.showDirectChannelsModal = false; state.loadingDMChannel = -1; state.windowWidth = Utils.windowWidth(); - - this.unreadCount = this.setUnreadCount(); this.state = state; + + this.unreadCountPerChannel = {}; + this.setUnreadCountPerChannel(); } - setUnreadCount() { + setUnreadCountPerChannel() { const channels = ChannelStore.getAll(); const members = ChannelStore.getAllMembers(); - const unreadCount = {}; + const channelUnreadCounts = {}; channels.forEach((ch) => { const chMember = members[ch.id]; @@ -70,22 +71,22 @@ export default class Sidebar extends React.Component { chUnreadCount = 0; } - unreadCount[ch.id] = {msgs: chUnreadCount, mentions: chMentionCount}; + channelUnreadCounts[ch.id] = {msgs: chUnreadCount, mentions: chMentionCount}; }); - return unreadCount; + this.unreadCountPerChannel = channelUnreadCounts; } getUnreadCount(channelId) { let mentions = 0; let msgs = 0; if (channelId) { - return this.unreadCount[channelId] ? this.unreadCount[channelId] : {msgs, mentions}; + return this.unreadCountPerChannel[channelId] ? this.unreadCountPerChannel[channelId] : {msgs, mentions}; } - Object.keys(this.unreadCount).forEach((chId) => { - msgs += this.unreadCount[chId].msgs; - mentions += this.unreadCount[chId].mentions; + Object.keys(this.unreadCountPerChannel).forEach((chId) => { + msgs += this.unreadCountPerChannel[chId].msgs; + mentions += this.unreadCountPerChannel[chId].mentions; }); return {msgs, mentions}; @@ -469,7 +470,7 @@ export default class Sidebar extends React.Component { render() { this.badgesActive = false; - this.unreadCount = this.setUnreadCount(); + this.setUnreadCountPerChannel(); // keep track of the first and last unread channels so we can use them to set the unread indicators this.firstUnreadChannel = null; -- cgit v1.2.3-1-g7c22 From 6d957a26d884cd2ff200a4d14b035bc03415cab6 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Mon, 26 Oct 2015 10:42:54 -0700 Subject: PLT-564 removing auto-hide from error bar --- web/react/components/error_bar.jsx | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'web') diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx index 6311d9460..f098384aa 100644 --- a/web/react/components/error_bar.jsx +++ b/web/react/components/error_bar.jsx @@ -9,12 +9,8 @@ export default class ErrorBar extends React.Component { this.onErrorChange = this.onErrorChange.bind(this); this.handleClose = this.handleClose.bind(this); - this.prevTimer = null; this.state = ErrorStore.getLastError(); - if (this.isValidError(this.state)) { - this.prevTimer = setTimeout(this.handleClose, 10000); - } } isValidError(s) { @@ -56,16 +52,8 @@ export default class ErrorBar extends React.Component { onErrorChange() { var newState = ErrorStore.getLastError(); - if (this.prevTimer != null) { - clearInterval(this.prevTimer); - this.prevTimer = null; - } - if (newState) { this.setState(newState); - if (!this.isConnectionError(newState)) { - this.prevTimer = setTimeout(this.handleClose, 10000); - } } else { this.setState({message: null}); } -- cgit v1.2.3-1-g7c22 From ff6e91f51d844a4703d3c4648b8b6bffe0cdabbc Mon Sep 17 00:00:00 2001 From: hmhealey Date: Mon, 26 Oct 2015 14:15:07 -0400 Subject: Fixed file upload and profile picture upload error display to work for serverside errors --- web/react/components/create_post.jsx | 10 ++++++++-- web/react/components/user_settings/user_settings_general.jsx | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'web') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 055be112d..0867bfdf2 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -253,8 +253,14 @@ export default class CreatePost extends React.Component { this.setState({uploadsInProgress: draft.uploadsInProgress, previews: draft.previews}); } handleUploadError(err, clientId) { + let message = err; + if (message && typeof message !== 'string') { + // err is an AppError from the server + message = err.message; + } + if (clientId === -1) { - this.setState({serverError: err}); + this.setState({serverError: message}); } else { const draft = PostStore.getDraft(this.state.channelId); @@ -265,7 +271,7 @@ export default class CreatePost extends React.Component { PostStore.storeDraft(this.state.channelId, draft); - this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: err}); + this.setState({uploadsInProgress: draft.uploadsInProgress, serverError: message}); } } handleTextDrop(text) { diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx index 70e559c30..1c8ce3c79 100644 --- a/web/react/components/user_settings/user_settings_general.jsx +++ b/web/react/components/user_settings/user_settings_general.jsx @@ -171,7 +171,7 @@ export default class UserSettingsGeneralTab extends React.Component { }.bind(this), function imageUploadFailure(err) { var state = this.setupInitialState(this.props); - state.serverError = err; + state.serverError = err.message; this.setState(state); }.bind(this) ); -- cgit v1.2.3-1-g7c22 From c94388042b684ae3c552f97505fdb67903db20ba Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 26 Oct 2015 15:10:17 -0400 Subject: Parse incoming webhook requsets into model instead of string map --- web/web.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'web') diff --git a/web/web.go b/web/web.go index 5f290ec99..bffe4858e 100644 --- a/web/web.go +++ b/web/web.go @@ -969,20 +969,20 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) { r.ParseForm() - var props map[string]string + var parsedRequest *model.IncomingWebhookRequest if r.Header.Get("Content-Type") == "application/json" { - props = model.MapFromJson(r.Body) + parsedRequest = model.IncomingWebhookRequestFromJson(r.Body) } else { - props = model.MapFromJson(strings.NewReader(r.FormValue("payload"))) + parsedRequest = model.IncomingWebhookRequestFromJson(strings.NewReader(r.FormValue("payload"))) } - text := props["text"] + text := parsedRequest.Text if len(text) == 0 { c.Err = model.NewAppError("incomingWebhook", "No text specified", "") return } - channelName := props["channel"] + channelName := parsedRequest.ChannelName var hook *model.IncomingWebhook if result := <-hchan; result.Err != nil { @@ -1012,8 +1012,8 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) { cchan = api.Srv.Store.Channel().Get(hook.ChannelId) } - overrideUsername := props["username"] - overrideIconUrl := props["icon_url"] + overrideUsername := parsedRequest.Username + overrideIconUrl := parsedRequest.IconURL if result := <-cchan; result.Err != nil { c.Err = model.NewAppError("incomingWebhook", "Couldn't find the channel", "err="+result.Err.Message) -- cgit v1.2.3-1-g7c22