Visibility toggle for password inputs
authorMarcel Werk <burntime@woltlab.com>
Fri, 26 Feb 2021 12:03:10 +0000 (13:03 +0100)
committerMarcel Werk <burntime@woltlab.com>
Fri, 26 Feb 2021 12:03:10 +0000 (13:03 +0100)
Closes #3392

com.woltlab.wcf/templates/headIncludeJavaScript.tpl
ts/WoltLabSuite/Core/Bootstrap.ts
ts/WoltLabSuite/Core/Ui/Password.ts [new file with mode: 0644]
wcfsetup/install/files/acp/templates/header.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Bootstrap.js
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Password.js [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 8a389eaac19b7e97c4afb2586849086c6739371a..814fef7aae066b32fb12d829e1b64a029acf9987 100644 (file)
@@ -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}',
index 65521193f58a978b72a5d37e16373011489e945b..32a483c0e784d1a1cfc7ad085d43663a4e02b2c8 100644 (file)
@@ -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 (file)
index 0000000..611f526
--- /dev/null
@@ -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";
+}
index 44de63ea214b4d2add0088fd5c449bda518a56e7..1db2bd2cedde4fe7c14aaeb8914b6cb051e300b6 100644 (file)
                                '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'}
                        });
                        
index f1625ad2aa1880e8fb1d79c0dc53ba725f35451d..54a855db546f316583e10db2b6400aa407022584 100644 (file)
@@ -8,7 +8,7 @@
  * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @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 (file)
index 0000000..59b8136
--- /dev/null
@@ -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";
+    }
+});
index 920b7bc5e39d1a00ea4badef7e1555443dacbcf1..f71d25c61d9899b9e8e933a312976d3520037370 100644 (file)
@@ -3988,6 +3988,8 @@ Dateianhänge:
                <item name="wcf.global.form.error.greaterThan.javaScript"><![CDATA[{literal}Der eingegebene Wert muss größer sein als {#$greaterThan}.{/literal}]]></item>
                <!-- /deprecated since 2.1 -->
                <item name="wcf.global.form.required"><![CDATA[Benötigte Angaben]]></item>
+               <item name="wcf.global.form.password.button.hide"><![CDATA[Verbergen]]></item>
+               <item name="wcf.global.form.password.button.show"><![CDATA[Anzeigen]]></item>
        </category>
        <category name="wcf.form">
                <item name="wcf.form.field.className"><![CDATA[Klasse]]></item>
index 1b348cdf2ddacd78f2b251c2597b538460207f5b..56f5a52ec06d8a35c5fff7dc5ad74f6ad509e277 100644 (file)
@@ -3934,6 +3934,8 @@ Attachments:
                <item name="wcf.global.form.error.lessThan.javaScript"><![CDATA[{literal}The entered value has to be less than {#$lessThan}.{/literal}]]></item>
                <!-- /deprecated since 2.1 -->
                <item name="wcf.global.form.required"><![CDATA[Required fields]]></item>
+               <item name="wcf.global.form.password.button.hide"><![CDATA[Hide]]></item>
+               <item name="wcf.global.form.password.button.show"><![CDATA[Show]]></item>
        </category>
        <category name="wcf.form">
                <item name="wcf.form.field.className"><![CDATA[Class]]></item>