Rank calculation when searching and a new sorter based on that rank.

Rank is calculated as requested in #814.
String rank calculation was taken from the auto type filter and
moved into a mixin.
Fixes #814
This commit is contained in:
Dennis Ploeger 2019-01-04 09:05:44 +01:00
parent 5204350453
commit 1b896f3218
8 changed files with 98 additions and 20 deletions

View File

@ -1,4 +1,5 @@
const EntryCollection = require('../collections/entry-collection');
const Ranking = require('../mixins/ranking');
const urlPartsRegex = /^(\w+:\/\/)?(?:(?:www|wwws|secure)\.)?([^\/]+)\/?(.*)/;
@ -78,22 +79,6 @@ AutoTypeFilter.prototype.getEntryRank = function(entry) {
return rank;
};
AutoTypeFilter.prototype.getStringRank = function(s1, s2) {
let ix = s1.indexOf(s2);
if (ix === 0 && s1.length === s2.length) {
return 10;
} else if (ix === 0) {
return 5;
} else if (ix > 0) {
return 3;
}
ix = s2.indexOf(s1);
if (ix === 0) {
return 5;
} else if (ix > 0) {
return 3;
}
return 0;
};
_.extend(AutoTypeFilter.prototype, Ranking);
module.exports = AutoTypeFilter;

View File

@ -19,7 +19,9 @@ const EntryCollection = Backbone.Collection.extend({
'-created': Comparators.dateComparator('created', false),
'updated': Comparators.dateComparator('updated', true),
'-updated': Comparators.dateComparator('updated', false),
'-attachments': function(x, y) { return this.attachmentSortVal(x).localeCompare(this.attachmentSortVal(y)); }
'-attachments': function(x, y) { return this.attachmentSortVal(x).localeCompare(this.attachmentSortVal(y)); },
'-rank': Comparators.rankComparator(false),
'rank': Comparators.rankComparator(true),
},
defaultComparator: 'title',

View File

@ -27,6 +27,7 @@
"help": "Help",
"settings": "Settings",
"plugins": "Plugins",
"rank": "Rank",
"cache": "cache",
"file": "file",
@ -146,6 +147,9 @@
"searchOptions": "Options",
"searchCase": "Match case",
"searchRegex": "RegEx",
"searchRank": "Rank",
"searchBestWorst": "Best {} Worst",
"searchWorstBest": "Worst {} Best",
"openOpen": "Open",
"openNew": "New",

View File

@ -0,0 +1,21 @@
const Ranking = {
getStringRank: function(s1, s2) {
let ix = s1.indexOf(s2);
if (ix === 0 && s1.length === s2.length) {
return 10;
} else if (ix === 0) {
return 5;
} else if (ix > 0) {
return 3;
}
ix = s2.indexOf(s1);
if (ix === 0) {
return 5;
} else if (ix > 0) {
return 3;
}
return 0;
}
};
module.exports = Ranking;

View File

@ -5,6 +5,7 @@ const Color = require('../util/color');
const IconUrl = require('../util/icon-url');
const Otp = require('../util/otp');
const kdbxweb = require('kdbxweb');
const Ranking = require('../mixins/ranking');
const EntryModel = Backbone.Model.extend({
defaults: {},
@ -17,6 +18,7 @@ const EntryModel = Backbone.Model.extend({
fieldRefIds: { T: 'Title', U: 'UserName', P: 'Password', A: 'URL', N: 'Notes' },
initialize: function() {
this.set({rank: 0});
},
setEntry: function(entry, group, file) {
@ -185,6 +187,10 @@ const EntryModel = Backbone.Model.extend({
},
matches: function(filter) {
if (filter && filter.textLower && (!filter.advanced || filter.advanced.rank)) {
// update rank, when rank calculation is enabled
this.updateRank(filter.textLower);
}
return !filter ||
(!filter.tagLower || this.searchTags.indexOf(filter.tagLower) >= 0) &&
(!filter.textLower || (filter.advanced ? this.matchesAdv(filter) : this.searchText.indexOf(filter.textLower) >= 0)) &&
@ -635,6 +641,51 @@ const EntryModel = Backbone.Model.extend({
this.entry.times.creationTime = this.entry.times.lastModTime;
this.entry.fields.Title = '';
this._fillByEntry();
},
updateRank: function(searchString) {
let rank = 0;
const ranking = [
{
field: 'Title',
multiplicator: 10
},
{
field: 'URL',
multiplicator: 8
},
{
field: 'UserName',
multiplicator: 5
},
{
field: 'Notes',
multiplicator: 2
}
];
const fieldNames = Object.keys(this.fields);
_.forEach(fieldNames, field => {
ranking.push(
{
field: field,
multiplicator: 2
}
);
});
_.forEach(ranking, rankingEntry => {
if (this._getFieldString(rankingEntry.field).toLowerCase() !== '') {
const calculatedRank = this.getStringRank(
searchString,
this._getFieldString(rankingEntry.field).toLowerCase()
) * rankingEntry.multiplicator;
rank += calculatedRank;
}
});
this.set({rank: rank});
}
});
@ -657,4 +708,6 @@ EntryModel.newEntry = function(group, file) {
return model;
};
_.extend(EntryModel.prototype, Ranking);
module.exports = EntryModel;

View File

@ -13,6 +13,14 @@ const Comparators = {
}
},
rankComparator: function(asc) {
if (asc) {
return function (x, y) { return x.get('rank') - y.get('rank'); };
} else {
return function (x, y) { return y.get('rank') - x.get('rank'); };
}
},
dateComparator: function(field, asc) {
if (asc) {
return function (x, y) { return x[field] - y[field]; };

View File

@ -43,7 +43,9 @@ const ListSearchView = Backbone.View.extend({
{ value: '-created', icon: 'sort-numeric-desc', loc: () => Locale.searchCreated + ' ' + this.addArrow(Locale.searchNO) },
{ value: 'updated', icon: 'sort-numeric-asc', loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchON) },
{ value: '-updated', icon: 'sort-numeric-desc', loc: () => Locale.searchUpdated + ' ' + this.addArrow(Locale.searchNO) },
{ value: '-attachments', icon: 'sort-amount-desc', loc: () => Locale.searchAttachments }
{ value: '-attachments', icon: 'sort-amount-desc', loc: () => Locale.searchAttachments },
{ value: '-rank', icon: 'sort-numeric-desc', loc: () => Locale.searchRank + ' ' + this.addArrow(Locale.searchBestWorst) },
{ value: 'rank', icon: 'sort-numeric-asc', loc: () => Locale.searchRank + ' ' + this.addArrow(Locale.searchWorstBest) },
];
this.sortIcons = {};
this.sortOptions.forEach(function(opt) {
@ -55,7 +57,8 @@ const ListSearchView = Backbone.View.extend({
url: true, protect: false,
notes: true, pass: false,
cs: false, regex: false,
history: false, title: true
history: false, title: true,
rank: true
};
if (this.model.advancedSearch) {
this.advancedSearch = _.extend({}, this.model.advancedSearch);

View File

@ -39,6 +39,8 @@
{{#if adv.regex}}checked{{/if}}><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"
{{#if adv.history}}checked{{/if}}><label for="list__search-adv-check-history">{{Res 'history'}}</label></div>
<div class="list__search-check"><input type="checkbox" id="list__search-adv-check-rank" data-id="rank"
{{#if adv.rank}}checked{{/if}}><label for="list__search-adv-check-rank">{{Res 'rank'}}</label></div>
</div>
</div>
</div>