Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit

  • Loading branch information...
commit f0c94714c6ed29075d91b5e97b415d86f2084baa 1 parent 90d9095
@autonome authored
Showing with 8,473 additions and 1 deletion.
  1. BIN  .DS_Store
  2. +27 −1 README.md
  3. +177 −0 asyncStorage.js
  4. +85 −0 browser.js
  5. +284 −0 cmdmanager.js
  6. +11 −0 commands/google.html
  7. +53 −0 demo.html
  8. +108 −0 eventEmitter.js
  9. +22 −0 idbstore.min.js
  10. +8 −0 mousetrap.min.js
  11. +325 −0 mustache.js
  12. +1,373 −0 nountypes.js
  13. +2,847 −0 parser.js
  14. +8 −0 preview.html
  15. +1,017 −0 registry.js
  16. BIN  skin/.DS_Store
  17. +276 −0 skin/browser.css
  18. BIN  skin/icons/amazon.ico
  19. BIN  skin/icons/answers.ico
  20. BIN  skin/icons/application_view_list.png
  21. BIN  skin/icons/arrow_out.png
  22. BIN  skin/icons/arrow_redo.png
  23. BIN  skin/icons/arrow_refresh.png
  24. BIN  skin/icons/arrow_undo.png
  25. BIN  skin/icons/ask.ico
  26. BIN  skin/icons/bing.ico
  27. BIN  skin/icons/calculator.png
  28. BIN  skin/icons/calendar.png
  29. BIN  skin/icons/calendar_add.png
  30. BIN  skin/icons/calendar_google.ico
  31. BIN  skin/icons/cmd_entry_bg.png
  32. BIN  skin/icons/color_wheel.png
  33. BIN  skin/icons/convert.png
  34. BIN  skin/icons/delete.png
  35. BIN  skin/icons/delicious.ico
  36. BIN  skin/icons/digg.ico
  37. BIN  skin/icons/ebay.ico
  38. BIN  skin/icons/email.png
  39. BIN  skin/icons/email_open.png
  40. BIN  skin/icons/exit_stop.png
  41. BIN  skin/icons/favicon.ico
  42. BIN  skin/icons/flickr.ico
  43. BIN  skin/icons/folder_star.png
  44. BIN  skin/icons/german_flag.png
  45. BIN  skin/icons/google.ico
  46. BIN  skin/icons/help.png
  47. BIN  skin/icons/home_house.png
  48. BIN  skin/icons/html_go.png
  49. BIN  skin/icons/imdb.ico
  50. BIN  skin/icons/jquery.ico
  51. BIN  skin/icons/left_arrow.png
  52. BIN  skin/icons/magnifier.png
  53. BIN  skin/icons/map.png
  54. BIN  skin/icons/map_add.png
  55. BIN  skin/icons/mozilla.ico
  56. BIN  skin/icons/page_code.png
  57. BIN  skin/icons/page_delete.png
  58. BIN  skin/icons/page_edit.png
  59. BIN  skin/icons/page_lightning.png
  60. BIN  skin/icons/page_refresh.png
  61. BIN  skin/icons/page_save.png
  62. BIN  skin/icons/plugin_edit.png
  63. BIN  skin/icons/script_lightning.png
  64. BIN  skin/icons/search.png
  65. BIN  skin/icons/selected_bg.png
  66. BIN  skin/icons/stop.png
  67. BIN  skin/icons/suggestion_bd.png
  68. BIN  skin/icons/sum.png
  69. BIN  skin/icons/tab_delete.png
  70. BIN  skin/icons/tab_go.png
  71. BIN  skin/icons/text_bold.png
  72. BIN  skin/icons/text_italic.png
  73. BIN  skin/icons/text_underline.png
  74. BIN  skin/icons/textfield_rename.png
  75. BIN  skin/icons/tinyurl.ico
  76. BIN  skin/icons/translate_bing.ico
  77. BIN  skin/icons/translate_google.ico
  78. BIN  skin/icons/tuner-scale.png
  79. BIN  skin/icons/tuner-top.png
  80. BIN  skin/icons/twitter.ico
  81. BIN  skin/icons/ubiquibot-square.png
  82. BIN  skin/icons/ubiquibot24.png
  83. BIN  skin/icons/union_jack.ico
  84. BIN  skin/icons/videosurf.ico
  85. BIN  skin/icons/wikipedia.ico
  86. BIN  skin/icons/wunderground.ico
  87. BIN  skin/icons/yahoo.ico
  88. BIN  skin/icons/yelp.ico
  89. BIN  skin/icons/youtube.ico
  90. +244 −0 suggestionMemory.js
  91. +268 −0 testCommands.js
  92. +91 −0 testgoog.html
  93. +437 −0 ubiquity.js
  94. +812 −0 utils.js
