diff --git a/scm-ui/ui-components/src/SplitAndReplace.stories.tsx b/scm-ui/ui-components/src/SplitAndReplace.stories.tsx index a49f760c87..69b81cc12e 100644 --- a/scm-ui/ui-components/src/SplitAndReplace.stories.tsx +++ b/scm-ui/ui-components/src/SplitAndReplace.stories.tsx @@ -24,36 +24,29 @@ import React from "react"; import { storiesOf } from "@storybook/react"; import SplitAndReplace from "./SplitAndReplace"; -import { Icon } from "@scm-manager/ui-components/src"; +import { Icon } from "@scm-manager/ui-components"; storiesOf("SplitAndReplace", module).add("Simple replacement", () => { const replacements = [ { textToReplace: "'", - replacement: , + replacement: , replaceAll: true }, { textToReplace: "`", - replacement: , - replaceAll: true - }, - { - replacement:
 
, - textToReplace: " ", + replacement: , replaceAll: true } ]; return ( <> -
-
- +
+ +
+
+ +
+ ); }); diff --git a/scm-ui/ui-components/src/SplitAndReplace.tsx b/scm-ui/ui-components/src/SplitAndReplace.tsx index 9db88e9aaa..8fc1269e72 100644 --- a/scm-ui/ui-components/src/SplitAndReplace.tsx +++ b/scm-ui/ui-components/src/SplitAndReplace.tsx @@ -35,14 +35,20 @@ type Props = { replacements: Replacement[]; }; -type PartToReplace = { - start: number; - length: number; - replacement: ReactNode; +const textWrapper = (s: string) => { + const first = s.startsWith(" ") ? <>  : ""; + const last = s.endsWith(" ") ? <>  : ""; + return ( +
+ {first} + {s} + {last} +
+ ); }; const SplitAndReplace: FC = ({ text, replacements }) => { - const parts = textSplitAndReplace(text, replacements, s =>
{s}
); + const parts = textSplitAndReplace(text, replacements, textWrapper); if (parts.length === 0) { return <>{parts[0]}; } diff --git a/scm-ui/ui-components/src/repos/changesets/ChangesetDescription.tsx b/scm-ui/ui-components/src/repos/changesets/ChangesetDescription.tsx index 0e80212a27..3a76689eac 100644 --- a/scm-ui/ui-components/src/repos/changesets/ChangesetDescription.tsx +++ b/scm-ui/ui-components/src/repos/changesets/ChangesetDescription.tsx @@ -35,11 +35,15 @@ type Props = { const ChangesetDescription: FC = ({ changeset, value }) => { const binder = useBinder(); - const replacements: Replacement[][] = binder.getExtensions("changeset.description.tokens", { - changeset, - value - }); - return r)} />; + const replacements: ((changeset: Changeset, value: string) => Replacement[])[] = binder.getExtensions( + "changeset.description.tokens", + { + changeset, + value + } + ); + + return r(changeset, value))} />; }; export default ChangesetDescription; diff --git a/scm-ui/ui-components/src/repos/changesets/Changesets.stories.tsx b/scm-ui/ui-components/src/repos/changesets/Changesets.stories.tsx index c895ab8ee9..2c4f1e48f3 100644 --- a/scm-ui/ui-components/src/repos/changesets/Changesets.stories.tsx +++ b/scm-ui/ui-components/src/repos/changesets/Changesets.stories.tsx @@ -54,7 +54,10 @@ const withAvatarFactory = (factory: (person: Person) => string, changeset: Chang ); }; -const withReplacements = (replacements: Replacement[][], changeset: Changeset) => { +const withReplacements = ( + replacements: ((changeset: Changeset, value: string) => Replacement[])[], + changeset: Changeset +) => { const binder = new Binder("changeset stories"); replacements.forEach(replacement => binder.bind("changeset.description.tokens", replacement)); return ( @@ -72,7 +75,7 @@ storiesOf("Changesets", module) .add("With Committer and Co-Author", () => ) .add("With multiple Co-Authors", () => ) .add("With avatar", () => { - return withAvatarFactory(person => hitchhiker, three); + return withAvatarFactory(() => hitchhiker, three); }) .add("Commiter and Co-Authors with avatar", () => { return withAvatarFactory(robohash, one); @@ -85,8 +88,8 @@ storiesOf("Changesets", module) const mail = Arthur; return withReplacements( [ - [{ textToReplace: "HOG-42", replacement: link }], - [{ textToReplace: "arthur@guide.galaxy", replacement: mail }] + () => [{ textToReplace: "HOG-42", replacement: link }], + () => [{ textToReplace: "arthur@guide.galaxy", replacement: mail }] ], five ); diff --git a/scm-ui/ui-webapp/src/index.tsx b/scm-ui/ui-webapp/src/index.tsx index 25a2ea0ea3..519b0c7951 100644 --- a/scm-ui/ui-webapp/src/index.tsx +++ b/scm-ui/ui-webapp/src/index.tsx @@ -34,6 +34,10 @@ import createReduxStore from "./createReduxStore"; import { BrowserRouter as Router } from "react-router-dom"; import { urls } from "@scm-manager/ui-components"; +import { binder } from "@scm-manager/ui-extensions"; +import ChangesetShortLink from "./repos/components/changesets/ChangesetShortLink"; + +binder.bind("changeset.description.tokens", ChangesetShortLink); const store = createReduxStore(); diff --git a/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetShortLink.tsx b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetShortLink.tsx new file mode 100644 index 0000000000..13a129fc23 --- /dev/null +++ b/scm-ui/ui-webapp/src/repos/components/changesets/ChangesetShortLink.tsx @@ -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. + */ + +import React from "react"; +import { Link } from "react-router-dom"; +import { Changeset } from "@scm-manager/ui-types"; +import { Replacement } from "@scm-manager/ui-components"; + +const ChangesetShortLink: (changeset: Changeset, value: string) => Replacement[] = (changeset, value) => { + const matches = value.match(/(\w+)\/(\w+)@([a-f0-9]+)/g); + if (!matches) { + return []; + } + return matches.map(value => { + const groups = value.match(/(\w+)\/(\w+)@([a-f0-9]+)/); + const namespace = groups[1]; + const name = groups[2]; + const revision = groups[3]; + const link = `/repo/${namespace}/${name}/changeset/${revision}`; + const replacement: Replacement = { + textToReplace: value, + replacement: {value} + }; + return replacement; + }); +}; + +export default ChangesetShortLink;