diff --git a/package.json b/package.json index 9beb5f891f..51e71936e3 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "resolutions": { "babel-core": "7.0.0-bridge.0", "gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#420d6cfa17a6a8f9bf1a517a2c629dcb332dbe13", - "refractor": "3.0.0", - "prismjs": "1.20.0" + "refractor": "3.2.0", + "prismjs": "1.22.0" }, "babel": { "presets": [ diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index 2ceb3c5f62..95058b976f 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -62,7 +62,7 @@ "react-markdown": "^4.0.6", "react-router-dom": "^5.1.2", "react-select": "^2.1.2", - "react-syntax-highlighter": "https://github.com/conorhastings/react-syntax-highlighter#08bcf49b1aa7877ce94f7208e73dfa6bef8b26e7", + "react-syntax-highlighter": "^15.2.1", "refractor": "^3.0.0" }, "babel": { diff --git a/scm-ui/ui-components/src/SyntaxHighlighter.tsx b/scm-ui/ui-components/src/SyntaxHighlighter.tsx index 93a0c4c555..f47251a4b0 100644 --- a/scm-ui/ui-components/src/SyntaxHighlighter.tsx +++ b/scm-ui/ui-components/src/SyntaxHighlighter.tsx @@ -24,7 +24,8 @@ import React from "react"; import { PrismAsyncLight as ReactSyntaxHighlighter } from "react-syntax-highlighter"; -import { ghcolors } from "react-syntax-highlighter/dist/esm/styles/prism"; +// eslint-disable-next-line no-restricted-imports +import highlightingTheme from "@scm-manager/ui-styles/src/syntax-highlighting.js"; type Props = { language?: string; @@ -52,7 +53,7 @@ class SyntaxHighlighter extends React.Component { const { showLineNumbers } = this.props; const language = this.getLanguage(); return ( - + {this.props.value} ); diff --git a/scm-ui/ui-components/src/repos/TokenizedDiffView.tsx b/scm-ui/ui-components/src/repos/TokenizedDiffView.tsx index 3f004272f8..020b7bf95b 100644 --- a/scm-ui/ui-components/src/repos/TokenizedDiffView.tsx +++ b/scm-ui/ui-components/src/repos/TokenizedDiffView.tsx @@ -29,7 +29,8 @@ import { File } from "./DiffTypes"; // styling for the diff tokens // this must be aligned with th style, which is used in the SyntaxHighlighter component -import "prism-themes/themes/prism-ghcolors.css"; +// eslint-disable-next-line no-restricted-imports +import "@scm-manager/ui-styles/src/syntax-highlighting.css"; const DiffView = styled(Diff)` /* align line numbers */ diff --git a/scm-ui/ui-components/src/repos/annotate/Annotate.tsx b/scm-ui/ui-components/src/repos/annotate/Annotate.tsx index 90ed0f9207..35e42cba48 100644 --- a/scm-ui/ui-components/src/repos/annotate/Annotate.tsx +++ b/scm-ui/ui-components/src/repos/annotate/Annotate.tsx @@ -26,7 +26,7 @@ import React, { FC, useReducer } from "react"; import { Repository, AnnotatedSource, AnnotatedLine } from "@scm-manager/ui-types"; // @ts-ignore import { PrismAsyncLight as ReactSyntaxHighlighter, createElement } from "react-syntax-highlighter"; -import { ghcolors } from "react-syntax-highlighter/dist/esm/styles/prism"; +import highlightingTheme from "@scm-manager/ui-styles/src/syntax-highlighting.js"; import { DateInput } from "../../useDateFormatter"; import Popover from "./Popover"; import AnnotateLine from "./AnnotateLine"; @@ -144,7 +144,7 @@ const Annotate: FC = ({ source, repository, baseDate }) => { {code} diff --git a/scm-ui/ui-styles/package.json b/scm-ui/ui-styles/package.json index 3e216fbeba..9cfef11c10 100644 --- a/scm-ui/ui-styles/package.json +++ b/scm-ui/ui-styles/package.json @@ -16,10 +16,13 @@ "react-diff-view": "^2.4.1" }, "devDependencies": { + "css": "^3.0.0", "css-loader": "^3.2.0", + "prettier": "^2.1.2", "sass": "^1.26.3", "sass-loader": "^8.0.0", "style-loader": "^1.0.0", + "to-camel-case": "^1.0.0", "webpack": "^4.41.5", "webpack-dev-server": "^3.10.1" }, diff --git a/scm-ui/ui-styles/scripts/build-syntax-highlighting-javascript.js b/scm-ui/ui-styles/scripts/build-syntax-highlighting-javascript.js new file mode 100644 index 0000000000..f28ef55580 --- /dev/null +++ b/scm-ui/ui-styles/scripts/build-syntax-highlighting-javascript.js @@ -0,0 +1,85 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +const path = require("path"); +const fs = require("fs"); +const css = require("css"); +const camel = require("to-camel-case"); +const prettier = require("prettier"); + +function createJavascriptStyleSheet(directory, inputFile, outputFile) { + fs.readFile(path.join(__dirname, `${directory}/${inputFile}`), "utf-8", (err, data) => { + const javacriptStylesheet = css.parse(data).stylesheet.rules.reduce((sheet, rule) => { + if (rule.type === "rule") { + const style = rule.selectors.reduce((selectors, selector) => { + const selectorObject = rule.declarations.reduce((declarations, declaration) => { + if (declaration.type === "declaration" && declaration.property) { + const camelCaseDeclarationProp = camel(declaration.property); + const key = + camelCaseDeclarationProp.includes("moz") || + camelCaseDeclarationProp.includes("webkit") || + (camelCaseDeclarationProp[0] === "o" && !camelCaseDeclarationProp.includes("overflow")) + ? `${camelCaseDeclarationProp.substring(0, 1).toUpperCase()}${camelCaseDeclarationProp.substring(1)}` + : camelCaseDeclarationProp; + declarations[key] = declaration.value; + } + return declarations; + }, {}); + + if (selector.substring(0, 6) === ".token") { + selector = selector.substring(7); + + // Regex to fix Prism theme selectors + // - Remove further `.token` classes + // - Remove the space (descendant combinator) + // to allow for styling multiple classes + // Ref: https://github.com/react-syntax-highlighter/react-syntax-highlighter/pull/305 + selector = selector.replace(/(?<=\w) (\.token)?(?=\.)/g, ""); + } + selectors[selector] = selectorObject; + return selectors; + }, {}); + sheet = Object.keys(style).reduce((stylesheet, selector) => { + if (stylesheet[selector]) { + stylesheet[selector] = { ...stylesheet[selector], ...style[selector] }; + } else { + stylesheet[selector] = style[selector]; + } + return stylesheet; + }, sheet); + } + return sheet; + }, {}); + fs.writeFile( + path.join(__dirname, directory, outputFile), + prettier.format(`/* --- DO NOT EDIT --- */ +/* Auto-generated from ${inputFile} */ + + export default ${JSON.stringify(javacriptStylesheet, null, 2)}`), + () => {} + ); + }); +} + +createJavascriptStyleSheet(path.join("..", "src"), "syntax-highlighting.css", "syntax-highlighting.js"); diff --git a/scm-ui/ui-styles/src/syntax-highlighting.css b/scm-ui/ui-styles/src/syntax-highlighting.css new file mode 100644 index 0000000000..7fbe8e70c9 --- /dev/null +++ b/scm-ui/ui-styles/src/syntax-highlighting.css @@ -0,0 +1,309 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +/* Generated with http://k88hudson.github.io/syntax-highlighting-theme-generator/www */ + +/* http://k88hudson.github.io/react-markdocs */ + +/** + + * @author k88hudson + + * + + * Based on prism.js default theme for JavaScript, CSS and HTML + + * Based on dabblet (http://dabblet.com) + + * @author Lea Verou + + */ + +/********************************************************* + +* General + +*/ + +pre[class*="language-"], + +code[class*="language-"] { + + color: #363636; + + font-size: 13px; + + text-shadow: none; + + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + + direction: ltr; + + text-align: left; + + white-space: pre; + + word-spacing: normal; + + word-break: normal; + + line-height: 1.5; + + -moz-tab-size: 4; + + -o-tab-size: 4; + + tab-size: 4; + + -webkit-hyphens: none; + + -moz-hyphens: none; + + -ms-hyphens: none; + + hyphens: none; + +} + +pre[class*="language-"]::selection, + +code[class*="language-"]::selection, + +pre[class*="language-"]::mozselection, + +code[class*="language-"]::mozselection { + + text-shadow: none; + + background: #7fe3cd; + +} + +@media print { + + pre[class*="language-"], + + code[class*="language-"] { + + text-shadow: none; + + } + +} + +pre[class*="language-"] { + + padding: 1em; + + margin: .5em 0; + + overflow: auto; + + background: #ffffff; + +} + +:not(pre) > code[class*="language-"] { + + padding: .1em .3em; + + border-radius: .3em; + + color: #ff3860; + + background: #fbe7eb; + +} + +/********************************************************* + +* Tokens + +*/ + +.namespace { + + opacity: .7; + +} + +.token.comment, + +.token.prolog, + +.token.doctype, + +.token.cdata { + + color: #9a9a9a; + +} + +.token.punctuation { + + color: #9a9a9a; + +} + +.token.property, + +.token.tag, + +.token.boolean, + +.token.number, + +.token.constant, + +.token.symbol, + +.token.deleted { + + color: #2c99c7; + +} + +.token.selector, + +.token.attr-name, + +.token.string, + +.token.char, + +.token.builtin, + +.token.inserted { + + color: #cca201; + +} + +.token.operator, + +.token.entity, + +.token.url, + +.language-css .token.string, + +.style .token.string { + + color: #686868; + + background: #ffffff; + +} + +.token.atrule, + +.token.attr-value, + +.token.keyword { + + color: #00a984; + +} + +.token.function { + + color: #ff3860; + +} + +.token.regex, + +.token.important, + +.token.variable { + + color: #a74eb2; + +} + +.token.important, + +.token.bold { + + font-weight: bold; + +} + +.token.italic { + + font-style: italic; + +} + +.token.entity { + + cursor: help; + +} + +/********************************************************* + +* Line highlighting + +*/ + +pre[data-line] { + + position: relative; + +} + +pre[class*="language-"] > code[class*="language-"] { + + position: relative; + + z-index: 1; + +} + +.line-highlight { + + position: absolute; + + left: 0; + + right: 0; + + padding: inherit 0; + + margin-top: 1em; + + background: #f5f5f5; + + box-shadow: inset 5px 0 0 #99d8f3; + + z-index: 0; + + pointer-events: none; + + line-height: inherit; + + white-space: pre; + +} diff --git a/scm-ui/ui-styles/src/syntax-highlighting.js b/scm-ui/ui-styles/src/syntax-highlighting.js new file mode 100644 index 0000000000..f96fa51771 --- /dev/null +++ b/scm-ui/ui-styles/src/syntax-highlighting.js @@ -0,0 +1,195 @@ +/* --- DO NOT EDIT --- */ +/* Auto-generated from syntax-highlighting.css */ + +export default { + 'pre[class*="language-"]': { + color: "#363636", + fontSize: "13px", + textShadow: "none", + fontFamily: "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace", + direction: "ltr", + textAlign: "left", + whiteSpace: "pre", + wordSpacing: "normal", + wordBreak: "normal", + lineHeight: "1.5", + MozTabSize: "4", + OTabSize: "4", + tabSize: "4", + WebkitHyphens: "none", + MozHyphens: "none", + msHyphens: "none", + hyphens: "none", + padding: "1em", + margin: ".5em 0", + overflow: "auto", + background: "#ffffff" + }, + 'code[class*="language-"]': { + color: "#363636", + fontSize: "13px", + textShadow: "none", + fontFamily: "Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace", + direction: "ltr", + textAlign: "left", + whiteSpace: "pre", + wordSpacing: "normal", + wordBreak: "normal", + lineHeight: "1.5", + MozTabSize: "4", + OTabSize: "4", + tabSize: "4", + WebkitHyphens: "none", + MozHyphens: "none", + msHyphens: "none", + hyphens: "none" + }, + 'pre[class*="language-"]::selection': { + textShadow: "none", + background: "#7fe3cd" + }, + 'code[class*="language-"]::selection': { + textShadow: "none", + background: "#7fe3cd" + }, + 'pre[class*="language-"]::mozselection': { + textShadow: "none", + background: "#7fe3cd" + }, + 'code[class*="language-"]::mozselection': { + textShadow: "none", + background: "#7fe3cd" + }, + ':not(pre) > code[class*="language-"]': { + padding: ".1em .3em", + borderRadius: ".3em", + color: "#ff3860", + background: "#fbe7eb" + }, + ".namespace": { + Opacity: ".7" + }, + comment: { + color: "#9a9a9a" + }, + prolog: { + color: "#9a9a9a" + }, + doctype: { + color: "#9a9a9a" + }, + cdata: { + color: "#9a9a9a" + }, + punctuation: { + color: "#9a9a9a" + }, + property: { + color: "#2c99c7" + }, + tag: { + color: "#2c99c7" + }, + boolean: { + color: "#2c99c7" + }, + number: { + color: "#2c99c7" + }, + constant: { + color: "#2c99c7" + }, + symbol: { + color: "#2c99c7" + }, + deleted: { + color: "#2c99c7" + }, + selector: { + color: "#cca201" + }, + "attr-name": { + color: "#cca201" + }, + string: { + color: "#cca201" + }, + char: { + color: "#cca201" + }, + builtin: { + color: "#cca201" + }, + inserted: { + color: "#cca201" + }, + operator: { + color: "#686868", + background: "#ffffff" + }, + entity: { + color: "#686868", + background: "#ffffff", + cursor: "help" + }, + url: { + color: "#686868", + background: "#ffffff" + }, + ".language-css .token.string": { + color: "#686868", + background: "#ffffff" + }, + ".style .token.string": { + color: "#686868", + background: "#ffffff" + }, + atrule: { + color: "#00a984" + }, + "attr-value": { + color: "#00a984" + }, + keyword: { + color: "#00a984" + }, + function: { + color: "#ff3860" + }, + regex: { + color: "#a74eb2" + }, + important: { + color: "#a74eb2", + fontWeight: "bold" + }, + variable: { + color: "#a74eb2" + }, + bold: { + fontWeight: "bold" + }, + italic: { + fontStyle: "italic" + }, + "pre[data-line]": { + position: "relative" + }, + 'pre[class*="language-"] > code[class*="language-"]': { + position: "relative", + zIndex: "1" + }, + ".line-highlight": { + position: "absolute", + left: "0", + right: "0", + padding: "inherit 0", + marginTop: "1em", + background: "#f5f5f5", + boxShadow: "inset 5px 0 0 #99d8f3", + zIndex: "0", + pointerEvents: "none", + lineHeight: "inherit", + whiteSpace: "pre" + } +};