From 1e4510be3bb13c00583e14dbbcf99ade7a76c9bd Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sat, 24 Oct 2015 03:25:26 +0200 Subject: [PATCH 1/7] 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 diff --git a/web/react/package.json b/web/react/package.json index e6a6623752..9af6f58800 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 43d81d322a..e65b7337f8 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -318,5 +318,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 e34f3d00a3..5be01d0554 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; + class MattermostInlineLexer extends marked.InlineLexer { constructor(links, options) { super(links, options); @@ -51,6 +77,41 @@ 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 11816efd98..7709e17f32 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -473,6 +473,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 0000000000..c774cf3974 --- /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 041831ed7a..837cfb133b 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -24,6 +24,7 @@ + From 3e7ffafa97c61ddd2f479022e4d63bdf745fcac3 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sat, 24 Oct 2015 15:50:20 +0200 Subject: [PATCH 2/7] code style theme chooser --- .../user_settings/code_theme_chooser.jsx | 55 ++++++++++++++++++ .../user_settings_appearance.jsx | 23 +++++++- web/react/utils/constants.jsx | 7 +++ web/react/utils/utils.jsx | 26 +++++++++ .../images/themes/code_themes/github.png | Bin 0 -> 9648 bytes .../images/themes/code_themes/monokai.png | Bin 0 -> 9303 bytes .../themes/code_themes/solarized_dark.png | Bin 0 -> 8172 bytes .../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 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 0000000000..eef4b24bae --- /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 8c62a189dd..e94894a1d9 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 0000000000000000000000000000000000000000..d0538d6c0bf154f96fd298f554c2eb62c39fca91 GIT binary patch literal 9648 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 mzg0tO5H;d4sraE_@D0000 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 - + From aeb0a08d077d35e46451142be1e06a4718a2d6bb Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sat, 24 Oct 2015 16:03:12 +0200 Subject: [PATCH 3/7] fix markup if code is of unknown language --- web/react/utils/markdown.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index 5be01d0554..ca26f77011 100644 --- a/web/react/utils/markdown.jsx +++ b/web/react/utils/markdown.jsx @@ -104,7 +104,7 @@ 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); From 742424228414793e6aaa06ce8a9de182cdfb2957 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sat, 24 Oct 2015 21:55:16 +0200 Subject: [PATCH 4/7] 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(-) diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 2c9959d4a2..1593f67064 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -347,6 +347,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 ca26f77011..b5d239eb5d 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; @@ -99,6 +101,8 @@ 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) { From bad01d40a2c9354573bfe1c4b9d33a05ffbe9b0f Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Wed, 28 Oct 2015 19:36:34 +0100 Subject: [PATCH 5/7] escape user input --- web/react/utils/markdown.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index b5d239eb5d..84690150ad 100644 --- a/web/react/utils/markdown.jsx +++ b/web/react/utils/markdown.jsx @@ -108,13 +108,13 @@ class MattermostMarkdownRenderer extends marked.Renderer { code(code, language) { if (!language || highlightJs.listLanguages().indexOf(language) < 0) { let parsed = super.code(code, language); - return '' + $(parsed).text() + ''; + return '
' + TextFormatting.sanitizeHtml($(parsed).text()) + '
'; } let parsed = highlightJs.highlight(language, code); return '
' + '' + HighlightedLanguages[language] + '' + - '' + parsed.value + '' + + '' + parsed.value + '' + '
'; } From 3c8fd9942557aee8a1bc06b2b973bb1e3f9519d0 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Wed, 28 Oct 2015 19:39:10 +0100 Subject: [PATCH 6/7] use XML syntax highlighting if provided language is html --- web/react/utils/markdown.jsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index 84690150ad..179416ea03 100644 --- a/web/react/utils/markdown.jsx +++ b/web/react/utils/markdown.jsx @@ -106,14 +106,20 @@ class MattermostMarkdownRenderer extends marked.Renderer { } code(code, language) { - if (!language || highlightJs.listLanguages().indexOf(language) < 0) { - let parsed = super.code(code, language); + let usedLanguage = language; + + if (String(usedLanguage).toLocaleLowerCase() === 'html') { + usedLanguage = 'xml'; + } + + if (!usedLanguage || highlightJs.listLanguages().indexOf(usedLanguage) < 0) { + let parsed = super.code(code, usedLanguage); return '
' + TextFormatting.sanitizeHtml($(parsed).text()) + '
'; } - let parsed = highlightJs.highlight(language, code); + let parsed = highlightJs.highlight(usedLanguage, code); return '
' + - '' + HighlightedLanguages[language] + '' + + '' + HighlightedLanguages[usedLanguage] + '' + '' + parsed.value + '' + '
'; } From d630567c91d01d5b78be84ac1a5e638e4e72516c Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Wed, 28 Oct 2015 20:04:22 +0100 Subject: [PATCH 7/7] allow code theme to be shareable --- web/react/components/user_settings/custom_theme_chooser.jsx | 5 ++++- .../components/user_settings/user_settings_appearance.jsx | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx index 44b3f45448..095e5b6226 100644 --- a/web/react/components/user_settings/custom_theme_chooser.jsx +++ b/web/react/components/user_settings/custom_theme_chooser.jsx @@ -40,11 +40,12 @@ export default class CustomThemeChooser extends React.Component { const theme = {type: 'custom'}; let index = 0; Constants.THEME_ELEMENTS.forEach((element) => { - if (index < colors.length) { + if (index < colors.length - 1) { theme[element.id] = colors[index]; } index++; }); + theme.codeTheme = colors[colors.length - 1]; this.props.updateTheme(theme); } @@ -78,6 +79,8 @@ export default class CustomThemeChooser extends React.Component { colors += theme[element.id] + ','; }); + colors += theme.codeTheme; + const pasteBox = (