From 1e4510be3bb13c00583e14dbbcf99ade7a76c9bd 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 43d81d322..e65b7337f 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 e34f3d00a..5be01d055 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 11816efd9..7709e17f3 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 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 3e7ffafa97c61ddd2f479022e4d63bdf745fcac3 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 aeb0a08d077d35e46451142be1e06a4718a2d6bb 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 5be01d055..ca26f7701 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); -- cgit v1.2.3-1-g7c22 From 742424228414793e6aaa06ce8a9de182cdfb2957 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 2c9959d4a..1593f6706 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 ca26f7701..b5d239eb5 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) { -- cgit v1.2.3-1-g7c22 From bad01d40a2c9354573bfe1c4b9d33a05ffbe9b0f Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Wed, 28 Oct 2015 19:36:34 +0100 Subject: escape user input --- web/react/utils/markdown.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'web') diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index b5d239eb5..84690150a 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 + '' + '
'; } -- cgit v1.2.3-1-g7c22 From 3c8fd9942557aee8a1bc06b2b973bb1e3f9519d0 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Wed, 28 Oct 2015 19:39:10 +0100 Subject: use XML syntax highlighting if provided language is html --- web/react/utils/markdown.jsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'web') diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx index 84690150a..179416ea0 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 + '' + '
'; } -- cgit v1.2.3-1-g7c22 From d630567c91d01d5b78be84ac1a5e638e4e72516c Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Wed, 28 Oct 2015 20:04:22 +0100 Subject: allow code theme to be shareable --- web/react/components/user_settings/custom_theme_chooser.jsx | 5 ++++- web/react/components/user_settings/user_settings_appearance.jsx | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'web') diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx index 44b3f4544..095e5b622 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 = (