diff --git a/src/entrypoints/authorize.ts b/src/entrypoints/authorize.ts index 68c9fe7c4674..541028b9d6ee 100644 --- a/src/entrypoints/authorize.ts +++ b/src/entrypoints/authorize.ts @@ -1,4 +1,4 @@ -import { mdiAlertCircle, mdiLock, mdiCheck } from "@mdi/js"; +import { mdiAlertCircle, mdiLock, mdiCheck, mdiEye, mdiEyeOff } from "@mdi/js"; import { toASCII } from "punycode"; import { LocalizeFunc, computeLocalize } from "../common/translations/localize"; import { extractSearchParamsObject } from "../common/url/search-params"; @@ -6,6 +6,7 @@ import type { AuthProvider } from "../data/auth"; import type { DataEntryFlowStep } from "../data/data_entry_flow"; import "../resources/roboto"; import { getLocalLanguage, getTranslation } from "../util/common-translation"; +import type { HaFormSchema } from "../components/ha-form/types"; let localize: LocalizeFunc = () => ""; let localizeLoaded = false; @@ -26,6 +27,7 @@ const loadLocalize = async () => { loadLocalize(); const content = document.getElementById("content")!; + const escape = (text: string) => text.replace(//g, ">"); const icon = (path: string, clazz: string) => @@ -33,6 +35,78 @@ const icon = (path: string, clazz: string) => const errorRow = (message: string) => `
${icon(mdiAlertCircle, "error")} ${message}
`; +const makeInput = ( + item: HaFormSchema, + name: string, + currentFormData: FormData | null +) => { + if (item.type === "string") { + const attributes: Record = { + placeholder: name, + name: item.name, + type: + item.name === "password" + ? "password" + : item.name === "code" + ? "number" + : "text", + autocomplete: + item.name === "username" + ? "username" + : item.name === "password" + ? "current-password" + : item.name === "code" + ? "one-time-code" + : undefined, + required: item.required, + }; + if (item.name === "username" && currentFormData) { + attributes.value = currentFormData.get("username") || undefined; + } + if (item.name === "username" || item.name === "password") { + attributes.autocapitalize = "off"; + attributes.autocorrect = "off"; + } + const attributesStr = Object.entries(attributes) + .filter(([_key, value]) => value) + .map(([key, value]) => `${key}="${value}"`) + .join(" "); + + let output = `
`; + if (item.name === "password") { + output += ``; + } + return `
${output}
`; + } else if (item.type === "select") { + const options = item.options.map( + (o) => `` + ); + return `
+ +
+ ${name} +
`; + } + return undefined; +}; + +window["togglePassword"] = ( + e: MouseEvent & { currentTarget: HTMLButtonElement } +) => { + const input = e.currentTarget.parentElement!.firstChild as HTMLInputElement; + if (input.type === "password") { + input.type = "text"; + e.currentTarget.innerHTML = icon(mdiEyeOff, ""); + } else { + input.type = "password"; + e.currentTarget.innerHTML = icon(mdiEye, ""); + } +}; const showError = (error: string) => { content.innerHTML = ` ${icon(mdiAlertCircle, "error")} @@ -155,9 +229,6 @@ const intro = async () => { let storeToken = true; const updateContainer = () => { - const currentForm = document.querySelector("form"); - const currentFormData = currentForm && new FormData(currentForm); - let contents = ""; if (mode.name === "app") @@ -170,6 +241,8 @@ const intro = async () => { })}

`; if (step?.type === "form") { + const currentForm = document.querySelector("form"); + const currentFormData = currentForm && new FormData(currentForm); if (step.errors?.base) { const error = localize( `ui.panel.page-authorize.form.providers.${authProvider.type}.error.${step.errors.base}` @@ -180,49 +253,9 @@ const intro = async () => { const name = localize( `ui.panel.page-authorize.form.providers.${authProvider.type}.step.${step.step_id}.data.${item.name}` ); - if (item.type === "string") { - const attributes: Record = { - placeholder: name, - name: item.name, - type: - item.name === "password" - ? "password" - : item.name === "code" - ? "number" - : "text", - autocomplete: - item.name === "username" - ? "username" - : item.name === "password" - ? "current-password" - : item.name === "code" - ? "one-time-code" - : undefined, - required: item.required, - }; - if (item.name === "username") { - if (currentFormData) - attributes.value = currentFormData.get("username") || undefined; - attributes.autocapitalize = "off"; - attributes.autocorrect = "off"; - } - const attributesStr = Object.entries(attributes) - .filter(([_key, value]) => value) - .map(([key, value]) => `${key}="${value}"`) - .join(" "); - contents += `
`; - } else if (item.type === "select") { - const options = item.options.map( - (o) => `` - ); - contents += `
- -
- ${name} -
`; - } + const result = makeInput(item, name, currentFormData); + console.log("adding", result); + if (result) contents += result; } if ( step.step_id !== "select_mfa_module" && diff --git a/src/html/authorize.html.template b/src/html/authorize.html.template index 5a2454444bb3..27012581f1a7 100644 --- a/src/html/authorize.html.template +++ b/src/html/authorize.html.template @@ -83,6 +83,7 @@ } input::placeholder { color: inherit; + opacity: var(--text-opacity); } input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { @@ -91,6 +92,10 @@ input[type=number] { appearance: textfield; } + input:focus-visible + .bar, select:focus-visible + .bar { + background-color: var(--primary-color); + height: 4px; + } .bar { position: absolute; background-color: var(--bar-color); @@ -100,13 +105,27 @@ pointer-events: none; transition: all 200ms; } + .input-wrapper > button { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + opacity: var(--text-opacity); + + width: 40px; + height: 40px; + top: 4px; + right: 4px; + padding: 0; + border-radius: 20px; + background-color: transparent; + } + .input-wrapper > button:hover { + background-color: inherit; + } input:is([type=text], [type=number], [type=password]):focus-visible, select:focus-visible { outline: none; } - input:focus-visible + .bar, select:focus-visible + .bar { - background-color: var(--primary-color); - height: 4px; - } label { display: flex; align-items: center; @@ -129,6 +148,7 @@ left: 16px; top: 6px; font-size: 12px; + opacity: var(--text-opacity); pointer-events: none; } @supports (moz-appearance: none) { @@ -153,6 +173,8 @@ border: none; cursor: pointer; white-space: nowrap; + } + .buttons > button { height: 40px; border-radius: 20px; } @@ -199,9 +221,7 @@ .input-wrapper { background-color: rgb(0, 0, 0, 0.08); --bar-color: rgb(0, 0, 0, 0.4); - } - input::placeholder, .name { - opacity: 0.8; + --text-opacity: 0.8; } @media (prefers-color-scheme: dark) { html { @@ -219,9 +239,7 @@ .input-wrapper { background-color: rgb(255, 255, 255, 0.05); --bar-color: rgb(255, 255, 255, 0.4); - } - input::placeholder, .name { - opacity: 0.6; + --text-opacity: 0.6; } }