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