diff --git a/src/components/dialogs/hacs-custom-repositories-dialog.ts b/src/components/dialogs/hacs-custom-repositories-dialog.ts
index 16419b3f..e7cdd5b3 100644
--- a/src/components/dialogs/hacs-custom-repositories-dialog.ts
+++ b/src/components/dialogs/hacs-custom-repositories-dialog.ts
@@ -91,8 +91,8 @@ export class HacsCustomRepositoriesDialog extends HacsDialogBase {
${repo.full_name} (${repo.category})
{
- ev.stopPropagation();
+ @click=${(ev: Event) => {
+ ev.preventDefault();
this._removeRepository(String(repo.id));
}}
>
diff --git a/src/hacs-my-redirect.ts b/src/hacs-my-redirect.ts
new file mode 100644
index 00000000..c360c5cd
--- /dev/null
+++ b/src/hacs-my-redirect.ts
@@ -0,0 +1,100 @@
+import { html, LitElement, TemplateResult } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { navigate } from "../homeassistant-frontend/src/common/navigate";
+import {
+ createSearchParam,
+ extractSearchParamsObject,
+} from "../homeassistant-frontend/src/common/url/search-params";
+import "../homeassistant-frontend/src/layouts/hass-error-screen";
+import {
+ ParamType,
+ Redirect,
+ Redirects,
+} from "../homeassistant-frontend/src/panels/my/ha-panel-my";
+import { HomeAssistant, Route } from "../homeassistant-frontend/src/types";
+import { Hacs } from "./data/hacs";
+
+export const REDIRECTS: Redirects = {
+ hacs_repository: {
+ redirect: "/hacs/repository",
+ params: {
+ owner: "string",
+ repository: "string",
+ category: "string?",
+ },
+ },
+};
+
+@customElement("hacs-my-redirect")
+class HacsMyRedirect extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property({ attribute: false }) public hacs!: Hacs;
+
+ @property({ attribute: false }) public route!: Route;
+
+ @state() private _error?: TemplateResult | string;
+
+ protected firstUpdated(_changedProperties): void {
+ const dividerPos = this.route.path.indexOf("/", 1);
+ const path = this.route.path.substr(dividerPos + 1);
+ const redirect = REDIRECTS[path];
+
+ if (!redirect) {
+ this._error = this.hacs.localize("my.not_supported", {
+ link: html`
+ ${this.hacs.localize("my.faq_link")}
+ `,
+ });
+ return;
+ }
+
+ let url: string;
+ try {
+ url = this._createRedirectUrl(redirect);
+ } catch (err: any) {
+ this._error = this.hacs.localize("my.error");
+ return;
+ }
+
+ navigate(url, { replace: true });
+ }
+
+ protected render(): TemplateResult {
+ if (this._error) {
+ return html``;
+ }
+ return html``;
+ }
+
+ private _createRedirectUrl(redirect: Redirect): string {
+ const params = this._createRedirectParams(redirect);
+ return `${redirect.redirect}${params}`;
+ }
+
+ private _createRedirectParams(redirect: Redirect): string {
+ const params = extractSearchParamsObject();
+ if (!redirect.params && !Object.keys(params).length) {
+ return "";
+ }
+ const resultParams = {};
+ for (const [key, type] of Object.entries(redirect.params || {})) {
+ if (!params[key] && type.endsWith("?")) {
+ continue;
+ }
+ if (!params[key] || !this._checkParamType(type, params[key])) {
+ throw Error();
+ }
+ resultParams[key] = params[key];
+ }
+ return `?${createSearchParam(resultParams)}`;
+ }
+
+ private _checkParamType(type: ParamType, _value: string) {
+ return type === "string" || type === "string?";
+ }
+}
diff --git a/src/hacs-router.ts b/src/hacs-router.ts
index a6316c8d..f0be5cdd 100644
--- a/src/hacs-router.ts
+++ b/src/hacs-router.ts
@@ -52,7 +52,12 @@ class HacsRouter extends HassRouterPage {
protected routerOptions: RouterOptions = {
defaultPage: "entry",
+ showLoading: true,
routes: {
+ _my_redirect: {
+ tag: "hacs-my-redirect",
+ load: () => import("./hacs-my-redirect"),
+ },
entry: {
tag: "hacs-entry-panel",
load: () => import("./panels/hacs-entry-panel"),
diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json
index 8d9ef2a6..4d1e219d 100644
--- a/src/localize/languages/en.json
+++ b/src/localize/languages/en.json
@@ -49,6 +49,15 @@
},
"pending_repository_upgrade": "You are running version {downloaded}, version {available} is available"
},
+ "my": {
+ "not_supported": "This redirect is not supported. Check the {link} for the supported redirects and the version they where introduced.",
+ "documentation": "documentation",
+ "faq_link": "My Home Assistant FAQ",
+ "error": "An unknown error occurred",
+ "repository_not_found": "Repository {repository} not found",
+ "add_repository_title": "Add custom repository",
+ "add_repository_description": "This will add the custom repository '{repository}' to be tracked by HACS, do you want to add it?"
+ },
"store": {
"explore": "Explore & download repositories",
"new_repositories_note": "There are some new repositories showing here",
@@ -222,4 +231,4 @@
"information": "Information",
"pending_updates": "Pending updates"
}
-}
+}
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
index 7cf05bff..8ee0dd34 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -109,7 +109,7 @@ class HacsFrontend extends HacsElement {
// Ignore if modifier keys are pressed
return;
}
- if (["c", "m", "e"].includes(ev.key)) {
+ if (["c", "e"].includes(ev.key)) {
// @ts-ignore
fireEvent(mainWindow, "hass-quick-bar-trigger", ev, {
bubbles: false,
diff --git a/src/panels/hacs-repository-panel.ts b/src/panels/hacs-repository-panel.ts
index 539b7c64..5d00a45a 100644
--- a/src/panels/hacs-repository-panel.ts
+++ b/src/panels/hacs-repository-panel.ts
@@ -20,6 +20,7 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { mainWindow } from "../../homeassistant-frontend/src/common/dom/get_main_window";
import { navigate } from "../../homeassistant-frontend/src/common/navigate";
+import { extractSearchParamsObject } from "../../homeassistant-frontend/src/common/url/search-params";
import "../../homeassistant-frontend/src/components/ha-fab";
import "../../homeassistant-frontend/src/components/ha-icon-overflow-menu";
import { getConfigEntries } from "../../homeassistant-frontend/src/data/config_entries";
@@ -31,8 +32,13 @@ import { HomeAssistant, Route } from "../../homeassistant-frontend/src/types";
import "../components/hacs-filter";
import "../components/hacs-repository-card";
import { Hacs } from "../data/hacs";
-import { fetchRepositoryInformation, RepositoryInfo } from "../data/repository";
-import { repositoryUninstall, repositoryUpdate } from "../data/websocket";
+import { fetchRepositoryInformation, RepositoryBase, RepositoryInfo } from "../data/repository";
+import {
+ getRepositories,
+ repositoryAdd,
+ repositoryUninstall,
+ repositoryUpdate,
+} from "../data/websocket";
import { HacsStyles } from "../styles/hacs-common-style";
import { markdown } from "../tools/markdown/markdown";
@@ -52,15 +58,76 @@ export class HacsRepositoryPanel extends LitElement {
@state() private _error?: string;
- protected firstUpdated(changedProperties: PropertyValues): void {
+ protected async firstUpdated(changedProperties: PropertyValues): Promise {
super.firstUpdated(changedProperties);
- const dividerPos = this.route.path.indexOf("/", 1);
- const repositoryId = this.route.path.substr(dividerPos + 1);
- if (!repositoryId) {
- this._error = "Missing repositoryId from route";
- return;
+ document.body.addEventListener("keydown", (ev: KeyboardEvent) => {
+ if (ev.ctrlKey || ev.shiftKey || ev.metaKey || ev.altKey) {
+ // Ignore if modifier keys are pressed
+ return;
+ }
+ if (["m"].includes(ev.key)) {
+ const myParams = new URLSearchParams({
+ redirect: "hacs_repository",
+ owner: this._repository!.full_name.split("/")[0],
+ repository: this._repository!.full_name.split("/")[0],
+ category: this._repository!.category,
+ });
+ window.open(`https://my.home-assistant.io/create-link/?${myParams.toString()}`, "_blank");
+ }
+ });
+
+ const params = extractSearchParamsObject();
+ if (Object.entries(params).length) {
+ let existing: RepositoryBase | undefined;
+ const requestedRepository = `${params.owner}/${params.repository}`;
+ existing = this.hacs.repositories.find(
+ (repository) =>
+ repository.full_name.toLocaleLowerCase() === requestedRepository.toLocaleLowerCase()
+ );
+ if (!existing && params.category) {
+ if (
+ !(await showConfirmationDialog(this, {
+ title: this.hacs.localize("my.add_repository_title"),
+ text: this.hacs.localize("my.add_repository_description", {
+ repository: requestedRepository,
+ }),
+ confirmText: this.hacs.localize("common.add"),
+ dismissText: this.hacs.localize("common.cancel"),
+ }))
+ ) {
+ this._error = this.hacs.localize("my.repository_not_found", {
+ repository: requestedRepository,
+ });
+ return;
+ }
+ try {
+ await repositoryAdd(this.hass, requestedRepository, params.category);
+ this.hacs.repositories = await getRepositories(this.hass);
+ existing = this.hacs.repositories.find(
+ (repository) =>
+ repository.full_name.toLocaleLowerCase() === requestedRepository.toLocaleLowerCase()
+ );
+ } catch (err: any) {
+ this._error = err;
+ return;
+ }
+ }
+ if (existing) {
+ this._fetchRepository(String(existing.id));
+ } else {
+ this._error = this.hacs.localize("my.repository_not_found", {
+ repository: requestedRepository,
+ });
+ }
+ } else {
+ const dividerPos = this.route.path.indexOf("/", 1);
+ const repositoryId = this.route.path.substr(dividerPos + 1);
+ if (!repositoryId) {
+ this._error = "Missing repositoryId from route";
+ return;
+ }
+ this._fetchRepository(repositoryId);
}
- this._fetchRepository(repositoryId);
}
protected updated(changedProps) {