mirror of https://github.com/keeweb/keeweb.git
auto-type hint
This commit is contained in:
parent
4c7072eeed
commit
46f3d76380
|
@ -151,7 +151,7 @@ AutoTypeRunner.prototype.tryParseCommand = function(op) {
|
|||
op.type = 'cmd';
|
||||
op.value = op.sep === '=' ? 'setDelay' : 'wait';
|
||||
if (!op.arg) {
|
||||
throw 'Delay requires seconds count';
|
||||
throw 'Delay requires milliseconds count';
|
||||
}
|
||||
if (isNaN(+op.arg)) {
|
||||
throw 'Bad delay: ' + op.arg;
|
||||
|
@ -168,7 +168,7 @@ AutoTypeRunner.prototype.tryParseCommand = function(op) {
|
|||
};
|
||||
|
||||
AutoTypeRunner.prototype.getEntryFieldKeys = function(field, op) {
|
||||
if (!field) {
|
||||
if (!field || !this.entry) {
|
||||
return '';
|
||||
}
|
||||
field = field.toLowerCase();
|
||||
|
@ -213,7 +213,7 @@ AutoTypeRunner.prototype.getEntryFieldKeys = function(field, op) {
|
|||
};
|
||||
|
||||
AutoTypeRunner.prototype.getEntryGroupName = function() {
|
||||
return this.entry.group.get('title');
|
||||
return this.entry && this.entry.group.get('title');
|
||||
};
|
||||
|
||||
AutoTypeRunner.prototype.dt = function(part) {
|
||||
|
@ -260,6 +260,9 @@ AutoTypeRunner.prototype.udt = function(part) {
|
|||
};
|
||||
|
||||
AutoTypeRunner.prototype.getOtp = function(op) {
|
||||
if (!this.entry) {
|
||||
return '';
|
||||
}
|
||||
this.entry.initOtpGenerator();
|
||||
if (!this.entry.otpGenerator) {
|
||||
return '';
|
||||
|
|
|
@ -10,7 +10,8 @@ var Links = {
|
|||
UpdateDesktop: 'https://github.com/antelle/keeweb/releases/download/v{ver}/UpdateDesktop.zip',
|
||||
ReleaseNotes: 'https://github.com/antelle/keeweb/blob/master/release-notes.md#release-notes',
|
||||
SelfHostedDropbox: 'https://github.com/antelle/keeweb#self-hosting',
|
||||
Manifest: 'https://antelle.github.io/keeweb/manifest.appcache'
|
||||
Manifest: 'https://antelle.github.io/keeweb/manifest.appcache',
|
||||
AutoType: 'https://github.com/antelle/keeweb/wiki/Auto-Type'
|
||||
};
|
||||
|
||||
module.exports = Links;
|
||||
|
|
|
@ -8,7 +8,8 @@ var Timeouts = {
|
|||
BeforeAutoLock: 300,
|
||||
CheckWindowClosed: 300,
|
||||
OtpFadeDuration: 10000,
|
||||
AutoTypeAfterHide: 100
|
||||
AutoTypeAfterHide: 100,
|
||||
DrobDownClickWait: 500
|
||||
};
|
||||
|
||||
module.exports = Timeouts;
|
||||
|
|
|
@ -101,6 +101,11 @@ var EntryModel = Backbone.Model.extend({
|
|||
this.autoTypeEnabled = this.entry.autoType.enabled;
|
||||
this.autoTypeObfuscation = this.entry.autoType.obfuscation === kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard;
|
||||
this.autoTypeSequence = this.entry.autoType.defaultSequence;
|
||||
this.autoTypeWindows = this.entry.autoType.items.map(this._convertAutoTypeItem);
|
||||
},
|
||||
|
||||
_convertAutoTypeItem: function(item) {
|
||||
return { window: item.window, sequence: item.keystrokeSequence };
|
||||
},
|
||||
|
||||
_iconFromId: function(id) {
|
||||
|
|
|
@ -8,7 +8,7 @@ var MenuItemModel = require('./menu/menu-item-model'),
|
|||
KdbxIcons = kdbxweb.Consts.Icons,
|
||||
GroupCollection, EntryCollection;
|
||||
|
||||
var DefaultAutoTypeSequence = '{username}{tab}{password}{enter}';
|
||||
var DefaultAutoTypeSequence = '{USERNAME}{TAB}{PASSWORD}{ENTER}';
|
||||
|
||||
var GroupModel = MenuItemModel.extend({
|
||||
defaults: _.extend({}, MenuItemModel.prototype.defaults, {
|
||||
|
|
|
@ -225,6 +225,8 @@ var Locale = {
|
|||
detAutoTypeShortcuts: 'Shortcuts',
|
||||
detAutoTypeShortcutsDesc: '{} or {} while the app is inactive',
|
||||
detAutoTypeObfuscation: 'Mix real keystrokes with random',
|
||||
detAutoTypeWindow: 'Window',
|
||||
detAutoTypeInputWindow: 'Window title',
|
||||
detSetupOtpAlert: 'Scan the QR code',
|
||||
detSetupOtpAlertBody: 'Please copy the QR code which is displayed on the authorization page.',
|
||||
detSetupOtpAlertBody1: '1. go to the authorization page',
|
||||
|
@ -243,6 +245,11 @@ var Locale = {
|
|||
detOtpQrWrong: 'Wrong QR code',
|
||||
detOtpQrWrongBody: 'Your QR code was successfully scanned but it doesn\'t contain one-time password data.',
|
||||
|
||||
autoTypeEntryFields: 'Entry fields',
|
||||
autoTypeModifiers: 'Modifier keys',
|
||||
autoTypeKeys: 'Keys',
|
||||
autoTypeLink: 'more...',
|
||||
|
||||
appSecWarn: 'Not Secure!',
|
||||
appSecWarnBody1: 'You have loaded this app with insecure connection. ' +
|
||||
'Someone may be watching you and stealing your passwords. ' +
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
'use strict';
|
||||
|
||||
var Backbone = require('backbone'),
|
||||
FeatureDetector = require('../util/feature-detector'),
|
||||
Links = require('../const/links'),
|
||||
Timeouts = require('../const/timeouts');
|
||||
|
||||
var AutoTypeHintView = Backbone.View.extend({
|
||||
template: require('templates/auto-type-hint.hbs'),
|
||||
|
||||
events: {},
|
||||
|
||||
initialize: function(opts) {
|
||||
this.input = opts.input;
|
||||
this.bodyClick = this.bodyClick.bind(this);
|
||||
this.inputBlur = this.inputBlur.bind(this);
|
||||
$('body').on('click', this.bodyClick);
|
||||
this.input.addEventListener('blur', this.inputBlur);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.renderTemplate({
|
||||
cmd: FeatureDetector.isMac() ? 'command' : 'ctrl',
|
||||
hasCtrl: FeatureDetector.isMac(),
|
||||
link: Links.AutoType
|
||||
});
|
||||
var rect = this.input.getBoundingClientRect();
|
||||
this.$el.appendTo(document.body).css({
|
||||
left: rect.left, top: rect.bottom + 1, width: rect.width
|
||||
});
|
||||
var selfRect = this.$el[0].getBoundingClientRect();
|
||||
var bodyRect = document.body.getBoundingClientRect();
|
||||
if (selfRect.bottom > bodyRect.bottom) {
|
||||
this.$el.css('height', selfRect.height + bodyRect.bottom - selfRect.bottom - 1);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
$('body').off('click', this.bodyClick);
|
||||
this.input.removeEventListener('blur', this.inputBlur);
|
||||
Backbone.View.prototype.remove.apply(this, arguments);
|
||||
},
|
||||
|
||||
bodyClick: function(e) {
|
||||
if (this.removeTimer) {
|
||||
clearTimeout(this.removeTimer);
|
||||
this.removeTimer = null;
|
||||
}
|
||||
if (e.target === this.input) {
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if ($.contains(this.$el[0], e.target) || e.target === this.$el[0]) {
|
||||
e.stopPropagation();
|
||||
if (e.target.tagName.toLowerCase() === 'a' && !e.target.href) {
|
||||
var text = $(e.target).text();
|
||||
if (text[0] !== '{') {
|
||||
text = text.split(' ')[0];
|
||||
}
|
||||
this.insertText(text);
|
||||
}
|
||||
this.input.focus();
|
||||
} else {
|
||||
this.remove();
|
||||
}
|
||||
},
|
||||
|
||||
inputBlur: function() {
|
||||
if (!this.removeTimer) {
|
||||
this.removeTimer = setTimeout(this.remove.bind(this), Timeouts.DrobDownClickWait);
|
||||
}
|
||||
},
|
||||
|
||||
insertText: function(text) {
|
||||
var pos = this.input.selectionEnd || this.input.value.length;
|
||||
this.input.value = this.input.value.substr(0, pos) + text + this.input.value.substr(pos);
|
||||
this.input.selectionStart = this.input.selectionEnd = pos + text.length;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = AutoTypeHintView;
|
|
@ -2,6 +2,7 @@
|
|||
'use strict';
|
||||
|
||||
var Backbone = require('backbone'),
|
||||
AutoTypeHintView = require('../auto-type-hint-view'),
|
||||
Locale = require('../../util/locale'),
|
||||
FeatureDetector = require('../../util/feature-detector'),
|
||||
AutoType = require('../../auto-type');
|
||||
|
@ -10,6 +11,7 @@ var DetailsAutoTypeView = Backbone.View.extend({
|
|||
template: require('templates/details/details-auto-type.hbs'),
|
||||
|
||||
events: {
|
||||
'focus #details__auto-type-sequence': 'seqFocus',
|
||||
'input #details__auto-type-sequence': 'seqInput',
|
||||
'keypress #details__auto-type-sequence': 'seqKeyPress',
|
||||
'keydown #details__auto-type-sequence': 'seqKeyDown',
|
||||
|
@ -17,6 +19,10 @@ var DetailsAutoTypeView = Backbone.View.extend({
|
|||
'change #details__auto-type-obfuscation': 'obfuscationChange'
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.views = {};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var detAutoTypeShortcutsDesc = Locale.detAutoTypeShortcutsDesc
|
||||
.replace('{}', FeatureDetector.actionShortcutSymbol() + 'T')
|
||||
|
@ -25,6 +31,7 @@ var DetailsAutoTypeView = Backbone.View.extend({
|
|||
enabled: this.model.getEffectiveEnableAutoType(),
|
||||
obfuscation: this.model.autoTypeObfuscation,
|
||||
sequence: this.model.autoTypeSequence,
|
||||
windows: this.model.autoTypeWindows,
|
||||
defaultSequence: this.model.group.getEffectiveAutoTypeSeq(),
|
||||
detAutoTypeShortcutsDesc: detAutoTypeShortcutsDesc
|
||||
});
|
||||
|
@ -34,7 +41,7 @@ var DetailsAutoTypeView = Backbone.View.extend({
|
|||
seqInput: function(e) {
|
||||
var that = this;
|
||||
var el = e.target;
|
||||
var seq = el.value;
|
||||
var seq = $.trim(el.value);
|
||||
AutoType.validate(this.model, seq, function(err) {
|
||||
$(el).toggleClass('input--error', !!err);
|
||||
if (!err) {
|
||||
|
@ -51,6 +58,13 @@ var DetailsAutoTypeView = Backbone.View.extend({
|
|||
e.stopPropagation();
|
||||
},
|
||||
|
||||
seqFocus: function(e) {
|
||||
if (!this.views.hint) {
|
||||
this.views.hint = new AutoTypeHintView({input: e.target}).render();
|
||||
this.views.hint.on('remove', (function() {delete this.views.hint; }).bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
enabledChange: function(e) {
|
||||
this.model.setEnableAutoType(e.target.checked);
|
||||
},
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
var Backbone = require('backbone'),
|
||||
Scrollable = require('../mixins/scrollable'),
|
||||
IconSelectView = require('./icon-select-view'),
|
||||
Launcher = require('../comp/launcher');
|
||||
AutoTypeHintView = require('./auto-type-hint-view'),
|
||||
AutoType = require('../auto-type');
|
||||
|
||||
var GrpView = Backbone.View.extend({
|
||||
template: require('templates/grp.hbs'),
|
||||
|
@ -13,6 +14,7 @@ var GrpView = Backbone.View.extend({
|
|||
'click .grp__buttons-trash': 'moveToTrash',
|
||||
'click .grp__back-button': 'returnToApp',
|
||||
'input #grp__field-title': 'changeTitle',
|
||||
'focus #grp__field-auto-type-seq': 'focusAutoTypeSeq',
|
||||
'input #grp__field-auto-type-seq': 'changeAutoTypeSeq',
|
||||
'change #grp__check-search': 'setEnableSearching',
|
||||
'change #grp__check-auto-type': 'setEnableAutoType'
|
||||
|
@ -31,7 +33,7 @@ var GrpView = Backbone.View.extend({
|
|||
customIcon: this.model.get('customIcon'),
|
||||
enableSearching: this.model.getEffectiveEnableSearching(),
|
||||
readonly: this.model.get('top'),
|
||||
canAutoType: !!Launcher,
|
||||
canAutoType: AutoType.enabled,
|
||||
autoTypeSeq: this.model.get('autoTypeSeq'),
|
||||
autoTypeEnabled: this.model.getEffectiveEnableAutoType(),
|
||||
defaultAutoTypeSeq: this.model.getParentEffectiveAutoTypeSeq()
|
||||
|
@ -76,8 +78,22 @@ var GrpView = Backbone.View.extend({
|
|||
},
|
||||
|
||||
changeAutoTypeSeq: function(e) {
|
||||
var seq = $.trim(e.target.value);
|
||||
this.model.setAutoTypeSeq(seq);
|
||||
var that = this;
|
||||
var el = e.target;
|
||||
var seq = $.trim(el.value);
|
||||
AutoType.validate(null, seq, function(err) {
|
||||
$(e.target).toggleClass('input--error', !!err);
|
||||
if (!err) {
|
||||
that.model.setAutoTypeSeq(seq);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
focusAutoTypeSeq: function(e) {
|
||||
if (!this.views.hint) {
|
||||
this.views.hint = new AutoTypeHintView({input: e.target}).render();
|
||||
this.views.hint.on('remove', (function() {delete this.views.hint; }).bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
showIconsSelect: function() {
|
||||
|
|
|
@ -8,6 +8,7 @@ body {
|
|||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
position: fixed;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
noscript {
|
||||
|
|
|
@ -5,12 +5,10 @@ body {
|
|||
color: text-color();
|
||||
background-color: background-color();
|
||||
}
|
||||
overflow: auto;
|
||||
font-family: $base-font-family;
|
||||
font-feature-settings: "kern", "liga";
|
||||
font-size: $base-font-size;
|
||||
line-height: $base-line-height;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
@import "base/base";
|
||||
|
||||
@import "utils/drag";
|
||||
@import "utils/common-dropdown";
|
||||
@import "utils/auto-type-hint";
|
||||
@import "utils/drag";
|
||||
@import "utils/selection";
|
||||
|
||||
@import "common/dates";
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
.auto-type-hint {
|
||||
@include common-dropdown;
|
||||
position: absolute;
|
||||
z-index: $z-index-no-modal;
|
||||
border-radius: $base-border-radius;
|
||||
padding: $base-padding;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
&__block {
|
||||
margin-bottom: $base-padding-v;
|
||||
>a, >b {
|
||||
font-weight: normal;
|
||||
display: inline-block;
|
||||
margin-right: $base-padding-h;
|
||||
margin-bottom: $base-padding-v;
|
||||
}
|
||||
}
|
||||
&__link-details {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
margin: $base-padding;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<div class="auto-type-hint">
|
||||
<a href="{{link}}" class="auto-type-hint__link-details" target="_blank">{{res 'autoTypeLink'}}</a>
|
||||
<div class="auto-type-hint__block">
|
||||
<div>{{res 'autoTypeEntryFields'}}:</div>
|
||||
<a>{TITLE}</a><a>{USERNAME}</a><a>{URL}</a><a>{PASSWORD}</a><a>{NOTES}</a><a>{GROUP}</a>
|
||||
<a>{TOTP}</a><a>{S:Custom Field Name}</a>
|
||||
</div>
|
||||
<div class="auto-type-hint__block">
|
||||
<div>{{res 'autoTypeModifiers'}}:</div>
|
||||
<a>+ (shift)</a><a>% (alt)</a><a>^ ({{cmd}})</a>{{#if hasCtrl}}<a>^^ (ctrl)</a>{{/if}}
|
||||
</div>
|
||||
<div class="auto-type-hint__block">
|
||||
<div>{{res 'autoTypeKeys'}}:</div>
|
||||
<a>{TAB}</a><a>{ENTER}</a><a>{SPACE}</a><a>{UP}</a><a>{DOWN}</a><a>{LEFT}</a><a>{RIGHT}</a><a>{HOME}</a><a>{END}</a>
|
||||
<a>{+}</a><a>{%}</a><a>{^}</a><a>{~}</a><a>{(}</a><a>{)}</a><a>{[}</a><a>{]}</a><a>\{{}</a><a>{}}</a>
|
||||
</div>
|
||||
</div>
|
|
@ -24,4 +24,15 @@
|
|||
<div class="details__field-label">{{res 'detAutoTypeShortcuts'}}</div>
|
||||
<div class="details__field-value">{{{detAutoTypeShortcutsDesc}}}</div>
|
||||
</div>
|
||||
{{!--{{#each windows as |win|}}
|
||||
<div class="details__field">
|
||||
<div class="details__field-label">{{res 'detAutoTypeWindow'}}</div>
|
||||
<div class="details__field-value details__auto-type-windows">
|
||||
<input type="text" maxlength="1024" class="details__auto-type-window-title"
|
||||
value="{{win.window}}" placeholder="{{res 'detAutoTypeInputWindow'}}" />
|
||||
<input type="text" maxlength="1024" class="details__auto-type-window-sequence"
|
||||
value="{{win.sequence}}" placeholder="{{../defaultSequence}}" />
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}--}}
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue