Skip to content

Commit

Permalink
move logic from add-on to content, add groups lib, add some session e…
Browse files Browse the repository at this point in the history
…xperimentation
  • Loading branch information
autonome committed May 19, 2014
1 parent 8f11f72 commit 1e7f4f1
Show file tree
Hide file tree
Showing 6 changed files with 602 additions and 211 deletions.
10 changes: 7 additions & 3 deletions data/commands-autocomplete.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@
padding-left: 0.5em;
}
#cmd {
color: #fff;
color: #6E6E6E;
overflow: hidden;
white-space: nowrap;
font: message-box;
font-size: 2em;
font-size: 3em;
}
.typed {
text-decoration: underline;
}
.completed {
color: #6E6E6E;
}
/* unused */
.nomatch {
color: red;
}
</style>
</head>
Expand Down
248 changes: 218 additions & 30 deletions data/commands-autocomplete.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,231 @@
// NOW
// TODO: configurable shortcut
// TODO: adaptive matching (incrementing)
// TODO: fix font in 29+

// FUTURE
// TODO: add support for registering new commands at runtime
// TODO: support selection feedback ranking
// TODO: support remembering last-executed command across restarts
// TODO: better fix for overflow text


let cmd = null,
commands = [],
active = false,
typed = "",
matches = [],
matchIndex = 0,
lastExecuted = "";

// set up match ranking storage
if (!localStorage.cmdMatchCounts)
localStorage.cmdMatchCounts = {};

function updateMatchCount(alias) {
localStorage.cmdMatchCounts[alias]++;
}

// set up adaptive match storage
if (!localStorage.cmdMatchFeedback)
localStorage.cmdMatchFeedback = {};

function updateMatchFeedback(typed, alias) {
localStorage.cmdMatchFeedback[typed] = alias;
}

self.port.on('show', function() {
onVisibilityChange()
})

window.addEventListener("DOMContentLoaded", function() {
window.removeEventListener("DOMContentLoaded", arguments.callee, false);
var cmd = document.getElementById("cmd");

// handle messages from jetpack code
self.on("message", function(msg) {
msg = JSON.parse(msg);
if (msg.completed) {
if (msg.typed) {
cmd.innerHTML = generateUnderlined(msg.completed, msg.typed);
}
else {
cmd.innerHTML = msg.completed;
}
cmd.style.color = "white";
}
else if (msg.typed) {
cmd.innerHTML = msg.typed;
cmd.style.color = "red";
}
window.removeEventListener("DOMContentLoaded", arguments.callee, false)
cmd = document.getElementById("cmd")
self.port.on('commands', function(msg) {
commands = msg.commands
})
}, false)

function execute(alias) {
self.port.emit('execute', {
alias: alias
});
}, false);
}

