diff --git a/CHANGELOG.md b/CHANGELOG.md index e89702b698..89c09ec46c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added links in diff views to expand the gaps between "hunks" ([#1178](https://github.com/scm-manager/scm-manager/pull/1178)) - Show commit contributors in table on changeset details view ([#1169](https://github.com/scm-manager/scm-manager/pull/1169)) - Show changeset parents on changeset details view ([#1189](https://github.com/scm-manager/scm-manager/pull/1189)) +- Annotate view to display commit metadata for each line of a file ([#1196](https://github.com/scm-manager/scm-manager/pull/1196)) ### Fixed - Avoid caching of detected browser language ([#1176](https://github.com/scm-manager/scm-manager/pull/1176)) diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index af4e65caf0..14fd4b4532 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web; import javax.ws.rs.core.MediaType; @@ -71,13 +71,12 @@ public class VndMediaType { @SuppressWarnings("squid:S2068") public static final String PASSWORD_OVERWRITE = PREFIX + "passwordOverwrite" + SUFFIX; public static final String PERMISSION_COLLECTION = PREFIX + "permissionCollection" + SUFFIX; - public static final String MERGE_RESULT = PREFIX + "mergeResult" + SUFFIX; - public static final String MERGE_COMMAND = PREFIX + "mergeCommand" + SUFFIX; public static final String NAMESPACE_STRATEGIES = PREFIX + "namespaceStrategies" + SUFFIX; public static final String ME = PREFIX + "me" + SUFFIX; public static final String SOURCE = PREFIX + "source" + SUFFIX; + public static final String ANNOTATE = PREFIX + "annotate" + SUFFIX; public static final String ERROR_TYPE = PREFIX + "error" + SUFFIX; public static final String REPOSITORY_ROLE = PREFIX + "repositoryRole" + SUFFIX; diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index 108008f28e..e5d82f9617 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -34,7 +34,7 @@ "@types/react-select": "^2.0.19", "@types/react-syntax-highlighter": "^11.0.1", "@types/storybook__addon-storyshots": "^5.1.1", - "@types/styled-components": "^4.1.19", + "@types/styled-components": "^5.1.0", "enzyme-context": "^1.1.2", "enzyme-context-react-router-4": "^2.0.0", "fetch-mock": "^7.5.1", @@ -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/DateFromNow.stories.tsx b/scm-ui/ui-components/src/Date.stories.tsx similarity index 68% rename from scm-ui/ui-components/src/DateFromNow.stories.tsx rename to scm-ui/ui-components/src/Date.stories.tsx index 8259bf6240..4d2fe16fbe 100644 --- a/scm-ui/ui-components/src/DateFromNow.stories.tsx +++ b/scm-ui/ui-components/src/Date.stories.tsx @@ -24,25 +24,41 @@ import React from "react"; import DateFromNow from "./DateFromNow"; import { storiesOf } from "@storybook/react"; +import DateShort from "./DateShort"; +import styled from "styled-components"; const baseProps = { timeZone: "Europe/Berlin", baseDate: "2019-10-12T13:56:42+02:00" }; -storiesOf("DateFromNow", module).add("Default", () => ( -
-

- -

-

- -

-

- -

-

- -

-
-)); +const dates = [ + "2009-06-30T18:30:00+02:00", + "2019-06-30T18:30:00+02:00", + "2019-10-12T13:56:40+02:00", + "2019-10-11T13:56:40+02:00" +]; + +const Wrapper = styled.div` + padding: 2rem; +`; + +storiesOf("Date", module) + .add("Date from now", () => ( + + {dates.map(d => ( +

+ +

+ ))} +
+ )) + .add("Short", () => ( + + {dates.map(d => ( +

+ +

+ ))} +
+ )); diff --git a/scm-ui/ui-components/src/DateElement.ts b/scm-ui/ui-components/src/DateElement.ts new file mode 100644 index 0000000000..5c4dd25afa --- /dev/null +++ b/scm-ui/ui-components/src/DateElement.ts @@ -0,0 +1,31 @@ +/* + * 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 styled from "styled-components"; + +const DateElement = styled.time` + border-bottom: 1px dotted rgba(219, 219, 219); + cursor: help; +`; + +export default DateElement; diff --git a/scm-ui/ui-components/src/DateFromNow.tsx b/scm-ui/ui-components/src/DateFromNow.tsx index 6b68bd8bd2..8b5a6c0a37 100644 --- a/scm-ui/ui-components/src/DateFromNow.tsx +++ b/scm-ui/ui-components/src/DateFromNow.tsx @@ -21,109 +21,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React from "react"; -import { withTranslation, WithTranslation } from "react-i18next"; -import { formatDistance, format, parseISO, Locale } from "date-fns"; -import { enUS, de, es } from "date-fns/locale"; -import styled from "styled-components"; -type LocaleMap = { - [key: string]: Locale; +import React, { FC } from "react"; +import useDateFormatter, { DateProps } from "./useDateFormatter"; +import DateElement from "./DateElement"; + +type Props = DateProps & { + className?: string; }; -type DateInput = Date | string; - -export const supportedLocales: LocaleMap = { - enUS, - en: enUS, - de, - es -}; - -type Props = WithTranslation & { - date?: DateInput; - timeZone?: string; - - /** - * baseDate is the date from which the distance is calculated, - * default is the current time (new Date()). This property - * is required to keep snapshots tests green over the time on - * ci server. - */ - baseDate?: DateInput; -}; - -type Options = { - addSuffix: boolean; - locale: Locale; - 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]; - if (locale) { - return locale; - } - } - - const locale = supportedLocales[language]; - if (locale) { - return locale; - } - - return enUS; -}; - -class DateFromNow extends React.Component { - getLocale = (): Locale => { - const { i18n } = this.props; - return chooseLocale(i18n.language, i18n.languages); - }; - - createOptions = () => { - const { timeZone } = this.props; - const options: Options = { - addSuffix: true, - locale: this.getLocale() - }; - if (timeZone) { - options.timeZone = timeZone; - } - 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 new Date(); - }; - - render() { - const { date } = this.props; - if (date) { - const isoDate = this.toDate(date); - const options = this.createOptions(); - const distance = formatDistance(isoDate, this.getBaseDate(), options); - const formatted = format(isoDate, "yyyy-MM-dd HH:mm:ss", options); - return {distance}; - } +const DateFromNow: FC = ({ className, ...dateProps }) => { + const formatter = useDateFormatter(dateProps); + if (!formatter) { return null; } -} -export default withTranslation()(DateFromNow); + return ( + + {formatter.formatDistance()} + + ); +}; + +export default DateFromNow; diff --git a/scm-ui/ui-components/src/DateShort.tsx b/scm-ui/ui-components/src/DateShort.tsx new file mode 100644 index 0000000000..d0da04cb87 --- /dev/null +++ b/scm-ui/ui-components/src/DateShort.tsx @@ -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. + */ + +import React, { FC } from "react"; +import useDateFormatter, { DateProps } from "./useDateFormatter"; +import DateElement from "./DateElement"; + +type Props = DateProps & { + className?: string; +}; + +const DateShort: FC = ({ className, ...dateProps }) => { + const formatter = useDateFormatter(dateProps); + if (!formatter) { + return null; + } + + return ( + + {formatter.formatShort()} + + ); +}; + +export default DateShort; 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 7406de61c8..4be4a8a349 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -1,14 +1,897 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Storyshots Annotate Default 1`] = ` +
+
+
+      
+        
+
+
+ Arthur Dent +
+ +
+ +
+ +
+
+ 1 +
+ +
+ + + package + + + main + + + +
+
+
+
+
+ 2 +
+ +
+ + + + +
+
+
+
+
+ Tricia Marie McMillan +
+ +
+ +
+ +
+
+ 3 +
+ +
+ + + + + + import + + + + + + "fmt" + + + + + + +
+
+
+
+
+ 4 +
+ +
+ + + + +
+
+
+
+
+ Arthur Dent +
+ +
+ +
+ +
+
+ 5 +
+ +
+ + + + + + func + + + + + + main + + + () + + + { + + + +
+
+
+
+
+ Ford Prefect +
+ +
+ +
+ +
+
+ 6 +
+ +
+ + + fmt.Println( + + + "Hello World" + + + ) + + + +
+
+
+
+
+ Arthur Dent +
+ +
+ +
+ +
+
+ 7 +
+ +
+ + } + + +
+
+
+
+
+ 8 +
+ +
+ + + + +
+
+ + + + +
+
+
+
+`; + +exports[`Storyshots Annotate With Avatars 1`] = ` +
+
+
+      
+        
+
+
+ Arthur Dent + Arthur Dent +
+ +
+ +
+ +
+
+ 1 +
+ +
+ + + package + + + main + + + +
+
+
+
+
+ 2 +
+ +
+ + + + +
+
+
+
+
+ Tricia Marie McMillan + Tricia Marie McMillan +
+ +
+ +
+ +
+
+ 3 +
+ +
+ + + + + + import + + + + + + "fmt" + + + + + + +
+
+
+
+
+ 4 +
+ +
+ + + + +
+
+
+
+
+ Arthur Dent + Arthur Dent +
+ +
+ +
+ +
+
+ 5 +
+ +
+ + + + + + func + + + + + + main + + + () + + + { + + + +
+
+
+
+
+ Ford Prefect + Ford Prefect +
+ +
+ +
+ +
+
+ 6 +
+ +
+ + + fmt.Println( + + + "Hello World" + + + ) + + + +
+
+
+
+
+ Arthur Dent + Arthur Dent +
+ +
+ +
+ +
+
+ 7 +
+ +
+ + } + + +
+
+
+
+
+ 8 +
+ +
+ + + + +
+
+ + + + +
+
+
+
+`; + exports[`Storyshots BranchSelector Default 1`] = `