From c2d6fc29ccbeb7a2e72f458bd59926dacd94fee2 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Mon, 12 Feb 2024 14:25:21 +0100 Subject: [PATCH 01/28] Initial commit --- src/gui/base/BorderTextField.ts | 381 ++++++++++++++++++++++++++ src/login/LoginForm.ts | 24 +- src/login/LoginView.ts | 6 +- src/settings/PasswordForm.ts | 14 +- src/settings/SelectMailAddressForm.ts | 6 +- 5 files changed, 406 insertions(+), 25 deletions(-) create mode 100644 src/gui/base/BorderTextField.ts diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts new file mode 100644 index 00000000000..9c8ce9be2ac --- /dev/null +++ b/src/gui/base/BorderTextField.ts @@ -0,0 +1,381 @@ +import m, { Children, ClassComponent, CVnode } from "mithril" +import { px, size } from "../size" +import { DefaultAnimationTime } from "../animation/Animations" +import { getElevatedBackground, getNavButtonIconBackground, getNavigationMenuBg, theme } from "../theme" +import type { TranslationKey } from "../../misc/LanguageViewModel" +import { lang } from "../../misc/LanguageViewModel" +import type { lazy } from "@tutao/tutanota-utils" +import type { keyHandler } from "../../misc/KeyManager" +import { TabIndex } from "../../api/common/TutanotaConstants" +import { ClickHandler, getOperatingClasses } from "./GuiUtils" + +export type BorderTextFieldAttrs = { + id?: string + label: TranslationKey | lazy + value: string + autocompleteAs?: Autocomplete + type?: BorderTextFieldType + helpLabel?: lazy | null + alignRight?: boolean + injectionsLeft?: lazy + // only used by the BubbleTextField (-> uses old TextField) to display bubbles and out of office notification + injectionsRight?: lazy + keyHandler?: keyHandler + onDomInputCreated?: (dom: HTMLInputElement) => void + // interceptor used by the BubbleTextField to react on certain keys + onfocus?: (dom: HTMLElement, input: HTMLInputElement) => unknown + onblur?: (...args: Array) => any + maxWidth?: number + class?: string + disabled?: boolean + // Creates a dummy TextField without interactively & disabled styling + isReadOnly?: boolean + oninput?: (value: string, input: HTMLInputElement) => unknown + onclick?: ClickHandler + doShowBorder?: boolean | null + fontSize?: string + min?: number + max?: number +} + +export const enum BorderTextFieldType { + Text = "text", + Email = "email", + Password = "password", + Area = "area", + Number = "number", + Time = "time", +} + +// relevant subset of possible values for the autocomplete html field +// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete +export const enum Autocomplete { + off = "off", + email = "email", + username = "username", + newPassword = "new-password", + currentPassword = "current-password", + oneTimeCode = "one-time-code", + ccNumber = "cc-number", + ccCsc = "cc-csc", + ccExp = "cc-exp", +} + +export const inputLineHeight: number = size.font_size_base + 8 +const inputMarginTop = size.font_size_small + size.hpad_small + 3 + +// this is not always correct because font size can be biggger/smaller and we ideally should take that into account +const baseLabelPosition = 56 +// it should fit +// compact button + 1 px border + 1 px padding to keep things centered = 32 +// 24px line-height + 12px label + some space between them = 36 + ? +const minInputHeight = 30 + +export class BorderTextField implements ClassComponent { + active: boolean + onblur: EventListener | null = null + domInput!: HTMLInputElement + _domWrapper!: HTMLElement + private _domLabel!: HTMLElement + private _domInputWrapper!: HTMLElement + private _didAutofill!: boolean + + constructor() { + this.active = false + } + + view(vnode: CVnode): Children { + const a = vnode.attrs + const maxWidth = a.maxWidth + const labelBase = !this.active && a.value === "" && !a.isReadOnly && !this._didAutofill && !a.injectionsLeft + const labelTransitionSpeed = DefaultAnimationTime / 2 + const doShowBorder = a.doShowBorder !== false + const borderWidth = this.active ? "2px" : "1px" + const borderColor = this.active ? theme.content_accent : theme.content_border + return m( + ".text-field.rel.overflow-hidden", + { + id: vnode.attrs.id, + oncreate: (vnode) => (this._domWrapper = vnode.dom as HTMLElement), + onclick: (e: MouseEvent) => (a.onclick ? a.onclick(e, this._domInputWrapper) : this.focus(e, a)), + class: a.class != null ? a.class : "pt" + " " + getOperatingClasses(a.disabled), + style: maxWidth + ? { + maxWidth: px(maxWidth), + } + : {}, + }, + [ + m( + "label.abs.text-ellipsis.noselect.z1.i.pr-s", + { + class: this.active ? "content-accent-fg" : "" + " " + getOperatingClasses(a.disabled), + oncreate: (vnode) => { + this._domLabel = vnode.dom as HTMLElement + }, + style: { + fontSize: `${size.font_size_base}px`, + transform: `translateY(-${this.active || vnode.attrs.value ? 30 : 0}px)`, + transition: `transform 100ms`, + background: getElevatedBackground(), // fixme: this gives back the wrong color :/ lightMode -> getElevatedBackground() | darkMode (only login) -> getNavigationMenuBg() + lineHeight: "24px", + margin: "17px 6px", + padding: "0px 10px", + }, + }, + lang.getMaybeLazy(a.label), + ), + m( + ".flex.flex-column", + { + style: {}, + }, + [ + // another wrapper to fix IE 11 min-height bug https://github.com/philipwalton/flexbugs#3-min-height-on-a-flex-container-wont-apply-to-its-flex-items + m( + ".flex.items-end.flex-wrap", + { + // .flex-wrap + style: { + "min-height": px(minInputHeight), + // 2 px border + border: doShowBorder ? `${borderWidth} solid ${borderColor}` : "", + "border-radius": "8px", + margin: this.active ? "0px" : "1px", + }, + }, + [ + a.injectionsLeft ? a.injectionsLeft() : null, // additional wrapper element for bubble input field. input field should always be in one line with right injections + m( + ".inputWrapper.flex-space-between.items-end", + { + style: { + minHeight: px(minInputHeight - 2), // minus padding + }, + oncreate: (vnode) => (this._domInputWrapper = vnode.dom as HTMLElement), + }, + [ + a.type !== BorderTextFieldType.Area ? this._getInputField(a) : this._getTextArea(a), + a.injectionsRight + ? m( + ".flex-end.items-center", + { + style: { + minHeight: "40px", //px(minInputHeight - 2), + margin: "7.5px 16px", + lineHeight: "40px", + }, // Fixme: The dropdown needs to be modified so it doesn't crop of the label + }, + a.injectionsRight(), + ) + : null, + ], + ), + ], + ), + ], + ), + a.helpLabel + ? m( + "small.noselect", + { + onclick: (e: MouseEvent) => { + e.stopPropagation() + }, + }, + a.helpLabel(), + ) + : [], + ], + ) + } + + _getInputField(a: BorderTextFieldAttrs): Children { + if (a.isReadOnly) { + return m( + ".text-break.selectable", + { + style: { + marginTop: px(inputMarginTop), + lineHeight: px(inputLineHeight), + }, + }, + a.value, + ) + } else { + // Due to modern browser's 'smart' password managers that try to autofill everything + // that remotely resembles a password field, we prepend invisible inputs to password fields + // that shouldn't be autofilled. + // since the autofill algorithm looks at inputs that come before and after the password field we need + // three dummies. + const autofillGuard: Children = + a.autocompleteAs === Autocomplete.off + ? [ + m("input.abs", { + style: { + opacity: "0", + height: "0", + }, + tabIndex: TabIndex.Programmatic, + type: BorderTextFieldType.Text, + }), + m("input.abs", { + style: { + opacity: "0", + height: "0", + }, + tabIndex: TabIndex.Programmatic, + type: BorderTextFieldType.Password, + }), + m("input.abs", { + style: { + opacity: "0", + height: "0", + }, + tabIndex: TabIndex.Programmatic, + type: BorderTextFieldType.Text, + }), + ] + : [] + return m( + ".flex-grow.rel", + autofillGuard.concat([ + m("input.input" + (a.alignRight ? ".right" : ""), { + autocomplete: a.autocompleteAs ?? "", + type: a.type, + min: a.min, + max: a.max, + "aria-label": lang.getMaybeLazy(a.label), + disabled: a.disabled, + class: getOperatingClasses(a.disabled) + " text", + + oncreate: (vnode) => { + this.domInput = vnode.dom as HTMLInputElement + a.onDomInputCreated?.(this.domInput) + this.domInput.value = a.value + if (a.type !== BorderTextFieldType.Area) { + ;(vnode.dom as HTMLElement).addEventListener("animationstart", (e: AnimationEvent) => { + if (e.animationName === "onAutoFillStart") { + this._didAutofill = true + m.redraw() + } else if (e.animationName === "onAutoFillCancel") { + this._didAutofill = false + m.redraw() + } + }) + } + }, + onfocus: (e: FocusEvent) => { + this.focus(e, a) + a.onfocus && a.onfocus(this._domWrapper, this.domInput) + }, + onblur: (e: FocusEvent) => this.blur(e, a), + onkeydown: (e: KeyboardEvent) => { + // keydown is used to cancel certain keypresses of the user (mainly needed for the BubbleTextField) + let key = { + key: e.key, + ctrl: e.ctrlKey, + shift: e.shiftKey, + } + return a.keyHandler != null ? a.keyHandler(key) : true + }, + onupdate: () => { + // only change the value if the value has changed otherwise the cursor in Safari and in the iOS App cannot be positioned. + if (this.domInput.value !== a.value) { + this.domInput.value = a.value + } + }, + oninput: () => { + a.oninput && a.oninput(this.domInput.value, this.domInput) + }, + onremove: () => { + // We clean up any value that might still be in DOM e.g. password + if (this.domInput) this.domInput.value = "" + }, + style: { + // fixme: add left padding + padding: "8px 16px", + maxWidth: a.maxWidth, + minWidth: px(20), + // fix for edge browser. buttons are cut off in small windows otherwise + lineHeight: px(56 - 16), + fontSize: a.fontSize, + }, + }), + ]), + ) + } + } + + _getTextArea(a: BorderTextFieldAttrs): Children { + if (a.isReadOnly) { + return m( + ".text-prewrap.text-break.selectable", + { + style: { + marginTop: px(inputMarginTop), + lineHeight: px(inputLineHeight), + }, + }, + a.value, + ) + } else { + return m("textarea.input-area.text-pre", { + "aria-label": lang.getMaybeLazy(a.label), + disabled: a.disabled, + class: getOperatingClasses(a.disabled) + " text", + oncreate: (vnode) => { + this.domInput = vnode.dom as HTMLInputElement + this.domInput.value = a.value + this.domInput.style.height = px(Math.max(a.value.split("\n").length, 1) * inputLineHeight) // display all lines on creation of text area + }, + onfocus: (e: FocusEvent) => this.focus(e, a), + onblur: (e: FocusEvent) => this.blur(e, a), + onkeydown: (e: KeyboardEvent) => { + let key = { + key: e.key, + ctrl: e.ctrlKey, + shift: e.shiftKey, + } + return a.keyHandler != null ? a.keyHandler(key) : true + }, + oninput: () => { + this.domInput.style.height = "0px" + this.domInput.style.height = px(this.domInput.scrollHeight) + a.oninput && a.oninput(this.domInput.value, this.domInput) + }, + onupdate: () => { + // only change the value if the value has changed otherwise the cursor in Safari and in the iOS App cannot be positioned. + if (this.domInput.value !== a.value) { + this.domInput.value = a.value + } + }, + style: { + marginTop: px(inputMarginTop), + lineHeight: px(inputLineHeight), + minWidth: px(20), // fix for edge browser. buttons are cut off in small windows otherwise + fontSize: a.fontSize, + }, + }) + } + } + + focus(e: Event, a: BorderTextFieldAttrs) { + if (!this.active && !a.disabled && !a.isReadOnly) { + this.active = true + this.domInput.focus() + + this._domWrapper.classList.add("active") + } + } + + blur(e: Event, a: BorderTextFieldAttrs) { + this._domWrapper.classList.remove("active") + this.active = false + if (a.onblur instanceof Function) a.onblur(e) + } + + isEmpty(value: string): boolean { + return value === "" + } +} diff --git a/src/login/LoginForm.ts b/src/login/LoginForm.ts index 9b093f832c3..6b96e5fa186 100644 --- a/src/login/LoginForm.ts +++ b/src/login/LoginForm.ts @@ -4,7 +4,7 @@ import Stream from "mithril/stream" import { Button, ButtonType } from "../gui/base/Button.js" import { liveDataAttrs } from "../gui/AriaUtils" import { lang, TranslationKey } from "../misc/LanguageViewModel" -import { Autocomplete, TextField, TextFieldType } from "../gui/base/TextField.js" +import { Autocomplete, BorderTextField, BorderTextFieldType } from "../gui/base/BorderTextField.js" import { Checkbox } from "../gui/base/Checkbox.js" import { client } from "../misc/ClientDetector" import { isApp, isDesktop, isOfflineStorageAvailable } from "../api/common/Env" @@ -24,8 +24,8 @@ export type LoginFormAttrs = { } export class LoginForm implements Component { - mailAddressTextField!: HTMLInputElement - passwordTextField!: HTMLInputElement + mailAddressBorderTextField!: HTMLInputElement + passwordBorderTextField!: HTMLInputElement // When iOS does auto-filling (always in WebView as of iOS 12.2 and in older Safari) // it only sends one input/change event for all fields so we didn't know if fields // were updated. So we kindly ask our fields to update themselves with real DOM values. @@ -36,9 +36,9 @@ export class LoginForm implements Component { this.autofillUpdateHandler = stream.combine(() => { requestAnimationFrame(() => { const oldAddress = a.mailAddress() - const newAddress = this.mailAddressTextField.value + const newAddress = this.mailAddressBorderTextField.value const oldPassword = a.password() - const newPassword = this.passwordTextField.value + const newPassword = this.passwordBorderTextField.value // only update values when they are different or we get stuck in an infinite loop if (oldAddress !== newAddress && newAddress != "") a.mailAddress(newAddress) if (oldPassword !== newPassword && newPassword != "") a.password(newPassword) @@ -49,7 +49,7 @@ export class LoginForm implements Component { onremove(vnode: Vnode) { vnode.attrs.password("") this.autofillUpdateHandler.end(true) - this.passwordTextField.value = "" + this.passwordBorderTextField.value = "" } _passwordDisabled(): boolean { @@ -77,14 +77,14 @@ export class LoginForm implements Component { [ m( "", - m(TextField, { + m(BorderTextField, { label: "mailAddress_label" as TranslationKey, value: a.mailAddress(), oninput: a.mailAddress, - type: TextFieldType.Email, + type: BorderTextFieldType.Email, autocompleteAs: Autocomplete.email, onDomInputCreated: (dom) => { - this.mailAddressTextField = dom + this.mailAddressBorderTextField = dom if (!client.isMobileDevice()) { dom.focus() // have email address auto-focus so the user can immediately type their username (unless on mobile) } @@ -93,13 +93,13 @@ export class LoginForm implements Component { ), m( "", - m(TextField, { + m(BorderTextField, { label: "password_label", value: a.password(), oninput: a.password, - type: TextFieldType.Password, + type: BorderTextFieldType.Password, autocompleteAs: Autocomplete.currentPassword, - onDomInputCreated: (dom) => (this.passwordTextField = dom), + onDomInputCreated: (dom) => (this.passwordBorderTextField = dom), }), ), a.savePassword && !this._passwordDisabled() diff --git a/src/login/LoginView.ts b/src/login/LoginView.ts index 9444a89df35..6004faa68e9 100644 --- a/src/login/LoginView.ts +++ b/src/login/LoginView.ts @@ -371,11 +371,11 @@ export class LoginView extends BaseTopLevelView implements TopLevelView { - loginForm.mailAddressTextField.value = "" - loginForm.passwordTextField.value = "" + loginForm.mailAddressBorderTextField.value = "" + loginForm.passwordBorderTextField.value = "" this.viewModel.mailAddress(args.loginWith ?? "") this.viewModel.password("") - loginForm.passwordTextField.focus() + loginForm.passwordBorderTextField.focus() }) } diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index 761fa614e4d..de95847fc03 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -1,5 +1,5 @@ import m, { Children, Component, Vnode } from "mithril" -import { Autocomplete, TextField, TextFieldType } from "../gui/base/TextField.js" +import { Autocomplete, BorderTextField, BorderTextFieldType } from "../gui/base/BorderTextField.js" import { CompletenessIndicator } from "../gui/CompletenessIndicator.js" import { getPasswordStrength, isSecurePassword, scaleToVisualPasswordStrength } from "../misc/passwords/PasswordUtils" import type { TranslationKey } from "../misc/LanguageViewModel" @@ -270,18 +270,18 @@ export class PasswordForm implements Component { }, [ attrs.model.config.checkOldPassword - ? m(TextField, { + ? m(BorderTextField, { label: "oldPassword_label", value: attrs.model.getOldPassword(), helpLabel: () => m(StatusField, { status: attrs.model.getOldPasswordStatus() }), oninput: (input) => attrs.model.setOldPassword(input), autocompleteAs: Autocomplete.currentPassword, fontSize: px(size.font_size_smaller), - type: attrs.model.isPasswordRevealed(PasswordFieldType.Old) ? TextFieldType.Text : TextFieldType.Password, + type: attrs.model.isPasswordRevealed(PasswordFieldType.Old) ? BorderTextFieldType.Text : BorderTextFieldType.Password, injectionsRight: () => this.renderRevealIcon(attrs, PasswordFieldType.Old), }) : null, - m(TextField, { + m(BorderTextField, { label: "newPassword_label", value: attrs.model.getNewPassword(), helpLabel: () => @@ -300,12 +300,12 @@ export class PasswordForm implements Component { oninput: (input) => attrs.model.setNewPassword(input), autocompleteAs: Autocomplete.newPassword, fontSize: px(size.font_size_smaller), - type: attrs.model.isPasswordRevealed(PasswordFieldType.New) ? TextFieldType.Text : TextFieldType.Password, + type: attrs.model.isPasswordRevealed(PasswordFieldType.New) ? BorderTextFieldType.Text : BorderTextFieldType.Password, injectionsRight: () => this.renderRevealIcon(attrs, PasswordFieldType.New), }), attrs.model.config.hideConfirmation ? null - : m(TextField, { + : m(BorderTextField, { label: "repeatedPassword_label", value: attrs.model.getRepeatedPassword(), autocompleteAs: Autocomplete.newPassword, @@ -315,7 +315,7 @@ export class PasswordForm implements Component { }), oninput: (input) => attrs.model.setRepeatedPassword(input), fontSize: px(size.font_size_smaller), - type: attrs.model.isPasswordRevealed(PasswordFieldType.Confirm) ? TextFieldType.Text : TextFieldType.Password, + type: attrs.model.isPasswordRevealed(PasswordFieldType.Confirm) ? BorderTextFieldType.Text : BorderTextFieldType.Password, injectionsRight: () => this.renderRevealIcon(attrs, PasswordFieldType.Confirm), }), attrs.passwordInfoKey ? m(".small.mt-s", lang.get(attrs.passwordInfoKey)) : null, diff --git a/src/settings/SelectMailAddressForm.ts b/src/settings/SelectMailAddressForm.ts index dbf09c6027a..b8313972e10 100644 --- a/src/settings/SelectMailAddressForm.ts +++ b/src/settings/SelectMailAddressForm.ts @@ -8,7 +8,7 @@ import { Icon } from "../gui/base/Icon" import { locator } from "../api/main/MainLocator" import { assertMainOrNode } from "../api/common/Env" import { px, size } from "../gui/size.js" -import { Autocomplete, inputLineHeight, TextField } from "../gui/base/TextField.js" +import { Autocomplete, inputLineHeight, BorderTextField } from "../gui/base/BorderTextField.js" import { attachDropdown, DropdownButtonAttrs } from "../gui/base/Dropdown.js" import { IconButton, IconButtonAttrs } from "../gui/base/IconButton.js" import { ButtonSize } from "../gui/base/ButtonSize.js" @@ -71,7 +71,7 @@ export class SelectMailAddressForm implements Component Date: Mon, 12 Feb 2024 16:43:01 +0100 Subject: [PATCH 02/28] Patched background color for label when using the dark mode on the login page --- src/gui/base/BorderTextField.ts | 17 ++++++++++------- src/login/LoginForm.ts | 3 +++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index 9c8ce9be2ac..207761c7f84 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -36,6 +36,8 @@ export type BorderTextFieldAttrs = { fontSize?: string min?: number max?: number + labelBgColorOverwrite?: string + // overwrites the default color `getElevatedBackground()` in order to display the correct color when not on a elevated background (-> dark mode LoginForm.ts) } export const enum BorderTextFieldType { @@ -116,11 +118,11 @@ export class BorderTextField implements ClassComponent { style: { fontSize: `${size.font_size_base}px`, transform: `translateY(-${this.active || vnode.attrs.value ? 30 : 0}px)`, - transition: `transform 100ms`, - background: getElevatedBackground(), // fixme: this gives back the wrong color :/ lightMode -> getElevatedBackground() | darkMode (only login) -> getNavigationMenuBg() - lineHeight: "24px", + transition: `transform ${labelTransitionSpeed}ms`, + lineHeight: px(size.font_size_base + 8), margin: "17px 6px", padding: "0px 10px", + background: vnode.attrs.labelBgColorOverwrite || getElevatedBackground(), }, }, lang.getMaybeLazy(a.label), @@ -138,10 +140,10 @@ export class BorderTextField implements ClassComponent { // .flex-wrap style: { "min-height": px(minInputHeight), - // 2 px border + // border: 2px when active; 1px whe inactive border: doShowBorder ? `${borderWidth} solid ${borderColor}` : "", "border-radius": "8px", - margin: this.active ? "0px" : "1px", + margin: this.active ? "0px" : "1px", // reserve space for border to not move other elements on focus change }, }, [ @@ -161,10 +163,11 @@ export class BorderTextField implements ClassComponent { ".flex-end.items-center", { style: { + // use minHeight to allow svgs to be rendered correctly minHeight: "40px", //px(minInputHeight - 2), - margin: "7.5px 16px", lineHeight: "40px", - }, // Fixme: The dropdown needs to be modified so it doesn't crop of the label + margin: "7.5px 16px", + }, }, a.injectionsRight(), ) diff --git a/src/login/LoginForm.ts b/src/login/LoginForm.ts index 6b96e5fa186..9f679dbbe80 100644 --- a/src/login/LoginForm.ts +++ b/src/login/LoginForm.ts @@ -11,6 +11,7 @@ import { isApp, isDesktop, isOfflineStorageAvailable } from "../api/common/Env" import { getWhitelabelCustomizations } from "../misc/WhitelabelCustomizations.js" import { BootstrapFeatureType } from "../api/common/TutanotaConstants.js" import { ACTIVATED_MIGRATION, isLegacyDomain } from "./LoginViewModel.js" +import { getNavigationMenuBg, theme } from "../gui/theme.js" export type LoginFormAttrs = { onSubmit: (username: string, password: string) => unknown @@ -83,6 +84,7 @@ export class LoginForm implements Component { oninput: a.mailAddress, type: BorderTextFieldType.Email, autocompleteAs: Autocomplete.email, + labelBgColorOverwrite: theme.themeId == "dark" ? getNavigationMenuBg() : undefined, onDomInputCreated: (dom) => { this.mailAddressBorderTextField = dom if (!client.isMobileDevice()) { @@ -99,6 +101,7 @@ export class LoginForm implements Component { oninput: a.password, type: BorderTextFieldType.Password, autocompleteAs: Autocomplete.currentPassword, + labelBgColorOverwrite: theme.themeId == "dark" ? getNavigationMenuBg() : undefined, onDomInputCreated: (dom) => (this.passwordBorderTextField = dom), }), ), From e7a8ecb35371a6b332da53707d2501d5e1e8ea1e Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Mon, 12 Feb 2024 17:47:27 +0100 Subject: [PATCH 03/28] Implemented border text field into the recovery dialog on the login page & fixed readonly fields --- src/gui/base/BorderTextField.ts | 18 +++++++++++------- src/login/recover/RecoverLoginDialog.ts | 10 +++++----- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index 207761c7f84..855b3a7b05e 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -120,8 +120,9 @@ export class BorderTextField implements ClassComponent { transform: `translateY(-${this.active || vnode.attrs.value ? 30 : 0}px)`, transition: `transform ${labelTransitionSpeed}ms`, lineHeight: px(size.font_size_base + 8), - margin: "17px 6px", - padding: "0px 10px", + margin: "17px 6px 0px 8px", + padding: "0px 10px 0px 6px", + "font-style": "normal", background: vnode.attrs.labelBgColorOverwrite || getElevatedBackground(), }, }, @@ -199,8 +200,10 @@ export class BorderTextField implements ClassComponent { ".text-break.selectable", { style: { - marginTop: px(inputMarginTop), - lineHeight: px(inputLineHeight), + padding: "8px 16px", + lineHeight: px(56 - 16), + //marginTop: px(inputMarginTop), + //lineHeight: px(inputLineHeight), }, }, a.value, @@ -296,7 +299,6 @@ export class BorderTextField implements ClassComponent { if (this.domInput) this.domInput.value = "" }, style: { - // fixme: add left padding padding: "8px 16px", maxWidth: a.maxWidth, minWidth: px(20), @@ -316,8 +318,10 @@ export class BorderTextField implements ClassComponent { ".text-prewrap.text-break.selectable", { style: { - marginTop: px(inputMarginTop), - lineHeight: px(inputLineHeight), + padding: "8px 16px", + lineHeight: px(56 - 16), + //marginTop: px(inputMarginTop), + //lineHeight: px(inputLineHeight), }, }, a.value, diff --git a/src/login/recover/RecoverLoginDialog.ts b/src/login/recover/RecoverLoginDialog.ts index 7d9e149301a..3b7b4c5d597 100644 --- a/src/login/recover/RecoverLoginDialog.ts +++ b/src/login/recover/RecoverLoginDialog.ts @@ -4,7 +4,7 @@ import Stream from "mithril/stream" import { AccessBlockedError, AccessDeactivatedError, NotAuthenticatedError, TooManyRequestsError } from "../../api/common/error/RestError" import { showProgressDialog } from "../../gui/dialogs/ProgressDialog" import { isMailAddress } from "../../misc/FormatValidator" -import { Autocomplete, TextField, TextFieldType } from "../../gui/base/TextField.js" +import { Autocomplete, BorderTextField, BorderTextFieldType } from "../../gui/base/BorderTextField.js" import { lang } from "../../misc/LanguageViewModel" import { PasswordForm, PasswordModel } from "../../settings/PasswordForm" import { Icons } from "../../gui/base/icons/Icons" @@ -67,14 +67,14 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di child: { view: () => { return [ - m(TextField, { + m(BorderTextField, { label: "mailAddress_label", value: emailAddressStream(), autocompleteAs: Autocomplete.email, oninput: emailAddressStream, }), m(editor), - m(TextField, { + m(BorderTextField, { label: "action_label", value: selectedValueLabelStream(), oninput: selectedValueLabelStream, @@ -85,9 +85,9 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di ? null : selectedAction() === "password" ? m(PasswordForm, { model: passwordModel }) - : m(TextField, { + : m(BorderTextField, { label: "password_label", - type: TextFieldType.Password, + type: BorderTextFieldType.Password, value: passwordValueStream(), autocompleteAs: Autocomplete.currentPassword, oninput: passwordValueStream, From d26a2ac0aef544689f00b1f947afa5de94c655ff Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Mon, 12 Feb 2024 17:48:10 +0100 Subject: [PATCH 04/28] WIP: Fix centering of icons in IconButtons --- src/gui/base/IconButton.ts | 7 ++++--- src/gui/base/buttons/ToggleButton.ts | 7 ++++--- src/settings/PasswordForm.ts | 3 +++ src/subscription/SignupForm.ts | 6 +++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/gui/base/IconButton.ts b/src/gui/base/IconButton.ts index 45601805637..de772024e6d 100644 --- a/src/gui/base/IconButton.ts +++ b/src/gui/base/IconButton.ts @@ -19,6 +19,7 @@ export interface IconButtonAttrs { size?: ButtonSize onblur?: () => unknown onkeydown?: (event: KeyboardEvent) => unknown + iconStyle?: Record } export class IconButton implements Component { @@ -26,6 +27,8 @@ export class IconButton implements Component { view(vnode: Vnode): Children { const { attrs } = vnode + var iconStyle = attrs.iconStyle ? attrs.iconStyle : {} + iconStyle["fill"] = getColors(attrs.colors ?? ButtonColor.Content).button return m( "button.icon-button.state-bg", { @@ -47,9 +50,7 @@ export class IconButton implements Component { container: "div", class: "center-h", large: true, - style: { - fill: getColors(attrs.colors ?? ButtonColor.Content).button, - }, + style: iconStyle, }), ) } diff --git a/src/gui/base/buttons/ToggleButton.ts b/src/gui/base/buttons/ToggleButton.ts index adfb7ae4a1a..a4286ce01a9 100644 --- a/src/gui/base/buttons/ToggleButton.ts +++ b/src/gui/base/buttons/ToggleButton.ts @@ -13,10 +13,13 @@ export interface ToggleButtonAttrs { size?: ButtonSize toggledTitle?: TranslationText style?: Record + iconStyle?: Record } export class ToggleButton implements Component { view({ attrs }: Vnode): Children { + var iconStyle = attrs.iconStyle ? attrs.iconStyle : {} + iconStyle["fill"] = getColors(attrs.colors ?? ButtonColor.Content).button return m( "button.toggle-button.state-bg", { @@ -32,9 +35,7 @@ export class ToggleButton implements Component { container: "div", class: "center-h", large: true, - style: { - fill: getColors(attrs.colors ?? ButtonColor.Content).button, - }, + style: iconStyle, }), ) } diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index de95847fc03..2d4c89fb5c1 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -349,6 +349,9 @@ export class PasswordForm implements Component { }, icon: attrs.model.isPasswordRevealed(passwordType) ? Icons.NoEye : Icons.Eye, size: ButtonSize.Compact, + iconStyle: { + "margin-bottom": "4px", + }, }) } } diff --git a/src/subscription/SignupForm.ts b/src/subscription/SignupForm.ts index 562b7b023fe..86c6e78b099 100644 --- a/src/subscription/SignupForm.ts +++ b/src/subscription/SignupForm.ts @@ -2,7 +2,7 @@ import m, { Children, Component, Vnode } from "mithril" import stream from "mithril/stream" import Stream from "mithril/stream" import { Dialog } from "../gui/base/Dialog" -import { Autocomplete, TextField } from "../gui/base/TextField.js" +import { Autocomplete, BorderTextField } from "../gui/base/BorderTextField.js" import { Button, ButtonType } from "../gui/base/Button.js" import { getWhitelabelRegistrationDomains } from "../login/LoginView" import type { NewAccountData } from "./UpgradeSubscriptionWizard" @@ -174,7 +174,7 @@ export class SignupForm implements Component { "#signup-account-dialog.flex-center", m(".flex-grow-shrink-auto.max-width-m.pt.pb.plr-l", [ a.readonly - ? m(TextField, { + ? m(BorderTextField, { label: "mailAddress_label", value: a.prefilledMailAddress ?? "", autocompleteAs: Autocomplete.newPassword, @@ -192,7 +192,7 @@ export class SignupForm implements Component { passwordInfoKey: "passwordImportance_msg", }), getWhitelabelRegistrationDomains().length > 0 - ? m(TextField, { + ? m(BorderTextField, { value: this._code(), oninput: this._code, label: "whitelabelRegistrationCode_label", From c953807dcd3ab8218bf721fae8f09daa18fc4f6c Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Tue, 13 Feb 2024 11:17:10 +0100 Subject: [PATCH 05/28] WIP: Adapt new layout (less text on signup) --- src/gui/CompletenessIndicator.ts | 10 +++++++++- src/gui/base/StatusField.ts | 3 ++- src/settings/PasswordForm.ts | 7 +++++-- src/settings/SelectMailAddressForm.ts | 4 ++-- src/subscription/SignupForm.ts | 12 ++++++------ 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/gui/CompletenessIndicator.ts b/src/gui/CompletenessIndicator.ts index 28f3ece7a89..13f20454272 100644 --- a/src/gui/CompletenessIndicator.ts +++ b/src/gui/CompletenessIndicator.ts @@ -19,7 +19,15 @@ export class CompletenessIndicator implements Component } export class StatusField implements Component { view(vnode: Vnode): Children { const { status } = vnode.attrs if (!status) return null - return m("", lang.get(status.text)) + return m("", vnode.attrs.style ? { style: vnode.attrs.style } : {}, lang.get(status.text)) } } diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index 2d4c89fb5c1..af7d4187d26 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -285,15 +285,17 @@ export class PasswordForm implements Component { label: "newPassword_label", value: attrs.model.getNewPassword(), helpLabel: () => - m(".flex.col.mt-xs", [ + m(".flex.col.mt-xs", { style: { margin: "4px 16px" } }, [ m(".flex.items-center", [ m( ".mr-s", + { style: { width: "100%" } }, m(CompletenessIndicator, { + width: "100%", percentageCompleted: scaleToVisualPasswordStrength(attrs.model.getPasswordStrength()), }), ), - m(StatusField, { status: attrs.model.getNewPasswordStatus() }), + //m(StatusField, { status: attrs.model.getNewPasswordStatus() }), ]), this.renderPasswordGeneratorHelp(attrs), ]), @@ -312,6 +314,7 @@ export class PasswordForm implements Component { helpLabel: () => m(StatusField, { status: attrs.model.getRepeatedPasswordStatus(), + style: { "margin-left": "16px" }, }), oninput: (input) => attrs.model.setRepeatedPassword(input), fontSize: px(size.font_size_smaller), diff --git a/src/settings/SelectMailAddressForm.ts b/src/settings/SelectMailAddressForm.ts index b8313972e10..4d8d012d18b 100644 --- a/src/settings/SelectMailAddressForm.ts +++ b/src/settings/SelectMailAddressForm.ts @@ -122,8 +122,8 @@ export class SelectMailAddressForm implements Component { }) : [ m(SelectMailAddressForm, mailAddressFormAttrs), // Leave as is - a.isPaidSubscription() - ? m(".small.mt-s", lang.get("configureCustomDomainAfterSignup_msg"), [ - m("a", { href: faqCustomDomainLink, target: "_blank" }, faqCustomDomainLink), - ]) - : null, + //a.isPaidSubscription() + // ? m(".small.mt-s", lang.get("configureCustomDomainAfterSignup_msg"), [ + // m("a", { href: faqCustomDomainLink, target: "_blank" }, faqCustomDomainLink), + // ]) + // : null, m(PasswordForm, { model: this.passwordModel, - passwordInfoKey: "passwordImportance_msg", + //passwordInfoKey: "passwordImportance_msg", }), getWhitelabelRegistrationDomains().length > 0 ? m(BorderTextField, { From 03c9ee42cb3f33b8416dd2f18fb5963b45817214 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Tue, 13 Feb 2024 11:17:37 +0100 Subject: [PATCH 06/28] Fixed wrong margin --- src/gui/base/BorderTextField.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index 855b3a7b05e..d4ef2ba873c 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -120,7 +120,7 @@ export class BorderTextField implements ClassComponent { transform: `translateY(-${this.active || vnode.attrs.value ? 30 : 0}px)`, transition: `transform ${labelTransitionSpeed}ms`, lineHeight: px(size.font_size_base + 8), - margin: "17px 6px 0px 8px", + margin: "17px 6px 0px 10px", padding: "0px 10px 0px 6px", "font-style": "normal", background: vnode.attrs.labelBgColorOverwrite || getElevatedBackground(), From 2f58fd6aa0fbe14ed2aa5758d2262ab8c507196c Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Tue, 13 Feb 2024 14:37:07 +0100 Subject: [PATCH 07/28] Fixed centering of icons in IconButtons --- src/gui/base/BorderTextField.ts | 2 +- src/settings/SelectMailAddressForm.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index d4ef2ba873c..6beb5accef3 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -167,7 +167,7 @@ export class BorderTextField implements ClassComponent { // use minHeight to allow svgs to be rendered correctly minHeight: "40px", //px(minInputHeight - 2), lineHeight: "40px", - margin: "7.5px 16px", + margin: "8px 16px", }, }, a.injectionsRight(), diff --git a/src/settings/SelectMailAddressForm.ts b/src/settings/SelectMailAddressForm.ts index 4d8d012d18b..9f5267a531a 100644 --- a/src/settings/SelectMailAddressForm.ts +++ b/src/settings/SelectMailAddressForm.ts @@ -103,6 +103,7 @@ export class SelectMailAddressForm implements Component attrs.availableDomains.map((domain) => this.createDropdownItemAttrs(domain, attrs)), showDropdown: () => true, From 988dd3229dea1b64544cdf951b959e9c0e1d3903 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Tue, 13 Feb 2024 15:46:52 +0100 Subject: [PATCH 08/28] Minor change to completeness bar --- src/gui/CompletenessIndicator.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/gui/CompletenessIndicator.ts b/src/gui/CompletenessIndicator.ts index 13f20454272..4dd6d5a33b1 100644 --- a/src/gui/CompletenessIndicator.ts +++ b/src/gui/CompletenessIndicator.ts @@ -1,5 +1,6 @@ import m, { Children, Component, Vnode } from "mithril" import { theme } from "./theme.js" +import { DefaultAnimationTime } from "./animation/Animations.js" export interface CompletenessIndicatorAttrs { percentageCompleted: number @@ -8,26 +9,28 @@ export interface CompletenessIndicatorAttrs { export class CompletenessIndicator implements Component { view({ attrs }: Vnode): Children { + const mediumStrengthPercentage: number = 30 + const goodStrengthPercentage: number = 75 return m( "", { style: { border: `1px solid ${theme.content_button}`, width: attrs.width ?? "100px", - height: "10px", + height: "8px", }, }, m("", { style: { - "background-color": attrs.percentageCompleted < 50 ? "red" : attrs.percentageCompleted < 75 ? "yellow" : "green", - // red to yellow to green + "background-color": + attrs.percentageCompleted < mediumStrengthPercentage ? "red" : attrs.percentageCompleted < goodStrengthPercentage ? "yellow" : "green", "background-image": - attrs.percentageCompleted < 50 - ? "linear-gradient(90deg, rgba(150,50,50,1) 0%, rgba(255,0,0,1) 100%)" // black to red - : attrs.percentageCompleted < 75 - ? "linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,255,0,1) 100%)" // red to yellow - : "linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,245,0,1) 50%, rgba(0,255,0,1) 100%)", - transition: "width 1s ease 0s", + attrs.percentageCompleted < mediumStrengthPercentage + ? "linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,0,0,1) 100%)" + : attrs.percentageCompleted < goodStrengthPercentage + ? "linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,255,0,1) 100%)" + : "linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,245,0,1) 75%, rgba(0,255,0,1) 100%)", + transition: `width ${DefaultAnimationTime * 3}ms ease 0s`, width: attrs.percentageCompleted + "%", height: "100%", }, From 388bfa3b5fcf2f85022c0a2905113dda33abf4ff Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Tue, 13 Feb 2024 16:21:22 +0100 Subject: [PATCH 09/28] Fix: colored completeness bar is not use everywhere anymore --- src/gui/CompletenessIndicator.ts | 37 +++++++++++++++++++++----------- src/settings/PasswordForm.ts | 1 + 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/gui/CompletenessIndicator.ts b/src/gui/CompletenessIndicator.ts index 4dd6d5a33b1..e876cd00aef 100644 --- a/src/gui/CompletenessIndicator.ts +++ b/src/gui/CompletenessIndicator.ts @@ -5,6 +5,7 @@ import { DefaultAnimationTime } from "./animation/Animations.js" export interface CompletenessIndicatorAttrs { percentageCompleted: number width?: string + passwordColorScale?: boolean } export class CompletenessIndicator implements Component { @@ -21,19 +22,29 @@ export class CompletenessIndicator implements Component { { style: { width: "100%" } }, m(CompletenessIndicator, { width: "100%", + passwordColorScale: true, percentageCompleted: scaleToVisualPasswordStrength(attrs.model.getPasswordStrength()), }), ), From 514666ae1ce5a171ae7208a8760ff031a67b6c98 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Tue, 13 Feb 2024 16:34:50 +0100 Subject: [PATCH 10/28] Round corners in recovery code field --- src/gui/editor/HtmlEditor.ts | 10 ++++++++-- src/login/recover/RecoverLoginDialog.ts | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gui/editor/HtmlEditor.ts b/src/gui/editor/HtmlEditor.ts index 3cc6ff10343..afbc1f3240a 100644 --- a/src/gui/editor/HtmlEditor.ts +++ b/src/gui/editor/HtmlEditor.ts @@ -20,6 +20,7 @@ export class HtmlEditor implements Component { private active = false private domTextArea: HTMLTextAreaElement | null = null private _showBorders = false + private borderRadius: number | null = null private minHeight: number | null = null private placeholderId: TranslationKey | null = null private placeholderDomElement: HTMLElement | null = null @@ -74,8 +75,8 @@ export class HtmlEditor implements Component { }, }) : null, - this.label ? m(".small.mt-form", lang.getMaybeLazy(this.label)) : null, - m(borderClasses, [ + this.label ? m(".small.mt-form", this.borderRadius ? { style: { "margin-left": px(16) } } : {}, lang.getMaybeLazy(this.label)) : null, + m(borderClasses, this.borderRadius ? { style: { "border-radius": px(this.borderRadius) } } : {}, [ getPlaceholder(), this.mode === HtmlEditorMode.WYSIWYG ? m(".wysiwyg.rel.overflow-hidden.selectable", [ @@ -157,6 +158,11 @@ export class HtmlEditor implements Component { return this } + setBorderRadius(px: number): HtmlEditor { + this.borderRadius = px + return this + } + setMinHeight(height: number): HtmlEditor { this.minHeight = height this.editor.setMinHeight(height) diff --git a/src/login/recover/RecoverLoginDialog.ts b/src/login/recover/RecoverLoginDialog.ts index 3b7b4c5d597..ece82bdce3e 100644 --- a/src/login/recover/RecoverLoginDialog.ts +++ b/src/login/recover/RecoverLoginDialog.ts @@ -61,6 +61,7 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di editor.setHtmlMonospace(true) editor.setMinHeight(80) editor.showBorders() + editor.setBorderRadius(8) const recoverDialog = Dialog.showActionDialog({ title: lang.get("recover_label"), type: DialogType.EditSmall, From 2ccc7f833dea6a3116dcae32d60e49e4db47949d Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Wed, 14 Feb 2024 10:01:30 +0100 Subject: [PATCH 11/28] Readd password strength description --- src/settings/PasswordForm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index 795d876fc1c..8132de55ed2 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -296,7 +296,7 @@ export class PasswordForm implements Component { percentageCompleted: scaleToVisualPasswordStrength(attrs.model.getPasswordStrength()), }), ), - //m(StatusField, { status: attrs.model.getNewPasswordStatus() }), + m(StatusField, { status: attrs.model.getNewPasswordStatus(), style: { "min-width": "max-content" } }), ]), this.renderPasswordGeneratorHelp(attrs), ]), From b951f472074447003f074e2d0faf5d0da6941e46 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Wed, 14 Feb 2024 16:21:18 +0100 Subject: [PATCH 12/28] Recovery code input: Switched from HtmlEditor to BorderTextField --- src/login/recover/RecoverLoginDialog.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/login/recover/RecoverLoginDialog.ts b/src/login/recover/RecoverLoginDialog.ts index ece82bdce3e..dd1e1ac19b4 100644 --- a/src/login/recover/RecoverLoginDialog.ts +++ b/src/login/recover/RecoverLoginDialog.ts @@ -29,6 +29,7 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di const passwordModel = new PasswordModel(locator.usageTestController, locator.logins, { checkOldPassword: false, enforceStrength: true }) const passwordValueStream = stream("") const emailAddressStream = stream(mailAddress || "") + const recoveryCodeStream = stream("") const resetPasswordAction: DropdownButtonAttrs = { label: "recoverSetNewPassword_action", click: () => selectedAction("password"), @@ -56,12 +57,6 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di return lang.get("choose_label") } }) - const editor = new HtmlEditor("recoveryCode_label") - editor.setMode(HtmlEditorMode.HTML) - editor.setHtmlMonospace(true) - editor.setMinHeight(80) - editor.showBorders() - editor.setBorderRadius(8) const recoverDialog = Dialog.showActionDialog({ title: lang.get("recover_label"), type: DialogType.EditSmall, @@ -74,7 +69,12 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di autocompleteAs: Autocomplete.email, oninput: emailAddressStream, }), - m(editor), + m(BorderTextField, { + label: "recoveryCode_label", + value: recoveryCodeStream(), + oninput: recoveryCodeStream, + type: BorderTextFieldType.Area, + }), m(BorderTextField, { label: "action_label", value: selectedValueLabelStream(), @@ -98,7 +98,7 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di }, okAction: async () => { const cleanMailAddress = emailAddressStream().trim().toLowerCase() - const cleanRecoverCodeValue = editor.getValue().replace(/\s/g, "").toLowerCase() + const cleanRecoverCodeValue = recoveryCodeStream().trim().replace(/\s/g, "").toLowerCase() if (!isMailAddress(cleanMailAddress, true)) { Dialog.message("mailAddressInvalid_msg") From dc159ec598b31842da90f3674910b3e7ce14aeaa Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Wed, 14 Feb 2024 16:22:54 +0100 Subject: [PATCH 13/28] Fixed some padding/margin issues --- src/settings/PasswordForm.ts | 7 ++----- src/settings/SelectMailAddressForm.ts | 3 +-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index 8132de55ed2..c3877e987f0 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -285,7 +285,7 @@ export class PasswordForm implements Component { label: "newPassword_label", value: attrs.model.getNewPassword(), helpLabel: () => - m(".flex.col.mt-xs", { style: { margin: "4px 16px" } }, [ + m(".flex.col.mt-xs", { style: { margin: "4px 16px 8px 16px" } }, [ m(".flex.items-center", [ m( ".mr-s", @@ -315,7 +315,7 @@ export class PasswordForm implements Component { helpLabel: () => m(StatusField, { status: attrs.model.getRepeatedPasswordStatus(), - style: { "margin-left": "16px" }, + style: { margin: "4px 16px 8px 16px" }, }), oninput: (input) => attrs.model.setRepeatedPassword(input), fontSize: px(size.font_size_smaller), @@ -353,9 +353,6 @@ export class PasswordForm implements Component { }, icon: attrs.model.isPasswordRevealed(passwordType) ? Icons.NoEye : Icons.Eye, size: ButtonSize.Compact, - iconStyle: { - "margin-bottom": "4px", - }, }) } } diff --git a/src/settings/SelectMailAddressForm.ts b/src/settings/SelectMailAddressForm.ts index 9f5267a531a..7d4ccd03ca9 100644 --- a/src/settings/SelectMailAddressForm.ts +++ b/src/settings/SelectMailAddressForm.ts @@ -87,7 +87,7 @@ export class SelectMailAddressForm implements Component attrs.availableDomains.map((domain) => this.createDropdownItemAttrs(domain, attrs)), showDropdown: () => true, From 4e3dd8d375d2a2c2b3c177a6ecc620ca9dfd9fed Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Wed, 14 Feb 2024 16:56:15 +0100 Subject: [PATCH 14/28] Switched to general size definition --- src/gui/base/BorderTextField.ts | 36 ++++++++++++++++----------------- src/gui/size.ts | 4 ++++ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index 6beb5accef3..f41f0ca186c 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -64,7 +64,6 @@ export const enum Autocomplete { } export const inputLineHeight: number = size.font_size_base + 8 -const inputMarginTop = size.font_size_small + size.hpad_small + 3 // this is not always correct because font size can be biggger/smaller and we ideally should take that into account const baseLabelPosition = 56 @@ -119,9 +118,9 @@ export class BorderTextField implements ClassComponent { fontSize: `${size.font_size_base}px`, transform: `translateY(-${this.active || vnode.attrs.value ? 30 : 0}px)`, transition: `transform ${labelTransitionSpeed}ms`, - lineHeight: px(size.font_size_base + 8), - margin: "17px 6px 0px 10px", - padding: "0px 10px 0px 6px", + margin: "11px 10px", + padding: "6px", + lineHeight: px(size.md_default_line_height), "font-style": "normal", background: vnode.attrs.labelBgColorOverwrite || getElevatedBackground(), }, @@ -165,9 +164,10 @@ export class BorderTextField implements ClassComponent { { style: { // use minHeight to allow svgs to be rendered correctly - minHeight: "40px", //px(minInputHeight - 2), - lineHeight: "40px", - margin: "8px 16px", + minHeight: px(size.md_default_line_height), //px(minInputHeight - 2), + lineHeight: px(size.md_default_line_height), + // 13px because 56 (md field height) - 30 (svg size) = 26 -> 26/2 = 13 + margin: `13px ${size.md_default_margin}px`, }, }, a.injectionsRight(), @@ -200,10 +200,8 @@ export class BorderTextField implements ClassComponent { ".text-break.selectable", { style: { - padding: "8px 16px", - lineHeight: px(56 - 16), - //marginTop: px(inputMarginTop), - //lineHeight: px(inputLineHeight), + padding: px(size.md_default_margin), + lineHeight: px(size.md_default_line_height), }, }, a.value, @@ -299,11 +297,12 @@ export class BorderTextField implements ClassComponent { if (this.domInput) this.domInput.value = "" }, style: { - padding: "8px 16px", + // needs to be padding instead of margin to stay within parent element + padding: px(size.md_default_margin), maxWidth: a.maxWidth, minWidth: px(20), // fix for edge browser. buttons are cut off in small windows otherwise - lineHeight: px(56 - 16), + lineHeight: px(size.md_default_line_height), fontSize: a.fontSize, }, }), @@ -318,10 +317,8 @@ export class BorderTextField implements ClassComponent { ".text-prewrap.text-break.selectable", { style: { - padding: "8px 16px", - lineHeight: px(56 - 16), - //marginTop: px(inputMarginTop), - //lineHeight: px(inputLineHeight), + margin: px(size.md_default_margin), + lineHeight: px(size.md_default_line_height), }, }, a.value, @@ -358,10 +355,11 @@ export class BorderTextField implements ClassComponent { } }, style: { - marginTop: px(inputMarginTop), - lineHeight: px(inputLineHeight), + margin: px(size.md_default_margin), + lineHeight: px(size.md_default_line_height), minWidth: px(20), // fix for edge browser. buttons are cut off in small windows otherwise fontSize: a.fontSize, + "white-space": "normal", }, }) } diff --git a/src/gui/size.ts b/src/gui/size.ts index ae3650e605c..82f2b31231a 100644 --- a/src/gui/size.ts +++ b/src/gui/size.ts @@ -85,6 +85,10 @@ export const size = { dot_size: 7, checkbox_size: 14, + + md_supporting_text_margin: 4, + md_default_margin: 16, + md_default_line_height: 24, } export const inputLineHeight: number = size.font_size_base + 8 From 257d8ae622c1bd534866a93f6bbde915b2002183 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Wed, 14 Feb 2024 16:57:21 +0100 Subject: [PATCH 15/28] Removed importance notice from password --- src/subscription/SignupForm.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/subscription/SignupForm.ts b/src/subscription/SignupForm.ts index 5d688e5d2cd..0ddda92358f 100644 --- a/src/subscription/SignupForm.ts +++ b/src/subscription/SignupForm.ts @@ -182,14 +182,8 @@ export class SignupForm implements Component { }) : [ m(SelectMailAddressForm, mailAddressFormAttrs), // Leave as is - //a.isPaidSubscription() - // ? m(".small.mt-s", lang.get("configureCustomDomainAfterSignup_msg"), [ - // m("a", { href: faqCustomDomainLink, target: "_blank" }, faqCustomDomainLink), - // ]) - // : null, m(PasswordForm, { model: this.passwordModel, - //passwordInfoKey: "passwordImportance_msg", }), getWhitelabelRegistrationDomains().length > 0 ? m(BorderTextField, { From 8d7d47e34af23706523093098f0c73fb345fd6a4 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Wed, 14 Feb 2024 17:14:38 +0100 Subject: [PATCH 16/28] Make it easier for a user to distinguish between a normal input and a text area --- src/gui/base/BorderTextField.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index f41f0ca186c..d088655d663 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -66,7 +66,6 @@ export const enum Autocomplete { export const inputLineHeight: number = size.font_size_base + 8 // this is not always correct because font size can be biggger/smaller and we ideally should take that into account -const baseLabelPosition = 56 // it should fit // compact button + 1 px border + 1 px padding to keep things centered = 32 // 24px line-height + 12px label + some space between them = 36 + ? @@ -164,7 +163,7 @@ export class BorderTextField implements ClassComponent { { style: { // use minHeight to allow svgs to be rendered correctly - minHeight: px(size.md_default_line_height), //px(minInputHeight - 2), + minHeight: px(size.md_default_line_height), lineHeight: px(size.md_default_line_height), // 13px because 56 (md field height) - 30 (svg size) = 26 -> 26/2 = 13 margin: `13px ${size.md_default_margin}px`, @@ -357,6 +356,7 @@ export class BorderTextField implements ClassComponent { style: { margin: px(size.md_default_margin), lineHeight: px(size.md_default_line_height), + minHeight: px(size.md_default_line_height * 2), minWidth: px(20), // fix for edge browser. buttons are cut off in small windows otherwise fontSize: a.fontSize, "white-space": "normal", From 0ed97ce8e53cede513f6f82106e0ca535cbcbd8a Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Thu, 15 Feb 2024 09:18:32 +0100 Subject: [PATCH 17/28] Removed now unnecessary workarounds which were induced in d26a2ac and 514666a --- src/gui/base/IconButton.ts | 7 +++---- src/gui/base/buttons/ToggleButton.ts | 7 +++---- src/gui/editor/HtmlEditor.ts | 10 ++-------- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/gui/base/IconButton.ts b/src/gui/base/IconButton.ts index de772024e6d..45601805637 100644 --- a/src/gui/base/IconButton.ts +++ b/src/gui/base/IconButton.ts @@ -19,7 +19,6 @@ export interface IconButtonAttrs { size?: ButtonSize onblur?: () => unknown onkeydown?: (event: KeyboardEvent) => unknown - iconStyle?: Record } export class IconButton implements Component { @@ -27,8 +26,6 @@ export class IconButton implements Component { view(vnode: Vnode): Children { const { attrs } = vnode - var iconStyle = attrs.iconStyle ? attrs.iconStyle : {} - iconStyle["fill"] = getColors(attrs.colors ?? ButtonColor.Content).button return m( "button.icon-button.state-bg", { @@ -50,7 +47,9 @@ export class IconButton implements Component { container: "div", class: "center-h", large: true, - style: iconStyle, + style: { + fill: getColors(attrs.colors ?? ButtonColor.Content).button, + }, }), ) } diff --git a/src/gui/base/buttons/ToggleButton.ts b/src/gui/base/buttons/ToggleButton.ts index a4286ce01a9..adfb7ae4a1a 100644 --- a/src/gui/base/buttons/ToggleButton.ts +++ b/src/gui/base/buttons/ToggleButton.ts @@ -13,13 +13,10 @@ export interface ToggleButtonAttrs { size?: ButtonSize toggledTitle?: TranslationText style?: Record - iconStyle?: Record } export class ToggleButton implements Component { view({ attrs }: Vnode): Children { - var iconStyle = attrs.iconStyle ? attrs.iconStyle : {} - iconStyle["fill"] = getColors(attrs.colors ?? ButtonColor.Content).button return m( "button.toggle-button.state-bg", { @@ -35,7 +32,9 @@ export class ToggleButton implements Component { container: "div", class: "center-h", large: true, - style: iconStyle, + style: { + fill: getColors(attrs.colors ?? ButtonColor.Content).button, + }, }), ) } diff --git a/src/gui/editor/HtmlEditor.ts b/src/gui/editor/HtmlEditor.ts index afbc1f3240a..3cc6ff10343 100644 --- a/src/gui/editor/HtmlEditor.ts +++ b/src/gui/editor/HtmlEditor.ts @@ -20,7 +20,6 @@ export class HtmlEditor implements Component { private active = false private domTextArea: HTMLTextAreaElement | null = null private _showBorders = false - private borderRadius: number | null = null private minHeight: number | null = null private placeholderId: TranslationKey | null = null private placeholderDomElement: HTMLElement | null = null @@ -75,8 +74,8 @@ export class HtmlEditor implements Component { }, }) : null, - this.label ? m(".small.mt-form", this.borderRadius ? { style: { "margin-left": px(16) } } : {}, lang.getMaybeLazy(this.label)) : null, - m(borderClasses, this.borderRadius ? { style: { "border-radius": px(this.borderRadius) } } : {}, [ + this.label ? m(".small.mt-form", lang.getMaybeLazy(this.label)) : null, + m(borderClasses, [ getPlaceholder(), this.mode === HtmlEditorMode.WYSIWYG ? m(".wysiwyg.rel.overflow-hidden.selectable", [ @@ -158,11 +157,6 @@ export class HtmlEditor implements Component { return this } - setBorderRadius(px: number): HtmlEditor { - this.borderRadius = px - return this - } - setMinHeight(height: number): HtmlEditor { this.minHeight = height this.editor.setMinHeight(height) From ca5986fd6849b0047a17accf7c3da7399d7450af Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Thu, 15 Feb 2024 10:13:56 +0100 Subject: [PATCH 18/28] Implement border field for payment details --- src/gui/base/BorderTextField.ts | 15 ++++++++++++++- src/subscription/SimplifiedCreditCardInput.ts | 8 ++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index d088655d663..4b3feb49f5f 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -38,6 +38,7 @@ export type BorderTextFieldAttrs = { max?: number labelBgColorOverwrite?: string // overwrites the default color `getElevatedBackground()` in order to display the correct color when not on a elevated background (-> dark mode LoginForm.ts) + areaTextFieldLines?: number } export const enum BorderTextFieldType { @@ -76,6 +77,7 @@ export class BorderTextField implements ClassComponent { onblur: EventListener | null = null domInput!: HTMLInputElement _domWrapper!: HTMLElement + setValue!: (value: string) => void private _domLabel!: HTMLElement private _domInputWrapper!: HTMLElement private _didAutofill!: boolean @@ -185,6 +187,9 @@ export class BorderTextField implements ClassComponent { onclick: (e: MouseEvent) => { e.stopPropagation() }, + style: { + margin: `${size.md_supporting_text_margin}px ${size.md_default_margin}px 0px `, + }, }, a.helpLabel(), ) @@ -268,6 +273,10 @@ export class BorderTextField implements ClassComponent { }) } }, + setValue: (value: string) => { + a.value = value + this.domInput.value = value + }, onfocus: (e: FocusEvent) => { this.focus(e, a) a.onfocus && a.onfocus(this._domWrapper, this.domInput) @@ -347,6 +356,10 @@ export class BorderTextField implements ClassComponent { this.domInput.style.height = px(this.domInput.scrollHeight) a.oninput && a.oninput(this.domInput.value, this.domInput) }, + setValue: (value: string) => { + a.value = value + this.domInput.value = value + }, onupdate: () => { // only change the value if the value has changed otherwise the cursor in Safari and in the iOS App cannot be positioned. if (this.domInput.value !== a.value) { @@ -356,7 +369,7 @@ export class BorderTextField implements ClassComponent { style: { margin: px(size.md_default_margin), lineHeight: px(size.md_default_line_height), - minHeight: px(size.md_default_line_height * 2), + minHeight: a.areaTextFieldLines ? px(size.md_default_line_height * a.areaTextFieldLines) : px(size.md_default_line_height * 2), minWidth: px(20), // fix for edge browser. buttons are cut off in small windows otherwise fontSize: a.fontSize, "white-space": "normal", diff --git a/src/subscription/SimplifiedCreditCardInput.ts b/src/subscription/SimplifiedCreditCardInput.ts index 27f660d80c7..fffc6466415 100644 --- a/src/subscription/SimplifiedCreditCardInput.ts +++ b/src/subscription/SimplifiedCreditCardInput.ts @@ -1,5 +1,5 @@ import m, { Children, Component, Vnode } from "mithril" -import { Autocomplete, TextField } from "../gui/base/TextField.js" +import { Autocomplete, BorderTextField } from "../gui/base/BorderTextField.js" import { SimplifiedCreditCardViewModel } from "./SimplifiedCreditCardInputModel.js" import { lang, TranslationKey } from "../misc/LanguageViewModel.js" import { Stage } from "@tutao/tutanota-usagetests" @@ -40,7 +40,7 @@ export class SimplifiedCreditCardInput implements Component this.renderCcNumberHelpLabel(viewModel), value: viewModel.creditCardNumber, @@ -52,7 +52,7 @@ export class SimplifiedCreditCardInput implements Component (this.ccNumberDom = dom), }), - m(TextField, { + m(BorderTextField, { label: "creditCardExpirationDateWithFormat_label", value: viewModel.expirationDate, // we only show the hint if the field is not empty and not selected to avoid showing errors while the user is typing. @@ -65,7 +65,7 @@ export class SimplifiedCreditCardInput implements Component (this.expDateDom = dom), autocompleteAs: Autocomplete.ccExp, }), - m(TextField, { + m(BorderTextField, { label: () => viewModel.getCvvLabel(), value: viewModel.cvv, helpLabel: () => this.renderCvvNumberHelpLabel(viewModel), From a0cd59a95b6ea36cf7d205042f192630a6bfcdf3 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Thu, 15 Feb 2024 16:24:22 +0100 Subject: [PATCH 19/28] We don't need the labelBgColorOverwrite anymore, we now use a blur backdrop. The RecoverLoginDialog needs the overwrite, because it uses 'will-change: opacity' in css which blocks the blur backdrop --- src/gui/base/BorderTextField.ts | 18 +++++++++--------- src/login/LoginForm.ts | 3 --- src/login/recover/RecoverLoginDialog.ts | 8 ++++++-- src/settings/PasswordForm.ts | 5 +++++ 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index 4b3feb49f5f..cc70bad37b2 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -1,7 +1,7 @@ import m, { Children, ClassComponent, CVnode } from "mithril" import { px, size } from "../size" import { DefaultAnimationTime } from "../animation/Animations" -import { getElevatedBackground, getNavButtonIconBackground, getNavigationMenuBg, theme } from "../theme" +import { theme } from "../theme" import type { TranslationKey } from "../../misc/LanguageViewModel" import { lang } from "../../misc/LanguageViewModel" import type { lazy } from "@tutao/tutanota-utils" @@ -37,7 +37,7 @@ export type BorderTextFieldAttrs = { min?: number max?: number labelBgColorOverwrite?: string - // overwrites the default color `getElevatedBackground()` in order to display the correct color when not on a elevated background (-> dark mode LoginForm.ts) + // overwrites the bg color of label, only in use to fix recovery dialog -> not working because the animation uses 'will-change: opacity' Animations.ts:327 areaTextFieldLines?: number } @@ -105,7 +105,9 @@ export class BorderTextField implements ClassComponent { ? { maxWidth: px(maxWidth), } - : {}, + : { + "margin-bottom": "16px", + }, }, [ m( @@ -116,14 +118,15 @@ export class BorderTextField implements ClassComponent { this._domLabel = vnode.dom as HTMLElement }, style: { - fontSize: `${size.font_size_base}px`, + fontSize: px(size.font_size_base), //`${this.active || vnode.attrs.value ? size.font_size_small : size.font_size_base}px`, transform: `translateY(-${this.active || vnode.attrs.value ? 30 : 0}px)`, - transition: `transform ${labelTransitionSpeed}ms`, + transition: `transform ${labelTransitionSpeed}ms`, // , font-size ${labelTransitionSpeed / 2}ms margin: "11px 10px", padding: "6px", lineHeight: px(size.md_default_line_height), "font-style": "normal", - background: vnode.attrs.labelBgColorOverwrite || getElevatedBackground(), + "background-color": a.labelBgColorOverwrite, + "backdrop-filter": "blur(100px)", }, }, lang.getMaybeLazy(a.label), @@ -187,9 +190,6 @@ export class BorderTextField implements ClassComponent { onclick: (e: MouseEvent) => { e.stopPropagation() }, - style: { - margin: `${size.md_supporting_text_margin}px ${size.md_default_margin}px 0px `, - }, }, a.helpLabel(), ) diff --git a/src/login/LoginForm.ts b/src/login/LoginForm.ts index 1aeeea863d1..eb345a87785 100644 --- a/src/login/LoginForm.ts +++ b/src/login/LoginForm.ts @@ -10,7 +10,6 @@ import { isApp, isDesktop, isOfflineStorageAvailable } from "../api/common/Env" import { getWhitelabelCustomizations } from "../misc/WhitelabelCustomizations.js" import { BootstrapFeatureType } from "../api/common/TutanotaConstants.js" import { ACTIVATED_MIGRATION, isLegacyDomain } from "./LoginViewModel.js" -import { getNavigationMenuBg, theme } from "../gui/theme.js" import { LoginButton } from "../gui/base/buttons/LoginButton.js" export type LoginFormAttrs = { @@ -84,7 +83,6 @@ export class LoginForm implements Component { oninput: a.mailAddress, type: BorderTextFieldType.Email, autocompleteAs: Autocomplete.email, - labelBgColorOverwrite: theme.themeId == "dark" ? getNavigationMenuBg() : undefined, onDomInputCreated: (dom) => { this.mailAddressBorderTextField = dom if (!client.isMobileDevice()) { @@ -101,7 +99,6 @@ export class LoginForm implements Component { oninput: a.password, type: BorderTextFieldType.Password, autocompleteAs: Autocomplete.currentPassword, - labelBgColorOverwrite: theme.themeId == "dark" ? getNavigationMenuBg() : undefined, onDomInputCreated: (dom) => (this.passwordBorderTextField = dom), }), ), diff --git a/src/login/recover/RecoverLoginDialog.ts b/src/login/recover/RecoverLoginDialog.ts index a9e4bd5947d..6ff9c2b271d 100644 --- a/src/login/recover/RecoverLoginDialog.ts +++ b/src/login/recover/RecoverLoginDialog.ts @@ -9,7 +9,6 @@ import { lang } from "../../misc/LanguageViewModel" import { PasswordForm, PasswordModel } from "../../settings/PasswordForm" import { Icons } from "../../gui/base/icons/Icons" import { Dialog, DialogType } from "../../gui/base/Dialog" -import { HtmlEditor, HtmlEditorMode } from "../../gui/editor/HtmlEditor" import { client } from "../../misc/ClientDetector" import { CancelledError } from "../../api/common/error/CancelledError" import { locator } from "../../api/main/MainLocator" @@ -18,6 +17,7 @@ import { assertMainOrNode } from "../../api/common/Env" import { createDropdown, DropdownButtonAttrs } from "../../gui/base/Dropdown.js" import { IconButton, IconButtonAttrs } from "../../gui/base/IconButton.js" import { ButtonSize } from "../../gui/base/ButtonSize.js" +import { theme } from "../../gui/theme" assertMainOrNode() export type ResetAction = "password" | "secondFactor" @@ -66,12 +66,14 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di value: emailAddressStream(), autocompleteAs: Autocomplete.email, oninput: emailAddressStream, + labelBgColorOverwrite: theme.elevated_bg, }), m(BorderTextField, { label: "recoveryCode_label", value: recoveryCodeStream(), oninput: recoveryCodeStream, type: BorderTextFieldType.Area, + labelBgColorOverwrite: theme.elevated_bg, }), m(BorderTextField, { label: "action_label", @@ -79,17 +81,19 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di oninput: selectedValueLabelStream, injectionsRight: () => m(IconButton, resetActionButtonAttrs), isReadOnly: true, + labelBgColorOverwrite: theme.elevated_bg, }), selectedAction() == null ? null : selectedAction() === "password" - ? m(PasswordForm, { model: passwordModel }) + ? m(PasswordForm, { model: passwordModel, labelBgColorOverwrite: theme.elevated_bg }) : m(BorderTextField, { label: "password_label", type: BorderTextFieldType.Password, value: passwordValueStream(), autocompleteAs: Autocomplete.currentPassword, oninput: passwordValueStream, + labelBgColorOverwrite: theme.elevated_bg, }), ] }, diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index 2b3d9956f0f..f6aac9f285d 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -23,6 +23,8 @@ assertMainOrNode() export interface PasswordFormAttrs { model: PasswordModel passwordInfoKey?: TranslationKey + labelBgColorOverwrite?: string + // overwrites the bg color of label, only in use to fix recovery dialog -> not working because the animation uses 'will-change: opacity' Animations.ts:327 } export interface PasswordModelConfig { @@ -279,6 +281,7 @@ export class PasswordForm implements Component { fontSize: px(size.font_size_smaller), type: attrs.model.isPasswordRevealed(PasswordFieldType.Old) ? BorderTextFieldType.Text : BorderTextFieldType.Password, injectionsRight: () => this.renderRevealIcon(attrs, PasswordFieldType.Old), + labelBgColorOverwrite: attrs.labelBgColorOverwrite, }) : null, m(BorderTextField, { @@ -305,6 +308,7 @@ export class PasswordForm implements Component { fontSize: px(size.font_size_smaller), type: attrs.model.isPasswordRevealed(PasswordFieldType.New) ? BorderTextFieldType.Text : BorderTextFieldType.Password, injectionsRight: () => this.renderRevealIcon(attrs, PasswordFieldType.New), + labelBgColorOverwrite: attrs.labelBgColorOverwrite, }), attrs.model.config.hideConfirmation ? null @@ -321,6 +325,7 @@ export class PasswordForm implements Component { fontSize: px(size.font_size_smaller), type: attrs.model.isPasswordRevealed(PasswordFieldType.Confirm) ? BorderTextFieldType.Text : BorderTextFieldType.Password, injectionsRight: () => this.renderRevealIcon(attrs, PasswordFieldType.Confirm), + labelBgColorOverwrite: attrs.labelBgColorOverwrite, }), attrs.passwordInfoKey ? m(".small.mt-s", lang.get(attrs.passwordInfoKey)) : null, ], From 4e5d8ff4f5b058afa75b186877896d6abfbd48c3 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Thu, 15 Feb 2024 16:57:08 +0100 Subject: [PATCH 20/28] Removed unused properties --- src/gui/base/BorderTextField.ts | 5 ----- src/settings/PasswordForm.ts | 2 -- 2 files changed, 7 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index cc70bad37b2..b7f8ed34582 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -77,7 +77,6 @@ export class BorderTextField implements ClassComponent { onblur: EventListener | null = null domInput!: HTMLInputElement _domWrapper!: HTMLElement - setValue!: (value: string) => void private _domLabel!: HTMLElement private _domInputWrapper!: HTMLElement private _didAutofill!: boolean @@ -356,10 +355,6 @@ export class BorderTextField implements ClassComponent { this.domInput.style.height = px(this.domInput.scrollHeight) a.oninput && a.oninput(this.domInput.value, this.domInput) }, - setValue: (value: string) => { - a.value = value - this.domInput.value = value - }, onupdate: () => { // only change the value if the value has changed otherwise the cursor in Safari and in the iOS App cannot be positioned. if (this.domInput.value !== a.value) { diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index f6aac9f285d..3cbcd1eab52 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -22,7 +22,6 @@ assertMainOrNode() export interface PasswordFormAttrs { model: PasswordModel - passwordInfoKey?: TranslationKey labelBgColorOverwrite?: string // overwrites the bg color of label, only in use to fix recovery dialog -> not working because the animation uses 'will-change: opacity' Animations.ts:327 } @@ -327,7 +326,6 @@ export class PasswordForm implements Component { injectionsRight: () => this.renderRevealIcon(attrs, PasswordFieldType.Confirm), labelBgColorOverwrite: attrs.labelBgColorOverwrite, }), - attrs.passwordInfoKey ? m(".small.mt-s", lang.get(attrs.passwordInfoKey)) : null, ], ) } From 2c35881006fe18a5f3e931f24efa10c3d64c42cc Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Thu, 15 Feb 2024 17:05:38 +0100 Subject: [PATCH 21/28] Get margin from sizes --- src/settings/PasswordForm.ts | 46 +++++++++++++++++---------- src/settings/SelectMailAddressForm.ts | 4 +-- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index 3cbcd1eab52..074f3b368cf 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -287,21 +287,31 @@ export class PasswordForm implements Component { label: "newPassword_label", value: attrs.model.getNewPassword(), helpLabel: () => - m(".flex.col.mt-xs", { style: { margin: "4px 16px 8px 16px" } }, [ - m(".flex.items-center", [ - m( - ".mr-s", - { style: { width: "100%" } }, - m(CompletenessIndicator, { - width: "100%", - passwordColorScale: true, - percentageCompleted: scaleToVisualPasswordStrength(attrs.model.getPasswordStrength()), - }), - ), - m(StatusField, { status: attrs.model.getNewPasswordStatus(), style: { "min-width": "max-content" } }), - ]), - this.renderPasswordGeneratorHelp(attrs), - ]), + m( + ".flex.col.mt-xs", + { + style: { + margin: `${size.md_default_margin / 4}px ${size.md_default_margin}px ${size.md_default_margin / 2}px ${ + size.md_default_margin + }px`, + }, + }, + [ + m(".flex.items-center", [ + m( + ".mr-s", + { style: { width: "100%" } }, + m(CompletenessIndicator, { + width: "100%", + passwordColorScale: true, + percentageCompleted: scaleToVisualPasswordStrength(attrs.model.getPasswordStrength()), + }), + ), + m(StatusField, { status: attrs.model.getNewPasswordStatus(), style: { "min-width": "max-content" } }), + ]), + this.renderPasswordGeneratorHelp(attrs), + ], + ), oninput: (input) => attrs.model.setNewPassword(input), autocompleteAs: Autocomplete.newPassword, fontSize: px(size.font_size_smaller), @@ -318,7 +328,11 @@ export class PasswordForm implements Component { helpLabel: () => m(StatusField, { status: attrs.model.getRepeatedPasswordStatus(), - style: { margin: "4px 16px 8px 16px" }, + style: { + margin: `${size.md_default_margin / 4}px ${size.md_default_margin}px ${size.md_default_margin / 2}px ${ + size.md_default_margin + }px`, + }, }), oninput: (input) => attrs.model.setRepeatedPassword(input), fontSize: px(size.font_size_smaller), diff --git a/src/settings/SelectMailAddressForm.ts b/src/settings/SelectMailAddressForm.ts index 7d4ccd03ca9..13af57ba5cc 100644 --- a/src/settings/SelectMailAddressForm.ts +++ b/src/settings/SelectMailAddressForm.ts @@ -122,8 +122,8 @@ export class SelectMailAddressForm implements Component Date: Fri, 16 Feb 2024 10:15:35 +0100 Subject: [PATCH 22/28] The BorderTextField now uses the elevated background as default, the overwrite is on the dark mode login page necessary. BorderTextField is now applied to the invoice address dialog. --- src/gui/base/BorderTextField.ts | 6 ++--- src/login/LoginForm.ts | 3 +++ src/login/recover/RecoverLoginDialog.ts | 6 +---- src/settings/PasswordForm.ts | 2 +- src/subscription/InvoiceDataInput.ts | 31 ++++++++++++------------- 5 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index b7f8ed34582..474fc516f89 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -37,7 +37,7 @@ export type BorderTextFieldAttrs = { min?: number max?: number labelBgColorOverwrite?: string - // overwrites the bg color of label, only in use to fix recovery dialog -> not working because the animation uses 'will-change: opacity' Animations.ts:327 + // overwrites the bg color of label, only in use to fix login in dark mode areaTextFieldLines?: number } @@ -124,8 +124,8 @@ export class BorderTextField implements ClassComponent { padding: "6px", lineHeight: px(size.md_default_line_height), "font-style": "normal", - "background-color": a.labelBgColorOverwrite, - "backdrop-filter": "blur(100px)", + "background-color": a.labelBgColorOverwrite || theme.elevated_bg, + //"backdrop-filter": "blur(100px)", }, }, lang.getMaybeLazy(a.label), diff --git a/src/login/LoginForm.ts b/src/login/LoginForm.ts index eb345a87785..f0a23407858 100644 --- a/src/login/LoginForm.ts +++ b/src/login/LoginForm.ts @@ -11,6 +11,7 @@ import { getWhitelabelCustomizations } from "../misc/WhitelabelCustomizations.js import { BootstrapFeatureType } from "../api/common/TutanotaConstants.js" import { ACTIVATED_MIGRATION, isLegacyDomain } from "./LoginViewModel.js" import { LoginButton } from "../gui/base/buttons/LoginButton.js" +import { getNavigationMenuBg, theme } from "../gui/theme" export type LoginFormAttrs = { onSubmit: (username: string, password: string) => unknown @@ -89,6 +90,7 @@ export class LoginForm implements Component { dom.focus() // have email address auto-focus so the user can immediately type their username (unless on mobile) } }, + labelBgColorOverwrite: theme.themeId == "dark" ? getNavigationMenuBg() : undefined, }), ), m( @@ -100,6 +102,7 @@ export class LoginForm implements Component { type: BorderTextFieldType.Password, autocompleteAs: Autocomplete.currentPassword, onDomInputCreated: (dom) => (this.passwordBorderTextField = dom), + labelBgColorOverwrite: theme.themeId == "dark" ? getNavigationMenuBg() : undefined, }), ), a.savePassword && !this._passwordDisabled() diff --git a/src/login/recover/RecoverLoginDialog.ts b/src/login/recover/RecoverLoginDialog.ts index 6ff9c2b271d..76e4b35dee2 100644 --- a/src/login/recover/RecoverLoginDialog.ts +++ b/src/login/recover/RecoverLoginDialog.ts @@ -66,14 +66,12 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di value: emailAddressStream(), autocompleteAs: Autocomplete.email, oninput: emailAddressStream, - labelBgColorOverwrite: theme.elevated_bg, }), m(BorderTextField, { label: "recoveryCode_label", value: recoveryCodeStream(), oninput: recoveryCodeStream, type: BorderTextFieldType.Area, - labelBgColorOverwrite: theme.elevated_bg, }), m(BorderTextField, { label: "action_label", @@ -81,19 +79,17 @@ export function show(mailAddress?: string | null, resetAction?: ResetAction): Di oninput: selectedValueLabelStream, injectionsRight: () => m(IconButton, resetActionButtonAttrs), isReadOnly: true, - labelBgColorOverwrite: theme.elevated_bg, }), selectedAction() == null ? null : selectedAction() === "password" - ? m(PasswordForm, { model: passwordModel, labelBgColorOverwrite: theme.elevated_bg }) + ? m(PasswordForm, { model: passwordModel }) : m(BorderTextField, { label: "password_label", type: BorderTextFieldType.Password, value: passwordValueStream(), autocompleteAs: Autocomplete.currentPassword, oninput: passwordValueStream, - labelBgColorOverwrite: theme.elevated_bg, }), ] }, diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index 074f3b368cf..edf91c8cec7 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -23,7 +23,7 @@ assertMainOrNode() export interface PasswordFormAttrs { model: PasswordModel labelBgColorOverwrite?: string - // overwrites the bg color of label, only in use to fix recovery dialog -> not working because the animation uses 'will-change: opacity' Animations.ts:327 + // overwrites the bg color of label, only in use to fix login in dark mode } export interface PasswordModelConfig { diff --git a/src/subscription/InvoiceDataInput.ts b/src/subscription/InvoiceDataInput.ts index 347867ca7d6..879ae1aaaed 100644 --- a/src/subscription/InvoiceDataInput.ts +++ b/src/subscription/InvoiceDataInput.ts @@ -3,10 +3,9 @@ import type { TranslationKey } from "../misc/LanguageViewModel" import { lang } from "../misc/LanguageViewModel" import type { Country } from "../api/common/CountryList" import { Countries, CountryType } from "../api/common/CountryList" -import { HtmlEditor, HtmlEditorMode } from "../gui/editor/HtmlEditor" import type { LocationServiceGetReturn } from "../api/entities/sys/TypeRefs.js" import { renderCountryDropdown } from "../gui/base/GuiUtils" -import { TextField } from "../gui/base/TextField.js" +import { BorderTextField, BorderTextFieldType } from "../gui/base/BorderTextField.js" import type { InvoiceData } from "../api/common/TutanotaConstants" import { LocationService } from "../api/entities/sys/Services" import { locator } from "../api/main/MainLocator" @@ -20,22 +19,14 @@ export enum InvoiceDataInputLocation { } export class InvoiceDataInput implements Component { - private readonly invoiceAddressComponent: HtmlEditor public readonly selectedCountry: Stream private vatNumber: string = "" private __paymentPaypalTest?: UsageTest + private invoiceAddressStream: stream constructor(private businessUse: boolean, invoiceData: InvoiceData, private readonly location = InvoiceDataInputLocation.Other) { this.__paymentPaypalTest = locator.usageTestController.getTest("payment.paypal") - - this.invoiceAddressComponent = new HtmlEditor() - .setMinHeight(120) - .showBorders() - .setPlaceholderId("invoiceAddress_label") - .setMode(HtmlEditorMode.HTML) - .setHtmlMonospace(false) - .setValue(invoiceData.invoiceAddress) - + this.invoiceAddressStream = stream(invoiceData.invoiceAddress) this.selectedCountry = stream(invoiceData.country) this.view = this.view.bind(this) @@ -46,7 +37,16 @@ export class InvoiceDataInput implements Component { return [ this.businessUse || this.location !== InvoiceDataInputLocation.InWizard ? m("", [ - m(".pt", m(this.invoiceAddressComponent)), + m( + ".pt", + m(BorderTextField, { + value: this.invoiceAddressStream(), + oninput: this.invoiceAddressStream, + areaTextFieldLines: 5, + type: BorderTextFieldType.Area, + label: "invoiceAddress_label", + }), + ), m(".small", lang.get(this.businessUse ? "invoiceAddressInfoBusiness_msg" : "invoiceAddressInfoPrivate_msg")), ]) : null, @@ -56,7 +56,7 @@ export class InvoiceDataInput implements Component { helpLabel: () => lang.get("invoiceCountryInfoConsumer_msg"), }), this.isVatIdFieldVisible() - ? m(TextField, { + ? m(BorderTextField, { label: "invoiceVatIdNo_label", value: this.vatNumber, oninput: (value) => (this.vatNumber = value), @@ -117,8 +117,7 @@ export class InvoiceDataInput implements Component { } private getAddress(): string { - return this.invoiceAddressComponent - .getValue() + return this.invoiceAddressStream() .split("\n") .filter((line) => line.trim().length > 0) .join("\n") From 7c06661adbed611b02c643e92a5ae9b2ffff597b Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Fri, 16 Feb 2024 10:51:06 +0100 Subject: [PATCH 23/28] WIP: Change/reserve distance between inputs --- src/gui/base/BorderTextField.ts | 2 +- src/settings/PasswordForm.ts | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index 474fc516f89..0a226e5f36e 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -105,7 +105,7 @@ export class BorderTextField implements ClassComponent { maxWidth: px(maxWidth), } : { - "margin-bottom": "16px", + "margin-bottom": vnode.attrs.helpLabel ? "" : "21px", }, }, [ diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index edf91c8cec7..2f6265b7d96 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -309,7 +309,6 @@ export class PasswordForm implements Component { ), m(StatusField, { status: attrs.model.getNewPasswordStatus(), style: { "min-width": "max-content" } }), ]), - this.renderPasswordGeneratorHelp(attrs), ], ), oninput: (input) => attrs.model.setNewPassword(input), @@ -344,22 +343,6 @@ export class PasswordForm implements Component { ) } - private renderPasswordGeneratorHelp(attrs: PasswordFormAttrs): Children { - return m("", [ - m( - ".b.mr-xs.hover.click.darkest-hover.mt-xs", - { - style: { display: "inline-block", color: theme.navigation_button_selected }, - onclick: async () => { - attrs.model.setNewPassword(await showPasswordGeneratorDialog()) - m.redraw() - }, - }, - lang.get("generatePassphrase_action"), - ), - ]) - } - private renderRevealIcon(attrs: PasswordFormAttrs, passwordType: PasswordFieldType): Children { return m(ToggleButton, { title: "revealPassword_action", From f8c24c9c4b2a66f02a8256319a0a8942f0a5d207 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Fri, 16 Feb 2024 15:25:38 +0100 Subject: [PATCH 24/28] Change indication of wrong/correct password on signup --- src/gui/base/Button.ts | 22 ++++++++++++ src/settings/SelectMailAddressForm.ts | 48 ++++++++++++++++----------- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/gui/base/Button.ts b/src/gui/base/Button.ts index e65c82c977c..912fb69ef10 100644 --- a/src/gui/base/Button.ts +++ b/src/gui/base/Button.ts @@ -20,6 +20,8 @@ export const enum ButtonColor { Content = "content", Elevated = "elevated", DrawerNav = "drawernav", + Success = "success", + Error = "error", } export function getColors(buttonColors: ButtonColor | null | undefined): { @@ -61,6 +63,26 @@ export function getColors(buttonColors: ButtonColor | null | undefined): { border: getElevatedBackground(), } + case ButtonColor.Success: + return { + button: "green", // fixme: use color from theme + button_selected: theme.content_button_selected, + button_icon_bg: getContentButtonIconBackground(), + icon: theme.content_button_icon, + icon_selected: theme.content_button_icon_selected, + border: getElevatedBackground(), + } + + case ButtonColor.Error: + return { + button: "red", // fixme: use color from theme + button_selected: theme.content_button_selected, + button_icon_bg: getContentButtonIconBackground(), + icon: theme.content_button_icon, + icon_selected: theme.content_button_icon_selected, + border: getElevatedBackground(), + } + case ButtonColor.Content: default: return { diff --git a/src/settings/SelectMailAddressForm.ts b/src/settings/SelectMailAddressForm.ts index 13af57ba5cc..21d26282412 100644 --- a/src/settings/SelectMailAddressForm.ts +++ b/src/settings/SelectMailAddressForm.ts @@ -1,10 +1,9 @@ -import m, { Children, Component, Vnode } from "mithril" +import m, { Child, Children, Component, Vnode } from "mithril" import type { TranslationKey } from "../misc/LanguageViewModel" import { lang } from "../misc/LanguageViewModel" import { isMailAddress } from "../misc/FormatValidator" import { AccessDeactivatedError } from "../api/common/error/RestError" import { formatMailAddressFromParts } from "../misc/Formatter" -import { Icon } from "../gui/base/Icon" import { locator } from "../api/main/MainLocator" import { assertMainOrNode } from "../api/common/Env" import { px, size } from "../gui/size.js" @@ -15,11 +14,12 @@ import { ButtonSize } from "../gui/base/ButtonSize.js" import { EmailDomainData } from "./mailaddress/MailAddressesUtils.js" import { BootIcons } from "../gui/base/icons/BootIcons.js" import { isTutanotaMailAddress } from "../api/common/mail/CommonMailUtils.js" +import { Icon } from "../gui/base/Icon" +import { ButtonColor } from "../gui/base/Button" +import { Icons } from "../gui/base/icons/Icons" assertMainOrNode() -const VALID_MESSAGE_ID = "mailAddressAvailable_msg" - export interface SelectMailAddressFormAttrs { selectedDomain: EmailDomainData availableDomains: readonly EmailDomainData[] @@ -37,7 +37,7 @@ export interface ValidationResult { export class SelectMailAddressForm implements Component { private username: string - private messageId: TranslationKey | null + private messageId?: TranslationKey | null private checkAddressTimeout: TimeoutID | null private isVerificationBusy: boolean private lastAttrs: SelectMailAddressFormAttrs @@ -67,7 +67,6 @@ export class SelectMailAddressForm implements Component { originalCallback(event, dom) this.username = "" - this.messageId = "mailAddressNeutral_msg" } } @@ -76,7 +75,6 @@ export class SelectMailAddressForm implements Component this.addressHelpLabel(), fontSize: px(size.font_size_smaller), oninput: (value) => { this.username = value @@ -112,6 +110,29 @@ export class SelectMailAddressForm implements Component {}, + }) + : this.messageId + ? m(IconButton, { + title: this.messageId, + icon: this.messageId == "mailAddressNeutral_msg" ? Icons.Warning : Icons.CircleReject, + size: ButtonSize.Compact, + colors: ButtonColor.Error, // fixme: no don't display red when no input + click: () => {}, + }) + : m(IconButton, { + title: () => "", + icon: Icons.Checkmark, + size: ButtonSize.Compact, + colors: ButtonColor.Success, + click: () => {}, + }), ], }) } @@ -120,19 +141,6 @@ export class SelectMailAddressForm implements Component domainData.domain, From dbfe0f5caf8169a3a0d0826310d1c552eaf8e942 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Fri, 16 Feb 2024 16:09:06 +0100 Subject: [PATCH 25/28] Changed valid indication position. Removed help label from password fields. Added the passphrase generator as button --- src/gui/CompletenessIndicator.ts | 15 ++++++------- src/settings/PasswordForm.ts | 36 +++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/gui/CompletenessIndicator.ts b/src/gui/CompletenessIndicator.ts index e876cd00aef..c1aa76f57fc 100644 --- a/src/gui/CompletenessIndicator.ts +++ b/src/gui/CompletenessIndicator.ts @@ -16,7 +16,7 @@ export class CompletenessIndicator implements Component { percentageCompleted: scaleToVisualPasswordStrength(attrs.model.getPasswordStrength()), }), ), - m(StatusField, { status: attrs.model.getNewPasswordStatus(), style: { "min-width": "max-content" } }), ]), ], ), @@ -315,7 +316,18 @@ export class PasswordForm implements Component { autocompleteAs: Autocomplete.newPassword, fontSize: px(size.font_size_smaller), type: attrs.model.isPasswordRevealed(PasswordFieldType.New) ? BorderTextFieldType.Text : BorderTextFieldType.Password, - injectionsRight: () => this.renderRevealIcon(attrs, PasswordFieldType.New), + injectionsRight: () => [ + m(IconButton, { + icon: Icons.More, + title: "generatePassphrase_action", + click: async () => { + attrs.model.setNewPassword(await showPasswordGeneratorDialog()) + m.redraw() + }, + size: ButtonSize.Compact, + }), + this.renderRevealIcon(attrs, PasswordFieldType.New), + ], labelBgColorOverwrite: attrs.labelBgColorOverwrite, }), attrs.model.config.hideConfirmation @@ -324,19 +336,19 @@ export class PasswordForm implements Component { label: "repeatedPassword_label", value: attrs.model.getRepeatedPassword(), autocompleteAs: Autocomplete.newPassword, - helpLabel: () => - m(StatusField, { - status: attrs.model.getRepeatedPasswordStatus(), - style: { - margin: `${size.md_default_margin / 4}px ${size.md_default_margin}px ${size.md_default_margin / 2}px ${ - size.md_default_margin - }px`, - }, - }), oninput: (input) => attrs.model.setRepeatedPassword(input), fontSize: px(size.font_size_smaller), type: attrs.model.isPasswordRevealed(PasswordFieldType.Confirm) ? BorderTextFieldType.Text : BorderTextFieldType.Password, - injectionsRight: () => this.renderRevealIcon(attrs, PasswordFieldType.Confirm), + injectionsRight: () => [ + m(IconButton, { + icon: attrs.model.getRepeatedPasswordStatus().type == "valid" ? Icons.Checkmark : Icons.CircleReject, + title: attrs.model.getRepeatedPasswordStatus().text, + click: () => {}, + colors: attrs.model.getRepeatedPasswordStatus().type == "valid" ? ButtonColor.Success : ButtonColor.Error, + size: ButtonSize.Compact, + }), + this.renderRevealIcon(attrs, PasswordFieldType.Confirm), + ], labelBgColorOverwrite: attrs.labelBgColorOverwrite, }), ], From 526683034922fe7ada377b5b902a219d9e314bf5 Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Fri, 16 Feb 2024 16:39:13 +0100 Subject: [PATCH 26/28] Spacing of the help label is now done by the BorderTextField --- src/gui/base/BorderTextField.ts | 7 +++++-- src/gui/base/StatusField.ts | 3 +-- src/settings/PasswordForm.ts | 30 +++++++----------------------- 3 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/gui/base/BorderTextField.ts b/src/gui/base/BorderTextField.ts index 0a226e5f36e..957b932bd7e 100644 --- a/src/gui/base/BorderTextField.ts +++ b/src/gui/base/BorderTextField.ts @@ -170,7 +170,7 @@ export class BorderTextField implements ClassComponent { minHeight: px(size.md_default_line_height), lineHeight: px(size.md_default_line_height), // 13px because 56 (md field height) - 30 (svg size) = 26 -> 26/2 = 13 - margin: `13px ${size.md_default_margin}px`, + margin: `13px ${size.md_default_margin - 6}px 13px ${size.md_default_margin}px`, }, }, a.injectionsRight(), @@ -184,11 +184,14 @@ export class BorderTextField implements ClassComponent { ), a.helpLabel ? m( - "small.noselect", + "div.noselect", { onclick: (e: MouseEvent) => { e.stopPropagation() }, + style: { + padding: `${size.md_default_margin / 4}px ${size.md_default_margin}px 0px`, + }, }, a.helpLabel(), ) diff --git a/src/gui/base/StatusField.ts b/src/gui/base/StatusField.ts index cb118eb990f..0e3958b8a28 100644 --- a/src/gui/base/StatusField.ts +++ b/src/gui/base/StatusField.ts @@ -7,14 +7,13 @@ assertMainOrNode() export type StatusType = "neutral" | "valid" | "invalid" export type StatusFieldAttrs = { status: Status - style?: Record } export class StatusField implements Component { view(vnode: Vnode): Children { const { status } = vnode.attrs if (!status) return null - return m("", vnode.attrs.style ? { style: vnode.attrs.style } : {}, lang.get(status.text)) + return m("", lang.get(status.text)) } } diff --git a/src/settings/PasswordForm.ts b/src/settings/PasswordForm.ts index c36b935956a..36a38ea0a50 100644 --- a/src/settings/PasswordForm.ts +++ b/src/settings/PasswordForm.ts @@ -289,29 +289,13 @@ export class PasswordForm implements Component { label: "newPassword_label", value: attrs.model.getNewPassword(), helpLabel: () => - m( - ".flex.col.mt-xs", - { - style: { - margin: `${size.md_default_margin / 4}px ${size.md_default_margin}px ${size.md_default_margin / 2}px ${ - size.md_default_margin - }px`, - }, - }, - [ - m(".flex.items-center", [ - m( - ".mr-s", - { style: { width: "100%" } }, - m(CompletenessIndicator, { - width: "100%", - passwordColorScale: true, - percentageCompleted: scaleToVisualPasswordStrength(attrs.model.getPasswordStrength()), - }), - ), - ]), - ], - ), + m(".flex.col.mt-xs", [ + m(CompletenessIndicator, { + width: "100%", + passwordColorScale: true, + percentageCompleted: scaleToVisualPasswordStrength(attrs.model.getPasswordStrength()), + }), + ]), oninput: (input) => attrs.model.setNewPassword(input), autocompleteAs: Autocomplete.newPassword, fontSize: px(size.font_size_smaller), From acb840f76c1e0ac0fd8ff0a8de39628ecc53955a Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Fri, 16 Feb 2024 16:50:11 +0100 Subject: [PATCH 27/28] Change the checkboxes to agree to the TOS --- src/subscription/SignupForm.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/subscription/SignupForm.ts b/src/subscription/SignupForm.ts index e272376fc46..6d25f037a64 100644 --- a/src/subscription/SignupForm.ts +++ b/src/subscription/SignupForm.ts @@ -48,6 +48,7 @@ export type SignupFormAttrs = { export class SignupForm implements Component { private readonly passwordModel: PasswordModel private readonly _confirmTerms: Stream + private readonly _confirmPrivacy: Stream private readonly _confirmAge: Stream private readonly _code: Stream private selectedDomain: EmailDomainData @@ -93,6 +94,7 @@ export class SignupForm implements Component { this.__signupPaidTest = locator.usageTestController.getTest("signup.paid") this._confirmTerms = stream(false) + this._confirmPrivacy = stream(false) this._confirmAge = stream(false) this._code = stream("") this._isMailVerificationBusy = false @@ -132,10 +134,15 @@ export class SignupForm implements Component { }, } const confirmTermsCheckBoxAttrs: CheckboxAttrs = { - label: renderTermsLabel, + label: () => renderTermsAndConditionsButton(TermsSection.Terms, CURRENT_TERMS_VERSION), checked: this._confirmTerms(), onChecked: this._confirmTerms, } + const confirmPrivacyPolicyCheckBoxAttrs: CheckboxAttrs = { + label: () => renderTermsAndConditionsButton(TermsSection.Privacy, CURRENT_PRIVACY_VERSION), + checked: this._confirmPrivacy(), + onChecked: this._confirmPrivacy, + } const confirmAgeCheckBoxAttrs: CheckboxAttrs = { label: () => lang.get("ageConfirmation_msg"), checked: this._confirmAge(), @@ -153,7 +160,9 @@ export class SignupForm implements Component { } const errorMessage = - this._mailAddressFormErrorId || this.passwordModel.getErrorMessageId() || (!this._confirmTerms() ? "termsAcceptedNeutral_msg" : null) + this._mailAddressFormErrorId || + this.passwordModel.getErrorMessageId() || + (!this._confirmTerms() || !this._confirmPrivacy() ? "termsAcceptedNeutral_msg" : null) if (errorMessage) { Dialog.message(errorMessage) @@ -201,7 +210,9 @@ export class SignupForm implements Component { label: "whitelabelRegistrationCode_label", }) : null, + m("", [lang.get("termsAndConditions_label")]), m(Checkbox, confirmTermsCheckBoxAttrs), + m(Checkbox, confirmPrivacyPolicyCheckBoxAttrs), m(Checkbox, confirmAgeCheckBoxAttrs), ], m( @@ -237,14 +248,6 @@ export class SignupForm implements Component { } } -function renderTermsLabel(): Children { - return [ - lang.get("termsAndConditions_label"), - m("div", renderTermsAndConditionsButton(TermsSection.Terms, CURRENT_TERMS_VERSION)), - m("div", renderTermsAndConditionsButton(TermsSection.Privacy, CURRENT_PRIVACY_VERSION)), - ] -} - /** * @return Signs the user up, if no captcha is needed or it has been solved correctly */ From fb883cc8d4f263059b6926b12b83e7d05018a8cb Mon Sep 17 00:00:00 2001 From: Bothim_TV Date: Fri, 16 Feb 2024 17:08:30 +0100 Subject: [PATCH 28/28] WIP: Add custom domain to selection --- src/api/common/TutanotaConstants.ts | 1 + src/settings/mailaddress/MailAddressesUtils.ts | 2 +- src/subscription/SignupForm.ts | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/api/common/TutanotaConstants.ts b/src/api/common/TutanotaConstants.ts index ee4a16e95fd..8069a2b1cf0 100644 --- a/src/api/common/TutanotaConstants.ts +++ b/src/api/common/TutanotaConstants.ts @@ -296,6 +296,7 @@ export const TUTANOTA_MAIL_ADDRESS_DOMAINS: ReadonlyArray = Object.freez "tutanota.com", "tutanota.de", "keemail.me", + "your-domain.com", ]) export const TUTANOTA_MAIL_ADDRESS_SIGNUP_DOMAINS = TUTANOTA_MAIL_ADDRESS_DOMAINS export const DEFAULT_PAID_MAIL_ADDRESS_SIGNUP_DOMAIN = "tuta.com" diff --git a/src/settings/mailaddress/MailAddressesUtils.ts b/src/settings/mailaddress/MailAddressesUtils.ts index 687c3ef3473..cefdb9fe5e9 100644 --- a/src/settings/mailaddress/MailAddressesUtils.ts +++ b/src/settings/mailaddress/MailAddressesUtils.ts @@ -23,5 +23,5 @@ export async function getAvailableDomains(logins: LoginController, onlyCustomDom } export function isPaidPlanDomain(domain: String): boolean { - return domain === "tuta.com" + return domain === "tuta.com" || domain === "your-domain.com" } diff --git a/src/subscription/SignupForm.ts b/src/subscription/SignupForm.ts index 6d25f037a64..8e7e7938896 100644 --- a/src/subscription/SignupForm.ts +++ b/src/subscription/SignupForm.ts @@ -108,7 +108,11 @@ export class SignupForm implements Component { selectedDomain: this.selectedDomain, onDomainChanged: (domain) => { if (!domain.isPaid || a.isPaidSubscription()) { - this.selectedDomain = domain + if (domain.domain == "your-domain.com") { + Dialog.message(() => `${lang.get("configureCustomDomainAfterSignup_msg") + faqCustomDomainLink}`) // fixme: faq link is not clickable! + } else { + this.selectedDomain = domain + } } else { Dialog.confirm(() => `${lang.get("paidEmailDomainSignup_msg")}\n${lang.get("changePaidPlan_msg")}`).then((confirmed) => { if (confirmed) {