entry auto-type ui

This commit is contained in:
antelle 2016-04-23 17:50:40 +03:00
parent b00e1196a1
commit 48f10ca4a7
20 changed files with 208 additions and 31 deletions

View File

@ -1,6 +1,6 @@
'use strict';
var Launcher = require('../../comp/launcher');
var Launcher = require('../comp/launcher');
var AutoTypeEmitterFactory = {
create: function(callback) {

View File

@ -1,6 +1,6 @@
'use strict';
var Launcher = require('../../comp/launcher');
var Launcher = require('../comp/launcher');
var AutoTypeHelperFactory = {
create: function() {

View File

@ -1,6 +1,6 @@
'use strict';
var Logger = require('../../util/logger');
var Logger = require('../util/logger');
var logger = new Logger('auto-type-obfuscator');
logger.setLevel(localStorage.autoTypeDebug ? Logger.Level.All : Logger.Level.Warn);

View File

@ -2,8 +2,8 @@
var AutoTypeObfuscator = require('./auto-type-obfuscator'),
AutoTypeEmitterFactory = require('./auto-type-emitter-factory'),
Format = require('../../util/format'),
Logger = require('../../util/logger');
Format = require('../util/format'),
Logger = require('../util/logger');
var emitterLogger = new Logger('auto-type-emitter');
emitterLogger.setLevel(localStorage.autoTypeDebug ? Logger.Level.All : Logger.Level.Warn);

View File

@ -1,6 +1,6 @@
'use strict';
var Launcher = require('../../launcher');
var Launcher = require('../../comp/launcher');
// http://eastmanreference.com/complete-list-of-applescript-key-codes/
var KeyMap = {

View File

@ -1,6 +1,6 @@
'use strict';
var Launcher = require('../../launcher');
var Launcher = require('../../comp/launcher');
// https://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
var KeyMap = {

View File

@ -1,6 +1,6 @@
'use strict';
var Launcher = require('../../launcher');
var Launcher = require('../../comp/launcher');
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
var KeyMap = {

View File

@ -1,6 +1,6 @@
'use strict';
var Launcher = require('../../launcher');
var Launcher = require('../../comp/launcher');
var ForeMostAppScript = 'tell application "System Events" to set frontApp to name of first process whose frontmost is true';
var ChromeScript = 'tell application "{}" to set appUrl to URL of active tab of front window\n' +

View File

@ -1,6 +1,6 @@
'use strict';
// var Launcher = require('../../launcher');
// var Launcher = require('../../comp/launcher');
var AutoTypeHelper = function() {
};

View File

@ -1,6 +1,6 @@
'use strict';
// var Launcher = require('../../launcher');
// var Launcher = require('../../comp/launcher');
var AutoTypeHelper = function() {
};

View File

@ -2,9 +2,9 @@
var AutoTypeParser = require('./auto-type-parser'),
AutoTypeHelperFactory = require('./auto-type-helper-factory'),
Launcher = require('../launcher'),
Logger = require('../../util/logger'),
Timeouts = require('../../const/timeouts');
Launcher = require('../comp/launcher'),
Logger = require('../util/logger'),
Timeouts = require('../const/timeouts');
var logger = new Logger('auto-type');
var clearTextAutoTypeLog = localStorage.autoTypeDebug;
@ -12,6 +12,8 @@ var clearTextAutoTypeLog = localStorage.autoTypeDebug;
var AutoType = {
helper: AutoTypeHelperFactory.create(),
enabled: !!Launcher,
run: function(entry, sequence, obfuscate, callback) {
logger.debug('Start', sequence);
var that = this;
@ -49,6 +51,16 @@ var AutoType = {
}
},
validate: function(entry, sequence, callback) {
try {
var parser = new AutoTypeParser(sequence);
var runner = parser.parse();
runner.resolve(entry, callback);
} catch (ex) {
return callback(ex);
}
},
printOps: function(ops) {
return '[' + ops.map(this.printOp, this).join(',') + ']';
},

View File

@ -69,6 +69,16 @@ _.extend(Backbone.View.prototype, {
remove: function() {
this.trigger('remove');
this.removeInnerViews();
if (this.scroll) {
try { this.scroll.dispose(); }
catch (e) { }
}
Tip.hideTips(this.$el);
this._parentRemove(arguments);
},
removeInnerViews: function() {
if (this.views) {
_.each(this.views, function(view) {
if (view) {
@ -81,13 +91,8 @@ _.extend(Backbone.View.prototype, {
}
}
});
this.views = {};
}
if (this.scroll) {
try { this.scroll.dispose(); }
catch (e) { }
}
Tip.hideTips(this.$el);
this._parentRemove(arguments);
},
deferRender: function() {

View File

@ -52,6 +52,7 @@ var EntryModel = Backbone.Model.extend({
this._buildSearchText();
this._buildSearchTags();
this._buildSearchColor();
this._buildAutoType();
},
_checkUpdatedEntry: function() {
@ -96,6 +97,12 @@ var EntryModel = Backbone.Model.extend({
this.searchColor = this.color;
},
_buildAutoType: function() {
this.autoTypeEnabled = this.entry.autoType.enabled;
this.autoTypeObfuscation = this.entry.autoType.obfuscation === kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard;
this.autoTypeSequence = this.entry.autoType.defaultSequence;
},
_iconFromId: function(id) {
return IconMap[id];
},
@ -451,6 +458,35 @@ var EntryModel = Backbone.Model.extend({
this.setField('otp', url ? kdbxweb.ProtectedValue.fromString(url) : undefined);
delete this.entry.fields['TOTP Seed'];
delete this.entry.fields['TOTP Settings'];
},
getEffectiveEnableAutoType: function() {
if (typeof this.entry.autoType.enabled === 'boolean') {
return this.entry.autoType.enabled;
}
return this.group.getEffectiveEnableAutoType();
},
setEnableAutoType: function(enabled) {
this._entryModified();
if (enabled === this.group.getEffectiveEnableAutoType()) {
enabled = null;
}
this.entry.autoType.enabled = enabled;
this._buildAutoType();
},
setAutoTypeObfuscation: function(enabled) {
this._entryModified();
this.entry.autoType.obfuscation =
enabled ? kdbxweb.Consts.AutoTypeObfuscationOptions.UseClipboard : kdbxweb.Consts.AutoTypeObfuscationOptions.None;
this._buildAutoType();
},
setAutoTypeSeq: function(seq) {
this._entryModified();
this.entry.autoType.defaultSequence = seq || undefined;
this._buildAutoType();
}
});

View File

@ -170,7 +170,7 @@ var GroupModel = MenuItemModel.extend({
parentGroup = parentGroup.parentGroup;
}
if (enabled === parentEnableSearching) {
enabled = undefined;
enabled = null;
}
this.group.enableSearching = enabled;
this.set('enableSearching', this.group.enableSearching);
@ -199,7 +199,7 @@ var GroupModel = MenuItemModel.extend({
parentGroup = parentGroup.parentGroup;
}
if (enabled === parentEnableAutoType) {
enabled = undefined;
enabled = null;
}
this.group.enableAutoType = enabled;
this.set('enableAutoType', this.group.enableAutoType);
@ -222,6 +222,17 @@ var GroupModel = MenuItemModel.extend({
this.set('autoTypeSeq', this.group.defaultAutoTypeSeq);
},
getEffectiveAutoTypeSeq: function() {
var grp = this;
while (grp) {
if (grp.get('autoTypeSeq')) {
return grp.get('autoTypeSeq');
}
grp = grp.parentGroup;
}
return '{username}{tab}{password}{enter}';
},
moveToTrash: function() {
this.file.setModified();
this.file.db.remove(this.group);

View File

@ -218,6 +218,13 @@ var Locale = {
detMenuHideEmpty: 'Hide empty fields',
detMenuAddField: 'Add {}',
detSetupOtp: 'One-time passwords',
detAutoType: 'Auto-type',
detAutoTypeEnabled: 'Enable auto-type for this entry',
detAutoTypeSequence: 'Keystrokes',
detAutoTypeInput: 'Input',
detAutoTypeShortcuts: 'Shortcuts',
detAutoTypeShortcutsDesc: '{} or {} while the app is inactive',
detAutoTypeObfuscation: 'Mix real keystrokes with random',
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',

View File

@ -0,0 +1,63 @@
'use strict';
var Backbone = require('backbone'),
Locale = require('../../util/locale'),
FeatureDetector = require('../../util/feature-detector'),
AutoType = require('../../auto-type');
var DetailsAutoTypeView = Backbone.View.extend({
template: require('templates/details/details-auto-type.hbs'),
events: {
'input #details__auto-type-sequence': 'seqInput',
'keypress #details__auto-type-sequence': 'seqKeyPress',
'keydown #details__auto-type-sequence': 'seqKeyDown',
'change #details__auto-type-enabled': 'enabledChange',
'change #details__auto-type-obfuscation': 'obfuscationChange'
},
render: function() {
var detAutoTypeShortcutsDesc = Locale.detAutoTypeShortcutsDesc
.replace('{}', FeatureDetector.actionShortcutSymbol() + 'T')
.replace('{}', FeatureDetector.globalShortcutSymbol() + 'T');
this.renderTemplate({
enabled: this.model.getEffectiveEnableAutoType(),
obfuscation: this.model.autoTypeObfuscation,
sequence: this.model.autoTypeSequence,
defaultSequence: this.model.group.getEffectiveAutoTypeSeq(),
detAutoTypeShortcutsDesc: detAutoTypeShortcutsDesc
});
return this;
},
seqInput: function(e) {
var that = this;
var el = e.target;
var seq = el.value;
AutoType.validate(this.model, seq, function(err) {
$(el).toggleClass('input--error', !!err);
if (!err) {
that.model.setAutoTypeSeq(seq);
}
});
},
seqKeyPress: function(e) {
e.stopPropagation();
},
seqKeyDown: function(e) {
e.stopPropagation();
},
enabledChange: function(e) {
this.model.setEnableAutoType(e.target.checked);
},
obfuscationChange: function(e) {
this.model.setAutoTypeObfuscation(e.target.checked);
}
});
module.exports = DetailsAutoTypeView;

View File

@ -18,13 +18,14 @@ var Backbone = require('backbone'),
DetailsHistoryView = require('./details-history-view'),
DetailsAttachmentView = require('./details-attachment-view'),
DetailsAddFieldView = require('./details-add-field-view'),
DetailsAutoTypeView = require('./details-auto-type-view'),
DropdownView = require('../../views/dropdown-view'),
Keys = require('../../const/keys'),
KeyHandler = require('../../comp/key-handler'),
Alerts = require('../../comp/alerts'),
CopyPaste = require('../../comp/copy-paste'),
OtpQrReqder = require('../../comp/otp-qr-reader'),
AutoType = require('../../comp/auto-type'),
AutoType = require('../../auto-type'),
Format = require('../../util/format'),
Locale = require('../../util/locale'),
Tip = require('../../util/tip'),
@ -100,14 +101,7 @@ var DetailsView = Backbone.View.extend({
render: function () {
this.removeScroll();
this.removeFieldViews();
if (this.views.sub) {
this.views.sub.remove();
delete this.views.sub;
}
if (this.views.dropdownView) {
this.views.dropdownView.remove();
delete this.views.dropdownView;
}
this.removeInnerViews();
if (!this.model) {
this.$el.html(this.emptyTemplate());
return;
@ -244,6 +238,9 @@ var DetailsView = Backbone.View.extend({
moreOptions.push({value: 'toggle-empty', icon: 'eye-slash', text: Locale.detMenuHideEmpty});
}
moreOptions.push({value: 'otp', icon: 'clock-o', text: Locale.detSetupOtp});
if (AutoType.enabled) {
moreOptions.push({value: 'auto-type', icon: 'keyboard-o', text: Locale.detAutoType});
}
var rect = this.moreView.labelEl[0].getBoundingClientRect();
dropdownView.render({
position: {top: rect.bottom, left: rect.left},
@ -269,6 +266,9 @@ var DetailsView = Backbone.View.extend({
case 'otp':
this.setupOtp();
break;
case 'auto-type':
this.toggleAutoType();
break;
default:
if (e.item.lastIndexOf('add:', 0) === 0) {
var fieldName = e.item.substr(4);
@ -757,6 +757,18 @@ var DetailsView = Backbone.View.extend({
}
},
toggleAutoType: function() {
if (this.views.autoType) {
this.views.autoType.remove();
delete this.views.autoType;
return;
}
this.views.autoType = new DetailsAutoTypeView({
el: this.$el.find('.details__body-fields'),
model: this.model
}).render();
},
autoType: function() {
var entry = this.model;
AutoType.hideWindow(function() {

View File

@ -241,6 +241,10 @@
line-height: 1.5em;
overflow: hidden;
}
>label {
font-weight: normal;
@include user-select(none);
}
.details__body-aside & {
@include th { color: muted-color(); }
a { @include th { color: muted-color(); } }

View File

@ -0,0 +1,27 @@
<div class="details__auto-type">
<div class="details__field">
<div class="details__field-label">{{res 'detAutoType'}}</div>
<div class="details__field-value">
<input type="checkbox" class="input-base" id="details__auto-type-enabled" {{#if enabled}}checked{{/if}} />
<label for="details__auto-type-enabled">{{res 'detAutoTypeEnabled'}}</label>
</div>
</div>
<div class="details__field">
<div class="details__field-label">{{res 'detAutoTypeSequence'}}</div>
<div class="details__field-value">
<input type="text" id="details__auto-type-sequence" maxlength="1024"
value="{{sequence}}" placeholder="{{defaultSequence}}" />
</div>
</div>
<div class="details__field">
<div class="details__field-label">{{res 'detAutoTypeInput'}}</div>
<div class="details__field-value">
<input type="checkbox" class="input-base" id="details__auto-type-obfuscation" {{#if obfuscation}}checked{{/if}} />
<label for="details__auto-type-obfuscation">{{res 'detAutoTypeObfuscation'}}</label>
</div>
</div>
<div class="details__field">
<div class="details__field-label">{{res 'detAutoTypeShortcuts'}}</div>
<div class="details__field-value">{{{detAutoTypeShortcutsDesc}}}</div>
</div>
</div>