diff --git a/app/scripts/comp/extension/protocol-impl.js b/app/scripts/comp/extension/protocol-impl.js index 553d9490..69be3982 100644 --- a/app/scripts/comp/extension/protocol-impl.js +++ b/app/scripts/comp/extension/protocol-impl.js @@ -290,7 +290,7 @@ function focusKeeWeb() { } } -async function findEntry(request, filterOptions) { +async function findEntry(request, returnIfOneMatch, filterOptions) { const payload = decryptRequest(request); await checkContentRequestPermissions(request); @@ -316,7 +316,7 @@ async function findEntry(request, filterOptions) { let entry; if (entries.length) { - if (entries.length === 1 && client.permissions.askGet === 'multiple') { + if (entries.length === 1 && returnIfOneMatch && client.permissions.askGet === 'multiple') { entry = entries[0]; } } else { @@ -481,7 +481,7 @@ const ProtocolHandlers = { }, async 'get-logins'(request) { - const entry = await findEntry(request); + const entry = await findEntry(request, true); return encryptResponse(request, { success: 'true', @@ -504,7 +504,7 @@ const ProtocolHandlers = { }, async 'get-totp-by-url'(request) { - const entry = await findEntry(request, { otp: true }); + const entry = await findEntry(request, true, { otp: true }); entry.initOtpGenerator(); @@ -548,6 +548,37 @@ const ProtocolHandlers = { }); }, + async 'get-any-field'(request) { + const entry = await findEntry(request, false); + + const selectEntryFieldView = new SelectEntryFieldView({ + entry + }); + const inactivityTimer = setTimeout(() => { + selectEntryFieldView.emit('result', undefined); + }, Timeouts.KeeWebConnectRequest); + + const field = await selectEntryFieldView.showAndGetResult(); + + clearTimeout(inactivityTimer); + + if (!field) { + throw makeError(Errors.userRejected); + } + + let value = entry.fields[field]; + if (value.isProtected) { + value = value.getText(); + } + + return encryptResponse(request, { + success: 'true', + version: getVersion(request), + field, + value + }); + }, + async 'get-totp'(request) { decryptRequest(request); await checkContentRequestPermissions(request); diff --git a/app/scripts/views/select/select-entry-field-view.js b/app/scripts/views/select/select-entry-field-view.js index a81f2a9f..085412ab 100644 --- a/app/scripts/views/select/select-entry-field-view.js +++ b/app/scripts/views/select/select-entry-field-view.js @@ -3,6 +3,7 @@ import { Events } from 'framework/events'; import { Keys } from 'const/keys'; import { Scrollable } from 'framework/views/scrollable'; import template from 'templates/select/select-entry-field.hbs'; +import { PasswordPresenter } from 'util/formatting/password-presenter'; class SelectEntryFieldView extends View { parent = 'body'; @@ -19,18 +20,29 @@ class SelectEntryFieldView extends View { constructor(model) { super(model); + + this.fields = this.model.entry ? this.getFields(this.model.entry) : []; + this.activeField = this.fields[0]?.field; + this.initScroll(); this.listenTo(Events, 'main-window-blur', this.mainWindowBlur); this.setupKeys(); } setupKeys() { + this.onKey(Keys.DOM_VK_UP, this.upPressed, false, 'select-entry-field'); + this.onKey(Keys.DOM_VK_DOWN, this.downPressed, false, 'select-entry-field'); this.onKey(Keys.DOM_VK_ESCAPE, this.escPressed, false, 'select-entry-field'); this.onKey(Keys.DOM_VK_RETURN, this.enterPressed, false, 'select-entry-field'); } render() { - super.render(this.model); + super.render({ + needsTouch: this.model.needsTouch, + deviceShortName: this.model.deviceShortName, + fields: this.fields, + activeField: this.activeField + }); document.activeElement.blur(); @@ -44,17 +56,62 @@ class SelectEntryFieldView extends View { } } - cancelAndClose() { - this.result = null; - this.emit('result', this.result); + getFields(entry) { + return Object.entries(entry.getAllFields()) + .map(([field, value]) => ({ + field, + value + })) + .filter(({ value }) => value) + .map(({ field, value }) => ({ + field, + value: value.isProtected ? PasswordPresenter.present(value.length) : value + })); + } + + upPressed(e) { + e.preventDefault(); + if (!this.activeField) { + return; + } + + const activeIndex = this.fields.findIndex((f) => f.field === this.activeField) - 1; + if (activeIndex >= 0) { + this.activeField = this.fields[activeIndex].field; + this.render(); + } + } + + downPressed(e) { + e.preventDefault(); + if (!this.activeField) { + return; + } + + const activeIndex = this.fields.findIndex((f) => f.field === this.activeField) + 1; + if (activeIndex < this.fields.length) { + this.activeField = this.fields[activeIndex].field; + this.render(); + } } escPressed() { - this.cancelAndClose(); + this.emit('result', undefined); } enterPressed() { - this.closeWithResult(); + if (!this.activeField) { + return; + } + + this.emit('result', this.activeField); + } + + itemClicked(e) { + const item = e.target.closest('.select-entry-field__item'); + this.activeField = item.dataset.field; + + this.emit('result', this.activeField); } mainWindowBlur() { @@ -72,7 +129,7 @@ class SelectEntryFieldView extends View { } cancelClicked() { - this.cancelAndClose(); + this.emit('result', undefined); } } diff --git a/app/styles/areas/_select-entry.scss b/app/styles/areas/_select-entry.scss index e336ddf5..004c9ba0 100644 --- a/app/styles/areas/_select-entry.scss +++ b/app/styles/areas/_select-entry.scss @@ -90,17 +90,17 @@ overflow: hidden; text-overflow: ellipsis; word-wrap: break-word; - &:first-of-type { + &.select-entry__item-icon-cell { width: 2em; text-align: center; } - &:nth-of-type(3) { + &.select-entry__item-user-cell { width: 25%; } - &:nth-of-type(4) { + &.select-entry__item-url-cell { width: 25%; } - &:nth-of-type(5) { + &.select-entry__item-options { width: 2em; } } diff --git a/app/templates/select/select-entry-field.hbs b/app/templates/select/select-entry-field.hbs index 582fd19c..0efb1a53 100644 --- a/app/templates/select/select-entry-field.hbs +++ b/app/templates/select/select-entry-field.hbs @@ -13,6 +13,19 @@
+ {{#if fields.length}} + + {{#each fields as |f|}} + + + + + {{/each}} +
{{f.field}}{{f.value}}
+ {{else}} +

{{res 'autoTypeNoMatches'}}

+ {{/if}}
diff --git a/app/templates/select/select-entry-item.hbs b/app/templates/select/select-entry-item.hbs index d6fa6d40..26b3ea5c 100644 --- a/app/templates/select/select-entry-item.hbs +++ b/app/templates/select/select-entry-item.hbs @@ -1,15 +1,15 @@ - + {{~#if customIcon~}} {{~else~}} {{~/if~}} - {{#if title}}{{title}}{{else}}({{res 'noTitle'}}){{/if}} - {{user}} - {{url}} + {{#if title}}{{title}}{{else}}({{res 'noTitle'}}){{/if}} + {{user}} + {{url}} {{#if itemOptions}}