diff --git a/package.json b/package.json index ec56b30b5ab090d81fffb6293f701e1b7db49c72..0d65d3eedc796ba69fbd817b07d01258de3eccb4 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "disk-stat": "1.0.4", "extract-text-webpack-plugin": "^v4.0.0-beta.0", "foundation-sites": "git+https://gitlab.syncad.com/hive/foundation-sites.git#544fb8a26efcf45e9ba9ccaa77b04d0ae35b9722", + "highlight.js": "^11.6.0", "hive-auth-client": "^0.1.5", "hive-uri": "^0.2.3", "hivesigner": "3.2.7", diff --git a/src/app/assets/stylesheets/_themes.scss b/src/app/assets/stylesheets/_themes.scss index a192a3010da83f057ab85e61c92494b2eddfa675..63ad32c0ed4a43416b6ce0a041d60ab4c92672d4 100755 --- a/src/app/assets/stylesheets/_themes.scss +++ b/src/app/assets/stylesheets/_themes.scss @@ -41,6 +41,8 @@ $themes: ( modalBackgroundColor: $color-background-almost-white, modalTextColorPrimary: $color-text-dark, commentBodyHighlightBackgroundColor: $color-background-almost-white-darker, + htmlhtmlCodeColor: #333333, + htmlhtmlCodeBackgroundColor: #f4f4f4, ), light: ( colorAccent: $color-hive-red, @@ -85,6 +87,8 @@ $themes: ( modalBackgroundColor: $color-white, modalTextColorPrimary: $color-text-dark, commentBodyHighlightBackgroundColor: $color-background-almost-white-darker, + htmlCodeColor: #333333, + htmlCodeBackgroundColor: #f4f4f4, ), dark: ( colorAccent: $color-hive-red, @@ -130,6 +134,8 @@ $themes: ( modalBackgroundColor: $color-background-dark, modalTextColorPrimary: $color-text-white, commentBodyHighlightBackgroundColor: $color-background-dark-lighter, + htmlCodeColor: #c5c8c6, + htmlCodeBackgroundColor: #1d1f21, ), ); diff --git a/src/app/assets/stylesheets/markdown.scss b/src/app/assets/stylesheets/markdown.scss index d38ed5c9a3d19dd2d299a270e4fb67ed23df5e76..0dc80ec6d513e4aa0777f0e237610bc164a712b5 100644 --- a/src/app/assets/stylesheets/markdown.scss +++ b/src/app/assets/stylesheets/markdown.scss @@ -59,16 +59,22 @@ code { padding: 0.2rem; - font-size: 85%; + font-size: 0.90rem; + line-height: 1.2rem; border-radius: 3px; border: none; - background-color: #F4F4F4; font-weight: inherit; overflow: scroll; + @include themify($themes) { + color: themed('htmlCodeColor'); + background-color: themed('htmlCodeBackgroundColor'); + } } pre > code { display: block; + overflow-y: hidden; + overflow-x: auto; } strong { diff --git a/src/app/components/all.scss b/src/app/components/all.scss index fbc1cba2298a9254d2f702fc4237d739bc1e3256..3154e7a22c91951fee4a0a56c804e5e4374d32ac 100644 --- a/src/app/components/all.scss +++ b/src/app/components/all.scss @@ -6,6 +6,7 @@ @import "./cards/MarkdownViewer"; @import "./cards/PostSummary"; @import "./cards/PostFull"; +@import "./cards/PostFull-code"; @import "./cards/PostsList"; @import "./cards/NotificationsList"; diff --git a/src/app/components/cards/MarkdownViewer.jsx b/src/app/components/cards/MarkdownViewer.jsx index 3267220c0ce48819d2429acfba6e7a5bf7f9b9de..9798678abd4f839bb237b4cbfee3507e9d39d618 100644 --- a/src/app/components/cards/MarkdownViewer.jsx +++ b/src/app/components/cards/MarkdownViewer.jsx @@ -5,7 +5,7 @@ import { connect } from 'react-redux'; import Remarkable from 'remarkable'; import sanitizeConfig, { noImageText } from 'app/utils/SanitizeConfig'; import sanitize from 'sanitize-html'; -import HtmlReady from 'shared/HtmlReady'; +import HtmlReady, { highlightCodes } from 'shared/HtmlReady'; import tt from 'counterpart'; import { generateMd as EmbeddedPlayerGenerateMd } from 'app/components/elements/EmbeddedPlayers'; @@ -122,6 +122,12 @@ class MarkdownViewer extends Component { return <div />; } + // Needs to be done here so that the tags added by HighlightJS won't be filtered of by the sanitizer + const higlightedText = highlightCodes(cleanText).html; + if (higlightedText) { + cleanText = higlightedText; + } + const noImageActive = cleanText.indexOf(noImageText) !== -1; // In addition to inserting the youtube component, this allows diff --git a/src/app/components/cards/PostFull-code.scss b/src/app/components/cards/PostFull-code.scss new file mode 100644 index 0000000000000000000000000000000000000000..22a2fefe159353fa14cfcf71268bb63dd0686523 --- /dev/null +++ b/src/app/components/cards/PostFull-code.scss @@ -0,0 +1,157 @@ +.theme-light { + /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + + /* Tomorrow Comment */ + .hljs-comment, + .hljs-quote { + color: #8e908c; + } + + /* Tomorrow Red */ + .hljs-variable, + .hljs-template-variable, + .hljs-tag, + .hljs-name, + .hljs-selector-id, + .hljs-selector-class, + .hljs-regexp, + .hljs-deletion { + color: #c82829; + } + + /* Tomorrow Orange */ + .hljs-number, + .hljs-built_in, + .hljs-builtin-name, + .hljs-literal, + .hljs-type, + .hljs-params, + .hljs-meta, + .hljs-link { + color: #f5871f; + } + + /* Tomorrow Yellow */ + .hljs-attribute { + color: #eab700; + } + + /* Tomorrow Green */ + .hljs-string, + .hljs-symbol, + .hljs-bullet, + .hljs-addition { + color: #718c00; + } + + /* Tomorrow Blue */ + .hljs-title, + .hljs-section { + color: #4271ae; + } + + /* Tomorrow Purple */ + .hljs-keyword, + .hljs-selector-tag { + color: #8959a8; + } + + .hljs { + display: block; + overflow-x: auto; + background: white; + color: #4d4d4c; + padding: 0.5em; + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: bold; + } + +} + +.theme-dark { + /* Tomorrow Night Theme */ + /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + /* Original theme - https://github.com/chriskempson/tomorrow-theme */ + /* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ + + /* Tomorrow Comment */ + .hljs-comment, + .hljs-quote { + color: #969896; + } + + /* Tomorrow Red */ + .hljs-variable, + .hljs-template-variable, + .hljs-tag, + .hljs-name, + .hljs-selector-id, + .hljs-selector-class, + .hljs-regexp, + .hljs-deletion { + color: #cc6666; + } + + /* Tomorrow Orange */ + .hljs-number, + .hljs-built_in, + .hljs-builtin-name, + .hljs-literal, + .hljs-type, + .hljs-params, + .hljs-meta, + .hljs-link { + color: #de935f; + } + + /* Tomorrow Yellow */ + .hljs-attribute { + color: #f0c674; + } + + /* Tomorrow Green */ + .hljs-string, + .hljs-symbol, + .hljs-bullet, + .hljs-addition { + color: #b5bd68; + } + + /* Tomorrow Blue */ + .hljs-title, + .hljs-section { + color: #81a2be; + } + + /* Tomorrow Purple */ + .hljs-keyword, + .hljs-selector-tag { + color: #b294bb; + } + + .hljs { + display: block; + overflow-x: auto; + background: #1d1f21; + color: #c5c8c6; + padding: 0.5em; + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: bold; + } + + .hljs-tag .hljs-attr, .hljs-tag .hljs-name { + color: #bbbbbb; + } +} diff --git a/src/app/components/cards/PostFull.jsx b/src/app/components/cards/PostFull.jsx index 409ff1546d273ff1d961f84ea190045f2d1433b4..830dd8d513c26c402e2536de8f4a6b4b24ff1d55 100644 --- a/src/app/components/cards/PostFull.jsx +++ b/src/app/components/cards/PostFull.jsx @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import { SRLWrapper } from "simple-react-lightbox"; import { Link } from 'react-router'; import classnames from 'classnames'; +import hljs from 'highlight.js/lib/common'; +import 'highlight.js/styles/default.css'; import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper'; import Icon from 'app/components/elements/Icon'; import { connect } from 'react-redux'; @@ -128,6 +130,10 @@ class PostFull extends React.Component { } } + if (process.env.BROWSER) { + hljs.highlightAll(); + } + this.state = { formId: _formId, PostFullReplyEditor: ReplyEditor(_formId + '-reply'), diff --git a/src/shared/HtmlReady.js b/src/shared/HtmlReady.js index d6caf91dbea4c06efd4eebe77d2bb8a73d79e5d1..df8f1e7c11b66da640635d525b0f8aa54c7c0642 100644 --- a/src/shared/HtmlReady.js +++ b/src/shared/HtmlReady.js @@ -1,5 +1,6 @@ import xmldom from 'xmldom'; import tt from 'counterpart'; +import hljs from 'highlight.js/lib/common'; import linksRe, { any as linksAny } from 'app/utils/Links'; import { validate_account_name } from 'app/utils/ChainValidation'; import { proxifyImageUrl, getDoubleSize } from 'app/utils/ProxifyUrl'; @@ -130,6 +131,34 @@ function traverse(node, state, depth = 0) { }); } +function traverseForCodeHighlight(node, depth = 0) { + if (!node || !node.childNodes) return; + Array.from(node.childNodes).forEach((child) => { + const tag = child.tagName ? child.tagName.toLowerCase() : null; + if (tag === 'code' && child.textContent.match(/\n/)) { + const highlightedContent = hljs.highlightAuto(child.textContent).value; + + child.parentNode.replaceChild(DOMParser.parseFromString(`<code>${highlightedContent}</code>`), child); + } + + traverseForCodeHighlight(child, depth + 1); + }); +} + +export function highlightCodes(html) { + if (html.match(/<code>.*?<\/code>/s)) { + const doc = DOMParser.parseFromString(html, 'text/html'); + traverseForCodeHighlight(doc); + return { + html: doc ? XMLSerializer.serializeToString(doc) : '', + }; + } + + return { + html: null, + }; +} + function link(state, child) { const url = child.getAttribute('href'); if (url) { diff --git a/yarn.lock b/yarn.lock index c3397729e096d983fb71f644769cb8705479fdce..8d14745166ce39204229cf220c0dc189353c7772 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9737,6 +9737,11 @@ highlight.js@^10.4.1, highlight.js@~10.7.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== +highlight.js@^11.6.0: + version "11.6.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.6.0.tgz#a50e9da05763f1bb0c1322c8f4f755242cff3f5a" + integrity sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw== + history@^3.0.0: version "3.3.0" resolved "https://registry.yarnpkg.com/history/-/history-3.3.0.tgz#fcedcce8f12975371545d735461033579a6dae9c"