|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
import kdbxweb from 'kdbxweb';
|
|
|
|
|
import * as kdbxweb from 'kdbxweb';
|
|
|
|
|
import { Model } from 'framework/model';
|
|
|
|
|
import { AppSettingsModel } from 'models/app-settings-model';
|
|
|
|
|
import { KdbxToHtml } from 'comp/format/kdbx-to-html';
|
|
|
|
@ -52,7 +52,7 @@ class EntryModel extends Model {
|
|
|
|
|
this.icon = this._iconFromId(entry.icon);
|
|
|
|
|
this.tags = entry.tags;
|
|
|
|
|
this.color = this._colorToModel(entry.bgColor) || this._colorToModel(entry.fgColor);
|
|
|
|
|
this.fields = this._fieldsToModel(entry.fields);
|
|
|
|
|
this.fields = this._fieldsToModel();
|
|
|
|
|
this.attachments = this._attachmentsToModel(entry.binaries);
|
|
|
|
|
this.created = entry.times.creationTime;
|
|
|
|
|
this.updated = entry.times.lastModTime;
|
|
|
|
@ -71,7 +71,7 @@ class EntryModel extends Model {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_getPassword() {
|
|
|
|
|
const password = this.entry.fields.Password || kdbxweb.ProtectedValue.fromString('');
|
|
|
|
|
const password = this.entry.fields.get('Password') || kdbxweb.ProtectedValue.fromString('');
|
|
|
|
|
if (!password.isProtected) {
|
|
|
|
|
return kdbxweb.ProtectedValue.fromString(password);
|
|
|
|
|
}
|
|
|
|
@ -79,7 +79,7 @@ class EntryModel extends Model {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_getFieldString(field) {
|
|
|
|
|
const val = this.entry.fields[field];
|
|
|
|
|
const val = this.entry.fields.get(field);
|
|
|
|
|
if (!val) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
@ -103,7 +103,7 @@ class EntryModel extends Model {
|
|
|
|
|
|
|
|
|
|
_buildSearchText() {
|
|
|
|
|
let text = '';
|
|
|
|
|
for (const value of Object.values(this.entry.fields)) {
|
|
|
|
|
for (const value of this.entry.fields.values()) {
|
|
|
|
|
if (typeof value === 'string') {
|
|
|
|
|
text += value.toLowerCase() + '\n';
|
|
|
|
|
}
|
|
|
|
@ -122,7 +122,7 @@ class EntryModel extends Model {
|
|
|
|
|
this.customIconId = null;
|
|
|
|
|
if (this.entry.customIcon) {
|
|
|
|
|
this.customIcon = IconUrlFormat.toDataUrl(
|
|
|
|
|
this.file.db.meta.customIcons[this.entry.customIcon]
|
|
|
|
|
this.file.db.meta.customIcons.get(this.entry.customIcon.id)?.data
|
|
|
|
|
);
|
|
|
|
|
this.customIconId = this.entry.customIcon.toString();
|
|
|
|
|
}
|
|
|
|
@ -164,13 +164,13 @@ class EntryModel extends Model {
|
|
|
|
|
return color ? Color.getNearest(color) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_fieldsToModel(fields) {
|
|
|
|
|
return omit(fields, BuiltInFields);
|
|
|
|
|
_fieldsToModel() {
|
|
|
|
|
return omit(this.getAllFields(), BuiltInFields);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_attachmentsToModel(binaries) {
|
|
|
|
|
const att = [];
|
|
|
|
|
for (let [title, data] of Object.entries(binaries)) {
|
|
|
|
|
for (let [title, data] of binaries) {
|
|
|
|
|
if (data && data.ref) {
|
|
|
|
|
data = data.value;
|
|
|
|
|
}
|
|
|
|
@ -210,7 +210,11 @@ class EntryModel extends Model {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getAllFields() {
|
|
|
|
|
return this.entry.fields;
|
|
|
|
|
const fields = {};
|
|
|
|
|
for (const [key, value] of this.entry.fields) {
|
|
|
|
|
fields[key] = value;
|
|
|
|
|
}
|
|
|
|
|
return fields;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getHistoryEntriesForSearch() {
|
|
|
|
@ -232,7 +236,7 @@ class EntryModel extends Model {
|
|
|
|
|
getFieldValue(field) {
|
|
|
|
|
field = field.toLowerCase();
|
|
|
|
|
let resolvedField;
|
|
|
|
|
Object.keys(this.entry.fields).some((entryField) => {
|
|
|
|
|
[...this.entry.fields.keys()].some((entryField) => {
|
|
|
|
|
if (entryField.toLowerCase() === field) {
|
|
|
|
|
resolvedField = entryField;
|
|
|
|
|
return true;
|
|
|
|
@ -240,7 +244,7 @@ class EntryModel extends Model {
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
if (resolvedField) {
|
|
|
|
|
let fieldValue = this.entry.fields[resolvedField];
|
|
|
|
|
let fieldValue = this.entry.fields.get(resolvedField);
|
|
|
|
|
const refValue = this._resolveFieldReference(fieldValue);
|
|
|
|
|
if (refValue !== undefined) {
|
|
|
|
|
fieldValue = refValue;
|
|
|
|
@ -276,7 +280,7 @@ class EntryModel extends Model {
|
|
|
|
|
if (!entry) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
return entry.entry.fields[FieldRefIds[fieldRefId]];
|
|
|
|
|
return entry.entry.fields.get(FieldRefIds[fieldRefId]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setColor(color) {
|
|
|
|
@ -329,10 +333,10 @@ class EntryModel extends Model {
|
|
|
|
|
if (hasValue || allowEmpty || BuiltInFields.indexOf(field) >= 0) {
|
|
|
|
|
this._entryModified();
|
|
|
|
|
val = this.sanitizeFieldValue(val);
|
|
|
|
|
this.entry.fields[field] = val;
|
|
|
|
|
} else if (Object.prototype.hasOwnProperty.call(this.entry.fields, field)) {
|
|
|
|
|
this.entry.fields.set(field, val);
|
|
|
|
|
} else if (this.entry.fields.has(field)) {
|
|
|
|
|
this._entryModified();
|
|
|
|
|
delete this.entry.fields[field];
|
|
|
|
|
this.entry.fields.delete(field);
|
|
|
|
|
}
|
|
|
|
|
this._fillByEntry();
|
|
|
|
|
}
|
|
|
|
@ -347,7 +351,7 @@ class EntryModel extends Model {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hasField(field) {
|
|
|
|
|
return Object.prototype.hasOwnProperty.call(this.entry.fields, field);
|
|
|
|
|
return this.entry.fields.has(field);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addAttachment(name, data) {
|
|
|
|
@ -360,7 +364,7 @@ class EntryModel extends Model {
|
|
|
|
|
|
|
|
|
|
removeAttachment(name) {
|
|
|
|
|
this._entryModified();
|
|
|
|
|
delete this.entry.binaries[name];
|
|
|
|
|
this.entry.binaries.delete(name);
|
|
|
|
|
this._fillByEntry();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -390,8 +394,8 @@ class EntryModel extends Model {
|
|
|
|
|
this.entry.pushHistory();
|
|
|
|
|
this.unsaved = true;
|
|
|
|
|
this.file.setModified();
|
|
|
|
|
this.entry.fields = {};
|
|
|
|
|
this.entry.binaries = {};
|
|
|
|
|
this.entry.fields = new Map();
|
|
|
|
|
this.entry.binaries = new Map();
|
|
|
|
|
this.entry.copyFrom(historyEntry);
|
|
|
|
|
this._entryModified();
|
|
|
|
|
this._fillByEntry();
|
|
|
|
@ -402,8 +406,8 @@ class EntryModel extends Model {
|
|
|
|
|
this.unsaved = false;
|
|
|
|
|
const historyEntry = this.entry.history[this.entry.history.length - 1];
|
|
|
|
|
this.entry.removeHistory(this.entry.history.length - 1);
|
|
|
|
|
this.entry.fields = {};
|
|
|
|
|
this.entry.binaries = {};
|
|
|
|
|
this.entry.fields = new Map();
|
|
|
|
|
this.entry.binaries = new Map();
|
|
|
|
|
this.entry.copyFrom(historyEntry);
|
|
|
|
|
this._fillByEntry();
|
|
|
|
|
}
|
|
|
|
@ -476,14 +480,14 @@ class EntryModel extends Model {
|
|
|
|
|
otpUrl = Otp.makeUrl(args.key, args.step, args.size);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (this.entry.fields['TOTP Seed']) {
|
|
|
|
|
} else if (this.entry.fields.get('TOTP Seed')) {
|
|
|
|
|
// TrayTOTP plugin format
|
|
|
|
|
let secret = this.entry.fields['TOTP Seed'];
|
|
|
|
|
let secret = this.entry.fields.get('TOTP Seed');
|
|
|
|
|
if (secret.isProtected) {
|
|
|
|
|
secret = secret.getText();
|
|
|
|
|
}
|
|
|
|
|
if (secret) {
|
|
|
|
|
let settings = this.entry.fields['TOTP Settings'];
|
|
|
|
|
let settings = this.entry.fields.get('TOTP Settings');
|
|
|
|
|
if (settings && settings.isProtected) {
|
|
|
|
|
settings = settings.getText();
|
|
|
|
|
}
|
|
|
|
@ -522,8 +526,8 @@ class EntryModel extends Model {
|
|
|
|
|
|
|
|
|
|
setOtpUrl(url) {
|
|
|
|
|
this.setField('otp', url ? kdbxweb.ProtectedValue.fromString(url) : undefined);
|
|
|
|
|
delete this.entry.fields['TOTP Seed'];
|
|
|
|
|
delete this.entry.fields['TOTP Settings'];
|
|
|
|
|
this.entry.fields.delete('TOTP Seed');
|
|
|
|
|
this.entry.fields.delete('TOTP Settings');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getEffectiveEnableAutoType() {
|
|
|
|
@ -574,7 +578,7 @@ class EntryModel extends Model {
|
|
|
|
|
newEntry.entry.uuid = uuid;
|
|
|
|
|
newEntry.entry.times.update();
|
|
|
|
|
newEntry.entry.times.creationTime = newEntry.entry.times.lastModTime;
|
|
|
|
|
newEntry.entry.fields.Title = this.title + nameSuffix;
|
|
|
|
|
newEntry.entry.fields.set('Title', this.title + nameSuffix);
|
|
|
|
|
newEntry._fillByEntry();
|
|
|
|
|
this.file.reload();
|
|
|
|
|
return newEntry;
|
|
|
|
@ -586,7 +590,7 @@ class EntryModel extends Model {
|
|
|
|
|
this.entry.uuid = uuid;
|
|
|
|
|
this.entry.times.update();
|
|
|
|
|
this.entry.times.creationTime = this.entry.times.lastModTime;
|
|
|
|
|
this.entry.fields.Title = '';
|
|
|
|
|
this.entry.fields.set('Title', '');
|
|
|
|
|
this._fillByEntry();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -612,7 +616,7 @@ class EntryModel extends Model {
|
|
|
|
|
const allFields = Object.keys(fieldWeights).concat(Object.keys(this.fields));
|
|
|
|
|
|
|
|
|
|
return allFields.reduce((rank, fieldName) => {
|
|
|
|
|
const val = this.entry.fields[fieldName];
|
|
|
|
|
const val = this.entry.fields.get(fieldName);
|
|
|
|
|
if (!val) {
|
|
|
|
|
return rank;
|
|
|
|
|
}
|
|
|
|
@ -630,20 +634,20 @@ class EntryModel extends Model {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
canCheckPasswordIssues() {
|
|
|
|
|
return !this.entry.customData?.IgnorePwIssues;
|
|
|
|
|
return !this.entry.customData?.has('IgnorePwIssues');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setIgnorePasswordIssues() {
|
|
|
|
|
if (!this.entry.customData) {
|
|
|
|
|
this.entry.customData = {};
|
|
|
|
|
this.entry.customData = new Map();
|
|
|
|
|
}
|
|
|
|
|
this.entry.customData.IgnorePwIssues = '1';
|
|
|
|
|
this.entry.customData.set('IgnorePwIssues', '1');
|
|
|
|
|
this._entryModified();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getNextUrlFieldName() {
|
|
|
|
|
const takenFields = new Set(
|
|
|
|
|
Object.keys(this.entry.fields).filter((f) => f.startsWith(ExtraUrlFieldName))
|
|
|
|
|
[...this.entry.fields.keys()].filter((f) => f.startsWith(ExtraUrlFieldName))
|
|
|
|
|
);
|
|
|
|
|
for (let i = 0; ; i++) {
|
|
|
|
|
const fieldName = i ? `${ExtraUrlFieldName}_${i}` : ExtraUrlFieldName;
|
|
|
|
|