Introduce new extension point for changeset description

This new extension point will only be rendered when the old extension
point is not bound.
This commit is contained in:
René Pfeuffer
2020-06-30 18:11:23 +02:00
parent f62683e528
commit 53993cfee7
8 changed files with 141 additions and 30 deletions

View File

@@ -24,10 +24,10 @@
import React, { FC, ReactNode } from "react"; import React, { FC, ReactNode } from "react";
import textSplitAndReplace from "./textSplitAndReplace"; import textSplitAndReplace from "./textSplitAndReplace";
type Replacement = { export type Replacement = {
textToReplace: string; textToReplace: string;
replacement: ReactNode; replacement: ReactNode;
replaceAll: boolean; replaceAll?: boolean;
}; };
type Props = { type Props = {

View File

@@ -218,6 +218,54 @@ const four: Changeset = {
} }
}; };
const five: Changeset = {
id: "d21cc6c359270aef2196796f4d96af65f51866dc",
author: { mail: "scm-admin@scm-manager.org", name: "SCM Administrator" },
date: new Date("2020-06-09T05:39:50Z"),
description: "HOG-42 Change mail to arthur@guide.galaxy\n\n",
_links: {
self: {
href:
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/changesets/d21cc6c359270aef2196796f4d96af65f51866dc"
},
diff: {
href:
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/d21cc6c359270aef2196796f4d96af65f51866dc"
},
sources: {
href:
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/sources/d21cc6c359270aef2196796f4d96af65f51866dc"
},
modifications: {
href:
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/modifications/d21cc6c359270aef2196796f4d96af65f51866dc"
},
diffParsed: {
href:
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/d21cc6c359270aef2196796f4d96af65f51866dc/parsed"
}
},
_embedded: {
tags: [],
branches: [],
parents: [
{
id: "e163c8f632db571c9aa51a8eb440e37cf550b825",
_links: {
self: {
href:
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/changesets/e163c8f632db571c9aa51a8eb440e37cf550b825"
},
diff: {
href:
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/e163c8f632db571c9aa51a8eb440e37cf550b825"
}
}
}
]
}
};
const changesets: PagedCollection = { const changesets: PagedCollection = {
page: 0, page: 0,
pageTotal: 1, pageTotal: 1,
@@ -246,5 +294,5 @@ const changesets: PagedCollection = {
} }
}; };
export { one, two, three, four }; export { one, two, three, four, five };
export default changesets; export default changesets;

View File

@@ -78,6 +78,7 @@ export { default as CardColumnGroup } from "./CardColumnGroup";
export { default as CardColumn } from "./CardColumn"; export { default as CardColumn } from "./CardColumn";
export { default as CardColumnSmall } from "./CardColumnSmall"; export { default as CardColumnSmall } from "./CardColumnSmall";
export { default as CommaSeparatedList } from "./CommaSeparatedList"; export { default as CommaSeparatedList } from "./CommaSeparatedList";
export { default as SplitAndReplace, Replacement } from "./SplitAndReplace";
export { default as comparators } from "./comparators"; export { default as comparators } from "./comparators";

View File

@@ -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 { Changeset } from "@scm-manager/ui-types";
import { useBinder } from "@scm-manager/ui-extensions";
import { SplitAndReplace, Replacement } from "@scm-manager/ui-components";
type Props = {
changeset: Changeset;
value: string;
};
const ChangesetDescription: FC<Props> = ({ changeset, value }) => {
const binder = useBinder();
const replacements: Replacement[][] = binder.getExtensions("changeset.description.tokens", {
changeset,
value
});
return <SplitAndReplace text={value} replacements={replacements.flatMap(r => r)} />;
};
export default ChangesetDescription;

View File

@@ -34,6 +34,7 @@ import ChangesetId from "./ChangesetId";
import ChangesetAuthor from "./ChangesetAuthor"; import ChangesetAuthor from "./ChangesetAuthor";
import ChangesetTags from "./ChangesetTags"; import ChangesetTags from "./ChangesetTags";
import ChangesetButtonGroup from "./ChangesetButtonGroup"; import ChangesetButtonGroup from "./ChangesetButtonGroup";
import ChangesetDescription from "./ChangesetDescription";
type Props = WithTranslation & { type Props = WithTranslation & {
repository: Repository; repository: Repository;
@@ -114,7 +115,7 @@ class ChangesetRow extends React.Component<Props> {
}} }}
renderAll={false} renderAll={false}
> >
{description.title} <ChangesetDescription changeset={changeset} value={description.title} />
</ExtensionPoint> </ExtensionPoint>
</h4> </h4>
<p className="is-hidden-touch"> <p className="is-hidden-touch">

View File

