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)
+`;