keeweb/app/scripts/views/fields/field-view-text.js

273 lines
8.6 KiB
JavaScript
Raw Normal View History

2019-09-15 14:16:32 +02:00
import kdbxweb from 'kdbxweb';
2019-09-16 22:57:56 +02:00
import { Events } from 'framework/events';
2019-09-15 14:16:32 +02:00
import { KeyHandler } from 'comp/browser/key-handler';
import { Keys } from 'const/keys';
import { Features } from 'util/features';
import { MdToHtml } from 'util/formatting/md-to-html';
2019-09-27 07:37:26 +02:00
import { PasswordPresenter } from 'util/formatting/password-presenter';
2019-09-15 14:16:32 +02:00
import { Tip } from 'util/ui/tip';
import { FieldView } from 'views/fields/field-view';
import { GeneratorView } from 'views/generator-view';
2019-09-18 07:08:23 +02:00
import { escape } from 'util/fn';
2015-10-17 23:49:24 +02:00
2019-09-16 19:55:06 +02:00
class FieldViewText extends FieldView {
hasOptions = true;
2019-09-16 23:36:22 +02:00
constructor(model, options) {
super(model, options);
this.once('remove', () => this.stopBlurListener());
}
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-27 07:37:26 +02:00
? PasswordPresenter.presentValueWithLineBreaks(value)
2019-09-18 07:08:23 +02:00
: escape(value || '').replace(/\n/g, '<br/>');
2019-09-16 19:55:06 +02:00
}
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 || '';
2019-09-16 19:55:06 +02:00
}
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
});
2019-09-16 23:36:22 +02:00
const fieldValueBlurBound = e => this.fieldValueBlur(e);
Events.on('click', fieldValueBlurBound);
this.stopBlurListener = () => Events.off('click', fieldValueBlurBound);
2019-09-16 22:57:56 +02:00
this.listenTo(Events, 'main-window-will-close', this.externalEndEdit);
this.listenTo(Events, 'user-idle', this.externalEndEdit);
2015-10-17 23:49:24 +02:00
if (this.model.multiline) {
this.setInputHeight();
}
2019-09-15 08:11:11 +02:00
if (Features.isMobile) {
2017-04-15 23:26:52 +02:00
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]);
2019-09-16 19:55:06 +02:00
}
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-09-16 19:55:06 +02:00
}
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-09-16 19:55:06 +02:00
}
2015-10-20 21:58:07 +02:00
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();
const shadowSpread = parseInt(this.input.css('--focus-shadow-spread')) || 0;
2019-08-16 23:05:39 +02:00
this.gen = new GeneratorView({
2019-09-15 18:33:45 +02:00
pos: { left: fieldRect.left, top: fieldRect.bottom + shadowSpread },
password: this.value
2019-09-16 20:54:14 +02:00
});
this.gen.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-09-16 19:55:06 +02:00
}
2015-10-20 21:58:07 +02:00
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-09-16 19:55:06 +02:00
}
2015-10-20 21:58:07 +02:00
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-09-16 19:55:06 +02:00
}
2015-10-20 21:58:07 +02:00
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);
}
2019-09-16 19:55:06 +02:00
}
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-09-16 19:55:06 +02:00
}
2015-10-17 23:49:24 +02:00
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
}
2019-09-16 19:55:06 +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-09-16 19:55:06 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
fieldValueInputClick() {
2015-10-20 21:58:07 +02:00
if (this.gen) {
this.hideGenerator();
}
2019-09-16 19:55:06 +02:00
}
2015-10-20 21:58:07 +02:00
2019-08-18 10:17:09 +02:00
fieldValueInputMouseDown(e) {
2016-01-16 13:35:34 +01:00
e.stopPropagation();
2019-09-16 19:55:06 +02:00
}
2016-01-16 13:35:34 +01:00
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)) {
if (this.gen) {
e.target.value = this.gen.password;
this.hideGenerator();
return;
}
this.stopBlurListener();
this.endEdit(e.target.value);
2015-10-17 23:49:24 +02:00
}
} else if (code === Keys.DOM_VK_ESCAPE) {
this.stopBlurListener();
2015-10-17 23:49:24 +02:00
this.endEdit();
} else if (code === Keys.DOM_VK_TAB) {
e.preventDefault();
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) {
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();
2019-09-16 19:55:06 +02:00
}
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-09-16 19:55:06 +02:00
}
2016-09-19 19:05:23 +02:00
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;
}
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);
}
2019-09-16 19:55:06 +02:00
super.endEdit(newVal, extra);
}
2015-10-20 21:58:07 +02:00
2019-09-16 23:36:22 +02:00
stopBlurListener() {}
2019-09-16 22:57:56 +02:00
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();
}
2019-09-16 19:55:06 +02:00
}
2017-04-15 23:26:52 +02:00
mobileFieldControlTouchStart(e) {
this.$el.attr('active-mobile-action', $(e.target).data('action'));
2019-09-16 19:55:06 +02:00
}
2017-04-15 23:26:52 +02:00
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
}
2019-09-16 19:55:06 +02:00
}
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');
}
2015-10-17 23:49:24 +02:00
}
2019-09-16 19:55:06 +02:00
}
2015-10-17 23:49:24 +02:00
2019-09-15 14:16:32 +02:00
export { FieldViewText };