function generateUnderlined(wholeString, subString) {
let startIndex = wholeString.toLowerCase().indexOf(subString.toLowerCase());
let endIndex = startIndex + subString.length;
var startIndex = wholeString.toLowerCase().indexOf(subString.toLowerCase());
var endIndex = startIndex + subString.length;
var str = ''
// substring is empty
if (!subString) {
str = "<span>" + wholeString + "</span>"
}
// occurs at beginning
if (startIndex === 0) {
return "<span class='typed'>" + subString + "</span>" +
"<span>" + wholeString.substring(subString.length) + "</span>";
else if (startIndex === 0) {
str = "<span class='typed'>" + subString + "</span>" +
"<span class='completed'>" + wholeString.substring(subString.length) + "</span>";
}
// occurs in middle
else if (endIndex != wholeString.length) {
return "<span>" + wholeString.substring(0, startIndex) + "</span>" +
"<span class='typed'>" + wholeString.substring(startIndex, startIndex + subString.length) + "</span>" +
"<span>" + wholeString.substring(endIndex) + "</span>";
str = "<span class='completed'>" + wholeString.substring(0, startIndex) + "</span>" +
"<span class='typed'>" + wholeString.substring(startIndex, startIndex + subString.length) + "</span>" +
"<span class='completed'>" + wholeString.substring(endIndex) + "</span>";
}
// occurs at the end
else {
return "<span>" + wholeString.substring(0, startIndex) + "</span>" +
"<span class='typed'>" + subString + "</span>";
str = "<span class='completed'>" + wholeString.substring(0, startIndex) + "</span>" +
"<span class='typed'>" + subString + "</span>";
}
return str;
}

function onKeyPress(e) {
//console.log('onKeyUp', String.fromCharCode(e.which), 'active?', active)
//console.log(e.which, e.altKey, e.ctrlKey, e.shiftKey, e.metaKey)
if (!active) {
return;
}

e.preventDefault();

// if user pressed return, attempt to execute command
//console.log('RETURN?', e.which == e.DOM_VK_RETURN, hasModifier(e))
if (e.which == e.DOM_VK_RETURN && !hasModifier(e)) {
let alias = matches[matchIndex];
if (commands.indexOf(alias) > -1) {
execute(alias);
lastExecuted = alias;
updateMatchCount(alias);
updateMatchFeedback(typed, alias);
typed = "";
// ONLY IF OWN WINDOW
//window.close();
}
}

// attempt to complete typed characters to a command
else if (!hasModifier(e) && !isModifier(e) && !isIgnorable(e)) {
//console.log('active, no modifier, is not a modifier and not ignorable')
// correct on backspace
if (e.which == 8)
typed = typed.substring(0, typed.length - 1);
// otherwise add typed character to buffer
else {
//console.log('updating', String.fromCharCode(e.which))
typed += String.fromCharCode(e.which);
}

// search, and update UI
matches = findMatchingCommands(typed);
if (matches.length) {
update(typed, matches[0]);
matchIndex = 0;
}
else {
update(typed);
}
}

// tab -> shift to next result
// shift + tab -> shift to previous result
else if (e.keyCode == e.DOM_VK_TAB) {
//console.log('tab')
if (e.shiftKey && matchIndex)
update(typed, matches[--matchIndex]);
else if (matchIndex + 1 < matches.length)
update(typed, matches[++matchIndex]);
}
}
window.addEventListener("keypress", onKeyPress, false);

function hasModifier(e) {
return e.altKey || e.ctrlKey || e.shiftKey || e.metaKey
}
function isModifier(e) {
return [e.altKey, e.ctrlKey, e.shiftKey, e.metaKey].indexOf(e.which) != -1
}
function isIgnorable(e) {
switch(e.which) {
case 38: //up arrow
case 40: //down arrow
case 37: //left arrow
case 39: //right arrow
case 33: //page up
case 34: //page down
case 36: //home
case 35: //end
case 13: //enter
case 9: //tab
case 27: //esc
case 16: //shift
case 17: //ctrl
case 18: //alt
case 20: //caps lock
// we handle this for editing
//case 8: //backspace
// need to handle for editing also?
case 46: //delete
case 0:
return true;
break;
default:
return false;
}
}

function onVisibilityChange() {
//console.log('onVisibilityChange')
window.focus()
if (document.hidden) {
active = false
}
else {
active = true
// panel is visible, so initialize input box
update("", lastExecuted || "Type a command...");
}
}
//document.addEventListener("visibilitychange", onVisibilityChange, false);

function update(typed, completed) {
//console.log('update', typed, completed)
var cmd = document.querySelector("#cmd")
var str = ''
if (completed) {
str = generateUnderlined(completed, typed);
}
// no match
else if (typed) {
str = typed;
}
cmd.innerHTML = str
}

function findMatchingCommands(text) {
let match = null,
count = commands.length,
matches = [];

// basic index search
// TODO: wat. fix this shit.
for (var i = 0; i < count; i++) {
if (commands[i].toLowerCase().indexOf(typed.toLowerCase()) != -1)
matches.push(commands[i]);
}

// sort by match count
matches.sort(function(a, b) {
var aCount = localStorage.cmdMatchCounts[a] || 0;
var bCount = localStorage.cmdMatchCounts[b] || 0;
return bCount - aCount;
})

// insert adaptive feedback
if (localStorage.cmdMatchFeedback[typed]) {
//console.log('ADAPT!')
matches.unshift(localStorage.cmdMatchFeedback[typed])
}

return matches;
}

39 changes: 17 additions & 22 deletions lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,6 @@ Module for searching available commands in the browser.
Allows searching and execution of commands in Firefox, such as
'Back', 'Zoom In', 'Reload'.
Example that adds buttons to the add-on bar for every command,
which trigger the command when clicked:
const widget = require("widget");
const commands = require("commands");
commands.search("", function(command) {
widget.Widget({
label: command.alias,
content: command.alias,
onClick: command.execute
});
});
API
The 'search' method takes two parameters. the first is a string of text
Expand All @@ -43,14 +30,21 @@ TODO:
*/

let commands = {};
let aliases = {};
let aliasArray = [];
let cached = false;
let commands = {},
aliases = {},
aliasArray = [],
cached = false;

function getXULDocument() {
var utils = require('sdk/window/utils'),
window = utils.getMostRecentBrowserWindow();
//window = utils.getXULWindow(active);
return window.document;
}

function cacheCommands() {
let window = require("window-utils").activeBrowserWindow;
let document = window.document;
var document = getXULDocument();

// harvest all basic commands
let els = document.querySelectorAll("command");
let len = els.length;
Expand Down Expand Up @@ -113,8 +107,7 @@ function command(id, alias) {
this.alias = alias;
this.execute = function() {
if (commands[id]) {
let document = require("window-utils").activeBrowserWindow.document
let el = document.getElementById(id);
let el = getXULDocument().getElementById(id);
if (el)
el.doCommand();
}
Expand All @@ -128,7 +121,9 @@ exports.search = function(text, callback) {
}

for (var i in aliasArray) {
if (!text.length || (new RegExp(text, "i")).test(aliasArray[i]))
if (!text.length || (new RegExp(text, "i")).test(aliasArray[i])) {
callback(new command(aliases[aliasArray[i]], aliasArray[i]));
}
}
callback(null);
};
Loading

0 comments on commit 1e7f4f1

Please sign in to comment.