[epiphany] ephy.js: Improve multi login experience



commit 87f1033b0fbb785d5a0219e492ebd88b8d380410
Author: Jan-Michael Brummer <jan brummer tabos org>
Date:   Thu Aug 27 20:43:32 2020 +0200

    ephy.js: Improve multi login experience
    
    Reimplement password fill user menu function, which fixes:
    
    - multi user selection
    - keyboard highlight selection
    - mouse hovering
    - user selection with return
    - closing of password menu after leaving content
    
    Fixes: https://gitlab.gnome.org/GNOME/epiphany/-/issues/1143

 embed/web-process-extension/resources/js/ephy.js | 229 +++++++++++------------
 1 file changed, 108 insertions(+), 121 deletions(-)
---
diff --git a/embed/web-process-extension/resources/js/ephy.js 
b/embed/web-process-extension/resources/js/ephy.js
index 028919717..dd9026986 100644
--- a/embed/web-process-extension/resources/js/ephy.js
+++ b/embed/web-process-extension/resources/js/ephy.js
@@ -132,155 +132,141 @@ Ephy.PreFillUserMenu = class PreFillUserMenu
     {
         this._manager = manager;
         this._formAuth = formAuth;
-        this._userElement = formAuth.usernameNode;
+        this._userNode = formAuth.usernameNode;
         this._users = users;
-        this._passwordElement = formAuth.passwordNode;
-        this._selected = null;
-        this._wasEdited = false;
-
-        this._userElement.addEventListener('input', this._onInput.bind(this), true);
-        this._userElement.addEventListener('mouseup', this._onMouseUp.bind(this), false);
-        this._userElement.addEventListener('keydown', this._onKeyDown.bind(this), false);
-        this._userElement.addEventListener('change', this._removeMenu, false);
-        this._userElement.addEventListener('blur', this._removeMenu, false);
+        this._passwordNode = formAuth.passwordNode;
+        this._currentFocus = -1;
+
+        let style = document.createElement('style');
+        style.innerHTML =
+            '.autocomplete-items {' +
+                'position: absolute;' +
+                'box-shadow: 5px 5px 5px rgba(0,0,0,0.2);' +
+                'z-index: 99;' +
+                'top: 100%;' +
+                'left: 0;' +
+                'right: 0;' +
+            '}' +
+            '.autocomplete-items div {' +
+                'padding: 10px;' +
+                'cursor: pointer;' +
+                'background-color: #fff;' +
+                'border-bottom: 1px solid #d4d4d4;' +
+            '}' +
+            '.autocomplete-items div:hover {' +
+                'background-color: #e9e9e9;' +
+            '}' +
+            '.autocomplete-active {' +
+                'background-color: DodgerBlue !important;' +
+                'color: #ffffff;' +
+            '}';
+
+        document.getElementsByTagName("head")[0].appendChild(style);
+
+        this._userNode.addEventListener('input', this._onInput.bind(this), true);
+        this._userNode.addEventListener('keydown', this._onKeyDown.bind(this), true);
+        this._userNode.addEventListener('mouseup', this._onMouseUp.bind(this), true);
+        document.addEventListener("click", e => {
+            this._closeAllLists(e.target);
+        });
     }
 
-    // Private
-
     _onInput(event)
     {
-        if (this._manager.isAutoFilling(this._userElement))
+        if (this._manager.isAutoFilling(this._userNode))
             return;
 
-        this._wasEdited = true;
-        this._removeMenu();
-        this._showMenu(false);
+          this._showMenu(false);
     }
 
-    _onMouseUp(event)
+    _onMouseUp(e)
     {
-        if (document.getElementById('ephy-user-choices-container'))
-            return;
-
-        this._showMenu(!this._wasEdited);
+        this._showMenu(true);
     }
 