@@ -28,12 +28,13 @@ import styled from "styled-components";
import { MemoryRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
import repository from "../../__resources__/repository"; import repository from "../../__resources__/repository";
import ChangesetRow from "./ChangesetRow"; import ChangesetRow from "./ChangesetRow";
import {one, two, three, four} from "../../__resources__/changesets"; import { one, two, three, four, five } from "../../__resources__/changesets";
import { Binder, BinderContext } from "@scm-manager/ui-extensions"; import { Binder, BinderContext } from "@scm-manager/ui-extensions";
// @ts-ignore // @ts-ignore
import hitchhiker from "../../__resources__/hitchhiker.png"; import hitchhiker from "../../__resources__/hitchhiker.png";
import { Person } from "../../avatar/Avatar"; import { Person } from "../../avatar/Avatar";
import {Changeset} from "@scm-manager/ui-types/src"; import { Changeset } from "@scm-manager/ui-types";
import { Replacement } from "../../SplitAndReplace";
const Wrapper = styled.div` const Wrapper = styled.div`
margin: 2rem; margin: 2rem;
@@ -41,7 +42,7 @@ const Wrapper = styled.div`
const robohash = (person: Person) => { const robohash = (person: Person) => {
return `https://robohash.org/${person.mail}`; return `https://robohash.org/${person.mail}`;
} };
const withAvatarFactory = (factory: (person: Person) => string, changeset: Changeset) => { const withAvatarFactory = (factory: (person: Person) => string, changeset: Changeset) => {
const binder = new Binder("changeset stories"); const binder = new Binder("changeset stories");
@@ -53,21 +54,23 @@ const withAvatarFactory = (factory: (person: Person) => string, changeset: Chang
); );
}; };
const withReplacements = (replacements: Replacement[][], changeset: Changeset) => {
const binder = new Binder("changeset stories");
replacements.forEach(replacement => binder.bind("changeset.description.tokens", replacement));
return (
<BinderContext.Provider value={binder}>
<ChangesetRow repository={repository} changeset={changeset} />
</BinderContext.Provider>
);
};
storiesOf("Changesets", module) storiesOf("Changesets", module)
.addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>) .addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
.addDecorator(storyFn => <Wrapper className="box box-link-shadow">{storyFn()}</Wrapper>) .addDecorator(storyFn => <Wrapper className="box box-link-shadow">{storyFn()}</Wrapper>)
.add("Default", () => ( .add("Default", () => <ChangesetRow repository={repository} changeset={three} />)
<ChangesetRow repository={repository} changeset={three} /> .add("With Committer", () => <ChangesetRow repository={repository} changeset={two} />)
)) .add("With Committer and Co-Author", () => <ChangesetRow repository={repository} changeset={one} />)
.add("With Committer", () => ( .add("With multiple Co-Authors", () => <ChangesetRow repository={repository} changeset={four} />)
<ChangesetRow repository={repository} changeset={two} />
))
.add("With Committer and Co-Author", () => (
<ChangesetRow repository={repository} changeset={one} />
))
.add("With multiple Co-Authors", () => (
<ChangesetRow repository={repository} changeset={four} />
))
.add("With avatar", () => { .add("With avatar", () => {
return withAvatarFactory(person => hitchhiker, three); return withAvatarFactory(person => hitchhiker, three);
}) })
@@ -76,4 +79,15 @@ storiesOf("Changesets", module)
}) })
.add("Co-Authors with avatar", () => { .add("Co-Authors with avatar", () => {
return withAvatarFactory(robohash, four); return withAvatarFactory(robohash, four);
})
.add("Replacements", () => {
const link = <a href={"http://example.com/hog"}>HOG-42</a>;
const mail = <a href={"mailto:hog@example.com"}>Arthur</a>;
return withReplacements(
[
[{ textToReplace: "HOG-42", replacement: link }],
[{ textToReplace: "arthur@guide.galaxy", replacement: mail }]
],
five
);
}); });

View File

@@ -27,6 +27,7 @@ export { changesets };
export { default as ChangesetAuthor, SingleContributor } from "./ChangesetAuthor"; export { default as ChangesetAuthor, SingleContributor } from "./ChangesetAuthor";
export { default as ChangesetButtonGroup } from "./ChangesetButtonGroup"; export { default as ChangesetButtonGroup } from "./ChangesetButtonGroup";
export { default as ChangesetDescription } from "./ChangesetDescription";
export { default as ChangesetDiff } from "./ChangesetDiff"; export { default as ChangesetDiff } from "./ChangesetDiff";
export { default as ChangesetId } from "./ChangesetId"; export { default as ChangesetId } from "./ChangesetId";
export { default as ChangesetList } from "./ChangesetList"; export { default as ChangesetList } from "./ChangesetList";

View File

@@ -32,6 +32,7 @@ import {
AvatarWrapper, AvatarWrapper,
Button, Button,
ChangesetAuthor, ChangesetAuthor,
ChangesetDescription,
ChangesetDiff, ChangesetDiff,
ChangesetId, ChangesetId,
changesets, changesets,
@@ -106,7 +107,7 @@ const ContributorToggleLine = styled.p`
const ChangesetSummary = styled.div` const ChangesetSummary = styled.div`
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
` `;
const SeparatedParents = styled.div` const SeparatedParents = styled.div`
margin-left: 1em; margin-left: 1em;
@@ -180,7 +181,7 @@ class ChangesetDetails extends React.Component<Props, State> {
}} }}
renderAll={false} renderAll={false}
> >
{description.title} <ChangesetDescription changeset={changeset} value={description.title} />
</ExtensionPoint> </ExtensionPoint>
</h4> </h4>
<article className="media"> <article className="media">
@@ -217,7 +218,7 @@ class ChangesetDetails extends React.Component<Props, State> {
}} }}
renderAll={false} renderAll={false}
> >
{item} <ChangesetDescription changeset={changeset} value={item} />
</ExtensionPoint> </ExtensionPoint>
<br /> <br />
</span> </span>