diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json
index 7390623378..23ed2b76ba 100644
--- a/scm-ui/ui-components/package.json
+++ b/scm-ui/ui-components/package.json
@@ -60,7 +60,7 @@
"react-markdown": "^4.0.6",
"react-router-dom": "^5.1.2",
"react-select": "^2.1.2",
- "react-syntax-highlighter": "^11.0.2"
+ "react-syntax-highlighter": "https://github.com/conorhastings/react-syntax-highlighter#08bcf49b1aa7877ce94f7208e73dfa6bef8b26e7"
},
"babel": {
"presets": [
diff --git a/scm-ui/ui-components/src/Annotate.stories.tsx b/scm-ui/ui-components/src/Annotate.stories.tsx
new file mode 100644
index 0000000000..582c7545d0
--- /dev/null
+++ b/scm-ui/ui-components/src/Annotate.stories.tsx
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+import { storiesOf } from "@storybook/react";
+import * as React from "react";
+import styled from "styled-components";
+import Annotate, { AnnotatedSource } from "./Annotate";
+
+const Wrapper = styled.div`
+ margin: 2rem;
+`;
+
+const commitCreateNewApp = {
+ revision: "0d8c1d328f4599b363755671afe667c7ace52bae",
+ author: {
+ name: "Arthur Dent",
+ mail: "arthur.dent@hitchhiker.com"
+ },
+ description: "create new app",
+ when: new Date()
+};
+
+const commitFixedMissingImport = {
+ revision: "fab38559ce3ab8c388e067712b4bd7ab94b9fa9b",
+ author: {
+ name: "Tricia Marie McMillan",
+ mail: "trillian@hitchhiker.com"
+ },
+ description: "fixed missing import",
+ when: new Date()
+};
+
+const commitImplementMain = {
+ revision: "5203292ab2bc0c020dd22adc4d3897da4930e43f",
+ author: {
+ name: "Ford Prefect",
+ mail: "ford.prefect@hitchhiker.com"
+ },
+ description: "implemented main function",
+ when: new Date()
+};
+
+const source: AnnotatedSource = {
+ language: "go",
+ lines: [
+ {
+ lineNumber: 1,
+ code: "package main",
+ ...commitCreateNewApp
+ },
+ {
+ lineNumber: 2,
+ code: "",
+ ...commitCreateNewApp
+ },
+ {
+ lineNumber: 3,
+ code: 'import "fmt"',
+ ...commitFixedMissingImport
+ },
+ {
+ lineNumber: 4,
+ code: "",
+ ...commitFixedMissingImport
+ },
+ {
+ lineNumber: 5,
+ code: "func main() {",
+ ...commitCreateNewApp
+ },
+ {
+ lineNumber: 6,
+ code: ' fmt.Println("Hello World")',
+ ...commitImplementMain
+ },
+ {
+ lineNumber: 7,
+ code: "}",
+ ...commitCreateNewApp
+ },
+ {
+ lineNumber: 8,
+ code: "",
+ ...commitCreateNewApp
+ }
+ ]
+};
+
+storiesOf("Annotate", module)
+ .addDecorator(storyFn => {storyFn()})
+ .add("Default", () => );
diff --git a/scm-ui/ui-components/src/Annotate.tsx b/scm-ui/ui-components/src/Annotate.tsx
new file mode 100644
index 0000000000..b71d78023b
--- /dev/null
+++ b/scm-ui/ui-components/src/Annotate.tsx
@@ -0,0 +1,135 @@
+/*
+ * 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.
+ */
+
+import React, { FC } from "react";
+import { Person } from "@scm-manager/ui-types";
+
+// @ts-ignore
+import { LightAsync as ReactSyntaxHighlighter, createElement } from "react-syntax-highlighter";
+
+// @ts-ignore
+import { arduinoLight } from "react-syntax-highlighter/dist/cjs/styles/hljs";
+import styled from "styled-components";
+import DateShort from "./DateShort";
+
+// TODO move types to ui-types
+
+export type AnnotatedSource = {
+ lines: AnnotatedLine[];
+ language: string;
+};
+
+export type AnnotatedLine = {
+ author: Person;
+ code: string;
+ description: string;
+ lineNumber: number;
+ revision: string;
+ when: Date;
+};
+
+type Props = {
+ source: AnnotatedSource;
+};
+
+const Author = styled.a`
+ width: 8em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ float: left;
+`;
+
+const LineNumber = styled.span`
+ width: 3em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ border-left: 1px solid lightgrey;
+ border-right: 1px solid lightgrey;
+
+ text-align: right;
+ float: left;
+
+ padding: 0 0.5em;
+`;
+
+const When = styled.span`
+ width: 90px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ float: left;
+
+ margin: 0 0.5em;
+`;
+
+const Annotate: FC = ({ source }) => {
+ // @ts-ignore
+ const defaultRenderer = ({ rows, stylesheet, useInlineStyles }) => {
+ // @ts-ignore
+ return rows.map((node, i) => {
+ const line = createElement({
+ node,
+ stylesheet,
+ useInlineStyles,
+ key: `code-segement${i}`
+ });
+
+ if (i + 1 < rows.length) {
+ const annotation = source.lines[i];
+ return (
+
+ {annotation.author.name}{" "}
+
+
+ {" "}
+ {i + 1} {line}
+
+ );
+ }
+
+ return line;
+ });
+ };
+
+ const code = source.lines.reduce((content, line) => {
+ content += line.code + "\n";
+ return content;
+ }, "");
+
+ return (
+
+ {code}
+
+ );
+};
+
+export default Annotate;
diff --git a/scm-ui/ui-components/src/DateFromNow.tsx b/scm-ui/ui-components/src/DateFromNow.tsx
index 6b68bd8bd2..eda6efdcc7 100644
--- a/scm-ui/ui-components/src/DateFromNow.tsx
+++ b/scm-ui/ui-components/src/DateFromNow.tsx
@@ -23,16 +23,14 @@
*/
import React from "react";
import { withTranslation, WithTranslation } from "react-i18next";
-import { formatDistance, format, parseISO, Locale } from "date-fns";
+import { formatDistance, format, Locale } from "date-fns";
import { enUS, de, es } from "date-fns/locale";
-import styled from "styled-components";
+import { DateInput, DateElement, FullDateFormat, toDate } from "./dates";
type LocaleMap = {
[key: string]: Locale;
};
-type DateInput = Date | string;
-
export const supportedLocales: LocaleMap = {
enUS,
en: enUS,
@@ -59,11 +57,6 @@ type Options = {
timeZone?: string;
};
-const DateElement = styled.time`
- border-bottom: 1px dotted rgba(219, 219, 219);
- cursor: help;
-`;
-
export const chooseLocale = (language: string, languages?: string[]) => {
for (const lng of languages || []) {
const locale = supportedLocales[lng];
@@ -98,17 +91,10 @@ class DateFromNow extends React.Component {
return options;
};
- toDate = (value: DateInput): Date => {
- if (value instanceof Date) {
- return value;
- }
- return parseISO(value);
- };
-
getBaseDate = () => {
const { baseDate } = this.props;
if (baseDate) {
- return this.toDate(baseDate);
+ return toDate(baseDate);
}
return new Date();
};
@@ -116,10 +102,10 @@ class DateFromNow extends React.Component {
render() {
const { date } = this.props;
if (date) {
- const isoDate = this.toDate(date);
+ const isoDate = toDate(date);
const options = this.createOptions();
const distance = formatDistance(isoDate, this.getBaseDate(), options);
- const formatted = format(isoDate, "yyyy-MM-dd HH:mm:ss", options);
+ const formatted = format(isoDate, FullDateFormat, options);
return {distance};
}
return null;
diff --git a/scm-ui/ui-components/src/DateShort.tsx b/scm-ui/ui-components/src/DateShort.tsx
new file mode 100644
index 0000000000..86510177ad
--- /dev/null
+++ b/scm-ui/ui-components/src/DateShort.tsx
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+import React, { FC } from "react";
+import { format } from "date-fns";
+import { toDate, ShortDateFormat, FullDateFormat, DateElement } from "./dates";
+
+type Props = {
+ value?: Date | string;
+ className?: string;
+};
+
+const DateShort: FC = ({ value, className }) => {
+ if (!value) {
+ return null;
+ }
+ const date = toDate(value);
+ return (
+
+ {format(date, ShortDateFormat)}
+
+ );
+};
+
+export default DateShort;
diff --git a/scm-ui/ui-components/src/dates.ts b/scm-ui/ui-components/src/dates.ts
new file mode 100644
index 0000000000..c335750ad2
--- /dev/null
+++ b/scm-ui/ui-components/src/dates.ts
@@ -0,0 +1,19 @@
+import styled from "styled-components";
+import { parseISO } from "date-fns";
+
+export type DateInput = Date | string;
+
+export const ShortDateFormat = "yyyy-MM-dd";
+export const FullDateFormat = "yyyy-MM-dd HH:mm:ss";
+
+export const DateElement = styled.time`
+ border-bottom: 1px dotted rgba(219, 219, 219);
+ cursor: help;
+`;
+
+export const toDate = (value: DateInput): Date => {
+ if (value instanceof Date) {
+ return value;
+ }
+ return parseISO(value);
+};
diff --git a/scm-ui/ui-components/src/index.ts b/scm-ui/ui-components/src/index.ts
index 68ec1ec2ef..07f4bdf5f3 100644
--- a/scm-ui/ui-components/src/index.ts
+++ b/scm-ui/ui-components/src/index.ts
@@ -43,7 +43,9 @@ import {
export { validation, urls, repositories };
+export { default as Annotate } from "./Annotate";
export { default as DateFromNow } from "./DateFromNow";
+export { default as DateShort } from "./DateShort";
export { default as ErrorNotification } from "./ErrorNotification";
export { default as ErrorPage } from "./ErrorPage";
export { default as Icon } from "./Icon";
diff --git a/yarn.lock b/yarn.lock
index cb8829e58a..8651f444e7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11809,7 +11809,7 @@ pretty-hrtime@^1.0.3:
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
-prismjs@^1.8.4:
+prismjs@^1.16.0, prismjs@^1.8.4:
version "1.20.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.20.0.tgz#9b685fc480a3514ee7198eac6a3bf5024319ff03"
integrity sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==
@@ -12424,6 +12424,16 @@ react-syntax-highlighter@^11.0.2:
prismjs "^1.8.4"
refractor "^2.4.1"
+"react-syntax-highlighter@https://github.com/conorhastings/react-syntax-highlighter#08bcf49b1aa7877ce94f7208e73dfa6bef8b26e7":
+ version "12.0.2"
+ resolved "https://github.com/conorhastings/react-syntax-highlighter#08bcf49b1aa7877ce94f7208e73dfa6bef8b26e7"
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ highlight.js "~9.13.0"
+ lowlight "~1.11.0"
+ prismjs "^1.16.0"
+ refractor "^2.10.1"
+
react-test-renderer@^16.0.0-0, react-test-renderer@^16.10.2:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"
@@ -12736,7 +12746,7 @@ reflect.ownkeys@^0.2.0:
resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=
-refractor@^2.4.1:
+refractor@^2.10.1, refractor@^2.4.1:
version "2.10.1"
resolved "https://registry.yarnpkg.com/refractor/-/refractor-2.10.1.tgz#166c32f114ed16fd96190ad21d5193d3afc7d34e"
integrity sha512-Xh9o7hQiQlDbxo5/XkOX6H+x/q8rmlmZKr97Ie1Q8ZM32IRRd3B/UxuA/yXDW79DBSXGWxm2yRTbcTVmAciJRw==