Skip to content
This repository was archived by the owner on May 27, 2019. It is now read-only.

Commit 49bbfd5

Browse files
eraydmax-baz
authored andcommitted
Implement as-you-type filtering, make Enter submit the first entry (#220, fixes #40, fixes #211)
1 parent e1aa4b7 commit 49bbfd5

File tree

4 files changed

+129
-29
lines changed

4 files changed

+129
-29
lines changed

chrome/script.browserify.js

Lines changed: 104 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
"use strict";
22

33
var m = require("mithril");
4+
var FuzzySort = require("fuzzysort");
45
var app = "com.dannyvankooten.browserpass";
56
var activeTab;
67
var searching = false;
7-
var logins;
8+
var resultLogins = [];
9+
var logins = [];
10+
var fillOnSubmit = false;
811
var error;
912
var domain, urlDuringSearch;
1013

@@ -24,10 +27,10 @@ function view() {
2427
results = m("div.status-text", "Error: " + error);
2528
error = undefined;
2629
} else if (logins) {
27-
if (logins.length === 0) {
30+
if (logins.length === 0 && domain && domain.length > 0) {
2831
results = m(
2932
"div.status-text",
30-
m.trust(`No passwords found for <strong>${domain}</strong>.`)
33+
m.trust(`No matching passwords found for <strong>${domain}</strong>.`)
3134
);
3235
} else if (logins.length > 0) {
3336
results = logins.map(function(login) {
@@ -61,22 +64,29 @@ function view() {
6164
m(
6265
"form",
6366
{
64-
onsubmit: submitSearchForm
67+
onsubmit: submitSearchForm,
68+
onkeydown: searchKeyHandler
6569
},
6670
[
67-
m("input", {
68-
type: "text",
69-
id: "search-field",
70-
name: "s",
71-
placeholder: "Search password..",
72-
autocomplete: "off",
73-
autofocus: "on"
71+
m("div", {
72+
"id": "filter-search"
7473
}),
75-
m("input", {
76-
type: "submit",
77-
value: "Search",
78-
style: "display: none;"
79-
})
74+
m("div", [
75+
m("input", {
76+
type: "text",
77+
id: "search-field",
78+
name: "s",
79+
placeholder: "Search passwords..",
80+
autocomplete: "off",
81+
autofocus: "on",
82+
oninput: filterLogins
83+
}),
84+
m("input", {
85+
type: "submit",
86+
value: "Search",
87+
style: "display: none;"
88+
})
89+
])
8090
]
8191
)
8292
]),
@@ -86,15 +96,73 @@ function view() {
8696
]);
8797
}
8898

99+
function filterLogins(e) {
100+
// use fuzzy search to filter results
101+
var filter = e.target.value.trim().split(/[\s\/]+/);
102+
if (filter.length > 0) {
103+
logins = resultLogins.slice(0);
104+
filter.forEach(function(word) {
105+
if (word.length > 0) {
106+
var refine = [];
107+
FuzzySort.go(word, logins, {allowTypo: false}).forEach(function(result) {
108+
refine.push(result.target);
109+
});
110+
logins = refine.slice(0);
111+
}
112+
});
113+
114+
// fill login forms on submit rather than initiating a search
115+
fillOnSubmit = logins.length > 0;
116+
} else {
117+
// reset the result list if the filter is empty
118+
logins = resultLogins.slice(0);
119+
}
120+
121+
// redraw the list
122+
m.redraw();
123+
124+
// show / hide the filter hint
125+
showFilterHint(logins.length);
126+
}
127+
128+
function searchKeyHandler(e) {
129+
// switch to search mode if backspace is pressed and no filter text has been entered
130+
if (e.code == "Backspace" && logins.length > 0 && e.target.value.length == 0) {
131+
e.preventDefault();
132+
logins = resultLogins = [];
133+
e.target.value = fillOnSubmit ? '' : domain;
134+
domain = '';
135+
showFilterHint(false);
136+
}
137+
}
138+
139+
function showFilterHint(show=true) {
140+
var filterHint = document.getElementById("filter-search");
141+
var searchField = document.getElementById("search-field");
142+
if (show) {
143+
filterHint.style.display = "block";
144+
searchField.setAttribute("placeholder", "Refine search...");
145+
} else {
146+
filterHint.style.display = "none";
147+
searchField.setAttribute("placeholder", "Search passwords...");
148+
}
149+
}
150+
89151
function submitSearchForm(e) {
90152
e.preventDefault();
91153

92-
// don't search without input.
93-
if (!this.s.value.length) {
94-
return;
95-
}
154+
if (fillOnSubmit && logins.length > 0) {
155+
// fill using the first result
156+
getLoginData.bind(logins[0])();
157+
} else {
158+
// don't search without input.
159+
if (!this.s.value.length) {
160+
return;
161+
}
96162

97-
searchPassword(this.s.value);
163+
// search for matching entries
164+
searchPassword(this.s.value, "search", false);
165+
}
98166
}
99167

100168
function init(tab) {
@@ -108,9 +176,9 @@ function init(tab) {
108176
searchPassword(activeDomain, "match_domain");
109177
}
110178

111-
function searchPassword(_domain, action="search") {
179+
function searchPassword(_domain, action="search", useFillOnSubmit=true) {
112180
searching = true;
113-
logins = null;
181+
logins = resultLogins = [];
114182
domain = _domain;
115183
urlDuringSearch = activeTab.url;
116184
m.redraw();
@@ -132,7 +200,13 @@ function searchPassword(_domain, action="search") {
132200
}
133201

134202
searching = false;
135-
logins = response;
203+
logins = resultLogins = response ? response : [];
204+
document.getElementById("filter-search").textContent = domain;
205+
fillOnSubmit = useFillOnSubmit && logins.length > 0;
206+
if (logins.length > 0) {
207+
showFilterHint(true);
208+
document.getElementById("search-field").value = '';
209+
}
136210
m.redraw();
137211
}
138212
);
@@ -160,13 +234,14 @@ function getFaviconUrl(domain) {
160234

161235
function getLoginData() {
162236
searching = true;
163-
logins = null;
237+
logins = resultLogins = [];
164238
m.redraw();
165239

166240
chrome.runtime.sendMessage(
167241
{ action: "login", entry: this, urlDuringSearch: urlDuringSearch },
168242
function(response) {
169243
searching = false;
244+
fillOnSubmit = false;
170245

171246
if (response.error) {
172247
error = response.error;
@@ -222,14 +297,16 @@ function keyHandler(e) {
222297
switchFocus("div.entry:first-child > .login", "nextElementSibling");
223298
break;
224299
case "c":
225-
if (e.ctrlKey) {
300+
if (e.target.id != "search-field" && e.ctrlKey) {
226301
document.activeElement["nextElementSibling"][
227302
"nextElementSibling"
228303
].click();
229304
}
230305
break;
231306
case "C":
232-
document.activeElement["nextElementSibling"].click();
307+
if (e.target.id != "search-field") {
308+
document.activeElement["nextElementSibling"].click();
309+
}
233310
break;
234311
}
235312
}

chrome/styles.css

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,37 @@ body {
77
font-size: 14px;
88
}
99

10+
.search > form {
11+
border-bottom: 1px solid #bbb;
12+
display: flex;
13+
flex-wrap: nowrap;
14+
}
15+
16+
.search > form :last-child {
17+
width: 100%;
18+
}
19+
1020
.search input {
1121
box-sizing: border-box;
1222
width: 100%;
1323
padding: 6px;
1424
border: 0;
15-
border-bottom: 1px solid #bbb;
1625
background: url("icon-search.svg") center right 6px no-repeat;
1726
background-size: 16px 16px;
1827
background-color: white;
1928
color: black;
2029
padding-right: 20px;
2130
}
2231

32+
#filter-search {
33+
background: #eee;
34+
border: 0;
35+
box-sizing: border-box;
36+
display: none;
37+
padding: 6px;
38+
padding-top: 5px;
39+
}
40+
2341
.search input:focus {
2442
outline: 0;
2543
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"dependencies": {
33
"browserify": "^14.4.0",
4-
"mithril": "^1.1.4"
4+
"mithril": "^1.1.4",
5+
"fuzzysort": "^1.1.0"
56
}
67
}

yarn.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,10 @@ function-bind@^1.0.2:
376376
version "1.1.0"
377377
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
378378

379+
fuzzysort@^1.1.0:
380+
version "1.1.1"
381+
resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-1.1.1.tgz#bf128f1a4cc6e6b7188665ac5676de46a3d81768"
382+
379383
glob@^7.1.0:
380384
version "7.1.1"
381385
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"

0 commit comments

Comments
 (0)