diff --git a/app/scripts/models/entry-model.js b/app/scripts/models/entry-model.js index 3c8b373e..77ba3a96 100644 --- a/app/scripts/models/entry-model.js +++ b/app/scripts/models/entry-model.js @@ -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]; diff --git a/app/scripts/util/locale.js b/app/scripts/util/locale.js index 16274270..e53dffc2 100644 --- a/app/scripts/util/locale.js +++ b/app/scripts/util/locale.js @@ -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', diff --git a/app/scripts/util/tip.js b/app/scripts/util/tip.js index a909c95e..4eb6e2c7 100644 --- a/app/scripts/util/tip.js +++ b/app/scripts/util/tip.js @@ -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() { diff --git a/app/scripts/views/list-search-view.js b/app/scripts/views/list-search-view.js index 5c3df1ea..74292501 100644 --- a/app/scripts/views/list-search-view.js +++ b/app/scripts/views/list-search-view.js @@ -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(); diff --git a/app/styles/areas/_list.scss b/app/styles/areas/_list.scss index c2a930cf..2eb2cd58 100644 --- a/app/styles/areas/_list.scss +++ b/app/styles/areas/_list.scss @@ -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 { diff --git a/app/templates/list-search.hbs b/app/templates/list-search.hbs index f068432c..bbd968d8 100644 --- a/app/templates/list-search.hbs +++ b/app/templates/list-search.hbs @@ -5,7 +5,7 @@
- +
@@ -13,5 +13,27 @@
+
+
{{res 'searchSearchIn'}}:
+
+
+
+
+
+
+
{{res 'searchOptions'}}:
+
+
+
+
diff --git a/release-notes.md b/release-notes.md index 3415398a..ebaa1ede 100644 --- a/release-notes.md +++ b/release-notes.md @@ -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