Fill best matching password entry with Ctrl+Shift+F (#131)
This commit is contained in:
@@ -121,7 +121,7 @@ Browserpass was designed with an assumption that certain conventions are being f
|
|||||||
|
|
||||||
### First steps in browser extension
|
### First steps in browser extension
|
||||||
|
|
||||||
Click on the icon or use <kbd>Ctrl+Shift+L</kbd> to open Browserpass with the entries that match current domain.
|
Click on the icon or use <kbd>Ctrl+Shift+L</kbd> to open the Browserpass popup with the entries that match the current domain. You can also use <kbd>Ctrl+Shift+F</kbd> to fill the form with the best matching credentials without even opening the popup (the best matching credentials are the first ones on the list if you open the popup).
|
||||||
|
|
||||||
How to change the shortcut:
|
How to change the shortcut:
|
||||||
|
|
||||||
@@ -139,8 +139,9 @@ If you want to intentionally disable phishing attack protection and search the e
|
|||||||
Note: If the cursor is located in the search input field, every shortcut that works on the selected entry will be applied on the first entry in the popup list.
|
Note: If the cursor is located in the search input field, every shortcut that works on the selected entry will be applied on the first entry in the popup list.
|
||||||
|
|
||||||
| Shortcut | Action |
|
| Shortcut | Action |
|
||||||
| ---------------------------------------------------- | ----------------------------------------------- |
|
| ---------------------------------------------------- | ------------------------------------------------ |
|
||||||
| <kbd>Ctrl+Shift+L</kbd> | Open Browserpass popup |
|
| <kbd>Ctrl+Shift+L</kbd> | Open Browserpass popup |
|
||||||
|
| <kbd>Ctrl+Shift+F</kbd> | Fill the form with the best matching credentials |
|
||||||
| <kbd>Enter</kbd> | Submit form with currently selected credentials |
|
| <kbd>Enter</kbd> | Submit form with currently selected credentials |
|
||||||
| Arrow keys and <kbd>Tab</kbd> / <kbd>Shift+Tab</kbd> | Navigate popup list |
|
| Arrow keys and <kbd>Tab</kbd> / <kbd>Shift+Tab</kbd> | Navigate popup list |
|
||||||
| <kbd>Ctrl+C</kbd> | Copy password to clipboard |
|
| <kbd>Ctrl+C</kbd> | Copy password to clipboard |
|
||||||
@@ -151,7 +152,7 @@ Note: If the cursor is located in the search input field, every shortcut that wo
|
|||||||
|
|
||||||
### Password matching and sorting
|
### Password matching and sorting
|
||||||
|
|
||||||
When you first open Browserpass popup, you will see a badge with the current domain name in the search input field:
|
When you first open the Browserpass popup, you will see a badge with the current domain name in the search input field:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@@ -57,6 +57,30 @@ chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// handle keyboard shortcuts
|
||||||
|
chrome.commands.onCommand.addListener(async command => {
|
||||||
|
switch (command) {
|
||||||
|
case "fillBest":
|
||||||
|
try {
|
||||||
|
const settings = await getFullSettings();
|
||||||
|
if (settings.tab.url.match(/^(chrome|about):/)) {
|
||||||
|
// only fill on real domains
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleMessage(settings, { action: "listFiles" }, listResults => {
|
||||||
|
const logins = helpers.prepareLogins(listResults.files, settings);
|
||||||
|
const bestLogin = helpers.filterSortLogins(logins, "", true)[0];
|
||||||
|
if (bestLogin) {
|
||||||
|
handleMessage(settings, { action: "fill", login: bestLogin }, () => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
chrome.runtime.onInstalled.addListener(onExtensionInstalled);
|
chrome.runtime.onInstalled.addListener(onExtensionInstalled);
|
||||||
|
|
||||||
//----------------------------------- Function definitions ----------------------------------//
|
//----------------------------------- Function definitions ----------------------------------//
|
||||||
@@ -78,27 +102,18 @@ async function updateMatchingPasswordsCount(tabId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get tab info
|
// Get tab info
|
||||||
let currentDomain = undefined;
|
|
||||||
try {
|
try {
|
||||||
const tab = await chrome.tabs.get(tabId);
|
const tab = await chrome.tabs.get(tabId);
|
||||||
currentDomain = new URL(tab.url).hostname;
|
settings.host = new URL(tab.url).hostname;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Unable to determine domain of the tab with id ${tabId}`);
|
throw new Error(`Unable to determine domain of the tab with id ${tabId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let matchedPasswordsCount = 0;
|
const logins = helpers.prepareLogins(response.data.files, settings);
|
||||||
for (var storeId in response.data.files) {
|
const matchedPasswordsCount = logins.reduce(
|
||||||
for (var key in response.data.files[storeId]) {
|
(acc, login) => acc + (login.recent.count || login.inCurrentDomain ? 1 : 0),
|
||||||
const login = response.data.files[storeId][key].replace(/\.gpg$/i, "");
|
0
|
||||||
const domain = helpers.pathToDomain(storeId + "/" + login, currentDomain);
|
);
|
||||||
const inCurrentDomain =
|
|
||||||
currentDomain === domain || currentDomain.endsWith("." + domain);
|
|
||||||
const recent = settings.recent[sha1(currentDomain + sha1(storeId + sha1(login)))];
|
|
||||||
if (recent || inCurrentDomain) {
|
|
||||||
matchedPasswordsCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchedPasswordsCount) {
|
if (matchedPasswordsCount) {
|
||||||
// Set badge for the current tab
|
// Set badge for the current tab
|
||||||
|
240
src/helpers.js
240
src/helpers.js
@@ -1,10 +1,14 @@
|
|||||||
//------------------------------------- Initialisation --------------------------------------//
|
//------------------------------------- Initialisation --------------------------------------//
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
const FuzzySort = require("fuzzysort");
|
||||||
const TldJS = require("tldjs");
|
const TldJS = require("tldjs");
|
||||||
|
const sha1 = require("sha1");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
pathToDomain
|
pathToDomain,
|
||||||
|
prepareLogins,
|
||||||
|
filterSortLogins
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------------------------- Function definitions ----------------------------------//
|
//----------------------------------- Function definitions ----------------------------------//
|
||||||
@@ -37,3 +41,237 @@ function pathToDomain(path, currentHost) {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare list of logins based on provided files
|
||||||
|
*
|
||||||
|
* @since 3.1.0
|
||||||
|
*
|
||||||
|
* @param string array List of password files
|
||||||
|
* @param string object Settings object
|
||||||
|
* @return array List of logins
|
||||||
|
*/
|
||||||
|
function prepareLogins(files, settings) {
|
||||||
|
const logins = [];
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
for (let storeId in files) {
|
||||||
|
for (let key in files[storeId]) {
|
||||||
|
// set login fields
|
||||||
|
const login = {
|
||||||
|
index: index++,
|
||||||
|
store: settings.stores[storeId],
|
||||||
|
login: files[storeId][key].replace(/\.gpg$/i, ""),
|
||||||
|
allowFill: true
|
||||||
|
};
|
||||||
|
login.domain = pathToDomain(storeId + "/" + login.login, settings.host);
|
||||||
|
login.inCurrentDomain =
|
||||||
|
settings.host == login.domain || settings.host.endsWith("." + login.domain);
|
||||||
|
login.recent =
|
||||||
|
settings.recent[sha1(settings.host + sha1(login.store.id + sha1(login.login)))];
|
||||||
|
if (!login.recent) {
|
||||||
|
login.recent = {
|
||||||
|
when: 0,
|
||||||
|
count: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
logins.push(login);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return logins;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter and sort logins
|
||||||
|
*
|
||||||
|
* @since 3.1.0
|
||||||
|
*
|
||||||
|
* @param string array List of logins
|
||||||
|
* @param string object Settings object
|
||||||
|
* @return array Filtered and sorted list of logins
|
||||||
|
*/
|
||||||
|
function filterSortLogins(logins, searchQuery, currentDomainOnly) {
|
||||||
|
var fuzzyFirstWord = searchQuery.substr(0, 1) !== " ";
|
||||||
|
searchQuery = searchQuery.trim();
|
||||||
|
|
||||||
|
// get candidate list
|
||||||
|
var candidates = logins.map(candidate => {
|
||||||
|
let lastSlashIndex = candidate.login.lastIndexOf("/") + 1;
|
||||||
|
return Object.assign(candidate, {
|
||||||
|
path: candidate.login.substr(0, lastSlashIndex),
|
||||||
|
display: candidate.login.substr(lastSlashIndex)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var mostRecent = null;
|
||||||
|
if (currentDomainOnly) {
|
||||||
|
var recent = candidates.filter(function(login) {
|
||||||
|
if (login.recent.count > 0) {
|
||||||
|
// find most recently used login
|
||||||
|
if (!mostRecent || login.recent.when > mostRecent.recent.when) {
|
||||||
|
mostRecent = login;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
var remainingInCurrentDomain = candidates.filter(
|
||||||
|
login => login.inCurrentDomain && !login.recent.count
|
||||||
|
);
|
||||||
|
candidates = recent.concat(remainingInCurrentDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.sort((a, b) => {
|
||||||
|
// show most recent first
|
||||||
|
if (a === mostRecent) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b === mostRecent) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by frequency
|
||||||
|
var countDiff = b.recent.count - a.recent.count;
|
||||||
|
if (countDiff) {
|
||||||
|
return countDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by specificity, only if filtering for one domain
|
||||||
|
if (currentDomainOnly) {
|
||||||
|
var domainLevelsDiff =
|
||||||
|
(b.login.match(/\./g) || []).length - (a.login.match(/\./g) || []).length;
|
||||||
|
if (domainLevelsDiff) {
|
||||||
|
return domainLevelsDiff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort alphabetically
|
||||||
|
return a.login.localeCompare(b.login);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (searchQuery.length) {
|
||||||
|
let filter = searchQuery.split(/\s+/);
|
||||||
|
let fuzzyFilter = fuzzyFirstWord ? filter[0] : "";
|
||||||
|
let substringFilters = filter.slice(fuzzyFirstWord ? 1 : 0).map(w => w.toLowerCase());
|
||||||
|
|
||||||
|
// First reduce the list by running the substring search
|
||||||
|
substringFilters.forEach(function(word) {
|
||||||
|
candidates = candidates.filter(c => c.login.toLowerCase().indexOf(word) >= 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then run the fuzzy filter
|
||||||
|
let fuzzyResults = {};
|
||||||
|
if (fuzzyFilter) {
|
||||||
|
candidates = FuzzySort.go(fuzzyFilter, candidates, {
|
||||||
|
keys: ["login", "store.name"],
|
||||||
|
allowTypo: false
|
||||||
|
}).map(result => {
|
||||||
|
fuzzyResults[result.obj.login] = result;
|
||||||
|
return result.obj;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally highlight all matches
|
||||||
|
candidates = candidates.map(c => highlightMatches(c, fuzzyResults, substringFilters));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefix root entries with slash to let them have some visible path
|
||||||
|
candidates.forEach(c => {
|
||||||
|
c.path = c.path || "/";
|
||||||
|
});
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------- Private functions ----------------------------------//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight filter matches
|
||||||
|
*
|
||||||
|
* @since 3.0.0
|
||||||
|
*
|
||||||
|
* @param object entry password entry
|
||||||
|
* @param object fuzzyResults positions of fuzzy filter matches
|
||||||
|
* @param array substringFilters list of substring filters applied
|
||||||
|
* @return object entry with highlighted matches
|
||||||
|
*/
|
||||||
|
function highlightMatches(entry, fuzzyResults, substringFilters) {
|
||||||
|
// Add all positions of the fuzzy search to the array
|
||||||
|
let matches = (fuzzyResults[entry.login] && fuzzyResults[entry.login][0]
|
||||||
|
? fuzzyResults[entry.login][0].indexes
|
||||||
|
: []
|
||||||
|
).slice();
|
||||||
|
|
||||||
|
// Add all positions of substring searches to the array
|
||||||
|
let login = entry.login.toLowerCase();
|
||||||
|
for (let word of substringFilters) {
|
||||||
|
let startIndex = login.indexOf(word);
|
||||||
|
for (let i = 0; i < word.length; i++) {
|
||||||
|
matches.push(startIndex + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the final array of matches before
|
||||||
|
matches = sortUnique(matches, (a, b) => a - b);
|
||||||
|
|
||||||
|
const OPEN = "<em>";
|
||||||
|
const CLOSE = "</em>";
|
||||||
|
let highlighted = "";
|
||||||
|
var matchesIndex = 0;
|
||||||
|
var opened = false;
|
||||||
|
for (var i = 0; i < entry.login.length; ++i) {
|
||||||
|
var char = entry.login[i];
|
||||||
|
|
||||||
|
if (i == entry.path.length) {
|
||||||
|
if (opened) {
|
||||||
|
highlighted += CLOSE;
|
||||||
|
}
|
||||||
|
var path = highlighted;
|
||||||
|
highlighted = "";
|
||||||
|
if (opened) {
|
||||||
|
highlighted += OPEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches[matchesIndex] === i) {
|
||||||
|
matchesIndex++;
|
||||||
|
if (!opened) {
|
||||||
|
opened = true;
|
||||||
|
highlighted += OPEN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (opened) {
|
||||||
|
opened = false;
|
||||||
|
highlighted += CLOSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
highlighted += char;
|
||||||
|
}
|
||||||
|
if (opened) {
|
||||||
|
opened = false;
|
||||||
|
highlighted += CLOSE;
|
||||||
|
}
|
||||||
|
let display = highlighted;
|
||||||
|
|
||||||
|
return Object.assign(entry, {
|
||||||
|
path: path,
|
||||||
|
display: display
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort and remove duplicates
|
||||||
|
*
|
||||||
|
* @since 3.0.0
|
||||||
|
*
|
||||||
|
* @param array array items to sort
|
||||||
|
* @param function comparator sort comparator
|
||||||
|
* @return array sorted items without duplicates
|
||||||
|
*/
|
||||||
|
function sortUnique(array, comparator) {
|
||||||
|
return array
|
||||||
|
.sort(comparator)
|
||||||
|
.filter((elem, index, arr) => index == !arr.length || arr[index - 1] != elem);
|
||||||
|
}
|
||||||
|
@@ -39,6 +39,12 @@
|
|||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Ctrl+Shift+L"
|
"default": "Ctrl+Shift+L"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"fillBest": {
|
||||||
|
"suggested_key": {
|
||||||
|
"default": "Ctrl+Shift+F"
|
||||||
|
},
|
||||||
|
"description": "Fill form with the best matching credentials"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -42,6 +42,12 @@
|
|||||||
"suggested_key": {
|
"suggested_key": {
|
||||||
"default": "Ctrl+Shift+L"
|
"default": "Ctrl+Shift+L"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"fillBest": {
|
||||||
|
"suggested_key": {
|
||||||
|
"default": "Ctrl+Shift+F"
|
||||||
|
},
|
||||||
|
"description": "Fill form with the best matching credentials"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
module.exports = Interface;
|
module.exports = Interface;
|
||||||
|
|
||||||
const m = require("mithril");
|
const m = require("mithril");
|
||||||
const FuzzySort = require("fuzzysort");
|
|
||||||
const Moment = require("moment");
|
const Moment = require("moment");
|
||||||
const SearchInterface = require("./searchinterface");
|
const SearchInterface = require("./searchinterface");
|
||||||
|
const helpers = require("../helpers");
|
||||||
|
|
||||||
const LATEST_NATIVE_APP_VERSION = 3000003;
|
const LATEST_NATIVE_APP_VERSION = 3000003;
|
||||||
|
|
||||||
@@ -144,188 +144,11 @@ function view(ctl, params) {
|
|||||||
/**
|
/**
|
||||||
* Run a search
|
* Run a search
|
||||||
*
|
*
|
||||||
* @param string s Search string
|
* @param string searchQuery Search query
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
function search(s) {
|
function search(searchQuery) {
|
||||||
var self = this;
|
this.results = helpers.filterSortLogins(this.logins, searchQuery, this.currentDomainOnly);
|
||||||
var fuzzyFirstWord = s.substr(0, 1) !== " ";
|
|
||||||
s = s.trim();
|
|
||||||
|
|
||||||
// get candidate list
|
|
||||||
var candidates = this.logins.map(candidate => {
|
|
||||||
let lastSlashIndex = candidate.login.lastIndexOf("/") + 1;
|
|
||||||
return Object.assign(candidate, {
|
|
||||||
path: candidate.login.substr(0, lastSlashIndex),
|
|
||||||
display: candidate.login.substr(lastSlashIndex)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
var mostRecent = null;
|
|
||||||
if (this.currentDomainOnly) {
|
|
||||||
var recent = candidates.filter(function(login) {
|
|
||||||
if (login.recent.count > 0) {
|
|
||||||
// find most recently used login
|
|
||||||
if (!mostRecent || login.recent.when > mostRecent.recent.when) {
|
|
||||||
mostRecent = login;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
var remainingInCurrentDomain = candidates.filter(
|
|
||||||
login => login.inCurrentDomain && !login.recent.count
|
|
||||||
);
|
|
||||||
candidates = recent.concat(remainingInCurrentDomain);
|
|
||||||
}
|
|
||||||
candidates.sort((a, b) => {
|
|
||||||
// show most recent first
|
|
||||||
if (a === mostRecent) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (b === mostRecent) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by frequency
|
|
||||||
var countDiff = b.recent.count - a.recent.count;
|
|
||||||
if (countDiff) {
|
|
||||||
return countDiff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by specificity, only if filtering for one domain
|
|
||||||
if (this.currentDomainOnly) {
|
|
||||||
var domainLevelsDiff =
|
|
||||||
(b.login.match(/\./g) || []).length - (a.login.match(/\./g) || []).length;
|
|
||||||
if (domainLevelsDiff) {
|
|
||||||
return domainLevelsDiff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort alphabetically
|
|
||||||
return a.login.localeCompare(b.login);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (s.length) {
|
|
||||||
let filter = s.split(/\s+/);
|
|
||||||
let fuzzyFilter = fuzzyFirstWord ? filter[0] : "";
|
|
||||||
let substringFilters = filter.slice(fuzzyFirstWord ? 1 : 0).map(w => w.toLowerCase());
|
|
||||||
|
|
||||||
// First reduce the list by running the substring search
|
|
||||||
substringFilters.forEach(function(word) {
|
|
||||||
candidates = candidates.filter(c => c.login.toLowerCase().indexOf(word) >= 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then run the fuzzy filter
|
|
||||||
let fuzzyResults = {};
|
|
||||||
if (fuzzyFilter) {
|
|
||||||
candidates = FuzzySort.go(fuzzyFilter, candidates, {
|
|
||||||
keys: ["login", "store.name"],
|
|
||||||
allowTypo: false
|
|
||||||
}).map(result => {
|
|
||||||
fuzzyResults[result.obj.login] = result;
|
|
||||||
return result.obj;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally highlight all matches
|
|
||||||
candidates = candidates.map(c => highlightMatches(c, fuzzyResults, substringFilters));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefix root entries with slash to let them have some visible path
|
|
||||||
candidates.forEach(c => {
|
|
||||||
c.path = c.path || "/";
|
|
||||||
});
|
|
||||||
|
|
||||||
this.results = candidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Highlight filter matches
|
|
||||||
*
|
|
||||||
* @since 3.0.0
|
|
||||||
*
|
|
||||||
* @param object entry password entry
|
|
||||||
* @param object fuzzyResults positions of fuzzy filter matches
|
|
||||||
* @param array substringFilters list of substring filters applied
|
|
||||||
* @return object entry with highlighted matches
|
|
||||||
*/
|
|
||||||
function highlightMatches(entry, fuzzyResults, substringFilters) {
|
|
||||||
// Add all positions of the fuzzy search to the array
|
|
||||||
let matches = (fuzzyResults[entry.login] && fuzzyResults[entry.login][0]
|
|
||||||
? fuzzyResults[entry.login][0].indexes
|
|
||||||
: []
|
|
||||||
).slice();
|
|
||||||
|
|
||||||
// Add all positions of substring searches to the array
|
|
||||||
let login = entry.login.toLowerCase();
|
|
||||||
for (let word of substringFilters) {
|
|
||||||
let startIndex = login.indexOf(word);
|
|
||||||
for (let i = 0; i < word.length; i++) {
|
|
||||||
matches.push(startIndex + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the final array of matches before
|
|
||||||
matches = sortUnique(matches, (a, b) => a - b);
|
|
||||||
|
|
||||||
const OPEN = "<em>";
|
|
||||||
const CLOSE = "</em>";
|
|
||||||
let highlighted = "";
|
|
||||||
var matchesIndex = 0;
|
|
||||||
var opened = false;
|
|
||||||
for (var i = 0; i < entry.login.length; ++i) {
|
|
||||||
var char = entry.login[i];
|
|
||||||
|
|
||||||
if (i == entry.path.length) {
|
|
||||||
if (opened) {
|
|
||||||
highlighted += CLOSE;
|
|
||||||
}
|
|
||||||
var path = highlighted;
|
|
||||||
highlighted = "";
|
|
||||||
if (opened) {
|
|
||||||
highlighted += OPEN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matches[matchesIndex] === i) {
|
|
||||||
matchesIndex++;
|
|
||||||
if (!opened) {
|
|
||||||
opened = true;
|
|
||||||
highlighted += OPEN;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (opened) {
|
|
||||||
opened = false;
|
|
||||||
highlighted += CLOSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
highlighted += char;
|
|
||||||
}
|
|
||||||
if (opened) {
|
|
||||||
opened = false;
|
|
||||||
highlighted += CLOSE;
|
|
||||||
}
|
|
||||||
let display = highlighted;
|
|
||||||
|
|
||||||
return Object.assign(entry, {
|
|
||||||
path: path,
|
|
||||||
display: display
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort and remove duplicates
|
|
||||||
*
|
|
||||||
* @since 3.0.0
|
|
||||||
*
|
|
||||||
* @param array array items to sort
|
|
||||||
* @param function comparator sort comparator
|
|
||||||
* @return array sorted items without duplicates
|
|
||||||
*/
|
|
||||||
function sortUnique(array, comparator) {
|
|
||||||
return array
|
|
||||||
.sort(comparator)
|
|
||||||
.filter((elem, index, arr) => index == !arr.length || arr[index - 1] != elem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
require("chrome-extension-async");
|
require("chrome-extension-async");
|
||||||
const sha1 = require("sha1");
|
|
||||||
const Interface = require("./interface");
|
const Interface = require("./interface");
|
||||||
const helpers = require("../helpers");
|
const helpers = require("../helpers");
|
||||||
|
|
||||||
@@ -58,34 +57,11 @@ async function run() {
|
|||||||
throw new Error(response.message);
|
throw new Error(response.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
var logins = [];
|
const logins = helpers.prepareLogins(response.files, settings);
|
||||||
var index = 0;
|
for (let login of logins) {
|
||||||
for (var storeId in response.files) {
|
|
||||||
for (var key in response.files[storeId]) {
|
|
||||||
// set login fields
|
|
||||||
var login = {
|
|
||||||
index: index++,
|
|
||||||
store: settings.stores[storeId],
|
|
||||||
login: response.files[storeId][key].replace(/\.gpg$/i, ""),
|
|
||||||
allowFill: true
|
|
||||||
};
|
|
||||||
login.domain = helpers.pathToDomain(storeId + "/" + login.login, settings.host);
|
|
||||||
login.inCurrentDomain =
|
|
||||||
settings.host == login.domain || settings.host.endsWith("." + login.domain);
|
|
||||||
login.recent =
|
|
||||||
settings.recent[sha1(settings.host + sha1(login.store.id + sha1(login.login)))];
|
|
||||||
if (!login.recent) {
|
|
||||||
login.recent = {
|
|
||||||
when: 0,
|
|
||||||
count: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// bind handlers
|
|
||||||
login.doAction = withLogin.bind({ settings: settings, login: login });
|
login.doAction = withLogin.bind({ settings: settings, login: login });
|
||||||
|
}
|
||||||
|
|
||||||
logins.push(login);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var popup = new Interface(settings, logins);
|
var popup = new Interface(settings, logins);
|
||||||
popup.attach(document.body);
|
popup.attach(document.body);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
Reference in New Issue
Block a user