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(
- '
'
+ ''
);
});
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 += '' + tag(node) + '>';
+ }
+
+ 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