-    _onKeyDown(event)
+    _showMenu(showAllUsers)
     {
-        if (event.key == 'Escape') {
-            this._removeMenu();
-            return;
-        }
-
-        if (event.key != 'ArrowDown' && event.key != 'ArrowUp')
-            return;
+        let a, b, i, val = this._userNode.value;
+
+        this._closeAllLists();
+        this._currentFocus = -1;
+        a = document.createElement("div");
+        a.id = this._userNode.id + "-autocomplete-list";
+        a.className = "autocomplete-items";
+        let elementRect = this._userNode.getBoundingClientRect();
+        a.style.width = this._userNode.offsetWidth + 'px';
+        a.style.left = elementRect.left + 'px';
+        a.style.top = elementRect.top + elementRect.height + 'px';
+        document.body.appendChild(a);
+        this._passwordNode.value = null;
+
+        for (let user of this._users) {
+          if (showAllUsers || !val || user.substr(0, val.length).toUpperCase() == val.toUpperCase()) {
+            b = document.createElement("div");
+            b.innerHTML = "<strong>" + user.substr(0, val.length) + "</strong>";
+            b.innerHTML += user.substr(val.length);
+            b.innerHTML += "<input type='hidden' value='" + user + "'>";
+
+            b.addEventListener("click", e => {
+              this._formAuth.username = e.target.getElementsByTagName("input")[0].value;
+              this._closeAllLists();
+              this._passwordNode.value = null;
+              this._manager.preFill(this._formAuth);
+            });
 
-        let container = document.getElementById('ephy-user-choices-container');
-        if (!container) {
-            this._showMenu(!this._wasEdited);
-            return;
+            a.appendChild(b);
+          }
         }
+    }
 
-        let newSelect = null;
-        if (this._selected)
-            newSelect = event.key != 'ArrowUp' ? this._selected.previousSibling : this._selected.nextSibling;
-
-        if (!newSelect)
-            newSelect = event.key != 'ArrowUp' ? container.firstElementChild.lastElementChild : 
container.firstElementChild.firstElementChild;
-
-        if (newSelect) {
-            this._selected = newSelect;
-            this._userElement.value = this._selected.firstElementChild.textContent;
-            this._usernameSelected();
-        } else {
-            this._passwordElement.value = '';
+    _onKeyDown(e)
+    {
+        let autocompleteList = document.getElementById(this._userNode.id + "-autocomplete-list");
+        if (autocompleteList)
+            autocompleteList = autocompleteList.getElementsByTagName("div");
+
+        if (e.code == 'ArrowDown') {
+            this._currentFocus++;
+            this._addActive(autocompleteList);
+        } else if (e.code == 'ArrowUp') {
+            this._currentFocus--;
+            this._addActive(autocompleteList);
+        } else if (e.code == 'Enter') {
+            e.preventDefault();
+            if (this._currentFocus > -1)
+                if (autocompleteList)
+                    autocompleteList[this._currentFocus].click();
         }
-
-        event.preventDefault();
     }
 
-    _showMenu(showAll)
+    _addActive(autocompleteList)
     {
-        let mainDiv = document.createElement('div');
-        mainDiv.id = 'ephy-user-choices-container';
-
-        let elementRect = this._userElement.getBoundingClientRect();
-
-        // 2147483647 is the maximum value browsers will take for z-index.
-        // See http://stackoverflow.com/questions/8565821/css-max-z-index-value
-        mainDiv.style.cssText = 'position: absolute;' +
-            'z-index: 2147483647;' +
-            'cursor: default;' +
-            'background-color: white;' +
-            'box-shadow: 5px 5px 5px rgba(0,0,0,0.2);' +
-            'border-top: 0px;' +
-            'border-radius: 8px;' +
-            'padding: 12px 0px;' +
-            '-webkit-user-modify: read-only ! important;';
-        mainDiv.style.width = this._userElement.offsetWidth + 'px';
-        mainDiv.style.left = elementRect.left + document.body.scrollLeft + 'px';
-        mainDiv.style.top = elementRect.top + elementRect.height + document.body.scrollTop + 'px';
-
-        let ul = document.createElement('ul');
-        ul.style.cssText = 'margin: 0; padding: 0;';
-        ul.tabindex = -1;
-        mainDiv.appendChild(ul);
-
-        this._selected = null;
-        for (let i = 0; i < this._users.length; i++) {
-            let user = this._users[i];
-            if (!showAll && !user.startsWith(this._userElement.value))
-                continue;
+        if (!autocompleteList)
+            return false;
 
-            let li = document.createElement('li');
-            li.style.cssText = 'list-style-type: none ! important;' +
-                'background-image: none ! important;' +
-                'padding: 3px 6px ! important;' +
-                'color: black;' +
-                'margin: 0px;';
-            // FIXME: selection colors.
-            li.tabindex = -1;
-            ul.appendChild(li);
-
-            if (user == this._userElement.value)
-                this._selected = li;
-
-            let anchor = document.createElement('a');
-            anchor.style.cssText = 'font-weight: normal ! important;' +
-                'font-family: sans ! important;' +
-                'text-decoration: none ! important;' +
-                'color: black;' +
-                '-webkit-user-modify: read-only ! important;';
-            // FIXME: selection colors.
-            anchor.textContent = user;
-            li.appendChild(anchor);
-
-            li.addEventListener('mousedown', event => {
-                this._userElement.value = user;
-                this._selected = li;
-                this._removeMenu();
-                this._usernameSelected();
-            }, true);
-        }
+        this._removeActive(autocompleteList);
 
-        document.body.appendChild(mainDiv);
+        if (this._currentFocus >= autocompleteList.length)
+            this._currentFocus = 0;
+        else if (this._currentFocus < 0)
+            this._currentFocus = (autocompleteList.length - 1);
 
-        if (!this._selected)
-            this._passwordElement.value = '';
+        autocompleteList[this._currentFocus].classList.add("autocomplete-active");
     }
 
-    _removeMenu()
+    _removeActive(autocompleteList)
     {
-        let menu = document.getElementById('ephy-user-choices-container');
-        if (menu)
-            menu.parentNode.removeChild(menu);
+        for (let autocomplete of autocompleteList)
+            autocomplete.classList.remove("autocomplete-active");
     }
 
-    _usernameSelected()
+    _closeAllLists(elem)
     {
-        this._formAuth.username = this._userElement.value;
-        this._passwordElement.value = '';
-        this._manager.preFill(this._formAuth);
+      let autocompleteItems = document.getElementsByClassName("autocomplete-items");
+
+      for (let autocomplete of autocompleteItems)
+        if (elem != autocomplete && elem != this._userNode)
+          autocomplete.parentNode.removeChild(autocomplete);
     }
 }
 
@@ -486,7 +472,8 @@ Ephy.FormManager = class FormManager
             Ephy.passwordManager.queryUsernames(formAuth.origin).then(users => {
                 if (users.length > 1) {
                     Ephy.log('More than one saved username, hooking menu for choosing which one to select');
-                    this._preFillUserMenu = new Ephy.PreFillUserMenu(this, formAuth, users);
+                    if (!this._preFillUserMenu)
+                        this._preFillUserMenu = new Ephy.PreFillUserMenu(this, formAuth, users);
                 }
                 this.preFill(formAuth);
             });


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]