Bug 1549814 - Add 'New Login' button to create new saved logins. r=MattN,Pike

Differential Revision: https://phabricator.services.mozilla.com/D31713

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jared Wein 2019-05-21 19:24:19 +00:00
Родитель eec920c32d
Коммит 4ad2b10786
12 изменённых файлов: 110 добавлений и 26 удалений

Просмотреть файл

@ -35,6 +35,7 @@ let LEGACY_ACTORS = {
matches: ["about:logins"],
module: "resource:///actors/AboutLoginsChild.jsm",
events: {
"AboutLoginsCreateLogin": {wantUntrusted: true},
"AboutLoginsDeleteLogin": {wantUntrusted: true},
"AboutLoginsOpenSite": {wantUntrusted: true},
"AboutLoginsUpdateLogin": {wantUntrusted: true},
@ -546,6 +547,7 @@ const listeners = {
},
mm: {
"AboutLogins:CreateLogin": ["AboutLoginsParent"],
"AboutLogins:DeleteLogin": ["AboutLoginsParent"],
"AboutLogins:OpenSite": ["AboutLoginsParent"],
"AboutLogins:Subscribe": ["AboutLoginsParent"],

Просмотреть файл

@ -31,6 +31,10 @@ class AboutLoginsChild extends ActorChild {
});
break;
}
case "AboutLoginsCreateLogin": {
this.mm.sendAsyncMessage("AboutLogins:CreateLogin", {login: event.detail});
break;
}
case "AboutLoginsDeleteLogin": {
this.mm.sendAsyncMessage("AboutLogins:DeleteLogin", {login: event.detail});
break;

Просмотреть файл

@ -74,6 +74,16 @@ var AboutLoginsParent = {
}
switch (message.name) {
case "AboutLogins:CreateLogin": {
let newLogin = message.data.login;
Object.assign(newLogin, {
formSubmitURL: "",
usernameField: "",
passwordField: "",
});
Services.logins.addLogin(LoginHelper.vanillaObjectToLogin(newLogin));
break;
}
case "AboutLogins:DeleteLogin": {
let login = LoginHelper.vanillaObjectToLogin(message.data.login);
Services.logins.removeLogin(login);

Просмотреть файл

@ -17,6 +17,7 @@ header {
align-items: center;
background-color: var(--in-content-box-background);
border-bottom: 1px solid var(--in-content-box-border-color);
padding: 0 18px;
}
login-filter {
@ -26,6 +27,7 @@ login-filter {
login-list {
grid-area: logins;
overflow: hidden auto;
}
login-item {
@ -35,7 +37,12 @@ login-item {
#branding-logo {
height: 32px;
margin-inline-end: 18px;
}
#create-login-button {
margin-inline-start: 18px;
margin-inline-end: 0;
}
:root:not(.official-branding) #branding-logo {

Просмотреть файл

@ -10,7 +10,9 @@
### need to be applied to the composed node where they can be moved to the proper
### descendant after translation.
about-logins-page-title = Login Manager
about-logins-page-title = Logins & Passwords
create-login-button = New Login
login-filter =
.placeholder = Search Logins
@ -24,20 +26,21 @@ login-list =
login-item =
.cancel-button = Cancel
.copied-password-button = ✓ Copied!
.copied-username-button = ✓ Copied!
.copy-password-button = Copy
.copy-username-button = Copy
.delete-button = Delete
.edit-button = Edit
.hostname-label = Website Address
.modal-input-reveal-checkbox-hide = Hide password
.modal-input-reveal-checkbox-show = Show password
.copied-password-button = ✓ Copied!
.copied-username-button = ✓ Copied!
.copy-password-button = Copy
.copy-username-button = Copy
.new-login-title = New Entry
.open-site-button = Launch
.password-label = Password
.save-changes-button = Save Changes
.time-created = Created: { DATETIME($timeCreated, day: "numeric", month: "long", year: "numeric") }
.time-changed = Last changed: { DATETIME($timeChanged, day: "numeric", month: "long", year: "numeric") }
.time-changed = Last modified: { DATETIME($timeChanged, day: "numeric", month: "long", year: "numeric") }
.time-used = Last used: { DATETIME($timeUsed, day: "numeric", month: "long", year: "numeric") }
.username-label = Username

Просмотреть файл

@ -25,6 +25,7 @@
<img id="branding-logo" src="chrome://branding/content/aboutlogins.svg" alt=""/>
<login-filter data-l10n-id="login-filter"
data-l10n-attrs="placeholder"></login-filter>
<button id="create-login-button" data-l10n-id="create-login-button"></button>
</header>
<login-list data-l10n-id="login-list"
data-l10n-attrs="count"
@ -41,6 +42,7 @@
hostname-label,
modal-input-reveal-checkbox-hide,
modal-input-reveal-checkbox-show,
new-login-title,
open-site-button,
password-label,
save-changes-button,
@ -77,7 +79,8 @@
<div class="detail-row">
<label>
<span class="hostname-label field-label"></span>
<span class="hostname"/>
<span class="hostname-saved-value"></span>
<input type="text" name="hostname"/>
</label>
<button class="open-site-button"></button>
</div>

Просмотреть файл

@ -7,6 +7,12 @@ let gElements = {};
document.addEventListener("DOMContentLoaded", () => {
gElements.loginList = document.querySelector("login-list");
gElements.loginItem = document.querySelector("login-item");
gElements.newLoginButton = document.querySelector("#create-login-button");
gElements.newLoginButton.addEventListener("click", () => {
gElements.loginItem.setLogin({});
gElements.loginList.clearSelection();
});
document.dispatchEvent(new CustomEvent("AboutLoginsInit", {bubbles: true}));
}, {once: true});
@ -19,6 +25,7 @@ window.addEventListener("AboutLoginsChromeToContent", event => {
}
case "LoginAdded": {
gElements.loginList.loginAdded(event.detail.value);
gElements.loginItem.loginAdded(event.detail.value);
break;
}
case "LoginModified": {

Просмотреть файл

@ -7,6 +7,5 @@
}
input {
margin: 18px;
flex: auto;
}

Просмотреть файл

@ -8,6 +8,11 @@
padding-right: 40px;
}
:host([isNewLogin]) .hostname-saved-value,
:host([isNewLogin]) copy-to-clipboard-button,
:host([isNewLogin]) .open-site-button,
:host([isNewLogin]) .meta-info,
:host(:not([isNewLogin])) input[name="hostname"],
:host(:not([editing])) .save-changes-button,
:host(:not([editing])) .cancel-button {
display: none;

Просмотреть файл

@ -57,6 +57,7 @@ class LoginItem extends ReflectedFluentElement {
"hostname-label",
"modal-input-reveal-checkbox-hide",
"modal-input-reveal-checkbox-show",
"new-login-title",
"open-site-button",
"password-label",
"save-changes-button",
@ -97,6 +98,14 @@ class LoginItem extends ReflectedFluentElement {
.setAttribute("reveal-checkbox-show", this.getAttribute(attrName));
break;
}
case "new-login-title": {
let title = this.shadowRoot.querySelector(".title");
title.setAttribute(attrName, this.getAttribute(attrName));
if (!this._login.title) {
title.textContent = this.getAttribute(attrName);
}
break;
}
default:
return false;
}
@ -110,8 +119,10 @@ class LoginItem extends ReflectedFluentElement {
timeUsed: this._login.timeLastUsed || "",
};
document.l10n.setAttributes(this, "login-item", l10nArgs);
this.shadowRoot.querySelector(".title").textContent = this._login.title || "";
this.shadowRoot.querySelector(".hostname").textContent = this._login.hostname || "";
let title = this.shadowRoot.querySelector(".title");
title.textContent = this._login.title || title.getAttribute("new-login-title");
this.shadowRoot.querySelector(".hostname-saved-value").textContent = this._login.hostname || "";
this.shadowRoot.querySelector("modal-input[name='username']").setAttribute("value", this._login.username || "");
this.shadowRoot.querySelector("modal-input[name='password']").setAttribute("value", this._login.password || "");
}
@ -153,21 +164,19 @@ class LoginItem extends ReflectedFluentElement {
return;
}
if (event.target.classList.contains("save-changes-button")) {
let loginUpdates = {
guid: this._login.guid,
};
let formUsername = this.shadowRoot.querySelector("modal-input[name='username']").value.trim();
if (formUsername != this._login.username) {
loginUpdates.username = formUsername;
let loginUpdates = this._loginFromForm();
if (this._login.guid) {
loginUpdates.guid = this._login.guid;
document.dispatchEvent(new CustomEvent("AboutLoginsUpdateLogin", {
bubbles: true,
detail: loginUpdates,
}));
} else {
document.dispatchEvent(new CustomEvent("AboutLoginsCreateLogin", {
bubbles: true,
detail: loginUpdates,
}));
}
let formPassword = this.shadowRoot.querySelector("modal-input[name='password']").value.trim();
if (formPassword != this._login.password) {
loginUpdates.password = formPassword;
}
document.dispatchEvent(new CustomEvent("AboutLoginsUpdateLogin", {
bubbles: true,
detail: loginUpdates,
}));
}
break;
}
@ -175,17 +184,29 @@ class LoginItem extends ReflectedFluentElement {
}
setLogin(login) {
this._login = login;
this.toggleAttribute("isNewLogin", !login.guid);
this.toggleEditing(!login.guid);
this.render();
}
loginAdded(login) {
if (this._login.guid ||
!window.AboutLoginsUtils.doLoginsMatch(login, this._loginFromForm())) {
return;
}
this.toggleEditing(false);
this._login = login;
this.render();
}
loginModified(login) {
if (login.guid != this._login.guid) {
if (this._login.guid != login.guid) {
return;
}
this._login = login;
this.toggleEditing(false);
this.render();
}
@ -199,10 +220,24 @@ class LoginItem extends ReflectedFluentElement {
toggleEditing(force) {
let shouldEdit = force !== undefined ? force : !this.hasAttribute("editing");
if (!shouldEdit) {
this.removeAttribute("isNewLogin");
}
this.shadowRoot.querySelector(".edit-button").disabled = shouldEdit;
this.shadowRoot.querySelectorAll("modal-input")
.forEach(el => el.toggleAttribute("editing", shouldEdit));
this.toggleAttribute("editing", shouldEdit);
}
_loginFromForm() {
return {
username: this.shadowRoot.querySelector("modal-input[name='username']").value.trim(),
password: this.shadowRoot.querySelector("modal-input[name='password']").value.trim(),
hostname: this.hasAttribute("isNewLogin") ? this.shadowRoot.querySelector("input[name='hostname']").value.trim()
: this.shadowRoot.querySelector(".hostname-saved-value").textContent,
};
}
}
customElements.define("login-item", LoginItem);

Просмотреть файл

@ -83,6 +83,14 @@ class LoginList extends ReflectedFluentElement {
return this.reflectedFluentIDs;
}
clearSelection() {
if (!this._selectedItem) {
return;
}
this._selectedItem.classList.remove("selected");
this._selectedItem = null;
}
setLogins(logins) {
this._logins = logins;
this.render();

Просмотреть файл

@ -8,6 +8,7 @@
--reveal-button-opacity-active: 1;
display: flex;
align-items: center;
}
:host([editing]) .locked-value,