From 3eb03c87169d44b5e3417fff234454205256872f Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Fri, 26 Feb 2021 13:03:10 +0100 Subject: [PATCH] Visibility toggle for password inputs Closes #3392 --- .../templates/headIncludeJavaScript.tpl | 4 +- ts/WoltLabSuite/Core/Bootstrap.ts | 2 + ts/WoltLabSuite/Core/Ui/Password.ts | 57 +++++++++++++++++++ .../install/files/acp/templates/header.tpl | 4 +- .../files/js/WoltLabSuite/Core/Bootstrap.js | 4 +- .../files/js/WoltLabSuite/Core/Ui/Password.js | 52 +++++++++++++++++ wcfsetup/install/lang/de.xml | 2 + wcfsetup/install/lang/en.xml | 2 + 8 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 ts/WoltLabSuite/Core/Ui/Password.ts create mode 100644 wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Password.js diff --git a/com.woltlab.wcf/templates/headIncludeJavaScript.tpl b/com.woltlab.wcf/templates/headIncludeJavaScript.tpl index 8a389eaac1..814fef7aae 100644 --- a/com.woltlab.wcf/templates/headIncludeJavaScript.tpl +++ b/com.woltlab.wcf/templates/headIncludeJavaScript.tpl @@ -130,7 +130,9 @@ window.addEventListener('pageshow', function(event) { 'wcf.date.datePicker.month': '{jslang}wcf.date.datePicker.month{/jslang}', 'wcf.date.datePicker.year': '{jslang}wcf.date.datePicker.year{/jslang}', 'wcf.date.datePicker.hour': '{jslang}wcf.date.datePicker.hour{/jslang}', - 'wcf.date.datePicker.minute': '{jslang}wcf.date.datePicker.minute{/jslang}' + 'wcf.date.datePicker.minute': '{jslang}wcf.date.datePicker.minute{/jslang}', + 'wcf.global.form.password.button.hide': '{jslang}wcf.global.form.password.button.hide{/jslang}', + 'wcf.global.form.password.button.show': '{jslang}wcf.global.form.password.button.show{/jslang}' {if MODULE_LIKE} ,'wcf.like.button.like': '{jslang}wcf.like.button.like{/jslang}', 'wcf.like.button.dislike': '{jslang}wcf.like.button.dislike{/jslang}', diff --git a/ts/WoltLabSuite/Core/Bootstrap.ts b/ts/WoltLabSuite/Core/Bootstrap.ts index 65521193f5..32a483c0e7 100644 --- a/ts/WoltLabSuite/Core/Bootstrap.ts +++ b/ts/WoltLabSuite/Core/Bootstrap.ts @@ -25,6 +25,7 @@ import * as UiPageAction from "./Ui/Page/Action"; import * as UiTabMenu from "./Ui/TabMenu"; import * as UiTooltip from "./Ui/Tooltip"; import * as UiPageJumpTo from "./Ui/Page/JumpTo"; +import * as UiPassword from "./Ui/Password"; // perfectScrollbar does not need to be bound anywhere, it just has to be loaded for WCF.js // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -89,6 +90,7 @@ export function setup(options: BoostrapOptions): void { UiTabMenu.setup(); UiDialog.setup(); UiTooltip.setup(); + UiPassword.setup(); // Convert forms with `method="get"` into `method="post"` document.querySelectorAll("form[method=get]").forEach((form: HTMLFormElement) => { diff --git a/ts/WoltLabSuite/Core/Ui/Password.ts b/ts/WoltLabSuite/Core/Ui/Password.ts new file mode 100644 index 0000000000..611f526737 --- /dev/null +++ b/ts/WoltLabSuite/Core/Ui/Password.ts @@ -0,0 +1,57 @@ +import DomChangeListener from "../Dom/Change/Listener"; +import * as Language from "../Language"; + +const _knownElements = new WeakSet(); + +export function setup(): void { + initElements(); + DomChangeListener.add("WoltLabSuite/Core/Ui/Password", () => initElements()); +} + +function initElements(): void { + document.querySelectorAll("input[type=password]").forEach((input: HTMLInputElement) => { + if (!_knownElements.has(input)) { + initElement(input); + } + }); +} + +function initElement(input: HTMLInputElement): void { + _knownElements.add(input); + + const inputAddon = document.createElement("div"); + inputAddon.classList.add("inputAddon"); + input.insertAdjacentElement("beforebegin", inputAddon); + inputAddon.appendChild(input); + + const button = document.createElement("span"); + button.title = Language.get("wcf.global.form.password.button.show"); + button.classList.add("button", "inputSuffix", "jsTooltip"); + button.setAttribute("role", "button"); + button.tabIndex = 0; + button.setAttribute("aria-hidden", "true"); + inputAddon.appendChild(button); + + const icon = document.createElement("span"); + icon.classList.add("icon", "icon16", "fa-eye-slash"); + button.appendChild(icon); + + button.addEventListener("click", () => { + toggle(input, button, icon); + }); + button.addEventListener("keydown", (event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + toggle(input, button, icon); + } + }); +} + +function toggle(input: HTMLInputElement, button: HTMLElement, icon: HTMLElement): void { + icon.classList.toggle("fa-eye"); + icon.classList.toggle("fa-eye-slash"); + button.dataset.tooltip = Language.get( + "wcf.global.form.password.button." + (input.type === "password" ? "hide" : "show"), + ); + input.type = input.type === "password" ? "text" : "password"; +} diff --git a/wcfsetup/install/files/acp/templates/header.tpl b/wcfsetup/install/files/acp/templates/header.tpl index 44de63ea21..1db2bd2ced 100644 --- a/wcfsetup/install/files/acp/templates/header.tpl +++ b/wcfsetup/install/files/acp/templates/header.tpl @@ -141,7 +141,9 @@ 'wcf.date.datePicker.month': '{jslang}wcf.date.datePicker.month{/jslang}', 'wcf.date.datePicker.year': '{jslang}wcf.date.datePicker.year{/jslang}', 'wcf.date.datePicker.hour': '{jslang}wcf.date.datePicker.hour{/jslang}', - 'wcf.date.datePicker.minute': '{jslang}wcf.date.datePicker.minute{/jslang}' + 'wcf.date.datePicker.minute': '{jslang}wcf.date.datePicker.minute{/jslang}', + 'wcf.global.form.password.button.hide': '{jslang}wcf.global.form.password.button.hide{/jslang}', + 'wcf.global.form.password.button.show': '{jslang}wcf.global.form.password.button.show{/jslang}' {event name='javascriptLanguageImport'} }); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Bootstrap.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Bootstrap.js index f1625ad2aa..54a855db54 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Bootstrap.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Bootstrap.js @@ -8,7 +8,7 @@ * @license GNU Lesser General Public License * @module WoltLabSuite/Core/Bootstrap */ -define(["require", "exports", "tslib", "./Core", "./Date/Picker", "./Date/Time/Relative", "./Devtools", "./Dom/Change/Listener", "./Environment", "./Event/Handler", "./Language", "./StringUtil", "./Ui/Dialog", "./Ui/Dropdown/Simple", "./Ui/Mobile", "./Ui/Page/Action", "./Ui/TabMenu", "./Ui/Tooltip", "./Ui/Page/JumpTo"], function (require, exports, tslib_1, Core, Picker_1, DateTimeRelative, Devtools_1, Listener_1, Environment, EventHandler, Language, StringUtil, Dialog_1, Simple_1, UiMobile, UiPageAction, UiTabMenu, UiTooltip, UiPageJumpTo) { +define(["require", "exports", "tslib", "./Core", "./Date/Picker", "./Date/Time/Relative", "./Devtools", "./Dom/Change/Listener", "./Environment", "./Event/Handler", "./Language", "./StringUtil", "./Ui/Dialog", "./Ui/Dropdown/Simple", "./Ui/Mobile", "./Ui/Page/Action", "./Ui/TabMenu", "./Ui/Tooltip", "./Ui/Page/JumpTo", "./Ui/Password"], function (require, exports, tslib_1, Core, Picker_1, DateTimeRelative, Devtools_1, Listener_1, Environment, EventHandler, Language, StringUtil, Dialog_1, Simple_1, UiMobile, UiPageAction, UiTabMenu, UiTooltip, UiPageJumpTo, UiPassword) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.setup = void 0; @@ -28,6 +28,7 @@ define(["require", "exports", "tslib", "./Core", "./Date/Picker", "./Date/Time/R UiTabMenu = tslib_1.__importStar(UiTabMenu); UiTooltip = tslib_1.__importStar(UiTooltip); UiPageJumpTo = tslib_1.__importStar(UiPageJumpTo); + UiPassword = tslib_1.__importStar(UiPassword); // non strict equals by intent if (window.WCF == null) { window.WCF = {}; @@ -74,6 +75,7 @@ define(["require", "exports", "tslib", "./Core", "./Date/Picker", "./Date/Time/R UiTabMenu.setup(); Dialog_1.default.setup(); UiTooltip.setup(); + UiPassword.setup(); // Convert forms with `method="get"` into `method="post"` document.querySelectorAll("form[method=get]").forEach((form) => { form.method = "post"; diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Password.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Password.js new file mode 100644 index 0000000000..59b8136761 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Password.js @@ -0,0 +1,52 @@ +define(["require", "exports", "tslib", "../Dom/Change/Listener", "../Language"], function (require, exports, tslib_1, Listener_1, Language) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.setup = void 0; + Listener_1 = tslib_1.__importDefault(Listener_1); + Language = tslib_1.__importStar(Language); + const _knownElements = new WeakSet(); + function setup() { + initElements(); + Listener_1.default.add("WoltLabSuite/Core/Ui/Password", () => initElements()); + } + exports.setup = setup; + function initElements() { + document.querySelectorAll("input[type=password]").forEach((input) => { + if (!_knownElements.has(input)) { + initElement(input); + } + }); + } + function initElement(input) { + _knownElements.add(input); + const inputAddon = document.createElement("div"); + inputAddon.classList.add("inputAddon"); + input.insertAdjacentElement("beforebegin", inputAddon); + inputAddon.appendChild(input); + const button = document.createElement("span"); + button.title = Language.get("wcf.global.form.password.button.show"); + button.classList.add("button", "inputSuffix", "jsTooltip"); + button.setAttribute("role", "button"); + button.tabIndex = 0; + button.setAttribute("aria-hidden", "true"); + inputAddon.appendChild(button); + const icon = document.createElement("span"); + icon.classList.add("icon", "icon16", "fa-eye-slash"); + button.appendChild(icon); + button.addEventListener("click", () => { + toggle(input, button, icon); + }); + button.addEventListener("keydown", (event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + toggle(input, button, icon); + } + }); + } + function toggle(input, button, icon) { + icon.classList.toggle("fa-eye"); + icon.classList.toggle("fa-eye-slash"); + button.dataset.tooltip = Language.get("wcf.global.form.password.button." + (input.type === "password" ? "hide" : "show")); + input.type = input.type === "password" ? "text" : "password"; + } +}); diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 920b7bc5e3..f71d25c61d 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -3988,6 +3988,8 @@ Dateianhänge: + + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 1b348cdf2d..56f5a52ec0 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -3934,6 +3934,8 @@ Attachments: + + -- 2.20.1