mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-01 19:15:52 +01:00
codify extension points docs (#1947)
This pull request converts the current incomplete textual documentation of the available frontend extension points to in-code definitions that act both as documentation and as type helpers for improving overall code quality. All extension points available in the SCM-Manager core are now available, but no plugin was updated and only those parts of the core codebase had the new types added that did not require runtime changes. The only exception to this is the breadcrumbs, which was a simple change that is fully backwards-compatible.
This commit is contained in:
committed by
GitHub
parent
5006e9b821
commit
4d203ff36f
@@ -2,123 +2,6 @@
|
|||||||
title: Extension Points
|
title: Extension Points
|
||||||
---
|
---
|
||||||
|
|
||||||
The following extension points are provided for the frontend:
|
The available extension points are now maintained in-code, providing typescript types for improved developer experience and code quality assurance.
|
||||||
|
|
||||||
### admin.navigation
|
You can browse and import them directly in your frontend code from the `@scm-manager/ui-extensions` package.
|
||||||
### admin.route
|
|
||||||
### admin.setting
|
|
||||||
### changeset.description.tokens
|
|
||||||
- Can be used to replace parts of a changeset description with components
|
|
||||||
- Has to be bound with a funktion taking the changeset and the (partial) description and returning `Replacement` objects with the following attributes:
|
|
||||||
- textToReplace: The text part of the description that should be replaced by a component
|
|
||||||
- replacement: The component to take instead of the text to replace
|
|
||||||
- replaceAll: Optional boolean; if set to `true`, all occurances of the text will be replaced (default: `false`)
|
|
||||||
### changeset.right
|
|
||||||
### changesets.author.suffix
|
|
||||||
### group.navigation
|
|
||||||
### group.route
|
|
||||||
### group.setting
|
|
||||||
### main.route
|
|
||||||
- Add a new Route to the main Route (scm/)
|
|
||||||
- Props: authenticated?: boolean, links: Links
|
|
||||||
|
|
||||||
### plugins.plugin-avatar
|
|
||||||
### primary-navigation
|
|
||||||
### primary-navigation.first-menu
|
|
||||||
- A placeholder for the first navigation menu.
|
|
||||||
- A PrimaryNavigationLink Component can be used here
|
|
||||||
- Actually this Extension Point is used from the Activity Plugin to display the activities at the first Main Navigation menu.
|
|
||||||
|
|
||||||
### primary-navigation.logout
|
|
||||||
### profile.route
|
|
||||||
### profile.setting
|
|
||||||
### repo-config.route
|
|
||||||
### repo-config.details
|
|
||||||
### repos.branch-details.information
|
|
||||||
### repos.content.metadata
|
|
||||||
- Location: At meta data view for file
|
|
||||||
- can be used to render additional meta data line
|
|
||||||
- Props: file: string, repository: Repository, revision: string
|
|
||||||
|
|
||||||
### repos.create.namespace
|
|
||||||
### repos.sources.content.actionbar
|
|
||||||
### repository.navigation
|
|
||||||
### repository.navigation.topLevel
|
|
||||||
### repositoryRole.role-details.information
|
|
||||||
### repository.setting
|
|
||||||
### repos.repository-avatar
|
|
||||||
### repos.repository-avatar.primary
|
|
||||||
- Location: At each repository in repository overview
|
|
||||||
- can be used to add avatar for each repository (e.g., to mark repository type)
|
|
||||||
|
|
||||||
### repos.repository-details.information
|
|
||||||
- Location: At bottom of a single repository view
|
|
||||||
- can be used to show detailed information about the repository (how to clone, e.g.)
|
|
||||||
### repos.sources.view
|
|
||||||
### roles.route
|
|
||||||
### user.route
|
|
||||||
### user.setting
|
|
||||||
### markdown-renderer.code.{language}
|
|
||||||
- Dynamic extension point for custom language-specific renderers
|
|
||||||
- Overrides the default Syntax Highlighter
|
|
||||||
- Used by the Markdown Plantuml Plugin
|
|
||||||
### markdown-renderer.link.protocol
|
|
||||||
- Define custom protocols and their renderers for links in markdown
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```markdown
|
|
||||||
[description](myprotocol:somelink)
|
|
||||||
```
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
binder.bind("markdown-renderer.link.protocol", { protocol: "myprotocol", renderer: MyProtocolRenderer })
|
|
||||||
```
|
|
||||||
|
|
||||||
# Deprecated
|
|
||||||
|
|
||||||
### changeset.description
|
|
||||||
- can be used to replace the whole description of a changeset
|
|
||||||
|
|
||||||
**Deprecated:** Use `changeset.description.tokens` instead
|
|
||||||
|
|
||||||
### changeset.avatar-factory
|
|
||||||
- Location: At every changeset (detailed view as well as changeset overview)
|
|
||||||
- can be used to add avatar (such as gravatar) for each changeset
|
|
||||||
- expects a function: `(Changeset) => void`
|
|
||||||
|
|
||||||
### repos.sources.view
|
|
||||||
- Location: At sources viewer
|
|
||||||
- can be used to render a special source that is not an image or a source code
|
|
||||||
|
|
||||||
### main.redirect
|
|
||||||
- Extension Point for a link factory that provide the Redirect Link
|
|
||||||
- Actually used from the activity plugin: binder.bind("main.redirect", () => "/activity");
|
|
||||||
|
|
||||||
### markdown-renderer-factory
|
|
||||||
- A Factory function to create markdown [renderer](https://github.com/rexxars/react-markdown#node-types)
|
|
||||||
- The factory function will be called with a renderContext parameter of type Object. this parameter is given as a prop for the MarkdownView component.
|
|
||||||
|
|
||||||
**example:**
|
|
||||||
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
let MarkdownFactory = (renderContext) => {
|
|
||||||
|
|
||||||
let Heading= (props) => {
|
|
||||||
return React.createElement(`h${props.level}`,
|
|
||||||
props['data-sourcepos'] ? {'data-sourcepos': props['data-sourcepos']} : {},
|
|
||||||
props.children);
|
|
||||||
};
|
|
||||||
return {heading : Heading};
|
|
||||||
};
|
|
||||||
|
|
||||||
binder.bind("markdown-renderer-factory", MarkdownFactory);
|
|
||||||
```
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
<MarkdownView
|
|
||||||
renderContext={{pullRequest, repository}}
|
|
||||||
className="content"
|
|
||||||
content={pullRequest.description}
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { binder } from "@scm-manager/ui-extensions";
|
import { binder, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import ProtocolInformation from "./ProtocolInformation";
|
import ProtocolInformation from "./ProtocolInformation";
|
||||||
import GitAvatar from "./GitAvatar";
|
import GitAvatar from "./GitAvatar";
|
||||||
|
|
||||||
@@ -40,9 +40,18 @@ export const gitPredicate = (props: any) => {
|
|||||||
return !!(props && props.repository && props.repository.type === "git");
|
return !!(props && props.repository && props.repository.type === "git");
|
||||||
};
|
};
|
||||||
|
|
||||||
binder.bind("repos.repository-details.information", ProtocolInformation, gitPredicate);
|
binder.bind<extensionPoints.RepositoryDetailsInformation>(
|
||||||
|
"repos.repository-details.information",
|
||||||
|
ProtocolInformation,
|
||||||
|
gitPredicate
|
||||||
|
);
|
||||||
binder.bind("repos.branch-details.information", GitBranchInformation, { priority: 100, predicate: gitPredicate });
|
binder.bind("repos.branch-details.information", GitBranchInformation, { priority: 100, predicate: gitPredicate });
|
||||||
binder.bind("repos.tag-details.information", GitTagInformation, gitPredicate);
|
|
||||||
|
binder.bind<extensionPoints.RepositoryTagDetailsInformation>(
|
||||||
|
"repos.tag-details.information",
|
||||||
|
GitTagInformation,
|
||||||
|
gitPredicate
|
||||||
|
);
|
||||||
binder.bind("repos.repository-merge.information", GitMergeInformation, gitPredicate);
|
binder.bind("repos.repository-merge.information", GitMergeInformation, gitPredicate);
|
||||||
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
|
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { binder } from "@scm-manager/ui-extensions";
|
import { binder, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import ProtocolInformation from "./ProtocolInformation";
|
import ProtocolInformation from "./ProtocolInformation";
|
||||||
import HgAvatar from "./HgAvatar";
|
import HgAvatar from "./HgAvatar";
|
||||||
import { ConfigurationBinder as cfgBinder } from "@scm-manager/ui-components";
|
import { ConfigurationBinder as cfgBinder } from "@scm-manager/ui-components";
|
||||||
@@ -35,14 +35,23 @@ const hgPredicate = (props: any) => {
|
|||||||
return props.repository && props.repository.type === "hg";
|
return props.repository && props.repository.type === "hg";
|
||||||
};
|
};
|
||||||
|
|
||||||
binder.bind("repos.repository-details.information", ProtocolInformation, hgPredicate);
|
binder.bind<extensionPoints.RepositoryDetailsInformation>(
|
||||||
|
"repos.repository-details.information",
|
||||||
|
ProtocolInformation,
|
||||||
|
hgPredicate
|
||||||
|
);
|
||||||
binder.bind("repos.branch-details.information", HgBranchInformation, { priority: 100, predicate: hgPredicate });
|
binder.bind("repos.branch-details.information", HgBranchInformation, { priority: 100, predicate: hgPredicate });
|
||||||
binder.bind("repos.tag-details.information", HgTagInformation, hgPredicate);
|
|
||||||
|
binder.bind<extensionPoints.RepositoryTagDetailsInformation>(
|
||||||
|
"repos.tag-details.information",
|
||||||
|
HgTagInformation,
|
||||||
|
hgPredicate
|
||||||
|
);
|
||||||
binder.bind("repos.repository-avatar", HgAvatar, hgPredicate);
|
binder.bind("repos.repository-avatar", HgAvatar, hgPredicate);
|
||||||
|
|
||||||
// bind repository specific configuration
|
// bind repository specific configuration
|
||||||
|
|
||||||
binder.bind("repo-config.route", HgRepositoryConfigurationForm, hgPredicate);
|
binder.bind<extensionPoints.RepoConfigRoute>("repo-config.route", HgRepositoryConfigurationForm, hgPredicate);
|
||||||
|
|
||||||
// bind global configuration
|
// bind global configuration
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { binder } from "@scm-manager/ui-extensions";
|
import { binder, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { ConfigurationBinder as cfgBinder } from "@scm-manager/ui-components";
|
import { ConfigurationBinder as cfgBinder } from "@scm-manager/ui-components";
|
||||||
import ProtocolInformation from "./ProtocolInformation";
|
import ProtocolInformation from "./ProtocolInformation";
|
||||||
import SvnAvatar from "./SvnAvatar";
|
import SvnAvatar from "./SvnAvatar";
|
||||||
@@ -32,7 +32,11 @@ const svnPredicate = (props: any) => {
|
|||||||
return props.repository && props.repository.type === "svn";
|
return props.repository && props.repository.type === "svn";
|
||||||
};
|
};
|
||||||
|
|
||||||
binder.bind("repos.repository-details.information", ProtocolInformation, svnPredicate);
|
binder.bind<extensionPoints.RepositoryDetailsInformation>(
|
||||||
|
"repos.repository-details.information",
|
||||||
|
ProtocolInformation,
|
||||||
|
svnPredicate
|
||||||
|
);
|
||||||
binder.bind("repos.repository-avatar", SvnAvatar, svnPredicate);
|
binder.bind("repos.repository-avatar", SvnAvatar, svnPredicate);
|
||||||
|
|
||||||
// bind global configuration
|
// bind global configuration
|
||||||
|
|||||||
@@ -255,13 +255,25 @@ const Breadcrumb: FC<Props> = ({
|
|||||||
|
|
||||||
const renderExtensionPoints = () => {
|
const renderExtensionPoints = () => {
|
||||||
if (
|
if (
|
||||||
binder.hasExtension<extensionPoints.ReposSourcesEmptyActionbar>("repos.sources.empty.actionbar") &&
|
binder.hasExtension<extensionPoints.ReposSourcesEmptyActionbar>("repos.sources.empty.actionbar", extProps) &&
|
||||||
sources?._embedded?.children?.length === 0
|
sources?._embedded?.children?.length === 0
|
||||||
) {
|
) {
|
||||||
return <ExtensionPoint name="repos.sources.empty.actionbar" props={{ repository, sources }} renderAll={true} />;
|
return (
|
||||||
|
<ExtensionPoint<extensionPoints.ReposSourcesEmptyActionbar>
|
||||||
|
name="repos.sources.empty.actionbar"
|
||||||
|
props={{ repository, sources }}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (binder.hasExtension<extensionPoints.ReposSourcesActionbar>("repos.sources.actionbar")) {
|
if (binder.hasExtension<extensionPoints.ReposSourcesActionbar>("repos.sources.actionbar", extProps)) {
|
||||||
return <ExtensionPoint name="repos.sources.actionbar" props={extProps} renderAll={true} />;
|
return (
|
||||||
|
<ExtensionPoint<extensionPoints.ReposSourcesActionbar>
|
||||||
|
name="repos.sources.actionbar"
|
||||||
|
props={extProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,9 +23,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Person } from "@scm-manager/ui-types";
|
import { Person } from "@scm-manager/ui-types";
|
||||||
|
import { extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
// re export type to avoid breaking changes,
|
// re export type to avoid breaking changes,
|
||||||
// after the type was moved to ui-types
|
// after the type was moved to ui-types
|
||||||
export { Person };
|
export { Person };
|
||||||
|
|
||||||
export const EXTENSION_POINT = "avatar.factory";
|
export const EXTENSION_POINT: extensionPoints.AvatarFactory["name"] = "avatar.factory";
|
||||||
|
|||||||
@@ -22,12 +22,12 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { useBinder } from "@scm-manager/ui-extensions";
|
import { extensionPoints, useBinder } from "@scm-manager/ui-extensions";
|
||||||
import { EXTENSION_POINT } from "./Avatar";
|
import { EXTENSION_POINT } from "./Avatar";
|
||||||
|
|
||||||
const AvatarWrapper: FC = ({ children }) => {
|
const AvatarWrapper: FC = ({ children }) => {
|
||||||
const binder = useBinder();
|
const binder = useBinder();
|
||||||
if (binder.hasExtension(EXTENSION_POINT)) {
|
if (binder.hasExtension<extensionPoints.AvatarFactory>(EXTENSION_POINT)) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { storiesOf } from "@storybook/react";
|
import { storiesOf } from "@storybook/react";
|
||||||
import Footer from "./Footer";
|
import Footer from "./Footer";
|
||||||
import { Binder, BinderContext } from "@scm-manager/ui-extensions";
|
import { Binder, BinderContext, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { Me } from "@scm-manager/ui-types";
|
import { Me } from "@scm-manager/ui-types";
|
||||||
import { EXTENSION_POINT } from "../avatar/Avatar";
|
import { EXTENSION_POINT } from "../avatar/Avatar";
|
||||||
// @ts-ignore ignore unknown png
|
// @ts-ignore ignore unknown png
|
||||||
@@ -43,11 +43,8 @@ const trillian: Me = {
|
|||||||
_links: {},
|
_links: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const bindAvatar = (binder: Binder, avatar: string) => {
|
const bindAvatar = (binder: Binder, avatar: string) =>
|
||||||
binder.bind(EXTENSION_POINT, () => {
|
binder.bind<extensionPoints.AvatarFactory>(EXTENSION_POINT, () => avatar);
|
||||||
return avatar;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const bindLinks = (binder: Binder) => {
|
const bindLinks = (binder: Binder) => {
|
||||||
binder.bind("footer.information", () => <ExternalNavLink to="#" label="REST API" />);
|
binder.bind("footer.information", () => <ExternalNavLink to="#" label="REST API" />);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Links, Me } from "@scm-manager/ui-types";
|
import { Links, Me } from "@scm-manager/ui-types";
|
||||||
import { ExtensionPoint, useBinder } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints, useBinder } from "@scm-manager/ui-extensions";
|
||||||
import { AvatarImage } from "../avatar";
|
import { AvatarImage } from "../avatar";
|
||||||
import NavLink from "../navigation/NavLink";
|
import NavLink from "../navigation/NavLink";
|
||||||
import FooterSection from "./FooterSection";
|
import FooterSection from "./FooterSection";
|
||||||
@@ -84,7 +84,7 @@ const Footer: FC<Props> = ({ me, version, links }) => {
|
|||||||
const extensionProps = { me, url: "/me", links };
|
const extensionProps = { me, url: "/me", links };
|
||||||
let meSectionTile;
|
let meSectionTile;
|
||||||
if (me) {
|
if (me) {
|
||||||
if (binder.hasExtension(EXTENSION_POINT)) {
|
if (binder.hasExtension<extensionPoints.AvatarFactory>(EXTENSION_POINT)) {
|
||||||
meSectionTile = <TitleWithAvatar me={me} />;
|
meSectionTile = <TitleWithAvatar me={me} />;
|
||||||
} else {
|
} else {
|
||||||
meSectionTile = <TitleWithIcon title={me.displayName} icon="user-circle" />;
|
meSectionTile = <TitleWithIcon title={me.displayName} icon="user-circle" />;
|
||||||
@@ -105,7 +105,11 @@ const Footer: FC<Props> = ({ me, version, links }) => {
|
|||||||
<NavLink to="/me/settings/publicKeys" label={t("profile.publicKeysNavLink")} />
|
<NavLink to="/me/settings/publicKeys" label={t("profile.publicKeysNavLink")} />
|
||||||
)}
|
)}
|
||||||
{me?._links?.apiKeys && <NavLink to="/me/settings/apiKeys" label={t("profile.apiKeysNavLink")} />}
|
{me?._links?.apiKeys && <NavLink to="/me/settings/apiKeys" label={t("profile.apiKeysNavLink")} />}
|
||||||
<ExtensionPoint name="profile.setting" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.ProfileSetting>
|
||||||
|
name="profile.setting"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</FooterSection>
|
</FooterSection>
|
||||||
) : null}
|
) : null}
|
||||||
<FooterSection title={<TitleWithIcon title={t("footer.information.title")} icon="info-circle" />}>
|
<FooterSection title={<TitleWithIcon title={t("footer.information.title")} icon="info-circle" />}>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import SyntaxHighlighter from "../SyntaxHighlighter";
|
import SyntaxHighlighter from "../SyntaxHighlighter";
|
||||||
import { ExtensionPoint, useBinder } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints, useBinder } from "@scm-manager/ui-extensions";
|
||||||
import { useIndexLinks } from "@scm-manager/ui-api";
|
import { useIndexLinks } from "@scm-manager/ui-api";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -32,14 +32,15 @@ type Props = {
|
|||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MarkdownCodeRenderer: FC<Props> = (props) => {
|
const MarkdownCodeRenderer: FC<Props> = props => {
|
||||||
const binder = useBinder();
|
const binder = useBinder();
|
||||||
const indexLinks = useIndexLinks();
|
const indexLinks = useIndexLinks();
|
||||||
const { language } = props;
|
const { language } = props;
|
||||||
|
|
||||||
const extensionKey = `markdown-renderer.code.${language}`;
|
const extensionProps = { ...props, indexLinks };
|
||||||
if (binder.hasExtension(extensionKey, props)) {
|
const extensionKey = `markdown-renderer.code.${language}` as const;
|
||||||
return <ExtensionPoint name={extensionKey} props={{ ...props, indexLinks }} />;
|
if (binder.hasExtension<extensionPoints.MarkdownCodeRenderer>(extensionKey, extensionProps)) {
|
||||||
|
return <ExtensionPoint<extensionPoints.MarkdownCodeRenderer> name={extensionKey} props={extensionProps} />;
|
||||||
}
|
}
|
||||||
return <SyntaxHighlighter {...props} />;
|
return <SyntaxHighlighter {...props} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ import MarkdownChangelog from "../__resources__/markdown-changelog.md";
|
|||||||
import Title from "../layout/Title";
|
import Title from "../layout/Title";
|
||||||
import { Subtitle } from "../layout";
|
import { Subtitle } from "../layout";
|
||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter } from "react-router-dom";
|
||||||
import { Binder, BinderContext } from "@scm-manager/ui-extensions";
|
import { Binder, BinderContext, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { ProtocolLinkRendererExtension, ProtocolLinkRendererProps } from "./markdownExtensions";
|
import { ProtocolLinkRendererProps } from "./markdownExtensions";
|
||||||
|
|
||||||
const Spacing = styled.div`
|
const Spacing = styled.div`
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
@@ -64,10 +64,10 @@ storiesOf("MarkdownView", module)
|
|||||||
))
|
))
|
||||||
.add("Links", () => {
|
.add("Links", () => {
|
||||||
const binder = new Binder("custom protocol link renderer");
|
const binder = new Binder("custom protocol link renderer");
|
||||||
binder.bind("markdown-renderer.link.protocol", {
|
binder.bind<extensionPoints.MarkdownLinkProtocolRenderer<"scw">>("markdown-renderer.link.protocol", {
|
||||||
protocol: "scw",
|
protocol: "scw",
|
||||||
renderer: ProtocolLinkRenderer
|
renderer: ProtocolLinkRenderer
|
||||||
} as ProtocolLinkRendererExtension);
|
});
|
||||||
return (
|
return (
|
||||||
<BinderContext.Provider value={binder}>
|
<BinderContext.Provider value={binder}>
|
||||||
<MarkdownView content={MarkdownLinks} basePath="/scm/" />
|
<MarkdownView content={MarkdownLinks} basePath="/scm/" />
|
||||||
@@ -76,10 +76,10 @@ storiesOf("MarkdownView", module)
|
|||||||
})
|
})
|
||||||
.add("Links without Base Path", () => {
|
.add("Links without Base Path", () => {
|
||||||
const binder = new Binder("custom protocol link renderer");
|
const binder = new Binder("custom protocol link renderer");
|
||||||
binder.bind("markdown-renderer.link.protocol", {
|
binder.bind<extensionPoints.MarkdownLinkProtocolRenderer<"scw">>("markdown-renderer.link.protocol", {
|
||||||
protocol: "scw",
|
protocol: "scw",
|
||||||
renderer: ProtocolLinkRenderer
|
renderer: ProtocolLinkRenderer
|
||||||
} as ProtocolLinkRendererExtension);
|
});
|
||||||
return (
|
return (
|
||||||
<BinderContext.Provider value={binder}>
|
<BinderContext.Provider value={binder}>
|
||||||
<MarkdownView content={MarkdownLinks} />
|
<MarkdownView content={MarkdownLinks} />
|
||||||
@@ -107,7 +107,7 @@ storiesOf("MarkdownView", module)
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
binder.bind("markdown-renderer.code.uml", Container);
|
binder.bind<extensionPoints.MarkdownCodeRenderer<"uml">>("markdown-renderer.code.uml", Container);
|
||||||
return (
|
return (
|
||||||
<BinderContext.Provider value={binder}>
|
<BinderContext.Provider value={binder}>
|
||||||
<MarkdownView content={MarkdownUmlCodeBlock} />
|
<MarkdownView content={MarkdownUmlCodeBlock} />
|
||||||
@@ -116,7 +116,7 @@ storiesOf("MarkdownView", module)
|
|||||||
})
|
})
|
||||||
.add("XSS Prevention", () => <MarkdownView content={MarkdownXss} skipHtml={false} />);
|
.add("XSS Prevention", () => <MarkdownView content={MarkdownXss} skipHtml={false} />);
|
||||||
|
|
||||||
export const ProtocolLinkRenderer: FC<ProtocolLinkRendererProps> = ({ protocol, href, children }) => {
|
export const ProtocolLinkRenderer: FC<ProtocolLinkRendererProps<"scw">> = ({ protocol, href, children }) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ border: "1px dashed lightgray", padding: "2px" }}>
|
<div style={{ border: "1px dashed lightgray", padding: "2px" }}>
|
||||||
<h4>
|
<h4>
|
||||||
|
|||||||
@@ -21,18 +21,17 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import { FC } from "react";
|
import { extensionPoints, ExtractProps } from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
export type ProtocolLinkRendererProps = {
|
export type ProtocolLinkRendererProps<Protocol extends string | undefined = undefined> = ExtractProps<
|
||||||
protocol: string;
|
extensionPoints.MarkdownLinkProtocolRenderer<Protocol>["type"]["renderer"]
|
||||||
href: string;
|
>;
|
||||||
};
|
|
||||||
|
|
||||||
export type ProtocolLinkRendererExtension = {
|
/**
|
||||||
protocol: string;
|
* @deprecated use {@link MarkdownLinkProtocolRenderer}`["type"]` instead
|
||||||
renderer: FC<ProtocolLinkRendererProps>;
|
*/
|
||||||
};
|
export type ProtocolLinkRendererExtension = extensionPoints.MarkdownLinkProtocolRenderer["type"];
|
||||||
|
|
||||||
export type ProtocolLinkRendererExtensionMap = {
|
export type ProtocolLinkRendererExtensionMap = {
|
||||||
[protocol: string]: FC<ProtocolLinkRendererProps>;
|
[protocol: string]: extensionPoints.MarkdownLinkProtocolRenderer["type"]["renderer"] | undefined;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,9 +24,7 @@
|
|||||||
import React, { FC, ReactNode } from "react";
|
import React, { FC, ReactNode } from "react";
|
||||||
import PrimaryNavigationLink from "./PrimaryNavigationLink";
|
import PrimaryNavigationLink from "./PrimaryNavigationLink";
|
||||||
import { Links } from "@scm-manager/ui-types";
|
import { Links } from "@scm-manager/ui-types";
|
||||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { binder, ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { urls } from "@scm-manager/ui-api";
|
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -37,7 +35,6 @@ type Appender = (to: string, match: string, label: string, linkName: string) =>
|
|||||||
|
|
||||||
const PrimaryNavigation: FC<Props> = ({ links }) => {
|
const PrimaryNavigation: FC<Props> = ({ links }) => {
|
||||||
const [t] = useTranslation("commons");
|
const [t] = useTranslation("commons");
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const createNavigationAppender = (navItems: ReactNode[]): Appender => {
|
const createNavigationAppender = (navItems: ReactNode[]): Appender => {
|
||||||
return (to: string, match: string, label: string, linkName: string) => {
|
return (to: string, match: string, label: string, linkName: string) => {
|
||||||
@@ -63,13 +60,15 @@ const PrimaryNavigation: FC<Props> = ({ links }) => {
|
|||||||
|
|
||||||
const extensionProps = {
|
const extensionProps = {
|
||||||
links,
|
links,
|
||||||
label: t("primary-navigation.first-menu"),
|
label: t("primary-navigation.first-menu")
|
||||||
};
|
};
|
||||||
|
|
||||||
const append = createNavigationAppender(navItems);
|
const append = createNavigationAppender(navItems);
|
||||||
if (binder.hasExtension("primary-navigation.first-menu", extensionProps)) {
|
if (
|
||||||
|
binder.hasExtension<extensionPoints.PrimaryNavigationFirstMenu>("primary-navigation.first-menu", extensionProps)
|
||||||
|
) {
|
||||||
navItems.push(
|
navItems.push(
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.PrimaryNavigationFirstMenu>
|
||||||
key="primary-navigation.first-menu"
|
key="primary-navigation.first-menu"
|
||||||
name="primary-navigation.first-menu"
|
name="primary-navigation.first-menu"
|
||||||
props={extensionProps}
|
props={extensionProps}
|
||||||
@@ -82,12 +81,12 @@ const PrimaryNavigation: FC<Props> = ({ links }) => {
|
|||||||
append("/admin", "/admin", "primary-navigation.admin", "config");
|
append("/admin", "/admin", "primary-navigation.admin", "config");
|
||||||
|
|
||||||
navItems.push(
|
navItems.push(
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.PrimaryNavigation>
|
||||||
key="primary-navigation"
|
key="primary-navigation"
|
||||||
name="primary-navigation"
|
name="primary-navigation"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
props={{
|
props={{
|
||||||
links,
|
links
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { Repository } from "@scm-manager/ui-types";
|
import { Repository } from "@scm-manager/ui-types";
|
||||||
import { Image } from "@scm-manager/ui-components";
|
import { Image } from "@scm-manager/ui-components";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
@@ -38,13 +38,13 @@ type Props = {
|
|||||||
|
|
||||||
const renderExtensionPoint = (repository: Repository) => {
|
const renderExtensionPoint = (repository: Repository) => {
|
||||||
return (
|
return (
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.PrimaryRepositoryAvatar>
|
||||||
name="repos.repository-avatar.primary"
|
name="repos.repository-avatar.primary"
|
||||||
props={{
|
props={{
|
||||||
repository,
|
repository,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.RepositoryAvatar>
|
||||||
name="repos.repository-avatar"
|
name="repos.repository-avatar"
|
||||||
props={{
|
props={{
|
||||||
repository,
|
repository,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import React, { FC, useState } from "react";
|
|||||||
import { Repository } from "@scm-manager/ui-types";
|
import { Repository } from "@scm-manager/ui-types";
|
||||||
import { DateFromNow, Modal } from "@scm-manager/ui-components";
|
import { DateFromNow, Modal } from "@scm-manager/ui-components";
|
||||||
import RepositoryAvatar from "./RepositoryAvatar";
|
import RepositoryAvatar from "./RepositoryAvatar";
|
||||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { binder, ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import GroupEntry from "../layout/GroupEntry";
|
import GroupEntry from "../layout/GroupEntry";
|
||||||
import RepositoryFlags from "./RepositoryFlags";
|
import RepositoryFlags from "./RepositoryFlags";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
@@ -103,7 +103,7 @@ const RepositoryEntry: FC<Props> = ({ repository, baseDate }) => {
|
|||||||
active={openCloneModal}
|
active={openCloneModal}
|
||||||
title={t("overview.clone")}
|
title={t("overview.clone")}
|
||||||
body={
|
body={
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.RepositoryDetailsInformation>
|
||||||
name="repos.repository-details.information"
|
name="repos.repository-details.information"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
props={{
|
props={{
|
||||||
@@ -144,7 +144,10 @@ const RepositoryEntry: FC<Props> = ({ repository, baseDate }) => {
|
|||||||
const actions = createContentRight();
|
const actions = createContentRight();
|
||||||
const name = (
|
const name = (
|
||||||
<div className="is-flex">
|
<div className="is-flex">
|
||||||
<ExtensionPoint name="repository.card.beforeTitle" props={{ repository }} />
|
<ExtensionPoint<extensionPoints.RepositoryCardBeforeTitle>
|
||||||
|
name="repository.card.beforeTitle"
|
||||||
|
props={{ repository }}
|
||||||
|
/>
|
||||||
<Name>{repository.name}</Name> <RepositoryFlags repository={repository} className="is-hidden-mobile" />
|
<Name>{repository.name}</Name> <RepositoryFlags repository={repository} className="is-hidden-mobile" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,7 +26,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 { Repository } from "@scm-manager/ui-types";
|
import { Repository } from "@scm-manager/ui-types";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { TooltipLocation } from "../Tooltip";
|
import { TooltipLocation } from "../Tooltip";
|
||||||
import RepositoryFlag from "./RepositoryFlag";
|
import RepositoryFlag from "./RepositoryFlag";
|
||||||
import HealthCheckFailureDetail from "./HealthCheckFailureDetail";
|
import HealthCheckFailureDetail from "./HealthCheckFailureDetail";
|
||||||
@@ -90,7 +90,11 @@ const RepositoryFlags: FC<Props> = ({ repository, className, tooltipLocation = "
|
|||||||
{modal}
|
{modal}
|
||||||
<RepositoryFlagContainer>
|
<RepositoryFlagContainer>
|
||||||
{repositoryFlags}
|
{repositoryFlags}
|
||||||
<ExtensionPoint name="repository.flags" props={{ repository, tooltipLocation }} renderAll={true} />
|
<ExtensionPoint<extensionPoints.RepositoryFlags>
|
||||||
|
name="repository.flags"
|
||||||
|
props={{ repository, tooltipLocation }}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</RepositoryFlagContainer>
|
</RepositoryFlagContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Changeset, Person } from "@scm-manager/ui-types";
|
import { Changeset, Person } from "@scm-manager/ui-types";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useBinder } from "@scm-manager/ui-extensions";
|
import { extensionPoints, useBinder } from "@scm-manager/ui-extensions";
|
||||||
import { EXTENSION_POINT } from "../../avatar/Avatar";
|
import { EXTENSION_POINT } from "../../avatar/Avatar";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import CommaSeparatedList from "../../CommaSeparatedList";
|
import CommaSeparatedList from "../../CommaSeparatedList";
|
||||||
@@ -42,7 +42,7 @@ type PersonProps = {
|
|||||||
|
|
||||||
const useAvatar = (person: Person): string | undefined => {
|
const useAvatar = (person: Person): string | undefined => {
|
||||||
const binder = useBinder();
|
const binder = useBinder();
|
||||||
const factory: (person: Person) => string | undefined = binder.getExtension(EXTENSION_POINT);
|
const factory = binder.getExtension<extensionPoints.AvatarFactory>(EXTENSION_POINT);
|
||||||
if (factory) {
|
if (factory) {
|
||||||
return factory(person);
|
return factory(person);
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,9 @@ const ChangesetAuthor: FC<Props> = ({ changeset }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// extensions
|
// extensions
|
||||||
const extensions = binder.getExtensions("changesets.author.suffix", { changeset });
|
const extensions = binder.getExtensions<extensionPoints.ChangesetsAuthorSuffix>("changesets.author.suffix", {
|
||||||
|
changeset
|
||||||
|
});
|
||||||
if (extensions) {
|
if (extensions) {
|
||||||
authorLine.push(...extensions);
|
authorLine.push(...extensions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
|
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Changeset } from "@scm-manager/ui-types";
|
import { Changeset } from "@scm-manager/ui-types";
|
||||||
import { useBinder } from "@scm-manager/ui-extensions";
|
import { extensionPoints, useBinder } from "@scm-manager/ui-extensions";
|
||||||
import { SplitAndReplace, Replacement } from "@scm-manager/ui-components";
|
import { Replacement, SplitAndReplace } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
changeset: Changeset;
|
changeset: Changeset;
|
||||||
@@ -35,15 +35,14 @@ type Props = {
|
|||||||
const ChangesetDescription: FC<Props> = ({ changeset, value }) => {
|
const ChangesetDescription: FC<Props> = ({ changeset, value }) => {
|
||||||
const binder = useBinder();
|
const binder = useBinder();
|
||||||
|
|
||||||
const replacements: ((changeset: Changeset, value: string) => Replacement[])[] = binder.getExtensions(
|
const replacements: ((changeset: Changeset, value: string) => Replacement[])[] = binder.getExtensions<
|
||||||
"changeset.description.tokens",
|
extensionPoints.ChangesetDescriptionTokens
|
||||||
{
|
>("changeset.description.tokens", {
|
||||||
changeset,
|
changeset,
|
||||||
value,
|
value
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return <SplitAndReplace text={value} replacements={replacements.flatMap((r) => r(changeset, value))} />;
|
return <SplitAndReplace text={value} replacements={replacements.flatMap(r => r(changeset, value))} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ChangesetDescription;
|
export default ChangesetDescription;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { Changeset, File, Repository } from "@scm-manager/ui-types";
|
import { Changeset, File, Repository } from "@scm-manager/ui-types";
|
||||||
import ChangesetButtonGroup from "./ChangesetButtonGroup";
|
import ChangesetButtonGroup from "./ChangesetButtonGroup";
|
||||||
import SingleChangeset from "./SingleChangeset";
|
import SingleChangeset from "./SingleChangeset";
|
||||||
@@ -54,7 +54,7 @@ const ChangesetRow: FC<Props> = ({ repository, changeset, file }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className={classNames("column", "is-flex", "is-justify-content-flex-end", "is-align-items-center")}>
|
<div className={classNames("column", "is-flex", "is-justify-content-flex-end", "is-align-items-center")}>
|
||||||
<ChangesetButtonGroup repository={repository} changeset={changeset} file={file} />
|
<ChangesetButtonGroup repository={repository} changeset={changeset} file={file} />
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.ChangesetRight>
|
||||||
name="changeset.right"
|
name="changeset.right"
|
||||||
props={{
|
props={{
|
||||||
repository,
|
repository,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { AvatarImage, AvatarWrapper } from "../../avatar";
|
import { AvatarImage, AvatarWrapper } from "../../avatar";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import ChangesetDescription from "./ChangesetDescription";
|
import ChangesetDescription from "./ChangesetDescription";
|
||||||
import { Trans } from "react-i18next";
|
import { Trans } from "react-i18next";
|
||||||
import ChangesetAuthor from "./ChangesetAuthor";
|
import ChangesetAuthor from "./ChangesetAuthor";
|
||||||
@@ -72,7 +72,7 @@ const SingleChangeset: FC<Props> = ({ repository, changeset }) => {
|
|||||||
</AvatarWrapper>
|
</AvatarWrapper>
|
||||||
<FullWidthDiv className={classNames("media-right", "ml-0")}>
|
<FullWidthDiv className={classNames("media-right", "ml-0")}>
|
||||||
<h4 className={classNames("has-text-weight-bold", "is-ellipsis-overflow")}>
|
<h4 className={classNames("has-text-weight-bold", "is-ellipsis-overflow")}>
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.ChangesetDescription>
|
||||||
name="changeset.description"
|
name="changeset.description"
|
||||||
props={{
|
props={{
|
||||||
changeset,
|
changeset,
|
||||||
|
|||||||
@@ -21,39 +21,70 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
import React, { FC, ReactNode } from "react";
|
import React, { PropsWithChildren, ReactNode } from "react";
|
||||||
import { Binder } from "./binder";
|
import { Binder, ExtensionPointDefinition } from "./binder";
|
||||||
import useBinder from "./useBinder";
|
import useBinder from "./useBinder";
|
||||||
|
|
||||||
|
export type Renderable<P> = React.ReactElement | React.ComponentType<P>;
|
||||||
|
export type RenderableExtensionPointDefinition<
|
||||||
|
Name extends string = string,
|
||||||
|
P = undefined
|
||||||
|
> = ExtensionPointDefinition<Name, Renderable<P>, P>;
|
||||||
|
|
||||||
|
export type SimpleRenderableDynamicExtensionPointDefinition<
|
||||||
|
Prefix extends string,
|
||||||
|
Suffix extends string | undefined,
|
||||||
|
Properties
|
||||||
|
> = RenderableExtensionPointDefinition<Suffix extends string ? `${Prefix}${Suffix}` : `${Prefix}${string}`, Properties>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Obsolete type
|
||||||
|
*/
|
||||||
type PropTransformer = (props: object) => object;
|
type PropTransformer = (props: object) => object;
|
||||||
|
|
||||||
type Props = {
|
type BaseProps<E extends RenderableExtensionPointDefinition> = {
|
||||||
name: string;
|
name: E["name"];
|
||||||
renderAll?: boolean;
|
renderAll?: boolean;
|
||||||
props?: object;
|
/**
|
||||||
|
* @deprecated Obsolete property, do not use
|
||||||
|
*/
|
||||||
propTransformer?: PropTransformer;
|
propTransformer?: PropTransformer;
|
||||||
wrapper?: boolean;
|
wrapper?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createInstance = (Component: any, props: object, key?: number) => {
|
type Props<E extends RenderableExtensionPointDefinition> = BaseProps<E> &
|
||||||
const instanceProps = {
|
(E["props"] extends undefined
|
||||||
...props,
|
? { props?: E["props"] }
|
||||||
...(Component.props || {}),
|
: {
|
||||||
key,
|
props: E["props"];
|
||||||
};
|
});
|
||||||
if (React.isValidElement(Component)) {
|
|
||||||
return React.cloneElement(Component, instanceProps);
|
|
||||||
}
|
|
||||||
return <Component {...instanceProps} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderAllExtensions = (binder: Binder, name: string, props: object) => {
|
function createInstance<P>(Component: Renderable<P>, props: P, key?: number) {
|
||||||
const extensions = binder.getExtensions(name, props);
|
if (React.isValidElement(Component)) {
|
||||||
|
return React.cloneElement(Component, {
|
||||||
|
...props,
|
||||||
|
...Component.props,
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return <Component {...props} key={key} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderAllExtensions = <E extends RenderableExtensionPointDefinition<string, unknown>>(
|
||||||
|
binder: Binder,
|
||||||
|
name: E["name"],
|
||||||
|
props: E["props"]
|
||||||
|
) => {
|
||||||
|
const extensions = binder.getExtensions<E>(name, props);
|
||||||
return <>{extensions.map((cmp, index) => createInstance(cmp, props, index))}</>;
|
return <>{extensions.map((cmp, index) => createInstance(cmp, props, index))}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderWrapperExtensions = (binder: Binder, name: string, props: object) => {
|
const renderWrapperExtensions = <E extends RenderableExtensionPointDefinition<string, any>>(
|
||||||
const extensions = [...(binder.getExtensions(name, props) || [])];
|
binder: Binder,
|
||||||
|
name: E["name"],
|
||||||
|
props: E["props"]
|
||||||
|
) => {
|
||||||
|
const extensions = binder.getExtensions<E>(name, props);
|
||||||
extensions.reverse();
|
extensions.reverse();
|
||||||
|
|
||||||
let instance: any = null;
|
let instance: any = null;
|
||||||
@@ -68,8 +99,12 @@ const renderWrapperExtensions = (binder: Binder, name: string, props: object) =>
|
|||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSingleExtension = (binder: Binder, name: string, props: object) => {
|
const renderSingleExtension = <E extends RenderableExtensionPointDefinition<string, unknown>>(
|
||||||
const cmp = binder.getExtension(name, props);
|
binder: Binder,
|
||||||
|
name: E["name"],
|
||||||
|
props: E["props"]
|
||||||
|
) => {
|
||||||
|
const cmp = binder.getExtension<E>(name, props);
|
||||||
if (!cmp) {
|
if (!cmp) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -97,18 +132,21 @@ const createRenderProps = (propTransformer?: PropTransformer, props?: object) =>
|
|||||||
/**
|
/**
|
||||||
* ExtensionPoint renders components which are bound to an extension point.
|
* ExtensionPoint renders components which are bound to an extension point.
|
||||||
*/
|
*/
|
||||||
const ExtensionPoint: FC<Props> = ({ name, propTransformer, props, renderAll, wrapper, children }) => {
|
export default function ExtensionPoint<
|
||||||
|
E extends RenderableExtensionPointDefinition<string, any> = RenderableExtensionPointDefinition<string, any>
|
||||||
|
>({ name, propTransformer, props, renderAll, wrapper, children }: PropsWithChildren<Props<E>>): JSX.Element | null {
|
||||||
const binder = useBinder();
|
const binder = useBinder();
|
||||||
const renderProps = createRenderProps(propTransformer, { ...(props || {}), children });
|
const renderProps: E["props"] | {} = createRenderProps(propTransformer, {
|
||||||
if (!binder.hasExtension(name, renderProps)) {
|
...(props || {}),
|
||||||
|
children,
|
||||||
|
});
|
||||||
|
if (!binder.hasExtension<E>(name, renderProps)) {
|
||||||
return renderDefault(children);
|
return renderDefault(children);
|
||||||
} else if (renderAll) {
|
} else if (renderAll) {
|
||||||
if (wrapper) {
|
if (wrapper) {
|
||||||
return renderWrapperExtensions(binder, name, renderProps);
|
return renderWrapperExtensions<E>(binder, name, renderProps);
|
||||||
}
|
}
|
||||||
return renderAllExtensions(binder, name, renderProps);
|
return renderAllExtensions<E>(binder, name, renderProps);
|
||||||
}
|
}
|
||||||
return renderSingleExtension(binder, name, renderProps);
|
return renderSingleExtension<E>(binder, name, renderProps);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ExtensionPoint;
|
|
||||||
|
|||||||
@@ -22,7 +22,9 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
import { Binder, ExtensionPointDefinition, SimpleDynamicExtensionPointDefinition } from "./binder";
|
import { Binder, ExtensionPointDefinition, SimpleDynamicExtensionPointDefinition } from "./binder";
|
||||||
|
import ExtensionPoint, { RenderableExtensionPointDefinition } from "./ExtensionPoint";
|
||||||
|
|
||||||
describe("binder tests", () => {
|
describe("binder tests", () => {
|
||||||
let binder: Binder;
|
let binder: Binder;
|
||||||
@@ -117,6 +119,59 @@ describe("binder tests", () => {
|
|||||||
expect(binderExtensionC).not.toBeNull();
|
expect(binderExtensionC).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should allow typings for renderable extension points", () => {
|
||||||
|
type TestExtensionPointA = RenderableExtensionPointDefinition<"test.extension.a">;
|
||||||
|
type TestExtensionPointB = RenderableExtensionPointDefinition<"test.extension.b", { testProp: boolean[] }>;
|
||||||
|
|
||||||
|
binder.bind<TestExtensionPointA>(
|
||||||
|
"test.extension.a",
|
||||||
|
() => <h1>Hello world</h1>,
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
const binderExtensionA = binder.getExtension<TestExtensionPointA>("test.extension.a");
|
||||||
|
expect(binderExtensionA).not.toBeNull();
|
||||||
|
binder.bind<TestExtensionPointB>("test.extension.b", ({ testProp }) => (
|
||||||
|
<h1>
|
||||||
|
{testProp.map((b) => (
|
||||||
|
<span>{b}</span>
|
||||||
|
))}
|
||||||
|
</h1>
|
||||||
|
));
|
||||||
|
const binderExtensionsB = binder.getExtensions<TestExtensionPointB>("test.extension.b", {
|
||||||
|
testProp: [true, false],
|
||||||
|
});
|
||||||
|
expect(binderExtensionsB).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render typed extension point", () => {
|
||||||
|
type TestExtensionPointA = RenderableExtensionPointDefinition<"test.extension.a">;
|
||||||
|
type TestExtensionPointB = RenderableExtensionPointDefinition<"test.extension.b", { testProp: boolean[] }>;
|
||||||
|
|
||||||
|
binder.bind<TestExtensionPointA>(
|
||||||
|
"test.extension.a",
|
||||||
|
() => <h1>Hello world</h1>,
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
const binderExtensionA = <ExtensionPoint<TestExtensionPointA> name="test.extension.a" />;
|
||||||
|
expect(binderExtensionA).not.toBeNull();
|
||||||
|
binder.bind<TestExtensionPointB>("test.extension.b", ({ testProp }) => (
|
||||||
|
<h1>
|
||||||
|
{testProp.map((b) => (
|
||||||
|
<span>{b}</span>
|
||||||
|
))}
|
||||||
|
</h1>
|
||||||
|
));
|
||||||
|
const binderExtensionsB = (
|
||||||
|
<ExtensionPoint<TestExtensionPointB>
|
||||||
|
name="test.extension.b"
|
||||||
|
props={{
|
||||||
|
testProp: [true, false],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(binderExtensionsB).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("should allow typings for dynamic extension points", () => {
|
it("should allow typings for dynamic extension points", () => {
|
||||||
type MarkdownCodeLanguageRendererProps = {
|
type MarkdownCodeLanguageRendererProps = {
|
||||||
language?: string;
|
language?: string;
|
||||||
@@ -21,8 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
type Predicate<P extends Record<any, any> = Record<any, any>> = (props: P) => unknown;
|
||||||
type Predicate<P extends Record<any, any> = Record<any, any>> = (props: P) => boolean;
|
|
||||||
|
|
||||||
type ExtensionRegistration<P, T> = {
|
type ExtensionRegistration<P, T> = {
|
||||||
predicate: Predicate<P>;
|
predicate: Predicate<P>;
|
||||||
@@ -78,15 +77,29 @@ export class Binder {
|
|||||||
*
|
*
|
||||||
* @param extensionPoint name of extension point
|
* @param extensionPoint name of extension point
|
||||||
* @param extension provided extension
|
* @param extension provided extension
|
||||||
* @param predicate to decide if the extension gets rendered for the given props
|
|
||||||
*/
|
*/
|
||||||
bind<E extends ExtensionPointDefinition<string, unknown>>(extensionPoint: E["name"], extension: E["type"]): void;
|
bind<E extends ExtensionPointDefinition<string, unknown>>(extensionPoint: E["name"], extension: E["type"]): void;
|
||||||
|
/**
|
||||||
|
* Binds an extension to the extension point.
|
||||||
|
*
|
||||||
|
* @param extensionPoint name of extension point
|
||||||
|
* @param extension provided extension
|
||||||
|
* @param predicate to decide if the extension gets rendered for the given props
|
||||||
|
* @param extensionName name used for sorting alphabetically on retrieval (ASC)
|
||||||
|
*/
|
||||||
bind<E extends ExtensionPointDefinition<string, unknown, any>>(
|
bind<E extends ExtensionPointDefinition<string, unknown, any>>(
|
||||||
extensionPoint: E["name"],
|
extensionPoint: E["name"],
|
||||||
extension: E["type"],
|
extension: E["type"],
|
||||||
predicate?: Predicate<E["props"]>,
|
predicate?: Predicate<E["props"]>,
|
||||||
extensionName?: string
|
extensionName?: string
|
||||||
): void;
|
): void;
|
||||||
|
/**
|
||||||
|
* Binds an extension to the extension point.
|
||||||
|
*
|
||||||
|
* @param extensionPoint name of extension point
|
||||||
|
* @param extension provided extension
|
||||||
|
* @param options object with additional settings
|
||||||
|
*/
|
||||||
bind<E extends ExtensionPointDefinition<string, unknown, any>>(
|
bind<E extends ExtensionPointDefinition<string, unknown, any>>(
|
||||||
extensionPoint: E["name"],
|
extensionPoint: E["name"],
|
||||||
extension: E["type"],
|
extension: E["type"],
|
||||||
@@ -125,13 +138,18 @@ export class Binder {
|
|||||||
this.extensionPoints[extensionPoint].push(registration);
|
this.extensionPoints[extensionPoint].push(registration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first extension or null for the given extension point and its props.
|
||||||
|
*
|
||||||
|
* @param extensionPoint name of extension point
|
||||||
|
*/
|
||||||
|
getExtension<E extends ExtensionPointDefinition<string, any>>(extensionPoint: E["name"]): E["type"] | null;
|
||||||
/**
|
/**
|
||||||
* Returns the first extension or null for the given extension point and its props.
|
* Returns the first extension or null for the given extension point and its props.
|
||||||
*
|
*
|
||||||
* @param extensionPoint name of extension point
|
* @param extensionPoint name of extension point
|
||||||
* @param props of the extension point
|
* @param props of the extension point
|
||||||
*/
|
*/
|
||||||
getExtension<E extends ExtensionPointDefinition<string, any, undefined>>(extensionPoint: E["name"]): E["type"] | null;
|
|
||||||
getExtension<E extends ExtensionPointDefinition<string, any, any>>(
|
getExtension<E extends ExtensionPointDefinition<string, any, any>>(
|
||||||
extensionPoint: E["name"],
|
extensionPoint: E["name"],
|
||||||
props: E["props"]
|
props: E["props"]
|
||||||
@@ -147,16 +165,19 @@ export class Binder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all registered extensions for the given extension point and its props.
|
||||||
|
*
|
||||||
|
* @param extensionPoint name of extension point
|
||||||
|
*/
|
||||||
|
getExtensions<E extends ExtensionPointDefinition<string, unknown>>(extensionPoint: E["name"]): Array<E["type"]>;
|
||||||
/**
|
/**
|
||||||
* Returns all registered extensions for the given extension point and its props.
|
* Returns all registered extensions for the given extension point and its props.
|
||||||
*
|
*
|
||||||
* @param extensionPoint name of extension point
|
* @param extensionPoint name of extension point
|
||||||
* @param props of the extension point
|
* @param props of the extension point
|
||||||
*/
|
*/
|
||||||
getExtensions<E extends ExtensionPointDefinition<string, any, undefined>>(
|
getExtensions<E extends ExtensionPointDefinition<string, unknown, any>>(
|
||||||
extensionPoint: E["name"]
|
|
||||||
): Array<E["type"]>;
|
|
||||||
getExtensions<E extends ExtensionPointDefinition<string, any, any>>(
|
|
||||||
extensionPoint: E["name"],
|
extensionPoint: E["name"],
|
||||||
props: E["props"]
|
props: E["props"]
|
||||||
): Array<E["type"]>;
|
): Array<E["type"]>;
|
||||||
@@ -175,11 +196,19 @@ export class Binder {
|
|||||||
/**
|
/**
|
||||||
* Returns true if at least one extension is bound to the extension point and its props.
|
* Returns true if at least one extension is bound to the extension point and its props.
|
||||||
*/
|
*/
|
||||||
|
hasExtension<E extends ExtensionPointDefinition<string, unknown>>(extensionPoint: E["name"]): boolean;
|
||||||
|
/**
|
||||||
|
* Returns true if at least one extension is bound to the extension point and its props.
|
||||||
|
*/
|
||||||
|
hasExtension<E extends ExtensionPointDefinition<string, unknown, any>>(
|
||||||
|
extensionPoint: E["name"],
|
||||||
|
props: E["props"]
|
||||||
|
): boolean;
|
||||||
hasExtension<E extends ExtensionPointDefinition<any, unknown, any>>(
|
hasExtension<E extends ExtensionPointDefinition<any, unknown, any>>(
|
||||||
extensionPoint: E["name"],
|
extensionPoint: E["name"],
|
||||||
props?: E["props"]
|
props?: E["props"]
|
||||||
): boolean {
|
): boolean {
|
||||||
return this.getExtensions<E>(extensionPoint, props).length > 0;
|
return this.getExtensions(extensionPoint, props).length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,19 +22,32 @@
|
|||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactNode } from "react";
|
||||||
import {
|
import {
|
||||||
Branch,
|
Branch,
|
||||||
BranchDetails,
|
Changeset,
|
||||||
File,
|
File,
|
||||||
|
Group,
|
||||||
|
HalRepresentation,
|
||||||
|
Hit,
|
||||||
IndexResources,
|
IndexResources,
|
||||||
Links,
|
Links,
|
||||||
|
Me,
|
||||||
|
Namespace,
|
||||||
NamespaceStrategies,
|
NamespaceStrategies,
|
||||||
|
Person,
|
||||||
|
Plugin,
|
||||||
Repository,
|
Repository,
|
||||||
RepositoryCreation,
|
RepositoryCreation,
|
||||||
RepositoryTypeCollection
|
RepositoryRole,
|
||||||
|
RepositoryRoleBase,
|
||||||
|
RepositoryTypeCollection,
|
||||||
|
Tag,
|
||||||
|
User,
|
||||||
} from "@scm-manager/ui-types";
|
} from "@scm-manager/ui-types";
|
||||||
import { ExtensionPointDefinition } from "./binder";
|
import { ExtensionPointDefinition } from "./binder";
|
||||||
|
import { RenderableExtensionPointDefinition, SimpleRenderableDynamicExtensionPointDefinition } from "./ExtensionPoint";
|
||||||
|
import ExtractProps from "./extractProps";
|
||||||
|
|
||||||
type RepositoryCreatorSubFormProps = {
|
type RepositoryCreatorSubFormProps = {
|
||||||
repository: RepositoryCreation;
|
repository: RepositoryCreation;
|
||||||
@@ -43,128 +56,466 @@ type RepositoryCreatorSubFormProps = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RepositoryCreatorComponentProps = {
|
export type RepositoryCreatorComponentProps = ExtractProps<RepositoryCreator["type"]["component"]>;
|
||||||
namespaceStrategies: NamespaceStrategies;
|
|
||||||
repositoryTypes: RepositoryTypeCollection;
|
|
||||||
index: IndexResources;
|
|
||||||
|
|
||||||
nameForm: React.ComponentType<RepositoryCreatorSubFormProps>;
|
/**
|
||||||
informationForm: React.ComponentType<RepositoryCreatorSubFormProps>;
|
* @deprecated use {@link RepositoryCreator}`["type"]` instead
|
||||||
};
|
*/
|
||||||
|
export type RepositoryCreatorExtension = RepositoryCreator["type"];
|
||||||
|
export type RepositoryCreator = ExtensionPointDefinition<"repos.creator",
|
||||||
|
{
|
||||||
|
subtitle: string;
|
||||||
|
path: string;
|
||||||
|
icon: string;
|
||||||
|
label: string;
|
||||||
|
component: React.ComponentType<{
|
||||||
|
namespaceStrategies: NamespaceStrategies;
|
||||||
|
repositoryTypes: RepositoryTypeCollection;
|
||||||
|
index: IndexResources;
|
||||||
|
|
||||||
export type RepositoryCreatorExtension = {
|
nameForm: React.ComponentType<RepositoryCreatorSubFormProps>;
|
||||||
subtitle: string;
|
informationForm: React.ComponentType<RepositoryCreatorSubFormProps>;
|
||||||
path: string;
|
}>;
|
||||||
icon: string;
|
}>;
|
||||||
label: string;
|
|
||||||
component: React.ComponentType<RepositoryCreatorComponentProps>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RepositoryCreator = ExtensionPointDefinition<"repos.creator", RepositoryCreatorExtension>;
|
export type RepositoryFlags = RenderableExtensionPointDefinition<"repository.flags",
|
||||||
|
{ repository: Repository; tooltipLocation?: "bottom" | "right" | "top" | "left" }>;
|
||||||
|
|
||||||
export type RepositoryFlags = ExtensionPointDefinition<"repository.flags", { repository: Repository }>;
|
/**
|
||||||
|
* @deprecated use {@link ReposSourcesActionbar}`["props"]` instead
|
||||||
|
*/
|
||||||
|
export type ReposSourcesActionbarExtensionProps = ReposSourcesActionbar["props"];
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link ReposSourcesActionbar} instead
|
||||||
|
*/
|
||||||
|
export type ReposSourcesActionbarExtension = ReposSourcesActionbar;
|
||||||
|
export type ReposSourcesActionbar = RenderableExtensionPointDefinition<"repos.sources.actionbar",
|
||||||
|
{
|
||||||
|
baseUrl: string;
|
||||||
|
revision: string;
|
||||||
|
branch: Branch | undefined;
|
||||||
|
path: string;
|
||||||
|
sources: File;
|
||||||
|
repository: Repository;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type ReposSourcesActionbarExtensionProps = {
|
/**
|
||||||
baseUrl: string;
|
* @deprecated use {@link ReposSourcesEmptyActionbar}`["props"]` instead
|
||||||
revision: string;
|
*/
|
||||||
branch: Branch | undefined;
|
export type ReposSourcesEmptyActionbarExtensionProps = ReposSourcesEmptyActionbar["props"];
|
||||||
path: string;
|
/**
|
||||||
sources: File;
|
* @deprecated use {@link ReposSourcesEmptyActionbar} instead
|
||||||
repository: Repository;
|
*/
|
||||||
};
|
export type ReposSourcesEmptyActionbarExtension = ReposSourcesEmptyActionbar;
|
||||||
export type ReposSourcesActionbarExtension = React.ComponentType<ReposSourcesActionbarExtensionProps>;
|
export type ReposSourcesEmptyActionbar = RenderableExtensionPointDefinition<"repos.sources.empty.actionbar",
|
||||||
export type ReposSourcesActionbar = ExtensionPointDefinition<"repos.sources.actionbar", ReposSourcesActionbarExtension>;
|
{
|
||||||
|
sources: File;
|
||||||
|
repository: Repository;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type ReposSourcesEmptyActionbarExtensionProps = {
|
/**
|
||||||
sources: File;
|
* @deprecated use {@link ReposSourcesTreeWrapper}`["props"]` instead
|
||||||
repository: Repository;
|
*/
|
||||||
};
|
export type ReposSourcesTreeWrapperProps = ReposSourcesTreeWrapper["props"];
|
||||||
export type ReposSourcesEmptyActionbarExtension = ReposSourcesActionbarExtension;
|
|
||||||
export type ReposSourcesEmptyActionbar = ExtensionPointDefinition<
|
|
||||||
"repos.sources.empty.actionbar",
|
|
||||||
ReposSourcesEmptyActionbarExtension
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type ReposSourcesTreeWrapperProps = {
|
/**
|
||||||
repository: Repository;
|
* @deprecated use {@link ReposSourcesTreeWrapper} instead
|
||||||
directory: File;
|
*/
|
||||||
baseUrl: string;
|
export type ReposSourcesTreeWrapperExtension = ReposSourcesTreeWrapper;
|
||||||
revision: string;
|
export type ReposSourcesTreeWrapper = RenderableExtensionPointDefinition<"repos.source.tree.wrapper",
|
||||||
};
|
{
|
||||||
|
repository: Repository;
|
||||||
export type ReposSourcesTreeWrapperExtension = ExtensionPointDefinition<
|
directory: File;
|
||||||
"repos.source.tree.wrapper",
|
baseUrl: string;
|
||||||
React.ComponentType<ReposSourcesTreeWrapperProps>
|
revision: string;
|
||||||
>;
|
}>;
|
||||||
|
|
||||||
export type ReposSourcesTreeRowProps = {
|
export type ReposSourcesTreeRowProps = {
|
||||||
repository: Repository;
|
repository: Repository;
|
||||||
file: File;
|
file: File;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReposSourcesTreeRowRightExtension = ExtensionPointDefinition<
|
/**
|
||||||
"repos.sources.tree.row.right",
|
* @deprecated use {@link ReposSourcesTreeRowRight} instead
|
||||||
React.ComponentType<ReposSourcesTreeRowProps>
|
*/
|
||||||
>;
|
export type ReposSourcesTreeRowRightExtension = ReposSourcesTreeRowRight;
|
||||||
export type ReposSourcesTreeRowAfterExtension = ExtensionPointDefinition<
|
export type ReposSourcesTreeRowRight = RenderableExtensionPointDefinition<"repos.sources.tree.row.right",
|
||||||
"repos.sources.tree.row.after",
|
ReposSourcesTreeRowProps>;
|
||||||
React.ComponentType<ReposSourcesTreeRowProps>
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type PrimaryNavigationLoginButtonProps = {
|
/**
|
||||||
links: Links;
|
* @deprecated use {@link ReposSourcesTreeRowAfter} instead
|
||||||
label: string;
|
*/
|
||||||
loginUrl: string;
|
export type ReposSourcesTreeRowAfterExtension = ReposSourcesTreeRowAfter;
|
||||||
from: string;
|
export type ReposSourcesTreeRowAfter = RenderableExtensionPointDefinition<"repos.sources.tree.row.after",
|
||||||
to: string;
|
ReposSourcesTreeRowProps>;
|
||||||
className: string;
|
|
||||||
content: React.ReactNode;
|
/**
|
||||||
|
* @deprecated use {@link PrimaryNavigationLoginButton}`["props"]` instead
|
||||||
|
*/
|
||||||
|
export type PrimaryNavigationLoginButtonProps = PrimaryNavigationLoginButton["props"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use {@link PrimaryNavigationLoginButton} instead
|
||||||
|
*/
|
||||||
|
export type PrimaryNavigationLoginButtonExtension = PrimaryNavigationLoginButton;
|
||||||
|
export type PrimaryNavigationLoginButton = RenderableExtensionPointDefinition<"primary-navigation.login",
|
||||||
|
{
|
||||||
|
links: Links;
|
||||||
|
label: string;
|
||||||
|
loginUrl: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
className: string;
|
||||||
|
content: React.ReactNode;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link PrimaryNavigationLogoutButtonExtension}`["props"]` instead
|
||||||
|
*/
|
||||||
|
export type PrimaryNavigationLogoutButtonProps = PrimaryNavigationLogoutButton["props"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link PrimaryNavigationLogoutButton} instead
|
||||||
|
*/
|
||||||
|
export type PrimaryNavigationLogoutButtonExtension = PrimaryNavigationLogoutButton;
|
||||||
|
export type PrimaryNavigationLogoutButton = RenderableExtensionPointDefinition<"primary-navigation.logout",
|
||||||
|
{
|
||||||
|
links: Links;
|
||||||
|
label: string;
|
||||||
|
className: string;
|
||||||
|
content: React.ReactNode;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link SourceExtension}`["props"]` instead
|
||||||
|
*/
|
||||||
|
export type SourceExtensionProps = SourceExtension["props"];
|
||||||
|
export type SourceExtension = RenderableExtensionPointDefinition<"repos.sources.extensions",
|
||||||
|
{
|
||||||
|
repository: Repository;
|
||||||
|
baseUrl: string;
|
||||||
|
revision: string;
|
||||||
|
extension: string;
|
||||||
|
sources: File | undefined;
|
||||||
|
path: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link RepositoryOverviewTop}`["props"]` instead
|
||||||
|
*/
|
||||||
|
export type RepositoryOverviewTopExtensionProps = RepositoryOverviewTop["props"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link RepositoryOverviewTop} instead
|
||||||
|
*/
|
||||||
|
export type RepositoryOverviewTopExtension = RepositoryOverviewTop;
|
||||||
|
export type RepositoryOverviewTop = RenderableExtensionPointDefinition<"repository.overview.top",
|
||||||
|
{
|
||||||
|
page: number;
|
||||||
|
search: string;
|
||||||
|
namespace?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link RepositoryOverviewLeft} instead
|
||||||
|
*/
|
||||||
|
export type RepositoryOverviewLeftExtension = RepositoryOverviewLeft;
|
||||||
|
export type RepositoryOverviewLeft = ExtensionPointDefinition<"repository.overview.left", React.ComponentType>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link RepositoryOverviewTitle} instead
|
||||||
|
*/
|
||||||
|
export type RepositoryOverviewTitleExtension = RepositoryOverviewTitle;
|
||||||
|
export type RepositoryOverviewTitle = RenderableExtensionPointDefinition<"repository.overview.title">;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use {@link RepositoryOverviewSubtitle} instead
|
||||||
|
*/
|
||||||
|
export type RepositoryOverviewSubtitleExtension = RepositoryOverviewSubtitle;
|
||||||
|
export type RepositoryOverviewSubtitle = RenderableExtensionPointDefinition<"repository.overview.subtitle">;
|
||||||
|
|
||||||
|
// From docs
|
||||||
|
|
||||||
|
export type AdminNavigation = RenderableExtensionPointDefinition<"admin.navigation", { links: Links; url: string }>;
|
||||||
|
|
||||||
|
export type AdminRoute = RenderableExtensionPointDefinition<"admin.route", { links: Links; url: string }>;
|
||||||
|
|
||||||
|
export type AdminSetting = RenderableExtensionPointDefinition<"admin.setting", { links: Links; url: string }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - can be used to replace the whole description of a changeset
|
||||||
|
*
|
||||||
|
* @deprecated Use `changeset.description.tokens` instead
|
||||||
|
*/
|
||||||
|
export type ChangesetDescription = RenderableExtensionPointDefinition<"changeset.description",
|
||||||
|
{ changeset: Changeset; value: string }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Can be used to replace parts of a changeset description with components
|
||||||
|
* - Has to be bound with a funktion taking the changeset and the (partial) description and returning `Replacement` objects with the following attributes:
|
||||||
|
* - textToReplace: The text part of the description that should be replaced by a component
|
||||||
|
* - replacement: The component to take instead of the text to replace
|
||||||
|
* - replaceAll: Optional boolean; if set to `true`, all occurances of the text will be replaced (default: `false`)
|
||||||
|
*/
|
||||||
|
export type ChangesetDescriptionTokens = ExtensionPointDefinition<"changeset.description.tokens",
|
||||||
|
(changeset: Changeset, value: string) => Array<{
|
||||||
|
textToReplace: string;
|
||||||
|
replacement: ReactNode;
|
||||||
|
replaceAll?: boolean;
|
||||||
|
}>,
|
||||||
|
{ changeset: Changeset; value: string }>;
|
||||||
|
|
||||||
|
export type ChangesetRight = RenderableExtensionPointDefinition<"changeset.right",
|
||||||
|
{ repository: Repository; changeset: Changeset }>;
|
||||||
|
|
||||||
|
export type ChangesetsAuthorSuffix = RenderableExtensionPointDefinition<"changesets.author.suffix",
|
||||||
|
{ changeset: Changeset }>;
|
||||||
|
|
||||||
|
export type GroupNavigation = RenderableExtensionPointDefinition<"group.navigation", { group: Group; url: string }>;
|
||||||
|
|
||||||
|
export type GroupRoute = RenderableExtensionPointDefinition<"group.route", { group: Group; url: string }>;
|
||||||
|
export type GroupSetting = RenderableExtensionPointDefinition<"group.setting", { group: Group; url: string }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Add a new Route to the main Route (scm/)
|
||||||
|
* - Props: authenticated?: boolean, links: Links
|
||||||
|
*/
|
||||||
|
export type MainRoute = RenderableExtensionPointDefinition<"main.route",
|
||||||
|
{
|
||||||
|
me: Me;
|
||||||
|
authenticated?: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type PluginAvatar = RenderableExtensionPointDefinition<"plugins.plugin-avatar",
|
||||||
|
{
|
||||||
|
plugin: Plugin;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type PrimaryNavigation = RenderableExtensionPointDefinition<"primary-navigation", { links: Links }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - A placeholder for the first navigation menu.
|
||||||
|
* - A PrimaryNavigationLink Component can be used here
|
||||||
|
* - Actually this Extension Point is used from the Activity Plugin to display the activities at the first Main Navigation menu.
|
||||||
|
*/
|
||||||
|
export type PrimaryNavigationFirstMenu = RenderableExtensionPointDefinition<"primary-navigation.first-menu",
|
||||||
|
{ links: Links; label: string }>;
|
||||||
|
|
||||||
|
export type ProfileRoute = RenderableExtensionPointDefinition<"profile.route", { me: Me; url: string }>;
|
||||||
|
export type ProfileSetting = RenderableExtensionPointDefinition<"profile.setting",
|
||||||
|
{ me?: Me; url: string; links: Links }>;
|
||||||
|
|
||||||
|
export type RepoConfigRoute = RenderableExtensionPointDefinition<"repo-config.route",
|
||||||
|
{ repository: Repository; url: string }>;
|
||||||
|
|
||||||
|
export type RepoConfigDetails = RenderableExtensionPointDefinition<"repo-config.details",
|
||||||
|
{ repository: Repository; url: string }>;
|
||||||
|
|
||||||
|
export type ReposBranchDetailsInformation = RenderableExtensionPointDefinition<"repos.branch-details.information",
|
||||||
|
{ repository: Repository; branch: Branch }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Location: At meta data view for file
|
||||||
|
* - can be used to render additional meta data line
|
||||||
|
* - Props: file: string, repository: Repository, revision: string
|
||||||
|
*/
|
||||||
|
export type ReposContentMetaData = RenderableExtensionPointDefinition<"repos.content.metadata",
|
||||||
|
{ file: File; repository: Repository; revision: string }>;
|
||||||
|
|
||||||
|
export type ReposCreateNamespace = RenderableExtensionPointDefinition<"repos.create.namespace",
|
||||||
|
{
|
||||||
|
label: string;
|
||||||
|
helpText: string;
|
||||||
|
value: string;
|
||||||
|
onChange: (namespace: string) => void;
|
||||||
|
errorMessage: string;
|
||||||
|
validationError?: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ReposSourcesContentActionBar = RenderableExtensionPointDefinition<"repos.sources.content.actionbar",
|
||||||
|
{
|
||||||
|
repository: Repository;
|
||||||
|
file: File;
|
||||||
|
revision: string;
|
||||||
|
handleExtensionError: React.Dispatch<React.SetStateAction<Error | undefined>>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type RepositoryNavigation = RenderableExtensionPointDefinition<"repository.navigation",
|
||||||
|
{ repository: Repository; url: string; indexLinks: Links }>;
|
||||||
|
|
||||||
|
export type RepositoryNavigationTopLevel = RenderableExtensionPointDefinition<"repository.navigation.topLevel",
|
||||||
|
{ repository: Repository; url: string; indexLinks: Links }>;
|
||||||
|
|
||||||
|
export type RepositoryRoleDetailsInformation = RenderableExtensionPointDefinition<"repositoryRole.role-details.information",
|
||||||
|
{ role: RepositoryRole }>;
|
||||||
|
|
||||||
|
export type RepositorySetting = RenderableExtensionPointDefinition<"repository.setting",
|
||||||
|
{ repository: Repository; url: string; indexLinks: Links }>;
|
||||||
|
|
||||||
|
export type RepositoryAvatar = RenderableExtensionPointDefinition<"repos.repository-avatar",
|
||||||
|
{ repository: Repository }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Location: At each repository in repository overview
|
||||||
|
* - can be used to add avatar for each repository (e.g., to mark repository type)
|
||||||
|
*/
|
||||||
|
export type PrimaryRepositoryAvatar = RenderableExtensionPointDefinition<"repos.repository-avatar.primary",
|
||||||
|
{ repository: Repository }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Location: At bottom of a single repository view
|
||||||
|
* - can be used to show detailed information about the repository (how to clone, e.g.)
|
||||||
|
*/
|
||||||
|
export type RepositoryDetailsInformation = RenderableExtensionPointDefinition<"repos.repository-details.information",
|
||||||
|
{ repository: Repository }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Location: At sources viewer
|
||||||
|
* - can be used to render a special source that is not an image or a source code
|
||||||
|
*/
|
||||||
|
export type RepositorySourcesView = RenderableExtensionPointDefinition<"repos.sources.view",
|
||||||
|
{ file: File; contentType: string; revision: string; basePath: string }>;
|
||||||
|
|
||||||
|
export type RolesRoute = RenderableExtensionPointDefinition<"roles.route",
|
||||||
|
{ role: HalRepresentation & RepositoryRoleBase & { creationDate?: string; lastModified?: string }; url: string }>;
|
||||||
|
|
||||||
|
export type UserRoute = RenderableExtensionPointDefinition<"user.route", { user: User; url: string }>;
|
||||||
|
export type UserSetting = RenderableExtensionPointDefinition<"user.setting", { user: User; url: string }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Dynamic extension point for custom language-specific renderers
|
||||||
|
* - Overrides the default Syntax Highlighter for the given language
|
||||||
|
* - Used by the Markdown Plantuml Plugin
|
||||||
|
*/
|
||||||
|
export type MarkdownCodeRenderer<Language extends string | undefined = undefined> =
|
||||||
|
SimpleRenderableDynamicExtensionPointDefinition<"markdown-renderer.code.",
|
||||||
|
Language,
|
||||||
|
{
|
||||||
|
language?: Language extends string ? Language : string;
|
||||||
|
value: string;
|
||||||
|
indexLinks: Links;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Define custom protocols and their renderers for links in markdown
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```markdown
|
||||||
|
* [description](myprotocol:somelink)
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```typescript
|
||||||
|
* binder.bind<extensionPoints.MarkdownLinkProtocolRenderer<"myprotocol">>("markdown-renderer.link.protocol", { protocol: "myprotocol", renderer: MyProtocolRenderer })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export type MarkdownLinkProtocolRenderer<Protocol extends string | undefined = undefined> = ExtensionPointDefinition<"markdown-renderer.link.protocol",
|
||||||
|
{
|
||||||
|
protocol: Protocol extends string ? Protocol : string;
|
||||||
|
renderer: React.ComponentType<{
|
||||||
|
protocol: Protocol extends string ? Protocol : string;
|
||||||
|
href: string;
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to determine an avatar image url from a given {@link Person}.
|
||||||
|
*
|
||||||
|
* @see https://github.com/scm-manager/scm-gravatar-plugin
|
||||||
|
*/
|
||||||
|
export type AvatarFactory = ExtensionPointDefinition<"avatar.factory", (person: Person) => string | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Location: At every changeset (detailed view as well as changeset overview)
|
||||||
|
* - can be used to add avatar (such as gravatar) for each changeset
|
||||||
|
*
|
||||||
|
* @deprecated Has no effect, use {@link AvatarFactory} instead
|
||||||
|
*/
|
||||||
|
export type ChangesetAvatarFactory = ExtensionPointDefinition<"changeset.avatar-factory",
|
||||||
|
(changeset: Changeset) => void>;
|
||||||
|
|
||||||
|
type MainRedirectProps = {
|
||||||
|
me: Me;
|
||||||
|
authenticated?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PrimaryNavigationLoginButtonExtension = ExtensionPointDefinition<
|
/**
|
||||||
"primary-navigation.login",
|
* - Extension Point for a link factory that provide the Redirect Link
|
||||||
PrimaryNavigationLoginButtonProps
|
* - Actually used from the activity plugin: binder.bind("main.redirect", () => "/activity");
|
||||||
>;
|
*/
|
||||||
|
export type MainRedirect = ExtensionPointDefinition<"main.redirect",
|
||||||
|
(props: MainRedirectProps) => string,
|
||||||
|
MainRedirectProps>;
|
||||||
|
|
||||||
export type PrimaryNavigationLogoutButtonProps = {
|
/**
|
||||||
links: Links;
|
* - A Factory function to create markdown [renderer](https://github.com/rexxars/react-markdown#node-types)
|
||||||
label: string;
|
* - The factory function will be called with a renderContext parameter of type Object. this parameter is given as a prop for the {@link MarkdownView} component.
|
||||||
className: string;
|
*
|
||||||
content: React.ReactNode;
|
* @deprecated Use {@link MarkdownCodeRenderer} or {@link MarkdownLinkProtocolRenderer} instead
|
||||||
};
|
*/
|
||||||
|
export type MarkdownRendererFactory = ExtensionPointDefinition<"markdown-renderer-factory",
|
||||||
|
(renderContext: unknown) => Record<string, React.ComponentType<any>>>;
|
||||||
|
|
||||||
export type PrimaryNavigationLogoutButtonExtension = ExtensionPointDefinition<
|
export type RepositoryCardBeforeTitle = RenderableExtensionPointDefinition<"repository.card.beforeTitle",
|
||||||
"primary-navigation.logout",
|
{ repository: Repository }>;
|
||||||
PrimaryNavigationLogoutButtonProps
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type SourceExtensionProps = {
|
export type RepositoryCreationInitialization = RenderableExtensionPointDefinition<"repos.create.initialize",
|
||||||
|
{
|
||||||
|
repository: Repository;
|
||||||
|
setCreationContextEntry: (key: string, value: any) => void;
|
||||||
|
indexResources: Partial<HalRepresentation> & {
|
||||||
|
links?: Links;
|
||||||
|
version?: string;
|
||||||
|
initialization?: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type NamespaceTopLevelNavigation = RenderableExtensionPointDefinition<"namespace.navigation.topLevel",
|
||||||
|
{ namespace: Namespace; url: string }>;
|
||||||
|
|
||||||
|
export type NamespaceRoute = RenderableExtensionPointDefinition<"namespace.route",
|
||||||
|
{ namespace: Namespace; url: string }>;
|
||||||
|
|
||||||
|
export type NamespaceSetting = RenderableExtensionPointDefinition<"namespace.setting",
|
||||||
|
{ namespace: Namespace; url: string }>;
|
||||||
|
|
||||||
|
export type RepositoryTagDetailsInformation = RenderableExtensionPointDefinition<"repos.tag-details.information",
|
||||||
|
{ repository: Repository; tag: Tag }>;
|
||||||
|
|
||||||
|
export type SearchHitRenderer<Type extends string | undefined = undefined> = RenderableExtensionPointDefinition<Type extends string ? `search.hit.${Type}.renderer` : `search.hit.${string}.renderer`,
|
||||||
|
{ hit: Hit }>;
|
||||||
|
|
||||||
|
export type RepositorySourcesContentDownloadButton = RenderableExtensionPointDefinition<"repos.sources.content.downloadButton",
|
||||||
|
{ repository: Repository; file: File }>;
|
||||||
|
|
||||||
|
export type RepositoryRoute = RenderableExtensionPointDefinition<"repository.route",
|
||||||
|
{ repository: Repository; url: string; indexLinks: Links }>;
|
||||||
|
|
||||||
|
type RepositoryRedirectProps = {
|
||||||
|
namespace: string;
|
||||||
|
name: string;
|
||||||
repository: Repository;
|
repository: Repository;
|
||||||
baseUrl: string;
|
loading: false;
|
||||||
revision: string;
|
error: null;
|
||||||
extension: string;
|
repoLink: string;
|
||||||
sources: File | undefined;
|
indexLinks: Links;
|
||||||
path: string;
|
match: {
|
||||||
};
|
params: {
|
||||||
export type SourceExtension = ExtensionPointDefinition<"repos.sources.extensions", SourceExtensionProps>;
|
namespace: string;
|
||||||
|
name: string;
|
||||||
export type RepositoryOverviewTopExtensionProps = {
|
};
|
||||||
page: number;
|
isExact: boolean;
|
||||||
search: string;
|
path: string;
|
||||||
namespace?: string;
|
url: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RepositoryOverviewTopExtension = ExtensionPointDefinition<
|
export type RepositoryRedirect = ExtensionPointDefinition<"repository.redirect",
|
||||||
"repository.overview.top",
|
(props: RepositoryRedirectProps) => string,
|
||||||
React.ComponentType<RepositoryOverviewTopExtensionProps>,
|
RepositoryRedirectProps>;
|
||||||
RepositoryOverviewTopExtensionProps
|
|
||||||
>;
|
export type InitializationStep<Step extends string | undefined = undefined> =
|
||||||
export type RepositoryOverviewLeftExtension = ExtensionPointDefinition<"repository.overview.left", React.ComponentType>;
|
SimpleRenderableDynamicExtensionPointDefinition<"initialization.step.",
|
||||||
export type RepositoryOverviewTitleExtension = ExtensionPointDefinition<
|
Step,
|
||||||
"repository.overview.title",
|
{
|
||||||
React.ComponentType
|
data: HalRepresentation;
|
||||||
>;
|
}>;
|
||||||
export type RepositoryOverviewSubtitleExtension = ExtensionPointDefinition<
|
|
||||||
"repository.overview.subtitle",
|
|
||||||
React.ComponentType
|
|
||||||
>;
|
|
||||||
|
|||||||
28
scm-ui/ui-extensions/src/extractProps.ts
Normal file
28
scm-ui/ui-extensions/src/extractProps.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type ExtractProps<T> = T extends React.ComponentType<infer U> ? U : never;
|
||||||
|
|
||||||
|
export default ExtractProps;
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
export { default as binder, Binder, ExtensionPointDefinition } from "./binder";
|
export { default as binder, Binder, ExtensionPointDefinition } from "./binder";
|
||||||
export * from "./useBinder";
|
export * from "./useBinder";
|
||||||
export { default as ExtensionPoint } from "./ExtensionPoint";
|
export { default as ExtensionPoint } from "./ExtensionPoint";
|
||||||
|
export { default as ExtractProps } from "./extractProps";
|
||||||
|
|
||||||
// suppress eslint prettier warning,
|
// suppress eslint prettier warning,
|
||||||
// because prettier does not understand "* as"
|
// because prettier does not understand "* as"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Redirect, Route, RouteProps, Switch, useRouteMatch } from "react-router-dom";
|
import { Redirect, Route, RouteProps, Switch, useRouteMatch } from "react-router-dom";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import {
|
import {
|
||||||
CustomQueryFlexWrappedColumns,
|
CustomQueryFlexWrappedColumns,
|
||||||
NavLink,
|
NavLink,
|
||||||
@@ -97,7 +97,7 @@ const Admin: FC = () => {
|
|||||||
<Route path={`${url}/roles/:page`} exact>
|
<Route path={`${url}/roles/:page`} exact>
|
||||||
<RepositoryRoles baseUrl={`${url}/roles`} />
|
<RepositoryRoles baseUrl={`${url}/roles`} />
|
||||||
</Route>
|
</Route>
|
||||||
<ExtensionPoint name="admin.route" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.AdminRoute> name="admin.route" props={extensionProps} renderAll={true} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</PrimaryContentColumn>
|
</PrimaryContentColumn>
|
||||||
<SecondaryNavigationColumn>
|
<SecondaryNavigationColumn>
|
||||||
@@ -142,7 +142,11 @@ const Admin: FC = () => {
|
|||||||
activeWhenMatch={matchesRoles}
|
activeWhenMatch={matchesRoles}
|
||||||
activeOnlyWhenExact={false}
|
activeOnlyWhenExact={false}
|
||||||
/>
|
/>
|
||||||
<ExtensionPoint name="admin.navigation" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.AdminNavigation>
|
||||||
|
name="admin.navigation"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
<SubNavigation
|
<SubNavigation
|
||||||
to={`${url}/settings/general`}
|
to={`${url}/settings/general`}
|
||||||
label={t("admin.menu.settingsNavLink")}
|
label={t("admin.menu.settingsNavLink")}
|
||||||
@@ -154,7 +158,11 @@ const Admin: FC = () => {
|
|||||||
label={t("admin.menu.generalNavLink")}
|
label={t("admin.menu.generalNavLink")}
|
||||||
testId="admin-settings-general-link"
|
testId="admin-settings-general-link"
|
||||||
/>
|
/>
|
||||||
<ExtensionPoint name="admin.setting" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.AdminSetting>
|
||||||
|
name="admin.setting"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</SubNavigation>
|
</SubNavigation>
|
||||||
</SecondaryNavigation>
|
</SecondaryNavigation>
|
||||||
</SecondaryNavigationColumn>
|
</SecondaryNavigationColumn>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { Plugin } from "@scm-manager/ui-types";
|
import { Plugin } from "@scm-manager/ui-types";
|
||||||
import { Image } from "@scm-manager/ui-components";
|
import { Image } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export default class PluginAvatar extends React.Component<Props> {
|
|||||||
const { plugin } = this.props;
|
const { plugin } = this.props;
|
||||||
return (
|
return (
|
||||||
<BoundingBox className="image is-64x64">
|
<BoundingBox className="image is-64x64">
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.PluginAvatar>
|
||||||
name="plugins.plugin-avatar"
|
name="plugins.plugin-avatar"
|
||||||
props={{
|
props={{
|
||||||
plugin
|
plugin
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { WithTranslation, withTranslation } from "react-i18next";
|
import { WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { ExtensionPoint } 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 { Button, Level } from "@scm-manager/ui-components";
|
||||||
import PermissionRoleDetailsTable from "./PermissionRoleDetailsTable";
|
import PermissionRoleDetailsTable from "./PermissionRoleDetailsTable";
|
||||||
@@ -54,7 +54,7 @@ class PermissionRoleDetails extends React.Component<Props> {
|
|||||||
<>
|
<>
|
||||||
<PermissionRoleDetailsTable role={role} />
|
<PermissionRoleDetailsTable role={role} />
|
||||||
{this.renderEditButton()}
|
{this.renderEditButton()}
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.RepositoryRoleDetailsInformation>
|
||||||
name="repositoryRole.role-details.information"
|
name="repositoryRole.role-details.information"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
props={{
|
props={{
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Route, useParams, useRouteMatch } from "react-router-dom";
|
import { Route, useParams, useRouteMatch } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { ErrorPage, Loading, Title, urls } from "@scm-manager/ui-components";
|
import { ErrorPage, Loading, Title, urls } from "@scm-manager/ui-components";
|
||||||
import PermissionRoleDetail from "../components/PermissionRoleDetails";
|
import PermissionRoleDetail from "../components/PermissionRoleDetails";
|
||||||
import EditRepositoryRole from "./EditRepositoryRole";
|
import EditRepositoryRole from "./EditRepositoryRole";
|
||||||
@@ -62,7 +62,7 @@ const SingleRepositoryRole: FC = () => {
|
|||||||
<Route path={`${url}/edit`} exact>
|
<Route path={`${url}/edit`} exact>
|
||||||
<EditRepositoryRole role={role} />
|
<EditRepositoryRole role={role} />
|
||||||
</Route>
|
</Route>
|
||||||
<ExtensionPoint name="roles.route" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.RolesRoute> name="roles.route" props={extensionProps} renderAll={true} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,14 +23,14 @@
|
|||||||
*/
|
*/
|
||||||
import React, { FC, useState } from "react";
|
import React, { FC, useState } from "react";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { ErrorBoundary, Loading, Header } from "@scm-manager/ui-components";
|
import { ErrorBoundary, Header, Loading } from "@scm-manager/ui-components";
|
||||||
import PluginLoader from "./PluginLoader";
|
import PluginLoader from "./PluginLoader";
|
||||||
import ScrollToTop from "./ScrollToTop";
|
import ScrollToTop from "./ScrollToTop";
|
||||||
import IndexErrorPage from "./IndexErrorPage";
|
import IndexErrorPage from "./IndexErrorPage";
|
||||||
import { useIndex } from "@scm-manager/ui-api";
|
import { useIndex } from "@scm-manager/ui-api";
|
||||||
import { Link } from "@scm-manager/ui-types";
|
import { Link } from "@scm-manager/ui-types";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import { binder } from "@scm-manager/ui-extensions";
|
import { binder, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import InitializationAdminAccountStep from "./InitializationAdminAccountStep";
|
import InitializationAdminAccountStep from "./InitializationAdminAccountStep";
|
||||||
|
|
||||||
const Index: FC = () => {
|
const Index: FC = () => {
|
||||||
@@ -69,4 +69,7 @@ const Index: FC = () => {
|
|||||||
|
|
||||||
export default Index;
|
export default Index;
|
||||||
|
|
||||||
binder.bind("initialization.step.adminAccount", InitializationAdminAccountStep);
|
binder.bind<extensionPoints.InitializationStep<"adminAccount">>(
|
||||||
|
"initialization.step.adminAccount",
|
||||||
|
InitializationAdminAccountStep
|
||||||
|
);
|
||||||
|
|||||||
@@ -27,10 +27,9 @@ import { urls } from "@scm-manager/ui-components";
|
|||||||
import { binder, ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
import { binder, ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Links } from "@scm-manager/ui-types";
|
import { Links } from "@scm-manager/ui-types";
|
||||||
import { useLocation } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import HeaderButton from "../components/HeaderButton";
|
import HeaderButton from "../components/HeaderButton";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import HeaderButtonContent, { headerButtonContentClassName } from "../components/HeaderButtonContent";
|
import HeaderButtonContent, { headerButtonContentClassName } from "../components/HeaderButtonContent";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -63,14 +62,20 @@ const LoginButton: FC<Props> = ({ burgerMode, links, className }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (links?.login) {
|
if (links?.login) {
|
||||||
const shouldRenderExtension = binder.hasExtension("primary-navigation.login", extensionProps);
|
const shouldRenderExtension = binder.hasExtension<extensionPoints.PrimaryNavigationLoginButton>(
|
||||||
|
"primary-navigation.login",
|
||||||
|
extensionProps
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<HeaderButton
|
<HeaderButton
|
||||||
data-testid="primary-navigation-login"
|
data-testid="primary-navigation-login"
|
||||||
className={classNames("is-flex-start", "navbar-item", className)}
|
className={classNames("is-flex-start", "navbar-item", className)}
|
||||||
>
|
>
|
||||||
{shouldRenderExtension ? (
|
{shouldRenderExtension ? (
|
||||||
<ExtensionPoint name="primary-navigation.login" props={extensionProps} />
|
<ExtensionPoint<extensionPoints.PrimaryNavigationLoginButton>
|
||||||
|
name="primary-navigation.login"
|
||||||
|
props={extensionProps}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Link to={to} className={headerButtonContentClassName}>
|
<Link to={to} className={headerButtonContentClassName}>
|
||||||
{content}
|
{content}
|
||||||
|
|||||||
@@ -43,16 +43,18 @@ const LogoutButton: FC<Props> = ({ burgerMode, links, className }) => {
|
|||||||
const label = t("primary-navigation.logout");
|
const label = t("primary-navigation.logout");
|
||||||
const content = <HeaderButtonContent burgerMode={burgerMode} label={label} icon="sign-out-alt" />;
|
const content = <HeaderButtonContent burgerMode={burgerMode} label={label} icon="sign-out-alt" />;
|
||||||
|
|
||||||
const extensionProps: extensionPoints.PrimaryNavigationLogoutButtonProps = {
|
const extensionProps = {
|
||||||
links,
|
links,
|
||||||
label,
|
label,
|
||||||
|
|
||||||
className: headerButtonContentClassName,
|
className: headerButtonContentClassName,
|
||||||
content
|
content
|
||||||
};
|
};
|
||||||
|
|
||||||
if (links?.logout) {
|
if (links?.logout) {
|
||||||
const shouldRenderExtension = binder.hasExtension("primary-navigation.logout", extensionProps);
|
const shouldRenderExtension = binder.hasExtension<extensionPoints.PrimaryNavigationLogoutButton>(
|
||||||
|
"primary-navigation.logout",
|
||||||
|
extensionProps
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderButton
|
<HeaderButton
|
||||||
@@ -60,7 +62,10 @@ const LogoutButton: FC<Props> = ({ burgerMode, links, className }) => {
|
|||||||
className={classNames("is-flex-start", "navbar-item", className)}
|
className={classNames("is-flex-start", "navbar-item", className)}
|
||||||
>
|
>
|
||||||
{shouldRenderExtension ? (
|
{shouldRenderExtension ? (
|
||||||
<ExtensionPoint name="primary-navigation.logout" props={extensionProps} />
|
<ExtensionPoint<extensionPoints.PrimaryNavigationLogoutButton>
|
||||||
|
name="primary-navigation.logout"
|
||||||
|
props={extensionProps}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Link to="/logout" className={headerButtonContentClassName}>
|
<Link to="/logout" className={headerButtonContentClassName}>
|
||||||
{content}
|
{content}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { Redirect, Route, Switch } from "react-router-dom";
|
|||||||
import { Links, Me } from "@scm-manager/ui-types";
|
import { Links, Me } from "@scm-manager/ui-types";
|
||||||
|
|
||||||
import { ErrorBoundary, Loading, ProtectedRoute } from "@scm-manager/ui-components";
|
import { ErrorBoundary, Loading, ProtectedRoute } from "@scm-manager/ui-components";
|
||||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { binder, ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
// auth routes
|
// auth routes
|
||||||
const Login = React.lazy(() => import("../containers/Login"));
|
const Login = React.lazy(() => import("../containers/Login"));
|
||||||
@@ -65,7 +65,7 @@ type Props = {
|
|||||||
|
|
||||||
const Main: FC<Props> = props => {
|
const Main: FC<Props> = props => {
|
||||||
const { authenticated, me } = props;
|
const { authenticated, me } = props;
|
||||||
const redirectUrlFactory = binder.getExtension("main.redirect", props);
|
const redirectUrlFactory = binder.getExtension<extensionPoints.MainRedirect>("main.redirect", props);
|
||||||
let url = "/";
|
let url = "/";
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
url = "/repos/";
|
url = "/repos/";
|
||||||
@@ -110,7 +110,7 @@ const Main: FC<Props> = props => {
|
|||||||
<ProtectedRoute path="/search/:type/:page" component={Search} authenticated={authenticated} />
|
<ProtectedRoute path="/search/:type/:page" component={Search} authenticated={authenticated} />
|
||||||
<ProtectedRoute path="/search/:type/" component={Search} authenticated={authenticated} />
|
<ProtectedRoute path="/search/:type/" component={Search} authenticated={authenticated} />
|
||||||
<ProtectedRoute path="/help/search-syntax/" component={Syntax} authenticated={authenticated} />
|
<ProtectedRoute path="/help/search-syntax/" component={Syntax} authenticated={authenticated} />
|
||||||
<ExtensionPoint name="main.route" renderAll={true} props={props} />
|
<ExtensionPoint<extensionPoints.MainRoute> name="main.route" renderAll={true} props={props} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import {
|
|||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import ChangeUserPassword from "./ChangeUserPassword";
|
import ChangeUserPassword from "./ChangeUserPassword";
|
||||||
import ProfileInfo from "./ProfileInfo";
|
import ProfileInfo from "./ProfileInfo";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import SetPublicKeys from "../users/components/publicKeys/SetPublicKeys";
|
import SetPublicKeys from "../users/components/publicKeys/SetPublicKeys";
|
||||||
import SetPublicKeysNavLink from "../users/components/navLinks/SetPublicKeysNavLink";
|
import SetPublicKeysNavLink from "../users/components/navLinks/SetPublicKeysNavLink";
|
||||||
import SetApiKeys from "../users/components/apiKeys/SetApiKeys";
|
import SetApiKeys from "../users/components/apiKeys/SetApiKeys";
|
||||||
@@ -100,7 +100,11 @@ const Profile: FC = () => {
|
|||||||
<SetApiKeys user={me} />
|
<SetApiKeys user={me} />
|
||||||
</Route>
|
</Route>
|
||||||
)}
|
)}
|
||||||
<ExtensionPoint name="profile.route" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.ProfileRoute>
|
||||||
|
name="profile.route"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</PrimaryContentColumn>
|
</PrimaryContentColumn>
|
||||||
<SecondaryNavigationColumn>
|
<SecondaryNavigationColumn>
|
||||||
<SecondaryNavigation label={t("profile.navigationLabel")}>
|
<SecondaryNavigation label={t("profile.navigationLabel")}>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Route, useParams, useRouteMatch } from "react-router-dom";
|
import { Route, useParams, useRouteMatch } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import {
|
import {
|
||||||
CustomQueryFlexWrappedColumns,
|
CustomQueryFlexWrappedColumns,
|
||||||
ErrorPage,
|
ErrorPage,
|
||||||
@@ -79,7 +79,7 @@ const SingleGroup: FC = () => {
|
|||||||
<Route path={`${url}/settings/permissions`} exact>
|
<Route path={`${url}/settings/permissions`} exact>
|
||||||
<SetGroupPermissions group={group} />
|
<SetGroupPermissions group={group} />
|
||||||
</Route>
|
</Route>
|
||||||
<ExtensionPoint name="group.route" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.GroupRoute> name="group.route" props={extensionProps} renderAll={true} />
|
||||||
</PrimaryContentColumn>
|
</PrimaryContentColumn>
|
||||||
<SecondaryNavigationColumn>
|
<SecondaryNavigationColumn>
|
||||||
<SecondaryNavigation label={t("singleGroup.menu.navigationLabel")}>
|
<SecondaryNavigation label={t("singleGroup.menu.navigationLabel")}>
|
||||||
@@ -89,7 +89,11 @@ const SingleGroup: FC = () => {
|
|||||||
label={t("singleGroup.menu.informationNavLink")}
|
label={t("singleGroup.menu.informationNavLink")}
|
||||||
title={t("singleGroup.menu.informationNavLink")}
|
title={t("singleGroup.menu.informationNavLink")}
|
||||||
/>
|
/>
|
||||||
<ExtensionPoint name="group.navigation" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.GroupNavigation>
|
||||||
|
name="group.navigation"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
<SubNavigation
|
<SubNavigation
|
||||||
to={`${url}/settings/general`}
|
to={`${url}/settings/general`}
|
||||||
label={t("singleGroup.menu.settingsNavLink")}
|
label={t("singleGroup.menu.settingsNavLink")}
|
||||||
@@ -97,7 +101,11 @@ const SingleGroup: FC = () => {
|
|||||||
>
|
>
|
||||||
<EditGroupNavLink group={group} editUrl={`${url}/settings/general`} />
|
<EditGroupNavLink group={group} editUrl={`${url}/settings/general`} />
|
||||||
<SetPermissionsNavLink group={group} permissionsUrl={`${url}/settings/permissions`} />
|
<SetPermissionsNavLink group={group} permissionsUrl={`${url}/settings/permissions`} />
|
||||||
<ExtensionPoint name="group.setting" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.GroupSetting>
|
||||||
|
name="group.setting"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</SubNavigation>
|
</SubNavigation>
|
||||||
</SecondaryNavigation>
|
</SecondaryNavigation>
|
||||||
</SecondaryNavigationColumn>
|
</SecondaryNavigationColumn>
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ import i18n from "./i18n";
|
|||||||
import { BrowserRouter as Router } from "react-router-dom";
|
import { BrowserRouter as Router } from "react-router-dom";
|
||||||
|
|
||||||
import { urls } from "@scm-manager/ui-components";
|
import { urls } from "@scm-manager/ui-components";
|
||||||
import { binder } from "@scm-manager/ui-extensions";
|
import { binder, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import ChangesetShortLink from "./repos/components/changesets/ChangesetShortLink";
|
import ChangesetShortLink from "./repos/components/changesets/ChangesetShortLink";
|
||||||
|
|
||||||
import "./tokenExpired";
|
import "./tokenExpired";
|
||||||
import { ApiProvider } from "@scm-manager/ui-api";
|
import { ApiProvider } from "@scm-manager/ui-api";
|
||||||
|
|
||||||
binder.bind("changeset.description.tokens", ChangesetShortLink);
|
binder.bind<extensionPoints.ChangesetDescriptionTokens>("changeset.description.tokens", ChangesetShortLink);
|
||||||
|
|
||||||
const root = document.getElementById("root");
|
const root = document.getElementById("root");
|
||||||
if (!root) {
|
if (!root) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import BranchDetail from "./BranchDetail";
|
import BranchDetail from "./BranchDetail";
|
||||||
import { ExtensionPoint } 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";
|
||||||
import BranchDangerZone from "../containers/BranchDangerZone";
|
import BranchDangerZone from "../containers/BranchDangerZone";
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ class BranchView extends React.Component<Props> {
|
|||||||
<BranchDetail repository={repository} branch={branch} />
|
<BranchDetail repository={repository} branch={branch} />
|
||||||
<hr />
|
<hr />
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.ReposBranchDetailsInformation>
|
||||||
name="repos.branch-details.information"
|
name="repos.branch-details.information"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
props={{
|
props={{
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { CUSTOM_NAMESPACE_STRATEGY } from "@scm-manager/ui-types";
|
import { CUSTOM_NAMESPACE_STRATEGY } from "@scm-manager/ui-types";
|
||||||
import { Autocomplete } from "@scm-manager/ui-components";
|
import { Autocomplete } from "@scm-manager/ui-components";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNamespaceSuggestions } from "@scm-manager/ui-api";
|
import { useNamespaceSuggestions } from "@scm-manager/ui-api";
|
||||||
|
|
||||||
@@ -77,7 +77,13 @@ const NamespaceInput: FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ExtensionPoint name="repos.create.namespace" props={props} renderAll={false} />;
|
return (
|
||||||
|
<ExtensionPoint<extensionPoints.ReposCreateNamespace>
|
||||||
|
name="repos.create.namespace"
|
||||||
|
props={props}
|
||||||
|
renderAll={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NamespaceInput;
|
export default NamespaceInput;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Repository } from "@scm-manager/ui-types";
|
import { Repository } from "@scm-manager/ui-types";
|
||||||
import RepositoryDetailTable from "./RepositoryDetailTable";
|
import RepositoryDetailTable from "./RepositoryDetailTable";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
repository: Repository;
|
repository: Repository;
|
||||||
@@ -38,7 +38,7 @@ class RepositoryDetails extends React.Component<Props> {
|
|||||||
<RepositoryDetailTable repository={repository} />
|
<RepositoryDetailTable repository={repository} />
|
||||||
<hr />
|
<hr />
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.RepositoryDetailsInformation>
|
||||||
name="repos.repository-details.information"
|
name="repos.repository-details.information"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
props={{
|
props={{
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import React, { FC, useState } from "react";
|
|||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { Changeset, ParentChangeset, Repository } from "@scm-manager/ui-types";
|
import { Changeset, ParentChangeset, Repository } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
AvatarImage,
|
AvatarImage,
|
||||||
@@ -146,7 +146,7 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
|
|||||||
<>
|
<>
|
||||||
<div className={classNames("content", "m-0")}>
|
<div className={classNames("content", "m-0")}>
|
||||||
<SubSubtitle>
|
<SubSubtitle>
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.ChangesetDescription>
|
||||||
name="changeset.description"
|
name="changeset.description"
|
||||||
props={{
|
props={{
|
||||||
changeset,
|
changeset,
|
||||||
@@ -206,7 +206,7 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
|
|||||||
{description.message.split("\n").map((item, key) => {
|
{description.message.split("\n").map((item, key) => {
|
||||||
return (
|
return (
|
||||||
<span key={key}>
|
<span key={key}>
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.ChangesetDescription>
|
||||||
name="changeset.description"
|
name="changeset.description"
|
||||||
props={{
|
props={{
|
||||||
changeset,
|
changeset,
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import React, { FC } from "react";
|
|||||||
import { Changeset, Person } from "@scm-manager/ui-types";
|
import { Changeset, Person } from "@scm-manager/ui-types";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useBinder } from "@scm-manager/ui-extensions";
|
import { extensionPoints, useBinder } from "@scm-manager/ui-extensions";
|
||||||
import { ContributorAvatar, CommaSeparatedList } from "@scm-manager/ui-components";
|
import { CommaSeparatedList, ContributorAvatar } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
changeset: Changeset;
|
changeset: Changeset;
|
||||||
@@ -39,7 +39,7 @@ const SizedTd = styled.td`
|
|||||||
const Contributor: FC<{ person: Person }> = ({ person }) => {
|
const Contributor: FC<{ person: Person }> = ({ person }) => {
|
||||||
const [t] = useTranslation("repos");
|
const [t] = useTranslation("repos");
|
||||||
const binder = useBinder();
|
const binder = useBinder();
|
||||||
const avatarFactory = binder.getExtension("avatar.factory");
|
const avatarFactory = binder.getExtension<extensionPoints.AvatarFactory>("avatar.factory");
|
||||||
let prefix = null;
|
let prefix = null;
|
||||||
if (avatarFactory) {
|
if (avatarFactory) {
|
||||||
const avatar = avatarFactory(person);
|
const avatar = avatarFactory(person);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { NamespaceCollection, Repository } from "@scm-manager/ui-types";
|
|||||||
|
|
||||||
import groupByNamespace from "./groupByNamespace";
|
import groupByNamespace from "./groupByNamespace";
|
||||||
import RepositoryGroupEntry from "./RepositoryGroupEntry";
|
import RepositoryGroupEntry from "./RepositoryGroupEntry";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
repositories: Repository[];
|
repositories: Repository[];
|
||||||
@@ -44,7 +44,7 @@ class RepositoryList extends React.Component<Props> {
|
|||||||
const groups = groupByNamespace(repositories, namespaces);
|
const groups = groupByNamespace(repositories, namespaces);
|
||||||
return (
|
return (
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.RepositoryOverviewTop>
|
||||||
name="repository.overview.top"
|
name="repository.overview.top"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
props={{
|
props={{
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { useRouteMatch } from "react-router-dom";
|
|||||||
import RepositoryForm from "../components/form";
|
import RepositoryForm from "../components/form";
|
||||||
import { Repository } from "@scm-manager/ui-types";
|
import { Repository } from "@scm-manager/ui-types";
|
||||||
import { ErrorNotification, Subtitle, urls } from "@scm-manager/ui-components";
|
import { ErrorNotification, Subtitle, urls } from "@scm-manager/ui-components";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import RepositoryDangerZone from "./RepositoryDangerZone";
|
import RepositoryDangerZone from "./RepositoryDangerZone";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ExportRepository from "./ExportRepository";
|
import ExportRepository from "./ExportRepository";
|
||||||
@@ -57,9 +57,17 @@ const EditRepo: FC<Props> = ({ repository }) => {
|
|||||||
<UpdateNotification isUpdated={isUpdated} />
|
<UpdateNotification isUpdated={isUpdated} />
|
||||||
<ErrorNotification error={error} />
|
<ErrorNotification error={error} />
|
||||||
<RepositoryForm repository={repository} loading={isLoading} modifyRepository={update} />
|
<RepositoryForm repository={repository} loading={isLoading} modifyRepository={update} />
|
||||||
<ExtensionPoint name="repo-config.details" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.RepoConfigDetails>
|
||||||
|
name="repo-config.details"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
{repository._links.exportInfo && <ExportRepository repository={repository} />}
|
{repository._links.exportInfo && <ExportRepository repository={repository} />}
|
||||||
<ExtensionPoint name="repo-config.route" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.RepoConfigRoute>
|
||||||
|
name="repo-config.route"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
{(repository._links.runHealthCheck || repository.healthCheckRunning) && (
|
{(repository._links.runHealthCheck || repository.healthCheckRunning) && (
|
||||||
<RunHealthCheck repository={repository} />
|
<RunHealthCheck repository={repository} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ const Overview: FC = () => {
|
|||||||
const [t] = useTranslation("repos");
|
const [t] = useTranslation("repos");
|
||||||
const binder = useBinder();
|
const binder = useBinder();
|
||||||
|
|
||||||
const extensions = binder.getExtensions<extensionPoints.RepositoryOverviewLeftExtension>("repository.overview.left");
|
const extensions = binder.getExtensions<extensionPoints.RepositoryOverviewLeft>("repository.overview.left");
|
||||||
|
|
||||||
// we keep the create permission in the state,
|
// we keep the create permission in the state,
|
||||||
// because it does not change during searching or paging
|
// because it does not change during searching or paging
|
||||||
@@ -167,8 +167,16 @@ const Overview: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
documentTitle={t("overview.title")}
|
documentTitle={t("overview.title")}
|
||||||
title={<ExtensionPoint name="repository.overview.title">{t("overview.title")}</ExtensionPoint>}
|
title={
|
||||||
subtitle={<ExtensionPoint name="repository.overview.subtitle">{t("overview.subtitle")}</ExtensionPoint>}
|
<ExtensionPoint<extensionPoints.RepositoryOverviewTitle> name="repository.overview.title">
|
||||||
|
{t("overview.title")}
|
||||||
|
</ExtensionPoint>
|
||||||
|
}
|
||||||
|
subtitle={
|
||||||
|
<ExtensionPoint<extensionPoints.RepositoryOverviewSubtitle> name="repository.overview.subtitle">
|
||||||
|
{t("overview.subtitle")}
|
||||||
|
</ExtensionPoint>
|
||||||
|
}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
error={error}
|
error={error}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import React, { useState } from "react";
|
|||||||
import { match as Match } from "react-router";
|
import { match as Match } from "react-router";
|
||||||
import { Link as RouteLink, Redirect, Route, RouteProps, Switch, useRouteMatch } from "react-router-dom";
|
import { Link as RouteLink, Redirect, Route, RouteProps, Switch, useRouteMatch } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { binder, ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { Changeset, Link } from "@scm-manager/ui-types";
|
import { Changeset, Link } from "@scm-manager/ui-types";
|
||||||
import {
|
import {
|
||||||
CustomQueryFlexWrappedColumns,
|
CustomQueryFlexWrappedColumns,
|
||||||
@@ -122,7 +122,7 @@ const RepositoryRoot = () => {
|
|||||||
match
|
match
|
||||||
};
|
};
|
||||||
|
|
||||||
const redirectUrlFactory = binder.getExtension("repository.redirect", props);
|
const redirectUrlFactory = binder.getExtension<extensionPoints.RepositoryRedirect>("repository.redirect", props);
|
||||||
let redirectedUrl;
|
let redirectedUrl;
|
||||||
if (redirectUrlFactory) {
|
if (redirectUrlFactory) {
|
||||||
redirectedUrl = url + redirectUrlFactory(props);
|
redirectedUrl = url + redirectUrlFactory(props);
|
||||||
@@ -295,12 +295,20 @@ const RepositoryRoot = () => {
|
|||||||
<Route path={`${url}/compare/:sourceType/:sourceName`}>
|
<Route path={`${url}/compare/:sourceType/:sourceName`}>
|
||||||
<CompareRoot repository={repository} baseUrl={`${url}/compare`} />
|
<CompareRoot repository={repository} baseUrl={`${url}/compare`} />
|
||||||
</Route>
|
</Route>
|
||||||
<ExtensionPoint name="repository.route" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.RepositoryRoute>
|
||||||
|
name="repository.route"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</PrimaryContentColumn>
|
</PrimaryContentColumn>
|
||||||
<SecondaryNavigationColumn>
|
<SecondaryNavigationColumn>
|
||||||
<SecondaryNavigation label={t("repositoryRoot.menu.navigationLabel")}>
|
<SecondaryNavigation label={t("repositoryRoot.menu.navigationLabel")}>
|
||||||
<ExtensionPoint name="repository.navigation.topLevel" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.RepositoryNavigationTopLevel>
|
||||||
|
name="repository.navigation.topLevel"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`${url}/info`}
|
to={`${url}/info`}
|
||||||
icon="fas fa-info-circle"
|
icon="fas fa-info-circle"
|
||||||
@@ -337,7 +345,11 @@ const RepositoryRoot = () => {
|
|||||||
activeOnlyWhenExact={false}
|
activeOnlyWhenExact={false}
|
||||||
title={t("repositoryRoot.menu.sourcesNavLink")}
|
title={t("repositoryRoot.menu.sourcesNavLink")}
|
||||||
/>
|
/>
|
||||||
<ExtensionPoint name="repository.navigation" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.RepositoryNavigation>
|
||||||
|
name="repository.navigation"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
<SubNavigation
|
<SubNavigation
|
||||||
to={`${url}/settings/general`}
|
to={`${url}/settings/general`}
|
||||||
label={t("repositoryRoot.menu.settingsNavLink")}
|
label={t("repositoryRoot.menu.settingsNavLink")}
|
||||||
@@ -345,7 +357,11 @@ const RepositoryRoot = () => {
|
|||||||
>
|
>
|
||||||
<EditRepoNavLink repository={repository} editUrl={`${url}/settings/general`} />
|
<EditRepoNavLink repository={repository} editUrl={`${url}/settings/general`} />
|
||||||
<PermissionsNavLink permissionUrl={`${url}/settings/permissions`} repository={repository} />
|
<PermissionsNavLink permissionUrl={`${url}/settings/permissions`} repository={repository} />
|
||||||
<ExtensionPoint name="repository.setting" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.RepositorySetting>
|
||||||
|
name="repository.setting"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</SubNavigation>
|
</SubNavigation>
|
||||||
</SecondaryNavigation>
|
</SecondaryNavigation>
|
||||||
</SecondaryNavigationColumn>
|
</SecondaryNavigationColumn>
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ import {
|
|||||||
SecondaryNavigation,
|
SecondaryNavigation,
|
||||||
SecondaryNavigationColumn,
|
SecondaryNavigationColumn,
|
||||||
StateMenuContextProvider,
|
StateMenuContextProvider,
|
||||||
SubNavigation
|
SubNavigation,
|
||||||
|
urls,
|
||||||
} from "@scm-manager/ui-components";
|
} from "@scm-manager/ui-components";
|
||||||
import Permissions from "../../permissions/containers/Permissions";
|
import Permissions from "../../permissions/containers/Permissions";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import PermissionsNavLink from "./PermissionsNavLink";
|
import PermissionsNavLink from "./PermissionsNavLink";
|
||||||
import { urls } from "@scm-manager/ui-components";
|
|
||||||
import { useNamespace } from "@scm-manager/ui-api";
|
import { useNamespace } from "@scm-manager/ui-api";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -81,15 +81,27 @@ const NamespaceRoot: FC = () => {
|
|||||||
</PrimaryContentColumn>
|
</PrimaryContentColumn>
|
||||||
<SecondaryNavigationColumn>
|
<SecondaryNavigationColumn>
|
||||||
<SecondaryNavigation label={t("namespaceRoot.menu.navigationLabel")}>
|
<SecondaryNavigation label={t("namespaceRoot.menu.navigationLabel")}>
|
||||||
<ExtensionPoint name="namespace.navigation.topLevel" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.NamespaceTopLevelNavigation>
|
||||||
<ExtensionPoint name="namespace.route" props={extensionProps} renderAll={true} />
|
name="namespace.navigation.topLevel"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
|
<ExtensionPoint<extensionPoints.NamespaceRoute>
|
||||||
|
name="namespace.route"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
<SubNavigation
|
<SubNavigation
|
||||||
to={`${url}/settings`}
|
to={`${url}/settings`}
|
||||||
label={t("namespaceRoot.menu.settingsNavLink")}
|
label={t("namespaceRoot.menu.settingsNavLink")}
|
||||||
title={t("namespaceRoot.menu.settingsNavLink")}
|
title={t("namespaceRoot.menu.settingsNavLink")}
|
||||||
>
|
>
|
||||||
<PermissionsNavLink permissionUrl={`${url}/settings/permissions`} namespace={namespace} />
|
<PermissionsNavLink permissionUrl={`${url}/settings/permissions`} namespace={namespace} />
|
||||||
<ExtensionPoint name="namespace.setting" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.NamespaceSetting>
|
||||||
|
name="namespace.setting"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</SubNavigation>
|
</SubNavigation>
|
||||||
</SecondaryNavigation>
|
</SecondaryNavigation>
|
||||||
</SecondaryNavigationColumn>
|
</SecondaryNavigationColumn>
|
||||||
|
|||||||
@@ -87,7 +87,12 @@ const FileTree: FC<Props> = ({ repository, directory, baseUrl, revision, fetchNe
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="panel-block">
|
<div className="panel-block">
|
||||||
<ExtensionPoint name="repos.source.tree.wrapper" props={extProps} renderAll={true} wrapper={true}>
|
<ExtensionPoint<extensionPoints.ReposSourcesTreeWrapper>
|
||||||
|
name="repos.source.tree.wrapper"
|
||||||
|
props={extProps}
|
||||||
|
renderAll={true}
|
||||||
|
wrapper={true}
|
||||||
|
>
|
||||||
<table className="table table-hover table-sm is-fullwidth">
|
<table className="table table-hover table-sm is-fullwidth">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -116,15 +116,24 @@ class FileTreeLeaf extends React.Component<Props> {
|
|||||||
<MinWidthTd className={classNames("is-word-break", "is-hidden-touch")}>
|
<MinWidthTd className={classNames("is-word-break", "is-hidden-touch")}>
|
||||||
{this.contentIfPresent(file, "description", file => file.description)}
|
{this.contentIfPresent(file, "description", file => file.description)}
|
||||||
</MinWidthTd>
|
</MinWidthTd>
|
||||||
{binder.hasExtension("repos.sources.tree.row.right") && (
|
|
||||||
|
{binder.hasExtension<extensionPoints.ReposSourcesTreeRowRight>("repos.sources.tree.row.right", extProps) && (
|
||||||
<ExtensionTd className="is-hidden-mobile">
|
<ExtensionTd className="is-hidden-mobile">
|
||||||
{!file.directory && (
|
{!file.directory && (
|
||||||
<ExtensionPoint name="repos.sources.tree.row.right" props={extProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.ReposSourcesTreeRowRight>
|
||||||
|
name="repos.sources.tree.row.right"
|
||||||
|
props={extProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</ExtensionTd>
|
</ExtensionTd>
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
<ExtensionPoint name="repos.sources.tree.row.after" props={extProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.ReposSourcesTreeRowAfter>
|
||||||
|
name="repos.sources.tree.row.after"
|
||||||
|
props={extProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import React from "react";
|
|||||||
import { WithTranslation, withTranslation } from "react-i18next";
|
import { WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { File, Link, Repository } from "@scm-manager/ui-types";
|
import { File, Link, Repository } from "@scm-manager/ui-types";
|
||||||
import { DownloadButton } from "@scm-manager/ui-components";
|
import { DownloadButton } from "@scm-manager/ui-components";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
type Props = WithTranslation & {
|
type Props = WithTranslation & {
|
||||||
repository: Repository;
|
repository: Repository;
|
||||||
@@ -38,7 +38,10 @@ class DownloadViewer extends React.Component<Props> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="has-text-centered">
|
<div className="has-text-centered">
|
||||||
<ExtensionPoint name="repos.sources.content.downloadButton" props={{ repository, file }}>
|
<ExtensionPoint<extensionPoints.RepositorySourcesContentDownloadButton>
|
||||||
|
name="repos.sources.content.downloadButton"
|
||||||
|
props={{ repository, file }}
|
||||||
|
>
|
||||||
<DownloadButton url={(file._links.self as Link).href} displayName={t("sources.content.downloadButton")} />
|
<DownloadButton url={(file._links.self as Link).href} displayName={t("sources.content.downloadButton")} />
|
||||||
</ExtensionPoint>
|
</ExtensionPoint>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import React, { FC, ReactNode, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { File, Repository } from "@scm-manager/ui-types";
|
import { File, Repository } from "@scm-manager/ui-types";
|
||||||
import { DateFromNow, ErrorNotification, FileSize, Icon, OpenInFullscreenButton } from "@scm-manager/ui-components";
|
import { DateFromNow, ErrorNotification, FileSize, Icon, OpenInFullscreenButton } from "@scm-manager/ui-components";
|
||||||
import FileButtonAddons from "../components/content/FileButtonAddons";
|
import FileButtonAddons from "../components/content/FileButtonAddons";
|
||||||
@@ -136,7 +136,7 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
|
|||||||
modalBody={<BorderLessDiv className="panel">{content}</BorderLessDiv>}
|
modalBody={<BorderLessDiv className="panel">{content}</BorderLessDiv>}
|
||||||
tooltipStyle="htmlTitle"
|
tooltipStyle="htmlTitle"
|
||||||
/>
|
/>
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.ReposSourcesContentActionBar>
|
||||||
name="repos.sources.content.actionbar"
|
name="repos.sources.content.actionbar"
|
||||||
props={{
|
props={{
|
||||||
repository,
|
repository,
|
||||||
@@ -195,7 +195,7 @@ const Content: FC<Props> = ({ file, repository, revision, breadcrumb, error }) =
|
|||||||
<td>{t("sources.content.description")}</td>
|
<td>{t("sources.content.description")}</td>
|
||||||
<td className="is-word-break">{description}</td>
|
<td className="is-word-break">{description}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.ReposContentMetaData>
|
||||||
name="repos.content.metadata"
|
name="repos.content.metadata"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
props={{
|
props={{
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import React, { FC } from "react";
|
|||||||
import SourcecodeViewer from "../components/content/SourcecodeViewer";
|
import SourcecodeViewer from "../components/content/SourcecodeViewer";
|
||||||
import ImageViewer from "../components/content/ImageViewer";
|
import ImageViewer from "../components/content/ImageViewer";
|
||||||
import DownloadViewer from "../components/content/DownloadViewer";
|
import DownloadViewer from "../components/content/DownloadViewer";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import { File, Link, Repository } from "@scm-manager/ui-types";
|
import { File, Link, Repository } from "@scm-manager/ui-types";
|
||||||
import { ErrorNotification, Loading, PdfViewer } from "@scm-manager/ui-components";
|
import { ErrorNotification, Loading, PdfViewer } from "@scm-manager/ui-components";
|
||||||
import SwitchableMarkdownViewer from "../components/content/SwitchableMarkdownViewer";
|
import SwitchableMarkdownViewer from "../components/content/SwitchableMarkdownViewer";
|
||||||
@@ -75,7 +75,7 @@ const SourcesView: FC<Props> = ({ file, repository, revision }) => {
|
|||||||
sources = <PdfViewer src={file} />;
|
sources = <PdfViewer src={file} />;
|
||||||
} else {
|
} else {
|
||||||
sources = (
|
sources = (
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.RepositorySourcesView>
|
||||||
name="repos.sources.view"
|
name="repos.sources.view"
|
||||||
props={{
|
props={{
|
||||||
file,
|
file,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Repository, Tag } from "@scm-manager/ui-types";
|
import { Repository, Tag } from "@scm-manager/ui-types";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import TagDetail from "./TagDetail";
|
import TagDetail from "./TagDetail";
|
||||||
import TagDangerZone from "../container/TagDangerZone";
|
import TagDangerZone from "../container/TagDangerZone";
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ const TagView: FC<Props> = ({ repository, tag }) => {
|
|||||||
<TagDetail tag={tag} repository={repository} />
|
<TagDetail tag={tag} repository={repository} />
|
||||||
<hr />
|
<hr />
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<ExtensionPoint
|
<ExtensionPoint<extensionPoints.RepositoryTagDetailsInformation>
|
||||||
name="repos.tag-details.information"
|
name="repos.tag-details.information"
|
||||||
renderAll={true}
|
renderAll={true}
|
||||||
props={{
|
props={{
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ import RepositoryHit from "./RepositoryHit";
|
|||||||
import GenericHit from "./GenericHit";
|
import GenericHit from "./GenericHit";
|
||||||
import UserHit from "./UserHit";
|
import UserHit from "./UserHit";
|
||||||
import GroupHit from "./GroupHit";
|
import GroupHit from "./GroupHit";
|
||||||
import { Notification, HitProps } from "@scm-manager/ui-components";
|
import { HitProps, Notification } from "@scm-manager/ui-components";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -62,7 +62,7 @@ const InternalHitRenderer: FC<HitComponentProps> = ({ type, hit }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const HitComponent: FC<HitComponentProps> = ({ hit, type }) => (
|
const HitComponent: FC<HitComponentProps> = ({ hit, type }) => (
|
||||||
<ExtensionPoint name={`search.hit.${type}.renderer`} props={{ hit }}>
|
<ExtensionPoint<extensionPoints.SearchHitRenderer> name={`search.hit.${type}.renderer`} props={{ hit }}>
|
||||||
<InternalHitRenderer type={type} hit={hit} />
|
<InternalHitRenderer type={type} hit={hit} />
|
||||||
</ExtensionPoint>
|
</ExtensionPoint>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
*/
|
*/
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import { Route, useParams, useRouteMatch } from "react-router-dom";
|
import { Route, useParams, useRouteMatch } from "react-router-dom";
|
||||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
import { ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions";
|
||||||
import {
|
import {
|
||||||
CustomQueryFlexWrappedColumns,
|
CustomQueryFlexWrappedColumns,
|
||||||
ErrorPage,
|
ErrorPage,
|
||||||
@@ -97,7 +97,7 @@ const SingleUser: FC = () => {
|
|||||||
<Route path={`${url}/settings/apiKeys`}>
|
<Route path={`${url}/settings/apiKeys`}>
|
||||||
<SetApiKeys user={user} />
|
<SetApiKeys user={user} />
|
||||||
</Route>
|
</Route>
|
||||||
<ExtensionPoint name="user.route" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.UserRoute> name="user.route" props={extensionProps} renderAll={true} />
|
||||||
</PrimaryContentColumn>
|
</PrimaryContentColumn>
|
||||||
<SecondaryNavigationColumn>
|
<SecondaryNavigationColumn>
|
||||||
<SecondaryNavigation label={t("singleUser.menu.navigationLabel")}>
|
<SecondaryNavigation label={t("singleUser.menu.navigationLabel")}>
|
||||||
@@ -119,7 +119,11 @@ const SingleUser: FC = () => {
|
|||||||
<SetPermissionsNavLink user={user} permissionsUrl={`${url}/settings/permissions`} />
|
<SetPermissionsNavLink user={user} permissionsUrl={`${url}/settings/permissions`} />
|
||||||
<SetPublicKeysNavLink user={user} publicKeyUrl={`${url}/settings/publickeys`} />
|
<SetPublicKeysNavLink user={user} publicKeyUrl={`${url}/settings/publickeys`} />
|
||||||
<SetApiKeysNavLink user={user} apiKeyUrl={`${url}/settings/apiKeys`} />
|
<SetApiKeysNavLink user={user} apiKeyUrl={`${url}/settings/apiKeys`} />
|
||||||
<ExtensionPoint name="user.setting" props={extensionProps} renderAll={true} />
|
<ExtensionPoint<extensionPoints.UserSetting>
|
||||||
|
name="user.setting"
|
||||||
|
props={extensionProps}
|
||||||
|
renderAll={true}
|
||||||
|
/>
|
||||||
</SubNavigation>
|
</SubNavigation>
|
||||||
</SecondaryNavigation>
|
</SecondaryNavigation>
|
||||||
</SecondaryNavigationColumn>
|
</SecondaryNavigationColumn>
|
||||||
|
|||||||
Reference in New Issue
Block a user