aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhil Crosby2012-06-12 21:59:20 -0700
committerPhil Crosby2012-06-12 22:00:35 -0700
commitf69f952d9332dcc6e4831ec52982f0012d4aed9f (patch)
treeaf7c89b737509a09124762e45f4c718c8bd0cd66
parent38bb8e5850e2352266c84b2ed9db39e04ca1e694 (diff)
downloadvimium-f69f952d9332dcc6e4831ec52982f0012d4aed9f.tar.bz2
Port vomnibar.js to coffeescript
-rw-r--r--.gitignore1
-rw-r--r--content_scripts/vomnibar.coffee212
-rw-r--r--content_scripts/vomnibar.js253
3 files changed, 213 insertions, 253 deletions
diff --git a/.gitignore b/.gitignore
index 0d46f679..d3d6f4b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ background_scripts/commands.js
background_scripts/settings.js
content_scripts/link_hints.js
content_scripts/vimium_frontend.js
+content_scripts/vomnibar.js
tests/completion_test.js
tests/test_helper.js
tests/utils_test.js
diff --git a/content_scripts/vomnibar.coffee b/content_scripts/vomnibar.coffee
new file mode 100644
index 00000000..a9a5af26
--- /dev/null
+++ b/content_scripts/vomnibar.coffee
@@ -0,0 +1,212 @@
+Vomnibar =
+ vomnibarUI: null # the dialog instance for this window
+ completers: {}
+
+ getCompleter: (name) ->
+ if (!(name of @completers))
+ @completers[name] = new BackgroundCompleter(name)
+ @completers[name]
+
+ #
+ # Activate the Vomnibox.
+ #
+ activateWithCompleter: (completerName, refreshInterval, initialQueryValue) ->
+ completer = @getCompleter(completerName)
+ @vomnibarUI = new VomnibarUI() unless @vomnibarUI
+ completer.refresh()
+ @vomnibarUI.setCompleter(completer)
+ @vomnibarUI.setRefreshInterval(refreshInterval)
+ @vomnibarUI.show()
+ if (initialQueryValue)
+ @vomnibarUI.setQuery(initialQueryValue)
+ @vomnibarUI.update()
+
+ activate: -> @activateWithCompleter("omni", 100)
+ activateWithCurrentUrl: -> @activateWithCompleter("omni", 100, window.location.toString())
+ activateTabSelection: -> @activateWithCompleter("tabs", 0)
+ getUI: -> @vomnibarUI
+
+
+class VomnibarUI
+ constructor: ->
+ @refreshInterval = 0
+ @initDom()
+
+ setQuery: (query) -> @input.value = query
+
+ setCompleter: (completer) ->
+ @completer = completer
+ @reset()
+
+ setRefreshInterval: (refreshInterval) -> @refreshInterval = refreshInterval
+
+ show: ->
+ @box.style.display = "block"
+ @input.focus()
+ handlerStack.push({ keydown: @onKeydown.bind(this) })
+
+ hide: ->
+ @box.style.display = "none"
+ @completionList.style.display = "none"
+ @input.blur()
+ handlerStack.pop()
+
+ reset: ->
+ @input.value = ""
+ @updateTimer = null
+ @completions = []
+ @selection = -1
+ @update(true)
+
+ updateSelection: ->
+ if (@completions.length > 0)
+ @selection = Math.min(@selection, @completions.length - 1)
+ for i in [0...@completionList.children.length]
+ @completionList.children[i].className = (if i == @selection then "selected" else "")
+
+ #
+ # Returns the user's action ("up", "down", "enter", "dismiss" or null) based on their keypress.
+ # We support the arrow keys and other shortcuts for moving, so this method hides that complexity.
+ #
+ actionFromKeyEvent: (event) ->
+ key = KeyboardUtils.getKeyChar(event)
+ if (KeyboardUtils.isEscape(event))
+ return "dismiss"
+ else if (key == "up" ||
+ (event.shiftKey && event.keyCode == keyCodes.tab) ||
+ (event.ctrlKey && (key == "k" || key == "p")))
+ return "up"
+ else if (key == "down" ||
+ (event.keyCode == keyCodes.tab && !event.shiftKey) ||
+ (event.ctrlKey && (key == "j" || key == "n")))
+ return "down"
+ else if (event.keyCode == keyCodes.enter)
+ return "enter"
+
+ onKeydown: (event) ->
+ action = @actionFromKeyEvent(event)
+ return true unless action # pass through
+
+ openInNewTab = (event.shiftKey || KeyboardUtils.isPrimaryModifierKey(event))
+ if (action == "dismiss")
+ @hide()
+ else if (action == "up")
+ @selection -=1 if @selection >= 0
+ @updateSelection()
+ else if (action == "down")
+ @selection += 1 if (@selection < @completions.length - 1)
+ @updateSelection()
+ else if (action == "enter")
+ # If they type something and hit enter without selecting a completion from our list of suggestions,
+ # try to open their query as a URL directly. If it doesn't look like a URL, we will search using
+ # google.
+ if (@selection == -1)
+ query = @input.value.trim()
+ chrome.extension.sendRequest({
+ handler: if openInNewTab then "openUrlInNewTab" else "openUrlInCurrentTab"
+ url: query })
+ else
+ @update true, =>
+ # Shift+Enter will open the result in a new tab instead of the current tab.
+ @completions[@selection].performAction(openInNewTab)
+ @hide()
+
+ # It seems like we have to manually supress the event here and still return true.
+ event.stopPropagation()
+ event.preventDefault()
+ true
+
+ updateCompletions: (callback) ->
+ query = @input.value.trim()
+
+ @completer.filter query, (completions) =>
+ @completions = completions
+ @populateUiWithCompletions(completions)
+ callback() if callback
+
+ populateUiWithCompletions: (completions) ->
+ # update completion list with the new data
+ @completionList.innerHTML = completions.map((completion) -> "<li>" + completion.html + "</li>").join("")
+ @completionList.style.display = if completions.length > 0 then "block" else "none"
+ @updateSelection()
+
+ update: (updateSynchronously, callback) ->
+ if (updateSynchronously)
+ # cancel scheduled update
+ if (@updateTimer != null)
+ window.clearTimeout(@updateTimer)
+ @updateCompletions(callback)
+ else if (@updateTimer != null)
+ # an update is already scheduled, don't do anything
+ return
+ else
+ # always update asynchronously for better user experience and to take some load off the CPU
+ # (not every keystroke will cause a dedicated update)
+ @updateTimer = setTimeout(=>
+ @updateCompletions(callback)
+ @updateTimer = null
+ @refreshInterval)
+
+ initDom: ->
+ @box = Utils.createElementFromHtml(
+ '<div id="vomnibar" class="vimiumReset">' +
+ '<div class="searchArea">' +
+ '<input type="text" />' +
+ '</div>' +
+ '<ul></ul>' +
+ '</div>')
+ @box.style.display = "none"
+ document.body.appendChild(@box)
+
+ @input = document.querySelector("#vomnibar input")
+ @input.addEventListener "input", => @update()
+ console.log("@input:", @input);
+ @completionList = document.querySelector("#vomnibar ul")
+ @completionList.style.display = "none"
+
+#
+# Sends filter and refresh requests to a Vomnibox completer on the background page.
+#
+class BackgroundCompleter
+ # - name: The background page completer that you want to interface with. Either "omni" or "tabs". */
+ constructor: (@name) ->
+ @filterPort = chrome.extension.connect({ name: "filterCompleter" })
+
+ refresh: -> chrome.extension.sendRequest({ handler: "refreshCompleter", name: @name })
+
+ filter: (query, callback) ->
+ id = Utils.createUniqueId()
+ @filterPort.onMessage.addListener (msg) ->
+ return if (msg.id != id)
+ # The result objects coming from the background page will be of the form:
+ # { html: "", type: "", url: "" }
+ # type will be one of [tab, bookmark, history, domain].
+ results = msg.results.map (result) ->
+ functionToCall = if (result.type == "tab")
+ BackgroundCompleter.completionActions.switchToTab.curry(result.tabId)
+ else
+ BackgroundCompleter.completionActions.navigateToUrl.curry(result.url)
+ result.performAction = functionToCall
+ result
+ callback(results)
+
+ @filterPort.postMessage({ id: id, name: @name, query: query })
+
+extend BackgroundCompleter,
+ #
+ # These are the actions we can perform when the user selects a result in the Vomnibox.
+ #
+ completionActions:
+ navigateToUrl: (url, openInNewTab) ->
+ # If the URL is a bookmarklet prefixed with javascript:, we shouldn't open that in a new tab.
+ if (url.indexOf("javascript:") == 0)
+ openInNewTab = false
+ chrome.extension.sendRequest(
+ handler: if openInNewTab then "openUrlInNewTab" else "openUrlInCurrentTab"
+ url: url,
+ selected: openInNewTab)
+
+ switchToTab: (tabId) -> chrome.extension.sendRequest({ handler: "selectSpecificTab", id: tabId })
+
+root = exports ? window
+root.Vomnibar = Vomnibar \ No newline at end of file
diff --git a/content_scripts/vomnibar.js b/content_scripts/vomnibar.js
deleted file mode 100644
index c1deeb3c..00000000
--- a/content_scripts/vomnibar.js
+++ /dev/null
@@ -1,253 +0,0 @@
-var vomnibar = (function() {
- var vomnibarUI = null; // the dialog instance for this window
- var completers = { };
-
- function getCompleter(name) {
- if (!(name in completers))
- completers[name] = new BackgroundCompleter(name);
- return completers[name];
- }
-
- /*
- * Activate the Vomnibox.
- */
- function activate(completerName, refreshInterval, initialQueryValue) {
- var completer = getCompleter(completerName);
- if (!vomnibarUI)
- vomnibarUI = new VomnibarUI();
- completer.refresh();
- vomnibarUI.setCompleter(completer);
- vomnibarUI.setRefreshInterval(refreshInterval);
- vomnibarUI.show();
- if (initialQueryValue) {
- vomnibarUI.setQuery(initialQueryValue);
- vomnibarUI.update();
- }
- }
-
- /** User interface for fuzzy completion */
- var VomnibarUI = Class.extend({
- init: function() {
- this.refreshInterval = 0;
- this.initDom();
- },
-
- setQuery: function(query) { this.input.value = query; },
-
- setCompleter: function(completer) {
- this.completer = completer;
- this.reset();
- },
-
- setRefreshInterval: function(refreshInterval) { this.refreshInterval = refreshInterval; },
-
- show: function() {
- this.box.style.display = "block";
- this.input.focus();
- handlerStack.push({ keydown: this.onKeydown.bind(this) });
- },
-
- hide: function() {
- this.box.style.display = "none";
- this.completionList.style.display = "none";
- this.input.blur();
- handlerStack.pop();
- },
-
- reset: function() {
- this.input.value = "";
- this.updateTimer = null;
- this.completions = [];
- this.selection = -1;
- this.update(true);
- },
-
- updateSelection: function() {
- if (this.completions.length > 0)
- this.selection = Math.min(this.selection, this.completions.length - 1);
- for (var i = 0; i < this.completionList.children.length; ++i)
- this.completionList.children[i].className = (i == this.selection) ? "selected" : "";
- },
-
- /*
- * Returns the user's action ("up", "down", "enter", "dismiss" or null) based on their keypress.
- * We support the arrow keys and other shortcuts for moving, so this method hides that complexity.
- */
- actionFromKeyEvent: function(event) {
- var key = KeyboardUtils.getKeyChar(event);
- if (KeyboardUtils.isEscape(event))
- return "dismiss";
- else if (key == "up" ||
- (event.shiftKey && event.keyCode == keyCodes.tab) ||
- (event.ctrlKey && (key == "k" || key == "p")))
- return "up";
- else if (key == "down" ||
- (event.keyCode == keyCodes.tab && !event.shiftKey) ||
- (event.ctrlKey && (key == "j" || key == "n")))
- return "down";
- else if (event.keyCode == keyCodes.enter)
- return "enter";
- },
-
- onKeydown: function(event) {
- var action = this.actionFromKeyEvent(event);
- if (!action) return true; // pass through
-
- var openInNewTab = (event.shiftKey || KeyboardUtils.isPrimaryModifierKey(event));
- if (action == "dismiss") {
- this.hide();
- }
- else if (action == "up") {
- if (this.selection >= 0)
- this.selection -= 1;
- this.updateSelection();
- }
- else if (action == "down") {
- if (this.selection < this.completions.length - 1)
- this.selection += 1;
- this.updateSelection();
- }
- else if (action == "enter") {
- // If they type something and hit enter without selecting a completion from our list of suggestions,
- // try to open their query as a URL directly. If it doesn't look like a URL, we will search using
- // google.
- if (this.selection == -1) {
- var query = this.input.value.trim();
- chrome.extension.sendRequest({
- handler: openInNewTab ? "openUrlInNewTab" : "openUrlInCurrentTab",
- url: query });
- } else {
- this.update(true, function() {
- // Shift+Enter will open the result in a new tab instead of the current tab.
- this.completions[this.selection].performAction(openInNewTab);
- this.hide();
- }.proxy(this));
- }
- }
-
- // It seems like we have to manually supress the event here and still return true.
- event.stopPropagation();
- event.preventDefault();
- return true;
- },
-
- updateCompletions: function(callback) {
- query = this.input.value.trim();
-
- this.completer.filter(query, function(completions) {
- this.completions = completions;
- this.populateUiWithCompletions(completions);
- if (callback) callback();
- }.proxy(this));
- },
-
- populateUiWithCompletions: function(completions) {
- // update completion list with the new data
- this.completionList.innerHTML = completions.map(function(completion) {
- return "<li>" + completion.html + "</li>";
- }).join('');
-
- this.completionList.style.display = (completions.length > 0) ? "block" : "none";
- this.updateSelection();
- },
-
- update: function(updateSynchronously, callback) {
- if (updateSynchronously) {
- // cancel scheduled update
- if (this.updateTimer !== null)
- window.clearTimeout(this.updateTimer);
- this.updateCompletions(callback);
- } else if (this.updateTimer !== null) {
- // an update is already scheduled, don't do anything
- return;
- } else {
- // always update asynchronously for better user experience and to take some load off the CPU
- // (not every keystroke will cause a dedicated update)
- this.updateTimer = setTimeout(function() {
- this.updateCompletions(callback);
- this.updateTimer = null;
- }.proxy(this), this.refreshInterval);
- }
- },
-
- initDom: function() {
- this.box = Utils.createElementFromHtml(
- '<div id="vomnibar" class="vimiumReset">' +
- '<div class="searchArea">' +
- '<input type="text" />' +
- '</div>' +
- '<ul></ul>' +
- '</div>');
- this.box.style.display = 'none';
- document.body.appendChild(this.box);
-
- this.input = document.querySelector("#vomnibar input");
- this.input.addEventListener("input", function() { this.update(); }.bind(this));
- this.completionList = document.querySelector("#vomnibar ul");
- this.completionList.style.display = "none";
- }
- });
-
- /*
- * Sends filter and refresh requests to a Vomnibox completer on the background page.
- */
- var BackgroundCompleter = Class.extend({
- /* - name: The background page completer that you want to interface with. Either "omni" or "tabs". */
- init: function(name) {
- this.name = name;
- this.filterPort = chrome.extension.connect({ name: "filterCompleter" });
- },
-
- refresh: function() { chrome.extension.sendRequest({ handler: "refreshCompleter", name: this.name }); },
-
- filter: function(query, callback) {
- var id = Utils.createUniqueId();
- this.filterPort.onMessage.addListener(function(msg) {
- if (msg.id != id) return;
- // The result objects coming from the background page will be of the form:
- // { html: "", type: "", url: "" }
- // type will be one of [tab, bookmark, history, domain].
- var results = msg.results.map(function(result) {
- var functionToCall;
- if (result.type == "tab")
- functionToCall = completionActions.switchToTab.curry(result.tabId);
- else
- functionToCall = completionActions.navigateToUrl.curry(result.url);
- result.performAction = functionToCall;
- return result;
- });
- callback(results);
- });
- this.filterPort.postMessage({ id: id, name: this.name, query: query });
- }
- });
-
- /*
- * These are the actions we can perform when the user selects a result in the Vomnibox.
- */
- var completionActions = {
- navigateToUrl: function(url, openInNewTab) {
- // If the URL is a bookmarklet prefixed with javascript:, we shouldn't open that in a new tab.
- if (url.indexOf("javascript:") == 0)
- openInNewTab = false;
- chrome.extension.sendRequest({
- handler: openInNewTab ? "openUrlInNewTab" : "openUrlInCurrentTab",
- url: url,
- selected: openInNewTab
- });
- },
-
- switchToTab: function(tabId) {
- chrome.extension.sendRequest({ handler: "selectSpecificTab", id: tabId });
- }
- };
-
- // public interface
- return {
- activate: function() { activate("omni", 100); },
- activateWithCurrentUrl: function() { activate("omni", 100, window.location.toString()); },
- activateTabSelection: function() { activate("tabs", 0); },
- /* Used by our vomnibar dev harness. */
- getUI: function() { return vomnibarUI; }
- }
-})();