mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 01:15:44 +01:00
New extension points for repository overview (#1828)
The landing-page-plugin is being reworked and integrated into the repository overview. This requires new extension points and slightly adjusted components to better match the repository overview page visually. Also, binder options can now be passed as an object which offer a new priority option that causes sorting in descending order.
This commit is contained in:
committed by
GitHub
parent
35f4cb3e61
commit
57aacba03a
@@ -74,7 +74,7 @@ describe("binder tests", () => {
|
||||
binder.bind("hitchhiker.trillian", "earth2", (props: Props) => props.category === "a");
|
||||
|
||||
const extensions = binder.getExtensions("hitchhiker.trillian", {
|
||||
category: "b"
|
||||
category: "b",
|
||||
});
|
||||
expect(extensions).toEqual(["earth"]);
|
||||
});
|
||||
@@ -109,7 +109,7 @@ describe("binder tests", () => {
|
||||
expect(binderExtensionA).not.toBeNull();
|
||||
binder.bind<TestExtensionPointB>("test.extension.b", 2);
|
||||
const binderExtensionsB = binder.getExtensions<TestExtensionPointB>("test.extension.b", {
|
||||
testProp: [true, false]
|
||||
testProp: [true, false],
|
||||
});
|
||||
expect(binderExtensionsB).toHaveLength(1);
|
||||
binder.bind("test.extension.c", 2, () => false);
|
||||
@@ -123,24 +123,78 @@ describe("binder tests", () => {
|
||||
value: string;
|
||||
};
|
||||
|
||||
type MarkdownCodeLanguageRendererExtensionPoint<
|
||||
S extends string | undefined = undefined
|
||||
> = SimpleDynamicExtensionPointDefinition<
|
||||
"markdown-renderer.code.",
|
||||
(props: any) => any,
|
||||
MarkdownCodeLanguageRendererProps,
|
||||
S
|
||||
>;
|
||||
type MarkdownCodeLanguageRendererExtensionPoint<S extends string | undefined = undefined> =
|
||||
SimpleDynamicExtensionPointDefinition<
|
||||
"markdown-renderer.code.",
|
||||
(props: any) => any,
|
||||
MarkdownCodeLanguageRendererProps,
|
||||
S
|
||||
>;
|
||||
type UmlExtensionPoint = MarkdownCodeLanguageRendererExtensionPoint<"uml">;
|
||||
|
||||
binder.bind<UmlExtensionPoint>("markdown-renderer.code.uml", props => props.value);
|
||||
binder.bind<UmlExtensionPoint>("markdown-renderer.code.uml", (props) => props.value);
|
||||
|
||||
const language = "uml";
|
||||
const extensionPointName = `markdown-renderer.code.${language}` as const;
|
||||
const dynamicExtension = binder.getExtension<MarkdownCodeLanguageRendererExtensionPoint>(extensionPointName, {
|
||||
language: "uml",
|
||||
value: "const a = 2;"
|
||||
value: "const a = 2;",
|
||||
});
|
||||
expect(dynamicExtension).not.toBeNull();
|
||||
});
|
||||
|
||||
it("should allow options parameter", () => {
|
||||
binder.bind("hitchhiker.trillian", "planetA", {
|
||||
predicate: () => true,
|
||||
extensionName: "zeroWaste",
|
||||
});
|
||||
|
||||
const extensions = binder.getExtensions("hitchhiker.trillian");
|
||||
expect(extensions).toEqual(["planetA"]);
|
||||
});
|
||||
|
||||
it("should allow empty options parameter", () => {
|
||||
binder.bind("hitchhiker.trillian", "planetA", {});
|
||||
|
||||
const extensions = binder.getExtensions("hitchhiker.trillian");
|
||||
expect(extensions).toEqual(["planetA"]);
|
||||
});
|
||||
|
||||
it("should allow options parameter with only predicate", () => {
|
||||
binder.bind("hitchhiker.trillian", "planetA", {
|
||||
predicate: () => true,
|
||||
});
|
||||
|
||||
const extensions = binder.getExtensions("hitchhiker.trillian");
|
||||
expect(extensions).toEqual(["planetA"]);
|
||||
});
|
||||
|
||||
it("should allow options parameter with only extensionName", () => {
|
||||
binder.bind("hitchhiker.trillian", "planetA", {
|
||||
extensionName: "zeroWaste",
|
||||
});
|
||||
|
||||
const extensions = binder.getExtensions("hitchhiker.trillian");
|
||||
expect(extensions).toEqual(["planetA"]);
|
||||
});
|
||||
|
||||
it("should order by priority in descending order", () => {
|
||||
binder.bind("hitchhiker.trillian", "planetA", { priority: 10 });
|
||||
binder.bind("hitchhiker.trillian", "planetB", { priority: 50 });
|
||||
binder.bind("hitchhiker.trillian", "planetC", { priority: 100 });
|
||||
binder.bind("hitchhiker.trillian", "planetD", { priority: 75 });
|
||||
|
||||
const extensions = binder.getExtensions("hitchhiker.trillian");
|
||||
expect(extensions).toEqual(["planetC", "planetD", "planetB", "planetA"]);
|
||||
});
|
||||
|
||||
it("should order by priority over ordering by name", () => {
|
||||
binder.bind("hitchhiker.trillian", "planetA", { priority: 10, extensionName: "ignore" });
|
||||
binder.bind("hitchhiker.trillian", "planetB", { priority: 50 });
|
||||
binder.bind("hitchhiker.trillian", "planetC", { priority: 100, extensionName: "me" });
|
||||
binder.bind("hitchhiker.trillian", "planetD", { priority: 75 });
|
||||
|
||||
const extensions = binder.getExtensions("hitchhiker.trillian");
|
||||
expect(extensions).toEqual(["planetC", "planetD", "planetB", "planetA"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@ type ExtensionRegistration<P, T> = {
|
||||
predicate: Predicate<P>;
|
||||
extension: T;
|
||||
extensionName: string;
|
||||
priority: number;
|
||||
};
|
||||
|
||||
export type ExtensionPointDefinition<N extends string, T, P = undefined> = {
|
||||
@@ -36,7 +37,26 @@ export type ExtensionPointDefinition<N extends string, T, P = undefined> = {
|
||||
props: P;
|
||||
};
|
||||
|
||||
export type SimpleDynamicExtensionPointDefinition<P extends string, T, Props, S extends string | undefined> = ExtensionPointDefinition<S extends string ? `${P}${S}` : `${P}${string}`, T, Props>;
|
||||
export type SimpleDynamicExtensionPointDefinition<P extends string, T, Props, S extends string | undefined> =
|
||||
ExtensionPointDefinition<S extends string ? `${P}${S}` : `${P}${string}`, T, Props>;
|
||||
|
||||
export type BindOptions<Props> = {
|
||||
predicate?: Predicate<Props>;
|
||||
|
||||
/**
|
||||
* Extensions are ordered by name (ASC).
|
||||
*/
|
||||
extensionName?: string;
|
||||
|
||||
/**
|
||||
* Extensions are ordered by priority (DESC).
|
||||
*/
|
||||
priority?: number;
|
||||
};
|
||||
|
||||
function isBindOptions<Props>(input?: Predicate<Props> | BindOptions<Props>): input is BindOptions<Props> {
|
||||
return typeof input !== "function" && typeof input === "object";
|
||||
}
|
||||
|
||||
/**
|
||||
* Binder is responsible for binding plugin extensions to their corresponding extension points.
|
||||
@@ -60,10 +80,7 @@ export class Binder {
|
||||
* @param extension provided extension
|
||||
* @param predicate to decide if the extension gets rendered for the given props
|
||||
*/
|
||||
bind<E extends ExtensionPointDefinition<string, unknown, undefined>>(
|
||||
extensionPoint: E["name"],
|
||||
extension: E["type"]
|
||||
): void;
|
||||
bind<E extends ExtensionPointDefinition<string, unknown>>(extensionPoint: E["name"], extension: E["type"]): void;
|
||||
bind<E extends ExtensionPointDefinition<string, unknown, any>>(
|
||||
extensionPoint: E["name"],
|
||||
extension: E["type"],
|
||||
@@ -73,17 +90,38 @@ export class Binder {
|
||||
bind<E extends ExtensionPointDefinition<string, unknown, any>>(
|
||||
extensionPoint: E["name"],
|
||||
extension: E["type"],
|
||||
predicate?: Predicate<E["props"]>,
|
||||
options?: BindOptions<E["props"]>
|
||||
): void;
|
||||
bind<E extends ExtensionPointDefinition<string, unknown, any>>(
|
||||
extensionPoint: E["name"],
|
||||
extension: E["type"],
|
||||
predicateOrOptions?: Predicate<E["props"]> | BindOptions<E["props"]>,
|
||||
extensionName?: string
|
||||
) {
|
||||
let predicate: Predicate<E["props"]> = () => true;
|
||||
let priority = 0;
|
||||
if (isBindOptions(predicateOrOptions)) {
|
||||
if (predicateOrOptions.predicate) {
|
||||
predicate = predicateOrOptions.predicate;
|
||||
}
|
||||
if (predicateOrOptions.extensionName) {
|
||||
extensionName = predicateOrOptions.extensionName;
|
||||
}
|
||||
if (typeof predicateOrOptions.priority === "number") {
|
||||
priority = predicateOrOptions.priority;
|
||||
}
|
||||
} else if (predicateOrOptions) {
|
||||
predicate = predicateOrOptions;
|
||||
}
|
||||
if (!this.extensionPoints[extensionPoint]) {
|
||||
this.extensionPoints[extensionPoint] = [];
|
||||
}
|
||||
const registration = {
|
||||
predicate: predicate ? predicate : () => true,
|
||||
predicate,
|
||||
extension,
|
||||
extensionName: extensionName ? extensionName : ""
|
||||
};
|
||||
extensionName: extensionName ? extensionName : "",
|
||||
priority,
|
||||
} as ExtensionRegistration<E["props"], E["type"]>;
|
||||
this.extensionPoints[extensionPoint].push(registration);
|
||||
}
|
||||
|
||||
@@ -128,10 +166,10 @@ export class Binder {
|
||||
): Array<E["type"]> {
|
||||
let registrations = this.extensionPoints[extensionPoint] || [];
|
||||
if (props) {
|
||||
registrations = registrations.filter(reg => reg.predicate(props));
|
||||
registrations = registrations.filter((reg) => reg.predicate(props));
|
||||
}
|
||||
registrations.sort(this.sortExtensions);
|
||||
return registrations.map(reg => reg.extension);
|
||||
return registrations.map((reg) => reg.extension);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +189,11 @@ export class Binder {
|
||||
const regA = a.extensionName ? a.extensionName.toUpperCase() : "";
|
||||
const regB = b.extensionName ? b.extensionName.toUpperCase() : "";
|
||||
|
||||
if (regA === "" && regB !== "") {
|
||||
if (a.priority > b.priority) {
|
||||
return -1;
|
||||
} else if (a.priority < b.priority) {
|
||||
return 1;
|
||||
} else if (regA === "" && regB !== "") {
|
||||
return 1;
|
||||
} else if (regA !== "" && regB === "") {
|
||||
return -1;
|
||||
|
||||
@@ -135,3 +135,24 @@ export type PrimaryNavigationLogoutButtonExtension = ExtensionPointDefinition<
|
||||
"primary-navigation.logout",
|
||||
PrimaryNavigationLogoutButtonProps
|
||||
>;
|
||||
|
||||
export type RepositoryOverviewTopExtensionProps = {
|
||||
page: number;
|
||||
search: string;
|
||||
namespace?: string;
|
||||
};
|
||||
|
||||
export type RepositoryOverviewTopExtension = ExtensionPointDefinition<
|
||||
"repository.overview.top",
|
||||
React.ComponentType<RepositoryOverviewTopExtensionProps>,
|
||||
RepositoryOverviewTopExtensionProps
|
||||
>;
|
||||
export type RepositoryOverviewLeftExtension = ExtensionPointDefinition<"repository.overview.left", React.ComponentType>;
|
||||
export type RepositoryOverviewTitleExtension = ExtensionPointDefinition<
|
||||
"repository.overview.title",
|
||||
React.ComponentType
|
||||
>;
|
||||
export type RepositoryOverviewSubtitleExtension = ExtensionPointDefinition<
|
||||
"repository.overview.subtitle",
|
||||
React.ComponentType
|
||||
>;
|
||||
|
||||
Reference in New Issue
Block a user