mirror of https://github.com/keeweb/keeweb.git
selecting other fields for browser extensions
This commit is contained in:
parent
b7e53185b0
commit
0be60475cf
|
@ -290,7 +290,7 @@ function focusKeeWeb() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findEntry(request, filterOptions) {
|
async function findEntry(request, returnIfOneMatch, filterOptions) {
|
||||||
const payload = decryptRequest(request);
|
const payload = decryptRequest(request);
|
||||||
await checkContentRequestPermissions(request);
|
await checkContentRequestPermissions(request);
|
||||||
|
|
||||||
|
@ -316,7 +316,7 @@ async function findEntry(request, filterOptions) {
|
||||||
let entry;
|
let entry;
|
||||||
|
|
||||||
if (entries.length) {
|
if (entries.length) {
|
||||||
if (entries.length === 1 && client.permissions.askGet === 'multiple') {
|
if (entries.length === 1 && returnIfOneMatch && client.permissions.askGet === 'multiple') {
|
||||||
entry = entries[0];
|
entry = entries[0];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -481,7 +481,7 @@ const ProtocolHandlers = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async 'get-logins'(request) {
|
async 'get-logins'(request) {
|
||||||
const entry = await findEntry(request);
|
const entry = await findEntry(request, true);
|
||||||
|
|
||||||
return encryptResponse(request, {
|
return encryptResponse(request, {
|
||||||
success: 'true',
|
success: 'true',
|
||||||
|
@ -504,7 +504,7 @@ const ProtocolHandlers = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async 'get-totp-by-url'(request) {
|
async 'get-totp-by-url'(request) {
|
||||||
const entry = await findEntry(request, { otp: true });
|
const entry = await findEntry(request, true, { otp: true });
|
||||||
|
|
||||||
entry.initOtpGenerator();
|
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) {
|
async 'get-totp'(request) {
|
||||||
decryptRequest(request);
|
decryptRequest(request);
|
||||||
await checkContentRequestPermissions(request);
|
await checkContentRequestPermissions(request);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Events } from 'framework/events';
|
||||||
import { Keys } from 'const/keys';
|
import { Keys } from 'const/keys';
|
||||||
import { Scrollable } from 'framework/views/scrollable';
|
import { Scrollable } from 'framework/views/scrollable';
|
||||||
import template from 'templates/select/select-entry-field.hbs';
|
import template from 'templates/select/select-entry-field.hbs';
|
||||||
|
import { PasswordPresenter } from 'util/formatting/password-presenter';
|
||||||
|
|
||||||
class SelectEntryFieldView extends View {
|
class SelectEntryFieldView extends View {
|
||||||
parent = 'body';
|
parent = 'body';
|
||||||
|
@ -19,18 +20,29 @@ class SelectEntryFieldView extends View {
|
||||||
|
|
||||||
constructor(model) {
|
constructor(model) {
|
||||||
super(model);
|
super(model);
|
||||||
|
|
||||||
|
this.fields = this.model.entry ? this.getFields(this.model.entry) : [];
|
||||||
|
this.activeField = this.fields[0]?.field;
|
||||||
|
|
||||||
this.initScroll();
|
this.initScroll();
|
||||||
this.listenTo(Events, 'main-window-blur', this.mainWindowBlur);
|
this.listenTo(Events, 'main-window-blur', this.mainWindowBlur);
|
||||||
this.setupKeys();
|
this.setupKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
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_ESCAPE, this.escPressed, false, 'select-entry-field');
|
||||||
this.onKey(Keys.DOM_VK_RETURN, this.enterPressed, false, 'select-entry-field');
|
this.onKey(Keys.DOM_VK_RETURN, this.enterPressed, false, 'select-entry-field');
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
super.render(this.model);
|
super.render({
|
||||||
|
needsTouch: this.model.needsTouch,
|
||||||
|
deviceShortName: this.model.deviceShortName,
|
||||||
|
fields: this.fields,
|
||||||
|
activeField: this.activeField
|
||||||
|
});
|
||||||
|
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
|
|
||||||
|
@ -44,17 +56,62 @@ class SelectEntryFieldView extends View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelAndClose() {
|
getFields(entry) {
|
||||||
this.result = null;
|
return Object.entries(entry.getAllFields())
|
||||||
this.emit('result', this.result);
|
.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() {
|
escPressed() {
|
||||||
this.cancelAndClose();
|
this.emit('result', undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
enterPressed() {
|
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() {
|
mainWindowBlur() {
|
||||||
|
@ -72,7 +129,7 @@ class SelectEntryFieldView extends View {
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelClicked() {
|
cancelClicked() {
|
||||||
this.cancelAndClose();
|
this.emit('result', undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,17 +90,17 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
&:first-of-type {
|
&.select-entry__item-icon-cell {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
&:nth-of-type(3) {
|
&.select-entry__item-user-cell {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
}
|
}
|
||||||
&:nth-of-type(4) {
|
&.select-entry__item-url-cell {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
}
|
}
|
||||||
&:nth-of-type(5) {
|
&.select-entry__item-options {
|
||||||
width: 2em;
|
width: 2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,19 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="select-entry-field__items">
|
<div class="select-entry-field__items">
|
||||||
<div class="scroller">
|
<div class="scroller">
|
||||||
|
{{#if fields.length}}
|
||||||
|
<table class="select-entry-field__table">
|
||||||
|
{{#each fields as |f|}}
|
||||||
|
<tr class="select-entry-field__item {{#ifeq f.field ../activeField}}select-entry-field__item--active{{/ifeq}}"
|
||||||
|
data-field="{{f.field}}">
|
||||||
|
<td>{{f.field}}</td>
|
||||||
|
<td>{{f.value}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</table>
|
||||||
|
{{else}}
|
||||||
|
<h1 class="select-entry__empty-title muted-color">{{res 'autoTypeNoMatches'}}</h1>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="scroller__bar-wrapper"><div class="scroller__bar"></div></div>
|
<div class="scroller__bar-wrapper"><div class="scroller__bar"></div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<tr class="select-entry__item {{#if active}}select-entry__item--active{{/if}}" data-id="{{id}}"
|
<tr class="select-entry__item {{#if active}}select-entry__item--active{{/if}}" data-id="{{id}}"
|
||||||
id="select-entry__item--{{id}}">
|
id="select-entry__item--{{id}}">
|
||||||
<td>
|
<td class="select-entry__item-icon-cell">
|
||||||
{{~#if customIcon~}}
|
{{~#if customIcon~}}
|
||||||
<img src="{{customIcon}}" class="select-entry__item-icon select-entry__item-icon--custom {{#if color}}{{color}}{{/if}}" />
|
<img src="{{customIcon}}" class="select-entry__item-icon select-entry__item-icon--custom {{#if color}}{{color}}{{/if}}" />
|
||||||
{{~else~}}
|
{{~else~}}
|
||||||
<i class="fa fa-{{icon}} {{#if color}}{{color}}-color{{/if}} select-entry__item-icon"></i>
|
<i class="fa fa-{{icon}} {{#if color}}{{color}}-color{{/if}} select-entry__item-icon"></i>
|
||||||
{{~/if~}}
|
{{~/if~}}
|
||||||
</td>
|
</td>
|
||||||
<td>{{#if title}}{{title}}{{else}}({{res 'noTitle'}}){{/if}}</td>
|
<td class="select-entry__item-title-cell">{{#if title}}{{title}}{{else}}({{res 'noTitle'}}){{/if}}</td>
|
||||||
<td>{{user}}</td>
|
<td class="select-entry__item-user-cell">{{user}}</td>
|
||||||
<td>{{url}}</td>
|
<td class="select-entry__item-url-cell">{{url}}</td>
|
||||||
{{#if itemOptions}}
|
{{#if itemOptions}}
|
||||||
<td class="select-entry__item-options">
|
<td class="select-entry__item-options">
|
||||||
<i class="fa fa-ellipsis-h"></i>
|
<i class="fa fa-ellipsis-h"></i>
|
||||||
|
|
Loading…
Reference in New Issue