diff --git a/THIRD-PARTY-LICENSES b/THIRD-PARTY-LICENSES index 63b0959b..02b902d0 100644 --- a/THIRD-PARTY-LICENSES +++ b/THIRD-PARTY-LICENSES @@ -1,27 +1,34 @@ -** prismjs; version 1.29.0 -- https://github.com/PrismJS/prism/ -Copyright (c) 2012 Lea Verou +** highlight.js; version 11.11.0 -- https://github.com/highlightjs/highlight.js/ -MIT LICENSE +BSD 3-Clause License -Copyright (c) 2012 Lea Verou +Copyright (c) 2006, Ivan Sagalaev. +All rights reserved. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------ @@ -68,7 +75,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------ -** unescape-html; version 1.1.0 -- https://github.com/ForbesLindesay/ +** unescape-html; version 1.1.0 -- https://github.com/ForbesLindesay/unescape-html MIT LICENSE diff --git a/package-lock.json b/package-lock.json index 6e9f1a61..85819314 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "license": "Apache License 2.0", "dependencies": { "escape-html": "^1.0.3", + "highlight.js": "^11.11.0", "just-clone": "^6.2.0", "marked": "^14.1.0", - "prismjs": "1.29.0", "sanitize-html": "^2.12.1", "unescape-html": "^1.1.0" }, @@ -26,7 +26,6 @@ "@types/jest": "^29.5.5", "@types/json-schema": "7.0.7", "@types/node": "17.0.29", - "@types/prismjs": "^1.26.2", "@types/sanitize-html": "^2.11.0", "@typescript-eslint/eslint-plugin": "^5.34.0", "@typescript-eslint/parser": "^5.62.0", @@ -61,9 +60,9 @@ }, "peerDependencies": { "escape-html": "^1.0.3", + "highlight.js": "^11.11.0", "just-clone": "^6.2.0", - "marked": "^12.0.2", - "prismjs": "1.29.0", + "marked": "^14.1.0", "sanitize-html": "^2.12.1", "unescape-html": "^1.1.0" } @@ -1754,12 +1753,6 @@ "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==", "dev": true }, - "node_modules/@types/prismjs": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", - "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==", - "dev": true - }, "node_modules/@types/sanitize-html": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", @@ -3263,26 +3256,11 @@ "webpack": "^5.0.0" } }, - "node_modules/css-loader/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/css-loader/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -3290,12 +3268,6 @@ "node": ">=10" } }, - "node_modules/css-loader/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5165,6 +5137,14 @@ "node": ">= 0.4" } }, + "node_modules/highlight.js": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.0.tgz", + "integrity": "sha512-6ErL7JlGu2CNFHyRQEuDogOyGPNiqcuWdt4iSSFUPyferNTGlNTPFqeV36Y/XwA4V/TJ8l0sxp6FTnxud/mf8g==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -7799,10 +7779,9 @@ } }, "node_modules/marked": { - "version": "14.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.3.tgz", - "integrity": "sha512-ZibJqTULGlt9g5k4VMARAktMAjXoVnnr+Y3aCqW1oDftcV4BA3UmrBifzXoZyenHRk75csiPu9iwsTj4VNBT0g==", - "license": "MIT", + "version": "14.1.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.4.tgz", + "integrity": "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==", "bin": { "marked": "bin/marked.js" }, @@ -8430,9 +8409,9 @@ } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { "node": "^10 || ^12 || >= 14" @@ -8442,13 +8421,13 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", - "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { @@ -8459,12 +8438,12 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", - "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" @@ -8489,9 +8468,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -8569,14 +8548,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", - "engines": { - "node": ">=6" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", diff --git a/package.json b/package.json index 2b62cdf3..8879827b 100644 --- a/package.json +++ b/package.json @@ -36,17 +36,17 @@ }, "dependencies": { "escape-html": "^1.0.3", + "highlight.js": "^11.11.0", "just-clone": "^6.2.0", "marked": "^14.1.0", - "prismjs": "1.29.0", "sanitize-html": "^2.12.1", "unescape-html": "^1.1.0" }, "peerDependencies": { "escape-html": "^1.0.3", + "highlight.js": "^11.11.0", "just-clone": "^6.2.0", - "marked": "^12.0.2", - "prismjs": "1.29.0", + "marked": "^14.1.0", "sanitize-html": "^2.12.1", "unescape-html": "^1.1.0" }, @@ -59,7 +59,6 @@ "@types/jest": "^29.5.5", "@types/json-schema": "7.0.7", "@types/node": "17.0.29", - "@types/prismjs": "^1.26.2", "@types/sanitize-html": "^2.11.0", "@typescript-eslint/eslint-plugin": "^5.34.0", "@typescript-eslint/parser": "^5.62.0", diff --git a/src/components/__test__/syntax-highlighter.spec.ts b/src/components/__test__/syntax-highlighter.spec.ts index 68ef8834..c8fa0842 100644 --- a/src/components/__test__/syntax-highlighter.spec.ts +++ b/src/components/__test__/syntax-highlighter.spec.ts @@ -9,7 +9,7 @@ describe('syntax-highlighter', () => { }); expect(testSyntaxHighlighter.render.outerHTML.replace('\n', '')).toBe( - '
alert("hello");
' + '
alert("hello");
' ); }); diff --git a/src/components/syntax-highlighter.ts b/src/components/syntax-highlighter.ts index b71e9829..80deacbf 100644 --- a/src/components/syntax-highlighter.ts +++ b/src/components/syntax-highlighter.ts @@ -4,38 +4,6 @@ */ import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; -import { highlightElement } from 'prismjs'; - -import 'prismjs/components/prism-markup.min'; -import 'prismjs/components/prism-xml-doc.min'; -import 'prismjs/components/prism-css.min'; -import 'prismjs/components/prism-clike.min'; -import 'prismjs/components/prism-javascript.min'; -import 'prismjs/components/prism-typescript.min'; -import 'prismjs/components/prism-jsx.min'; -import 'prismjs/components/prism-diff.min'; -import 'prismjs/components/prism-tsx.min'; -import 'prismjs/components/prism-lua.min'; -import 'prismjs/components/prism-java.min'; -import 'prismjs/components/prism-json.min'; -import 'prismjs/components/prism-markdown.min'; -import 'prismjs/components/prism-mongodb.min'; -import 'prismjs/components/prism-c.min'; -import 'prismjs/components/prism-bash.min'; -import 'prismjs/components/prism-go.min'; -import 'prismjs/components/prism-csharp.min'; -import 'prismjs/components/prism-objectivec.min'; -import 'prismjs/components/prism-python.min'; -import 'prismjs/components/prism-regex.min'; -import 'prismjs/components/prism-swift.min'; -import 'prismjs/components/prism-scala.min'; -import 'prismjs/components/prism-scss.min'; -import 'prismjs/components/prism-less.min'; -import 'prismjs/components/prism-ruby.min'; -import 'prismjs/components/prism-rust.min'; -import 'prismjs/plugins/line-numbers/prism-line-numbers.js'; -import 'prismjs/plugins/keep-markup/prism-keep-markup.js'; -import 'prismjs/plugins/diff-highlight/prism-diff-highlight.min'; import { CodeBlockActions, @@ -47,43 +15,12 @@ import { Icon } from './icon'; import { cancelEvent } from '../helper/events'; import { highlightersWithTooltip } from './card/card-body'; import escapeHTML from 'escape-html'; -import '../styles/components/_syntax-highlighter.scss'; import { copyToClipboard } from '../helper/chat-item'; import testIds from '../helper/test-ids'; import unescapeHTML from 'unescape-html'; - -const langs = [ - 'markup', - 'xml', - 'css', - 'clike', - 'diff', - 'javascript', - 'typescript', - 'jsx', - 'tsx', - 'lua', - 'java', - 'json', - 'go', - 'markdown', - 'mongodb', - 'c', - 'bash', - 'csharp', - 'objectivec', - 'python', - 'regex', - 'swift', - 'scala', - 'scss', - 'less', - 'ruby', - 'rust', -]; - -const IMPORTED_LANGS = [ ...langs, ...(langs.map(lang => `diff-${lang}`)) ]; -const DEFAULT_LANG = 'clike'; +import hljs from 'highlight.js'; +import '../styles/components/_syntax-highlighter.scss'; +import { mergeHTMLPlugin } from '../helper/merge-html-plugin'; export interface SyntaxHighlighterProps { codeStringWithMarkup: string; @@ -97,6 +34,8 @@ export interface SyntaxHighlighterProps { onCodeBlockAction?: OnCodeBlockActionFunction; } +const DEFAULT_LANGUAGE = 'c'; + export class SyntaxHighlighter { private readonly props?: SyntaxHighlighterProps; private readonly codeBlockButtons: ExtendedHTMLElement[] = []; @@ -105,6 +44,9 @@ export class SyntaxHighlighter { constructor (props: SyntaxHighlighterProps) { this.props = props; + hljs.addPlugin(mergeHTMLPlugin); + hljs.configure({ ignoreUnescapedHTML: true }); + // To ensure we are not leaving anything unescaped before escaping i.e to prevent double escaping let escapedCodeBlock = escapeHTML(unescapeHTML(props.codeStringWithMarkup)); @@ -114,19 +56,30 @@ export class SyntaxHighlighter { .replace(new RegExp(escapeHTML(highlightersWithTooltip.start.markupEnd), 'g'), highlightersWithTooltip.start.markupEnd) .replace(new RegExp(escapeHTML(highlightersWithTooltip.end.markup), 'g'), highlightersWithTooltip.end.markup); + const codeElement = DomBuilder.getInstance().build({ + type: 'code', + classNames: [ + ...(props.language !== undefined ? [ `language-${props.language.replace('diff-', '')}` ] : [ (props.block ?? false) ? DEFAULT_LANGUAGE : 'language-plaintext' ]), + ...(props.showLineNumbers === true ? [ 'line-numbers' ] : []), + ], + innerHTML: escapedCodeBlock + }); + hljs.highlightElement(codeElement); + + // Overlay another code element for diffs, as highlight.js doesn't allow multiple language styles + const diffOverlay = DomBuilder.getInstance().build({ + type: 'code', + classNames: [ 'diff', 'language-diff' ], + innerHTML: escapedCodeBlock + }); + hljs.highlightElement(diffOverlay); + const preElement = DomBuilder.getInstance().build({ type: 'pre', testId: testIds.chatItem.syntaxHighlighter.codeBlock, - classNames: [ 'keep-markup', - `language-${props.language !== undefined && IMPORTED_LANGS.includes(props.language) ? props.language : DEFAULT_LANG}`, - ...(((props.language?.match('diff')) != null) ? [ 'diff-highlight' ] : []), - ...(props.showLineNumbers === true ? [ 'line-numbers' ] : []), - ], children: [ - { - type: 'code', - innerHTML: escapedCodeBlock, - } + codeElement, + ((props.language?.match('diff')) != null) ? diffOverlay : '' ], events: { copy: (e) => { @@ -140,7 +93,6 @@ export class SyntaxHighlighter { } } }); - highlightElement(preElement); if (props.codeBlockActions != null) { Object.keys(props.codeBlockActions).forEach((actionId: string) => { diff --git a/src/helper/merge-html-plugin.ts b/src/helper/merge-html-plugin.ts new file mode 100644 index 00000000..3ca2fe3f --- /dev/null +++ b/src/helper/merge-html-plugin.ts @@ -0,0 +1,129 @@ +/* + Highlight.js does not support unescaped HTML by default to prevent XSS. + This plugin allows this, so that we can implement highlights with tooltips. + + Taken from: https://github.com/highlightjs/highlight.js/issues/2889 +*/ + +import { HighlightResult, HLJSPlugin } from 'highlight.js'; + +export const mergeHTMLPlugin = (function () { + let originalStream: Event[]; + + function escapeHTML (value: string): string { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + const mergeHTMLPlugin: HLJSPlugin = { + 'before:highlightElement': ({ el }: {el: Node}) => { + originalStream = nodeStream(el); + }, + 'after:highlightElement': ({ el, result, text }: {el: Element; result: HighlightResult; text: string}) => { + if (originalStream.length === 0) return; + + const resultNode = document.createElement('div'); + resultNode.innerHTML = result.value; + result.value = mergeStreams(originalStream, nodeStream(resultNode), text); + el.innerHTML = result.value; + } + }; + + interface Event { + event: 'start' | 'stop'; + offset: number; + node: Node; + } + + function tag (node: Node): string { + return node.nodeName.toLowerCase(); + } + + function nodeStream (node: Node): Event[] { + const result: Event[] = []; + (function _nodeStream (node, offset) { + for (let child = node.firstChild; child != null; child = child.nextSibling) { + if (child.nodeType === 3) { + offset += child.nodeValue?.length ?? 0; + } else if (child.nodeType === 1) { + result.push({ + event: 'start', + offset, + node: child + }); + offset = _nodeStream(child, offset); + if (tag(child).match(/br|hr|img|input/) == null) { + result.push({ + event: 'stop', + offset, + node: child + }); + } + } + } + return offset; + })(node, 0); + return result; + } + + function mergeStreams (original: Event[], highlighted: Event[], value: string): string { + let processed = 0; + let result = ''; + const nodeStack = []; + + function selectStream (): Event[] { + if ((original.length === 0) || (highlighted.length === 0)) { + return (original.length > 0) ? original : highlighted; + } + if (original[0].offset !== highlighted[0].offset) { + return (original[0].offset < highlighted[0].offset) ? original : highlighted; + } + + return highlighted[0].event === 'start' ? original : highlighted; + } + + function open (node: Node): void { + function attributeString (attr: Attr): string { + return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"'; + } + // @ts-expect-error + result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>'; + } + + function close (node: Node): void { + result += ''; + } + + function render (event: Event): void { + (event.event === 'start' ? open : close)(event.node); + } + + while ((original.length > 0) || (highlighted.length > 0)) { + let stream = selectStream(); + result += escapeHTML(value.substring(processed, stream[0].offset)); + processed = stream[0].offset; + if (stream === original) { + nodeStack.reverse().forEach(close); + do { + render(stream.splice(0, 1)[0]); + stream = selectStream(); + } while (stream === original && (stream.length > 0) && stream[0].offset === processed); + nodeStack.reverse().forEach(open); + } else { + if (stream[0].event === 'start') { + nodeStack.push(stream[0].node); + } else { + nodeStack.pop(); + } + render(stream.splice(0, 1)[0]); + } + } + return result + escapeHTML(value.substr(processed)); + } + + return mergeHTMLPlugin; +}()); diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index 0cf2e766..11a10c9b 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -29,40 +29,18 @@ --mynah-color-toggle: var(--vscode-sideBar-background); --mynah-color-toggle-reverse: rgba(0, 0, 0, 0.5); - --mynah-color-syntax-bg: var(--vscode-terminal-dropBackground); - --mynah-color-syntax-variable: var( - --vscode-gitDecoration-modifiedResourceForeground, - var(--vscode-debugTokenExpression-name) - ); - --mynah-color-syntax-function: var( - --vscode-debugTokenExpression-boolean, - var(--vscode-gitDecoration-modifiedResourceForeground) - ); - --mynah-color-syntax-operator: var( - --vscode-terminal-foreground, - var(--vscode-debugTokenExpression-name, var(--mynah-color-text-default)) - ); - --mynah-color-syntax-property: var(--vscode-terminal-ansiCyan, var(--mynah-bg-gradient-mid)); - --mynah-color-syntax-comment: var(--vscode-debugConsole-sourceForeground, var(--mynah-color-text-weak)); - --mynah-color-syntax-code: var(--vscode-editor-foreground, var(--mynah-color-text-default)); - --mynah-color-syntax-keyword: var(--vscode-debugTokenExpression-name, var(--mynah-color-status-info)); - --mynah-color-syntax-string: var(--vscode-debugTokenExpression-string, var(--mynah-bg-gradient-next)); - --mynah-color-syntax-boolean: var( - --vscode-debugTokenExpression-boolean, - var(--vscode-debugTokenExpression-string, var(--mynah-bg-gradient-next)) - ); - --mynah-color-syntax-number: var( - --vscode-debugTokenExpression-number, - var(--vscode-debugTokenExpression-string, var(--mynah-bg-gradient-next)) - ); - --mynah-color-syntax-regex: var( - --vscode-terminal-ansiMagenta, - var(--vscode-debugTokenExpression-string, var(--mynah-bg-gradient-next)) - ); - --mynah-color-syntax-class-name: var( - --vscode-gitDecoration-modifiedResourceForeground, - var(--mynah-bg-gradient-mid) - ); + --mynah-color-syntax-bg: var(--vscode-terminal-dropBackground, #fafafa); + --mynah-color-syntax-variable: var(--vscode-debugTokenExpression-number, #986801); + --mynah-color-syntax-function: var(--vscode-debugTokenExpression-boolean, #e45649); + --mynah-color-syntax-property: var(--vscode-terminal-ansiCyan, #0184bb); + --mynah-color-syntax-operator: var(--vscode-terminal-foreground, #4078f2); + --mynah-color-syntax-comment: var(--vscode-debugConsole-sourceForeground, #a0a1a7); + --mynah-color-syntax-code: var(--vscode-editor-foreground, var(--mynah-color-text-default, #383a42)); + --mynah-color-syntax-keyword: var(--vscode-debugTokenExpression-name, #a626a4); + --mynah-color-syntax-string: var(--vscode-debugTokenExpression-string, #50a14f); + --mynah-color-syntax-class-name: var(--vscode-gitDecoration-modifiedResourceForeground, #c18401); + --mynah-color-syntax-deletion: rgba(255, 0, 0, 0.1); + --mynah-color-syntax-addition: rgba(0, 255, 128, 0.1); --mynah-color-status-info: #0971d3; --mynah-color-status-success: #037f03; diff --git a/src/styles/components/_syntax-highlighter.scss b/src/styles/components/_syntax-highlighter.scss index 9ab04f35..f904f823 100644 --- a/src/styles/components/_syntax-highlighter.scss +++ b/src/styles/components/_syntax-highlighter.scss @@ -1,18 +1,5 @@ @import '../scss-variables'; -pre.diff-highlight > code .token.deleted:not(.prefix), -pre > code.diff-highlight .token.deleted:not(.prefix) { - background-color: rgba(255, 0, 0, 0.1); - color: inherit; - display: block; -} -pre.diff-highlight > code .token.inserted:not(.prefix), -pre > code.diff-highlight .token.inserted:not(.prefix) { - background-color: rgba(0, 255, 128, 0.1); - color: inherit; - display: block; -} - .mynah-syntax-highlighter { display: flex; flex-flow: column nowrap; @@ -176,93 +163,101 @@ pre > code.diff-highlight .token.inserted:not(.prefix) { } } - > code::selection, - &::selection { - text-shadow: none; - background: #b3d4fc; - } - - .token { - &.comment, - &.prolog, - &.doctype, - &.cdata { - color: var(--mynah-color-syntax-comment); - font-style: italic; - } + .diff { + position: absolute; + top: 0; + left: 0; + padding: var(--mynah-sizing-2); + background-color: transparent; + color: transparent !important; - &.keyword { - color: var(--mynah-color-syntax-keyword); - font-style: italic; + .hljs-deletion { + background-color: var(--mynah-color-syntax-deletion); } - &.string, - &.char, - &.attr-value, - &.builtin, - &.deleted, - &.inserted, - &.tag, - &.symbol { - color: var(--mynah-color-syntax-string); + .hljs-addition { + background-color: var(--mynah-color-syntax-addition); } + } - &.number, - &.integer, - &.float { - color: var(--mynah-color-syntax-number); - } + .hljs { + color: var(--mynah-color-syntax-code); - &.namespace { - opacity: 0.7; + .hljs-comment, + .hljs-quote { + color: var(--mynah-color-syntax-comment); + font-style: italic; } - &.boolean { - color: var(--mynah-color-syntax-boolean); + .hljs-doctag, + .hljs-keyword, + .hljs-formula { + color: var(--mynah-color-syntax-keyword); } - &.function { + .hljs-section, + .hljs-name, + .hljs-selector-tag, + .hljs-subst, + .hljs-title.function_ { color: var(--mynah-color-syntax-function); } - &.class-name { - color: var(--mynah-color-syntax-class-name); + .hljs-literal, + .hljs-property { + color: var(--mynah-color-syntax-property); } - &.operator, - &.punctuation, - &.url { - color: var(--mynah-color-syntax-operator); + .hljs-string, + .hljs-regexp, + .hljs-attribute, + .hljs-meta .hljs-string { + color: var(--mynah-color-syntax-string); } - &.important, - &.variable, - &.parameter { + .hljs-attr, + .hljs-variable, + .hljs-template-variable, + .hljs-type, + .hljs-selector-class, + .hljs-selector-attr, + .hljs-selector-pseudo, + .hljs-number { color: var(--mynah-color-syntax-variable); } - &.constant, - &.property { - color: var(--mynah-color-syntax-property); + .hljs-symbol, + .hljs-bullet, + .hljs-link, + .hljs-meta, + .hljs-selector-id, + .hljs-title { + color: var(--mynah-color-syntax-operator); } - &.regex { - color: var(--mynah-color-syntax-regex); + .hljs-built_in, + .hljs-title.class_, + .hljs-class .hljs-title { + color: var(--mynah-color-syntax-class-name); } - &.important, - &.bold { + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { font-weight: bold; } - &.italic { - font-style: italic; + .hljs-link { + text-decoration: underline; } } - .language-css .token.string, - .style .token.string { - color: var(--mynah-color-syntax-operator); + > code::selection, + &::selection { + text-shadow: none; + background: #b3d4fc; } &.line-numbers { @@ -306,6 +301,7 @@ pre > code.diff-highlight .token.inserted:not(.prefix) { border-radius: var(--mynah-card-radius); border: var(--mynah-border-width) solid var(--mynah-color-border-default); padding: var(--mynah-sizing-5); + .mynah-card-body { > p:first-child:last-of-type, > p p:first-child { diff --git a/ui-tests/__test__/__image_snapshots__/chromium/main-spec-ts-open-mynah-ui-should-parse-markdown-1-snap.png b/ui-tests/__test__/__image_snapshots__/chromium/main-spec-ts-open-mynah-ui-should-parse-markdown-1-snap.png index 5ad52a5b..4c724612 100644 Binary files a/ui-tests/__test__/__image_snapshots__/chromium/main-spec-ts-open-mynah-ui-should-parse-markdown-1-snap.png and b/ui-tests/__test__/__image_snapshots__/chromium/main-spec-ts-open-mynah-ui-should-parse-markdown-1-snap.png differ diff --git a/ui-tests/__test__/__image_snapshots__/webkit/main-spec-ts-open-mynah-ui-should-parse-markdown-1-snap.png b/ui-tests/__test__/__image_snapshots__/webkit/main-spec-ts-open-mynah-ui-should-parse-markdown-1-snap.png index 0b05aa4e..1b6350ca 100644 Binary files a/ui-tests/__test__/__image_snapshots__/webkit/main-spec-ts-open-mynah-ui-should-parse-markdown-1-snap.png and b/ui-tests/__test__/__image_snapshots__/webkit/main-spec-ts-open-mynah-ui-should-parse-markdown-1-snap.png differ