diff --git a/CHANGELOG.md b/CHANGELOG.md index c85a7d964e..b2595d3e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Build on windows ([#1048](https://github.com/scm-manager/scm-manager/issues/1048), [#1049](https://github.com/scm-manager/scm-manager/issues/1049), [#1056](https://github.com/scm-manager/scm-manager/pull/1056)) - Show specific notification for plugin actions on plugin administration ([#1057](https://github.com/scm-manager/scm-manager/pull/1057)) +- Invalid markdown could make parts of the page inaccessible ([#1077](https://github.com/scm-manager/scm-manager/pull/1077)) ## 2.0.0-rc5 - 2020-03-12 ### Added diff --git a/scm-ui/ui-components/.storybook/config.js b/scm-ui/ui-components/.storybook/config.js index 68dbde6db1..bfaea0245c 100644 --- a/scm-ui/ui-components/.storybook/config.js +++ b/scm-ui/ui-components/.storybook/config.js @@ -21,16 +21,27 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import i18n from "i18next"; +import i18next from "i18next"; import { initReactI18next } from "react-i18next"; import { addDecorator, configure } from "@storybook/react"; import { withI18next } from "storybook-addon-i18next"; import "!style-loader!css-loader!sass-loader!../../ui-styles/src/scm.scss"; -import React, { ReactNode } from "react"; +import React from "react"; import { MemoryRouter } from "react-router-dom"; -i18n.use(initReactI18next).init({ + +let i18n = i18next; + +// only use fetch backend for storybook +// and not for storyshots +if (!process.env.JEST_WORKER_ID) { + const Backend = require("i18next-fetch-backend"); + i18n = i18n.use(Backend.default) +} + +i18n +.use(initReactI18next).init({ whitelist: ["en", "de", "es"], lng: "en", fallbackLng: "en", @@ -39,6 +50,12 @@ i18n.use(initReactI18next).init({ }, react: { useSuspense: false + }, + backend: { + loadPath: "/locales/{{lng}}/{{ns}}.json", + init: { + credentials: "same-origin" + } } }); diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index b15ff201dd..8900a701e3 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -38,6 +38,7 @@ "enzyme-context": "^1.1.2", "enzyme-context-react-router-4": "^2.0.0", "fetch-mock": "^7.5.1", + "i18next-fetch-backend": "^2.2.0", "raf": "^3.4.0", "react-test-renderer": "^16.10.2", "storybook-addon-i18next": "^1.2.1", diff --git a/scm-ui/ui-components/src/MarkdownView.stories.tsx b/scm-ui/ui-components/src/MarkdownView.stories.tsx index ed9e18119f..464efe42cc 100644 --- a/scm-ui/ui-components/src/MarkdownView.stories.tsx +++ b/scm-ui/ui-components/src/MarkdownView.stories.tsx @@ -28,19 +28,24 @@ import styled from "styled-components"; import TestPage from "./__resources__/test-page.md"; import MarkdownWithoutLang from "./__resources__/markdown-without-lang.md"; +import MarkdownXmlCodeBlock from "./__resources__/markdown-xml-codeblock.md"; +import MarkdownInlineXml from "./__resources__/markdown-inline-xml.md"; +import Title from "./layout/Title"; +import { Subtitle } from "./layout"; const Spacing = styled.div` padding: 2em; `; storiesOf("MarkdownView", module) - .add("Default", () => ( - - - - )) - .add("Code without Lang", () => ( - - - + .addDecorator(story => {story()}) + .add("Default", () => ) + .add("Code without Lang", () => ) + .add("Xml Code Block", () => ) + .add("Inline Xml", () => ( + <> + + <Subtitle subtitle="Inline xml outside of a code block is not supported" /> + <MarkdownView content={MarkdownInlineXml} /> + </> )); diff --git a/scm-ui/ui-components/src/MarkdownView.tsx b/scm-ui/ui-components/src/MarkdownView.tsx index 846d1d1f2f..3e20830585 100644 --- a/scm-ui/ui-components/src/MarkdownView.tsx +++ b/scm-ui/ui-components/src/MarkdownView.tsx @@ -21,13 +21,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React from "react"; +import React, { FC } from "react"; import { withRouter, RouteComponentProps } from "react-router-dom"; // @ts-ignore import Markdown from "react-markdown/with-html"; import { binder } from "@scm-manager/ui-extensions"; +import ErrorBoundary from "./ErrorBoundary"; import SyntaxHighlighter from "./SyntaxHighlighter"; import MarkdownHeadingRenderer from "./MarkdownHeadingRenderer"; +import { useTranslation } from "react-i18next"; +import Notification from "./Notification"; type Props = RouteComponentProps & { content: string; @@ -37,6 +40,35 @@ type Props = RouteComponentProps & { enableAnchorHeadings?: boolean; }; +const xmlMarkupSample = `\`\`\`xml +<your> + <xml> + <content/> + </xml> +</your> +\`\`\``; + +const MarkdownErrorNotification: FC = () => { + const [t] = useTranslation("commons"); + return ( + <Notification type="danger"> + <div className="content"> + <p className="subtitle">{t("markdownErrorNotification.title")}</p> + <p>{t("markdownErrorNotification.description")}</p> + <pre> + <code>{xmlMarkupSample}</code> + </pre> + <p> + {t("markdownErrorNotification.spec")}:{" "} + <a href="https://github.github.com/gfm/" target="_blank"> + GitHub Flavored Markdown Spec + </a> + </p> + </div> + </Notification> + ); +}; + class MarkdownView extends React.Component<Props> { static defaultProps: Partial<Props> = { enableAnchorHeadings: false, @@ -87,15 +119,17 @@ class MarkdownView extends React.Component<Props> { } return ( - <div ref={el => (this.contentRef = el)}> - <Markdown - className="content" - skipHtml={skipHtml} - escapeHtml={skipHtml} - source={content} - renderers={rendererList} - /> - </div> + <ErrorBoundary fallback={MarkdownErrorNotification}> + <div ref={el => (this.contentRef = el)}> + <Markdown + className="content" + skipHtml={skipHtml} + escapeHtml={skipHtml} + source={content} + renderers={rendererList} + /> + </div> + </ErrorBoundary> ); } } diff --git a/scm-ui/ui-components/src/__resources__/markdown-inline-xml.md.ts b/scm-ui/ui-components/src/__resources__/markdown-inline-xml.md.ts new file mode 100644 index 0000000000..bbb9eabc5e --- /dev/null +++ b/scm-ui/ui-components/src/__resources__/markdown-inline-xml.md.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ +export default `# Xml in Markdown +<project [...]> + + [...] + + <build> + <plugins> + + [...] + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>animal-sniffer-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + + [...] + +</project>`; diff --git a/scm-ui/ui-components/src/__resources__/markdown-xml-codeblock.md.ts b/scm-ui/ui-components/src/__resources__/markdown-xml-codeblock.md.ts new file mode 100644 index 0000000000..716b47f967 --- /dev/null +++ b/scm-ui/ui-components/src/__resources__/markdown-xml-codeblock.md.ts @@ -0,0 +1,49 @@ +/* + * 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. + */ +export default `# Xml Code Block in Markdown +\`\`\`xml +<project [...]> + + [...] + + <build> + <plugins> + + [...] + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>animal-sniffer-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + + [...] + +</project> +\`\`\``; diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index bc7416defb..b900c277d9 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -33768,6 +33768,426 @@ func main() { </div> `; +exports[`Storyshots MarkdownView Inline Xml 1`] = ` +<div + className="MarkdownViewstories__Spacing-sc-1hk90gd-0 eqTNCI" +> + <h1 + className="title" + > + Inline Xml + </h1> + <h2 + className="subtitle" + > + Inline xml outside of a code block is not supported + </h2> + <div + className="notification is-danger" + > + + <div + className="content" + > + <p + className="subtitle" + > + markdownErrorNotification.title + </p> + <p> + markdownErrorNotification.description + </p> + <pre> + <code> + \`\`\`xml +<your> + <xml> + <content/> + </xml> +</your> +\`\`\` + </code> + </pre> + <p> + markdownErrorNotification.spec + : + + <a + href="https://github.github.com/gfm/" + target="_blank" + > + GitHub Flavored Markdown Spec + </a> + </p> + </div> + </div> +</div> +`; + +exports[`Storyshots MarkdownView Xml Code Block 1`] = ` +<div + className="MarkdownViewstories__Spacing-sc-1hk90gd-0 eqTNCI" +> + <div> + <div + className="content" + > + <h1> + Xml Code Block in Markdown + </h1> + <pre + style={ + Object { + "background": "#FFFFFF", + "color": "#434f54", + "display": "block", + "overflowX": "auto", + "padding": "0.5em", + } + } + > + <code> + <span + className="hljs-tag" + style={Object {}} + > + < + <span + style={ + Object { + "color": "#00979D", + } + } + > + project + </span> + [ + <span + className="hljs-attr" + style={Object {}} + > + ... + </span> + ]> + </span> + + + [...] + + + <span + className="hljs-tag" + style={Object {}} + > + < + <span + style={ + Object { + "color": "#00979D", + } + } + > + build + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + < + <span + style={ + Object { + "color": "#00979D", + } + } + > + plugins + </span> + > + </span> + + + [...] + + + <span + className="hljs-tag" + style={Object {}} + > + < + <span + style={ + Object { + "color": "#00979D", + } + } + > + plugin + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + < + <span + style={ + Object { + "color": "#00979D", + } + } + > + groupId + </span> + > + </span> + org.apache.maven.plugins + <span + className="hljs-tag" + style={Object {}} + > + </ + <span + style={ + Object { + "color": "#00979D", + } + } + > + groupId + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + < + <span + style={ + Object { + "color": "#00979D", + } + } + > + artifactId + </span> + > + </span> + maven-enforcer-plugin + <span + className="hljs-tag" + style={Object {}} + > + </ + <span + style={ + Object { + "color": "#00979D", + } + } + > + artifactId + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + </ + <span + style={ + Object { + "color": "#00979D", + } + } + > + plugin + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + < + <span + style={ + Object { + "color": "#00979D", + } + } + > + plugin + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + < + <span + style={ + Object { + "color": "#00979D", + } + } + > + groupId + </span> + > + </span> + org.codehaus.mojo + <span + className="hljs-tag" + style={Object {}} + > + </ + <span + style={ + Object { + "color": "#00979D", + } + } + > + groupId + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + < + <span + style={ + Object { + "color": "#00979D", + } + } + > + artifactId + </span> + > + </span> + animal-sniffer-maven-plugin + <span + className="hljs-tag" + style={Object {}} + > + </ + <span + style={ + Object { + "color": "#00979D", + } + } + > + artifactId + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + </ + <span + style={ + Object { + "color": "#00979D", + } + } + > + plugin + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + </ + <span + style={ + Object { + "color": "#00979D", + } + } + > + plugins + </span> + > + </span> + + + <span + className="hljs-tag" + style={Object {}} + > + </ + <span + style={ + Object { + "color": "#00979D", + } + } + > + build + </span> + > + </span> + + + [...] + + + <span + className="hljs-tag" + style={Object {}} + > + </ + <span + style={ + Object { + "color": "#00979D", + } + } + > + project + </span> + > + </span> + </code> + </pre> + </div> + </div> +</div> +`; + exports[`Storyshots SyntaxHighlighter Go 1`] = ` <div className="SyntaxHighlighterstories__Spacing-sc-1dcldp5-0 eofOgh" diff --git a/scm-ui/ui-webapp/public/locales/de/commons.json b/scm-ui/ui-webapp/public/locales/de/commons.json index 8b9e9942e7..d88b77b850 100644 --- a/scm-ui/ui-webapp/public/locales/de/commons.json +++ b/scm-ui/ui-webapp/public/locales/de/commons.json @@ -33,6 +33,11 @@ "wrongLoginCredentials": "Ungültige Anmeldedaten", "forbidden": "Sie haben nicht die Berechtigung, diesen Datensatz zu sehen" }, + "markdownErrorNotification": { + "title": "Markdown Inhalt konnte nicht geparsed werden", + "description": "Der Inhalt enthält kein valides Markdown. Wenn XML im Inhalt verwendet wird, muss sichergestellt werden, dass sich dieser in einem Code-Block befindet z. B.:", + "spec": "Die Spezifikation kann hier eingesehen werden" + }, "loading": { "alt": "Lade ..." }, diff --git a/scm-ui/ui-webapp/public/locales/en/commons.json b/scm-ui/ui-webapp/public/locales/en/commons.json index 6b7b6c8fb6..3d5af75f78 100644 --- a/scm-ui/ui-webapp/public/locales/en/commons.json +++ b/scm-ui/ui-webapp/public/locales/en/commons.json @@ -34,6 +34,11 @@ "wrongLoginCredentials": "Invalid credentials", "forbidden": "You don't have permission to view this entity" }, + "markdownErrorNotification": { + "title": "Markdown content could not be parsed", + "description": "The Content does not contain valid markdown. If you use XML, please ensure that it is wrapped in a code block e.g.:", + "spec": "Please have a look at the specification" + }, "loading": { "alt": "Loading ..." }, diff --git a/yarn.lock b/yarn.lock index 7ebd28adce..c81b892b90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3656,11 +3656,6 @@ babel-code-frame@^6.22.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@7.0.0-bridge.0: - version "7.0.0-bridge.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" - integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== - babel-eslint@^10.0.3: version "10.0.3" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a" @@ -6806,7 +6801,7 @@ fast-safe-stringify@^1.0.8, fast-safe-stringify@^1.2.1: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-1.2.3.tgz#9fe22c37fb2f7f86f06b8f004377dbf8f1ee7bc1" integrity sha512-QJYT/i0QYoiZBQ71ivxdyTqkwKkQ0oxACXHYxH2zYHJEgzi2LsbjgvtzTbLi1SZcF190Db2YP7I7eTsU2egOlw== -fault@^1.0.0: +fault@^1.0.0, fault@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== @@ -7345,7 +7340,7 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" -gitdiff-parser@^0.1.2, "gitdiff-parser@https://github.com/scm-manager/gitdiff-parser#ed3fe7de73dbb0a06c3e6adbbdf22dbae6e66351": +gitdiff-parser@^0.1.2: version "0.1.2" resolved "https://github.com/scm-manager/gitdiff-parser#ed3fe7de73dbb0a06c3e6adbbdf22dbae6e66351" @@ -9654,7 +9649,7 @@ lower-case@^1.1.1: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= -lowlight@1.13.1, lowlight@^1.13.0, lowlight@~1.11.0: +lowlight@^1.13.0: version "1.13.1" resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.13.1.tgz#c4f0e03906ebd23fedf2d258f6ab2f6324cf90eb" integrity sha512-kQ71/T6RksEVz9AlPq07/2m+SU/1kGvt9k39UtvHX760u4SaWakaYH7hYgH5n6sTsCWk4MVYzUzLU59aN5CSmQ== @@ -9662,6 +9657,14 @@ lowlight@1.13.1, lowlight@^1.13.0, lowlight@~1.11.0: fault "^1.0.0" highlight.js "~9.16.0" +lowlight@~1.11.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc" + integrity sha512-xrGGN6XLL7MbTMdPD6NfWPwY43SNkjf/d0mecSx/CW36fUZTjRHEq0/Cdug3TWKtRXLWi7iMl1eP0olYxj/a4A== + dependencies: + fault "^1.0.2" + highlight.js "~9.13.0" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"