mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-07-06 02:59:15 +02:00
Merged in feature/changeset_committer_extension_point (pull request #182)
Redesign changeset list and added extension point for changeset author
This commit is contained in:
@@ -27,7 +27,7 @@ public interface HalAppender {
|
||||
LinkArrayBuilder linkArrayBuilder(String rel);
|
||||
|
||||
/**
|
||||
* Appends one embedded to the json response.
|
||||
* Appends one embedded object to the json response.
|
||||
*
|
||||
* @param rel name of relation
|
||||
* @param embeddedItem embedded object
|
||||
@@ -40,7 +40,7 @@ public interface HalAppender {
|
||||
interface LinkArrayBuilder {
|
||||
|
||||
/**
|
||||
* Append an link to the array.
|
||||
* Append a link to the array.
|
||||
*
|
||||
* @param name name of link
|
||||
* @param href link target
|
||||
|
||||
@@ -2,31 +2,40 @@
|
||||
import React from "react";
|
||||
import moment from "moment";
|
||||
import { translate } from "react-i18next";
|
||||
import injectSheet from "react-jss";
|
||||
|
||||
const styles = {
|
||||
date: {
|
||||
borderBottom: "1px dotted rgba(219, 219, 219)",
|
||||
cursor: "help"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
date?: string,
|
||||
|
||||
// context props
|
||||
classes: any,
|
||||
i18n: any
|
||||
};
|
||||
|
||||
class DateFromNow extends React.Component<Props> {
|
||||
static format(locale: string, date?: string) {
|
||||
let fromNow = "";
|
||||
if (date) {
|
||||
fromNow = moment(date)
|
||||
.locale(locale)
|
||||
.fromNow();
|
||||
}
|
||||
return fromNow;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { i18n, date } = this.props;
|
||||
const { i18n, date, classes } = this.props;
|
||||
|
||||
const fromNow = DateFromNow.format(i18n.language, date);
|
||||
return <span>{fromNow}</span>;
|
||||
if (date) {
|
||||
const dateWithLocale = moment(date).locale(i18n.language);
|
||||
|
||||
return (
|
||||
<time title={dateWithLocale.format()} className={classes.date}>
|
||||
{dateWithLocale.fromNow()}
|
||||
</time>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate()(DateFromNow);
|
||||
export default injectSheet(styles)(translate()(DateFromNow));
|
||||
|
||||
@@ -4,21 +4,27 @@ import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
message: string,
|
||||
className: string,
|
||||
className?: string,
|
||||
location: string,
|
||||
children: React.Node
|
||||
};
|
||||
|
||||
class Tooltip extends React.Component<Props> {
|
||||
|
||||
static defaultProps = {
|
||||
location: "right"
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, message, children } = this.props;
|
||||
const { className, message, location, children } = this.props;
|
||||
const multiline = message.length > 60 ? "is-tooltip-multiline" : "";
|
||||
return (
|
||||
<div
|
||||
className={classNames("tooltip", "is-tooltip-right", multiline, className)}
|
||||
<span
|
||||
className={classNames("tooltip", "is-tooltip-" + location, multiline, className)}
|
||||
data-tooltip={message}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import * as React from "react";
|
||||
import classNames from "classnames";
|
||||
import { withRouter } from "react-router-dom";
|
||||
|
||||
export type ButtonProps = {
|
||||
label: string,
|
||||
label?: string,
|
||||
loading?: boolean,
|
||||
disabled?: boolean,
|
||||
action?: (event: Event) => void,
|
||||
link?: string,
|
||||
fullWidth?: boolean,
|
||||
className?: string,
|
||||
children?: React.Node,
|
||||
classes: any
|
||||
};
|
||||
|
||||
@@ -45,6 +46,7 @@ class Button extends React.Component<Props> {
|
||||
type,
|
||||
color,
|
||||
fullWidth,
|
||||
children,
|
||||
className
|
||||
} = this.props;
|
||||
const loadingClass = loading ? "is-loading" : "";
|
||||
@@ -62,7 +64,7 @@ class Button extends React.Component<Props> {
|
||||
className
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{label} {children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,41 +1,30 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import Button from "./Button";
|
||||
import * as React from "react";
|
||||
|
||||
type Props = {
|
||||
firstlabel: string,
|
||||
secondlabel: string,
|
||||
firstAction?: (event: Event) => void,
|
||||
secondAction?: (event: Event) => void,
|
||||
firstIsSelected: boolean
|
||||
addons?: boolean,
|
||||
className?: string,
|
||||
children: React.Node
|
||||
};
|
||||
|
||||
class ButtonGroup extends React.Component<Props> {
|
||||
|
||||
static defaultProps = {
|
||||
addons: true
|
||||
};
|
||||
|
||||
render() {
|
||||
const { firstlabel, secondlabel, firstAction, secondAction, firstIsSelected } = this.props;
|
||||
|
||||
let showFirstColor = "";
|
||||
let showSecondColor = "";
|
||||
|
||||
if (firstIsSelected) {
|
||||
showFirstColor += "link is-selected";
|
||||
} else {
|
||||
showSecondColor += "link is-selected";
|
||||
const { addons, className, children } = this.props;
|
||||
let styleClasses = "buttons";
|
||||
if (addons) {
|
||||
styleClasses += " has-addons";
|
||||
}
|
||||
if (className) {
|
||||
styleClasses += " " + className;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="buttons has-addons">
|
||||
<Button
|
||||
label={firstlabel}
|
||||
color={showFirstColor}
|
||||
action={firstAction}
|
||||
/>
|
||||
<Button
|
||||
label={secondlabel}
|
||||
color={showSecondColor}
|
||||
action={secondAction}
|
||||
/>
|
||||
<div className={styleClasses}>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type {Changeset} from "@scm-manager/ui-types";
|
||||
import type { Changeset } from "@scm-manager/ui-types";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import {translate} from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
changeset: Changeset
|
||||
changeset: Changeset,
|
||||
|
||||
// context props
|
||||
t: (string) => string
|
||||
};
|
||||
|
||||
class ChangesetAuthor extends React.Component<Props> {
|
||||
@@ -13,26 +18,35 @@ class ChangesetAuthor extends React.Component<Props> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { name } = changeset.author;
|
||||
const { name, mail } = changeset.author;
|
||||
if (mail) {
|
||||
return this.withExtensionPoint(this.renderWithMail(name, mail));
|
||||
}
|
||||
return this.withExtensionPoint(<>{name}</>);
|
||||
}
|
||||
|
||||
renderWithMail(name: string, mail: string) {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{name} {this.renderMail()}
|
||||
</>
|
||||
<a href={"mailto: " + mail} title={t("changeset.author.mailto") + " " + mail}>
|
||||
{name}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderMail() {
|
||||
const { mail } = this.props.changeset.author;
|
||||
if (mail) {
|
||||
return (
|
||||
<a className="is-hidden-mobile" href={"mailto:" + mail}>
|
||||
<
|
||||
{mail}
|
||||
>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
withExtensionPoint(child: any) {
|
||||
const { t } = this.props;
|
||||
return (
|
||||
<>
|
||||
{t("changeset.author.prefix")} {child}
|
||||
<ExtensionPoint
|
||||
name="changesets.author.suffix"
|
||||
props={{ changeset: this.props.changeset }}
|
||||
renderAll={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangesetAuthor;
|
||||
export default translate("repos")(ChangesetAuthor);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
import ButtonGroup from "../../buttons/ButtonGroup";
|
||||
import Button from "../../buttons/Button";
|
||||
import { createChangesetLink, createSourcesLink } from "./changesets";
|
||||
import { translate } from "react-i18next";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
changeset: Changeset,
|
||||
|
||||
// context props
|
||||
t: (string) => string
|
||||
}
|
||||
|
||||
class ChangesetButtonGroup extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { repository, changeset, t } = this.props;
|
||||
|
||||
const changesetLink = createChangesetLink(repository, changeset);
|
||||
const sourcesLink = createSourcesLink(repository, changeset);
|
||||
|
||||
return (
|
||||
<ButtonGroup className="is-pulled-right">
|
||||
<Button link={changesetLink}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-code-branch"></i>
|
||||
</span>
|
||||
<span className="is-hidden-mobile is-hidden-tablet-only">
|
||||
{t("changeset.buttons.details")}
|
||||
</span>
|
||||
</Button>
|
||||
<Button link={sourcesLink}>
|
||||
<span className="icon">
|
||||
<i className="fas fa-code"></i>
|
||||
</span>
|
||||
<span className="is-hidden-mobile is-hidden-tablet-only">
|
||||
{t("changeset.buttons.sources")}
|
||||
</span>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate("repos")(ChangesetButtonGroup);
|
||||
@@ -25,7 +25,7 @@ class ChangesetDiff extends React.Component<Props> {
|
||||
render() {
|
||||
const { changeset, t } = this.props;
|
||||
if (!this.isDiffSupported(changeset)) {
|
||||
return <Notification type="danger">{t("changesets.changeset.diffNotSupported")}</Notification>;
|
||||
return <Notification type="danger">{t("changeset.diffNotSupported")}</Notification>;
|
||||
} else {
|
||||
const url = this.createUrl(changeset);
|
||||
return <LoadingDiff url={url} />;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import {Link} from "react-router-dom";
|
||||
import React from "react";
|
||||
import type {Changeset, Repository} from "@scm-manager/ui-types";
|
||||
import { createChangesetLink } from "./changesets";
|
||||
|
||||
type Props = {
|
||||
repository: Repository,
|
||||
@@ -20,13 +21,11 @@ export default class ChangesetId extends React.Component<Props> {
|
||||
};
|
||||
|
||||
renderLink = () => {
|
||||
const { changeset, repository } = this.props;
|
||||
const { repository, changeset } = this.props;
|
||||
const link = createChangesetLink(repository, changeset);
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/repo/${repository.namespace}/${repository.name}/changeset/${
|
||||
changeset.id
|
||||
}`}
|
||||
>
|
||||
<Link to={link}>
|
||||
{this.shortId(changeset)}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -8,21 +8,39 @@ import ChangesetId from "./ChangesetId";
|
||||
import injectSheet from "react-jss";
|
||||
import { DateFromNow } from "../..";
|
||||
import ChangesetAuthor from "./ChangesetAuthor";
|
||||
import ChangesetTag from "./ChangesetTag";
|
||||
|
||||
import { parseDescription } from "./changesets";
|
||||
import { AvatarWrapper, AvatarImage } from "../../avatar";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import ChangesetTags from "./ChangesetTags";
|
||||
import ChangesetButtonGroup from "./ChangesetButtonGroup";
|
||||
|
||||
const styles = {
|
||||
pointer: {
|
||||
cursor: "pointer"
|
||||
changeset: {
|
||||
// & references parent rule
|
||||
// have a look at https://cssinjs.org/jss-plugin-nested?v=v10.0.0-alpha.9
|
||||
"& + &": {
|
||||
borderTop: "1px solid rgba(219, 219, 219, 0.5)",
|
||||
marginTop: "1rem",
|
||||
paddingTop: "1rem"
|
||||
}
|
||||
},
|
||||
changesetGroup: {
|
||||
marginBottom: "1em"
|
||||
avatarFigure: {
|
||||
marginTop: ".25rem",
|
||||
marginRight: ".5rem",
|
||||
},
|
||||
withOverflow: {
|
||||
overflow: "auto"
|
||||
avatarImage: {
|
||||
height: "35px",
|
||||
width: "35px"
|
||||
},
|
||||
isVcentered: {
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto"
|
||||
},
|
||||
metadata: {
|
||||
marginLeft: 0
|
||||
},
|
||||
tag: {
|
||||
marginTop: ".5rem"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,74 +52,70 @@ type Props = {
|
||||
};
|
||||
|
||||
class ChangesetRow extends React.Component<Props> {
|
||||
createLink = (changeset: Changeset) => {
|
||||
createChangesetId = (changeset: Changeset) => {
|
||||
const { repository } = this.props;
|
||||
return <ChangesetId changeset={changeset} repository={repository} />;
|
||||
};
|
||||
|
||||
getTags = () => {
|
||||
const { changeset } = this.props;
|
||||
return changeset._embedded.tags || [];
|
||||
};
|
||||
|
||||
render() {
|
||||
const { changeset, classes } = this.props;
|
||||
const changesetLink = this.createLink(changeset);
|
||||
const dateFromNow = <DateFromNow date={changeset.date} />;
|
||||
const authorLine = <ChangesetAuthor changeset={changeset} />;
|
||||
const { repository, changeset, classes } = this.props;
|
||||
const description = parseDescription(changeset.description);
|
||||
const changesetId = this.createChangesetId(changeset);
|
||||
const dateFromNow = <DateFromNow date={changeset.date} />;
|
||||
|
||||
return (
|
||||
<article className={classNames("media", classes.inner)}>
|
||||
<AvatarWrapper>
|
||||
<div>
|
||||
<figure className="media-left">
|
||||
<p className="image is-64x64">
|
||||
<AvatarImage person={changeset.author} />
|
||||
</p>
|
||||
</figure>
|
||||
<div className={classes.changeset}>
|
||||
<div className="columns">
|
||||
<div className="column is-three-fifths">
|
||||
|
||||
<h4 className="has-text-weight-bold is-ellipsis-overflow">
|
||||
<ExtensionPoint
|
||||
name="changeset.description"
|
||||
props={{ changeset, value: description.title }}
|
||||
renderAll={false}
|
||||
>
|
||||
{description.title}
|
||||
</ExtensionPoint>
|
||||
</h4>
|
||||
|
||||
<div className="media">
|
||||
<AvatarWrapper>
|
||||
<figure className={classNames(classes.avatarFigure, "media-left")}>
|
||||
<div className={classNames("image", classes.avatarImage)}>
|
||||
<AvatarImage person={changeset.author} />
|
||||
</div>
|
||||
</figure>
|
||||
</AvatarWrapper>
|
||||
<div className={classNames(classes.metadata, "media-right")}>
|
||||
<p className="is-hidden-mobile is-hidden-tablet-only">
|
||||
<Interpolate
|
||||
i18nKey="changeset.summary"
|
||||
id={changesetId}
|
||||
time={dateFromNow}
|
||||
/>
|
||||
</p>
|
||||
<p className="is-hidden-desktop">
|
||||
<Interpolate
|
||||
i18nKey="changeset.shortSummary"
|
||||
id={changesetId}
|
||||
time={dateFromNow}
|
||||
/>
|
||||
</p>
|
||||
<p className="is-size-7">
|
||||
<ChangesetAuthor changeset={changeset} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</AvatarWrapper>
|
||||
<div className={classNames("media-content", classes.withOverflow)}>
|
||||
<div className="content">
|
||||
<p className="is-ellipsis-overflow">
|
||||
<strong>
|
||||
<ExtensionPoint
|
||||
name="changesets.changeset.description"
|
||||
props={{ changeset, value: description.title }}
|
||||
renderAll={false}
|
||||
>
|
||||
{description.title}
|
||||
</ExtensionPoint>
|
||||
</strong>
|
||||
<br />
|
||||
<Interpolate
|
||||
i18nKey="changesets.changeset.summary"
|
||||
id={changesetLink}
|
||||
time={dateFromNow}
|
||||
/>
|
||||
</p>{" "}
|
||||
<div className="is-size-7">{authorLine}</div>
|
||||
<div className={classNames("column", classes.isVcentered)}>
|
||||
<ChangesetTags changeset={changeset} />
|
||||
<ChangesetButtonGroup repository={repository} changeset={changeset} />
|
||||
</div>
|
||||
</div>
|
||||
{this.renderTags()}
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTags = () => {
|
||||
const tags = this.getTags();
|
||||
if (tags.length > 0) {
|
||||
return (
|
||||
<div className="media-right">
|
||||
{tags.map((tag: Tag) => {
|
||||
return <ChangesetTag key={tag.name} tag={tag} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(translate("repos")(ChangesetRow));
|
||||
|
||||
@@ -1,32 +1,17 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Tag } from "@scm-manager/ui-types";
|
||||
import injectSheet from "react-jss";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
spacing: {
|
||||
marginRight: "4px"
|
||||
}
|
||||
};
|
||||
import ChangesetTagBase from "./ChangesetTagBase";
|
||||
|
||||
type Props = {
|
||||
tag: Tag,
|
||||
|
||||
// context props
|
||||
classes: Object
|
||||
tag: Tag
|
||||
};
|
||||
|
||||
class ChangesetTag extends React.Component<Props> {
|
||||
render() {
|
||||
const { tag, classes } = this.props;
|
||||
return (
|
||||
<span className="tag is-info">
|
||||
<span className={classNames("fa", "fa-tag", classes.spacing)} />{" "}
|
||||
{tag.name}
|
||||
</span>
|
||||
);
|
||||
const { tag } = this.props;
|
||||
return <ChangesetTagBase icon={"fa-tag"} label={tag.name} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(ChangesetTag);
|
||||
export default ChangesetTag;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import injectSheet from "react-jss";
|
||||
import classNames from "classnames";
|
||||
|
||||
const styles = {
|
||||
tag: {
|
||||
marginTop: ".5rem"
|
||||
},
|
||||
spacing: {
|
||||
marginRight: ".25rem"
|
||||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
icon: string,
|
||||
label: string,
|
||||
|
||||
// context props
|
||||
classes: Object
|
||||
};
|
||||
|
||||
class ChangesetTagBase extends React.Component<Props> {
|
||||
render() {
|
||||
const { icon, label, classes } = this.props;
|
||||
return (
|
||||
<span className={classNames(classes.tag, "tag", "is-info")}>
|
||||
<span className={classNames("fa", icon, classes.spacing)} /> {label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectSheet(styles)(ChangesetTagBase);
|
||||
@@ -0,0 +1,31 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Changeset} from "@scm-manager/ui-types";
|
||||
import ChangesetTag from "./ChangesetTag";
|
||||
import ChangesetTagsCollapsed from "./ChangesetTagsCollapsed";
|
||||
|
||||
type Props = {
|
||||
changeset: Changeset
|
||||
};
|
||||
|
||||
class ChangesetTags extends React.Component<Props> {
|
||||
|
||||
getTags = () => {
|
||||
const { changeset } = this.props;
|
||||
return changeset._embedded.tags || [];
|
||||
};
|
||||
|
||||
render() {
|
||||
const tags = this.getTags();
|
||||
|
||||
if (tags.length === 1) {
|
||||
return <ChangesetTag tag={tags[0]} />;
|
||||
} else if (tags.length > 1) {
|
||||
return <ChangesetTagsCollapsed tags={tags} />;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangesetTags;
|
||||
@@ -0,0 +1,27 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import type { Tag } from "@scm-manager/ui-types";
|
||||
import ChangesetTagBase from "./ChangesetTagBase";
|
||||
import { translate } from "react-i18next";
|
||||
import Tooltip from "../../Tooltip";
|
||||
|
||||
type Props = {
|
||||
tags: Tag[],
|
||||
|
||||
// context props
|
||||
t: (string) => string
|
||||
};
|
||||
|
||||
class ChangesetTagsCollapsed extends React.Component<Props> {
|
||||
render() {
|
||||
const { tags, t } = this.props;
|
||||
const message = tags.map((tag) => tag.name).join(", ");
|
||||
return (
|
||||
<Tooltip location="top" message={message}>
|
||||
<ChangesetTagBase icon={"fa-tags"} label={ tags.length + " " + t("changeset.tags") } />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate("repos")(ChangesetTagsCollapsed);
|
||||
@@ -1,9 +1,19 @@
|
||||
// @flow
|
||||
import type { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
|
||||
export type Description = {
|
||||
title: string,
|
||||
message: string
|
||||
};
|
||||
|
||||
export function createChangesetLink(repository: Repository, changeset: Changeset) {
|
||||
return `/repo/${repository.namespace}/${repository.name}/changeset/${changeset.id}`;
|
||||
}
|
||||
|
||||
export function createSourcesLink(repository: Repository, changeset: Changeset) {
|
||||
return `/repo/${repository.namespace}/${repository.name}/sources/${changeset.id}`;
|
||||
}
|
||||
|
||||
export function parseDescription(description?: string): Description {
|
||||
const desc = description ? description : "";
|
||||
const lineBreak = desc.indexOf("\n");
|
||||
|
||||
@@ -3,8 +3,11 @@ import * as changesets from "./changesets";
|
||||
export { changesets };
|
||||
|
||||
export { default as ChangesetAuthor } from "./ChangesetAuthor";
|
||||
export { default as ChangesetButtonGroup } from "./ChangesetButtonGroup";
|
||||
export { default as ChangesetDiff } from "./ChangesetDiff";
|
||||
export { default as ChangesetId } from "./ChangesetId";
|
||||
export { default as ChangesetList } from "./ChangesetList";
|
||||
export { default as ChangesetRow } from "./ChangesetRow";
|
||||
export { default as ChangesetTag } from "./ChangesetTag";
|
||||
export { default as ChangesetDiff } from "./ChangesetDiff";
|
||||
export { default as ChangesetTags } from "./ChangesetTags";
|
||||
export { default as ChangesetTagsCollapsed } from "./ChangesetTagsCollapsed";
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"i18next": "^11.4.0",
|
||||
"i18next-browser-languagedetector": "^2.2.2",
|
||||
"i18next-fetch-backend": "^0.1.0",
|
||||
"jss-nested": "^6.0.1",
|
||||
"moment": "^2.22.2",
|
||||
"node-sass": "^4.9.3",
|
||||
"postcss-easy-import": "^3.0.0",
|
||||
|
||||
@@ -42,18 +42,21 @@
|
||||
"changesets": {
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Changesets konnten nicht abgerufen werden",
|
||||
"branchSelectorLabel": "Branches",
|
||||
"changeset": {
|
||||
"description": "Beschreibung",
|
||||
"summary": "Changeset {{id}} wurde committet {{time}}",
|
||||
"diffNotSupported": "Diff des Changesets wird von diesem Repositorytyp nicht unterstützt",
|
||||
"id": "ID",
|
||||
"contact": "Kontakt",
|
||||
"date": "Datum"
|
||||
},
|
||||
"branchSelectorLabel": "Branches"
|
||||
},
|
||||
"changeset": {
|
||||
"description": "Beschreibung",
|
||||
"summary": "Changeset {{id}} wurde committet {{time}}",
|
||||
"shortSummary": "Committet {{id}} {{time}}",
|
||||
"tags": "Tags",
|
||||
"diffNotSupported": "Diff des Changesets wird von diesem Repositorytyp nicht unterstützt",
|
||||
"author": {
|
||||
"name": "Autor",
|
||||
"mail": "Mail"
|
||||
"prefix": "Verfasst von",
|
||||
"mailto": "Mail senden an"
|
||||
},
|
||||
"buttons": {
|
||||
"details": "Details",
|
||||
"sources": "Sources"
|
||||
}
|
||||
},
|
||||
"repositoryForm": {
|
||||
|
||||
@@ -42,18 +42,21 @@
|
||||
"changesets": {
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Could not fetch changesets",
|
||||
"branchSelectorLabel": "Branches",
|
||||
"changeset": {
|
||||
"description": "Description",
|
||||
"summary": "Changeset {{id}} was committed {{time}}",
|
||||
"diffNotSupported": "Diff of changesets is not supported by the type of repository",
|
||||
"id": "ID",
|
||||
"contact": "Contact",
|
||||
"date": "Date"
|
||||
},
|
||||
"branchSelectorLabel": "Branches"
|
||||
},
|
||||
"changeset": {
|
||||
"description": "Description",
|
||||
"summary": "Changeset {{id}} was committed {{time}}",
|
||||
"shortSummary": "Committed {{id}} {{time}}",
|
||||
"tags": "Tags",
|
||||
"diffNotSupported": "Diff of changesets is not supported by the type of repository",
|
||||
"author": {
|
||||
"name": "Author",
|
||||
"mail": "Mail"
|
||||
"prefix": "Authored by",
|
||||
"mailto": "Send mail to"
|
||||
},
|
||||
"buttons": {
|
||||
"details": "Details",
|
||||
"sources": "Sources"
|
||||
}
|
||||
},
|
||||
"repositoryForm": {
|
||||
|
||||
@@ -17,6 +17,12 @@ import { ConnectedRouter } from "react-router-redux";
|
||||
|
||||
import { urls } from "@scm-manager/ui-components";
|
||||
|
||||
import jss from "jss";
|
||||
import jssNested from "jss-nested";
|
||||
|
||||
// setup jss and install required plugins
|
||||
jss.setup(jssNested());
|
||||
|
||||
// Create a history of your choosing (we're using a browser history in this case)
|
||||
const history: BrowserHistory = createHistory({
|
||||
basename: urls.contextPath
|
||||
|
||||
@@ -22,6 +22,11 @@ import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
const styles = {
|
||||
spacing: {
|
||||
marginRight: "1em"
|
||||
},
|
||||
tags: {
|
||||
"& .tag": {
|
||||
marginLeft: ".25rem"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,7 +53,7 @@ class ChangesetDetails extends React.Component<Props> {
|
||||
<div className="content">
|
||||
<h4>
|
||||
<ExtensionPoint
|
||||
name="changesets.changeset.description"
|
||||
name="changeset.description"
|
||||
props={{ changeset, value: description.title }}
|
||||
renderAll={false}
|
||||
>
|
||||
@@ -67,7 +72,7 @@ class ChangesetDetails extends React.Component<Props> {
|
||||
</p>
|
||||
<p>
|
||||
<Interpolate
|
||||
i18nKey="changesets.changeset.summary"
|
||||
i18nKey="changeset.summary"
|
||||
id={id}
|
||||
time={date}
|
||||
/>
|
||||
@@ -81,7 +86,7 @@ class ChangesetDetails extends React.Component<Props> {
|
||||
return (
|
||||
<span key={key}>
|
||||
<ExtensionPoint
|
||||
name="changesets.changeset.description"
|
||||
name="changeset.description"
|
||||
props={{ changeset, value: item }}
|
||||
renderAll={false}
|
||||
>
|
||||
@@ -106,10 +111,11 @@ class ChangesetDetails extends React.Component<Props> {
|
||||
};
|
||||
|
||||
renderTags = () => {
|
||||
const { classes } = this.props;
|
||||
const tags = this.getTags();
|
||||
if (tags.length > 0) {
|
||||
return (
|
||||
<div className="level-item">
|
||||
<div className={classNames("level-item", classes.tags)}>
|
||||
{tags.map((tag: Tag) => {
|
||||
return <ChangesetTag key={tag.name} tag={tag} />;
|
||||
})}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import { translate } from "react-i18next";
|
||||
import { ButtonGroup } from "@scm-manager/ui-components";
|
||||
import { ButtonGroup, Button } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
t: string => string,
|
||||
@@ -18,39 +18,32 @@ class FileButtonGroup extends React.Component<Props> {
|
||||
this.props.showHistory(false);
|
||||
};
|
||||
|
||||
color = (selected: boolean) => {
|
||||
return selected ? "link is-selected" : null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, historyIsSelected } = this.props;
|
||||
|
||||
const sourcesLabel = (
|
||||
<>
|
||||
<span className="icon">
|
||||
<i className="fas fa-code" />
|
||||
</span>
|
||||
<span className="is-hidden-mobile">
|
||||
{t("sources.content.sourcesButton")}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
const historyLabel = (
|
||||
<>
|
||||
<span className="icon">
|
||||
<i className="fas fa-history" />
|
||||
</span>
|
||||
<span className="is-hidden-mobile">
|
||||
{t("sources.content.historyButton")}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<ButtonGroup
|
||||
firstlabel={sourcesLabel}
|
||||
secondlabel={historyLabel}
|
||||
firstAction={this.showSources}
|
||||
secondAction={this.showHistory}
|
||||
firstIsSelected={!historyIsSelected}
|
||||
/>
|
||||
<ButtonGroup>
|
||||
<Button action={this.showSources} color={ this.color(!historyIsSelected) }>
|
||||
<span className="icon">
|
||||
<i className="fas fa-code"/>
|
||||
</span>
|
||||
<span className="is-hidden-mobile">
|
||||
{t("sources.content.sourcesButton")}
|
||||
</span>
|
||||
</Button>
|
||||
<Button action={this.showHistory} color={ this.color(historyIsSelected) }>
|
||||
<span className="icon">
|
||||
<i className="fas fa-history"/>
|
||||
</span>
|
||||
<span className="is-hidden-mobile">
|
||||
{t("sources.content.historyButton")}
|
||||
</span>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user