diff --git a/scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx b/scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx index f6b01eb06b..3ee5e3f188 100644 --- a/scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx +++ b/scm-ui/ui-components/src/MarkdownLinkRenderer.test.tsx @@ -100,4 +100,19 @@ describe("test createLocalLink", () => { const localLink = createLocalLink("/src", "/src/docs/index.md", "/docs/Home.md"); expect(localLink).toBe("/src/docs/Home.md"); }); + + it("should resolve .. with in path", () => { + const localLink = createLocalLink("/src", "/src/docs/installation/index.md", "../../README.md"); + expect(localLink).toBe("/src/README.md"); + }); + + it("should resolve . with in path", () => { + const localLink = createLocalLink("/src", "/src/README.md", "./LICENSE.md"); + expect(localLink).toBe("/src/LICENSE.md"); + }); + + it("should handle complex path", () => { + const localLink = createLocalLink("/src", "/src/docs/installation/index.md", "./.././../docs/index.md"); + expect(localLink).toBe("/src/docs/index.md"); + }); }); diff --git a/scm-ui/ui-components/src/MarkdownLinkRenderer.tsx b/scm-ui/ui-components/src/MarkdownLinkRenderer.tsx index 30e187265e..2de0846f28 100644 --- a/scm-ui/ui-components/src/MarkdownLinkRenderer.tsx +++ b/scm-ui/ui-components/src/MarkdownLinkRenderer.tsx @@ -49,6 +49,19 @@ const join = (left: string, right: string) => { return left + right; }; +const normalizePath = (path: string) => { + const stack = []; + const parts = path.split("/"); + for (const part of parts) { + if (part === "..") { + stack.pop(); + } else if (part !== ".") { + stack.push(part) + } + } + return stack.join("/") +}; + export const createLocalLink = (basePath: string, currentPath: string, link: string) => { if (link.startsWith("/")) { return join(basePath, link); @@ -66,7 +79,7 @@ export const createLocalLink = (basePath: string, currentPath: string, link: str } else { path = path.substring(0, lastSlash); } - return join(path, link); + return normalizePath(join(path, link)); }; type LinkProps = { diff --git a/scm-ui/ui-components/src/MarkdownView.stories.tsx b/scm-ui/ui-components/src/MarkdownView.stories.tsx index e43f9afd75..2c2e1cc9f4 100644 --- a/scm-ui/ui-components/src/MarkdownView.stories.tsx +++ b/scm-ui/ui-components/src/MarkdownView.stories.tsx @@ -30,6 +30,7 @@ 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 MarkdownLinks from "./__resources__/markdown-links.md"; import Title from "./layout/Title"; import { Subtitle } from "./layout"; import { MemoryRouter } from "react-router-dom"; @@ -50,4 +51,5 @@ storiesOf("MarkdownView", module) - )); + )) + .add("Links", () => ); diff --git a/scm-ui/ui-components/src/__resources__/markdown-links.md.ts b/scm-ui/ui-components/src/__resources__/markdown-links.md.ts new file mode 100644 index 0000000000..46be993a3a --- /dev/null +++ b/scm-ui/ui-components/src/__resources__/markdown-links.md.ts @@ -0,0 +1,46 @@ +/* + * 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 `# Links + +Show case for different style of markdown links. +Please note that some of the links may not work in storybook, +the story is mostly for checking if the links are rendered correct. + +## External + +External Links should be opened in a new tab: [external link](https://scm-manager.org) + +## Anchor + +Anchor Links should be rendered a simple a tag with an href: [anchor link](#sample) + +## Protocol + +Links with a protocol other than http should be rendered a simple a tag with an href e.g.: [mail link](mailto:marvin@hitchhiker.com) + +## Internal + +Internal links should be rendered by react-router: [internal link](/buttons) +`;