mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-12-20 15:29:48 +01:00
Set descriptive document titles
Document titles represent the pages, for example in lists with bookmarks. They are important for navigation and orientation in websites. If the offer or the content of the page is not labeled, orientation is impaired. This changes the behavior for setting document titles. The functionality has been removed from the Page and Title components and is now represented by `useDocumentTitle` hook to better describe the content of inividual pages. Co-authored-by: Anna Vetcininova<anna.vetcininova@cloudogu.com>
This commit is contained in:
2
gradle/changelog/document_title.yaml
Normal file
2
gradle/changelog/document_title.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- type: changed
|
||||
description: Replace title behavior with `useDocumentTitle` hook for setting descriptive document titles
|
||||
@@ -15,11 +15,12 @@
|
||||
*/
|
||||
|
||||
import React, { FC, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Title, ConfigurationForm, InputField, Checkbox, validation } from "@scm-manager/ui-components";
|
||||
import { useConfigLink } from "@scm-manager/ui-api";
|
||||
import { HalRepresentation } from "@scm-manager/ui-types";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useConfigLink } from "@scm-manager/ui-api";
|
||||
import { ConfigurationForm, InputField, Checkbox, validation } from "@scm-manager/ui-components";
|
||||
import { Title, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { HalRepresentation } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
link: string;
|
||||
@@ -37,6 +38,7 @@ type Configuration = HalRepresentation & {
|
||||
|
||||
const GitGlobalConfiguration: FC<Props> = ({ link }) => {
|
||||
const [t] = useTranslation("plugins");
|
||||
useDocumentTitle(t("scm-git-plugin.config.title"));
|
||||
|
||||
const { initialConfiguration, isReadOnly, update, ...formProps } = useConfigLink<Configuration>(link);
|
||||
const { formState, handleSubmit, register, reset } = useForm<Configuration>({ mode: "onChange" });
|
||||
@@ -45,7 +47,7 @@ const GitGlobalConfiguration: FC<Props> = ({ link }) => {
|
||||
if (initialConfiguration) {
|
||||
reset(initialConfiguration);
|
||||
}
|
||||
}, [initialConfiguration]);
|
||||
}, [initialConfiguration, reset]);
|
||||
|
||||
const isValidDefaultBranch = (value: string) => {
|
||||
return validation.isBranchValid(value);
|
||||
@@ -58,7 +60,7 @@ const GitGlobalConfiguration: FC<Props> = ({ link }) => {
|
||||
onSubmit={handleSubmit(update)}
|
||||
{...formProps}
|
||||
>
|
||||
<Title title={t("scm-git-plugin.config.title")} />
|
||||
<Title>{t("scm-git-plugin.config.title")}</Title>
|
||||
<InputField
|
||||
label={t("scm-git-plugin.config.gcExpression")}
|
||||
helpText={t("scm-git-plugin.config.gcExpressionHelpText")}
|
||||
|
||||
@@ -14,25 +14,26 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Title, Configuration } from "@scm-manager/ui-components";
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Configuration } from "@scm-manager/ui-components";
|
||||
import { Title, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import HgConfigurationForm from "./HgConfigurationForm";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
type Props = {
|
||||
link: string;
|
||||
};
|
||||
|
||||
class HgGlobalConfiguration extends React.Component<Props> {
|
||||
render() {
|
||||
const { link, t } = this.props;
|
||||
const HgGlobalConfiguration: FC<Props> = ({ link }) => {
|
||||
const [t] = useTranslation("plugins");
|
||||
useDocumentTitle(t("scm-hg-plugin.config.title"));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title title={t("scm-hg-plugin.config.title")} />
|
||||
<Title>{t("scm-hg-plugin.config.title")}</Title>
|
||||
<Configuration link={link} render={(props: any) => <HgConfigurationForm {...props} />} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default withTranslation("plugins")(HgGlobalConfiguration);
|
||||
export default HgGlobalConfiguration;
|
||||
|
||||
@@ -14,25 +14,26 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import { Title, Configuration } from "@scm-manager/ui-components";
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Configuration } from "@scm-manager/ui-components";
|
||||
import { Title, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import SvnConfigurationForm from "./SvnConfigurationForm";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
type Props = {
|
||||
link: string;
|
||||
};
|
||||
|
||||
class SvnGlobalConfiguration extends React.Component<Props> {
|
||||
render() {
|
||||
const { link, t } = this.props;
|
||||
const SvnGlobalConfiguration: FC<Props> = ({ link }) => {
|
||||
const [t] = useTranslation("plugins");
|
||||
useDocumentTitle(t("scm-svn-plugin.config.title"));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title title={t("scm-svn-plugin.config.title")} />
|
||||
<Title>{t("scm-svn-plugin.config.title")}</Title>
|
||||
<Configuration link={link} render={(props: any) => <SvnConfigurationForm {...props} />} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default withTranslation("plugins")(SvnGlobalConfiguration);
|
||||
export default SvnGlobalConfiguration;
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { MissingLinkError, urls, useIndexLink } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import ErrorNotification from "./ErrorNotification";
|
||||
import ErrorPage from "./ErrorPage";
|
||||
import { Subtitle, Title } from "./layout";
|
||||
@@ -53,6 +54,7 @@ const RedirectIconContainer = styled.div`
|
||||
|
||||
const RedirectPage = () => {
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("errorNotification.prefix"));
|
||||
// we use an icon instead of loading spinner,
|
||||
// because a redirect is synchron and a spinner does not spin on a synchron action
|
||||
return (
|
||||
@@ -106,7 +108,7 @@ const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, errorInfo, fallback: Fallb
|
||||
|
||||
const fallbackProps = {
|
||||
error,
|
||||
errorInfo
|
||||
errorInfo,
|
||||
};
|
||||
|
||||
return <FallbackComponent {...fallbackProps} />;
|
||||
@@ -128,7 +130,7 @@ class ErrorBoundary extends React.Component<Props, State> {
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
this.setState({
|
||||
error,
|
||||
errorInfo
|
||||
errorInfo,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import ErrorNotification from "./ErrorNotification";
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BackendError, ForbiddenError } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import ErrorNotification from "./ErrorNotification";
|
||||
|
||||
type Props = {
|
||||
error: Error;
|
||||
@@ -24,28 +26,26 @@ type Props = {
|
||||
subtitle: string;
|
||||
};
|
||||
|
||||
class ErrorPage extends React.Component<Props> {
|
||||
render() {
|
||||
const { title, error } = this.props;
|
||||
const ErrorPage: FC<Props> = ({ error, title, subtitle }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("errorNotification.prefix"));
|
||||
|
||||
return (
|
||||
<section className="section">
|
||||
<div className="box column is-4 is-offset-4 container">
|
||||
<h1 className="title">{title}</h1>
|
||||
{this.renderSubtitle()}
|
||||
<ErrorNotification error={error} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
renderSubtitle = () => {
|
||||
const { error, subtitle } = this.props;
|
||||
const renderSubtitle = () => {
|
||||
if (error instanceof BackendError || error instanceof ForbiddenError) {
|
||||
return null;
|
||||
}
|
||||
return <p className="subtitle">{subtitle}</p>;
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="section">
|
||||
<div className="box column is-4 is-offset-4 container">
|
||||
<h1 className="title">{title}</h1>
|
||||
{renderSubtitle()}
|
||||
<ErrorNotification error={error} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default ErrorPage;
|
||||
|
||||
@@ -14,20 +14,19 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import React, { FC, ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import Loading from "./../Loading";
|
||||
import ErrorNotification from "./../ErrorNotification";
|
||||
import ErrorBoundary from "../ErrorBoundary";
|
||||
import Title from "./Title";
|
||||
import Subtitle from "./Subtitle";
|
||||
import PageActions from "./PageActions";
|
||||
import ErrorBoundary from "../ErrorBoundary";
|
||||
|
||||
type Props = {
|
||||
title?: ReactNode;
|
||||
// Use the documentTitle to set the document title (browser title) whenever you want to set one
|
||||
// and use something different than a string for the title property.
|
||||
// [DEPRECATED] Use useDocumentTitle hook inside the component instead.
|
||||
documentTitle?: string;
|
||||
afterTitle?: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
@@ -44,43 +43,19 @@ const PageActionContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default class Page extends React.Component<Props> {
|
||||
componentDidUpdate() {
|
||||
const textualTitle = this.getTextualTitle();
|
||||
if (textualTitle && textualTitle !== document.title) {
|
||||
document.title = textualTitle;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { error } = this.props;
|
||||
return (
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
{this.renderPageHeader()}
|
||||
<ErrorBoundary>
|
||||
<ErrorNotification error={error} />
|
||||
{this.renderContent()}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
isPageAction(node: any) {
|
||||
const Page: FC<Props> = ({ title, afterTitle, subtitle, loading, error, showContentOnError, children }) => {
|
||||
const isPageAction = (node: any) => {
|
||||
return (
|
||||
node.displayName === PageActions.displayName || (node.type && node.type.displayName === PageActions.displayName)
|
||||
);
|
||||
}
|
||||
|
||||
renderPageHeader() {
|
||||
const { error, afterTitle, title, subtitle, children } = this.props;
|
||||
};
|
||||
|
||||
const renderPageHeader = () => {
|
||||
let pageActions = null;
|
||||
let pageActionsExists = false;
|
||||
React.Children.forEach(children, child => {
|
||||
React.Children.forEach(children, (child) => {
|
||||
if (child && !error) {
|
||||
if (this.isPageAction(child)) {
|
||||
if (isPageAction(child)) {
|
||||
pageActions = (
|
||||
<PageActionContainer
|
||||
className={classNames(
|
||||
@@ -99,6 +74,7 @@ export default class Page extends React.Component<Props> {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const underline = pageActionsExists ? <hr className="header-with-actions" /> : null;
|
||||
|
||||
if (title || subtitle) {
|
||||
@@ -107,9 +83,7 @@ export default class Page extends React.Component<Props> {
|
||||
<div className="columns">
|
||||
<div className="column">
|
||||
<div className="is-flex is-flex-wrap-wrap is-align-items-center">
|
||||
<Title className="mb-0 mr-2" title={this.getTextualTitle()}>
|
||||
{this.getTitleComponent()}
|
||||
</Title>
|
||||
<Title className="mb-0 mr-2">{title}</Title>
|
||||
{afterTitle}
|
||||
</div>
|
||||
{subtitle ? <Subtitle>{subtitle}</Subtitle> : null}
|
||||
@@ -121,11 +95,9 @@ export default class Page extends React.Component<Props> {
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const { loading, children, showContentOnError, error } = this.props;
|
||||
};
|
||||
|
||||
const renderContent = () => {
|
||||
if (error && !showContentOnError) {
|
||||
return null;
|
||||
}
|
||||
@@ -134,33 +106,27 @@ export default class Page extends React.Component<Props> {
|
||||
}
|
||||
|
||||
const content: ReactNode[] = [];
|
||||
React.Children.forEach(children, child => {
|
||||
React.Children.forEach(children, (child) => {
|
||||
if (child) {
|
||||
if (!this.isPageAction(child)) {
|
||||
if (!isPageAction(child)) {
|
||||
content.push(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
return content;
|
||||
}
|
||||
|
||||
getTextualTitle: () => string | undefined = () => {
|
||||
const { title, documentTitle } = this.props;
|
||||
if (documentTitle) {
|
||||
return documentTitle;
|
||||
} else if (typeof title === "string") {
|
||||
return title;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
getTitleComponent = () => {
|
||||
const { title } = this.props;
|
||||
if (title && typeof title !== "string") {
|
||||
return title;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
{renderPageHeader()}
|
||||
<ErrorBoundary>
|
||||
<ErrorNotification error={error} />
|
||||
{renderContent()}
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React, { FC, useEffect } from "react";
|
||||
import React, { FC } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
@@ -28,17 +28,7 @@ type Props = {
|
||||
* @deprecated Please import the identical module from "@scm-manager/ui-core"
|
||||
*/
|
||||
|
||||
const Title: FC<Props> = ({ title, preventRefreshingPageTitle, customPageTitle, className, children }) => {
|
||||
useEffect(() => {
|
||||
if (!preventRefreshingPageTitle) {
|
||||
if (customPageTitle) {
|
||||
document.title = customPageTitle;
|
||||
} else if (title) {
|
||||
document.title = title;
|
||||
}
|
||||
}
|
||||
}, [title, preventRefreshingPageTitle, customPageTitle]);
|
||||
|
||||
const Title: FC<Props> = ({ title, className, children }) => {
|
||||
if (children) {
|
||||
return <h1 className={classNames("title", className)}>{children}</h1>;
|
||||
} else if (title) {
|
||||
|
||||
@@ -15,4 +15,5 @@
|
||||
*/
|
||||
|
||||
export { default as useAriaId } from "./useAriaId";
|
||||
export { default as useDocumentTitle, useDocumentTitleForRepository } from "./useDocumentTitle";
|
||||
export { createAttributesForTesting, isDevBuild } from "./devbuild";
|
||||
|
||||
48
scm-ui/ui-core/src/base/helpers/useDocumentTitle.test.ts
Normal file
48
scm-ui/ui-core/src/base/helpers/useDocumentTitle.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import { renderHook } from "@testing-library/react-hooks";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import { binder } from "@scm-manager/ui-extensions";
|
||||
import useDocumentTitle, { useDocumentTitleForRepository } from "./useDocumentTitle";
|
||||
|
||||
describe("useDocumentTitle", () => {
|
||||
it("should set document title", () => {
|
||||
renderHook(() => useDocumentTitle("Part1", "Part2"));
|
||||
expect(document.title).toBe("Part1 - Part2 - SCM-Manager");
|
||||
});
|
||||
|
||||
it("should append title if extension is a string", () => {
|
||||
binder.getExtension = () => ({ documentTitle: "myInstance" });
|
||||
renderHook(() => useDocumentTitle("Part1", "Part2"));
|
||||
expect(document.title).toBe("Part1 - Part2 - SCM-Manager (myInstance)");
|
||||
});
|
||||
|
||||
it("should modify title if extension is a function", () => {
|
||||
binder.getExtension = () => ({ documentTitle: (title: string) => `Modified: ${title}` });
|
||||
renderHook(() => useDocumentTitle("Part1", "Part2"));
|
||||
expect(document.title).toBe("Modified: Part1 - Part2 - SCM-Manager");
|
||||
});
|
||||
});
|
||||
|
||||
describe("useDocumentTitleForRepository", () => {
|
||||
const repository: Repository = { namespace: "namespace", name: "name" } as Repository;
|
||||
|
||||
it("should set the document title for a repository", () => {
|
||||
renderHook(() => useDocumentTitleForRepository(repository, "Part1", "Part2"));
|
||||
expect(document.title).toBe("Part1 - Part2 - namespace/name - SCM-Manager");
|
||||
});
|
||||
});
|
||||
51
scm-ui/ui-core/src/base/helpers/useDocumentTitle.ts
Normal file
51
scm-ui/ui-core/src/base/helpers/useDocumentTitle.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { binder, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
|
||||
/**
|
||||
* Hook to set the document title.
|
||||
*
|
||||
* @param titleParts - An array of title parts to be joined.
|
||||
* Title parts should be sorted with the highest specificity first.
|
||||
*/
|
||||
export default function useDocumentTitle(...titleParts: string[]) {
|
||||
useEffect(() => {
|
||||
const extension = binder.getExtension<extensionPoints.DocumentTitleExtensionPoint>("document.title");
|
||||
let title = `${titleParts.join(" - ")} - SCM-Manager`;
|
||||
if (extension) {
|
||||
if (typeof extension.documentTitle === "string") {
|
||||
title += ` (${extension.documentTitle})`;
|
||||
} else if (typeof extension.documentTitle === "function") {
|
||||
title = extension.documentTitle(title);
|
||||
}
|
||||
}
|
||||
document.title = title;
|
||||
}, [titleParts]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to set the document title for a repository.
|
||||
*
|
||||
* @param repository - The repository for which the title should be set.
|
||||
* @param titleParts - An array of title parts to be joined.
|
||||
* Title parts should be sorted with the highest specificity first.
|
||||
*/
|
||||
export function useDocumentTitleForRepository(repository: Repository, ...titleParts: string[]) {
|
||||
useDocumentTitle(...titleParts, repository.namespace + "/" + repository.name);
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React, { HTMLAttributes, useEffect } from "react";
|
||||
import React, { HTMLAttributes } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
@@ -24,16 +24,6 @@ type Props = {
|
||||
};
|
||||
const Title = React.forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement> & Props>(
|
||||
({ title, customPageTitle, preventRefreshingPageTitle, children, className, ...props }, ref) => {
|
||||
useEffect(() => {
|
||||
if (!preventRefreshingPageTitle) {
|
||||
if (customPageTitle) {
|
||||
document.title = customPageTitle;
|
||||
} else if (title) {
|
||||
document.title = title;
|
||||
}
|
||||
}
|
||||
}, [title, preventRefreshingPageTitle, customPageTitle]);
|
||||
|
||||
if (children || title) {
|
||||
return (
|
||||
<h1 className={classNames("title", className)} {...props} ref={ref}>
|
||||
|
||||
@@ -257,6 +257,11 @@ export type RepositoryOverviewListOptionsExtensionPoint = ExtensionPointDefiniti
|
||||
() => { pageSize?: number; showArchived?: boolean }
|
||||
>;
|
||||
|
||||
export type DocumentTitleExtensionPoint = ExtensionPointDefinition<
|
||||
"document.title",
|
||||
{ documentTitle: string | ((originalTitle: string) => string) }
|
||||
>;
|
||||
|
||||
// From docs
|
||||
|
||||
export type AdminNavigation = RenderableExtensionPointDefinition<"admin.navigation", { links: Links; url: string }>;
|
||||
|
||||
@@ -114,12 +114,14 @@
|
||||
"repositoryRole": {
|
||||
"navLink": "Berechtigungsrollen",
|
||||
"title": "Berechtigungsrollen",
|
||||
"titleWithPage": "Berechtigungsrollen Seite {{page}} von {{total}}",
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Berechtigungsrollen Fehler",
|
||||
"detailsTitle": "Berechtigungsrolle",
|
||||
"createSubtitle": "Berechtigungsrolle erstellen",
|
||||
"editSubtitle": "Berechtigungsrolle bearbeiten",
|
||||
"overview": {
|
||||
"title": "Übersicht aller verfügbaren Berechtigungsrollen",
|
||||
"subtitle": "Übersicht aller verfügbaren Berechtigungsrollen",
|
||||
"noPermissionRoles": "Keine Berechtigungsrollen gefunden.",
|
||||
"createButton": "Berechtigungsrolle erstellen"
|
||||
},
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"loading": "Lade Daten ..."
|
||||
},
|
||||
"logout": {
|
||||
"title": "Abmeldung",
|
||||
"error": {
|
||||
"title": "Abmeldung fehlgeschlagen",
|
||||
"subtitle": "Während der Abmeldung ist ein Fehler aufgetreten."
|
||||
@@ -135,6 +136,7 @@
|
||||
"previous": "Zurück"
|
||||
},
|
||||
"profile": {
|
||||
"subtitle": "Information",
|
||||
"navigationLabel": "Profil",
|
||||
"informationNavLink": "Information",
|
||||
"changePasswordNavLink": "Passwort ändern",
|
||||
@@ -263,6 +265,7 @@
|
||||
"ariaLabel": "Globale Suche",
|
||||
"placeholder": "Suche...",
|
||||
"title": "Suche",
|
||||
"titleWithPage": "Suche Seite {{page}} von {{total}}",
|
||||
"subtitle": "{{type}} Ergebnisse",
|
||||
"subtitleWithContext": "{{type}} Ergebnisse für in \"{{context}}\"",
|
||||
"withQueryType": " mit {{queryType}}",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"createButton": "Gruppe erstellen"
|
||||
},
|
||||
"singleGroup": {
|
||||
"settingsTitle": "Generelle Einstellungen",
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Gruppen Fehler",
|
||||
"menu": {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"skipLfsHelpText": "Ist diese Option aktiviert, werden beim Import keine (potentiell vorhandenen) LFS-Dateien in den SCM-Manager geladen. Diese Option ist nur für Git Repositories relevant."
|
||||
},
|
||||
"repositoryRoot": {
|
||||
"settingsTitle": "Generelle Einstellungen",
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Repository Fehler",
|
||||
"menu": {
|
||||
@@ -56,6 +57,9 @@
|
||||
},
|
||||
"overview": {
|
||||
"title": "Repositories",
|
||||
"titleWithPage": "Repositories Seite {{page}} von {{total}}",
|
||||
"titleWithNamespace": "Repositories in {{namespace}}",
|
||||
"titleWithNamespaceAndPage": "Repositories Seite {{page}} von {{total}} in {{namespace}}",
|
||||
"subtitle": "Übersicht aller verfügbaren Repositories",
|
||||
"noRepositories": "Keine Repositories gefunden.",
|
||||
"invalidNamespace": "Keine Repositories gefunden. Möglicherweise existiert der ausgewählte Namespace nicht.",
|
||||
@@ -172,7 +176,8 @@
|
||||
"cancel": "Nein",
|
||||
"submit": "Ja"
|
||||
}
|
||||
}
|
||||
},
|
||||
"branchWithNamespaceName": "Branch {{branch}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"compare": {
|
||||
"title": "Vergleiche Änderungen",
|
||||
@@ -183,6 +188,16 @@
|
||||
"target": "Target",
|
||||
"with": "Vergleiche Änderungen mit...",
|
||||
"filter": "Auswahl filtern...",
|
||||
"typeTitle": {
|
||||
"b": "Branch",
|
||||
"t": "Tag",
|
||||
"r": "Revision"
|
||||
},
|
||||
"type": {
|
||||
"b": "Branch",
|
||||
"t": "Tag",
|
||||
"r": "Revision"
|
||||
},
|
||||
"emptyResult": "Es wurden keine dem Filter entsprechenden Ergebnisse gefunden.",
|
||||
"tabs": {
|
||||
"b": "Branches",
|
||||
@@ -197,7 +212,8 @@
|
||||
"tabs": {
|
||||
"diff": "Diff",
|
||||
"commits": "Commits"
|
||||
}
|
||||
},
|
||||
"compareSourceAndTargetWithNamespaceName": "Vergleiche {{sourceType}} {{source}} mit {{targetType}} {{target}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"tags": {
|
||||
"overview": {
|
||||
@@ -249,7 +265,8 @@
|
||||
"cancel": "Nein",
|
||||
"submit": "Ja"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tagWithNamespaceName": "Tag {{tag}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"code": {
|
||||
"sources": "Sources",
|
||||
@@ -258,6 +275,11 @@
|
||||
"noBranches": "Keine Sources für das Repository gefunden."
|
||||
},
|
||||
"changesets": {
|
||||
"commitsWithNamespaceName": "Commits in {{namespace}}/{{name}}",
|
||||
"commitsWithPageAndNamespaceName": "Commits Seite {{page}} von {{total}} in {{namespace}}/{{name}}",
|
||||
"commitsWithPageRevisionAndNamespaceName": "Commits Seite {{page}} von {{total}} auf {{revision}} in {{namespace}}/{{name}}",
|
||||
"commitsWithRevisionAndNamespaceName": "Commits auf {{revision}} in {{namespace}}/{{name}}",
|
||||
"idWithNamespaceName": "{{id}} in {{namespace}}/{{name}}",
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Changesets konnten nicht abgerufen werden",
|
||||
"noChangesets": "Keine Changesets in diesem Branch gefunden. Die Commits könnten gelöscht worden sein.",
|
||||
@@ -420,7 +442,9 @@
|
||||
"notBound": "Keine Erweiterung angebunden."
|
||||
},
|
||||
"loadMore": "Laden",
|
||||
"moreFilesAvailable": "Es werden nur die ersten {{count}} Dateien angezeigt. Es sind weitere Dateien vorhanden."
|
||||
"moreFilesAvailable": "Es werden nur die ersten {{count}} Dateien angezeigt. Es sind weitere Dateien vorhanden.",
|
||||
"pathWithRevisionAndNamespaceName": "{{path}} von {{revision}} in {{namespace}}/{{name}}",
|
||||
"sourcesWithRevisionAndNamespaceName": "Sources von {{revision}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"permission": {
|
||||
"title": "Berechtigungen",
|
||||
@@ -547,6 +571,13 @@
|
||||
"started": "Die Reindizierung wurde erfolgreich gestartet. Dies ist eine asynchrone Operation und kann einige Zeit in Anspruch nehmen."
|
||||
},
|
||||
"diff": {
|
||||
"changes": {
|
||||
"add": "added",
|
||||
"delete": "deleted",
|
||||
"modify": "modified",
|
||||
"rename": "renamed",
|
||||
"copy": "copied"
|
||||
},
|
||||
"jumpToSource": "Zur Quelldatei springen",
|
||||
"jumpToTarget": "Zur vorherigen Version der Datei springen",
|
||||
"sideBySide": "Zur zweispaltigen Ansicht wechseln",
|
||||
@@ -587,7 +618,8 @@
|
||||
"notifications": {
|
||||
"queryToShort": "Tippe mindestens 2 Zeichen ein, um die Suche zu starten",
|
||||
"emptyResult": "Es wurden keine Ergebnisse für <0>{{query}}</0> gefunden"
|
||||
}
|
||||
},
|
||||
"searchWithRevisionAndNamespaceName": "Suche auf {{revision}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"shortcuts": {
|
||||
"info": "Wechsel zur Repository-Info",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"createButton": "Benutzer erstellen"
|
||||
},
|
||||
"singleUser": {
|
||||
"settingsTitle": "Generelle Einstellungen",
|
||||
"errorTitle": "Fehler",
|
||||
"errorSubtitle": "Unbekannter Benutzer Fehler",
|
||||
"menu": {
|
||||
|
||||
@@ -114,12 +114,14 @@
|
||||
"repositoryRole": {
|
||||
"navLink": "Permission Roles",
|
||||
"title": "Permission Roles",
|
||||
"titleWithPage": "Permission Roles page {{page}} of {{total}}",
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Unknown Permission Role Error",
|
||||
"detailsTitle": "Permission Role",
|
||||
"createSubtitle": "Create Permission Role",
|
||||
"editSubtitle": "Edit Permission Role",
|
||||
"overview": {
|
||||
"title": "Overview of all Permission Roles",
|
||||
"subtitle": "Overview of all Permission Roles",
|
||||
"noPermissionRoles": "No Permission Roles found.",
|
||||
"createButton": "Create Permission Role"
|
||||
},
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"error": "Error"
|
||||
},
|
||||
"logout": {
|
||||
"title": "Logout",
|
||||
"error": {
|
||||
"title": "Logout Failed",
|
||||
"subtitle": "Something went wrong during logout"
|
||||
@@ -136,6 +137,7 @@
|
||||
"previous": "Previous"
|
||||
},
|
||||
"profile": {
|
||||
"subtitle": "Information",
|
||||
"navigationLabel": "Profile",
|
||||
"informationNavLink": "Information",
|
||||
"changePasswordNavLink": "Change Password",
|
||||
@@ -264,6 +266,7 @@
|
||||
"ariaLabel": "Global search",
|
||||
"placeholder": "Search...",
|
||||
"title": "Search",
|
||||
"titleWithPage": "Search page {{page}} of {{total}}",
|
||||
"subtitle": "{{type}} results",
|
||||
"subtitleWithContext": "{{type}} results in \"{{context}}\"",
|
||||
"withQueryType": " with {{queryType}}",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"createButton": "Create Group"
|
||||
},
|
||||
"singleGroup": {
|
||||
"settingsTitle": "General Settings",
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Unknown group error",
|
||||
"menu": {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"skipLfsHelpText": "Check this if (potentially available) LFS files shall not be loaded into SCM-Manager during the import. This option is relevant only for git repositories."
|
||||
},
|
||||
"repositoryRoot": {
|
||||
"settingsTitle": "General Settings",
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Unknown repository error",
|
||||
"menu": {
|
||||
@@ -56,6 +57,9 @@
|
||||
},
|
||||
"overview": {
|
||||
"title": "Repositories",
|
||||
"titleWithPage": "Repositories page {{page}} of {{total}}",
|
||||
"titleWithNamespace": "Repositories in {{namespace}}",
|
||||
"titleWithNamespaceAndPage": "Repositories page {{page}} of {{total}} in {{namespace}}",
|
||||
"subtitle": "Overview of available repositories",
|
||||
"noRepositories": "No repositories found.",
|
||||
"invalidNamespace": "No repositories found. It's likely that the selected namespace does not exist.",
|
||||
@@ -172,7 +176,8 @@
|
||||
"cancel": "No",
|
||||
"submit": "Yes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"branchWithNamespaceName": "Branch {{branch}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"compare": {
|
||||
"title": "Compare Changes",
|
||||
@@ -183,6 +188,16 @@
|
||||
"target": "Target",
|
||||
"with": "Compare changes with...",
|
||||
"filter": "Filter selection...",
|
||||
"typeTitle": {
|
||||
"b": "Branch",
|
||||
"t": "Tag",
|
||||
"r": "Revision"
|
||||
},
|
||||
"type": {
|
||||
"b": "branch",
|
||||
"t": "tag",
|
||||
"r": "revision"
|
||||
},
|
||||
"emptyResult": "No results matching the filter were found.",
|
||||
"tabs": {
|
||||
"b": "Branches",
|
||||
@@ -197,7 +212,8 @@
|
||||
"tabs": {
|
||||
"diff": "Diff",
|
||||
"commits": "Commits"
|
||||
}
|
||||
},
|
||||
"compareSourceAndTargetWithNamespaceName": "Compare {{sourceType}} {{source}} with {{targetType}} {{target}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"tags": {
|
||||
"overview": {
|
||||
@@ -249,7 +265,8 @@
|
||||
"cancel": "No",
|
||||
"submit": "Yes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tagWithNamespaceName": "Tag {{tag}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"code": {
|
||||
"sources": "Sources",
|
||||
@@ -258,6 +275,11 @@
|
||||
"noBranches": "No sources found for this repository."
|
||||
},
|
||||
"changesets": {
|
||||
"commitsWithNamespaceName": "Commits in {{namespace}}/{{name}}",
|
||||
"commitsWithPageAndNamespaceName": "Commits page {{page}} of {{total}} in {{namespace}}/{{name}}",
|
||||
"commitsWithPageRevisionAndNamespaceName": "Commits page {{page}} of {{total}} on {{revision}} in {{namespace}}/{{name}}",
|
||||
"commitsWithRevisionAndNamespaceName": "Commits on {{revision}} in {{namespace}}/{{name}}",
|
||||
"idWithNamespaceName": "{{id}} in {{namespace}}/{{name}}",
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Could not fetch changesets",
|
||||
"noChangesets": "No changesets found for this branch. The commits could have been removed.",
|
||||
@@ -420,7 +442,9 @@
|
||||
"notBound": "No extension bound."
|
||||
},
|
||||
"loadMore": "Load",
|
||||
"moreFilesAvailable": "These are just the first {{count}} files. There are more files available."
|
||||
"moreFilesAvailable": "These are just the first {{count}} files. There are more files available.",
|
||||
"pathWithRevisionAndNamespaceName": "{{path}} on {{revision}} in {{namespace}}/{{name}}",
|
||||
"sourcesWithRevisionAndNamespaceName": "Sources on {{revision}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"permission": {
|
||||
"title": "Permissions",
|
||||
@@ -523,6 +547,17 @@
|
||||
"cancel": "No"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"tooltip": "Read only. The archive cannot be changed."
|
||||
},
|
||||
"exporting": {
|
||||
"tooltip": "Read only. The repository is currently being exported."
|
||||
},
|
||||
"healthCheckFailure": {
|
||||
"tooltip": "This repository has health check failures. Click to get details.",
|
||||
"title": "Health Check Failures",
|
||||
"close": "Close"
|
||||
},
|
||||
"runHealthCheck": {
|
||||
"button": "Run Health Checks",
|
||||
"subtitle": "Health Checks",
|
||||
@@ -535,17 +570,6 @@
|
||||
"description": "Deletes all existing search indices for this repository and recreates them from scratch. This may take a while.",
|
||||
"started": "Reindexing has been started successfully. This is an asynchronous operation and may take a while."
|
||||
},
|
||||
"archive": {
|
||||
"tooltip": "Read only. The archive cannot be changed."
|
||||
},
|
||||
"exporting": {
|
||||
"tooltip": "Read only. The repository is currently being exported."
|
||||
},
|
||||
"healthCheckFailure": {
|
||||
"tooltip": "This repository has health check failures. Click to get details.",
|
||||
"title": "Health Check Failures",
|
||||
"close": "Close"
|
||||
},
|
||||
"diff": {
|
||||
"changes": {
|
||||
"add": "added",
|
||||
@@ -594,7 +618,8 @@
|
||||
"notifications": {
|
||||
"queryToShort": "Type at least two characters to start the search",
|
||||
"emptyResult": "Nothing found for query <0>{{query}}</0>"
|
||||
}
|
||||
},
|
||||
"searchWithRevisionAndNamespaceName": "Search on {{revision}} in {{namespace}}/{{name}}"
|
||||
},
|
||||
"shortcuts": {
|
||||
"info": "Switch to repository info",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"createButton": "Create User"
|
||||
},
|
||||
"singleUser": {
|
||||
"settingsTitle": "General Settings",
|
||||
"errorTitle": "Error",
|
||||
"errorSubtitle": "Unknown user error",
|
||||
"menu": {
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { devices, ErrorNotification, Image, Loading, Subtitle, Title } from "@scm-manager/ui-components";
|
||||
import { useUpdateInfo, useVersion } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
const BoxShadowBox = styled.div`
|
||||
box-shadow: 0 2px 3px rgba(40, 177, 232, 0.1), 0 0 0 2px rgba(40, 177, 232, 0.2);
|
||||
@@ -41,6 +42,7 @@ const MobileWrapped = styled.article`
|
||||
|
||||
const AdminDetails: FC = () => {
|
||||
const [t] = useTranslation("admin");
|
||||
useDocumentTitle(t("admin.info.title"));
|
||||
const version = useVersion();
|
||||
const { data: updateInfo, error, isLoading } = useUpdateInfo();
|
||||
|
||||
@@ -64,7 +66,7 @@ const AdminDetails: FC = () => {
|
||||
<h3 className="has-text-weight-medium">{t("admin.info.newRelease.title")}</h3>
|
||||
<p>
|
||||
{t("admin.info.newRelease.description", {
|
||||
version: updateInfo?.latestVersion
|
||||
version: updateInfo?.latestVersion,
|
||||
})}
|
||||
</p>
|
||||
<a className="button is-warning is-pulled-right" target="_blank" href={updateInfo?.link} rel="noreferrer">
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Link } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Loading, Title } from "@scm-manager/ui-components";
|
||||
import ConfigForm from "../components/form/ConfigForm";
|
||||
import { useConfig, useIndexLinks, useNamespaceStrategies, useUpdateConfig } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
const GlobalConfig: FC = () => {
|
||||
const indexLinks = useIndexLinks();
|
||||
@@ -31,6 +32,7 @@ const GlobalConfig: FC = () => {
|
||||
isLoading: isLoadingNamespaceStrategies,
|
||||
} = useNamespaceStrategies();
|
||||
const [t] = useTranslation("config");
|
||||
useDocumentTitle(t("config.title"));
|
||||
const error = configLoadingError || namespaceStrategiesLoadingError || updateError || undefined;
|
||||
const isLoading = isLoadingNamespaceStrategies || isLoadingConfig;
|
||||
const canUpdateConfig = !!(config && (config._links.update as Link).href);
|
||||
|
||||
@@ -36,6 +36,7 @@ import CloudoguPlatformBanner from "../components/CloudoguPlatformBanner";
|
||||
import PluginCenterAuthInfo from "../components/PluginCenterAuthInfo";
|
||||
import styled from "styled-components";
|
||||
import { Button } from "@scm-manager/ui-buttons";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
export enum PluginAction {
|
||||
INSTALL = "install",
|
||||
@@ -70,6 +71,7 @@ const StickyHeader = styled.div`
|
||||
|
||||
const PluginsOverview: FC<Props> = ({ installed }) => {
|
||||
const [t] = useTranslation("admin");
|
||||
useDocumentTitle(installed ? t("plugins.installedSubtitle") : t("plugins.availableSubtitle"));
|
||||
const {
|
||||
data: availablePlugins,
|
||||
isLoading: isLoadingAvailablePlugins,
|
||||
|
||||
@@ -14,49 +14,54 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import { Button, Level } from "@scm-manager/ui-components";
|
||||
import { Level, LinkButton, Title, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import PermissionRoleDetailsTable from "./PermissionRoleDetailsTable";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
type Props = {
|
||||
role: RepositoryRole;
|
||||
url: string;
|
||||
};
|
||||
|
||||
class PermissionRoleDetails extends React.Component<Props> {
|
||||
renderEditButton() {
|
||||
const { t, url } = this.props;
|
||||
if (!!this.props.role._links.update) {
|
||||
const PermissionRoleDetails: FC<Props> = ({ role, url }) => {
|
||||
const [t] = useTranslation("admin");
|
||||
useDocumentTitle(t("repositoryRole.detailsTitle"));
|
||||
|
||||
const renderEditButton = () => {
|
||||
if (!!role._links.update) {
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<Level right={<Button label={t("repositoryRole.editButton")} link={`${url}/edit`} color="primary" />} />
|
||||
</>
|
||||
);
|
||||
<Level
|
||||
right={
|
||||
<LinkButton to={`${url}/edit`} variant="primary" color="primary">
|
||||
{t("repositoryRole.editButton")}
|
||||
</LinkButton>
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { role } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PermissionRoleDetailsTable role={role} />
|
||||
{this.renderEditButton()}
|
||||
<ExtensionPoint<extensionPoints.RepositoryRoleDetailsInformation>
|
||||
name="repositoryRole.role-details.information"
|
||||
renderAll={true}
|
||||
props={{
|
||||
role
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default withTranslation("admin")(PermissionRoleDetails);
|
||||
return (
|
||||
<>
|
||||
<Title>{t("repositoryRole.detailsTitle")}</Title>
|
||||
<PermissionRoleDetailsTable role={role} />
|
||||
{renderEditButton()}
|
||||
<ExtensionPoint<extensionPoints.RepositoryRoleDetailsInformation>
|
||||
name="repositoryRole.role-details.information"
|
||||
renderAll={true}
|
||||
props={{
|
||||
role,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PermissionRoleDetails;
|
||||
|
||||
@@ -20,9 +20,11 @@ import { ErrorNotification, Loading, Subtitle, Title } from "@scm-manager/ui-com
|
||||
import RepositoryRoleForm from "./RepositoryRoleForm";
|
||||
import { useCreateRepositoryRole } from "@scm-manager/ui-api";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
const CreateRepositoryRole: FC = () => {
|
||||
const [t] = useTranslation("admin");
|
||||
useDocumentTitle(t("repositoryRole.createSubtitle"));
|
||||
const { error, isLoading: loading, create, repositoryRole: created } = useCreateRepositoryRole();
|
||||
|
||||
if (created) {
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
import React, { FC } from "react";
|
||||
import RepositoryRoleForm from "./RepositoryRoleForm";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
|
||||
import { ErrorNotification, Loading, Subtitle, Title } from "@scm-manager/ui-components";
|
||||
import { RepositoryRole } from "@scm-manager/ui-types";
|
||||
import DeleteRepositoryRole from "./DeleteRepositoryRole";
|
||||
import { useUpdateRepositoryRole } from "@scm-manager/ui-api";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = {
|
||||
role: RepositoryRole;
|
||||
@@ -29,6 +30,7 @@ type Props = {
|
||||
|
||||
const EditRepositoryRole: FC<Props> = ({ role }) => {
|
||||
const [t] = useTranslation("admin");
|
||||
useDocumentTitle(t("repositoryRole.editSubtitle"));
|
||||
const { isUpdated, update, error, isLoading: loading } = useUpdateRepositoryRole();
|
||||
|
||||
if (isUpdated) {
|
||||
@@ -43,6 +45,7 @@ const EditRepositoryRole: FC<Props> = ({ role }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title title={t("repositoryRole.detailsTitle")} />
|
||||
<Subtitle subtitle={t("repositoryRole.editSubtitle")} />
|
||||
<RepositoryRoleForm role={role} submitForm={update} />
|
||||
<DeleteRepositoryRole role={role} />
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
} from "@scm-manager/ui-components";
|
||||
import PermissionRoleTable from "../components/PermissionRoleTable";
|
||||
import { useRepositoryRoles } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type RepositoryRolesPageProps = {
|
||||
data?: RepositoryRoleCollection;
|
||||
@@ -62,6 +63,11 @@ const RepositoryRoles: FC<Props> = ({ baseUrl }) => {
|
||||
const page = urls.getPageFromMatch({ params });
|
||||
const { isLoading: loading, error, data } = useRepositoryRoles({ page: page - 1 });
|
||||
const [t] = useTranslation("admin");
|
||||
useDocumentTitle(
|
||||
data?.pageTotal && data.pageTotal > 1 && page
|
||||
? t("repositoryRole.titleWithPage", { page, total: data.pageTotal })
|
||||
: t("repositoryRole.title")
|
||||
);
|
||||
const canAddRoles = !!data?._links.create;
|
||||
|
||||
if (error) {
|
||||
@@ -79,7 +85,7 @@ const RepositoryRoles: FC<Props> = ({ baseUrl }) => {
|
||||
return (
|
||||
<>
|
||||
<Title title={t("repositoryRole.title")} />
|
||||
<Subtitle subtitle={t("repositoryRole.overview.title")} />
|
||||
<Subtitle subtitle={t("repositoryRole.overview.subtitle")} />
|
||||
<RepositoryRolesPage data={data} page={page} baseUrl={baseUrl} />
|
||||
{canAddRoles ? (
|
||||
<CreateButton label={t("repositoryRole.overview.createButton")} link={`${baseUrl}/create`} />
|
||||
|
||||
@@ -44,12 +44,11 @@ const SingleRepositoryRole: FC = () => {
|
||||
|
||||
const extensionProps = {
|
||||
role,
|
||||
url: escapedUrl
|
||||
url: escapedUrl,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title title={t("repositoryRole.title")} />
|
||||
<Route path={`${escapedUrl}/info`}>
|
||||
<PermissionRoleDetail role={role} url={url} />
|
||||
</Route>
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import InfoBox from "./InfoBox";
|
||||
import LoginForm from "./LoginForm";
|
||||
import { Image, Loading } from "@scm-manager/ui-components";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { useLoginInfo } from "@scm-manager/ui-api";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Image, Loading } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import InfoBox from "./InfoBox";
|
||||
import LoginForm from "./LoginForm";
|
||||
|
||||
const TopMarginBox = styled.div`
|
||||
margin-top: 5rem;
|
||||
@@ -57,6 +58,7 @@ type Props = {
|
||||
const LoginInfo: FC<Props> = (props) => {
|
||||
const { isLoading: isLoadingLoginInfo, data: info } = useLoginInfo();
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("login.title"));
|
||||
|
||||
if (isLoadingLoginInfo) {
|
||||
return <Loading />;
|
||||
|
||||
@@ -15,13 +15,20 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { ButtonGroup, Checkbox, SubmitButton, Subtitle } from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Me } from "@scm-manager/ui-types";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { ButtonGroup, Checkbox, SubmitButton, Subtitle } from "@scm-manager/ui-components";
|
||||
import { AccessibilityConfig, useAccessibilityConfig } from "../accessibilityConfig";
|
||||
|
||||
const Accessibility: FC = () => {
|
||||
type Props = {
|
||||
me: Me;
|
||||
};
|
||||
|
||||
const Accessibility: FC<Props> = ({ me }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("profile.accessibility.subtitle"), me.displayName);
|
||||
const { value: accessibilityConfig, setValue: setAccessibilityConfig, isLoading } = useAccessibilityConfig();
|
||||
const {
|
||||
register,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import React, { FC, FormEvent, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ErrorNotification,
|
||||
InputField,
|
||||
@@ -22,9 +23,9 @@ import {
|
||||
Notification,
|
||||
PasswordConfirmation,
|
||||
SubmitButton,
|
||||
Subtitle
|
||||
Subtitle,
|
||||
} from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { Me } from "@scm-manager/ui-types";
|
||||
import { useChangeUserPassword } from "@scm-manager/ui-api";
|
||||
|
||||
@@ -34,6 +35,7 @@ type Props = {
|
||||
|
||||
const ChangeUserPassword: FC<Props> = ({ me }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("password.subtitle"), me.displayName);
|
||||
const { isLoading, error, passwordChanged, changePassword, reset } = useChangeUserPassword(me);
|
||||
const [oldPassword, setOldPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
|
||||
@@ -18,6 +18,7 @@ import React from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Notification, Page } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type LocationState = {
|
||||
code: string;
|
||||
@@ -26,6 +27,7 @@ type LocationState = {
|
||||
const ExternalError = () => {
|
||||
const { code } = useParams<LocationState>();
|
||||
const [t] = useTranslation(["commons", "plugins"]);
|
||||
useDocumentTitle(`${t("app.error.title")}: ${t(`plugins:errors.${code}.displayName`)}`);
|
||||
|
||||
return (
|
||||
<Page title={t("app.error.title")} subtitle={t(`plugins:errors.${code}.displayName`)}>
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
import React, { FC, useState } from "react";
|
||||
import App from "./App";
|
||||
import { ErrorBoundary, Header, Loading } from "@scm-manager/ui-components";
|
||||
import { ErrorBoundary, Header } from "@scm-manager/ui-components";
|
||||
import { Loading } from "@scm-manager/ui-core";
|
||||
import PluginLoader from "./PluginLoader";
|
||||
import ScrollToTop from "./ScrollToTop";
|
||||
import IndexErrorPage from "./IndexErrorPage";
|
||||
|
||||
@@ -15,15 +15,17 @@
|
||||
*/
|
||||
|
||||
import React, { FC, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ErrorPage, Loading } from "@scm-manager/ui-components";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLogout } from "@scm-manager/ui-api";
|
||||
import { ErrorPage, Loading } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
const Logout: FC = () => {
|
||||
const { error, logout } = useLogout();
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("logout.title"));
|
||||
|
||||
useEffect(() => {
|
||||
if (logout) {
|
||||
logout();
|
||||
|
||||
@@ -75,10 +75,10 @@ const Profile: FC = () => {
|
||||
<ProfileInfo me={me} />
|
||||
</Route>
|
||||
<Route path={`${url}/settings/theme`} exact>
|
||||
<Theme />
|
||||
<Theme me={me} />
|
||||
</Route>
|
||||
<Route path={`${url}/settings/accessibility`} exact>
|
||||
<Accessibility />
|
||||
<Accessibility me={me} />
|
||||
</Route>
|
||||
{mayChangePassword && (
|
||||
<Route path={`${url}/settings/password`}>
|
||||
|
||||
@@ -22,8 +22,9 @@ import {
|
||||
AvatarWrapper,
|
||||
createAttributesForTesting,
|
||||
InfoTable,
|
||||
MailLink
|
||||
MailLink,
|
||||
} from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = {
|
||||
me: Me;
|
||||
@@ -31,6 +32,7 @@ type Props = {
|
||||
|
||||
const ProfileInfo: FC<Props> = ({ me }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("profile.subtitle"), me.displayName);
|
||||
const renderGroups = () => {
|
||||
let groups = null;
|
||||
if (me.groups.length > 0) {
|
||||
@@ -39,7 +41,7 @@ const ProfileInfo: FC<Props> = ({ me }) => {
|
||||
<th>{t("profile.groups")}</th>
|
||||
<td className="p-0">
|
||||
<ul>
|
||||
{me.groups.map(group => {
|
||||
{me.groups.map((group) => {
|
||||
return <li>{group}</li>;
|
||||
})}
|
||||
</ul>
|
||||
|
||||
@@ -20,6 +20,8 @@ import { useForm } from "react-hook-form";
|
||||
import styled from "styled-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { Me } from "@scm-manager/ui-types";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
const LS_KEY = "scm.theme";
|
||||
|
||||
@@ -36,6 +38,10 @@ export const useThemeState = () => {
|
||||
return { theme, setTheme, isLoading };
|
||||
};
|
||||
|
||||
type Props = {
|
||||
me: Me;
|
||||
};
|
||||
|
||||
type ThemeForm = {
|
||||
theme: string;
|
||||
};
|
||||
@@ -47,21 +53,22 @@ const RadioColumn = styled.div`
|
||||
width: 2rem;
|
||||
`;
|
||||
|
||||
const Theme: FC = () => {
|
||||
const Theme: FC<Props> = ({ me }) => {
|
||||
const { theme, setTheme, isLoading } = useThemeState();
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
handleSubmit,
|
||||
formState: { isDirty },
|
||||
watch
|
||||
watch,
|
||||
} = useForm<ThemeForm>({
|
||||
mode: "onChange",
|
||||
defaultValues: {
|
||||
theme
|
||||
}
|
||||
theme,
|
||||
},
|
||||
});
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("profile.theme.subtitle"), me.displayName);
|
||||
|
||||
const onSubmit = (values: ThemeForm) => {
|
||||
setTheme(values.theme);
|
||||
@@ -71,7 +78,7 @@ const Theme: FC = () => {
|
||||
<>
|
||||
<Subtitle>{t("profile.theme.subtitle")}</Subtitle>
|
||||
<form className="is-flex is-flex-direction-column" onSubmit={handleSubmit(onSubmit)}>
|
||||
{themes.map(theme => {
|
||||
{themes.map((theme) => {
|
||||
const a11yId = createA11yId("theme");
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -14,20 +14,41 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { Checkbox, DateFromNow, InfoTable } from "@scm-manager/ui-components";
|
||||
import GroupMember from "./GroupMember";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
type Props = {
|
||||
group: Group;
|
||||
};
|
||||
|
||||
class Details extends React.Component<Props> {
|
||||
render() {
|
||||
const { group, t } = this.props;
|
||||
const Details: FC<Props> = ({ group }) => {
|
||||
const [t] = useTranslation("groups");
|
||||
useDocumentTitle(t("singleGroup.menu.informationNavLink"), group.name);
|
||||
|
||||
const renderMembers = () => {
|
||||
let member = null;
|
||||
if (group.members.length > 0) {
|
||||
member = (
|
||||
<tr>
|
||||
<th>{t("group.members")}</th>
|
||||
<td className="p-0">
|
||||
<ul className="ml-4">
|
||||
{group._embedded.members.map((member, index) => {
|
||||
return <GroupMember key={index} member={member} />;
|
||||
})}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
return member;
|
||||
};
|
||||
|
||||
return (
|
||||
<InfoTable className="content">
|
||||
<tbody>
|
||||
@@ -61,33 +82,15 @@ class Details extends React.Component<Props> {
|
||||
<DateFromNow date={group.lastModified} />
|
||||
</td>
|
||||
</tr>
|
||||
{this.renderMembers()}
|
||||
<ExtensionPoint<extensionPoints.GroupInformationTableBottom> name="group.information.table.bottom" props={{group}} renderAll={true} />
|
||||
{renderMembers()}
|
||||
<ExtensionPoint<extensionPoints.GroupInformationTableBottom>
|
||||
name="group.information.table.bottom"
|
||||
props={{ group }}
|
||||
renderAll={true}
|
||||
/>
|
||||
</tbody>
|
||||
</InfoTable>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderMembers() {
|
||||
const { group, t } = this.props;
|
||||
|
||||
let member = null;
|
||||
if (group.members.length > 0) {
|
||||
member = (
|
||||
<tr>
|
||||
<th>{t("group.members")}</th>
|
||||
<td className="p-0">
|
||||
<ul className="ml-4">
|
||||
{group._embedded.members.map((member, index) => {
|
||||
return <GroupMember key={index} member={member} />;
|
||||
})}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation("groups")(Details);
|
||||
export default Details;
|
||||
|
||||
@@ -18,11 +18,13 @@ import React, { FC } from "react";
|
||||
import { Redirect, useLocation } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useCreateGroup, urls } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { Page } from "@scm-manager/ui-components";
|
||||
import GroupForm from "../components/GroupForm";
|
||||
|
||||
const CreateGroup: FC = () => {
|
||||
const [t] = useTranslation("groups");
|
||||
useDocumentTitle(t("addGroup.title"));
|
||||
const { isLoading, create, error, group } = useCreateGroup();
|
||||
const location = useLocation();
|
||||
|
||||
|
||||
@@ -15,18 +15,22 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { useUpdateGroup, useUserSuggestions } from "@scm-manager/ui-api";
|
||||
import { useUpdateGroup } from "@scm-manager/ui-api";
|
||||
import { ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import UpdateNotification from "../../components/UpdateNotification";
|
||||
import GroupForm from "../components/GroupForm";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
import UpdateNotification from "../../components/UpdateNotification";
|
||||
|
||||
type Props = {
|
||||
group: Group;
|
||||
};
|
||||
|
||||
const EditGroup: FC<Props> = ({ group }) => {
|
||||
const [t] = useTranslation("groups");
|
||||
useDocumentTitle(t("singleGroup.settingsTitle"), group.name);
|
||||
const { error, isLoading, update, isUpdated } = useUpdateGroup();
|
||||
|
||||
return (
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
import React, { FC } from "react";
|
||||
import { Redirect, useLocation, useParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useGroups } from "@scm-manager/ui-api";
|
||||
import { Group, GroupCollection } from "@scm-manager/ui-types";
|
||||
import { useGroups } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import {
|
||||
CreateButton,
|
||||
LinkPaginator,
|
||||
@@ -59,6 +60,7 @@ const Groups: FC = () => {
|
||||
const page = urls.getPageFromMatch({ params });
|
||||
const { isLoading, error, data } = useGroups({ search, page: page - 1 });
|
||||
const [t] = useTranslation("groups");
|
||||
useDocumentTitle(t("groups.title"));
|
||||
const groups = data?._embedded?.groups;
|
||||
const canCreateGroups = !!data?._links.create;
|
||||
if (data && data.pageTotal < page && page > 1) {
|
||||
|
||||
@@ -14,24 +14,30 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import React, { FC } from "react";
|
||||
import SetPermissions from "./SetPermissions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Group } from "@scm-manager/ui-types";
|
||||
import { useGroupPermissions, useSetGroupPermissions } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import SetPermissions from "./SetPermissions";
|
||||
|
||||
type Props = {
|
||||
group: Group;
|
||||
};
|
||||
|
||||
const SetGroupPermissions: FC<Props> = ({ group }) => {
|
||||
const { data: selectedPermissions, isLoading: loadingPermissions, error: permissionsLoadError } = useGroupPermissions(
|
||||
group
|
||||
);
|
||||
const [t] = useTranslation("groups");
|
||||
useDocumentTitle(t("singleGroup.menu.setPermissionsNavLink"), group.name);
|
||||
const {
|
||||
data: selectedPermissions,
|
||||
isLoading: loadingPermissions,
|
||||
error: permissionsLoadError,
|
||||
} = useGroupPermissions(group);
|
||||
const {
|
||||
isLoading: isUpdatingPermissions,
|
||||
isUpdated: permissionsUpdated,
|
||||
setPermissions,
|
||||
error: permissionsUpdateError
|
||||
error: permissionsUpdateError,
|
||||
} = useSetGroupPermissions(group, selectedPermissions);
|
||||
return (
|
||||
<SetPermissions
|
||||
|
||||
@@ -16,22 +16,28 @@
|
||||
|
||||
import { User } from "@scm-manager/ui-types";
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SetPermissions from "./SetPermissions";
|
||||
import { useSetUserPermissions, useUserPermissions } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
const SetUserPermissions: FC<Props> = ({ user }) => {
|
||||
const { data: selectedPermissions, isLoading: loadingPermissions, error: permissionsLoadError } = useUserPermissions(
|
||||
user
|
||||
);
|
||||
const [t] = useTranslation("users");
|
||||
useDocumentTitle(t("singleUser.menu.setPermissionsNavLink"), user.displayName);
|
||||
const {
|
||||
data: selectedPermissions,
|
||||
isLoading: loadingPermissions,
|
||||
error: permissionsLoadError,
|
||||
} = useUserPermissions(user);
|
||||
const {
|
||||
isLoading: isUpdatingPermissions,
|
||||
isUpdated: permissionsUpdated,
|
||||
setPermissions,
|
||||
error: permissionsUpdateError
|
||||
error: permissionsUpdateError,
|
||||
} = useSetUserPermissions(user, selectedPermissions);
|
||||
return (
|
||||
<SetPermissions
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { Branch, Repository } from "@scm-manager/ui-types";
|
||||
import { SmallLoadingSpinner, Subtitle, useGeneratedId } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import BranchButtonGroup from "./BranchButtonGroup";
|
||||
import DefaultBranchTag from "./DefaultBranchTag";
|
||||
import AheadBehindTag from "./AheadBehindTag";
|
||||
@@ -33,6 +34,13 @@ type Props = {
|
||||
|
||||
const BranchDetail: FC<Props> = ({ repository, branch }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
useDocumentTitle(
|
||||
t("branch.branchWithNamespaceName", {
|
||||
branch: branch.name,
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
})
|
||||
);
|
||||
const { data, isLoading } = useBranchDetails(repository, branch);
|
||||
const labelId = useGeneratedId();
|
||||
let aheadBehind;
|
||||
@@ -70,7 +78,10 @@ const BranchDetail: FC<Props> = ({ repository, branch }) => {
|
||||
<BranchButtonGroup repository={repository} branch={branch} />
|
||||
</div>
|
||||
</div>
|
||||
<span id={labelId} className="is-size-7 has-text-secondary">{t("branch.aheadBehind.label")}</span>{aheadBehind}
|
||||
<span id={labelId} className="is-size-7 has-text-secondary">
|
||||
{t("branch.aheadBehind.label")}
|
||||
</span>
|
||||
{aheadBehind}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import BranchDetail from "./BranchDetail";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import { Branch, Repository } from "@scm-manager/ui-types";
|
||||
@@ -25,9 +25,7 @@ type Props = {
|
||||
branch: Branch;
|
||||
};
|
||||
|
||||
class BranchView extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository, branch } = this.props;
|
||||
const BranchView: FC<Props> = ({ repository, branch }) => {
|
||||
return (
|
||||
<>
|
||||
<BranchDetail repository={repository} branch={branch} />
|
||||
@@ -38,14 +36,13 @@ class BranchView extends React.Component<Props> {
|
||||
renderAll={true}
|
||||
props={{
|
||||
repository,
|
||||
branch
|
||||
branch,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<BranchDangerZone repository={repository} branch={branch} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default BranchView;
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Loading } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
|
||||
import { useBranches } from "@scm-manager/ui-api";
|
||||
import BranchTableWrapper from "./BranchTableWrapper";
|
||||
|
||||
@@ -27,6 +29,8 @@ type Props = {
|
||||
|
||||
const BranchesOverview: FC<Props> = ({ repository, baseUrl }) => {
|
||||
const { isLoading, error, data } = useBranches(repository);
|
||||
const [t] = useTranslation("repos");
|
||||
useDocumentTitleForRepository(repository, t("branches.overview.title"));
|
||||
|
||||
if (error) {
|
||||
return <ErrorNotification error={error} />;
|
||||
|
||||
@@ -19,10 +19,11 @@ import { Redirect, useLocation } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import queryString from "query-string";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
|
||||
import BranchForm from "../components/BranchForm";
|
||||
import { useBranches, useCreateBranch } from "@scm-manager/ui-api";
|
||||
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
|
||||
import { encodePart } from "../../sources/components/content/FileLink";
|
||||
import BranchForm from "../components/BranchForm";
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
@@ -33,6 +34,7 @@ const CreateBranch: FC<Props> = ({ repository }) => {
|
||||
const { isLoading: isLoadingList, error: errorList, data: branches } = useBranches(repository);
|
||||
const location = useLocation();
|
||||
const [t] = useTranslation("repos");
|
||||
useDocumentTitleForRepository(repository, t("branches.create.title"));
|
||||
|
||||
const transmittedName = (url: string): string | undefined => {
|
||||
const paramsName = queryString.parse(url).name;
|
||||
@@ -48,7 +50,9 @@ const CreateBranch: FC<Props> = ({ repository }) => {
|
||||
if (createdBranch) {
|
||||
return (
|
||||
<Redirect
|
||||
to={`/repo/${repository.namespace}/${repository.name}/branch/${encodeURIComponent(encodePart(createdBranch.name))}/info`}
|
||||
to={`/repo/${repository.namespace}/${repository.name}/branch/${encodeURIComponent(
|
||||
encodePart(createdBranch.name)
|
||||
)}/info`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import styled from "styled-components";
|
||||
import { Branch, Repository } from "@scm-manager/ui-types";
|
||||
import { urls, usePaths } from "@scm-manager/ui-api";
|
||||
import { createA11yId, ErrorNotification, FilterInput, Help, Icon, Loading } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import CodeActionBar from "../components/CodeActionBar";
|
||||
import FileSearchResults from "../components/FileSearchResults";
|
||||
import { filepathSearch } from "../utils/filepathSearch";
|
||||
@@ -60,7 +61,14 @@ const FileSearch: FC<Props> = ({ repository, baseUrl, branches, selectedBranch }
|
||||
const query = urls.getQueryStringFromLocation(location) || "";
|
||||
const prevSourcePath = urls.getPrevSourcePathFromLocation(location) || "";
|
||||
const [t] = useTranslation("repos");
|
||||
const [firstSelectedBranch, setBranchChanged] = useState<string | undefined>(selectedBranch);
|
||||
useDocumentTitle(
|
||||
t("fileSearch.searchWithRevisionAndNamespaceName", {
|
||||
revision: decodeURIComponent(revision),
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
})
|
||||
);
|
||||
const [firstSelectedBranch] = useState<string | undefined>(selectedBranch);
|
||||
|
||||
useEffect(() => {
|
||||
if (query.length > 1 && data) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import { devices, Icon } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import CompareSelector from "./CompareSelector";
|
||||
import { CompareBranchesParams } from "./CompareView";
|
||||
|
||||
@@ -59,12 +60,22 @@ const CompareSelectBar: FC<Props> = ({ repository, baseUrl }) => {
|
||||
const history = useHistory();
|
||||
const [source, setSource] = useState<CompareProps>({
|
||||
type: params?.sourceType,
|
||||
name: decodeURIComponent(params?.sourceName)
|
||||
name: decodeURIComponent(params?.sourceName),
|
||||
});
|
||||
const [target, setTarget] = useState<CompareProps>({
|
||||
type: params?.targetType,
|
||||
name: decodeURIComponent(params?.targetName)
|
||||
name: decodeURIComponent(params?.targetName),
|
||||
});
|
||||
useDocumentTitle(
|
||||
t("compare.compareSourceAndTargetWithNamespaceName", {
|
||||
sourceType: t(`compare.selector.type.${source.type}`),
|
||||
source: source.name,
|
||||
targetType: t(`compare.selector.type.${target.type}`),
|
||||
target: target.name,
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
})
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const tabUriComponent = location.pathname.split("/")[9];
|
||||
|
||||
@@ -85,17 +85,6 @@ const CompareSelector: FC<Props> = ({ onSelect, selected, label, repository }) =
|
||||
};
|
||||
});
|
||||
|
||||
const getActionTypeName = (type: CompareTypes) => {
|
||||
switch (type) {
|
||||
case "b":
|
||||
return "Branch";
|
||||
case "t":
|
||||
return "Tag";
|
||||
case "r":
|
||||
return "Revision";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ResponsiveWrapper className="field mb-0 is-flex is-flex-direction-column is-fullwidth">
|
||||
<label className="label">{label}</label>
|
||||
@@ -107,7 +96,7 @@ const CompareSelector: FC<Props> = ({ onSelect, selected, label, repository }) =
|
||||
onClick={() => setShowDropdown(!showDropdown)}
|
||||
>
|
||||
<span className="is-ellipsis-overflow">
|
||||
<strong>{getActionTypeName(selection.type)}:</strong> {selection.name}
|
||||
<strong>{t(`compare.selector.typeTitle.${selection.type}`)}:</strong> {selection.name}
|
||||
</span>
|
||||
<span className="icon is-small">
|
||||
<Icon>angle-down</Icon>
|
||||
|
||||
@@ -14,18 +14,21 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import RepositoryDetailTable from "./RepositoryDetailTable";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
|
||||
import RepositoryDetailTable from "./RepositoryDetailTable";
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
};
|
||||
|
||||
class RepositoryDetails extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository } = this.props;
|
||||
const RepositoryDetails: FC<Props> = ({ repository }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
useDocumentTitleForRepository(repository, t("repositoryRoot.menu.informationNavLink"));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RepositoryDetailTable repository={repository} />
|
||||
@@ -35,13 +38,12 @@ class RepositoryDetails extends React.Component<Props> {
|
||||
name="repos.repository-details.information"
|
||||
renderAll={true}
|
||||
props={{
|
||||
repository
|
||||
repository,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default RepositoryDetails;
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Changeset, Repository } from "@scm-manager/ui-types";
|
||||
import { ErrorPage, Loading } from "@scm-manager/ui-components";
|
||||
import ChangesetDetails from "../components/changesets/ChangesetDetails";
|
||||
import { FileControlFactory } from "@scm-manager/ui-components";
|
||||
import { RepositoryRevisionContextProvider, useChangeset } from "@scm-manager/ui-api";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
@@ -36,6 +37,18 @@ const ChangesetView: FC<Props> = ({ repository, fileControlFactoryFactory }) =>
|
||||
const { id } = useParams<Params>();
|
||||
const { isLoading, error, data: changeset } = useChangeset(repository, id);
|
||||
const [t] = useTranslation("repos");
|
||||
useDocumentTitle(
|
||||
changeset?.id
|
||||
? t("changesets.idWithNamespaceName", {
|
||||
id: changeset.id.slice(0, 7),
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
})
|
||||
: t("changesets.changesetsWithNamespaceName", {
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
})
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage title={t("changesets.errorTitle")} subtitle={t("changesets.errorSubtitle")} error={error} />;
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
Notification,
|
||||
urls,
|
||||
} from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
export const usePage = () => {
|
||||
const match = useRouteMatch();
|
||||
@@ -39,12 +40,54 @@ type Props = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
const Changesets: FC<Props> = ({ repository, branch, url }) => {
|
||||
const Changesets: FC<Props> = ({ repository, branch, ...props }) => {
|
||||
const page = usePage();
|
||||
|
||||
const { isLoading, error, data } = useChangesets(repository, { branch, page: page - 1 });
|
||||
const [t] = useTranslation("repos");
|
||||
const getDocumentTitle = () => {
|
||||
if (data?.pageTotal && data.pageTotal > 1 && page) {
|
||||
if (branch) {
|
||||
return t("changesets.commitsWithPageRevisionAndNamespaceName", {
|
||||
page,
|
||||
total: data.pageTotal,
|
||||
revision: branch.name,
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
});
|
||||
} else {
|
||||
return t("changesets.commitsWithPageAndNamespaceName", {
|
||||
page,
|
||||
total: data.pageTotal,
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
});
|
||||
}
|
||||
} else if (branch) {
|
||||
return t("changesets.commitsWithRevisionAndNamespaceName", {
|
||||
revision: branch.name,
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
});
|
||||
} else {
|
||||
return t("changesets.commitsWithNamespaceName", {
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
});
|
||||
}
|
||||
};
|
||||
useDocumentTitle(getDocumentTitle());
|
||||
|
||||
return <ChangesetsPanel repository={repository} error={error} isLoading={isLoading} data={data} url={url} />;
|
||||
return (
|
||||
<ChangesetsPanel
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
data={data}
|
||||
repository={repository}
|
||||
branch={branch}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type ChangesetsPanelProps = Props & {
|
||||
@@ -53,7 +96,7 @@ type ChangesetsPanelProps = Props & {
|
||||
data?: ChangesetCollection;
|
||||
};
|
||||
|
||||
export const ChangesetsPanel: FC<ChangesetsPanelProps> = ({ repository, error, isLoading, data, url }) => {
|
||||
export const ChangesetsPanel: FC<ChangesetsPanelProps> = ({ repository, error, isLoading, data, url, branch }) => {
|
||||
const page = usePage();
|
||||
const [t] = useTranslation("repos");
|
||||
const changesets = data?._embedded?.changesets;
|
||||
|
||||
@@ -16,16 +16,16 @@
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import CreateRepository from "./CreateRepository";
|
||||
import ImportRepository from "./ImportRepository";
|
||||
import { useBinder } from "@scm-manager/ui-extensions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Notification, Page, urls } from "@scm-manager/ui-components";
|
||||
import RepositoryFormSwitcher from "../components/form/RepositoryFormSwitcher";
|
||||
import { useIndex, useNamespaceStrategies, useRepositoryTypes } from "@scm-manager/ui-api";
|
||||
import { Page, urls } from "@scm-manager/ui-components";
|
||||
import { Notification, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { extensionPoints, useBinder } from "@scm-manager/ui-extensions";
|
||||
import NamespaceAndNameFields from "../components/NamespaceAndNameFields";
|
||||
import RepositoryInformationForm from "../components/RepositoryInformationForm";
|
||||
import { extensionPoints } from "@scm-manager/ui-extensions/";
|
||||
import RepositoryFormSwitcher from "../components/form/RepositoryFormSwitcher";
|
||||
import CreateRepository from "./CreateRepository";
|
||||
import ImportRepository from "./ImportRepository";
|
||||
|
||||
type CreatorRouteProps = {
|
||||
creator: extensionPoints.RepositoryCreatorExtension;
|
||||
@@ -41,13 +41,14 @@ const useCreateRepositoryData = () => {
|
||||
pageLoadingError: errorNS || errorRT || errorIdx || undefined,
|
||||
namespaceStrategies,
|
||||
repositoryTypes,
|
||||
index
|
||||
index,
|
||||
};
|
||||
};
|
||||
|
||||
const CreatorRoute: FC<CreatorRouteProps> = ({ creator, creators }) => {
|
||||
const { isPageLoading, pageLoadingError, namespaceStrategies, repositoryTypes, index } = useCreateRepositoryData();
|
||||
const [t] = useTranslation(["repos", "plugins"]);
|
||||
useDocumentTitle(creator.subtitle);
|
||||
|
||||
const Component = creator.component;
|
||||
|
||||
@@ -88,15 +89,15 @@ const CreateRepositoryRoot: FC = () => {
|
||||
path: "",
|
||||
icon: "plus",
|
||||
label: t("repositoryForm.createButton"),
|
||||
component: CreateRepository
|
||||
component: CreateRepository,
|
||||
},
|
||||
{
|
||||
subtitle: t("import.subtitle"),
|
||||
path: "import",
|
||||
icon: "file-upload",
|
||||
label: t("repositoryForm.importButton"),
|
||||
component: ImportRepository
|
||||
}
|
||||
component: ImportRepository,
|
||||
},
|
||||
];
|
||||
|
||||
const extCreators = binder.getExtensions<extensionPoints.RepositoryCreator>("repos.creator");
|
||||
@@ -106,7 +107,7 @@ const CreateRepositoryRoot: FC = () => {
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
{creators.map(creator => (
|
||||
{creators.map((creator) => (
|
||||
<Route key={creator.path} exact path={urls.concat("/repos/create", creator.path)}>
|
||||
<CreatorRoute creator={creator} creators={creators} />
|
||||
</Route>
|
||||
|
||||
@@ -16,18 +16,19 @@
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useRouteMatch } from "react-router-dom";
|
||||
import RepositoryForm from "../components/form";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Subtitle, urls } from "@scm-manager/ui-components";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import RepositoryDangerZone from "./RepositoryDangerZone";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ExportRepository from "./ExportRepository";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import { useUpdateRepository } from "@scm-manager/ui-api";
|
||||
import { ErrorNotification, Subtitle, urls } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import UpdateNotification from "../../components/UpdateNotification";
|
||||
import RepositoryForm from "../components/form";
|
||||
import Reindex from "../components/Reindex";
|
||||
import RepositoryDangerZone from "./RepositoryDangerZone";
|
||||
import ExportRepository from "./ExportRepository";
|
||||
import HealthCheckWarning from "./HealthCheckWarning";
|
||||
import RunHealthCheck from "./RunHealthCheck";
|
||||
import UpdateNotification from "../../components/UpdateNotification";
|
||||
import Reindex from "../components/Reindex";
|
||||
|
||||
type Props = {
|
||||
repository: Repository;
|
||||
@@ -37,11 +38,12 @@ const EditRepo: FC<Props> = ({ repository }) => {
|
||||
const match = useRouteMatch();
|
||||
const { isLoading, error, update, isUpdated } = useUpdateRepository();
|
||||
const [t] = useTranslation("repos");
|
||||
useDocumentTitleForRepository(repository, t("repositoryRoot.settingsTitle"));
|
||||
|
||||
const url = urls.matchedUrlFromMatch(match);
|
||||
const extensionProps = {
|
||||
repository,
|
||||
url
|
||||
url,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
PageActions,
|
||||
urls,
|
||||
} from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import RepositoryList from "../components/list";
|
||||
import { useNamespaceAndNameContext, useNamespaces, useRepositories } from "@scm-manager/ui-api";
|
||||
import { NamespaceCollection, RepositoryCollection } from "@scm-manager/ui-types";
|
||||
@@ -170,6 +171,20 @@ const Overview: FC = () => {
|
||||
const { isLoading, error, namespace, namespaces, repositories, search, page } = useOverviewData();
|
||||
const history = useHistory();
|
||||
const [t] = useTranslation("repos");
|
||||
const getDocumentTitle = () => {
|
||||
if (repositories?.pageTotal && repositories.pageTotal > 1 && page) {
|
||||
if (namespace) {
|
||||
return t("overview.titleWithNamespaceAndPage", { page, total: repositories.pageTotal, namespace });
|
||||
} else {
|
||||
return t("overview.titleWithPage", { page, total: repositories.pageTotal });
|
||||
}
|
||||
} else if (namespace) {
|
||||
return t("overview.titleWithNamespace", { namespace });
|
||||
} else {
|
||||
return t("overview.title");
|
||||
}
|
||||
};
|
||||
useDocumentTitle(getDocumentTitle());
|
||||
const binder = useBinder();
|
||||
const context = useNamespaceAndNameContext();
|
||||
useEffect(() => {
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useImportLog } from "@scm-manager/ui-api";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Page } from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useImportLog } from "@scm-manager/ui-api";
|
||||
import { Page } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type Params = {
|
||||
logId: string;
|
||||
@@ -28,6 +29,7 @@ const ImportLog: FC = () => {
|
||||
const { logId } = useParams<Params>();
|
||||
const { isLoading, data, error } = useImportLog(logId);
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("importLog.title"));
|
||||
|
||||
return (
|
||||
<Page title={t("importLog.title")} loading={isLoading} error={error}>
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
import { Namespace } from "@scm-manager/ui-types";
|
||||
import React, { FC } from "react";
|
||||
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-core";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRepositories } from "@scm-manager/ui-api";
|
||||
import { DateFromNow } from "@scm-manager/ui-components";
|
||||
import { Link } from "react-router-dom";
|
||||
import { ErrorNotification, Loading, Subtitle, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { Namespace } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
namespace: Namespace;
|
||||
@@ -28,6 +28,7 @@ type Props = {
|
||||
|
||||
const NamespaceInformation: FC<Props> = ({ namespace }) => {
|
||||
const [t] = useTranslation("namespaces");
|
||||
useDocumentTitle(t("namespaceRoot.infoPage.subtitle"), namespace.namespace);
|
||||
const { data: repositories, error, isLoading } = useRepositories({ namespace: namespace, pageSize: 9999, page: 0 });
|
||||
|
||||
if (error) {
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAvailablePermissions, usePermissions } from "@scm-manager/ui-api";
|
||||
import { ErrorPage, Loading, Subtitle } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { Namespace, Repository } from "@scm-manager/ui-types";
|
||||
import CreatePermissionForm from "./CreatePermissionForm";
|
||||
import PermissionsTable from "../components/PermissionsTable";
|
||||
import { useAvailablePermissions, usePermissions } from "@scm-manager/ui-api";
|
||||
|
||||
type Props = {
|
||||
namespaceOrRepository: Namespace | Repository;
|
||||
@@ -37,13 +38,17 @@ const usePermissionData = (namespaceOrRepository: Namespace | Repository) => {
|
||||
isLoading: permissions.isLoading || availablePermissions.isLoading,
|
||||
error: permissions.error || availablePermissions.error,
|
||||
permissions: permissions.data,
|
||||
availablePermissions: availablePermissions.data
|
||||
availablePermissions: availablePermissions.data,
|
||||
};
|
||||
};
|
||||
|
||||
const Permissions: FC<Props> = ({ namespaceOrRepository }) => {
|
||||
const { isLoading, error, permissions, availablePermissions } = usePermissionData(namespaceOrRepository);
|
||||
const [t] = useTranslation("repos");
|
||||
useDocumentTitle(
|
||||
t("repositoryRoot.menu.permissionsNavLink"),
|
||||
namespaceOrRepository.namespace + (isRepository(namespaceOrRepository) ? "/" + namespaceOrRepository.name : "")
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage title={t("permission.error-title")} subtitle={t("permission.error-subtitle")} error={error} />;
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useHistory, useLocation, useParams } from "react-router-dom";
|
||||
import { RepositoryRevisionContextProvider, urls, useSources } from "@scm-manager/ui-api";
|
||||
import { Branch, Repository } from "@scm-manager/ui-types";
|
||||
import { Breadcrumb } from "@scm-manager/ui-components";
|
||||
import { Notification, ErrorNotification, Loading } from "@scm-manager/ui-core";
|
||||
import { Notification, ErrorNotification, Loading, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import FileTree from "../components/FileTree";
|
||||
import Content from "./Content";
|
||||
import CodeActionBar from "../../codeSection/components/CodeActionBar";
|
||||
@@ -57,6 +57,30 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const [t] = useTranslation("repos");
|
||||
const getDocumentTitle = () => {
|
||||
if (revision) {
|
||||
const getRevision = () => {
|
||||
return branches?.some((branch) => branch.name === revision) ? revision : revision.slice(0, 7);
|
||||
};
|
||||
if (path) {
|
||||
return t("sources.pathWithRevisionAndNamespaceName", {
|
||||
path: path,
|
||||
revision: getRevision(),
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
});
|
||||
} else {
|
||||
return t("sources.sourcesWithRevisionAndNamespaceName", {
|
||||
revision: getRevision(),
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return repository.namespace + "/" + repository.name;
|
||||
}
|
||||
};
|
||||
useDocumentTitle(getDocumentTitle());
|
||||
const [contentRef, setContentRef] = useState<HTMLElement | null>();
|
||||
|
||||
useScrollToElement(contentRef, () => location.hash, location.hash);
|
||||
@@ -72,7 +96,6 @@ const Sources: FC<Props> = ({ repository, branches, selectedBranch, baseUrl }) =
|
||||
);
|
||||
}
|
||||
}, [branches, selectedBranch, history, baseUrl, location.hash]);
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import { Repository, Tag } from "@scm-manager/ui-types";
|
||||
import { Subtitle, DateFromNow, SignatureIcon } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import TagButtonGroup from "./TagButtonGroup";
|
||||
import CompareLink from "../../compare/CompareLink";
|
||||
|
||||
@@ -29,6 +30,13 @@ type Props = {
|
||||
|
||||
const TagDetail: FC<Props> = ({ repository, tag }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
useDocumentTitle(
|
||||
t("tag.tagWithNamespaceName", {
|
||||
tag: tag.name,
|
||||
namespace: repository.namespace,
|
||||
name: repository.name,
|
||||
})
|
||||
);
|
||||
|
||||
const encodedTag = encodeURIComponent(tag.name);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import React, { FC, useMemo, useState } from "react";
|
||||
import { Repository } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Loading, Notification, Subtitle } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitleForRepository } from "@scm-manager/ui-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import orderTags, { SORT_OPTIONS, SortOption } from "../orderTags";
|
||||
import TagTable from "../components/TagTable";
|
||||
@@ -31,6 +32,7 @@ type Props = {
|
||||
const TagsOverview: FC<Props> = ({ repository, baseUrl }) => {
|
||||
const { isLoading, error, data } = useTags(repository);
|
||||
const [t] = useTranslation("repos");
|
||||
useDocumentTitleForRepository(repository, t("tags.overview.title"));
|
||||
const [sort, setSort] = useState<SortOption | undefined>();
|
||||
const tags = useMemo(() => orderTags(data?._embedded?.tags || [], sort), [data, sort]);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
Tag,
|
||||
urls,
|
||||
} from "@scm-manager/ui-components";
|
||||
import { Notification, Level } from "@scm-manager/ui-core";
|
||||
import { Notification, Level, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { Link, useLocation, useParams } from "react-router-dom";
|
||||
import { useIndex, useNamespaceAndNameContext, useSearch, useSearchCounts, useSearchTypes } from "@scm-manager/ui-api";
|
||||
import Results from "./Results";
|
||||
@@ -163,9 +163,22 @@ const InvalidSearch: FC = () => {
|
||||
|
||||
const Search: FC = () => {
|
||||
const { data: index } = useIndex();
|
||||
const [t] = useTranslation(["commons", "plugins"]);
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
const { query, selectedType, page, namespace, name } = usePageParams();
|
||||
const searchOptions = {
|
||||
type: selectedType,
|
||||
page: page - 1,
|
||||
pageSize: 25,
|
||||
namespaceContext: namespace,
|
||||
repositoryNameContext: name,
|
||||
};
|
||||
const { data, isLoading, error } = useSearch(query, searchOptions);
|
||||
const [t] = useTranslation(["commons", "plugins"]);
|
||||
useDocumentTitle(
|
||||
data?.pageTotal && data.pageTotal > 1 && page
|
||||
? t("search.titleWithPage", { page, total: data.pageTotal })
|
||||
: t("search.title")
|
||||
);
|
||||
const [showHelp, setShowHelp] = useState(false);
|
||||
const context = useNamespaceAndNameContext();
|
||||
useEffect(() => {
|
||||
context.setNamespace(namespace || "");
|
||||
@@ -176,14 +189,6 @@ const Search: FC = () => {
|
||||
context.setName("");
|
||||
};
|
||||
}, [namespace, name, context]);
|
||||
const searchOptions = {
|
||||
type: selectedType,
|
||||
page: page - 1,
|
||||
pageSize: 25,
|
||||
namespaceContext: namespace,
|
||||
repositoryNameContext: name,
|
||||
};
|
||||
const { data, isLoading, error } = useSearch(query, searchOptions);
|
||||
const types = useSearchTypes(searchOptions);
|
||||
types.sort(orderTypes(t));
|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
|
||||
import React, { FC, useState } from "react";
|
||||
import { useSearchableTypes, useSearchSyntaxContent } from "@scm-manager/ui-api";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { copyToClipboard, InputField, Loading, MarkdownView, Page } from "@scm-manager/ui-components";
|
||||
import { ErrorNotification, Icon, Tooltip, Button } from "@scm-manager/ui-core";
|
||||
import classNames from "classnames";
|
||||
import { parse } from "date-fns";
|
||||
import styled from "styled-components";
|
||||
import classNames from "classnames";
|
||||
import { useSearchableTypes, useSearchSyntaxContent } from "@scm-manager/ui-api";
|
||||
import { copyToClipboard, InputField, MarkdownView, Page } from "@scm-manager/ui-components";
|
||||
import { ErrorNotification, Icon, Loading, Tooltip, Button, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { SearchableType } from "@scm-manager/ui-types";
|
||||
|
||||
const StyledTooltip = styled(Tooltip)`
|
||||
@@ -41,7 +41,9 @@ type ExpandableProps = {
|
||||
|
||||
const Expandable: FC<ExpandableProps> = ({ header, children, className }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
useDocumentTitle(t("search.syntax.title"));
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={classNames("card search-syntax-accordion", className)}>
|
||||
<header>
|
||||
@@ -88,6 +90,7 @@ const Examples: FC<ExampleProps> = ({ searchableType }) => {
|
||||
<h5 className="title mt-5">{t("search.syntax.exampleQueries.title")}</h5>
|
||||
<div className="mb-2">{t("search.syntax.exampleQueries.description")}</div>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t("search.syntax.exampleQueries.table.description")}</th>
|
||||
<th>{t("search.syntax.exampleQueries.table.query")}</th>
|
||||
@@ -100,6 +103,7 @@ const Examples: FC<ExampleProps> = ({ searchableType }) => {
|
||||
<td>{example.explanation}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
@@ -126,6 +130,7 @@ const SearchableTypes: FC = () => {
|
||||
header={t(`plugins:search.types.${searchableType.name}.title`, searchableType.name)}
|
||||
>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t("search.syntax.fields.name")}</th>
|
||||
<th>{t("search.syntax.fields.type")}</th>
|
||||
@@ -148,6 +153,7 @@ const SearchableTypes: FC = () => {
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<Examples searchableType={searchableType} />
|
||||
</Expandable>
|
||||
|
||||
@@ -23,9 +23,10 @@ import {
|
||||
Notification,
|
||||
PasswordConfirmation,
|
||||
SubmitButton,
|
||||
Subtitle
|
||||
Subtitle,
|
||||
} from "@scm-manager/ui-components";
|
||||
import { useSetUserPassword } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
@@ -33,6 +34,7 @@ type Props = {
|
||||
|
||||
const SetUserPassword: FC<Props> = ({ user }) => {
|
||||
const [t] = useTranslation("users");
|
||||
useDocumentTitle(t("singleUser.menu.setPasswordNavLink"), user.displayName);
|
||||
const { passwordOverwritten, setPassword, error, isLoading, reset } = useSetUserPassword(user);
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [passwordValid, setPasswordValid] = useState(false);
|
||||
|
||||
@@ -22,6 +22,7 @@ import AddApiKey from "./AddApiKey";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useApiKeys, useDeleteApiKey } from "@scm-manager/ui-api";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = {
|
||||
user: User | Me;
|
||||
@@ -29,6 +30,7 @@ type Props = {
|
||||
|
||||
const SetApiKeys: FC<Props> = ({ user }) => {
|
||||
const [t] = useTranslation("users");
|
||||
useDocumentTitle(t("singleUser.menu.setApiKeyNavLink"), user.displayName);
|
||||
const { isLoading, data: apiKeys, error: fetchError } = useApiKeys(user);
|
||||
const { error: deletionError, remove } = useDeleteApiKey(user);
|
||||
const error = deletionError || fetchError;
|
||||
|
||||
@@ -21,6 +21,7 @@ import AddPublicKey from "./AddPublicKey";
|
||||
import PublicKeyTable from "./PublicKeyTable";
|
||||
import { ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
|
||||
import { useDeletePublicKey, usePublicKeys } from "@scm-manager/ui-api";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = {
|
||||
user: User | Me;
|
||||
@@ -28,6 +29,7 @@ type Props = {
|
||||
|
||||
const SetPublicKeys: FC<Props> = ({ user }) => {
|
||||
const [t] = useTranslation("users");
|
||||
useDocumentTitle(t("singleUser.menu.setPublicKeyNavLink"), user.displayName);
|
||||
const { error: fetchingError, isLoading, data: publicKeys } = usePublicKeys(user);
|
||||
const { error: deletionError, remove } = useDeletePublicKey(user);
|
||||
const error = fetchingError || deletionError;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React, { FC, useState } from "react";
|
||||
import { useTranslation, WithTranslation } from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { User } from "@scm-manager/ui-types";
|
||||
import {
|
||||
Checkbox,
|
||||
@@ -23,18 +23,20 @@ import {
|
||||
DateFromNow,
|
||||
Help,
|
||||
InfoTable,
|
||||
MailLink
|
||||
MailLink,
|
||||
} from "@scm-manager/ui-components";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
import PermissionOverview from "../PermissionOverview";
|
||||
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
type Props = {
|
||||
user: User;
|
||||
};
|
||||
|
||||
const Details: FC<Props> = ({ user }) => {
|
||||
const [t] = useTranslation("users");
|
||||
useDocumentTitle(t("singleUser.menu.informationNavLink"), user.displayName);
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const toggleCollapse = () => setCollapsed(!collapsed);
|
||||
|
||||
@@ -96,7 +98,11 @@ const Details: FC<Props> = ({ user }) => {
|
||||
<DateFromNow date={user.lastModified} />
|
||||
</td>
|
||||
</tr>
|
||||
<ExtensionPoint<extensionPoints.UserInformationTableBottom> name="user.information.table.bottom" props={{user}} renderAll={true} />
|
||||
<ExtensionPoint<extensionPoints.UserInformationTableBottom>
|
||||
name="user.information.table.bottom"
|
||||
props={{ user }}
|
||||
renderAll={true}
|
||||
/>
|
||||
</tbody>
|
||||
</InfoTable>
|
||||
{permissionOverview}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Page } from "@scm-manager/ui-components";
|
||||
import { Form, useCreateResource } from "@scm-manager/ui-forms";
|
||||
import * as userValidator from "../components/userValidation";
|
||||
import { Link, User, UserCollection, UserCreation } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification, Loading, Notification } from "@scm-manager/ui-core";
|
||||
import { ErrorNotification, Loading, Notification, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { useUsers } from "@scm-manager/ui-api";
|
||||
|
||||
type UserCreationForm = Pick<UserCreation, "password" | "name" | "displayName" | "active" | "external" | "mail"> & {
|
||||
@@ -30,6 +30,7 @@ type UserCreationForm = Pick<UserCreation, "password" | "name" | "displayName" |
|
||||
|
||||
const CreateUserForm: FC<{ users: UserCollection }> = ({ users }) => {
|
||||
const [t] = useTranslation("users");
|
||||
useDocumentTitle(t("createUser.title"));
|
||||
const { submit, submissionResult: createdUser } = useCreateResource<UserCreationForm, User>(
|
||||
(users._links.create as Link).href,
|
||||
["user", "users"],
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import DeleteUser from "./DeleteUser";
|
||||
import { User } from "@scm-manager/ui-types";
|
||||
import UserConverter from "../components/UserConverter";
|
||||
import { Form, useUpdateResource } from "@scm-manager/ui-forms";
|
||||
import * as userValidator from "../components/userValidation";
|
||||
import { Subtitle } from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { User } from "@scm-manager/ui-types";
|
||||
import { Subtitle } from "@scm-manager/ui-components";
|
||||
import { useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { Form, useUpdateResource } from "@scm-manager/ui-forms";
|
||||
import UserConverter from "../components/UserConverter";
|
||||
import * as userValidator from "../components/userValidation";
|
||||
import DeleteUser from "./DeleteUser";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
@@ -29,6 +30,7 @@ type Props = {
|
||||
|
||||
const EditUser: FC<Props> = ({ user }) => {
|
||||
const [t] = useTranslation("users");
|
||||
useDocumentTitle(t("singleUser.settingsTitle"), user.displayName);
|
||||
const { submit } = useUpdateResource<User>(user, (user) => user.name, {
|
||||
contentType: "application/vnd.scmm-user+json;v=2",
|
||||
collectionName: ["user", "users"],
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { useUsers } from "@scm-manager/ui-api";
|
||||
import { User, UserCollection } from "@scm-manager/ui-types";
|
||||
import { CreateButton, LinkPaginator, OverviewPageActions, Page, PageActions, urls } from "@scm-manager/ui-components";
|
||||
import { Notification } from "@scm-manager/ui-core";
|
||||
import { Notification, useDocumentTitle } from "@scm-manager/ui-core";
|
||||
import { UserTable } from "./../components/table";
|
||||
|
||||
type UserPageProps = {
|
||||
@@ -52,6 +52,7 @@ const Users: FC = () => {
|
||||
const page = urls.getPageFromMatch({ params });
|
||||
const { isLoading, error, data } = useUsers({ page: page - 1, search });
|
||||
const [t] = useTranslation("users");
|
||||
useDocumentTitle(t("users.title"));
|
||||
const users = data?._embedded?.users;
|
||||
const canAddUsers = !!data?._links.create;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user