mirror of https://github.com/keeweb/keeweb.git
advanced search
This commit is contained in:
parent
dc385b9dae
commit
9e72045a2a
|
@ -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];
|
||||
|
|
|
@ -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 → New',
|
||||
searchNO: 'New → 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',
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue