mirror of https://github.com/keeweb/keeweb.git
search
This commit is contained in:
parent
aa5a9fc6d2
commit
9263caed59
|
@ -0,0 +1,12 @@
|
|||
const BuiltInFields = [
|
||||
'Title',
|
||||
'Password',
|
||||
'UserName',
|
||||
'URL',
|
||||
'Notes',
|
||||
'TOTP Seed',
|
||||
'TOTP Settings',
|
||||
'_etm_template_uuid'
|
||||
];
|
||||
|
||||
export { BuiltInFields };
|
|
@ -3,29 +3,26 @@ import { Model } from 'framework/model';
|
|||
import { AppSettingsModel } from 'models/app-settings-model';
|
||||
import { KdbxToHtml } from 'comp/format/kdbx-to-html';
|
||||
import { IconMap } from 'const/icon-map';
|
||||
import { BuiltInFields } from 'const/entry-fields';
|
||||
import { AttachmentModel } from 'models/attachment-model';
|
||||
import { Color } from 'util/data/color';
|
||||
import { Otp } from 'util/data/otp';
|
||||
import { Ranking } from 'util/data/ranking';
|
||||
import { IconUrlFormat } from 'util/formatting/icon-url-format';
|
||||
import { omit } from 'util/fn';
|
||||
import { EntrySearch } from 'util/entry-search';
|
||||
|
||||
const UrlRegex = /^https?:\/\//i;
|
||||
const FieldRefRegex = /^\{REF:([TNPAU])@I:(\w{32})}$/;
|
||||
const BuiltInFields = [
|
||||
'Title',
|
||||
'Password',
|
||||
'UserName',
|
||||
'URL',
|
||||
'Notes',
|
||||
'TOTP Seed',
|
||||
'TOTP Settings',
|
||||
'_etm_template_uuid'
|
||||
];
|
||||
const FieldRefFields = ['title', 'password', 'user', 'url', 'notes'];
|
||||
const FieldRefIds = { T: 'Title', U: 'UserName', P: 'Password', A: 'URL', N: 'Notes' };
|
||||
|
||||
class EntryModel extends Model {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._search = new EntrySearch(this);
|
||||
}
|
||||
|
||||
setEntry(entry, group, file) {
|
||||
this.entry = entry;
|
||||
this.group = group;
|
||||
|
@ -206,172 +203,15 @@ class EntryModel extends Model {
|
|||
}
|
||||
|
||||
matches(filter) {
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
if (filter.tagLower) {
|
||||
if (this.searchTags.indexOf(filter.tagLower) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (filter.textLower) {
|
||||
if (filter.advanced) {
|
||||
if (!this.matchesAdv(filter)) {
|
||||
return false;
|
||||
}
|
||||
} else if (filter.textLowerParts) {
|
||||
const parts = filter.textLowerParts;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (this.searchText.indexOf(parts[i]) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.searchText.indexOf(filter.textLower) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filter.color) {
|
||||
if (filter.color === true) {
|
||||
if (!this.searchColor) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (this.searchColor !== filter.color) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filter.autoType) {
|
||||
if (!this.autoTypeEnabled) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return this._search.matches(filter);
|
||||
}
|
||||
|
||||
matchesAdv(filter) {
|
||||
const adv = filter.advanced;
|
||||
let search, match;
|
||||
if (adv.regex) {
|
||||
try {
|
||||
search = new RegExp(filter.text, adv.cs ? '' : 'i');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
match = this.matchRegex;
|
||||
} else if (adv.cs) {
|
||||
if (filter.textParts) {
|
||||
search = filter.textParts;
|
||||
match = this.matchStringMulti.bind(this, false);
|
||||
} else {
|
||||
search = filter.text;
|
||||
match = this.matchString;
|
||||
}
|
||||
} else {
|
||||
if (filter.textLowerParts) {
|
||||
search = filter.textLowerParts;
|
||||
match = this.matchStringMulti.bind(this, true);
|
||||
} else {
|
||||
search = filter.textLower;
|
||||
match = this.matchStringLower;
|
||||
}
|
||||
}
|
||||
if (this.matchEntry(this.entry, adv, match, search)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.history) {
|
||||
for (let i = 0, len = this.entry.history.length; i < len; i++) {
|
||||
if (this.matchEntry(this.entry.history[0], adv, match, search)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
getAllFields() {
|
||||
return this.entry.fields;
|
||||
}
|
||||
|
||||
matchString(str, find) {
|
||||
if (str.isProtected) {
|
||||
return str.includes(find);
|
||||
}
|
||||
return str.indexOf(find) >= 0;
|
||||
}
|
||||
|
||||
matchStringLower(str, findLower) {
|
||||
if (str.isProtected) {
|
||||
return str.includesLower(findLower);
|
||||
}
|
||||
return str.toLowerCase().indexOf(findLower) >= 0;
|
||||
}
|
||||
|
||||
matchStringMulti(lower, str, find, context) {
|
||||
for (let i = 0; i < find.length; i++) {
|
||||
const item = find[i];
|
||||
let strMatches;
|
||||
if (lower) {
|
||||
strMatches = str.isProtected ? str.includesLower(item) : str.includes(item);
|
||||
} else {
|
||||
strMatches = str.isProtected ? str.includes(item) : str.includes(item);
|
||||
}
|
||||
if (strMatches) {
|
||||
if (context.matches) {
|
||||
if (!context.matches.includes(item)) {
|
||||
context.matches.push(item);
|
||||
}
|
||||
} else {
|
||||
context.matches = [item];
|
||||
}
|
||||
}
|
||||
}
|
||||
return context.matches && context.matches.length === find.length;
|
||||
}
|
||||
|
||||
matchRegex(str, regex) {
|
||||
if (str.isProtected) {
|
||||
str = str.getText();
|
||||
}
|
||||
return regex.test(str);
|
||||
}
|
||||
|
||||
matchEntry(entry, adv, compare, search) {
|
||||
const matchField = this.matchField;
|
||||
const context = {};
|
||||
if (adv.user && matchField(entry, 'UserName', compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.url && matchField(entry, 'URL', compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.notes && matchField(entry, 'Notes', compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.pass && matchField(entry, 'Password', compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.title && matchField(entry, 'Title', compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
let matches = false;
|
||||
if (adv.other || adv.protect) {
|
||||
const fieldNames = Object.keys(entry.fields);
|
||||
matches = fieldNames.some(field => {
|
||||
if (BuiltInFields.indexOf(field) >= 0) {
|
||||
return false;
|
||||
}
|
||||
if (typeof entry.fields[field] === 'string') {
|
||||
return adv.other && matchField(entry, field, compare, search, context);
|
||||
} else {
|
||||
return adv.protect && matchField(entry, field, compare, search, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
matchField(entry, field, compare, search, context) {
|
||||
const val = entry.fields[field];
|
||||
return val ? compare(val, search, context) : false;
|
||||
getHistoryEntriesForSearch() {
|
||||
return this.entry.history;
|
||||
}
|
||||
|
||||
resolveFieldReferences() {
|
||||
|
|
|
@ -12,10 +12,10 @@ class ExternalDeviceModel extends Model {
|
|||
close() {}
|
||||
|
||||
forEachEntry(filter, callback) {
|
||||
for (const entry of this.entries.filter(entry =>
|
||||
entry.title.toLowerCase().includes(filter.textLower)
|
||||
)) {
|
||||
callback(entry);
|
||||
for (const entry of this.entries) {
|
||||
if (entry.matches(filter)) {
|
||||
callback(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,43 @@
|
|||
import { Model } from 'framework/model';
|
||||
import { EntrySearch } from 'util/entry-search';
|
||||
|
||||
class ExternalEntryModel extends Model {
|
||||
tags = [];
|
||||
fields = {};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._buildSearchText();
|
||||
this._buildFields();
|
||||
this._search = new EntrySearch(this);
|
||||
}
|
||||
|
||||
get external() {
|
||||
return true;
|
||||
}
|
||||
|
||||
matches(filter) {
|
||||
return this._search.matches(filter);
|
||||
}
|
||||
|
||||
_buildSearchText() {
|
||||
let text = '';
|
||||
if (this.title) {
|
||||
text += this.title.toLowerCase();
|
||||
}
|
||||
if (this.user) {
|
||||
text += '\n' + this.user.toLowerCase();
|
||||
}
|
||||
this.searchText = text;
|
||||
}
|
||||
|
||||
_buildFields() {
|
||||
this.fields.Title = this.title;
|
||||
}
|
||||
|
||||
getAllFields() {
|
||||
return this.fields;
|
||||
}
|
||||
}
|
||||
|
||||
ExternalEntryModel.defineModelProperties({
|
||||
|
@ -16,7 +47,9 @@ ExternalEntryModel.defineModelProperties({
|
|||
description: undefined,
|
||||
fields: undefined,
|
||||
icon: undefined,
|
||||
tags: undefined
|
||||
tags: undefined,
|
||||
searchText: undefined,
|
||||
_search: undefined
|
||||
});
|
||||
|
||||
export { ExternalEntryModel };
|
||||
|
|
|
@ -2,8 +2,10 @@ import { ExternalEntryModel } from 'models/external/external-entry-model';
|
|||
|
||||
class ExternalOtpEntryModel extends ExternalEntryModel {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.description = props.user;
|
||||
super({
|
||||
...props,
|
||||
description: props.user
|
||||
});
|
||||
}
|
||||
|
||||
initOtpGenerator() {
|
||||
|
@ -16,6 +18,11 @@ class ExternalOtpEntryModel extends ExternalEntryModel {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
_buildFields() {
|
||||
super._buildFields();
|
||||
this.fields.UserName = this.user;
|
||||
}
|
||||
}
|
||||
|
||||
ExternalOtpEntryModel.defineModelProperties({
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
import { BuiltInFields } from 'const/entry-fields';
|
||||
|
||||
class EntrySearch {
|
||||
constructor(model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
matches(filter) {
|
||||
if (!filter) {
|
||||
return true;
|
||||
}
|
||||
if (filter.tagLower) {
|
||||
if (this.model.searchTags && this.model.searchTags.indexOf(filter.tagLower) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (filter.textLower) {
|
||||
if (filter.advanced) {
|
||||
if (!this.matchesAdv(filter)) {
|
||||
return false;
|
||||
}
|
||||
} else if (filter.textLowerParts) {
|
||||
const parts = filter.textLowerParts;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (this.model.searchText && this.model.searchText.indexOf(parts[i]) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.model.searchText && this.model.searchText.indexOf(filter.textLower) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filter.color) {
|
||||
if (filter.color === true) {
|
||||
if (!this.model.searchColor) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (this.model.searchColor !== filter.color) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filter.autoType) {
|
||||
if (!this.model.autoTypeEnabled) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
matchesAdv(filter) {
|
||||
const adv = filter.advanced;
|
||||
let search, match;
|
||||
if (adv.regex) {
|
||||
try {
|
||||
search = new RegExp(filter.text, adv.cs ? '' : 'i');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
match = EntrySearch.matchRegex;
|
||||
} else if (adv.cs) {
|
||||
if (filter.textParts) {
|
||||
search = filter.textParts;
|
||||
match = EntrySearch.matchStringMulti;
|
||||
} else {
|
||||
search = filter.text;
|
||||
match = EntrySearch.matchString;
|
||||
}
|
||||
} else {
|
||||
if (filter.textLowerParts) {
|
||||
search = filter.textLowerParts;
|
||||
match = EntrySearch.matchStringMultiLower;
|
||||
} else {
|
||||
search = filter.textLower;
|
||||
match = EntrySearch.matchStringLower;
|
||||
}
|
||||
}
|
||||
if (EntrySearch.matchFields(this.model.getAllFields(), adv, match, search)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.history && this.model.getHistoryEntriesForSearch) {
|
||||
for (const historyEntry of this.model.getHistoryEntriesForSearch()) {
|
||||
if (EntrySearch.matchFields(historyEntry.fields, adv, match, search)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static matchString(str, find) {
|
||||
if (str.isProtected) {
|
||||
return str.includes(find);
|
||||
}
|
||||
return str.indexOf(find) >= 0;
|
||||
}
|
||||
|
||||
static matchStringLower(str, findLower) {
|
||||
if (str.isProtected) {
|
||||
return str.includesLower(findLower);
|
||||
}
|
||||
return str.toLowerCase().indexOf(findLower) >= 0;
|
||||
}
|
||||
|
||||
static matchStringMulti(str, find, context, lower) {
|
||||
for (let i = 0; i < find.length; i++) {
|
||||
const item = find[i];
|
||||
let strMatches;
|
||||
if (lower) {
|
||||
strMatches = str.isProtected ? str.includesLower(item) : str.includes(item);
|
||||
} else {
|
||||
strMatches = str.isProtected ? str.includes(item) : str.includes(item);
|
||||
}
|
||||
if (strMatches) {
|
||||
if (context.matches) {
|
||||
if (!context.matches.includes(item)) {
|
||||
context.matches.push(item);
|
||||
}
|
||||
} else {
|
||||
context.matches = [item];
|
||||
}
|
||||
}
|
||||
}
|
||||
return context.matches && context.matches.length === find.length;
|
||||
}
|
||||
|
||||
static matchStringMultiLower(str, find, context, lower) {
|
||||
return EntrySearch.matchStringLower(str, find, context, true);
|
||||
}
|
||||
|
||||
static matchRegex(str, regex) {
|
||||
if (str.isProtected) {
|
||||
str = str.getText();
|
||||
}
|
||||
return regex.test(str);
|
||||
}
|
||||
|
||||
static matchFields(fields, adv, compare, search) {
|
||||
const context = {};
|
||||
const matchField = EntrySearch.matchField;
|
||||
if (adv.user && matchField(fields.UserName, compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.url && matchField(fields.URL, compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.notes && matchField(fields.Notes, compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.pass && matchField(fields.Password, compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
if (adv.title && matchField(fields.Title, compare, search, context)) {
|
||||
return true;
|
||||
}
|
||||
let matches = false;
|
||||
if (adv.other || adv.protect) {
|
||||
const fieldNames = Object.keys(fields);
|
||||
matches = fieldNames.some(field => {
|
||||
if (BuiltInFields.indexOf(field) >= 0) {
|
||||
return false;
|
||||
}
|
||||
if (typeof fields[field] === 'string') {
|
||||
return adv.other && matchField(fields[field], compare, search, context);
|
||||
} else {
|
||||
return adv.protect && matchField(fields[field], compare, search, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
static matchField(val, compare, search, context) {
|
||||
return val ? compare(val, search, context) : false;
|
||||
}
|
||||
}
|
||||
|
||||
export { EntrySearch };
|
Loading…
Reference in New Issue