2017-01-31 07:50:28 +01:00
|
|
|
const Backbone = require('backbone');
|
2019-09-14 22:12:02 +02:00
|
|
|
const kdbxweb = require('kdbxweb');
|
2017-01-31 07:50:28 +01:00
|
|
|
const FieldView = require('./field-view');
|
|
|
|
const GeneratorView = require('../generator-view');
|
|
|
|
const KeyHandler = require('../../comp/key-handler');
|
|
|
|
const Keys = require('../../const/keys');
|
|
|
|
const PasswordGenerator = require('../../util/password-generator');
|
2017-04-15 23:26:52 +02:00
|
|
|
const FeatureDetector = require('../../util/feature-detector');
|
2017-04-16 10:19:46 +02:00
|
|
|
const Tip = require('../../util/tip');
|
2019-09-14 22:12:02 +02:00
|
|
|
const MdToHtml = require('../../util/md-to-html');
|
2015-10-17 23:49:24 +02:00
|
|
|
|
2017-01-31 07:50:28 +01:00
|
|
|
const FieldViewText = FieldView.extend({
|
2019-08-18 10:17:09 +02:00
|
|
|
renderValue(value) {
|
2019-09-14 22:12:02 +02:00
|
|
|
if (this.model.markdown) {
|
|
|
|
if (value && value.isProtected) {
|
|
|
|
value = value.getText();
|
|
|
|
}
|
|
|
|
return MdToHtml.convert(value);
|
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
return value && value.isProtected
|
2019-09-11 23:50:18 +02:00
|
|
|
? PasswordGenerator.presentValueWithLineBreaks(value)
|
2016-07-17 13:30:38 +02:00
|
|
|
: _.escape(value || '').replace(/\n/g, '<br/>');
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
getEditValue(value) {
|
2016-01-16 15:19:33 +01:00
|
|
|
return value && value.isProtected ? value.getText() : value || '';
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
startEdit() {
|
2017-01-31 07:50:28 +01:00
|
|
|
const text = this.getEditValue(this.value);
|
|
|
|
const isProtected = !!(this.value && this.value.isProtected);
|
2016-01-17 13:23:07 +01:00
|
|
|
this.$el.toggleClass('details__field--protected', isProtected);
|
2015-10-17 23:49:24 +02:00
|
|
|
this.input = $(document.createElement(this.model.multiline ? 'textarea' : 'input'));
|
|
|
|
this.valueEl.html('').append(this.input);
|
2019-08-16 23:05:39 +02:00
|
|
|
this.input
|
|
|
|
.attr({ autocomplete: 'off', spellcheck: 'false' })
|
|
|
|
.val(text)
|
|
|
|
.focus()[0]
|
|
|
|
.setSelectionRange(text.length, text.length);
|
2015-10-17 23:49:24 +02:00
|
|
|
this.input.bind({
|
|
|
|
input: this.fieldValueInput.bind(this),
|
|
|
|
keydown: this.fieldValueKeydown.bind(this),
|
2015-10-20 21:58:07 +02:00
|
|
|
keypress: this.fieldValueInput.bind(this),
|
2016-01-16 13:35:34 +01:00
|
|
|
click: this.fieldValueInputClick.bind(this),
|
|
|
|
mousedown: this.fieldValueInputMouseDown.bind(this)
|
2015-10-17 23:49:24 +02:00
|
|
|
});
|
2016-12-05 11:18:07 +01:00
|
|
|
this.listenTo(Backbone, 'click', this.fieldValueBlur);
|
|
|
|
this.listenTo(Backbone, 'main-window-will-close user-idle', this.externalEndEdit);
|
2015-10-17 23:49:24 +02:00
|
|
|
if (this.model.multiline) {
|
|
|
|
this.setInputHeight();
|
|
|
|
}
|
2017-04-15 23:26:52 +02:00
|
|
|
if (FeatureDetector.isMobile) {
|
|
|
|
this.createMobileControls();
|
|
|
|
}
|
2015-11-08 19:24:37 +01:00
|
|
|
if (this.model.canGen) {
|
2019-08-16 23:05:39 +02:00
|
|
|
$('<div/>')
|
|
|
|
.addClass('details__field-value-btn details__field-value-btn-gen')
|
|
|
|
.appendTo(this.valueEl)
|
2015-10-20 21:58:07 +02:00
|
|
|
.click(this.showGeneratorClick.bind(this))
|
|
|
|
.mousedown(this.showGenerator.bind(this));
|
|
|
|
}
|
2017-04-16 10:19:46 +02:00
|
|
|
Tip.hideTip(this.valueEl[0]);
|
|
|
|
Tip.hideTip(this.labelEl[0]);
|
2015-10-20 21:58:07 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
createMobileControls() {
|
2017-04-15 23:26:52 +02:00
|
|
|
this.mobileControls = {};
|
|
|
|
['cancel', 'apply'].forEach(action => {
|
|
|
|
this.mobileControls[action] = $('<div/>')
|
|
|
|
.addClass('details__field-value-btn details__field-value-btn-' + action)
|
|
|
|
.appendTo(this.labelEl)
|
|
|
|
.data('action', action)
|
|
|
|
.on({
|
2017-04-16 11:56:06 +02:00
|
|
|
mousedown: this.mobileFieldControlMouseDown.bind(this),
|
2017-04-15 23:26:52 +02:00
|
|
|
touchstart: this.mobileFieldControlTouchStart.bind(this),
|
2017-04-16 10:05:55 +02:00
|
|
|
touchend: this.mobileFieldControlTouchEnd.bind(this),
|
|
|
|
touchmove: this.mobileFieldControlTouchMove.bind(this)
|
2017-04-15 23:26:52 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
showGeneratorClick(e) {
|
2015-10-20 21:58:07 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
if (!this.gen) {
|
|
|
|
this.input.focus();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
showGenerator() {
|
2015-10-20 21:58:07 +02:00
|
|
|
if (this.gen) {
|
|
|
|
this.hideGenerator();
|
|
|
|
} else {
|
2017-01-31 07:50:28 +01:00
|
|
|
const fieldRect = this.input[0].getBoundingClientRect();
|
2019-09-08 11:50:26 +02:00
|
|
|
const shadowSpread = parseInt(this.input.css('--focus-shadow-spread'));
|
2019-08-16 23:05:39 +02:00
|
|
|
this.gen = new GeneratorView({
|
2019-08-18 08:05:38 +02:00
|
|
|
model: {
|
2019-09-08 11:50:26 +02:00
|
|
|
pos: { left: fieldRect.left, top: fieldRect.bottom + shadowSpread },
|
2019-08-18 08:05:38 +02:00
|
|
|
password: this.value
|
|
|
|
}
|
2019-08-16 23:05:39 +02:00
|
|
|
}).render();
|
2015-10-20 21:58:07 +02:00
|
|
|
this.gen.once('remove', this.generatorClosed.bind(this));
|
|
|
|
this.gen.once('result', this.generatorResult.bind(this));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
hideGenerator() {
|
2015-10-20 21:58:07 +02:00
|
|
|
if (this.gen) {
|
2017-01-31 07:50:28 +01:00
|
|
|
const gen = this.gen;
|
2015-10-20 21:58:07 +02:00
|
|
|
delete this.gen;
|
|
|
|
gen.remove();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
generatorClosed() {
|
2015-10-20 21:58:07 +02:00
|
|
|
if (this.gen) {
|
|
|
|
delete this.gen;
|
|
|
|
this.endEdit();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
generatorResult(password) {
|
2015-10-20 21:58:07 +02:00
|
|
|
if (this.gen) {
|
|
|
|
delete this.gen;
|
|
|
|
this.endEdit(password);
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
setInputHeight() {
|
2017-01-31 07:50:28 +01:00
|
|
|
const MinHeight = 18;
|
2015-10-17 23:49:24 +02:00
|
|
|
this.input.height(MinHeight);
|
2017-01-31 07:50:28 +01:00
|
|
|
let newHeight = this.input[0].scrollHeight;
|
2015-10-17 23:49:24 +02:00
|
|
|
if (newHeight <= MinHeight) {
|
|
|
|
newHeight = MinHeight;
|
|
|
|
}
|
|
|
|
this.input.height(newHeight);
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
fieldValueBlur() {
|
2016-01-16 13:35:34 +01:00
|
|
|
if (!this.gen && this.input) {
|
|
|
|
this.endEdit(this.input.val());
|
2015-10-20 21:58:07 +02:00
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
fieldValueInput(e) {
|
2015-10-17 23:49:24 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
if (this.model.multiline) {
|
|
|
|
this.setInputHeight();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
fieldValueInputClick() {
|
2015-10-20 21:58:07 +02:00
|
|
|
if (this.gen) {
|
|
|
|
this.hideGenerator();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
fieldValueInputMouseDown(e) {
|
2016-01-16 13:35:34 +01:00
|
|
|
e.stopPropagation();
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
fieldValueKeydown(e) {
|
2015-11-17 22:49:12 +01:00
|
|
|
KeyHandler.reg();
|
2017-01-31 07:50:28 +01:00
|
|
|
const code = e.keyCode || e.which;
|
2015-10-17 23:49:24 +02:00
|
|
|
if (code === Keys.DOM_VK_RETURN) {
|
2016-03-12 12:25:25 +01:00
|
|
|
if (!this.model.multiline || (!e.altKey && !e.shiftKey && !e.ctrlKey)) {
|
2016-08-14 21:14:20 +02:00
|
|
|
if (this.gen) {
|
|
|
|
e.target.value = this.gen.password;
|
|
|
|
this.hideGenerator();
|
|
|
|
return;
|
|
|
|
}
|
2016-08-21 20:38:18 +02:00
|
|
|
this.stopBlurListener();
|
2016-08-14 21:14:20 +02:00
|
|
|
this.endEdit(e.target.value);
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
|
|
|
} else if (code === Keys.DOM_VK_ESCAPE) {
|
2016-08-21 20:38:18 +02:00
|
|
|
this.stopBlurListener();
|
2015-10-17 23:49:24 +02:00
|
|
|
this.endEdit();
|
|
|
|
} else if (code === Keys.DOM_VK_TAB) {
|
|
|
|
e.preventDefault();
|
2016-08-21 20:38:18 +02:00
|
|
|
this.stopBlurListener();
|
2015-10-17 23:49:24 +02:00
|
|
|
this.endEdit(e.target.value, { tab: { field: this.model.name, prev: e.shiftKey } });
|
2016-08-15 22:08:17 +02:00
|
|
|
} else if (code === Keys.DOM_VK_G && e.metaKey) {
|
2016-08-14 18:40:44 +02:00
|
|
|
e.preventDefault();
|
|
|
|
this.showGenerator();
|
2016-08-22 18:41:40 +02:00
|
|
|
} else if (code === Keys.DOM_VK_S && (e.metaKey || e.ctrlKey)) {
|
|
|
|
this.stopBlurListener();
|
|
|
|
this.endEdit(e.target.value);
|
|
|
|
return;
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
2016-08-22 18:41:40 +02:00
|
|
|
e.stopPropagation();
|
2015-10-17 23:49:24 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
externalEndEdit() {
|
2016-09-19 19:05:23 +02:00
|
|
|
if (this.input) {
|
|
|
|
this.endEdit(this.input.val());
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
endEdit(newVal, extra) {
|
2015-10-20 21:58:07 +02:00
|
|
|
if (this.gen) {
|
|
|
|
this.hideGenerator();
|
|
|
|
}
|
2015-10-17 23:49:24 +02:00
|
|
|
if (!this.editing) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
delete this.input;
|
2017-04-15 23:26:52 +02:00
|
|
|
if (this.mobileControls) {
|
|
|
|
this.mobileControls.cancel.remove();
|
|
|
|
this.mobileControls.apply.remove();
|
|
|
|
delete this.mobileControls;
|
|
|
|
}
|
2016-08-21 20:38:18 +02:00
|
|
|
this.stopBlurListener();
|
2015-10-17 23:49:24 +02:00
|
|
|
if (typeof newVal === 'string' && this.value instanceof kdbxweb.ProtectedValue) {
|
|
|
|
newVal = kdbxweb.ProtectedValue.fromString(newVal);
|
|
|
|
}
|
|
|
|
if (typeof newVal === 'string') {
|
|
|
|
newVal = $.trim(newVal);
|
|
|
|
}
|
|
|
|
FieldView.prototype.endEdit.call(this, newVal, extra);
|
2015-10-20 21:58:07 +02:00
|
|
|
},
|
|
|
|
|
2019-08-18 10:17:09 +02:00
|
|
|
stopBlurListener() {
|
2016-08-21 20:38:18 +02:00
|
|
|
this.stopListening(Backbone, 'click main-window-will-close', this.fieldValueBlur);
|
|
|
|
},
|
|
|
|
|
2017-04-16 11:56:06 +02:00
|
|
|
mobileFieldControlMouseDown(e) {
|
2017-04-15 23:26:52 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
this.stopBlurListener();
|
|
|
|
const action = $(e.target).data('action');
|
|
|
|
if (action === 'apply') {
|
|
|
|
this.endEdit(this.input.val());
|
|
|
|
} else {
|
|
|
|
this.endEdit();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
mobileFieldControlTouchStart(e) {
|
|
|
|
this.$el.attr('active-mobile-action', $(e.target).data('action'));
|
|
|
|
},
|
|
|
|
|
2017-04-16 10:05:55 +02:00
|
|
|
mobileFieldControlTouchEnd(e) {
|
|
|
|
const shouldExecute = this.$el.attr('active-mobile-action') === $(e.target).data('action');
|
2017-04-15 23:26:52 +02:00
|
|
|
this.$el.removeAttr('active-mobile-action');
|
2017-04-16 10:05:55 +02:00
|
|
|
if (shouldExecute) {
|
2017-04-16 11:57:25 +02:00
|
|
|
this.mobileFieldControlMouseDown(e);
|
2017-04-16 10:05:55 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
mobileFieldControlTouchMove(e) {
|
|
|
|
const touch = e.originalEvent.targetTouches[0];
|
|
|
|
const rect = touch.target.getBoundingClientRect();
|
2019-08-16 23:05:39 +02:00
|
|
|
const inside =
|
|
|
|
touch.clientX >= rect.left &&
|
|
|
|
touch.clientX <= rect.right &&
|
|
|
|
touch.clientY >= rect.top &&
|
|
|
|
touch.clientY <= rect.bottom;
|
2017-04-16 10:05:55 +02:00
|
|
|
if (inside) {
|
|
|
|
this.$el.attr('active-mobile-action', $(e.target).data('action'));
|
|
|
|
} else {
|
|
|
|
this.$el.removeAttr('active-mobile-action');
|
|
|
|
}
|
2017-04-15 23:26:52 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
render() {
|
2015-10-20 21:58:07 +02:00
|
|
|
FieldView.prototype.render.call(this);
|
2015-10-17 23:49:24 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = FieldViewText;
|