mirror of https://github.com/keeweb/keeweb.git
entry auto-type ui
This commit is contained in:
parent
b00e1196a1
commit
48f10ca4a7
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
var Launcher = require('../../comp/launcher');
|
||||
var Launcher = require('../comp/launcher');
|
||||
|
||||
var AutoTypeEmitterFactory = {
|
||||
create: function(callback) {
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
var Launcher = require('../../comp/launcher');
|
||||
var Launcher = require('../comp/launcher');
|
||||
|
||||
var AutoTypeHelperFactory = {
|
||||
create: function() {
|
|
@ -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);
|
|
@ -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);
|
|
@ -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 = {
|
|
@ -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 = {
|
|
@ -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 = {
|
|
@ -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' +
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
// var Launcher = require('../../launcher');
|
||||
// var Launcher = require('../../comp/launcher');
|
||||
|
||||
var AutoTypeHelper = function() {
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
// var Launcher = require('../../launcher');
|
||||
// var Launcher = require('../../comp/launcher');
|
||||
|
||||
var AutoTypeHelper = function() {
|
||||
};
|
|
@ -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(',') + ']';
|
||||
},
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
|
@ -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() {
|
||||
|
|
|
@ -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(); } }
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue