advanced search

This commit is contained in:
Antelle 2016-01-13 23:33:14 +03:00
parent dc385b9dae
commit 9e72045a2a
7 changed files with 151 additions and 7 deletions

View File

@ -11,7 +11,7 @@ var EntryModel = Backbone.Model.extend({
defaults: {},
urlRegex: /^https?:\/\//i,
buildInFields: ['Title', 'Password', 'Notes', 'URL', 'UserName'],
builtInFields: ['Title', 'Password', 'Notes', 'URL', 'UserName'],
initialize: function() {
},
@ -111,7 +111,7 @@ var EntryModel = Backbone.Model.extend({
},
_fieldsToModel: function(fields) {
return _.omit(fields, this.buildInFields);
return _.omit(fields, this.builtInFields);
},
_attachmentsToModel: function(binaries) {
@ -142,10 +142,89 @@ var EntryModel = Backbone.Model.extend({
matches: function(filter) {
return !filter ||
(!filter.tagLower || this.searchTags.indexOf(filter.tagLower) >= 0) &&
(!filter.textLower || this.searchText.indexOf(filter.textLower) >= 0) &&
(!filter.textLower || (filter.advanced ? this.matchesAdv(filter) : this.searchText.indexOf(filter.textLower) >= 0)) &&
(!filter.color || filter.color === true && this.searchColor || this.searchColor === filter.color);
},
matchesAdv: function(filter) {
var adv = filter.advanced;
var 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) {
search = filter.text;
match = this.matchString;
} else {
search = filter.textLower;
match = this.matchStringLower;
}
if (this.matchEntry(this.entry, adv, match, search)) {
return true;
}
if (adv.history) {
for (var i = 0, len = this.entry.history.length; i < len; i++) {
if (this.matchEntry(this.entry.history[0], adv, match, search)) {
return true;
}
}
}
return false;
},
matchString: function(str, find) {
return str.indexOf(find) >= 0;
},
matchStringLower: function(str, findLower) {
return str.toLowerCase().indexOf(findLower) >= 0;
},
matchRegex: function(str, regex) {
return regex.test(str);
},
matchEntry: function(entry, adv, compare, search) {
var matchField = this.matchField;
if (adv.user && matchField(entry, 'UserName', compare, search)) {
return true;
}
if (adv.url && matchField(entry, 'URL', compare, search)) {
return true;
}
if (adv.notes && matchField(entry, 'Notes', compare, search)) {
return true;
}
if (adv.pass && matchField(entry, 'Password', compare, search)) {
return true;
}
if (adv.other && matchField(entry, 'Title', compare, search)) {
return true;
}
var matches = false;
if (adv.other || adv.protect) {
var builtInFields = this.builtInFields;
var fieldNames = Object.keys(entry.fields);
matches = fieldNames.some(function (field) {
if (builtInFields.indexOf(field) >= 0) {
return false;
}
if (typeof entry.fields[field] === 'string') {
return adv.other && matchField(entry, field, compare, search);
} else {
return adv.protect && matchField(entry, field, compare, search);
}
});
}
return matches;
},
matchField: function(entry, field, compare, search) {
var val = entry.fields[field];
return val ? compare(val.getText ? val.getText() : val, search) : false;
},
setColor: function(color) {
this._entryModified();
this.entry.bgColor = Color.getKnownBgColor(color);
@ -181,7 +260,7 @@ var EntryModel = Backbone.Model.extend({
setField: function(field, val) {
this._entryModified();
var hasValue = val && (typeof val === 'string' || val instanceof kdbxweb.ProtectedValue && val.byteLength);
if (hasValue || this.buildInFields.indexOf(field) >= 0) {
if (hasValue || this.builtInFields.indexOf(field) >= 0) {
this.entry.fields[field] = val;
} else {
delete this.entry.fields[field];

View File

@ -10,6 +10,7 @@ var Locale = {
name: 'name',
icon: 'icon',
title: 'title',
password: 'password',
user: 'user',
website: 'website',
tags: 'tags',
@ -69,6 +70,14 @@ var Locale = {
searchON: 'Old &rarr; New',
searchNO: 'New &rarr; Old',
searchShiftClickOr: 'shift-click or',
searchAdvTitle: 'Toggle advanced search',
searchSearchIn: 'Search in',
searchOther: 'Other fields',
searchProtect: 'Secure fields',
searchOptions: 'Options',
searchCase: 'Match case',
searchRegex: 'RegEx',
searchHistory: 'History',
openOpen: 'Open',
openNew: 'New',

View File

@ -20,6 +20,7 @@ Tip.prototype.init = function() {
}
this.el.removeAttr('title');
this.el.mouseenter(this.mouseenter.bind(this)).mouseleave(this.mouseleave.bind(this));
this.el.click(this.mouseleave.bind(this));
};
Tip.prototype.show = function() {

View File

@ -17,7 +17,8 @@ var ListSearchView = Backbone.View.extend({
'click .list__search-btn-new': 'createOptionsClick',
'click .list__search-btn-sort': 'sortOptionsClick',
'click .list__search-icon-search': 'advancedSearchClick',
'click .list__search-btn-menu': 'toggleMenu'
'click .list__search-btn-menu': 'toggleMenu',
'change .list__search-adv input[type=checkbox]': 'toggleAdvCheck'
},
views: null,
@ -26,6 +27,8 @@ var ListSearchView = Backbone.View.extend({
sortOptions: null,
sortIcons: null,
createOptions: null,
advancedSearchEnabled: false,
advancedSearch: null,
initialize: function () {
this.sortOptions = [
@ -51,6 +54,13 @@ var ListSearchView = Backbone.View.extend({
{ value: 'group', icon: 'folder', text: 'Group' }
];
this.views = {};
this.advancedSearch = {
user: true, other: true,
url: true, protect: false,
notes: true, pass: false,
cs: false, regex: false,
history: false
};
KeyHandler.onKey(Keys.DOM_VK_F, this.findKeyPress, this, KeyHandler.SHORTCUT_ACTION);
KeyHandler.onKey(Keys.DOM_VK_N, this.newKeyPress, this, KeyHandler.SHORTCUT_OPT);
KeyHandler.onKey(Keys.DOM_VK_DOWN, this.downKeyPress, this);
@ -185,13 +195,21 @@ var ListSearchView = Backbone.View.extend({
},
advancedSearchClick: function() {
require('../comp/alerts').notImplemented();
this.advancedSearchEnabled = !this.advancedSearchEnabled;
this.$el.find('.list__search-adv').toggleClass('hide', !this.advancedSearchEnabled);
Backbone.trigger('add-filter', { advanced: this.advancedSearchEnabled ? this.advancedSearch : false });
},
toggleMenu: function() {
Backbone.trigger('toggle-menu');
},
toggleAdvCheck: function(e) {
var setting = $(e.target).data('id');
this.advancedSearch[setting] = e.target.checked;
Backbone.trigger('add-filter', { advanced: this.advancedSearch });
},
hideSearchOptions: function() {
if (this.views.searchDropdown) {
this.views.searchDropdown.remove();

View File

@ -41,6 +41,7 @@
@include align-items(stretch);
@include flex-direction(row);
@include justify-content(flex-start);
@include flex-wrap(wrap);
}
&-field-wrap {
@include flex(1);
@ -79,6 +80,19 @@
}
}
}
&-adv {
@include flex(100%);
@include display(flex);
@include align-items(stretch);
@include flex-direction(row);
@include flex-wrap(wrap);
&-text {
@include flex(100%);
}
}
&-check {
@include flex(50%);
}
}
&__table {

View File

@ -5,7 +5,7 @@
</div>
<div class="list__search-field-wrap">
<input type="text" class="list__search-field input-padding-right" autocomplete="off">
<i class="list__search-icon-search fa fa-search"></i>
<i class="list__search-icon-search fa fa-search" title="{{res 'searchAdvTitle'}}"></i>
</div>
<div class="list__search-btn-new" title="{{res 'searchAddNew'}}">
<i class="fa fa-plus"></i>
@ -13,5 +13,27 @@
<div class="list__search-btn-sort" title="{{res 'searchSort'}}">
<i class="fa fa-sort-alpha-asc"></i>
</div>
<div class="list__search-adv hide">
<div class="list__search-adv-text">{{res 'searchSearchIn'}}:</div>
<div class="list__search-check"><input type="checkbox" id="list__searcn-adv-check-user" data-id="user" checked
><label for="list__searcn-adv-check-user">{{Res 'user'}}</label></div>
<div class="list__search-check"><input type="checkbox" id="list__search-adv-check-other" data-id="other" checked
><label for="list__search-adv-check-other">{{res 'searchOther'}}</label></div>
<div class="list__search-check"><input type="checkbox" id="list__search-adv-check-website" data-id="url" checked
><label for="list__search-adv-check-website">{{Res 'website'}}</label></div>
<div class="list__search-check"><input type="checkbox" id="list__search-adv-check-protect" data-id="protect"
><label for="list__search-adv-check-protect">{{res 'searchProtect'}}</label></div>
<div class="list__search-check"><input type="checkbox" id="list__search-adv-check-notes" data-id="notes" checked
><label for="list__search-adv-check-notes">{{Res 'notes'}}</label></div>
<div class="list__search-check"><input type="checkbox" id="list__search-adv-check-pass" data-id="pass"
><label for="list__search-adv-check-pass">{{Res 'password'}}</label></div>
<div class="list__search-adv-text">{{res 'searchOptions'}}:</div>
<div class="list__search-check"><input type="checkbox" id="list__search-adv-check-cs" data-id="cs"
><label for="list__search-adv-check-cs">{{res 'searchCase'}}</label></div>
<div class="list__search-check"><input type="checkbox" id="list__search-adv-check-regex" data-id="regex"
><label for="list__search-adv-check-regex">{{res 'searchRegex'}}</label></div>
<div class="list__search-check"><input type="checkbox" id="list__search-adv-check-history" data-id="history"
><label for="list__search-adv-check-history">{{res 'searchHistory'}}</label></div>
</div>
</div>
</div>

View File

@ -2,6 +2,7 @@ Release notes
-------------
##### v0.6.0 (not released yet)
Improvements
`+` advanced search
`+` save at exit for desktop app
`+` more reliable binaries management
`+` string resources globalization