@scm-manager/ui-extensions 2.32.2 → 2.32.3-20220229-134553

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scm-manager/ui-extensions",
3
- "version": "2.32.2",
3
+ "version": "2.32.3-20220229-134553",
4
4
  "main": "src/index.ts",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -10,7 +10,7 @@
10
10
  "test": "jest"
11
11
  },
12
12
  "dependencies": {
13
- "@scm-manager/ui-types": "^2.32.2",
13
+ "@scm-manager/ui-types": "^2.32.3-20220229-134553",
14
14
  "react": "^17.0.1"
15
15
  },
16
16
  "devDependencies": {
@@ -21,39 +21,70 @@
21
21
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  * SOFTWARE.
23
23
  */
24
- import React, { FC, ReactNode } from "react";
25
- import { Binder } from "./binder";
24
+ import React, { PropsWithChildren, ReactNode } from "react";
25
+ import { Binder, ExtensionPointDefinition } from "./binder";
26
26
  import useBinder from "./useBinder";
27
27
 
28
+ export type Renderable<P> = React.ReactElement | React.ComponentType<P>;
29
+ export type RenderableExtensionPointDefinition<
30
+ Name extends string = string,
31
+ P = undefined
32
+ > = ExtensionPointDefinition<Name, Renderable<P>, P>;
33
+
34
+ export type SimpleRenderableDynamicExtensionPointDefinition<
35
+ Prefix extends string,
36
+ Suffix extends string | undefined,
37
+ Properties
38
+ > = RenderableExtensionPointDefinition<Suffix extends string ? `${Prefix}${Suffix}` : `${Prefix}${string}`, Properties>;
39
+
40
+ /**
41
+ * @deprecated Obsolete type
42
+ */
28
43
  type PropTransformer = (props: object) => object;
29
44
 
30
- type Props = {
31
- name: string;
45
+ type BaseProps<E extends RenderableExtensionPointDefinition> = {
46
+ name: E["name"];
32
47
  renderAll?: boolean;
33
- props?: object;
48
+ /**
49
+ * @deprecated Obsolete property, do not use
50
+ */
34
51
  propTransformer?: PropTransformer;
35
52
  wrapper?: boolean;
36
53
  };
37
54
 
38
- const createInstance = (Component: any, props: object, key?: number) => {
39
- const instanceProps = {
40
- ...props,
41
- ...(Component.props || {}),
42
- key,
43
- };
55
+ type Props<E extends RenderableExtensionPointDefinition> = BaseProps<E> &
56
+ (E["props"] extends undefined
57
+ ? { props?: E["props"] }
58
+ : {
59
+ props: E["props"];
60
+ });
61
+
62
+ function createInstance<P>(Component: Renderable<P>, props: P, key?: number) {
44
63
  if (React.isValidElement(Component)) {
45
- return React.cloneElement(Component, instanceProps);
64
+ return React.cloneElement(Component, {
65
+ ...props,
66
+ ...Component.props,
67
+ key,
68
+ });
46
69
  }
47
- return <Component {...instanceProps} />;
48
- };
70
+ return <Component {...props} key={key} />;
71
+ }
49
72
 
50
- const renderAllExtensions = (binder: Binder, name: string, props: object) => {
51
- const extensions = binder.getExtensions(name, props);
73
+ const renderAllExtensions = <E extends RenderableExtensionPointDefinition<string, unknown>>(
74
+ binder: Binder,
75
+ name: E["name"],
76
+ props: E["props"]
77
+ ) => {
78
+ const extensions = binder.getExtensions<E>(name, props);
52
79
  return <>{extensions.map((cmp, index) => createInstance(cmp, props, index))}</>;
53
80
  };
54
81
 
55
- const renderWrapperExtensions = (binder: Binder, name: string, props: object) => {
56
- const extensions = [...(binder.getExtensions(name, props) || [])];
82
+ const renderWrapperExtensions = <E extends RenderableExtensionPointDefinition<string, any>>(
83
+ binder: Binder,
84
+ name: E["name"],
85
+ props: E["props"]
86
+ ) => {
87
+ const extensions = binder.getExtensions<E>(name, props);
57
88
  extensions.reverse();
58
89
 
59
90
  let instance: any = null;
@@ -68,8 +99,12 @@ const renderWrapperExtensions = (binder: Binder, name: string, props: object) =>
68
99
  return instance;
69
100
  };
70
101
 
71
- const renderSingleExtension = (binder: Binder, name: string, props: object) => {
72
- const cmp = binder.getExtension(name, props);
102
+ const renderSingleExtension = <E extends RenderableExtensionPointDefinition<string, unknown>>(
103
+ binder: Binder,
104
+ name: E["name"],
105
+ props: E["props"]
106
+ ) => {
107
+ const cmp = binder.getExtension<E>(name, props);
73
108
  if (!cmp) {
74
109
  return null;
75
110
  }
@@ -97,18 +132,21 @@ const createRenderProps = (propTransformer?: PropTransformer, props?: object) =>
97
132
  /**
98
133
  * ExtensionPoint renders components which are bound to an extension point.
99
134
  */
100
- const ExtensionPoint: FC<Props> = ({ name, propTransformer, props, renderAll, wrapper, children }) => {
135
+ export default function ExtensionPoint<
136
+ E extends RenderableExtensionPointDefinition<string, any> = RenderableExtensionPointDefinition<string, any>
137
+ >({ name, propTransformer, props, renderAll, wrapper, children }: PropsWithChildren<Props<E>>): JSX.Element | null {
101
138
  const binder = useBinder();
102
- const renderProps = createRenderProps(propTransformer, { ...(props || {}), children });
103
- if (!binder.hasExtension(name, renderProps)) {
139
+ const renderProps: E["props"] | {} = createRenderProps(propTransformer, {
140
+ ...(props || {}),
141
+ children,
142
+ });
143
+ if (!binder.hasExtension<E>(name, renderProps)) {
104
144
  return renderDefault(children);
105
145
  } else if (renderAll) {
106
146
  if (wrapper) {
107
- return renderWrapperExtensions(binder, name, renderProps);
147
+ return renderWrapperExtensions<E>(binder, name, renderProps);
108
148
  }
109
- return renderAllExtensions(binder, name, renderProps);
149
+ return renderAllExtensions<E>(binder, name, renderProps);
110
150
  }
111
- return renderSingleExtension(binder, name, renderProps);
112
- };
113
-
114
- export default ExtensionPoint;
151
+ return renderSingleExtension<E>(binder, name, renderProps);
152
+ }
@@ -22,7 +22,9 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
+ import React from "react";
25
26
  import { Binder, ExtensionPointDefinition, SimpleDynamicExtensionPointDefinition } from "./binder";
27
+ import ExtensionPoint, { RenderableExtensionPointDefinition } from "./ExtensionPoint";
26
28
 
27
29
  describe("binder tests", () => {
28
30
  let binder: Binder;
@@ -117,6 +119,59 @@ describe("binder tests", () => {
117
119
  expect(binderExtensionC).not.toBeNull();
118
120
  });
119
121
 
122
+ it("should allow typings for renderable extension points", () => {
123
+ type TestExtensionPointA = RenderableExtensionPointDefinition<"test.extension.a">;
124
+ type TestExtensionPointB = RenderableExtensionPointDefinition<"test.extension.b", { testProp: boolean[] }>;
125
+
126
+ binder.bind<TestExtensionPointA>(
127
+ "test.extension.a",
128
+ () => <h1>Hello world</h1>,
129
+ () => false
130
+ );
131
+ const binderExtensionA = binder.getExtension<TestExtensionPointA>("test.extension.a");
132
+ expect(binderExtensionA).not.toBeNull();
133
+ binder.bind<TestExtensionPointB>("test.extension.b", ({ testProp }) => (
134
+ <h1>
135
+ {testProp.map((b) => (
136
+ <span>{b}</span>
137
+ ))}
138
+ </h1>
139
+ ));
140
+ const binderExtensionsB = binder.getExtensions<TestExtensionPointB>("test.extension.b", {
141
+ testProp: [true, false],
142
+ });
143
+ expect(binderExtensionsB).toHaveLength(1);
144
+ });
145
+
146
+ it("should render typed extension point", () => {
147
+ type TestExtensionPointA = RenderableExtensionPointDefinition<"test.extension.a">;
148
+ type TestExtensionPointB = RenderableExtensionPointDefinition<"test.extension.b", { testProp: boolean[] }>;
149
+
150
+ binder.bind<TestExtensionPointA>(
151
+ "test.extension.a",
152
+ () => <h1>Hello world</h1>,
153
+ () => false
154
+ );
155
+ const binderExtensionA = <ExtensionPoint<TestExtensionPointA> name="test.extension.a" />;
156
+ expect(binderExtensionA).not.toBeNull();
157
+ binder.bind<TestExtensionPointB>("test.extension.b", ({ testProp }) => (
158
+ <h1>
159
+ {testProp.map((b) => (
160
+ <span>{b}</span>
161
+ ))}
162
+ </h1>
163
+ ));
164
+ const binderExtensionsB = (
165
+ <ExtensionPoint<TestExtensionPointB>
166
+ name="test.extension.b"
167
+ props={{
168
+ testProp: [true, false],
169
+ }}
170
+ />
171
+ );
172
+ expect(binderExtensionsB).not.toBeNull();
173
+ });
174
+
120
175
  it("should allow typings for dynamic extension points", () => {
121
176
  type MarkdownCodeLanguageRendererProps = {
122
177
  language?: string;
package/src/binder.ts CHANGED
@@ -21,8 +21,7 @@
21
21
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  * SOFTWARE.
23
23
  */
24
-
25
- type Predicate<P extends Record<any, any> = Record<any, any>> = (props: P) => boolean;
24
+ type Predicate<P extends Record<any, any> = Record<any, any>> = (props: P) => unknown;
26
25
 
27
26
  type ExtensionRegistration<P, T> = {
28
27
  predicate: Predicate<P>;
@@ -78,15 +77,29 @@ export class Binder {
78
77
  *
79
78
  * @param extensionPoint name of extension point
80
79
  * @param extension provided extension
81
- * @param predicate to decide if the extension gets rendered for the given props
82
80
  */
83
81
  bind<E extends ExtensionPointDefinition<string, unknown>>(extensionPoint: E["name"], extension: E["type"]): void;
82
+ /**
83
+ * Binds an extension to the extension point.
84
+ *
85
+ * @param extensionPoint name of extension point
86
+ * @param extension provided extension
87
+ * @param predicate to decide if the extension gets rendered for the given props
88
+ * @param extensionName name used for sorting alphabetically on retrieval (ASC)
89
+ */
84
90
  bind<E extends ExtensionPointDefinition<string, unknown, any>>(
85
91
  extensionPoint: E["name"],
86
92
  extension: E["type"],
87
93
  predicate?: Predicate<E["props"]>,
88
94
  extensionName?: string
89
95
  ): void;
96
+ /**
97
+ * Binds an extension to the extension point.
98
+ *
99
+ * @param extensionPoint name of extension point
100
+ * @param extension provided extension
101
+ * @param options object with additional settings
102
+ */
90
103
  bind<E extends ExtensionPointDefinition<string, unknown, any>>(
91
104
  extensionPoint: E["name"],
92
105
  extension: E["type"],
@@ -125,13 +138,18 @@ export class Binder {
125
138
  this.extensionPoints[extensionPoint].push(registration);
126
139
  }
127
140
 
141
+ /**
142
+ * Returns the first extension or null for the given extension point and its props.
143
+ *
144
+ * @param extensionPoint name of extension point
145
+ */
146
+ getExtension<E extends ExtensionPointDefinition<string, any>>(extensionPoint: E["name"]): E["type"] | null;
128
147
  /**
129
148
  * Returns the first extension or null for the given extension point and its props.
130
149
  *
131
150
  * @param extensionPoint name of extension point
132
151
  * @param props of the extension point
133
152
  */
134
- getExtension<E extends ExtensionPointDefinition<string, any, undefined>>(extensionPoint: E["name"]): E["type"] | null;
135
153
  getExtension<E extends ExtensionPointDefinition<string, any, any>>(
136
154
  extensionPoint: E["name"],
137
155
  props: E["props"]
@@ -147,16 +165,19 @@ export class Binder {
147
165
  return null;
148
166
  }
149
167
 
168
+ /**
169
+ * Returns all registered extensions for the given extension point and its props.
170
+ *
171
+ * @param extensionPoint name of extension point
172
+ */
173
+ getExtensions<E extends ExtensionPointDefinition<string, unknown>>(extensionPoint: E["name"]): Array<E["type"]>;
150
174
  /**
151
175
  * Returns all registered extensions for the given extension point and its props.
152
176
  *
153
177
  * @param extensionPoint name of extension point
154
178
  * @param props of the extension point
155
179
  */
156
- getExtensions<E extends ExtensionPointDefinition<string, any, undefined>>(
157
- extensionPoint: E["name"]
158
- ): Array<E["type"]>;
159
- getExtensions<E extends ExtensionPointDefinition<string, any, any>>(
180
+ getExtensions<E extends ExtensionPointDefinition<string, unknown, any>>(
160
181
  extensionPoint: E["name"],
161
182
  props: E["props"]
162
183
  ): Array<E["type"]>;
@@ -175,11 +196,19 @@ export class Binder {
175
196
  /**
176
197
  * Returns true if at least one extension is bound to the extension point and its props.
177
198
  */
199
+ hasExtension<E extends ExtensionPointDefinition<string, unknown>>(extensionPoint: E["name"]): boolean;
200
+ /**
201
+ * Returns true if at least one extension is bound to the extension point and its props.
202
+ */
203
+ hasExtension<E extends ExtensionPointDefinition<string, unknown, any>>(
204
+ extensionPoint: E["name"],
205
+ props: E["props"]
206
+ ): boolean;
178
207
  hasExtension<E extends ExtensionPointDefinition<any, unknown, any>>(
179
208
  extensionPoint: E["name"],
180
209
  props?: E["props"]
181
210
  ): boolean {
182
- return this.getExtensions<E>(extensionPoint, props).length > 0;
211
+ return this.getExtensions(extensionPoint, props).length > 0;
183
212
  }
184
213
 
185
214
  /**
@@ -22,19 +22,32 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import React from "react";
25
+ import React, { ReactNode } from "react";
26
26
  import {
27
27
  Branch,
28
- BranchDetails,
28
+ Changeset,
29
29
  File,
30
+ Group,
31
+ HalRepresentation,
32
+ Hit,
30
33
  IndexResources,
31
34
  Links,
35
+ Me,
36
+ Namespace,
32
37
  NamespaceStrategies,
38
+ Person,
39
+ Plugin,
33
40
  Repository,
34
41
  RepositoryCreation,
35
- RepositoryTypeCollection
42
+ RepositoryRole,
43
+ RepositoryRoleBase,
44
+ RepositoryTypeCollection,
45
+ Tag,
46
+ User,
36
47
  } from "@scm-manager/ui-types";
37
48
  import { ExtensionPointDefinition } from "./binder";
49
+ import { RenderableExtensionPointDefinition, SimpleRenderableDynamicExtensionPointDefinition } from "./ExtensionPoint";
50
+ import ExtractProps from "./extractProps";
38
51
 
39
52
  type RepositoryCreatorSubFormProps = {
40
53
  repository: RepositoryCreation;
@@ -43,128 +56,466 @@ type RepositoryCreatorSubFormProps = {
43
56
  disabled?: boolean;
44
57
  };
45
58
 
46
- export type RepositoryCreatorComponentProps = {
47
- namespaceStrategies: NamespaceStrategies;
48
- repositoryTypes: RepositoryTypeCollection;
49
- index: IndexResources;
59
+ export type RepositoryCreatorComponentProps = ExtractProps<RepositoryCreator["type"]["component"]>;
50
60
 
51
- nameForm: React.ComponentType<RepositoryCreatorSubFormProps>;
52
- informationForm: React.ComponentType<RepositoryCreatorSubFormProps>;
53
- };
54
-
55
- export type RepositoryCreatorExtension = {
56
- subtitle: string;
57
- path: string;
58
- icon: string;
59
- label: string;
60
- component: React.ComponentType<RepositoryCreatorComponentProps>;
61
- };
61
+ /**
62
+ * @deprecated use {@link RepositoryCreator}`["type"]` instead
63
+ */
64
+ export type RepositoryCreatorExtension = RepositoryCreator["type"];
65
+ export type RepositoryCreator = ExtensionPointDefinition<"repos.creator",
66
+ {
67
+ subtitle: string;
68
+ path: string;
69
+ icon: string;
70
+ label: string;
71
+ component: React.ComponentType<{
72
+ namespaceStrategies: NamespaceStrategies;
73
+ repositoryTypes: RepositoryTypeCollection;
74
+ index: IndexResources;
62
75
 
63
- export type RepositoryCreator = ExtensionPointDefinition<"repos.creator", RepositoryCreatorExtension>;
76
+ nameForm: React.ComponentType<RepositoryCreatorSubFormProps>;
77
+ informationForm: React.ComponentType<RepositoryCreatorSubFormProps>;
78
+ }>;
79
+ }>;
64
80
 
65
- export type RepositoryFlags = ExtensionPointDefinition<"repository.flags", { repository: Repository }>;
81
+ export type RepositoryFlags = RenderableExtensionPointDefinition<"repository.flags",
82
+ { repository: Repository; tooltipLocation?: "bottom" | "right" | "top" | "left" }>;
66
83
 
67
- export type ReposSourcesActionbarExtensionProps = {
68
- baseUrl: string;
69
- revision: string;
70
- branch: Branch | undefined;
71
- path: string;
72
- sources: File;
73
- repository: Repository;
74
- };
75
- export type ReposSourcesActionbarExtension = React.ComponentType<ReposSourcesActionbarExtensionProps>;
76
- export type ReposSourcesActionbar = ExtensionPointDefinition<"repos.sources.actionbar", ReposSourcesActionbarExtension>;
84
+ /**
85
+ * @deprecated use {@link ReposSourcesActionbar}`["props"]` instead
86
+ */
87
+ export type ReposSourcesActionbarExtensionProps = ReposSourcesActionbar["props"];
88
+ /**
89
+ * @deprecated use {@link ReposSourcesActionbar} instead
90
+ */
91
+ export type ReposSourcesActionbarExtension = ReposSourcesActionbar;
92
+ export type ReposSourcesActionbar = RenderableExtensionPointDefinition<"repos.sources.actionbar",
93
+ {
94
+ baseUrl: string;
95
+ revision: string;
96
+ branch: Branch | undefined;
97
+ path: string;
98
+ sources: File;
99
+ repository: Repository;
100
+ }>;
77
101
 
78
- export type ReposSourcesEmptyActionbarExtensionProps = {
79
- sources: File;
80
- repository: Repository;
81
- };
82
- export type ReposSourcesEmptyActionbarExtension = ReposSourcesActionbarExtension;
83
- export type ReposSourcesEmptyActionbar = ExtensionPointDefinition<
84
- "repos.sources.empty.actionbar",
85
- ReposSourcesEmptyActionbarExtension
86
- >;
102
+ /**
103
+ * @deprecated use {@link ReposSourcesEmptyActionbar}`["props"]` instead
104
+ */
105
+ export type ReposSourcesEmptyActionbarExtensionProps = ReposSourcesEmptyActionbar["props"];
106
+ /**
107
+ * @deprecated use {@link ReposSourcesEmptyActionbar} instead
108
+ */
109
+ export type ReposSourcesEmptyActionbarExtension = ReposSourcesEmptyActionbar;
110
+ export type ReposSourcesEmptyActionbar = RenderableExtensionPointDefinition<"repos.sources.empty.actionbar",
111
+ {
112
+ sources: File;
113
+ repository: Repository;
114
+ }>;
87
115
 
88
- export type ReposSourcesTreeWrapperProps = {
89
- repository: Repository;
90
- directory: File;
91
- baseUrl: string;
92
- revision: string;
93
- };
116
+ /**
117
+ * @deprecated use {@link ReposSourcesTreeWrapper}`["props"]` instead
118
+ */
119
+ export type ReposSourcesTreeWrapperProps = ReposSourcesTreeWrapper["props"];
94
120
 
95
- export type ReposSourcesTreeWrapperExtension = ExtensionPointDefinition<
96
- "repos.source.tree.wrapper",
97
- React.ComponentType<ReposSourcesTreeWrapperProps>
98
- >;
121
+ /**
122
+ * @deprecated use {@link ReposSourcesTreeWrapper} instead
123
+ */
124
+ export type ReposSourcesTreeWrapperExtension = ReposSourcesTreeWrapper;
125
+ export type ReposSourcesTreeWrapper = RenderableExtensionPointDefinition<"repos.source.tree.wrapper",
126
+ {
127
+ repository: Repository;
128
+ directory: File;
129
+ baseUrl: string;
130
+ revision: string;
131
+ }>;
99
132
 
100
133
  export type ReposSourcesTreeRowProps = {
101
134
  repository: Repository;
102
135
  file: File;
103
136
  };
104
137
 
105
- export type ReposSourcesTreeRowRightExtension = ExtensionPointDefinition<
106
- "repos.sources.tree.row.right",
107
- React.ComponentType<ReposSourcesTreeRowProps>
108
- >;
109
- export type ReposSourcesTreeRowAfterExtension = ExtensionPointDefinition<
110
- "repos.sources.tree.row.after",
111
- React.ComponentType<ReposSourcesTreeRowProps>
112
- >;
113
-
114
- export type PrimaryNavigationLoginButtonProps = {
115
- links: Links;
116
- label: string;
117
- loginUrl: string;
118
- from: string;
119
- to: string;
120
- className: string;
121
- content: React.ReactNode;
122
- };
138
+ /**
139
+ * @deprecated use {@link ReposSourcesTreeRowRight} instead
140
+ */
141
+ export type ReposSourcesTreeRowRightExtension = ReposSourcesTreeRowRight;
142
+ export type ReposSourcesTreeRowRight = RenderableExtensionPointDefinition<"repos.sources.tree.row.right",
143
+ ReposSourcesTreeRowProps>;
123
144
 
124
- export type PrimaryNavigationLoginButtonExtension = ExtensionPointDefinition<
125
- "primary-navigation.login",
126
- PrimaryNavigationLoginButtonProps
127
- >;
145
+ /**
146
+ * @deprecated use {@link ReposSourcesTreeRowAfter} instead
147
+ */
148
+ export type ReposSourcesTreeRowAfterExtension = ReposSourcesTreeRowAfter;
149
+ export type ReposSourcesTreeRowAfter = RenderableExtensionPointDefinition<"repos.sources.tree.row.after",
150
+ ReposSourcesTreeRowProps>;
151
+
152
+ /**
153
+ * @deprecated use {@link PrimaryNavigationLoginButton}`["props"]` instead
154
+ */
155
+ export type PrimaryNavigationLoginButtonProps = PrimaryNavigationLoginButton["props"];
156
+
157
+ /**
158
+ * use {@link PrimaryNavigationLoginButton} instead
159
+ */
160
+ export type PrimaryNavigationLoginButtonExtension = PrimaryNavigationLoginButton;
161
+ export type PrimaryNavigationLoginButton = RenderableExtensionPointDefinition<"primary-navigation.login",
162
+ {
163
+ links: Links;
164
+ label: string;
165
+ loginUrl: string;
166
+ from: string;
167
+ to: string;
168
+ className: string;
169
+ content: React.ReactNode;
170
+ }>;
171
+
172
+ /**
173
+ * @deprecated use {@link PrimaryNavigationLogoutButtonExtension}`["props"]` instead
174
+ */
175
+ export type PrimaryNavigationLogoutButtonProps = PrimaryNavigationLogoutButton["props"];
176
+
177
+ /**
178
+ * @deprecated use {@link PrimaryNavigationLogoutButton} instead
179
+ */
180
+ export type PrimaryNavigationLogoutButtonExtension = PrimaryNavigationLogoutButton;
181
+ export type PrimaryNavigationLogoutButton = RenderableExtensionPointDefinition<"primary-navigation.logout",
182
+ {
183
+ links: Links;
184
+ label: string;
185
+ className: string;
186
+ content: React.ReactNode;
187
+ }>;
128
188
 
129
- export type PrimaryNavigationLogoutButtonProps = {
130
- links: Links;
131
- label: string;
132
- className: string;
133
- content: React.ReactNode;
189
+ /**
190
+ * @deprecated use {@link SourceExtension}`["props"]` instead
191
+ */
192
+ export type SourceExtensionProps = SourceExtension["props"];
193
+ export type SourceExtension = RenderableExtensionPointDefinition<"repos.sources.extensions",
194
+ {
195
+ repository: Repository;
196
+ baseUrl: string;
197
+ revision: string;
198
+ extension: string;
199
+ sources: File | undefined;
200
+ path: string;
201
+ }>;
202
+
203
+ /**
204
+ * @deprecated use {@link RepositoryOverviewTop}`["props"]` instead
205
+ */
206
+ export type RepositoryOverviewTopExtensionProps = RepositoryOverviewTop["props"];
207
+
208
+ /**
209
+ * @deprecated use {@link RepositoryOverviewTop} instead
210
+ */
211
+ export type RepositoryOverviewTopExtension = RepositoryOverviewTop;
212
+ export type RepositoryOverviewTop = RenderableExtensionPointDefinition<"repository.overview.top",
213
+ {
214
+ page: number;
215
+ search: string;
216
+ namespace?: string;
217
+ }>;
218
+
219
+ /**
220
+ * @deprecated use {@link RepositoryOverviewLeft} instead
221
+ */
222
+ export type RepositoryOverviewLeftExtension = RepositoryOverviewLeft;
223
+ export type RepositoryOverviewLeft = ExtensionPointDefinition<"repository.overview.left", React.ComponentType>;
224
+
225
+ /**
226
+ * @deprecated use {@link RepositoryOverviewTitle} instead
227
+ */
228
+ export type RepositoryOverviewTitleExtension = RepositoryOverviewTitle;
229
+ export type RepositoryOverviewTitle = RenderableExtensionPointDefinition<"repository.overview.title">;
230
+
231
+ /**
232
+ * @deprecated use {@link RepositoryOverviewSubtitle} instead
233
+ */
234
+ export type RepositoryOverviewSubtitleExtension = RepositoryOverviewSubtitle;
235
+ export type RepositoryOverviewSubtitle = RenderableExtensionPointDefinition<"repository.overview.subtitle">;
236
+
237
+ // From docs
238
+
239
+ export type AdminNavigation = RenderableExtensionPointDefinition<"admin.navigation", { links: Links; url: string }>;
240
+
241
+ export type AdminRoute = RenderableExtensionPointDefinition<"admin.route", { links: Links; url: string }>;
242
+
243
+ export type AdminSetting = RenderableExtensionPointDefinition<"admin.setting", { links: Links; url: string }>;
244
+
245
+ /**
246
+ * - can be used to replace the whole description of a changeset
247
+ *
248
+ * @deprecated Use `changeset.description.tokens` instead
249
+ */
250
+ export type ChangesetDescription = RenderableExtensionPointDefinition<"changeset.description",
251
+ { changeset: Changeset; value: string }>;
252
+
253
+ /**
254
+ * - Can be used to replace parts of a changeset description with components
255
+ * - Has to be bound with a funktion taking the changeset and the (partial) description and returning `Replacement` objects with the following attributes:
256
+ * - textToReplace: The text part of the description that should be replaced by a component
257
+ * - replacement: The component to take instead of the text to replace
258
+ * - replaceAll: Optional boolean; if set to `true`, all occurances of the text will be replaced (default: `false`)
259
+ */
260
+ export type ChangesetDescriptionTokens = ExtensionPointDefinition<"changeset.description.tokens",
261
+ (changeset: Changeset, value: string) => Array<{
262
+ textToReplace: string;
263
+ replacement: ReactNode;
264
+ replaceAll?: boolean;
265
+ }>,
266
+ { changeset: Changeset; value: string }>;
267
+
268
+ export type ChangesetRight = RenderableExtensionPointDefinition<"changeset.right",
269
+ { repository: Repository; changeset: Changeset }>;
270
+
271
+ export type ChangesetsAuthorSuffix = RenderableExtensionPointDefinition<"changesets.author.suffix",
272
+ { changeset: Changeset }>;
273
+
274
+ export type GroupNavigation = RenderableExtensionPointDefinition<"group.navigation", { group: Group; url: string }>;
275
+
276
+ export type GroupRoute = RenderableExtensionPointDefinition<"group.route", { group: Group; url: string }>;
277
+ export type GroupSetting = RenderableExtensionPointDefinition<"group.setting", { group: Group; url: string }>;
278
+
279
+ /**
280
+ * - Add a new Route to the main Route (scm/)
281
+ * - Props: authenticated?: boolean, links: Links
282
+ */
283
+ export type MainRoute = RenderableExtensionPointDefinition<"main.route",
284
+ {
285
+ me: Me;
286
+ authenticated?: boolean;
287
+ }>;
288
+
289
+ export type PluginAvatar = RenderableExtensionPointDefinition<"plugins.plugin-avatar",
290
+ {
291
+ plugin: Plugin;
292
+ }>;
293
+
294
+ export type PrimaryNavigation = RenderableExtensionPointDefinition<"primary-navigation", { links: Links }>;
295
+
296
+ /**
297
+ * - A placeholder for the first navigation menu.
298
+ * - A PrimaryNavigationLink Component can be used here
299
+ * - Actually this Extension Point is used from the Activity Plugin to display the activities at the first Main Navigation menu.
300
+ */
301
+ export type PrimaryNavigationFirstMenu = RenderableExtensionPointDefinition<"primary-navigation.first-menu",
302
+ { links: Links; label: string }>;
303
+
304
+ export type ProfileRoute = RenderableExtensionPointDefinition<"profile.route", { me: Me; url: string }>;
305
+ export type ProfileSetting = RenderableExtensionPointDefinition<"profile.setting",
306
+ { me?: Me; url: string; links: Links }>;
307
+
308
+ export type RepoConfigRoute = RenderableExtensionPointDefinition<"repo-config.route",
309
+ { repository: Repository; url: string }>;
310
+
311
+ export type RepoConfigDetails = RenderableExtensionPointDefinition<"repo-config.details",
312
+ { repository: Repository; url: string }>;
313
+
314
+ export type ReposBranchDetailsInformation = RenderableExtensionPointDefinition<"repos.branch-details.information",
315
+ { repository: Repository; branch: Branch }>;
316
+
317
+ /**
318
+ * - Location: At meta data view for file
319
+ * - can be used to render additional meta data line
320
+ * - Props: file: string, repository: Repository, revision: string
321
+ */
322
+ export type ReposContentMetaData = RenderableExtensionPointDefinition<"repos.content.metadata",
323
+ { file: File; repository: Repository; revision: string }>;
324
+
325
+ export type ReposCreateNamespace = RenderableExtensionPointDefinition<"repos.create.namespace",
326
+ {
327
+ label: string;
328
+ helpText: string;
329
+ value: string;
330
+ onChange: (namespace: string) => void;
331
+ errorMessage: string;
332
+ validationError?: boolean;
333
+ }>;
334
+
335
+ export type ReposSourcesContentActionBar = RenderableExtensionPointDefinition<"repos.sources.content.actionbar",
336
+ {
337
+ repository: Repository;
338
+ file: File;
339
+ revision: string;
340
+ handleExtensionError: React.Dispatch<React.SetStateAction<Error | undefined>>;
341
+ }>;
342
+
343
+ export type RepositoryNavigation = RenderableExtensionPointDefinition<"repository.navigation",
344
+ { repository: Repository; url: string; indexLinks: Links }>;
345
+
346
+ export type RepositoryNavigationTopLevel = RenderableExtensionPointDefinition<"repository.navigation.topLevel",
347
+ { repository: Repository; url: string; indexLinks: Links }>;
348
+
349
+ export type RepositoryRoleDetailsInformation = RenderableExtensionPointDefinition<"repositoryRole.role-details.information",
350
+ { role: RepositoryRole }>;
351
+
352
+ export type RepositorySetting = RenderableExtensionPointDefinition<"repository.setting",
353
+ { repository: Repository; url: string; indexLinks: Links }>;
354
+
355
+ export type RepositoryAvatar = RenderableExtensionPointDefinition<"repos.repository-avatar",
356
+ { repository: Repository }>;
357
+
358
+ /**
359
+ * - Location: At each repository in repository overview
360
+ * - can be used to add avatar for each repository (e.g., to mark repository type)
361
+ */
362
+ export type PrimaryRepositoryAvatar = RenderableExtensionPointDefinition<"repos.repository-avatar.primary",
363
+ { repository: Repository }>;
364
+
365
+ /**
366
+ * - Location: At bottom of a single repository view
367
+ * - can be used to show detailed information about the repository (how to clone, e.g.)
368
+ */
369
+ export type RepositoryDetailsInformation = RenderableExtensionPointDefinition<"repos.repository-details.information",
370
+ { repository: Repository }>;
371
+
372
+ /**
373
+ * - Location: At sources viewer
374
+ * - can be used to render a special source that is not an image or a source code
375
+ */
376
+ export type RepositorySourcesView = RenderableExtensionPointDefinition<"repos.sources.view",
377
+ { file: File; contentType: string; revision: string; basePath: string }>;
378
+
379
+ export type RolesRoute = RenderableExtensionPointDefinition<"roles.route",
380
+ { role: HalRepresentation & RepositoryRoleBase & { creationDate?: string; lastModified?: string }; url: string }>;
381
+
382
+ export type UserRoute = RenderableExtensionPointDefinition<"user.route", { user: User; url: string }>;
383
+ export type UserSetting = RenderableExtensionPointDefinition<"user.setting", { user: User; url: string }>;
384
+
385
+ /**
386
+ * - Dynamic extension point for custom language-specific renderers
387
+ * - Overrides the default Syntax Highlighter for the given language
388
+ * - Used by the Markdown Plantuml Plugin
389
+ */
390
+ export type MarkdownCodeRenderer<Language extends string | undefined = undefined> =
391
+ SimpleRenderableDynamicExtensionPointDefinition<"markdown-renderer.code.",
392
+ Language,
393
+ {
394
+ language?: Language extends string ? Language : string;
395
+ value: string;
396
+ indexLinks: Links;
397
+ }>;
398
+
399
+ /**
400
+ * - Define custom protocols and their renderers for links in markdown
401
+ *
402
+ * Example:
403
+ * ```markdown
404
+ * [description](myprotocol:somelink)
405
+ * ```
406
+ *
407
+ * ```typescript
408
+ * binder.bind<extensionPoints.MarkdownLinkProtocolRenderer<"myprotocol">>("markdown-renderer.link.protocol", { protocol: "myprotocol", renderer: MyProtocolRenderer })
409
+ * ```
410
+ */
411
+ export type MarkdownLinkProtocolRenderer<Protocol extends string | undefined = undefined> = ExtensionPointDefinition<"markdown-renderer.link.protocol",
412
+ {
413
+ protocol: Protocol extends string ? Protocol : string;
414
+ renderer: React.ComponentType<{
415
+ protocol: Protocol extends string ? Protocol : string;
416
+ href: string;
417
+ }>;
418
+ }>;
419
+
420
+ /**
421
+ * Used to determine an avatar image url from a given {@link Person}.
422
+ *
423
+ * @see https://github.com/scm-manager/scm-gravatar-plugin
424
+ */
425
+ export type AvatarFactory = ExtensionPointDefinition<"avatar.factory", (person: Person) => string | undefined>;
426
+
427
+ /**
428
+ * - Location: At every changeset (detailed view as well as changeset overview)
429
+ * - can be used to add avatar (such as gravatar) for each changeset
430
+ *
431
+ * @deprecated Has no effect, use {@link AvatarFactory} instead
432
+ */
433
+ export type ChangesetAvatarFactory = ExtensionPointDefinition<"changeset.avatar-factory",
434
+ (changeset: Changeset) => void>;
435
+
436
+ type MainRedirectProps = {
437
+ me: Me;
438
+ authenticated?: boolean;
134
439
  };
135
440
 
136
- export type PrimaryNavigationLogoutButtonExtension = ExtensionPointDefinition<
137
- "primary-navigation.logout",
138
- PrimaryNavigationLogoutButtonProps
139
- >;
441
+ /**
442
+ * - Extension Point for a link factory that provide the Redirect Link
443
+ * - Actually used from the activity plugin: binder.bind("main.redirect", () => "/activity");
444
+ */
445
+ export type MainRedirect = ExtensionPointDefinition<"main.redirect",
446
+ (props: MainRedirectProps) => string,
447
+ MainRedirectProps>;
448
+
449
+ /**
450
+ * - A Factory function to create markdown [renderer](https://github.com/rexxars/react-markdown#node-types)
451
+ * - 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.
452
+ *
453
+ * @deprecated Use {@link MarkdownCodeRenderer} or {@link MarkdownLinkProtocolRenderer} instead
454
+ */
455
+ export type MarkdownRendererFactory = ExtensionPointDefinition<"markdown-renderer-factory",
456
+ (renderContext: unknown) => Record<string, React.ComponentType<any>>>;
457
+
458
+ export type RepositoryCardBeforeTitle = RenderableExtensionPointDefinition<"repository.card.beforeTitle",
459
+ { repository: Repository }>;
460
+
461
+ export type RepositoryCreationInitialization = RenderableExtensionPointDefinition<"repos.create.initialize",
462
+ {
463
+ repository: Repository;
464
+ setCreationContextEntry: (key: string, value: any) => void;
465
+ indexResources: Partial<HalRepresentation> & {
466
+ links?: Links;
467
+ version?: string;
468
+ initialization?: string;
469
+ };
470
+ }>;
471
+
472
+ export type NamespaceTopLevelNavigation = RenderableExtensionPointDefinition<"namespace.navigation.topLevel",
473
+ { namespace: Namespace; url: string }>;
474
+
475
+ export type NamespaceRoute = RenderableExtensionPointDefinition<"namespace.route",
476
+ { namespace: Namespace; url: string }>;
140
477
 
141
- export type SourceExtensionProps = {
478
+ export type NamespaceSetting = RenderableExtensionPointDefinition<"namespace.setting",
479
+ { namespace: Namespace; url: string }>;
480
+
481
+ export type RepositoryTagDetailsInformation = RenderableExtensionPointDefinition<"repos.tag-details.information",
482
+ { repository: Repository; tag: Tag }>;
483
+
484
+ export type SearchHitRenderer<Type extends string | undefined = undefined> = RenderableExtensionPointDefinition<Type extends string ? `search.hit.${Type}.renderer` : `search.hit.${string}.renderer`,
485
+ { hit: Hit }>;
486
+
487
+ export type RepositorySourcesContentDownloadButton = RenderableExtensionPointDefinition<"repos.sources.content.downloadButton",
488
+ { repository: Repository; file: File }>;
489
+
490
+ export type RepositoryRoute = RenderableExtensionPointDefinition<"repository.route",
491
+ { repository: Repository; url: string; indexLinks: Links }>;
492
+
493
+ type RepositoryRedirectProps = {
494
+ namespace: string;
495
+ name: string;
142
496
  repository: Repository;
143
- baseUrl: string;
144
- revision: string;
145
- extension: string;
146
- sources: File | undefined;
147
- path: string;
497
+ loading: false;
498
+ error: null;
499
+ repoLink: string;
500
+ indexLinks: Links;
501
+ match: {
502
+ params: {
503
+ namespace: string;
504
+ name: string;
505
+ };
506
+ isExact: boolean;
507
+ path: string;
508
+ url: string;
509
+ };
148
510
  };
149
- export type SourceExtension = ExtensionPointDefinition<"repos.sources.extensions", SourceExtensionProps>;
150
511
 
151
- export type RepositoryOverviewTopExtensionProps = {
152
- page: number;
153
- search: string;
154
- namespace?: string;
155
- };
512
+ export type RepositoryRedirect = ExtensionPointDefinition<"repository.redirect",
513
+ (props: RepositoryRedirectProps) => string,
514
+ RepositoryRedirectProps>;
156
515
 
157
- export type RepositoryOverviewTopExtension = ExtensionPointDefinition<
158
- "repository.overview.top",
159
- React.ComponentType<RepositoryOverviewTopExtensionProps>,
160
- RepositoryOverviewTopExtensionProps
161
- >;
162
- export type RepositoryOverviewLeftExtension = ExtensionPointDefinition<"repository.overview.left", React.ComponentType>;
163
- export type RepositoryOverviewTitleExtension = ExtensionPointDefinition<
164
- "repository.overview.title",
165
- React.ComponentType
166
- >;
167
- export type RepositoryOverviewSubtitleExtension = ExtensionPointDefinition<
168
- "repository.overview.subtitle",
169
- React.ComponentType
170
- >;
516
+ export type InitializationStep<Step extends string | undefined = undefined> =
517
+ SimpleRenderableDynamicExtensionPointDefinition<"initialization.step.",
518
+ Step,
519
+ {
520
+ data: HalRepresentation;
521
+ }>;
@@ -0,0 +1,28 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import React from "react";
25
+
26
+ type ExtractProps<T> = T extends React.ComponentType<infer U> ? U : never;
27
+
28
+ export default ExtractProps;
package/src/index.ts CHANGED
@@ -25,6 +25,7 @@
25
25
  export { default as binder, Binder, ExtensionPointDefinition } from "./binder";
26
26
  export * from "./useBinder";
27
27
  export { default as ExtensionPoint } from "./ExtensionPoint";
28
+ export { default as ExtractProps } from "./extractProps";
28
29
 
29
30
  // suppress eslint prettier warning,
30
31
  // because prettier does not understand "* as"