View
BIN  .DS_Store
Binary file not shown
View
28 README.md
@@ -1,4 +1,30 @@
webiquity
=========
-Proof of concept to port the Ubiquity add-on for Firefox to the web.
+Proof of concept to port the Ubiquity add-on for Firefox to the web.
+
+Concept
+* Natural-language GCLI components for the web
+* Like the Ubiquity add-on for Firefox, but pure web content
+* Could load into existing pages via Firefox/Chrome add-on content script (or bookmarklet)
+* Commands described via OpenWebApp manifests, with some added fields
+* Commands are URLs, so execution navigates to the URL, depending on context of use
+* Commands are previewed by loading preview URL into iframe repeatedly (move to postMessage)
+* Commands are web pages, so utilize cache for quick asset loading and permissions model is the web permissions model
+
+Notes
+* Gutted the Ubiquity code, removing as much as possible, while retaining the parser core
+* Nountypes themselves are super dodgy, need a full rewrite
+* Parser currently does reacharounds way out into the front-end, need to better abstract that shit
+* Way too much sync execution, need some eventing up in heeeere
+
+Misc TODO
+* move all display code out of cmdmanager.js
+* implement command updating via postMessage: load url once and postMessage to it for subsequent chars
+* refreshCommandList being called way too often
+* command is suggested with it's own text, eg "google google {search str}"
+* nountypes need unique identification, i mean wtf
+* make sure subsequent args don't overwrite core nountype list
+* add data persistence back in
+* review parser flows, still not very efficient
+* put all the Ubiquity license and contributor stuff back somewhere
View
177 asyncStorage.js
@@ -0,0 +1,177 @@
+/**
+ * This file defines an asynchronous version of the localStorage API, backed
+ * by an IndexedDB database. It creates a global asyncStorage object that
+ * has methods like the localStorage object.
+ *
+ * To store a value use setItem:
+ *
+ * asyncStorage.setItem('key', 'value');
+ *
+ * If you want confirmation that the value has been stored, pass a callback
+ * function as the third argument:
+ *
+ * asyncStorage.setItem('key', 'newvalue', function() {
+ * console.log('new value stored');
+ * });
+ *
+ * To read a value, call getItem(), but note that you must supply a callback
+ * function that the value will be passed to asynchronously:
+ *
+ * asyncStorage.getItem('key', function(value) {
+ * console.log('The value of key is:', value);
+ * });
+ *
+ * Note that unlike localStorage, asyncStorage does not allow you to store
+ * and retrieve values by setting and querying properties directly. You
+ * cannot just write asyncStorage.key; you have to explicitly call setItem()
+ * or getItem().
+ *
+ * removeItem(), clear(), length(), and key() are like the same-named
+ * methods of localStorage, but, like getItem() and setItem() they
+ * take a callback argument.
+ *
+ * The asynchronous nature of getItem() makes it tricky to retrieve
+ * multiple values. But unlike localStorage, asyncStorage does not
+ * require the values you store to be strings. So if you need to save
+ * multiple values and want to retrieve them together, in a single
+ * asynchronous operation, just group the values into a single
+ * object. The properties of this object may not include DOM elements,
+ * but they may include things like Blobs and typed arrays.
+ *
+ * Unit tests are in apps/gallery/test/unit/asyncStorage_test.js
+ */
+this.asyncStorage = (function() {
+
+ var DBNAME = 'asyncStorage';
+ var DBVERSION = 1;
+ var STORENAME = 'keyvaluepairs';
+ var db = null;
+
+ function withStore(type, f) {
+ if (db) {
+ f(db.transaction(STORENAME, type).objectStore(STORENAME));
+ } else {
+ window.indexedDB = window.indexedDB || window.mozIndexedDB;
+ var openreq = indexedDB.open(DBNAME, DBVERSION);
+ openreq.onerror = function withStoreOnError() {
+ console.error("asyncStorage: can't open database:", openreq.error.name);
+ };
+ openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() {
+ // First time setup: create an empty object store
+ openreq.result.createObjectStore(STORENAME);
+ };
+ openreq.onsuccess = function withStoreOnSuccess() {
+ db = openreq.result;
+ f(db.transaction(STORENAME, type).objectStore(STORENAME));
+ };
+ }
+ }
+
+ function getItem(key, callback) {
+ withStore('readonly', function getItemBody(store) {
+ var req = store.get(key);
+ req.onsuccess = function getItemOnSuccess() {
+ var value = req.result;
+ if (value === undefined)
+ value = null;
+ callback(value);
+ };
+ req.onerror = function getItemOnError() {
+ console.error('Error in asyncStorage.getItem(): ', req.error.name);
+ };
+ });
+ }
+
+ function setItem(key, value, callback) {
+ withStore('readwrite', function setItemBody(store) {
+ var req = store.put(value, key);
+ req.onsuccess = function setItemOnSuccess() {
+ callback();
+ };
+ req.onerror = function setItemOnError() {
+ console.error('Error in asyncStorage.setItem(): ', req.error.name);
+ };
+ });
+ }
+
+ function removeItem(key, callback) {
+ withStore('readwrite', function removeItemBody(store) {
+ var req = store.delete(key);
+ req.onsuccess = function removeItemOnSuccess() {
+ callback();
+ };
+ req.onerror = function removeItemOnError() {
+ console.error('Error in asyncStorage.removeItem(): ', req.error.name);
+ };
+ });
+ }
+
+ function clear(callback) {
+ withStore('readwrite', function clearBody(store) {
+ var req = store.clear();
+ req.onsuccess = function clearOnSuccess() {
+ callback();
+ };
+ req.onerror = function clearOnError() {
+ console.error('Error in asyncStorage.clear(): ', req.error.name);
+ };
+ });
+ }
+
+ function length(callback) {
+ withStore('readonly', function lengthBody(store) {
+ var req = store.count();
+ req.onsuccess = function lengthOnSuccess() {
+ callback(req.result);
+ };
+ req.onerror = function lengthOnError() {
+ console.error('Error in asyncStorage.length(): ', req.error.name);
+ };
+ });
+ }
+
+ function key(n, callback) {
+ if (n < 0) {
+ callback(null);
+ return;
+ }
+
+ withStore('readonly', function keyBody(store) {
+ var advanced = false;
+ var req = store.openCursor();
+ req.onsuccess = function keyOnSuccess() {
+ var cursor = req.result;
+ if (!cursor) {
+ // this means there weren't enough keys
+ callback(null);
+ return;
+ }
+ if (n === 0) {
+ // We have the first key, return it if that's what they wanted
+ callback(cursor.key);
+ } else {
+ if (!advanced) {
+ // Otherwise, ask the cursor to skip ahead n records
+ advanced = true;
+ cursor.advance(n);
+ } else {
+ // When we get here, we've got the nth key.
+ callback(cursor.key);
+ }
+ }
+ };
+ req.onerror = function keyOnError() {
+ console.error('Error in asyncStorage.key(): ', req.error.name);
+ };
+ });
+ }
+
+ return {
+ getItem: getItem,
+ setItem: setItem,
+ removeItem: removeItem,
+ clear: clear,
+ length: length,
+ key: key
+ };
+}());
View
85 browser.js
@@ -0,0 +1,85 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Ubiquity.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma <atul@mozilla.com>
+ * Maria Emerson <memerson@mozilla.com>
+ * Aza Raskin <aza@mozilla.com>
+ * Abimanyu Raja <abimanyuraja@gmail.com>
+ * Jono DiCarlo <jdicarlo@mozilla.com>
+ * Dietrich Ayala <dietrich@mozilla.com>
+ * Satoshi Murakami <murky.satyr@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var gUbiquity = null;
+
+addEventListener("load", function ubiquityBoot() {
+ removeEventListener("load", ubiquityBoot, false)
+ ubiquitySetup()
+}, false);
+
+function ubiquitySetup() {
+
+ var cmdSource = new CommandAggregator(testCommands)
+
+ var languageCode = 'en'
+ var parser = NLParser2.makeParserForLanguage(
+ languageCode,
+ cmdSource.getAllCommands(),
+ new SuggestionMemory('somefuckingkey'))
+
+ var cmdMan = new CommandManager(
+ cmdSource,
+ parser,
+ document.getElementById("ubiquity-suggest-container"),
+ document.getElementById("ubiquity-browser"));
+ cmdMan.refresh();
+
+ var panel = document.getElementById("ubiquity-transparent-panel");
+
+ /*
+ var suggFrame = document.getElementById("ubiquity-suggest");
+ MutationObserver(function resizeSuggs(ms) {
+ var doc = suggFrame.contentDocument;
+ suggFrame.height = (doc.body || doc.documentElement).clientHeight;
+ }).observe(suggFrame.contentDocument.body, {childList: true});
+ */
+
+ gUbiquity = new Ubiquity(panel,
+ document.getElementById("ubiquity-entry"),
+ cmdMan);
+
+ Mousetrap.bind('alt+space', function() {
+ gUbiquity.togglePanel()
+ return false
+ })
+}
View
284 cmdmanager.js
@@ -0,0 +1,284 @@
+const DEFAULT_PREVIEW_URL = "preview.html";
+const DEFAULT_MAX_SUGGESTIONS = 5;
+const MIN_MAX_SUGGS = 1;
+const MAX_MAX_SUGGS = 42;
+
+const NULL_QUERY = {suggestionList: [], finished: true, hasResults: false};
+
+var gDomNodes = {};
+
+CommandManager.__defineGetter__("maxSuggestions", function CM_getMaxSuggestions() {
+ return DEFAULT_MAX_SUGGESTIONS
+});
+
+CommandManager.__defineSetter__("maxSuggestions", function CM_setMaxSuggestions(value) {
+ var num = Math.max(MIN_MAX_SUGGS, Math.min(value | 0, MAX_MAX_SUGGS));
+});
+
+function CommandManager(cmdSource, parser, suggsNode, previewPaneNode) {
+ this.__cmdSource = cmdSource;
+ this.__hilitedIndex = 0;
+ this.__lastInput = "";
+ this.__lastSuggestion = null;
+ this.__queuedExecute = null;
+ this.__lastAsyncSuggestionCb = Boolean;
+ this.__nlParser = parser;
+ this.__dynaLoad = !parser;
+ this.__activeQuery = NULL_QUERY;
+ this.__domNodes = {
+ suggs: suggsNode,
+ preview: previewPaneNode,
+ };
+
+ var iframe = document.querySelector('#ubiquity-browser')
+
+ this._loadCommands();
+
+ this.setPreviewState("no-suggestions");
+
+ suggsNode.addEventListener("click", this, false);
+ suggsNode.addEventListener("DOMMouseScroll", this, false);
+}
+
+CommandManager.prototype = {
+ handleEvent: function CM_handleEvent(event) {
+ switch (event.type) {
+ case "click": {
+ if (event.button === 2) return;
+ let {target} = event;
+ do {
+ if (!("hasAttribute" in target)) return;
+ if (target.hasAttribute("index")) break;
+ } while ((target = target.parentNode));
+ let index = +target.getAttribute("index");
+ if (this.__hilitedIndex === index) return;
+ this.__hilitedIndex = index;
+ this.__lastAsyncSuggestionCb();
+ } break;
+ case "DOMMouseScroll": {
+ this[event.detail < 0 ? "moveIndicationUp" : "moveIndicationDown"]();
+ this.__lastAsyncSuggestionCb();
+ } break;
+ default: return;
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ },
+
+ setPreviewState: function CM_setPreviewState(state) {
+ var {suggs, preview} = this.__domNodes;
+ switch (state) {
+ case "computing-suggestions":
+ case "with-suggestions": {
+ suggs.style.display = "block";
+ preview.style.display = "block";
+ break;
+ }
+ case "no-suggestions": {
+ suggs.style.display = "none";
+ preview.style.display = "none";
+ preview.setAttribute('src', DEFAULT_PREVIEW_URL)
+ break;
+ }
+ default: throw new Error("Unknown state: " + state);
+ }
+ },
+
+ refresh: function CM_refresh() {
+ this.__cmdSource.refresh()
+ this.reset()
+ },
+
+ moveIndicationUp: function CM_moveIndicationUp(context) {
+ if (--this.__hilitedIndex < 0)
+ this.__hilitedIndex = this.__activeQuery.suggestionList.length - 1;
+ if (context) this._renderAll(context);
+ },
+
+ moveIndicationDown: function CM_moveIndicationDown(context) {
+ if (++this.__hilitedIndex >= this.__activeQuery.suggestionList.length)
+ this.__hilitedIndex = 0;
+ if (context) this._renderAll(context);
+ },
+
+ _loadCommands: function CM__loadCommands() {
+ if (this.__nlParser)
+ this.__nlParser.setCommandList(this.__cmdSource.getAllCommands());
+ },
+
+ _renderSuggestions: function CM__renderSuggestions() {
+ var {escapeHtml} = Utils, content = "";
+ var {__activeQuery: {suggestionList}, __hilitedIndex: hindex} = this;
+ for (let i = 0, l = suggestionList.length; i < l; ++i) {
+ let {displayHtml, icon} = suggestionList[i];
+ content += (
+ '<div class="suggested' + (i === hindex ? " hilited" : "") +
+ '" index="' + i + '"><div class="cmdicon">' +
+ (icon ? '<img src="' + escapeHtml(icon) + '"/>' : "") +
+ "</div>" + displayHtml + "</div>");
+ }
+ this.__domNodes.suggs.innerHTML = content;
+ },
+
+ _renderPreview: function CM__renderPreview(context) {
+ var activeSugg = this.hilitedSuggestion;
+ if (!activeSugg || activeSugg === this.__lastSuggestion)
+ return;
+
+ var self = this;
+ this.__lastSuggestion = activeSugg;
+ //console.log('renderPreview', activeSugg.previewUrl, activeSugg)
+ //console.log(activeSugg._verb.arguments[0].id)
+ //console.log(activeSugg.args.object[0].text)
+ //console.log('renderPreview', activeSugg.previewUrl)
+
+ var iframe = this.__domNodes.preview,
+ url = activeSugg.previewUrl
+
+ //this.__domNodes.preview.setAttribute('src', activeSugg.previewUrl)
+ this.__domNodes.preview.src = url
+ //this.__domNodes.preview.location = activeSugg.previewUrl
+ //this.__domNodes.preview.style.display = 'none'
+ },
+
+ _renderAll: function CM__renderAll(context) {
+ //if (window.gUbiquity.isPanelOpen) {
+ this._renderSuggestions();
+ this._renderPreview(context);
+ //}
+ },
+
+ reset: function CM_reset() {
+ var query = this.__activeQuery;
+ if (!query.finished)
+ query.cancel();
+ this.__activeQuery = NULL_QUERY;
+ this.__hilitedIndex = 0;
+ this.__lastInput = "";
+ this.__lastSuggestion = null;
+ this.__queuedExecute = null;
+ },
+
+ updateInput: function CM_updateInput(input, context, asyncSuggestionCb) {
+ this.reset();
+ this.__lastInput = input;
+
+ var query = this.__activeQuery =
+ this.__nlParser.newQuery(input, context, this.maxSuggestions, true);
+ query.onResults = asyncSuggestionCb || this.__lastAsyncSuggestionCb;
+
+ if (asyncSuggestionCb)
+ this.__lastAsyncSuggestionCb = asyncSuggestionCb;
+
+ query.run();
+ },
+
+ onSuggestionsUpdated: function CM_onSuggestionsUpdated(input, context) {
+ if (input !== this.__lastInput)
+ return
+
+ var {hilitedSuggestion} = this;
+ if (this.__queuedExecute && hilitedSuggestion) {
+ this.__queuedExecute(hilitedSuggestion);
+ this.__queuedExecute = null;
+ }
+
+ this.setPreviewState(this.__activeQuery.finished
+ ? (hilitedSuggestion
+ ? "with-suggestions"
+ : "no-suggestions")
+ : "computing-suggestions");
+ this._renderAll(context);
+ },
+
+ execute: function CM_execute(context) {
+ function doExecute(activeSugg) {
+ try {
+ this.__nlParser.strengthenMemory(activeSugg);
+ activeSugg.execute(context);
+ } catch (e) {
+ }
+ }
+ var {hilitedSuggestion} = this;
+ if (hilitedSuggestion)
+ doExecute.call(this, hilitedSuggestion);
+ else
+ this.__queuedExecute = doExecute;
+ },
+
+ getSuggestionListNoInput:
+ function CM_getSuggListNoInput(context, asyncSuggestionCb) {
+ let noInputQuery = this.__nlParser.newQuery(
+ "", context, 4 * CommandManager.maxSuggestions);
+ noInputQuery.onResults = function onResultsNoInput() {
+ asyncSuggestionCb(noInputQuery.suggestionList);
+ };
+ },
+
+ makeCommandSuggester: function CM_makeCommandSuggester() {
+ var self = this;
+ return function getAvailableCommands(context, popupCb) {
+ self.getSuggestionListNoInput(context, popupCb);
+ };
+ },
+
+ remember: function CM_remember() {
+ var {hilitedSuggestion} = this;
+ if (hilitedSuggestion) this.__nlParser.strengthenMemory(hilitedSuggestion);
+ },
+
+ get parser() this.__nlParser,
+ get lastInput() this.__lastInput,
+
+ get maxSuggestions() CommandManager.maxSuggestions,
+ get hasSuggestions() this.__activeQuery.hasResults,
+ get suggestions() this.__activeQuery.suggestionList,
+ get hilitedSuggestion()
+ this.__activeQuery.suggestionList[this.__hilitedIndex],
+ get hilitedIndex() this.__hilitedIndex,
+ set hilitedIndex(i) this.__hilitedIndex = i,
+};
+
+function CommandAggregator(input) {
+ var self = this;
+ var commands = {}
+ var commandNames = [];
+ var commandsByName = {__proto__: null};
+ var commandsByDomain = {__proto__: null};
+
+ self.refresh = function FA_refresh() {
+ commands = {}
+ commandNames = [];
+ commandsByName = {__proto__: null};
+
+ input.forEach(function(cmd) {
+ if (cmd.name && !cmd.names)
+ cmd.names = [cmd.name]
+ cmd.id = cmd.url
+ commandNames.push({
+ name: cmd.name,
+ url: cmd.url,
+ icon: cmd.icon,
+ });
+ commands[cmd.url] = commandsByName[cmd.name] = cmd
+ })
+ }
+
+ self.__defineGetter__("commandNames",
+ function FA_cmdNames() commandNames)
+ self.__defineGetter__("commandsByName",
+ function FA_cmdsByName() commandsByName)
+ self.__defineGetter__("commandsByDomain",
+ function FA_cmdsByDomain() commandsByDomain)
+
+ self.getAllCommands = function FA_getAllCommands() {
+ return commands
+ }
+
+ self.getCommand = function FA_getCommand(id) {
+ return commands[id] || commandsByName[id] || null
+ }
+
+ if (input)
+ self.refresh()
+}
View
11 commands/google.html
@@ -0,0 +1,11 @@
+<script>
+
+var url = 'https://ajax.googleapis.com/ajax/services/search/news?v=1.0&callback=processResults&q='
+
+
+
+function processResults(results) {
+ console.log(results)
+}
+
+</script>
View
53 demo.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <link rel="stylesheet" type="text/css" href="skin/browser.css">
+ <title>webiquity</title>
+</head>
+<body>
+ <div>
+ <div id="ubiquity-transparent-panel">
+ <div id="ubiquity-panel">
+ <div id="ubiquity-frame">
+ <div id="ubiquity-entry-container">
+ <input id="ubiquity-entry" type="text"></input>
+ </div>
+ <div id="ubiquity-suggest-container">
+ </div>
+ <div id="ubiquity-preview-container">
+ <div id="ubiquity-preview">
+ <iframe id="ubiquity-browser" src=""
+ width="480" height="500" transparent="true"
+ tooltip="aHTMLTooltip"></iframe>
+ </div>
+ </div>
+ <div id="ubiquity-help"></div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- general -->
+ <script type="application/javascript;version=1.8" src="asyncStorage.js"></script>
+ <script type="application/javascript;version=1.8" src="mousetrap.min.js"></script>
+ <script type="application/javascript;version=1.8" src="eventemitter.js"></script>
+ <script type="application/javascript;version=1.8" src="mustache.js"></script>
+
+ <script type="application/javascript;version=1.8" src="utils.js"></script>
+ <script type="application/javascript;version=1.8" src="nountypes.js"></script>
+ <script type="application/javascript;version=1.8" src="suggestionMemory.js"></script>
+ <script type="application/javascript;version=1.8" src="parser.js"></script>
+ <script type="application/javascript;version=1.8" src="registry.js"></script>
+
+ <script type="application/javascript;version=1.8" src="cmdmanager.js"></script>
+ <script type="application/javascript;version=1.8" src="ubiquity.js"></script>
+
+ <script type="application/javascript;version=1.8" src="browser.js"></script>
+ <script type="application/javascript;version=1.8" src="testCommands.js"></script>
+
+ <h1>alt+space</h1>
+
+</body>
+</html>
View
108 eventEmitter.js
@@ -0,0 +1,108 @@
+function EventEmitter(){
+ var map = new Object;
+ var listenernum = new Object;
+
+ this.addListener = function(eventname, listener){
+ this.on(eventname, listener);
+ }
+
+ this.on = function(eventname, listener){
+ var self = this;
+ if (arguments.length != 2) {
+ self.emit("error", "on() method must have 2 arguments.");
+ return;
+ }
+ if (typeof eventname != "string") {
+ self.emit("error", "on() first argument must String type.");
+ return;
+ }
+ if (!(typeof listener == "function")) {
+ self.emit("error", "on() second argument must Function type.");
+ return;
+ }
+ if (map[eventname])
+ ;
+ else
+ map[eventname] = new Object;
+
+ if (listenernum[eventname])
+ ;
+ else
+ listenernum[eventname] = "1";
+ var num = listenernum[eventname] = parseInt(listenernum[eventname]) + 1 + "";
+ map[eventname][num] = listener;
+ self.emit("newListener", eventname, listener);
+ return num;
+ }
+ this.removeListener = function(eventname, listenernum){
+ var self = this;
+ if (arguments.length != 2) {
+ self.emit("error", "removeListener() method must have 2 arguments.");
+ return;
+ }
+ if (typeof eventname != "string") {
+ self.emit("error", "removeListener() first argument must String type.");
+ return;
+ }
+ if (typeof listenernum != "string") {
+ self.emit("error", "removeListener() second argument must String type.");
+ return;
+ }
+ delete map[eventname][listenernum];
+ }
+ this.removeAllListener = function(eventname){
+ var self = this;
+ if (arguments.length != 1) {
+ self.emit("error", "removeAllListener() method must have 1 arguments.");
+ return;
+ }
+ if (typeof eventname != "string") {
+ self.emit("error", "removeAllListener() first argument must String type.");
+ return;
+ }
+ delete map[eventname];
+ }
+ this.listeners = function(eventname){
+ var self = this;
+ if (arguments.length != 1) {
+ self.emit("error", "listeners() method must have 1 arguments.");
+ return;
+ }
+ if (typeof eventname != "string") {
+ self.emit("error", "listeners() first argument must String type.");
+ return;
+ }
+ var result = new Array;
+ for (var key in map[eventname])
+ result.push(map[eventname][key]);
+ return result;
+ }
+ this.emit = function(eventname){
+ var self = this;
+ if (!(arguments.length >= 1)) {
+ self.emit("error", "emit() method must have 1 arguments.");
+ return;
+ }
+ if (typeof eventname != "string") {
+ self.emit("error", "emit() first argument must String type.");
+ return;
+ }
+ var args = new Array;
+
+ for (var i = 0; i < arguments.length - 1; i++) {
+ args[i] = arguments[i + 1];
+ }
+
+ for (var key in map[eventname]) {
+ var func = map[eventname][key];
+ func.apply(this, args);
+ }
+ }
+
+
+
+ this.on("error", function(exception){
+ console.log(exception);
+ });
+
+}
View
22 idbstore.min.js
@@ -0,0 +1,22 @@
+/*
+ IDBWrapper - A cross-browser wrapper for IndexedDB
+ Copyright (c) 2011 - 2013 Jens Arps
+ http://jensarps.de/
+
+ Licensed under the MIT (X11) license
+*/
+(function(g,e,f){"function"===typeof define?define(e):"undefined"!==typeof module&&module.exports?module.exports=e():f[g]=e()})("IDBStore",function(){var g={storeName:"Store",storePrefix:"IDBWrapper-",dbVersion:1,keyPath:"id",autoIncrement:!0,onStoreReady:function(){},onError:function(a){throw a;},indexes:[]},e=function(a,c){for(var b in g)this[b]="undefined"!=typeof a[b]?a[b]:g[b];this.dbName=this.storePrefix+this.storeName;this.dbVersion=parseInt(this.dbVersion,10);c&&(this.onStoreReady=c);this.idb=
+window.indexedDB||window.webkitIndexedDB||window.mozIndexedDB;this.keyRange=window.IDBKeyRange||window.webkitIDBKeyRange||window.mozIDBKeyRange;this.consts={READ_ONLY:"readonly",READ_WRITE:"readwrite",VERSION_CHANGE:"versionchange",NEXT:"next",NEXT_NO_DUPLICATE:"nextunique",PREV:"prev",PREV_NO_DUPLICATE:"prevunique"};this.openDB()};e.prototype={version:"1.0.0",db:null,dbName:null,dbVersion:null,store:null,storeName:null,keyPath:null,autoIncrement:null,indexes:null,features:null,onStoreReady:null,
+onError:null,_insertIdCount:0,openDB:function(){(this.features={}).hasAutoIncrement=!window.mozIndexedDB;var a=this.idb.open(this.dbName,this.dbVersion),c=!1;a.onerror=function(a){var c=!1;"error"in a.target?c="VersionError"==a.target.error.name:"errorCode"in a.target&&(c=12==a.target.errorCode);if(c)this.onError(Error("The version number provided is lower than the existing one."));else this.onError(a)}.bind(this);a.onsuccess=function(a){if(!c)if(this.db)this.onStoreReady();else if(this.db=a.target.result,
+"string"==typeof this.db.version)this.onError(Error("The IndexedDB implementation in this browser is outdated. Please upgrade your browser."));else if(this.db.objectStoreNames.contains(this.storeName))this.store=this.db.transaction([this.storeName],this.consts.READ_ONLY).objectStore(this.storeName),this.indexes.forEach(function(a){var b=a.name;b?(this.normalizeIndexData(a),this.hasIndex(b)?this.indexComplies(this.store.index(b),a)||(c=!0,this.onError(Error('Cannot modify index "'+b+'" for current version. Please bump version number to '+
+(this.dbVersion+1)+"."))):(c=!0,this.onError(Error('Cannot create new index "'+b+'" for current version. Please bump version number to '+(this.dbVersion+1)+".")))):(c=!0,this.onError(Error("Cannot create index: No index name given.")))},this),c||this.onStoreReady();else this.onError(Error("Something is wrong with the IndexedDB implementation in this browser. Please upgrade your browser."))}.bind(this);a.onupgradeneeded=function(a){this.db=a.target.result;this.store=this.db.objectStoreNames.contains(this.storeName)?
+a.target.transaction.objectStore(this.storeName):this.db.createObjectStore(this.storeName,{keyPath:this.keyPath,autoIncrement:this.autoIncrement});this.indexes.forEach(function(a){var b=a.name;b||(c=!0,this.onError(Error("Cannot create index: No index name given.")));this.normalizeIndexData(a);this.hasIndex(b)?this.indexComplies(this.store.index(b),a)||(this.store.deleteIndex(b),this.store.createIndex(b,a.keyPath,{unique:a.unique,multiEntry:a.multiEntry})):this.store.createIndex(b,a.keyPath,{unique:a.unique,
+multiEntry:a.multiEntry})},this)}.bind(this)},deleteDatabase:function(){this.idb.deleteDatabase&&this.idb.deleteDatabase(this.dbName)},put:function(a,c,b){b||(b=function(a){console.error("Could not write data.",a)});c||(c=f);"undefined"==typeof a[this.keyPath]&&!this.features.hasAutoIncrement&&(a[this.keyPath]=this._getUID());a=this.db.transaction([this.storeName],this.consts.READ_WRITE).objectStore(this.storeName).put(a);a.onsuccess=function(a){c(a.target.result)};a.onerror=b},get:function(a,c,b){b||
+(b=function(a){console.error("Could not read data.",a)});c||(c=f);a=this.db.transaction([this.storeName],this.consts.READ_ONLY).objectStore(this.storeName).get(a);a.onsuccess=function(a){c(a.target.result)};a.onerror=b},remove:function(a,c,b){b||(b=function(a){console.error("Could not remove data.",a)});c||(c=f);a=this.db.transaction([this.storeName],this.consts.READ_WRITE).objectStore(this.storeName)["delete"](a);a.onsuccess=function(a){c(a.target.result)};a.onerror=b},batch:function(a,c,b){b||(b=
+function(a){console.error("Could not apply batch.",a)});c||(c=f);"[object Array]"!=Object.prototype.toString.call(a)&&b(Error("dataArray argument must be of type Array."));var d=this.db.transaction([this.storeName],this.consts.READ_WRITE),h=a.length,i=!1;a.forEach(function(a){var e=a.type,f=a.key,g=a.value;if("remove"==e)a=d.objectStore(this.storeName)["delete"](f),a.onsuccess=function(){h--;0===h&&!i&&(i=!0,c())},a.onerror=function(a){d.abort();i||(i=!0,b(a,e,f))};else if("put"==e)"undefined"==typeof g[this.keyPath]&&
+!this.features.hasAutoIncrement&&(g[this.keyPath]=this._getUID()),a=d.objectStore(this.storeName).put(g),a.onsuccess=function(){h--;0===h&&!i&&(i=!0,c())},a.onerror=function(a){d.abort();i||(i=!0,b(a,e,g))}},this)},getAll:function(a,c){c||(c=function(a){console.error("Could not read data.",a)});a||(a=f);var b=this.db.transaction([this.storeName],this.consts.READ_ONLY),d=b.objectStore(this.storeName);d.getAll?(b=d.getAll(),b.onsuccess=function(c){a(c.target.result)},b.onerror=c):this._getAllCursor(b,
+a,c)},_getAllCursor:function(a,c,b){var d=[],a=a.objectStore(this.storeName).openCursor();a.onsuccess=function(a){(a=a.target.result)?(d.push(a.value),a["continue"]()):c(d)};a.onError=b},clear:function(a,c){c||(c=function(a){console.error("Could not clear store.",a)});a||(a=f);var b=this.db.transaction([this.storeName],this.consts.READ_WRITE).objectStore(this.storeName).clear();b.onsuccess=function(c){a(c.target.result)};b.onerror=c},_getUID:function(){return this._insertIdCount++ +Date.now()},getIndexList:function(){return this.store.indexNames},
+hasIndex:function(a){return this.store.indexNames.contains(a)},normalizeIndexData:function(a){a.keyPath=a.keyPath||a.name;a.unique=!!a.unique;a.multiEntry=!!a.multiEntry},indexComplies:function(a,c){return["keyPath","unique","multiEntry"].every(function(b){return"multiEntry"==b&&void 0===a[b]&&!1===c[b]?!0:c[b]==a[b]})},iterate:function(a,c){var c=j({index:null,order:"ASC",filterDuplicates:!1,keyRange:null,writeAccess:!1,onEnd:null,onError:function(a){console.error("Could not open cursor.",a)}},c||
+{}),b="desc"==c.order.toLowerCase()?"PREV":"NEXT";c.filterDuplicates&&(b+="_NO_DUPLICATE");var d=this.db.transaction([this.storeName],this.consts[c.writeAccess?"READ_WRITE":"READ_ONLY"]),h=d.objectStore(this.storeName);c.index&&(h=h.index(c.index));b=h.openCursor(c.keyRange,this.consts[b]);b.onerror=c.onError;b.onsuccess=function(b){if(b=b.target.result)a(b.value,b,d),b["continue"]();else if(c.onEnd)c.onEnd();else a(null)}},query:function(a,c){var b=[],c=c||{};c.onEnd=function(){a(b)};this.iterate(function(a){b.push(a)},
+c)},count:function(a,c){var c=j({index:null,keyRange:null},c||{}),b=c.onError||function(a){console.error("Could not open cursor.",a)},d=this.db.transaction([this.storeName],this.consts.READ_ONLY).objectStore(this.storeName);c.index&&(d=d.index(c.index));d=d.count(c.keyRange);d.onsuccess=function(b){a(b.target.result)};d.onError=function(a){b(a)}},makeKeyRange:function(a){var c="undefined"!=typeof a.lower,b="undefined"!=typeof a.upper;switch(!0){case c&&b:a=this.keyRange.bound(a.lower,a.upper,a.excludeLower,
+a.excludeUpper);break;case c:a=this.keyRange.lowerBound(a.lower,a.excludeLower);break;case b:a=this.keyRange.upperBound(a.upper,a.excludeUpper);break;default:throw Error('Cannot create KeyRange. Provide one or both of "lower" or "upper" value.');}return a}};var f=function(){},k={},j=function(a,c){var b,d;for(b in c)d=c[b],d!==k[b]&&d!==a[b]&&(a[b]=d);return a};e.version=e.prototype.version;return e},this);
View
8 mousetrap.min.js
@@ -0,0 +1,8 @@
+/* mousetrap v1.2.2 craig.is/killing/mice */
+(function(){function q(a,c,b){a.addEventListener?a.addEventListener(c,b,!1):a.attachEvent("on"+c,b)}function x(a){return"keypress"==a.type?String.fromCharCode(a.which):h[a.which]?h[a.which]:y[a.which]?y[a.which]:String.fromCharCode(a.which).toLowerCase()}function r(a,c){var a=a||{},b=!1,d;for(d in l)a[d]&&l[d]>c?b=!0:l[d]=0;b||(n=!1)}function z(a,c,b,d,F){var g,e,f=[],j=b.type;if(!k[a])return[];"keyup"==j&&s(a)&&(c=[a]);for(g=0;g<k[a].length;++g)if(e=k[a][g],!(e.seq&&l[e.seq]!=e.level)&&j==e.action&&
+("keypress"==j&&!b.metaKey&&!b.ctrlKey||c.sort().join(",")===e.modifiers.sort().join(",")))d&&e.combo==F&&k[a].splice(g,1),f.push(e);return f}function t(a,c,b){if(!u.stopCallback(c,c.target||c.srcElement,b)&&!1===a(c,b))c.preventDefault&&c.preventDefault(),c.stopPropagation&&c.stopPropagation(),c.returnValue=!1,c.cancelBubble=!0}function v(a){"number"!==typeof a.which&&(a.which=a.keyCode);var c=x(a);if(c)if("keyup"==a.type&&w==c)w=!1;else{var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");
+a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");var b=z(c,b,a),d,f={},g=0,e=!1;for(d=0;d<b.length;++d)b[d].seq?(e=!0,g=Math.max(g,b[d].level),f[b[d].seq]=1,t(b[d].callback,a,b[d].combo)):!e&&!n&&t(b[d].callback,a,b[d].combo);a.type==n&&!s(c)&&r(f,g)}}function s(a){return"shift"==a||"ctrl"==a||"alt"==a||"meta"==a}function A(a,c,b){if(!b){if(!p){p={};for(var d in h)95<d&&112>d||h.hasOwnProperty(d)&&(p[h[d]]=d)}b=p[a]?"keydown":"keypress"}"keypress"==b&&c.length&&(b="keydown");return b}function B(a,
+c,b,d,f){var a=a.replace(/\s+/g," "),g=a.split(" "),e,h,j=[];if(1<g.length){var i=a,m=b;l[i]=0;m||(m=A(g[0],[]));a=function(){n=m;++l[i];clearTimeout(C);C=setTimeout(r,1E3)};b=function(a){t(c,a,i);"keyup"!==m&&(w=x(a));setTimeout(r,10)};for(d=0;d<g.length;++d)B(g[d],d<g.length-1?a:b,m,i,d)}else{h="+"===a?["+"]:a.split("+");for(g=0;g<h.length;++g)e=h[g],D[e]&&(e=D[e]),b&&("keypress"!=b&&E[e])&&(e=E[e],j.push("shift")),s(e)&&j.push(e);b=A(e,j,b);k[e]||(k[e]=[]);z(e,j,{type:b},!d,a);k[e][d?"unshift":
+"push"]({callback:c,modifiers:j,action:b,seq:d,level:f,combo:a})}}for(var h={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},y={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},E={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7",
+"*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},D={option:"alt",command:"meta","return":"enter",escape:"esc"},p,k={},i={},l={},C,w=!1,n=!1,f=1;20>f;++f)h[111+f]="f"+f;for(f=0;9>=f;++f)h[f+96]=f;q(document,"keypress",v);q(document,"keydown",v);q(document,"keyup",v);var u={bind:function(a,c,b){for(var d=a instanceof Array?a:[a],f=0;f<d.length;++f)B(d[f],c,b);i[a+":"+b]=c;return this},unbind:function(a,c){i[a+":"+c]&&(delete i[a+":"+c],this.bind(a,function(){},
+c));return this},trigger:function(a,c){i[a+":"+c]();return this},reset:function(){k={};i={};return this},stopCallback:function(a,c){return-1<(" "+c.className+" ").indexOf(" mousetrap ")?!1:"INPUT"==c.tagName||"SELECT"==c.tagName||"TEXTAREA"==c.tagName||c.contentEditable&&"true"==c.contentEditable}};window.Mousetrap=u;"function"===typeof define&&define.amd&&define(u)})();
View
325 mustache.js
@@ -0,0 +1,325 @@
+/*
+ mustache.js — Logic-less templates in JavaScript
+
+ See http://mustache.github.com/ for more info.
+*/
+
+var Mustache = function() {
+ var Renderer = function() {};
+
+ Renderer.prototype = {
+ otag: "{{",
+ ctag: "}}",
+ pragmas: {},
+ buffer: [],
+ pragmas_implemented: {
+ "IMPLICIT-ITERATOR": true
+ },
+ context: {},
+
+ render: function(template, context, partials, in_recursion) {
+ // reset buffer & set context
+ if(!in_recursion) {
+ this.context = context;
+ this.buffer = []; // TODO: make this non-lazy
+ }
+
+ // fail fast
+ if(!this.includes("", template)) {
+ if(in_recursion) {
+ return template;
+ } else {
+ this.send(template);
+ return;
+ }
+ }
+
+ template = this.render_pragmas(template);
+ var html = this.render_section(template, context, partials);
+ if(in_recursion) {
+ return this.render_tags(html, context, partials, in_recursion);
+ }
+
+ this.render_tags(html, context, partials, in_recursion);
+ },
+
+ /*
+ Sends parsed lines
+ */
+ send: function(line) {
+ if(line !== "") {
+ this.buffer.push(line);
+ }
+ },
+
+ /*
+ Looks for %PRAGMAS
+ */
+ render_pragmas: function(template) {
+ // no pragmas
+ if(!this.includes("%", template)) {
+ return template;
+ }
+
+ var that = this;
+ var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
+ this.ctag, "g");
+ return template.replace(regex, function(match, pragma, options) {
+ if(!that.pragmas_implemented[pragma]) {
+ throw({message:
+ "This implementation of mustache doesn't understand the '" +
+ pragma + "' pragma"});
+ }
+ that.pragmas[pragma] = {};
+ if(options) {
+ var opts = options.split("=");
+ that.pragmas[pragma][opts[0]] = opts[1];
+ }
+ return "";
+ // ignore unknown pragmas silently
+ });
+ },
+
+ /*
+ Tries to find a partial in the curent scope and render it
+ */
+ render_partial: function(name, context, partials) {
+ name = this.trim(name);
+ if(!partials || partials[name] === undefined) {
+ throw({message: "unknown_partial '" + name + "'"});
+ }
+ if(typeof(context[name]) != "object") {
+ return this.render(partials[name], context, partials, true);
+ }
+ return this.render(partials[name], context[name], partials, true);
+ },
+
+ /*
+ Renders inverted (^) and normal (#) sections
+ */
+ render_section: function(template, context, partials) {
+ if(!this.includes("#", template) && !this.includes("^", template)) {
+ return template;
+ }
+
+ var that = this;
+ // CSW - Added "+?" so it finds the tighest bound, not the widest
+ var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
+ "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
+ "\\s*", "mg");
+
+ // for each {{#foo}}{{/foo}} section do...
+ return template.replace(regex, function(match, type, name, content) {
+ var value = that.find(name, context);
+ if(type == "^") { // inverted section
+ if(!value || that.is_array(value) && value.length === 0) {
+ // false or empty list, render it
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
+ } else if(type == "#") { // normal section
+ if(that.is_array(value)) { // Enumerable, Let's loop!
+ return that.map(value, function(row) {
+ return that.render(content, that.create_context(row),
+ partials, true);
+ }).join("");
+ } else if(that.is_object(value)) { // Object, Use it as subcontext!
+ return that.render(content, that.create_context(value),
+ partials, true);
+ } else if(typeof value === "function") {
+ // higher order section
+ return value.call(context, content, function(text) {
+ return that.render(text, context, partials, true);
+ });
+ } else if(value) { // boolean section
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
+ }
+ });
+ },
+
+ /*
+ Replace {{foo}} and friends with values from our view
+ */
+ render_tags: function(template, context, partials, in_recursion) {
+ // tit for tat
+ var that = this;
+
+ var new_regex = function() {
+ return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
+ that.ctag + "+", "g");
+ };
+
+ var regex = new_regex();
+ var tag_replace_callback = function(match, operator, name) {
+ switch(operator) {
+ case "!": // ignore comments
+ return "";
+ case "=": // set new delimiters, rebuild the replace regexp
+ that.set_delimiters(name);
+ regex = new_regex();
+ return "";
+ case ">": // render partial
+ return that.render_partial(name, context, partials);
+ case "{": // the triple mustache is unescaped
+ return that.find(name, context);
+ default: // escape the value
+ return that.escape(that.find(name, context));
+ }
+ };
+ var lines = template.split("\n");
+ for(var i = 0; i < lines.length; i++) {
+ lines[i] = lines[i].replace(regex, tag_replace_callback, this);
+ if(!in_recursion) {
+ this.send(lines[i]);
+ }
+ }
+
+ if(in_recursion) {
+ return lines.join("\n");
+ }
+ },
+
+ set_delimiters: function(delimiters) {
+ var dels = delimiters.split(" ");
+ this.otag = this.escape_regex(dels[0]);
+ this.ctag = this.escape_regex(dels[1]);
+ },
+
+ escape_regex: function(text) {
+ // thank you Simon Willison
+ if(!arguments.callee.sRE) {
+ var specials = [
+ '/', '.', '*', '+', '?', '|',
+ '(', ')', '[', ']', '{', '}', '\\'
+ ];
+ arguments.callee.sRE = new RegExp(
+ '(\\' + specials.join('|\\') + ')', 'g'
+ );
+ }
+ return text.replace(arguments.callee.sRE, '\\$1');
+ },
+
+ /*
+ find `name` in current `context`. That is find me a value
+ from the view object
+ */
+ find: function(name, context) {
+ name = this.trim(name);
+
+ // Checks whether a value is thruthy or false or 0
+ function is_kinda_truthy(bool) {
+ return bool === false || bool === 0 || bool;
+ }
+
+ var value;
+ if(is_kinda_truthy(context[name])) {
+ value = context[name];
+ } else if(is_kinda_truthy(this.context[name])) {
+ value = this.context[name];
+ }
+
+ if(typeof value === "function") {
+ return value.apply(context);
+ }
+ if(value !== undefined) {
+ return value;
+ }
+ // silently ignore unkown variables
+ return "";
+ },
+
+ // Utility methods
+
+ /* includes tag */
+ includes: function(needle, haystack) {
+ return haystack.indexOf(this.otag + needle) != -1;
+ },
+
+ /*
+ Does away with nasty characters
+ */
+ escape: function(s) {
+ s = String(s === null ? "" : s);
+ return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
+ switch(s) {
+ case "&": return "&amp;";
+ case "\\": return "\\\\";
+ case '"': return '&quot;';
+ case "'": return '&#39;';
+ case "<": return "&lt;";
+ case ">": return "&gt;";
+ default: return s;
+ }
+ });
+ },
+
+ // by @langalex, support for arrays of strings
+ create_context: function(_context) {
+ if(this.is_object(_context)) {
+ return _context;
+ } else {
+ var iterator = ".";
+ if(this.pragmas["IMPLICIT-ITERATOR"]) {
+ iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
+ }
+ var ctx = {};
+ ctx[iterator] = _context;
+ return ctx;
+ }
+ },
+
+ is_object: function(a) {
+ return a && typeof a == "object";
+ },
+
+ is_array: function(a) {
+ return Object.prototype.toString.call(a) === '[object Array]';
+ },
+
+ /*
+ Gets rid of leading and trailing whitespace
+ */
+ trim: function(s) {
+ return s.replace(/^\s*|\s*$/g, "");
+ },
+
+ /*
+ Why, why, why? Because IE. Cry, cry cry.
+ */
+ map: function(array, fn) {
+ if (typeof array.map == "function") {
+ return array.map(fn);
+ } else {
+ var r = [];
+ var l = array.length;
+ for(var i = 0; i < l; i++) {
+ r.push(fn(array[i]));
+ }
+ return r;
+ }
+ }
+ };
+
+ return({
+ name: "mustache.js",
+ version: "0.3.1-dev",
+
+ /*
+ Turns a template and view into HTML
+ */
+ to_html: function(template, view, partials, send_fun) {
+ var renderer = new Renderer();
+ if(send_fun) {
+ renderer.send = send_fun;
+ }
+ renderer.render(template, view, partials);
+ if(!send_fun) {
+ return renderer.buffer.join("\n");
+ }
+ }
+ });
+}();
View
1,373 nountypes.js
@@ -0,0 +1,1373 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Ubiquity.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Jono DiCarlo <jdicarlo@mozilla.com>
+ * Blair McBride <unfocused@gmail.com>
+ * Abimanyu Raja <abimanyuraja@gmail.com>
+ * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
+ * Satoshi Murakami <murky.satyr@gmail.com>
+ * Brandon Pung <brandonpung@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// = Built-in Noun Types =
+// **//FIXME//**
+// \\Explain:
+// * how nouns work.
+// * common properties.
+// ** {{{suggest}}}
+// ** {{{default}}}
+// ** {{{label}}} (, {{{name}}}, {{{id}}})
+// ** {{{noSelection}}}
+// ** {{{noExternalCalls}}}
+
+var NounUtils = (function() {
+
+ var nu = {}
+
+ // === {{{ NounUtils.NounType(label, expected, defaults) }}} ===
+ //
+ // Constructor of a noun type that accepts a specific set of inputs.
+ // See {{{NounType._from*}}} methods for details
+ // (but do not use them directly).
+ //
+ // {{{label}}} is an optional string specifying default label of the nountype.
+ //
+ // {{{expected}}} is the instance of {{{Array}}}, {{{Object}}} or {{{RegExp}}}.
+ // The array can optionally be a space-separated string.
+ //
+ // {{{defaults}}} is an optional array or space-separated string
+ // of default inputs.
+
+ function NounType(label, expected, defaults) {
+ if (!(this instanceof NounType))
+ return new NounType(label, expected, defaults);
+
+ if (typeof label !== "string")
+ [label, expected, defaults] = ["?", label, expected];
+
+ if (typeof expected.suggest === "function") return expected;
+
+ function maybe_qw(o) typeof o === "string" ? o.match(/\S+/g) || [] : o;
+ expected = maybe_qw(expected);
+ defaults = maybe_qw(defaults);
+
+ var maker = NounType["_from" + Utils.classOf(expected)];
+ for (let [k, v] in new Iterator(maker(expected))) this[k] = v;
+ this.suggest = maker.suggest;
+ this.label = label;
+ this.noExternalCalls = true;
+ this.cacheTime = -1;
+ // TODO fix to generate unique id
+ if (this.id) this.id += (new Date())
+ if (defaults) {
+ // [[a], [b, c], ...] => [a].concat([b, c], ...) => [a, b, c, ...]
+ this.default =
+ Array.concat.apply(0, [this.suggest(d) for each (d in defaults)]);
+ }
+ }
+ nu.NounType = NounType
+
+ // ** {{{ NounUtils.NounType._fromArray(words) }}} **
+ //
+ // Creates a noun type that accepts a finite list of specific words
+ // as the only valid inputs. Those words will be suggested as {{{text}}}s.
+ //
+ // {{{words}}} is the array of words.
+
+ NounType._fromArray = function NT_Array(words)({
+ id: "#na_",
+ name: words.slice(0, 2) + (words.length > 2 ? ",..." : ""),
+ _list: [makeSugg(w) for each (w in words)],
+ });
+
+ // ** {{{ NounUtils.NounType._fromObject(dict) }}} **
+ //
+ // Creates a noun type from the given key:value pairs, the key being
+ // the {{{text}}} attribute of its suggest and the value {{{data}}}.
+ //
+ // {{{dict}}} is the object of text:data pairs.
+
+ NounType._fromObject = function NT_Object(dict) {
+ var list = [makeSugg(key, null, dict[key]) for (key in dict)];
+ return {
+ name: ([s.text for each (s in list.slice(0, 2))] +
+ (list.length > 2 ? ",..." : "")),
+ _list: list,
+ };
+ };
+
+ NounType._fromArray.suggest = NounType._fromObject.suggest = (
+ function NT_suggest(text) grepSuggs(text, this._list));
+
+ // ** {{{ NounUtils.NounType._fromRegExp(regexp) }}} **
+ //
+ // Creates a noun type from the given regular expression object
+ // and returns it. The {{{data}}} attribute of the noun type is
+ // the {{{match}}} object resulting from the regular expression
+ // match.
+ //
+ // {{{regexp}}} is the RegExp object that checks inputs.
+
+ NounType._fromRegExp = function NT_RegExp(regexp) ({
+ id: "#nr_",
+ name: regexp + "",
+ rankLast: regexp.test(""),
+ _regexp: RegExp(
+ regexp.source,
+ [ "g"[regexp.global - 1]
+ , "i"[regexp.ignoreCase - 1]
+ , "m"[regexp.multiline - 1]
+ , "y"[regexp.sticky - 1]
+ ].join('')),
+ });
+ NounType._fromRegExp.suggest = function NT_RE_suggest(text, html, cb,
+ selectionIndices) {
+ var match = text.match(this._regexp);
+ if (!match) return [];
+ // ToDo: how to score global match
+ var score = "index" in match ? matchScore(match) : 1;
+ return [makeSugg(text, html, match, score, selectionIndices)];
+ };
+
+ // === {{{ NounUtils.matchScore(match) }}} ===
+ //
+ // Calculates the score for use in suggestions from
+ // a result array ({{{match}}}) of {{{RegExp#exec}}}.
+
+ const SCORE_BASE = 0.3;
+ const SCORE_LENGTH = 0.25;
+ const SCORE_INDEX = 1 - SCORE_BASE - SCORE_LENGTH;
+
+ function matchScore(match) {
+ var inLen = match.input.length;
+ return (SCORE_BASE +
+ SCORE_LENGTH * Math.sqrt(match[0].length / inLen) +
+ SCORE_INDEX * (1 - match.index / inLen));
+ }
+ nu.matchScore = matchScore
+
+ // === {{{NounUtils.makeSugg(text, html, data, score, selectionIndices)}}} ===
+ //
+ // Creates a suggestion object, filling in {{{text}}} and {{{html}}} if missing
+ // and constructing {{{summary}}} from {{{text}}} and {{{selectionIndices}}}.
+ // At least one of {{{text}}}, {{{html}}} or {{{data}}} is required.
+ //
+ // {{{text}}} can be any string.
+ //
+ // {{{html}}} must be a valid HTML string.
+ //
+ // {{{data}}} can be any value.
+ //
+ // {{{score}}} is an optional float number representing
+ // the score of the suggestion. Defaults to {{{1.0}}}.
+ //
+ // {{{selectionIndices}}} is an optional array containing the start and end
+ // indices of selection within {{{text}}}.
+
+ function makeSugg(text, html, data, score, selectionIndices, arg) {
+ if (text == null && html == null && arguments.length < 3)
+ // all inputs empty! There is no suggestion to be made.
+ return null;
+
+ // Shift the argument if appropriate:
+ if (typeof score === "object") {
+ selectionIndices = score;
+ score = null;
+ }
+
+ // Fill in missing fields however we can:
+ if (text != null) text += "";
+ if (html != null) html += "";
+ if (!text && data != null)
+ text = data.toString();
+ if (!html && text >= "")
+ html = Utils.escapeHtml(text);
+ if (!text && html >= "")
+ text = html.replace(/<[^>]*>/g, "");
+
+ // Create a summary of the text:
+ var snippetLength = 35;
+ var summary = (text.length > snippetLength
+ ? text.slice(0, snippetLength - 1) + "\u2026"
+ : text);
+
+ // If the input comes all or in part from a text selection,
+ // we'll stick some html tags into the summary so that the part
+ // that comes from the text selection can be visually marked in
+ // the suggestion list.
+ var [start, end] = selectionIndices || 0;
+ summary = (
+ start < end
+ ? (Utils.escapeHtml(summary.slice(0, start)) +
+ "<span class='selection'>" +
+ Utils.escapeHtml(summary.slice(start, end)) +
+ "</span>" +
+ Utils.escapeHtml(summary.slice(end)))
+ : Utils.escapeHtml(summary));
+
+ return {
+ text: text,
+ html: html,
+ data: data,
+ summary: summary,
+ score: score || 1,
+ arg: arg
+ }
+ }
+ nu.makeSugg = makeSugg
+
+ // === {{{ NounUtils.grepSuggs(input, suggs, key) }}} ===
+ //
+ // A helper function to grep a list of suggestion objects by user input.
+ // Returns an array of filtered suggetions, each of them assigned {{{score}}}
+ // calculated by {{{NounUtils.matchScore()}}}.
+ //
+ // {{{input}}} is a string that filters the list.
+ //
+ // {{{suggs}}} is an array or dictionary of suggestion objects.
+ //
+ // {{{key}}} is an optional string to specify the target property
+ // to match with. Defaults to {{{"text"}}}.
+
+ function grepSuggs(input, suggs, key) {
+ if (!input) return [];
+ if (key == null) key = "text";
+ var re = Utils.regexp(input, "i"), match;
+ return ([(sugg.score = matchScore(match), sugg)
+ for each (sugg in suggs) if ((match = re.exec(sugg[key])))]
+ .sort(byScoreDescending));
+ }
+ nu.grepSuggs = grepSuggs
+
+ function byScoreDescending(a, b) b.score - a.score;
+ nu.byScoreDescending = byScoreDescending
+
+ // === {{{ NounUtils.mixNouns(label, nouns) }}} ===
+ //
+ // Creates a noun by combining two or more nouns.
+ //
+ // {{{label}}} is an optional string specifying the created noun's label.
+ //
+ // {{{nouns}}} is the array of nouns.
+
+ function mixNouns(label) {
+ var gotLabel = typeof label === "string";
+ var nouns = gotLabel ? arguments[1] : label;
+ if (!Utils.isArray(nouns))
+ nouns = Array.slice(arguments, gotLabel ? 1 : 0);
+ function mixer(key) function suggestMixed() {
+ var val, suggsList = [
+ typeof val === "function" ? val.apply(noun, arguments) : val
+ for each (noun in nouns) if ((val = noun[key])) ];
+ return suggsList.concat.apply([], suggsList); // flatten
+ };
+ return {
+ label: gotLabel ? label : [n.label || "?" for each (n in nouns)].join("|"),
+ rankLast: nouns.some(function (n) n.rankLast),
+ noExternalCalls: nouns.every(function (n) n.noExternalCalls),
+ suggest: mixer("suggest"),
+ default: nouns.some(function (n) "default" in n) && mixer("default"),
+ };
+ }
+ nu.mixNouns = mixNouns
+
+ return nu
+}())
+
+// === {{{ noun_arb_text }}} ===
+// Suggests the input as is.
+// * {{{text, html}}} : user input
+
+var noun_arb_text = {
+ id: "#arbtxt",
+ label: "?",
+ rankLast: true,
+ noExternalCalls: true,
+ cacheTime: -1,
+ suggest: function nat_suggest(text, html, callback, selectionIndices) {
+ return [NounUtils.makeSugg(text, html, null, 0.3, selectionIndices)];
+ },
+};
+
+// === {{{ noun_type_email_service }}} ===
+// **//FIXME//**
+// * {{{text}}} :
+// * {{{html}}} :
+// * {{{data}}} :
+
+var noun_type_email_service = NounUtils.NounType("email service",
+ "googleapps gmail",
+ "gmail");
+
+// === {{{ noun_type_email }}} ===
+// Suggests an email address (RFC2822 minus domain-lit).
+// The regex is taken from:
+// http://blog.livedoor.jp/dankogai/archives/51190099.html
+// * {{{text, html}}} : email address
+
+const EMAIL_ATOM = "[\\w!#$%&'*+/=?^`{}~|-]+";
+var noun_type_email = {
+ label: "email",
+ noExternalCalls: true,
+ cacheTime: -1,
+ _email: RegExp("^(?:" + EMAIL_ATOM + "(?:\\." + EMAIL_ATOM +
+ ')*|(?:\\"(?:\\\\[^\\r\\n]|[^\\\\\\"])*\\"))@(' +
+ EMAIL_ATOM + "(?:\\." + EMAIL_ATOM + ")*)$"),
+ _username: RegExp("^(?:" + EMAIL_ATOM + "(?:\\." + EMAIL_ATOM +
+ ')*|(?:\\"(?:\\\\[^\\r\\n]|[^\\\\\\"])*\\"))$'),
+ suggest: function nt_email_suggest(text, html, cb, selectionIndices) {
+ if (this._username.test(text))
+ return [NounUtils.makeSugg(text, html, null, 0.3, selectionIndices)];
+
+ var match = text.match(this._email);
+ if (!match) return [];
+
+ var domain = match[1];
+ // if the domain doesn't have a period or the TLD
+ // has less than two letters, penalize
+ var score = /\.(?:\d+|[a-z]{2,})$/i.test(domain) ? 1 : 0.8;
+
+ return [NounUtils.makeSugg(text, html, null, score, selectionIndices)];
+ }
+};
+
+// === {{{ noun_type_percentage }}} ===
+// Suggests a percentage value.
+// * {{{text, html}}} : "?%"
+// * {{{data}}} : a float number (1.0 for 100% etc.)
+
+var noun_type_percentage = {
+ label: "percentage",
+ noExternalCalls: true,
+ cacheTime: -1,
+ default: NounUtils.makeSugg("100%", null, 1, 0.3),
+ suggest: function nt_percentage_suggest(text, html) {
+ var number = parseFloat(text);
+ if (isNaN(number)) return [];
+
+ var score = text.replace(/[^-\d.Ee%]/g, "").length / text.length;
+ var nopercent = text.indexOf("%") < 0;
+ if (nopercent) score *= 0.9;
+
+ var suggs = [NounUtils.makeSugg(number + "%", null, number / 100, score)];
+ // if the number's 10 or less and there's no
+ // % sign, also try interpreting it as a proportion instead of a
+ // percent and offer it as a suggestion as well, but with a lower
+ // score.
+ if (nopercent && number <= 10)
+ suggs.push(NounUtils.makeSugg(
+ number * 100 + "%", null, number, score * 0.9));
+ return suggs;
+ },
+};
+
+// === {{{ noun_type_search_engine }}} ===
+// **//FIXME//**
+// * {{{text, html}}} : name of the engine
+// * {{{data}}} : engine object (see {{{nsIBrowserSearchService}}})
+
+var noun_type_search_engine = {
+ label: "search engine",
+ noExternalCalls: true,
+ // the default search engine should just get 0.3 or so...
+ // if it's actually entered, it can get a higher score.
+ default: function nt_sengine_default()
+ this._sugg(this._BSS.defaultEngine, 0.3),
+ suggest: function nt_sengine_suggest(text) {
+ var suggs = this._BSS.getVisibleEngines({}).map(this._sugg);
+ return NounUtils.grepSuggs(text, suggs);
+ },
+ /*
+ _BSS: (Cc["@mozilla.org/browser/search-service;1"]
+ .getService(Ci.nsIBrowserSearchService)),
+ */
+ _sugg: function nt_sengine__sugg(engine, score) (
+ NounUtils.makeSugg(engine.name, null, engine, score || 1)),
+};
+
+// === {{{ noun_type_tag }}} ===
+// Suggests the input as comma separated tags,
+// plus completions based on existing tags.
+// Defaults to all tags.
+// * {{{text, html}}} : comma separated tags
+// * {{{data}}} : an array of tags
+
+var noun_type_tag = {
+ label: "tag1[,tag2 ...]",
+ rankLast: true,
+ noExternalCalls: true,
+ default: function nt_tag_default()
+ [NounUtils.makeSugg(tag, null, [tag], .3)
+ for each (tag in PlacesUtils.tagging.allTags)],
+ suggest: function nt_tag_suggest(text) {
+ // can accept multiple tags, separated by commas
+ var tags = text.split(/\s*,\s*/).filter(RegExp.prototype.test.bind(/\S/));
+ if (!tags.length) return tags;
+
+ var score = .3, {pow} = Math;
+ var {allTags} = PlacesUtils.tagging;
+ var allTagsLC = [tag.toLowerCase() for each (tag in allTags)];
+ for each (let [i, tag] in new Iterator(tags)) {
+ let index = allTagsLC.indexOf(tag.toLowerCase());
+ if (~index) {
+ // if preexisting, boost score
+ score = pow(score, .5);
+ // replace with the one with proper case
+ tags[i] = allTags[index];
+ }
+ else
+ // if multi-word, unboost score
+ score /= pow(2, (tag.match(/\s+/g) || "").length);
+ }
+ var suggs = [NounUtils.makeSugg(null, null, tags, score)];
+
+ // assume last tag is still being typed - suggest completions for that
+ var lastTagLC = tags[tags.length - 1].toLowerCase();
+ for (let [i, atagLC] in new Iterator(allTagsLC))
+ // only match from the beginning of a tag name (not the middle)
+ if (lastTagLC.length < atagLC.length &&
+ atagLC.indexOf(lastTagLC) === 0)
+ suggs.push(NounUtils.makeSugg(null, null,
+ tags.slice(0, -1).concat(allTags[i]),
+ pow(score, .5)));
+
+ return suggs;
+ }
+};
+
+// === {{{ noun_type_awesomebar }}} ===
+// Suggests "Awesome Bar" query results.
+// * {{{text, html}}} : title or url
+// * {{{data}}} : a query result
+// (see {{{Utils}}}{{{.history.search}}})
+
+var noun_type_awesomebar = {
+ label: "query",
+ rankLast: true,
+ noExternalCalls: true,
+ cacheTime: 0,
+ suggest: function nt_awesome_suggest(text, html, callback) {
+ text = text.trim();
+ if (!text) return [];
+
+ var reqObj = {readyState: 2}, {_match} = this;
+ Utils.history.search(text, function nt_awesome_results(results) {
+ reqObj.readyState = 4;
+ if (/\s/.test(text)) { // multi-word query
+ //TODO: should we calculate scores for these as well? if so, how?
+ callback([NounUtils.makeSugg(r.title || r.url, null, r)
+ for each(r in results)]);
+ return;
+ }
+ var returnArr = [], lctxt = text.toLowerCase();
+ for each (let r in results) {
+ let u = _match(r.url, lctxt);
+ let t = _match(r.title, lctxt);
+ let m = u.score > t.score ? u : t;
+ returnArr.push(NounUtils.makeSugg(m.input, null, r, m.score));
+ }
+ callback(returnArr);
+ });
+ return [reqObj];
+ },
+ // creates a fake match object with an applicable score
+ _match: function nt_awesome_match(input, lctxt) {
+ var index = input.toLowerCase().indexOf(lctxt);
+ var match = {index: index, input: input, 0: lctxt};
+ match.score = ~index && NounUtils.matchScore(match);
+ return match;
+ },
+};
+
+// === {{{ noun_type_extension }}} ===
+// Suggests installed extensions.
+// * {{{text, html}}} : extension name
+// * {{{data}}} : contains extension {{{id}}}, {{{name}}} and {{{version}}}
+
+var noun_type_extension = {
+ label: "name",
+ noExternalCalls: true,
+ suggest: function nt_ext_suggest(text, html, cb) {
+ if (this._list.length) return NounUtils.grepSuggs(text, this._list);
+
+ var fakeReq = {readyState: 2};
+ ("AddonManager" in Utils
+ ? Utils.AddonManager.getAllAddons(setList)
+ : setList(Utils.ExtensionManager.getItemList(2, {})));
+ function setList(exts) {
+ var {escapeHtml} = Utils;
+ this._list = [
+ let (h = escapeHtml(ext.name)) {
+ text: ext.name, data: ext, html: h, summary: h}
+ for each(ext in exts)];
+ fakeReq.readyState = 4;
+ cb(NounUtils.grepSuggs(text, this._list));
+ }
+ return [fakeReq];
+ },
+ _list: [],
+};
+
+// === {{{ noun_type_common_URI_scheme }}} ===
+// Suggests common URI schemes, which are the IANA-registered ones
+// plus Unofficial ones and a few Mozilla specific ones.
+// See [[http://en.wikipedia.org/wiki/URI_scheme]].
+// * {{{text, html}}} : URI scheme
+
+var common_URI_schemes = [
+ 'aaa aaas acap cap cid crid data dav dict dns fax file ftp go gopher h323',
+ 'http https icap im imap info ipp iris iris.beep iris.xpc iris.xpcs iris.lws',
+ 'ldap mailto mid modem msrp msrps mtqp mupdate news nfs nntp opaquelocktoken',
+ 'pop pres prospero rtsp service shttp sip sips snmp soap.beep soap.beeps tag',
+ 'tel telnet tftp thismessage tip tv urn vemmi wais xmlrpc.beep xmpp',
+ 'z39.50r z39.50s',
+ 'about afp aim apt bolo bzr callto cel cvs daap ed2k feed fish gg git',
+ 'gizmoproject iax2 irc ircs itms lastfm ldaps magnet mms msnim psyc rsync',
+ 'secondlife skype ssh svn sftp smb sms soldat steam unreal ut2004 view-source',
+ 'vzochat webcal wyciwyg xfire ymsgr',
+ 'chrome resource'
+].join(' ').match(/\S+/g);
+
+var noun_type_common_URI_scheme = NounUtils.NounType(
+ "URI scheme",
+ [scheme + ":" for each (scheme in common_URI_schemes)]);
+
+// === {{{ noun_type_url }}} ===
+// Suggests URLs from the user's input and/or history.
+// Defaults to the current page's URL if no input is given.
+// * {{{text, html}}} : URL
+// * {{{data}}} : null or query result (same as {{{noun_type_awesomebar}}})
+
+var noun_type_url = {
+ label: "URL",
+ noExternalCalls: true,
+ cacheTime: 0,
+ default: function nt_url_default() {
+ var {window} = NounUtils;
+ var {location: {href}, document: {activeElement}} = window;
+ if (/^https:\/\/www\.google\.[a-z.]+\/reader\/view\b/.test(href))
+ try { href = window.wrappedJSObject.getPermalink().url } catch ([]) {}
+ var suggs = [NounUtils.makeSugg(href, null, null, .5)];
+ if (activeElement && activeElement.href)
+ suggs.unshift(NounUtils.makeSugg(activeElement.href, null, null, .7));
+ return suggs;
+ },
+ suggest: function nt_url_suggest(text, html, callback, selectionIndices) {
+ text = text.trim();
+ if (!text || /\s/.test(text)) return [];
+
+ var score = 1;
+ // has scheme?
+ if (/^[\w.-]+:\/{0,2}(?=.)/.test(text)) {
+ var {lastMatch: scheme, rightContext: postScheme} = RegExp;
+ if (postScheme.indexOf(".") < 0) score *= 0.9;
+ }
+ // has TLD?
+ else if (text.indexOf(".") > 0 && /\b[a-z]{2,}\b/i.test(text)) {
+ var scheme = "http://", postScheme = text;
+ if (selectionIndices)
+ selectionIndices =
+ [i + scheme.length for each (i in selectionIndices)];
+ score *= 0.9;
+ }
+ else return [];
+
+ var [domain, path] = postScheme.split(/[/?#]/, 2);
+ // if it's just a domain name-looking thing, lower confidence
+ if (path == null) score *= 0.9;
+ // LDH charcodes include "Letters, Digits, and Hyphen".
+ // We'll throw in . @ : too.
+ if (/^(?![A-Za-z\d-.@:]+$)/.test(domain)) score *= 0.9;
+
+ var fakeRequest = {readyState: 2};
+ Utils.history.search(text, function nt_url_search(results) {
+ fakeRequest.readyState = 4;
+ var suggs = [], tlc = text.toLowerCase();
+ for each (let r in results) {
+ let urlIndex = r.url.toLowerCase().indexOf(tlc);
+ if (urlIndex < 0) continue;
+ let urlScore =
+ NounUtils.matchScore({index: urlIndex, 0: text, input: r.url});
+ suggs.push(NounUtils.makeSugg(
+ r.url, null, r, urlScore,
+ selectionIndices && [urlIndex, urlIndex + text.length]));
+ }
+ callback(suggs);
+ });
+
+ return [NounUtils.makeSugg(scheme + postScheme, null, null,
+ score, selectionIndices),
+ fakeRequest];
+ },
+};
+
+
+// === {{{ noun_type_command }}} ===
+// Suggests each installed command whose name matches the input.
+// * {{{text, html}}} : command name
+// * {{{data}}} : command object
+
+var noun_type_command = {
+ label: "name",
+ noExternalCalls: true,
+ cacheTime: 0,
+ suggest: function nt_command_suggest(text) {
+ if (!text) return [];
+ var grepee = this._get();
+ if (!grepee.length) return grepee;
+ var suggs = NounUtils.grepSuggs(text, grepee);
+ if (!suggs.length) return suggs;
+ Utils.uniq(suggs, function nt_command_id(s) s.data.id);
+ for each (let s in suggs) s.html = s.summary = Utils.escapeHtml(s.text);
+ return suggs;
+ },
+ _get: function nt_command__get() {
+ var cmds = commandSource.getAllCommands();
+ if ("disabled" in this) {
+ let {disabled} = this;
+ cmds = [cmd for each (cmd in cmds) if (cmd.disabled === disabled)];
+ }
+ return [{text: name, data: cmd}
+ for each (cmd in cmds) for each (name in cmd.names)];
+ },
+};
+
+// === {{{ noun_type_enabled_command }}} ===
+// === {{{ noun_type_disabled_command }}} ===
+// Same as {{{noun_type_command}}}, but with only enabled/disabled commands.
+
+var noun_type_enabled_command = {
+ __proto__: noun_type_command,
+ get disabled() false,
+};
+
+var noun_type_disabled_command = {
+ __proto__: noun_type_command,
+ get disabled() true,
+};
+
+// === {{{ noun_type_skin }}} ===
+// Suggests each installed skin whose name matches the input.
+// * {{{text, html}}} : skin name
+// * {{{data}}} : skin feed object (see {{{SkinFeedPlugin}}})
+
+var noun_type_skin = {
+ label: "name",
+ noExternalCalls: true,
+ cacheTime: 0,
+ suggest: function nt_skin_suggest(text, html, cb, selected) {
+ var suggs = [NounUtils.makeSugg(skin.metaData.name, null, skin)
+ for each (skin in skinService.skins)];
+ return NounUtils.grepSuggs(text, suggs);
+ },
+};
+
+// === {{{ noun_type_twitter_user }}} ===
+// Suggests Twitter IDs from the user's login info.
+// * {{{text, html}}} : Twitter ID
+// * {{{data}}} : login data (see {{{nsILoginManager}}})
+
+var noun_type_twitter_user = {
+ label: "user",
+ rankLast: true,
+ noExternalCalls: true,
+ cacheTime: 0,
+ suggest: function nt_twuser_suggest(text, html, cb, selected) {
+ // reject text from selection.
+ if (!text || selected) return [];
+
+ var foundAt = text[0] === "@";
+ if (foundAt) text = text.slice(1); // strip off the @
+
+ var suggs = NounUtils.grepSuggs(text, this.logins());
+
+ if (/^\w+$/.test(text) && suggs.every(function(s) s.text !== text))
+ suggs.push(NounUtils.makeSugg(text, null, {username: text}, .4));
+
+ if (foundAt) suggs = [
+ { __proto__: s,
+ summary: "@" + s.summary,
+ score: Math.pow(s.score, 0.8) }
+ for each (s in suggs)];
+
+ return suggs;
+ },
+ logins: function nt_twuser_logins(reload) {
+ // TODO: figure out how often to clear this list cache.
+ if (this._list && !reload) return this._list;
+ var list = [];
+ if (Utils.loggedIn) {
+ // Look for twitter usernames stored in password manager
+ const {LoginManager} = Utils, usersFound = {__proto__: null};
+ for each (let url in ["https://twitter.com", "http://twitter.com"]) {
+ for each (let login in LoginManager.findLogins({}, url, "", "")) {
+ let {username} = login;
+ if (username in usersFound) continue;
+ usersFound[username] = true;
+ list.push(NounUtils.makeSugg(username, null, login));
+ }
+ }
+ }
+ return this._list = list;
+ },
+ _list: null,
+};
+
+// === {{{ noun_type_number }}} ===
+// Suggests a number value. Defaults to 1.
+// * {{{text, html}}} : number text
+// * {{{data}}} : number
+
+var noun_type_number = {
+ label: "number",
+ noExternalCalls: true,
+ cacheTime: -1,
+ suggest: function nt_number_suggest(text) {
+ var num = +text;
+ return isNaN(num) ? [] : [NounUtils.makeSugg(text, null, num)];
+ },
+ default: NounUtils.makeSugg("1", null, 1, 0.5),
+};
+
+// === {{{ noun_type_date }}} ===
+// === {{{ noun_type_time }}} ===
+// === {{{ noun_type_date_time }}} ===
+// Suggests a date/time for input, using the mighty {{{Date.parse()}}}.
+// Defaults to today/now.
+// * {{{text, html}}} : date/time text
+// * {{{data}}} : {{{Date}}} instance
+
+function scoreDateTime(text) {
+ // Give penalty for short input only slightly,
+ // as Date.parse() can handle variety of lengths like:
+ // "t" or "Wednesday September 18th 2009 13:29:54 GMT+0900",
+ var score = Math.pow(text.length / 42, 1 / 17); // .8 ~
+ return score > 1 ? 1 : score;
+}
+
+var noun_type_date = {
+ label: "date",
+ noExternalCalls: true,
+ cacheTime: 0,
+ "default": function nt_date_default() this._sugg(Date.today()),
+ suggest: function nt_date_suggest(text) {
+ var date = Date.parse(text);
+ if (!date) return [];
+
+ var score = scoreDateTime(text);
+ if (date.isToday())
+ score *= .5;
+ if (date.getHours() || date.getMinutes() || date.getSeconds())
+ score *= .7;
+
+ return [this._sugg(date, score)];
+ },
+ _sugg: function nt_date__sugg(date, score)
+ NounUtils.makeSugg(date.toString("yyyy-MM-dd"), null, date, score),
+};
+
+var noun_type_time = {
+ label: "time",
+ noExternalCalls: true,
+ cacheTime: 0,
+ "default": function nt_time_default() this._sugg(Date.parse("now")),
+ suggest: function nt_time_suggest(text, html) {
+ var date = Date.parse(text);
+ if (!date) return [];
+
+ var score = scoreDateTime(text), now = Date.parse("now");
+ if (Math.abs(now - date) > 9) { // not "now"
+ if (!now.isSameDay(date))
+ score *= .7; // not "today"
+ if (!date.getHours() && !date.getMinutes() && !date.getSeconds())
+ score *= .5; // "00:00:00"
+ }
+ return [this._sugg(date, score)];
+ },
+ _sugg: function nt_time__sugg(date, score)
+ NounUtils.makeSugg(date.toString("hh:mm:ss tt"), null, date, score),
+};
+
+var noun_type_date_time = {
+ label: "date and time",
+ noExternalCalls: true,
+ cacheTime: 0,
+ "default": function nt_date_time_default() this._sugg(Date.parse("now")),
+ suggest: function nt_time_suggest(text) {
+ var date = Date.parse(text);
+ if (!date) return [];
+
+ var score = scoreDateTime(text), now = Date.parse("now");
+ if (Math.abs(now - date) > 9) { // not "now"
+ if (now.isSameDay(date))
+ score *= .7; // "today"
+ if (!date.getHours() && !date.getMinutes() && !date.getSeconds())
+ score *= .7; // "00:00:00"
+ }
+ return [this._sugg(date, score)];
+ },
+ _sugg: function nt_date_time__sugg(date, score)
+ NounUtils.makeSugg(date.toString("yyyy-MM-dd hh:mm tt"), null, date,
+ score),
+};
+
+// === {{{ noun_type_contact }}} ===
+// Same as {{{noun_type_email}}}, but also suggests
+// the user's contact informations that are fetched from Gmail (for now).
+// * {{{text}}} : email address
+// * {{{html}}} : same as {{{summary}}}
+// * {{{data}}} : name of contactee
+
+var noun_type_contact = {
+ label: "name or email",
+ suggest: function nt_contact_suggest(text, html, callback) {
+ var suggs = noun_type_email.suggest.apply(noun_type_email, arguments);
+ if (this._list) return this._grep(text).concat(suggs);
+ var self = this;
+ this._list = [];
+ getGmailContacts(
+ function nt_contact_ok(contacts) {
+ var list = self._list;
+ for each (var {name, email} in contacts) {
+ let htm = name + '&lt;' + email + '&gt;'
+ list.push({
+ text: email, html: htm, data: name, summary: htm, score: 1});
+ }
+ callback(self._grep(text));
+ },
+ function nt_contact_ng(info) {
+ setTimeout(function nt_contact_reset() { self._list = null },
+ self._retryInterval *= 2);
+ Utils.reportInfo(
+ info + " (retrying in " + self._retryInterval / 1e3 + " sec.)");
+ });
+ return suggs;
+ },
+ _list: null,
+ _retryInterval: 5e3,
+ _grep: function nt_contact__grep(text)
+ Utils.uniq([].concat(NounUtils.grepSuggs(text, this._list, "data"),
+ NounUtils.grepSuggs(text, this._list)),
+ "text"),
+};
+
+function getGmailContacts(ok, ng) {
+ if (!Utils.loggedIn)
+ return ng("Gmail: Not logged in.");
+ var logins = Utils.LoginManager
+ .findLogins({}, "https://www.google.com", "", "");
+ if (!logins.length)
+ return ng("No Google logins.");
+ var errors = 0;
+ for each (let login in logins) jQuery.ajax({
+ type: "POST", url: "https://www.google.com/accounts/ClientLogin",
+ data: {
+ Email : login.username,
+ Passwd : login.password,
+ accountType: "GOOGLE", service: "cp", source: "Mozilla-Ubiquity-0.6",
+ },
+ error: googleContactsError,
+ success: function googleClientLoggedIn(data, status, xhr) {
+ var [, auth] = /^Auth=(.+)/m.exec(data) || 0;
+ if (!auth) return this.error(xhr);
+ jQuery.ajax({
+ url: "https://www.google.com/m8/feeds/contacts/default/full",
+ dataType: "xml",
+ beforeSend: function setGoogleLoginAuth(xhr) {
+ xhr.setRequestHeader("Authorization", "GoogleLogin auth=" + auth);
+ },
+ error: googleContactsError,
+ success: function onContacts(atom) let (email) ok(
+ [{name : entry.querySelector("title").textContent,
+ email : email.getAttribute("address")}
+ for each (entry in Array.slice(atom.getElementsByTagName("entry")))
+ if (email = entry.querySelector("email"))]),
+ });
+ },
+ });
+ function googleContactsError(xhr) {
+ Utils.reportInfo(this.url + ": " + xhr.status + " " + xhr.statusText);
+ ++errors == logins.length && ng("Failed retrieving Google Contacts");
+ }
+}
+
+// === {{{ noun_type_geolocation }}} ===
+// * {{{text, html}}} : user input / "city,( state,) country"
+// * {{{data}}} : {{{null}}} or geoLocation object
+// (as returned by {{{NounUtils}}}{{{.getGeoLocation()}}})
+
+var noun_type_geolocation = {
+ label: "geolocation",
+ rankLast: true,
+ default: function nt_geoloc_default(loc) {
+ if (!(loc = loc || NounUtils.geoLocation)) return null;
+ var {city, state, country: text} = loc;
+ if (state && state !== city) text = state + ", " + text;
+ if (city) text = city + ", " + text;
+ return NounUtils.makeSugg(text, null, loc);
+ },
+ suggest: function nt_geoloc_suggest(text, html, callback, selectionIndices) {
+ // LONGTERM TODO: try to detect whether fragment is anything like
+ // a valid location or not, and don't suggest anything
+ // for input that's not a location.
+ var suggs = [NounUtils.makeSugg(text, null, null, 0.3, selectionIndices)];
+ // TODO: we should try to build this "here" handling into something like
+ // magic words (anaphora) handling in Parser 2: make it localizable.
+ if (/^\s*here\s*$/i.test(text)) {
+ let me = this;
+ suggs.push(NounUtils.getGeoLocation(function nt_geoloc_async(loc) {
+ callback(me.default(loc));
+ }));
+ }
+ return suggs;
+ },
+};
+
+// === {{{ noun_type_lang_google }}} ===
+// Suggests languages used in various Google services.
+// * {{{text, html}}} : language name
+// * {{{data}}} : language code
+//
+// {{{getLangName(code)}}} returns the corresponding language name
+// for {{{code}}}.
+
+var noun_type_lang_google = NounUtils.NounType("language", {
+ Afrikaans: "af",
+ Albanian: "sq",
+ Arabic: "ar",
+ Armenian: "hy",
+ Azerbaijani: "az",
+ Basque: "eu",
+ Belarusian: "be",
+ Bulgarian: "bg",
+ Catalan: "ca",
+ "Chinese Simplified": "zh-CN",
+ "Chinese Traditional": "zh-TW",
+ Croatian: "hr",
+ Czech: "cs",
+ Danish: "da",
+ Dutch: "nl",
+ English: "en",
+ Estonian: "et",
+ Filipino: "tl",
+ Finnish: "fi",
+ French: "fr",