Implement search (#3)
* Handle settings response properly * Change location of default store settings * Generate chrome extension * Move popup into its own folder * Move distribution javascript into new folder * Remove checkpoint * Ignore dist JS files * Rename background source file * Update clean rule * Fix make rule for generated files * Also tidy CSS files * Implement searching * Add icon to search bar * Add copy-password action * Add copy-user action * Add launch action * Button styling * Send action to backend * Set targets to .PHONY * Highlight the first entry * Allow disabling fuzzy search by starting search with a space
This commit is contained in:
166
src/popup/interface.js
Normal file
166
src/popup/interface.js
Normal file
@@ -0,0 +1,166 @@
|
||||
module.exports = Interface;
|
||||
|
||||
var m = require("mithril");
|
||||
var FuzzySort = require("fuzzysort");
|
||||
var SearchInterface = require("./searchinterface");
|
||||
|
||||
/**
|
||||
* Popup main interface
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param object settings Settings object
|
||||
* @param array logins Array of available logins
|
||||
* @return void
|
||||
*/
|
||||
function Interface(settings, logins) {
|
||||
// public methods
|
||||
this.attach = attach;
|
||||
this.view = view;
|
||||
this.search = search;
|
||||
|
||||
// fields
|
||||
this.settings = settings;
|
||||
this.logins = logins;
|
||||
this.results = [];
|
||||
this.active = true;
|
||||
this.searchPart = new SearchInterface(this);
|
||||
|
||||
// initialise with empty search
|
||||
this.search("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the interface on the given element
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param DOMElement element Target element
|
||||
* @return void
|
||||
*/
|
||||
function attach(element) {
|
||||
m.mount(element, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates vnodes for render
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param function ctl Controller
|
||||
* @param object params Runtime params
|
||||
* @return []Vnode
|
||||
*/
|
||||
function view(ctl, params) {
|
||||
var badges = Object.keys(this.settings.stores).length > 1;
|
||||
var nodes = [];
|
||||
nodes.push(m(this.searchPart));
|
||||
|
||||
nodes.push(
|
||||
m(
|
||||
"div.logins",
|
||||
this.results.map(function(result) {
|
||||
return m(
|
||||
"div.part.login",
|
||||
{
|
||||
key: result.index,
|
||||
tabindex: 0,
|
||||
onclick: function(e) {
|
||||
result.doAction("fill");
|
||||
},
|
||||
onkeydown: function(e) {
|
||||
switch (e.code) {
|
||||
case "ArrowDown":
|
||||
if (e.target.nextSibling) {
|
||||
e.target.nextSibling.focus();
|
||||
}
|
||||
break;
|
||||
case "ArrowUp":
|
||||
if (e.target.previousSibling) {
|
||||
e.target.previousSibling.focus();
|
||||
} else {
|
||||
document
|
||||
.querySelector(".part.search input[type=text]")
|
||||
.focus();
|
||||
}
|
||||
break;
|
||||
case "Enter":
|
||||
result.doAction("fill");
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
badges ? m("div.store.badge", result.store) : null,
|
||||
m("div.name", m.trust(result.display)),
|
||||
m("div.action.copy-password", {
|
||||
title: "Copy password",
|
||||
onclick: function(e) {
|
||||
e.stopPropagation();
|
||||
result.doAction("copyPassword");
|
||||
}
|
||||
}),
|
||||
m("div.action.copy-user", {
|
||||
title: "Copy username",
|
||||
onclick: function(e) {
|
||||
e.stopPropagation();
|
||||
result.doAction("copyUser");
|
||||
}
|
||||
}),
|
||||
m("div.action.launch", {
|
||||
title: "Open URL",
|
||||
onclick: function(e) {
|
||||
e.stopPropagation();
|
||||
result.doAction("launch");
|
||||
}
|
||||
})
|
||||
]
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a search
|
||||
*
|
||||
* @param string s Search string
|
||||
* @param bool fuzzyFirstWord Whether to use fuzzy search on the first word
|
||||
* @return void
|
||||
*/
|
||||
function search(s, fuzzyFirstWord = true) {
|
||||
var self = this;
|
||||
|
||||
// get candidate list
|
||||
var candidates = this.logins.map(result => Object.assign(result, { display: result.login }));
|
||||
if (this.active) {
|
||||
candidates = candidates.filter(login => login.active);
|
||||
}
|
||||
|
||||
if (s.length) {
|
||||
var filter = s.split(/\s+/);
|
||||
// fuzzy-search first word & add highlighting
|
||||
if (fuzzyFirstWord) {
|
||||
candidates = FuzzySort.go(filter[0], candidates, {
|
||||
keys: ["login", "store"],
|
||||
allowTypo: false
|
||||
}).map(result =>
|
||||
Object.assign(result.obj, {
|
||||
display: result[0]
|
||||
? FuzzySort.highlight(result[0], "<em>", "</em>")
|
||||
: result.obj.login
|
||||
})
|
||||
);
|
||||
}
|
||||
// substring-search to refine against each remaining word
|
||||
filter.slice(fuzzyFirstWord ? 1 : 0).forEach(function(word) {
|
||||
candidates = candidates.filter(
|
||||
login => login.login.toLowerCase().indexOf(word.toLowerCase()) >= 0
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this.results = candidates;
|
||||
}
|
Reference in New Issue
Block a user