diff --git a/phantom/static/phantom/css/admin.css b/phantom/static/phantom/css/admin.css
index 2da1c03..01008f1 100644
--- a/phantom/static/phantom/css/admin.css
+++ b/phantom/static/phantom/css/admin.css
@@ -1927,4 +1927,47 @@ a .icon-rotate-90:before,a .icon-rotate-180:before,a .icon-rotate-270:before,a .
display: none !important;
}
}
-/* End print directives */
\ No newline at end of file
+/* End print directives */
+
+.tt-hint {
+ color: #999
+}
+
+.tt-dropdown-menu {
+ width: 600px;
+ margin-top: 12px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+ border-radius: 8px;
+ -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ -moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
+ box-shadow: 0 5px 10px rgba(0,0,0,.2);
+}
+
+.tt-suggestion {
+ padding: 8px 20px;
+ font-size: 14px;
+ line-height: 18px;
+}
+
+.tt-suggestion + .tt-suggestion {
+ font-size: 14px;
+ border-top: 1px solid #ccc;
+}
+
+.tt-suggestions .repo-language {
+ float: right;
+ font-style: italic;
+}
+
+.tt-suggestions .repo-name {
+ font-size: 20px;
+ font-weight: bold;
+}
+
+.tt-suggestions .repo-description {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/phantom/static/phantom/js/typeahead.js b/phantom/static/phantom/js/typeahead.js
new file mode 100644
index 0000000..af32109
--- /dev/null
+++ b/phantom/static/phantom/js/typeahead.js
@@ -0,0 +1,1139 @@
+/*!
+ * typeahead.js 0.9.3
+ * https://github.com/twitter/typeahead
+ * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
+ */
+
+(function($) {
+ var VERSION = "0.9.3";
+ var utils = {
+ isMsie: function() {
+ var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent);
+ return match ? parseInt(match[2], 10) : false;
+ },
+ isBlankString: function(str) {
+ return !str || /^\s*$/.test(str);
+ },
+ escapeRegExChars: function(str) {
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
+ },
+ isString: function(obj) {
+ return typeof obj === "string";
+ },
+ isNumber: function(obj) {
+ return typeof obj === "number";
+ },
+ isArray: $.isArray,
+ isFunction: $.isFunction,
+ isObject: $.isPlainObject,
+ isUndefined: function(obj) {
+ return typeof obj === "undefined";
+ },
+ bind: $.proxy,
+ bindAll: function(obj) {
+ var val;
+ for (var key in obj) {
+ $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj));
+ }
+ },
+ indexOf: function(haystack, needle) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (haystack[i] === needle) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ each: $.each,
+ map: $.map,
+ filter: $.grep,
+ every: function(obj, test) {
+ var result = true;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (!(result = test.call(null, val, key, obj))) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ some: function(obj, test) {
+ var result = false;
+ if (!obj) {
+ return result;
+ }
+ $.each(obj, function(key, val) {
+ if (result = test.call(null, val, key, obj)) {
+ return false;
+ }
+ });
+ return !!result;
+ },
+ mixin: $.extend,
+ getUniqueId: function() {
+ var counter = 0;
+ return function() {
+ return counter++;
+ };
+ }(),
+ defer: function(fn) {
+ setTimeout(fn, 0);
+ },
+ debounce: function(func, wait, immediate) {
+ var timeout, result;
+ return function() {
+ var context = this, args = arguments, later, callNow;
+ later = function() {
+ timeout = null;
+ if (!immediate) {
+ result = func.apply(context, args);
+ }
+ };
+ callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if (callNow) {
+ result = func.apply(context, args);
+ }
+ return result;
+ };
+ },
+ throttle: function(func, wait) {
+ var context, args, timeout, result, previous, later;
+ previous = 0;
+ later = function() {
+ previous = new Date();
+ timeout = null;
+ result = func.apply(context, args);
+ };
+ return function() {
+ var now = new Date(), remaining = wait - (now - previous);
+ context = this;
+ args = arguments;
+ if (remaining <= 0) {
+ clearTimeout(timeout);
+ timeout = null;
+ previous = now;
+ result = func.apply(context, args);
+ } else if (!timeout) {
+ timeout = setTimeout(later, remaining);
+ }
+ return result;
+ };
+ },
+ tokenizeQuery: function(str) {
+ return $.trim(str).toLowerCase().split(/[\s]+/);
+ },
+ tokenizeText: function(str) {
+ return $.trim(str).toLowerCase().split(/[\s\-_]+/);
+ },
+ getProtocol: function() {
+ return location.protocol;
+ },
+ noop: function() {}
+ };
+ var EventTarget = function() {
+ var eventSplitter = /\s+/;
+ return {
+ on: function(events, callback) {
+ var event;
+ if (!callback) {
+ return this;
+ }
+ this._callbacks = this._callbacks || {};
+ events = events.split(eventSplitter);
+ while (event = events.shift()) {
+ this._callbacks[event] = this._callbacks[event] || [];
+ this._callbacks[event].push(callback);
+ }
+ return this;
+ },
+ trigger: function(events, data) {
+ var event, callbacks;
+ if (!this._callbacks) {
+ return this;
+ }
+ events = events.split(eventSplitter);
+ while (event = events.shift()) {
+ if (callbacks = this._callbacks[event]) {
+ for (var i = 0; i < callbacks.length; i += 1) {
+ callbacks[i].call(this, {
+ type: event,
+ data: data
+ });
+ }
+ }
+ }
+ return this;
+ }
+ };
+ }();
+ var EventBus = function() {
+ var namespace = "typeahead:";
+ function EventBus(o) {
+ if (!o || !o.el) {
+ $.error("EventBus initialized without el");
+ }
+ this.$el = $(o.el);
+ }
+ utils.mixin(EventBus.prototype, {
+ trigger: function(type) {
+ var args = [].slice.call(arguments, 1);
+ this.$el.trigger(namespace + type, args);
+ }
+ });
+ return EventBus;
+ }();
+ var PersistentStorage = function() {
+ var ls, methods;
+ try {
+ ls = window.localStorage;
+ ls.setItem("~~~", "!");
+ ls.removeItem("~~~");
+ } catch (err) {
+ ls = null;
+ }
+ function PersistentStorage(namespace) {
+ this.prefix = [ "__", namespace, "__" ].join("");
+ this.ttlKey = "__ttl__";
+ this.keyMatcher = new RegExp("^" + this.prefix);
+ }
+ if (ls && window.JSON) {
+ methods = {
+ _prefix: function(key) {
+ return this.prefix + key;
+ },
+ _ttlKey: function(key) {
+ return this._prefix(key) + this.ttlKey;
+ },
+ get: function(key) {
+ if (this.isExpired(key)) {
+ this.remove(key);
+ }
+ return decode(ls.getItem(this._prefix(key)));
+ },
+ set: function(key, val, ttl) {
+ if (utils.isNumber(ttl)) {
+ ls.setItem(this._ttlKey(key), encode(now() + ttl));
+ } else {
+ ls.removeItem(this._ttlKey(key));
+ }
+ return ls.setItem(this._prefix(key), encode(val));
+ },
+ remove: function(key) {
+ ls.removeItem(this._ttlKey(key));
+ ls.removeItem(this._prefix(key));
+ return this;
+ },
+ clear: function() {
+ var i, key, keys = [], len = ls.length;
+ for (i = 0; i < len; i++) {
+ if ((key = ls.key(i)).match(this.keyMatcher)) {
+ keys.push(key.replace(this.keyMatcher, ""));
+ }
+ }
+ for (i = keys.length; i--; ) {
+ this.remove(keys[i]);
+ }
+ return this;
+ },
+ isExpired: function(key) {
+ var ttl = decode(ls.getItem(this._ttlKey(key)));
+ return utils.isNumber(ttl) && now() > ttl ? true : false;
+ }
+ };
+ } else {
+ methods = {
+ get: utils.noop,
+ set: utils.noop,
+ remove: utils.noop,
+ clear: utils.noop,
+ isExpired: utils.noop
+ };
+ }
+ utils.mixin(PersistentStorage.prototype, methods);
+ return PersistentStorage;
+ function now() {
+ return new Date().getTime();
+ }
+ function encode(val) {
+ return JSON.stringify(utils.isUndefined(val) ? null : val);
+ }
+ function decode(val) {
+ return JSON.parse(val);
+ }
+ }();
+ var RequestCache = function() {
+ function RequestCache(o) {
+ utils.bindAll(this);
+ o = o || {};
+ this.sizeLimit = o.sizeLimit || 10;
+ this.cache = {};
+ this.cachedKeysByAge = [];
+ }
+ utils.mixin(RequestCache.prototype, {
+ get: function(url) {
+ return this.cache[url];
+ },
+ set: function(url, resp) {
+ var requestToEvict;
+ if (this.cachedKeysByAge.length === this.sizeLimit) {
+ requestToEvict = this.cachedKeysByAge.shift();
+ delete this.cache[requestToEvict];
+ }
+ this.cache[url] = resp;
+ this.cachedKeysByAge.push(url);
+ }
+ });
+ return RequestCache;
+ }();
+ var Transport = function() {
+ var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache;
+ function Transport(o) {
+ utils.bindAll(this);
+ o = utils.isString(o) ? {
+ url: o
+ } : o;
+ requestCache = requestCache || new RequestCache();
+ maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6;
+ this.url = o.url;
+ this.wildcard = o.wildcard || "%QUERY";
+ this.filter = o.filter;
+ this.replace = o.replace;
+ this.ajaxSettings = {
+ type: "get",
+ cache: o.cache,
+ timeout: o.timeout,
+ dataType: o.dataType || "json",
+ beforeSend: o.beforeSend
+ };
+ this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300);
+ }
+ utils.mixin(Transport.prototype, {
+ _get: function(url, cb) {
+ var that = this;
+ if (belowPendingRequestsThreshold()) {
+ this._sendRequest(url).done(done);
+ } else {
+ this.onDeckRequestArgs = [].slice.call(arguments, 0);
+ }
+ function done(resp) {
+ var data = that.filter ? that.filter(resp) : resp;
+ cb && cb(data);
+ requestCache.set(url, resp);
+ }
+ },
+ _sendRequest: function(url) {
+ var that = this, jqXhr = pendingRequests[url];
+ if (!jqXhr) {
+ incrementPendingRequests();
+ jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always);
+ }
+ return jqXhr;
+ function always() {
+ decrementPendingRequests();
+ pendingRequests[url] = null;
+ if (that.onDeckRequestArgs) {
+ that._get.apply(that, that.onDeckRequestArgs);
+ that.onDeckRequestArgs = null;
+ }
+ }
+ },
+ get: function(query, cb) {
+ var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp;
+ cb = cb || utils.noop;
+ url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery);
+ if (resp = requestCache.get(url)) {
+ utils.defer(function() {
+ cb(that.filter ? that.filter(resp) : resp);
+ });
+ } else {
+ this._get(url, cb);
+ }
+ return !!resp;
+ }
+ });
+ return Transport;
+ function incrementPendingRequests() {
+ pendingRequestsCount++;
+ }
+ function decrementPendingRequests() {
+ pendingRequestsCount--;
+ }
+ function belowPendingRequestsThreshold() {
+ return pendingRequestsCount < maxPendingRequests;
+ }
+ }();
+ var Dataset = function() {
+ var keys = {
+ thumbprint: "thumbprint",
+ protocol: "protocol",
+ itemHash: "itemHash",
+ adjacencyList: "adjacencyList"
+ };
+ function Dataset(o) {
+ utils.bindAll(this);
+ if (utils.isString(o.template) && !o.engine) {
+ $.error("no template engine specified");
+ }
+ if (!o.local && !o.prefetch && !o.remote) {
+ $.error("one of local, prefetch, or remote is required");
+ }
+ this.name = o.name || utils.getUniqueId();
+ this.limit = o.limit || 5;
+ this.minLength = o.minLength || 1;
+ this.header = o.header;
+ this.footer = o.footer;
+ this.valueKey = o.valueKey || "value";
+ this.template = compileTemplate(o.template, o.engine, this.valueKey);
+ this.local = o.local;
+ this.prefetch = o.prefetch;
+ this.remote = o.remote;
+ this.itemHash = {};
+ this.adjacencyList = {};
+ this.storage = o.name ? new PersistentStorage(o.name) : null;
+ }
+ utils.mixin(Dataset.prototype, {
+ _processLocalData: function(data) {
+ this._mergeProcessedData(this._processData(data));
+ },
+ _loadPrefetchData: function(o) {
+ var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred;
+ if (this.storage) {
+ storedThumbprint = this.storage.get(keys.thumbprint);
+ storedProtocol = this.storage.get(keys.protocol);
+ storedItemHash = this.storage.get(keys.itemHash);
+ storedAdjacencyList = this.storage.get(keys.adjacencyList);
+ }
+ isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol();
+ o = utils.isString(o) ? {
+ url: o
+ } : o;
+ o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3;
+ if (storedItemHash && storedAdjacencyList && !isExpired) {
+ this._mergeProcessedData({
+ itemHash: storedItemHash,
+ adjacencyList: storedAdjacencyList
+ });
+ deferred = $.Deferred().resolve();
+ } else {
+ deferred = $.getJSON(o.url).done(processPrefetchData);
+ }
+ return deferred;
+ function processPrefetchData(data) {
+ var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList;
+ if (that.storage) {
+ that.storage.set(keys.itemHash, itemHash, o.ttl);
+ that.storage.set(keys.adjacencyList, adjacencyList, o.ttl);
+ that.storage.set(keys.thumbprint, thumbprint, o.ttl);
+ that.storage.set(keys.protocol, utils.getProtocol(), o.ttl);
+ }
+ that._mergeProcessedData(processedData);
+ }
+ },
+ _transformDatum: function(datum) {
+ var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = {
+ value: value,
+ tokens: tokens
+ };
+ if (utils.isString(datum)) {
+ item.datum = {};
+ item.datum[this.valueKey] = datum;
+ } else {
+ item.datum = datum;
+ }
+ item.tokens = utils.filter(item.tokens, function(token) {
+ return !utils.isBlankString(token);
+ });
+ item.tokens = utils.map(item.tokens, function(token) {
+ return token.toLowerCase();
+ });
+ return item;
+ },
+ _processData: function(data) {
+ var that = this, itemHash = {}, adjacencyList = {};
+ utils.each(data, function(i, datum) {
+ var item = that._transformDatum(datum), id = utils.getUniqueId(item.value);
+ itemHash[id] = item;
+ utils.each(item.tokens, function(i, token) {
+ var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]);
+ !~utils.indexOf(adjacency, id) && adjacency.push(id);
+ });
+ });
+ return {
+ itemHash: itemHash,
+ adjacencyList: adjacencyList
+ };
+ },
+ _mergeProcessedData: function(processedData) {
+ var that = this;
+ utils.mixin(this.itemHash, processedData.itemHash);
+ utils.each(processedData.adjacencyList, function(character, adjacency) {
+ var masterAdjacency = that.adjacencyList[character];
+ that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency;
+ });
+ },
+ _getLocalSuggestions: function(terms) {
+ var that = this, firstChars = [], lists = [], shortestList, suggestions = [];
+ utils.each(terms, function(i, term) {
+ var firstChar = term.charAt(0);
+ !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar);
+ });
+ utils.each(firstChars, function(i, firstChar) {
+ var list = that.adjacencyList[firstChar];
+ if (!list) {
+ return false;
+ }
+ lists.push(list);
+ if (!shortestList || list.length < shortestList.length) {
+ shortestList = list;
+ }
+ });
+ if (lists.length < firstChars.length) {
+ return [];
+ }
+ utils.each(shortestList, function(i, id) {
+ var item = that.itemHash[id], isCandidate, isMatch;
+ isCandidate = utils.every(lists, function(list) {
+ return ~utils.indexOf(list, id);
+ });
+ isMatch = isCandidate && utils.every(terms, function(term) {
+ return utils.some(item.tokens, function(token) {
+ return token.indexOf(term) === 0;
+ });
+ });
+ isMatch && suggestions.push(item);
+ });
+ return suggestions;
+ },
+ initialize: function() {
+ var deferred;
+ this.local && this._processLocalData(this.local);
+ this.transport = this.remote ? new Transport(this.remote) : null;
+ deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve();
+ this.local = this.prefetch = this.remote = null;
+ this.initialize = function() {
+ return deferred;
+ };
+ return deferred;
+ },
+ getSuggestions: function(query, cb) {
+ var that = this, terms, suggestions, cacheHit = false;
+ if (query.length < this.minLength) {
+ return;
+ }
+ terms = utils.tokenizeQuery(query);
+ suggestions = this._getLocalSuggestions(terms).slice(0, this.limit);
+ if (suggestions.length < this.limit && this.transport) {
+ cacheHit = this.transport.get(query, processRemoteData);
+ }
+ !cacheHit && cb && cb(suggestions);
+ function processRemoteData(data) {
+ suggestions = suggestions.slice(0);
+ utils.each(data, function(i, datum) {
+ var item = that._transformDatum(datum), isDuplicate;
+ isDuplicate = utils.some(suggestions, function(suggestion) {
+ return item.value === suggestion.value;
+ });
+ !isDuplicate && suggestions.push(item);
+ return suggestions.length < that.limit;
+ });
+ cb && cb(suggestions);
+ }
+ }
+ });
+ return Dataset;
+ function compileTemplate(template, engine, valueKey) {
+ var renderFn, compiledTemplate;
+ if (utils.isFunction(template)) {
+ renderFn = template;
+ } else if (utils.isString(template)) {
+ compiledTemplate = engine.compile(template);
+ renderFn = utils.bind(compiledTemplate.render, compiledTemplate);
+ } else {
+ renderFn = function(context) {
+ return "
" + context[valueKey] + "
";
+ };
+ }
+ return renderFn;
+ }
+ }();
+ var InputView = function() {
+ function InputView(o) {
+ var that = this;
+ utils.bindAll(this);
+ this.specialKeyCodeMap = {
+ 9: "tab",
+ 27: "esc",
+ 37: "left",
+ 39: "right",
+ 13: "enter",
+ 38: "up",
+ 40: "down"
+ };
+ this.$hint = $(o.hint);
+ this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent);
+ if (!utils.isMsie()) {
+ this.$input.on("input.tt", this._compareQueryToInputValue);
+ } else {
+ this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) {
+ if (that.specialKeyCodeMap[$e.which || $e.keyCode]) {
+ return;
+ }
+ utils.defer(that._compareQueryToInputValue);
+ });
+ }
+ this.query = this.$input.val();
+ this.$overflowHelper = buildOverflowHelper(this.$input);
+ }
+ utils.mixin(InputView.prototype, EventTarget, {
+ _handleFocus: function() {
+ this.trigger("focused");
+ },
+ _handleBlur: function() {
+ this.trigger("blured");
+ },
+ _handleSpecialKeyEvent: function($e) {
+ var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode];
+ keyName && this.trigger(keyName + "Keyed", $e);
+ },
+ _compareQueryToInputValue: function() {
+ var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false;
+ if (isSameQueryExceptWhitespace) {
+ this.trigger("whitespaceChanged", {
+ value: this.query
+ });
+ } else if (!isSameQuery) {
+ this.trigger("queryChanged", {
+ value: this.query = inputValue
+ });
+ }
+ },
+ destroy: function() {
+ this.$hint.off(".tt");
+ this.$input.off(".tt");
+ this.$hint = this.$input = this.$overflowHelper = null;
+ },
+ focus: function() {
+ this.$input.focus();
+ },
+ blur: function() {
+ this.$input.blur();
+ },
+ getQuery: function() {
+ return this.query;
+ },
+ setQuery: function(query) {
+ this.query = query;
+ },
+ getInputValue: function() {
+ return this.$input.val();
+ },
+ setInputValue: function(value, silent) {
+ this.$input.val(value);
+ !silent && this._compareQueryToInputValue();
+ },
+ getHintValue: function() {
+ return this.$hint.val();
+ },
+ setHintValue: function(value) {
+ this.$hint.val(value);
+ },
+ getLanguageDirection: function() {
+ return (this.$input.css("direction") || "ltr").toLowerCase();
+ },
+ isOverflow: function() {
+ this.$overflowHelper.text(this.getInputValue());
+ return this.$overflowHelper.width() > this.$input.width();
+ },
+ isCursorAtEnd: function() {
+ var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range;
+ if (utils.isNumber(selectionStart)) {
+ return selectionStart === valueLength;
+ } else if (document.selection) {
+ range = document.selection.createRange();
+ range.moveStart("character", -valueLength);
+ return valueLength === range.text.length;
+ }
+ return true;
+ }
+ });
+ return InputView;
+ function buildOverflowHelper($input) {
+ return $("").css({
+ position: "absolute",
+ left: "-9999px",
+ visibility: "hidden",
+ whiteSpace: "nowrap",
+ fontFamily: $input.css("font-family"),
+ fontSize: $input.css("font-size"),
+ fontStyle: $input.css("font-style"),
+ fontVariant: $input.css("font-variant"),
+ fontWeight: $input.css("font-weight"),
+ wordSpacing: $input.css("word-spacing"),
+ letterSpacing: $input.css("letter-spacing"),
+ textIndent: $input.css("text-indent"),
+ textRendering: $input.css("text-rendering"),
+ textTransform: $input.css("text-transform")
+ }).insertAfter($input);
+ }
+ function compareQueries(a, b) {
+ a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
+ b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " ");
+ return a === b;
+ }
+ }();
+ var DropdownView = function() {
+ var html = {
+ suggestionsList: ''
+ }, css = {
+ suggestionsList: {
+ display: "block"
+ },
+ suggestion: {
+ whiteSpace: "nowrap",
+ cursor: "pointer"
+ },
+ suggestionChild: {
+ whiteSpace: "normal"
+ }
+ };
+ function DropdownView(o) {
+ utils.bindAll(this);
+ this.isOpen = false;
+ this.isEmpty = true;
+ this.isMouseOverDropdown = false;
+ this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover);
+ }
+ utils.mixin(DropdownView.prototype, EventTarget, {
+ _handleMouseenter: function() {
+ this.isMouseOverDropdown = true;
+ },
+ _handleMouseleave: function() {
+ this.isMouseOverDropdown = false;
+ },
+ _handleMouseover: function($e) {
+ var $suggestion = $($e.currentTarget);
+ this._getSuggestions().removeClass("tt-is-under-cursor");
+ $suggestion.addClass("tt-is-under-cursor");
+ },
+ _handleSelection: function($e) {
+ var $suggestion = $($e.currentTarget);
+ this.trigger("suggestionSelected", extractSuggestion($suggestion));
+ },
+ _show: function() {
+ this.$menu.css("display", "block");
+ },
+ _hide: function() {
+ this.$menu.hide();
+ },
+ _moveCursor: function(increment) {
+ var $suggestions, $cur, nextIndex, $underCursor;
+ if (!this.isVisible()) {
+ return;
+ }
+ $suggestions = this._getSuggestions();
+ $cur = $suggestions.filter(".tt-is-under-cursor");
+ $cur.removeClass("tt-is-under-cursor");
+ nextIndex = $suggestions.index($cur) + increment;
+ nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1;
+ if (nextIndex === -1) {
+ this.trigger("cursorRemoved");
+ return;
+ } else if (nextIndex < -1) {
+ nextIndex = $suggestions.length - 1;
+ }
+ $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor");
+ this._ensureVisibility($underCursor);
+ this.trigger("cursorMoved", extractSuggestion($underCursor));
+ },
+ _getSuggestions: function() {
+ return this.$menu.find(".tt-suggestions > .tt-suggestion");
+ },
+ _ensureVisibility: function($el) {
+ var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true);
+ if (elTop < 0) {
+ this.$menu.scrollTop(menuScrollTop + elTop);
+ } else if (menuHeight < elBottom) {
+ this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight));
+ }
+ },
+ destroy: function() {
+ this.$menu.off(".tt");
+ this.$menu = null;
+ },
+ isVisible: function() {
+ return this.isOpen && !this.isEmpty;
+ },
+ closeUnlessMouseIsOverDropdown: function() {
+ if (!this.isMouseOverDropdown) {
+ this.close();
+ }
+ },
+ close: function() {
+ if (this.isOpen) {
+ this.isOpen = false;
+ this.isMouseOverDropdown = false;
+ this._hide();
+ this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor");
+ this.trigger("closed");
+ }
+ },
+ open: function() {
+ if (!this.isOpen) {
+ this.isOpen = true;
+ !this.isEmpty && this._show();
+ this.trigger("opened");
+ }
+ },
+ setLanguageDirection: function(dir) {
+ var ltrCss = {
+ left: "0",
+ right: "auto"
+ }, rtlCss = {
+ left: "auto",
+ right: " 0"
+ };
+ dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss);
+ },
+ moveCursorUp: function() {
+ this._moveCursor(-1);
+ },
+ moveCursorDown: function() {
+ this._moveCursor(+1);
+ },
+ getSuggestionUnderCursor: function() {
+ var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first();
+ return $suggestion.length > 0 ? extractSuggestion($suggestion) : null;
+ },
+ getFirstSuggestion: function() {
+ var $suggestion = this._getSuggestions().first();
+ return $suggestion.length > 0 ? extractSuggestion($suggestion) : null;
+ },
+ renderSuggestions: function(dataset, suggestions) {
+ var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '%body
', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el;
+ if ($dataset.length === 0) {
+ $suggestionsList = $(html.suggestionsList).css(css.suggestionsList);
+ $dataset = $("").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu);
+ }
+ if (suggestions.length > 0) {
+ this.isEmpty = false;
+ this.isOpen && this._show();
+ elBuilder = document.createElement("div");
+ fragment = document.createDocumentFragment();
+ utils.each(suggestions, function(i, suggestion) {
+ suggestion.dataset = dataset.name;
+ compiledHtml = dataset.template(suggestion.datum);
+ elBuilder.innerHTML = wrapper.replace("%body", compiledHtml);
+ $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion);
+ $el.children().each(function() {
+ $(this).css(css.suggestionChild);
+ });
+ fragment.appendChild($el[0]);
+ });
+ $dataset.show().find(".tt-suggestions").html(fragment);
+ } else {
+ this.clearSuggestions(dataset.name);
+ }
+ this.trigger("suggestionsRendered");
+ },
+ clearSuggestions: function(datasetName) {
+ var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions");
+ $datasets.hide();
+ $suggestions.empty();
+ if (this._getSuggestions().length === 0) {
+ this.isEmpty = true;
+ this._hide();
+ }
+ }
+ });
+ return DropdownView;
+ function extractSuggestion($el) {
+ return $el.data("suggestion");
+ }
+ }();
+ var TypeaheadView = function() {
+ var html = {
+ wrapper: '',
+ hint: '',
+ dropdown: ''
+ }, css = {
+ wrapper: {
+ position: "relative",
+ display: "inline-block"
+ },
+ hint: {
+ position: "absolute",
+ top: "0",
+ left: "0",
+ borderColor: "transparent",
+ boxShadow: "none"
+ },
+ query: {
+ position: "relative",
+ verticalAlign: "top",
+ backgroundColor: "transparent"
+ },
+ dropdown: {
+ position: "absolute",
+ top: "100%",
+ left: "0",
+ zIndex: "100",
+ display: "none"
+ }
+ };
+ if (utils.isMsie()) {
+ utils.mixin(css.query, {
+ backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"
+ });
+ }
+ if (utils.isMsie() && utils.isMsie() <= 7) {
+ utils.mixin(css.wrapper, {
+ display: "inline",
+ zoom: "1"
+ });
+ utils.mixin(css.query, {
+ marginTop: "-1px"
+ });
+ }
+ function TypeaheadView(o) {
+ var $menu, $input, $hint;
+ utils.bindAll(this);
+ this.$node = buildDomStructure(o.input);
+ this.datasets = o.datasets;
+ this.dir = null;
+ this.eventBus = o.eventBus;
+ $menu = this.$node.find(".tt-dropdown-menu");
+ $input = this.$node.find(".tt-query");
+ $hint = this.$node.find(".tt-hint");
+ this.dropdownView = new DropdownView({
+ menu: $menu
+ }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent);
+ this.inputView = new InputView({
+ input: $input,
+ hint: $hint
+ }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete);
+ }
+ utils.mixin(TypeaheadView.prototype, EventTarget, {
+ _managePreventDefault: function(e) {
+ var $e = e.data, hint, inputValue, preventDefault = false;
+ switch (e.type) {
+ case "tabKeyed":
+ hint = this.inputView.getHintValue();
+ inputValue = this.inputView.getInputValue();
+ preventDefault = hint && hint !== inputValue;
+ break;
+
+ case "upKeyed":
+ case "downKeyed":
+ preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey;
+ break;
+ }
+ preventDefault && $e.preventDefault();
+ },
+ _setLanguageDirection: function() {
+ var dir = this.inputView.getLanguageDirection();
+ if (dir !== this.dir) {
+ this.dir = dir;
+ this.$node.css("direction", dir);
+ this.dropdownView.setLanguageDirection(dir);
+ }
+ },
+ _updateHint: function() {
+ var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match;
+ if (hint && dropdownIsVisible && !inputHasOverflow) {
+ inputValue = this.inputView.getInputValue();
+ query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, "");
+ escapedQuery = utils.escapeRegExChars(query);
+ beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i");
+ match = beginsWithQuery.exec(hint);
+ this.inputView.setHintValue(inputValue + (match ? match[1] : ""));
+ }
+ },
+ _clearHint: function() {
+ this.inputView.setHintValue("");
+ },
+ _clearSuggestions: function() {
+ this.dropdownView.clearSuggestions();
+ },
+ _setInputValueToQuery: function() {
+ this.inputView.setInputValue(this.inputView.getQuery());
+ },
+ _setInputValueToSuggestionUnderCursor: function(e) {
+ var suggestion = e.data;
+ this.inputView.setInputValue(suggestion.value, true);
+ },
+ _openDropdown: function() {
+ this.dropdownView.open();
+ },
+ _closeDropdown: function(e) {
+ this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"]();
+ },
+ _moveDropdownCursor: function(e) {
+ var $e = e.data;
+ if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) {
+ this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"]();
+ }
+ },
+ _handleSelection: function(e) {
+ var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor();
+ if (suggestion) {
+ this.inputView.setInputValue(suggestion.value);
+ byClick ? this.inputView.focus() : e.data.preventDefault();
+ byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close();
+ this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset);
+ }
+ },
+ _getSuggestions: function() {
+ var that = this, query = this.inputView.getQuery();
+ if (utils.isBlankString(query)) {
+ return;
+ }
+ utils.each(this.datasets, function(i, dataset) {
+ dataset.getSuggestions(query, function(suggestions) {
+ if (query === that.inputView.getQuery()) {
+ that.dropdownView.renderSuggestions(dataset, suggestions);
+ }
+ });
+ });
+ },
+ _autocomplete: function(e) {
+ var isCursorAtEnd, ignoreEvent, query, hint, suggestion;
+ if (e.type === "rightKeyed" || e.type === "leftKeyed") {
+ isCursorAtEnd = this.inputView.isCursorAtEnd();
+ ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed";
+ if (!isCursorAtEnd || ignoreEvent) {
+ return;
+ }
+ }
+ query = this.inputView.getQuery();
+ hint = this.inputView.getHintValue();
+ if (hint !== "" && query !== hint) {
+ suggestion = this.dropdownView.getFirstSuggestion();
+ this.inputView.setInputValue(suggestion.value);
+ this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset);
+ }
+ },
+ _propagateEvent: function(e) {
+ this.eventBus.trigger(e.type);
+ },
+ destroy: function() {
+ this.inputView.destroy();
+ this.dropdownView.destroy();
+ destroyDomStructure(this.$node);
+ this.$node = null;
+ },
+ setQuery: function(query) {
+ this.inputView.setQuery(query);
+ this.inputView.setInputValue(query);
+ this._clearHint();
+ this._clearSuggestions();
+ this._getSuggestions();
+ }
+ });
+ return TypeaheadView;
+ function buildDomStructure(input) {
+ var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint);
+ $wrapper = $wrapper.css(css.wrapper);
+ $dropdown = $dropdown.css(css.dropdown);
+ $hint.css(css.hint).css({
+ backgroundAttachment: $input.css("background-attachment"),
+ backgroundClip: $input.css("background-clip"),
+ backgroundColor: $input.css("background-color"),
+ backgroundImage: $input.css("background-image"),
+ backgroundOrigin: $input.css("background-origin"),
+ backgroundPosition: $input.css("background-position"),
+ backgroundRepeat: $input.css("background-repeat"),
+ backgroundSize: $input.css("background-size")
+ });
+ $input.data("ttAttrs", {
+ dir: $input.attr("dir"),
+ autocomplete: $input.attr("autocomplete"),
+ spellcheck: $input.attr("spellcheck"),
+ style: $input.attr("style")
+ });
+ $input.addClass("tt-query").attr({
+ autocomplete: "off",
+ spellcheck: false
+ }).css(css.query);
+ try {
+ !$input.attr("dir") && $input.attr("dir", "auto");
+ } catch (e) {}
+ return $input.wrap($wrapper).parent().prepend($hint).append($dropdown);
+ }
+ function destroyDomStructure($node) {
+ var $input = $node.find(".tt-query");
+ utils.each($input.data("ttAttrs"), function(key, val) {
+ utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val);
+ });
+ $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node);
+ $node.remove();
+ }
+ }();
+ (function() {
+ var cache = {}, viewKey = "ttView", methods;
+ methods = {
+ initialize: function(datasetDefs) {
+ var datasets;
+ datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ];
+ if (datasetDefs.length === 0) {
+ $.error("no datasets provided");
+ }
+ datasets = utils.map(datasetDefs, function(o) {
+ var dataset = cache[o.name] ? cache[o.name] : new Dataset(o);
+ if (o.name) {
+ cache[o.name] = dataset;
+ }
+ return dataset;
+ });
+ return this.each(initialize);
+ function initialize() {
+ var $input = $(this), deferreds, eventBus = new EventBus({
+ el: $input
+ });
+ deferreds = utils.map(datasets, function(dataset) {
+ return dataset.initialize();
+ });
+ $input.data(viewKey, new TypeaheadView({
+ input: $input,
+ eventBus: eventBus = new EventBus({
+ el: $input
+ }),
+ datasets: datasets
+ }));
+ $.when.apply($, deferreds).always(function() {
+ utils.defer(function() {
+ eventBus.trigger("initialized");
+ });
+ });
+ }
+ },
+ destroy: function() {
+ return this.each(destroy);
+ function destroy() {
+ var $this = $(this), view = $this.data(viewKey);
+ if (view) {
+ view.destroy();
+ $this.removeData(viewKey);
+ }
+ }
+ },
+ setQuery: function(query) {
+ return this.each(setQuery);
+ function setQuery() {
+ var view = $(this).data(viewKey);
+ view && view.setQuery(query);
+ }
+ }
+ };
+ $.fn.typeahead = function(method) {
+ if (methods[method]) {
+ return methods[method].apply(this, [].slice.call(arguments, 1));
+ } else {
+ return methods.initialize.apply(this, arguments);
+ }
+ };
+ })();
+})(phantom.jQuery);
\ No newline at end of file
diff --git a/phantom/static/phantom/js/typeahead.min.js b/phantom/static/phantom/js/typeahead.min.js
new file mode 100644
index 0000000..c76b9a2
--- /dev/null
+++ b/phantom/static/phantom/js/typeahead.min.js
@@ -0,0 +1,7 @@
+/*!
+ * typeahead.js 0.9.3
+ * https://github.com/twitter/typeahead
+ * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
+ */
+
+!function(a){var b="0.9.3",c={isMsie:function(){var a=/(msie) ([\w.]+)/i.exec(navigator.userAgent);return a?parseInt(a[2],10):!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},bind:a.proxy,bindAll:function(b){var c;for(var d in b)a.isFunction(c=b[d])&&(b[d]=a.proxy(c,b))},indexOf:function(a,b){for(var c=0;c=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},tokenizeQuery:function(b){return a.trim(b).toLowerCase().split(/[\s]+/)},tokenizeText:function(b){return a.trim(b).toLowerCase().split(/[\s\-_]+/)},getProtocol:function(){return location.protocol},noop:function(){}},d=function(){var a=/\s+/;return{on:function(b,c){var d;if(!c)return this;for(this._callbacks=this._callbacks||{},b=b.split(a);d=b.shift();)this._callbacks[d]=this._callbacks[d]||[],this._callbacks[d].push(c);return this},trigger:function(b,c){var d,e;if(!this._callbacks)return this;for(b=b.split(a);d=b.shift();)if(e=this._callbacks[d])for(var f=0;fa;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return c.isNumber(d)&&b()>d?!0:!1}}:{get:c.noop,set:c.noop,remove:c.noop,clear:c.noop,isExpired:c.noop},c.mixin(a.prototype,g),a}(),g=function(){function a(a){c.bindAll(this),a=a||{},this.sizeLimit=a.sizeLimit||10,this.cache={},this.cachedKeysByAge=[]}return c.mixin(a.prototype,{get:function(a){return this.cache[a]},set:function(a,b){var c;this.cachedKeysByAge.length===this.sizeLimit&&(c=this.cachedKeysByAge.shift(),delete this.cache[c]),this.cache[a]=b,this.cachedKeysByAge.push(a)}}),a}(),h=function(){function b(a){c.bindAll(this),a=c.isString(a)?{url:a}:a,i=i||new g,h=c.isNumber(a.maxParallelRequests)?a.maxParallelRequests:h||6,this.url=a.url,this.wildcard=a.wildcard||"%QUERY",this.filter=a.filter,this.replace=a.replace,this.ajaxSettings={type:"get",cache:a.cache,timeout:a.timeout,dataType:a.dataType||"json",beforeSend:a.beforeSend},this._get=(/^throttle$/i.test(a.rateLimitFn)?c.throttle:c.debounce)(this._get,a.rateLimitWait||300)}function d(){j++}function e(){j--}function f(){return h>j}var h,i,j=0,k={};return c.mixin(b.prototype,{_get:function(a,b){function c(c){var e=d.filter?d.filter(c):c;b&&b(e),i.set(a,c)}var d=this;f()?this._sendRequest(a).done(c):this.onDeckRequestArgs=[].slice.call(arguments,0)},_sendRequest:function(b){function c(){e(),k[b]=null,f.onDeckRequestArgs&&(f._get.apply(f,f.onDeckRequestArgs),f.onDeckRequestArgs=null)}var f=this,g=k[b];return g||(d(),g=k[b]=a.ajax(b,this.ajaxSettings).always(c)),g},get:function(a,b){var d,e,f=this,g=encodeURIComponent(a||"");return b=b||c.noop,d=this.replace?this.replace(this.url,g):this.url.replace(this.wildcard,g),(e=i.get(d))?c.defer(function(){b(f.filter?f.filter(e):e)}):this._get(d,b),!!e}}),b}(),i=function(){function d(b){c.bindAll(this),c.isString(b.template)&&!b.engine&&a.error("no template engine specified"),b.local||b.prefetch||b.remote||a.error("one of local, prefetch, or remote is required"),this.name=b.name||c.getUniqueId(),this.limit=b.limit||5,this.minLength=b.minLength||1,this.header=b.header,this.footer=b.footer,this.valueKey=b.valueKey||"value",this.template=e(b.template,b.engine,this.valueKey),this.local=b.local,this.prefetch=b.prefetch,this.remote=b.remote,this.itemHash={},this.adjacencyList={},this.storage=b.name?new f(b.name):null}function e(a,b,d){var e,f;return c.isFunction(a)?e=a:c.isString(a)?(f=b.compile(a),e=c.bind(f.render,f)):e=function(a){return""+a[d]+"
"},e}var g={thumbprint:"thumbprint",protocol:"protocol",itemHash:"itemHash",adjacencyList:"adjacencyList"};return c.mixin(d.prototype,{_processLocalData:function(a){this._mergeProcessedData(this._processData(a))},_loadPrefetchData:function(d){function e(a){var b=d.filter?d.filter(a):a,e=m._processData(b),f=e.itemHash,h=e.adjacencyList;m.storage&&(m.storage.set(g.itemHash,f,d.ttl),m.storage.set(g.adjacencyList,h,d.ttl),m.storage.set(g.thumbprint,n,d.ttl),m.storage.set(g.protocol,c.getProtocol(),d.ttl)),m._mergeProcessedData(e)}var f,h,i,j,k,l,m=this,n=b+(d.thumbprint||"");return this.storage&&(f=this.storage.get(g.thumbprint),h=this.storage.get(g.protocol),i=this.storage.get(g.itemHash),j=this.storage.get(g.adjacencyList)),k=f!==n||h!==c.getProtocol(),d=c.isString(d)?{url:d}:d,d.ttl=c.isNumber(d.ttl)?d.ttl:864e5,i&&j&&!k?(this._mergeProcessedData({itemHash:i,adjacencyList:j}),l=a.Deferred().resolve()):l=a.getJSON(d.url).done(e),l},_transformDatum:function(a){var b=c.isString(a)?a:a[this.valueKey],d=a.tokens||c.tokenizeText(b),e={value:b,tokens:d};return c.isString(a)?(e.datum={},e.datum[this.valueKey]=a):e.datum=a,e.tokens=c.filter(e.tokens,function(a){return!c.isBlankString(a)}),e.tokens=c.map(e.tokens,function(a){return a.toLowerCase()}),e},_processData:function(a){var b=this,d={},e={};return c.each(a,function(a,f){var g=b._transformDatum(f),h=c.getUniqueId(g.value);d[h]=g,c.each(g.tokens,function(a,b){var d=b.charAt(0),f=e[d]||(e[d]=[h]);!~c.indexOf(f,h)&&f.push(h)})}),{itemHash:d,adjacencyList:e}},_mergeProcessedData:function(a){var b=this;c.mixin(this.itemHash,a.itemHash),c.each(a.adjacencyList,function(a,c){var d=b.adjacencyList[a];b.adjacencyList[a]=d?d.concat(c):c})},_getLocalSuggestions:function(a){var b,d=this,e=[],f=[],g=[];return c.each(a,function(a,b){var d=b.charAt(0);!~c.indexOf(e,d)&&e.push(d)}),c.each(e,function(a,c){var e=d.adjacencyList[c];return e?(f.push(e),(!b||e.length").css({position:"absolute",left:"-9999px",visibility:"hidden",whiteSpace:"nowrap",fontFamily:b.css("font-family"),fontSize:b.css("font-size"),fontStyle:b.css("font-style"),fontVariant:b.css("font-variant"),fontWeight:b.css("font-weight"),wordSpacing:b.css("word-spacing"),letterSpacing:b.css("letter-spacing"),textIndent:b.css("text-indent"),textRendering:b.css("text-rendering"),textTransform:b.css("text-transform")}).insertAfter(b)}function f(a,b){return a=(a||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),b=(b||"").replace(/^\s*/g,"").replace(/\s{2,}/g," "),a===b}return c.mixin(b.prototype,d,{_handleFocus:function(){this.trigger("focused")},_handleBlur:function(){this.trigger("blured")},_handleSpecialKeyEvent:function(a){var b=this.specialKeyCodeMap[a.which||a.keyCode];b&&this.trigger(b+"Keyed",a)},_compareQueryToInputValue:function(){var a=this.getInputValue(),b=f(this.query,a),c=b?this.query.length!==a.length:!1;c?this.trigger("whitespaceChanged",{value:this.query}):b||this.trigger("queryChanged",{value:this.query=a})},destroy:function(){this.$hint.off(".tt"),this.$input.off(".tt"),this.$hint=this.$input=this.$overflowHelper=null},focus:function(){this.$input.focus()},blur:function(){this.$input.blur()},getQuery:function(){return this.query},setQuery:function(a){this.query=a},getInputValue:function(){return this.$input.val()},setInputValue:function(a,b){this.$input.val(a),!b&&this._compareQueryToInputValue()},getHintValue:function(){return this.$hint.val()},setHintValue:function(a){this.$hint.val(a)},getLanguageDirection:function(){return(this.$input.css("direction")||"ltr").toLowerCase()},isOverflow:function(){return this.$overflowHelper.text(this.getInputValue()),this.$overflowHelper.width()>this.$input.width()},isCursorAtEnd:function(){var a,b=this.$input.val().length,d=this.$input[0].selectionStart;return c.isNumber(d)?d===b:document.selection?(a=document.selection.createRange(),a.moveStart("character",-b),b===a.text.length):!0}}),b}(),k=function(){function b(b){c.bindAll(this),this.isOpen=!1,this.isEmpty=!0,this.isMouseOverDropdown=!1,this.$menu=a(b.menu).on("mouseenter.tt",this._handleMouseenter).on("mouseleave.tt",this._handleMouseleave).on("click.tt",".tt-suggestion",this._handleSelection).on("mouseover.tt",".tt-suggestion",this._handleMouseover)}function e(a){return a.data("suggestion")}var f={suggestionsList:''},g={suggestionsList:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"}};return c.mixin(b.prototype,d,{_handleMouseenter:function(){this.isMouseOverDropdown=!0},_handleMouseleave:function(){this.isMouseOverDropdown=!1},_handleMouseover:function(b){var c=a(b.currentTarget);this._getSuggestions().removeClass("tt-is-under-cursor"),c.addClass("tt-is-under-cursor")},_handleSelection:function(b){var c=a(b.currentTarget);this.trigger("suggestionSelected",e(c))},_show:function(){this.$menu.css("display","block")},_hide:function(){this.$menu.hide()},_moveCursor:function(a){var b,c,d,f;if(this.isVisible()){if(b=this._getSuggestions(),c=b.filter(".tt-is-under-cursor"),c.removeClass("tt-is-under-cursor"),d=b.index(c)+a,d=(d+1)%(b.length+1)-1,-1===d)return this.trigger("cursorRemoved"),void 0;-1>d&&(d=b.length-1),f=b.eq(d).addClass("tt-is-under-cursor"),this._ensureVisibility(f),this.trigger("cursorMoved",e(f))}},_getSuggestions:function(){return this.$menu.find(".tt-suggestions > .tt-suggestion")},_ensureVisibility:function(a){var b=this.$menu.height()+parseInt(this.$menu.css("paddingTop"),10)+parseInt(this.$menu.css("paddingBottom"),10),c=this.$menu.scrollTop(),d=a.position().top,e=d+a.outerHeight(!0);0>d?this.$menu.scrollTop(c+d):e>b&&this.$menu.scrollTop(c+(e-b))},destroy:function(){this.$menu.off(".tt"),this.$menu=null},isVisible:function(){return this.isOpen&&!this.isEmpty},closeUnlessMouseIsOverDropdown:function(){this.isMouseOverDropdown||this.close()},close:function(){this.isOpen&&(this.isOpen=!1,this.isMouseOverDropdown=!1,this._hide(),this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"),this.trigger("closed"))},open:function(){this.isOpen||(this.isOpen=!0,!this.isEmpty&&this._show(),this.trigger("opened"))},setLanguageDirection:function(a){var b={left:"0",right:"auto"},c={left:"auto",right:" 0"};"ltr"===a?this.$menu.css(b):this.$menu.css(c)},moveCursorUp:function(){this._moveCursor(-1)},moveCursorDown:function(){this._moveCursor(1)},getSuggestionUnderCursor:function(){var a=this._getSuggestions().filter(".tt-is-under-cursor").first();return a.length>0?e(a):null},getFirstSuggestion:function(){var a=this._getSuggestions().first();return a.length>0?e(a):null},renderSuggestions:function(b,d){var e,h,i,j,k,l="tt-dataset-"+b.name,m='%body
',n=this.$menu.find("."+l);0===n.length&&(h=a(f.suggestionsList).css(g.suggestionsList),n=a("").addClass(l).append(b.header).append(h).append(b.footer).appendTo(this.$menu)),d.length>0?(this.isEmpty=!1,this.isOpen&&this._show(),i=document.createElement("div"),j=document.createDocumentFragment(),c.each(d,function(c,d){d.dataset=b.name,e=b.template(d.datum),i.innerHTML=m.replace("%body",e),k=a(i.firstChild).css(g.suggestion).data("suggestion",d),k.children().each(function(){a(this).css(g.suggestionChild)}),j.appendChild(k[0])}),n.show().find(".tt-suggestions").html(j)):this.clearSuggestions(b.name),this.trigger("suggestionsRendered")},clearSuggestions:function(a){var b=a?this.$menu.find(".tt-dataset-"+a):this.$menu.find('[class^="tt-dataset-"]'),c=b.find(".tt-suggestions");b.hide(),c.empty(),0===this._getSuggestions().length&&(this.isEmpty=!0,this._hide())}}),b}(),l=function(){function b(a){var b,d,f;c.bindAll(this),this.$node=e(a.input),this.datasets=a.datasets,this.dir=null,this.eventBus=a.eventBus,b=this.$node.find(".tt-dropdown-menu"),d=this.$node.find(".tt-query"),f=this.$node.find(".tt-hint"),this.dropdownView=new k({menu:b}).on("suggestionSelected",this._handleSelection).on("cursorMoved",this._clearHint).on("cursorMoved",this._setInputValueToSuggestionUnderCursor).on("cursorRemoved",this._setInputValueToQuery).on("cursorRemoved",this._updateHint).on("suggestionsRendered",this._updateHint).on("opened",this._updateHint).on("closed",this._clearHint).on("opened closed",this._propagateEvent),this.inputView=new j({input:d,hint:f}).on("focused",this._openDropdown).on("blured",this._closeDropdown).on("blured",this._setInputValueToQuery).on("enterKeyed tabKeyed",this._handleSelection).on("queryChanged",this._clearHint).on("queryChanged",this._clearSuggestions).on("queryChanged",this._getSuggestions).on("whitespaceChanged",this._updateHint).on("queryChanged whitespaceChanged",this._openDropdown).on("queryChanged whitespaceChanged",this._setLanguageDirection).on("escKeyed",this._closeDropdown).on("escKeyed",this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed",this._managePreventDefault).on("upKeyed downKeyed",this._moveDropdownCursor).on("upKeyed downKeyed",this._openDropdown).on("tabKeyed leftKeyed rightKeyed",this._autocomplete)}function e(b){var c=a(g.wrapper),d=a(g.dropdown),e=a(b),f=a(g.hint);c=c.css(h.wrapper),d=d.css(h.dropdown),f.css(h.hint).css({backgroundAttachment:e.css("background-attachment"),backgroundClip:e.css("background-clip"),backgroundColor:e.css("background-color"),backgroundImage:e.css("background-image"),backgroundOrigin:e.css("background-origin"),backgroundPosition:e.css("background-position"),backgroundRepeat:e.css("background-repeat"),backgroundSize:e.css("background-size")}),e.data("ttAttrs",{dir:e.attr("dir"),autocomplete:e.attr("autocomplete"),spellcheck:e.attr("spellcheck"),style:e.attr("style")}),e.addClass("tt-query").attr({autocomplete:"off",spellcheck:!1}).css(h.query);try{!e.attr("dir")&&e.attr("dir","auto")}catch(i){}return e.wrap(c).parent().prepend(f).append(d)}function f(a){var b=a.find(".tt-query");c.each(b.data("ttAttrs"),function(a,d){c.isUndefined(d)?b.removeAttr(a):b.attr(a,d)}),b.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter(a),a.remove()}var g={wrapper:'',hint:'',dropdown:''},h={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none"},query:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"}};return c.isMsie()&&c.mixin(h.query,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),c.isMsie()&&c.isMsie()<=7&&(c.mixin(h.wrapper,{display:"inline",zoom:"1"}),c.mixin(h.query,{marginTop:"-1px"})),c.mixin(b.prototype,d,{_managePreventDefault:function(a){var b,c,d=a.data,e=!1;switch(a.type){case"tabKeyed":b=this.inputView.getHintValue(),c=this.inputView.getInputValue(),e=b&&b!==c;break;case"upKeyed":case"downKeyed":e=!d.shiftKey&&!d.ctrlKey&&!d.metaKey}e&&d.preventDefault()},_setLanguageDirection:function(){var a=this.inputView.getLanguageDirection();a!==this.dir&&(this.dir=a,this.$node.css("direction",a),this.dropdownView.setLanguageDirection(a))},_updateHint:function(){var a,b,d,e,f,g=this.dropdownView.getFirstSuggestion(),h=g?g.value:null,i=this.dropdownView.isVisible(),j=this.inputView.isOverflow();h&&i&&!j&&(a=this.inputView.getInputValue(),b=a.replace(/\s{2,}/g," ").replace(/^\s+/g,""),d=c.escapeRegExChars(b),e=new RegExp("^(?:"+d+")(.*$)","i"),f=e.exec(h),this.inputView.setHintValue(a+(f?f[1]:"")))},_clearHint:function(){this.inputView.setHintValue("")},_clearSuggestions:function(){this.dropdownView.clearSuggestions()},_setInputValueToQuery:function(){this.inputView.setInputValue(this.inputView.getQuery())},_setInputValueToSuggestionUnderCursor:function(a){var b=a.data;this.inputView.setInputValue(b.value,!0)},_openDropdown:function(){this.dropdownView.open()},_closeDropdown:function(a){this.dropdownView["blured"===a.type?"closeUnlessMouseIsOverDropdown":"close"]()},_moveDropdownCursor:function(a){var b=a.data;b.shiftKey||b.ctrlKey||b.metaKey||this.dropdownView["upKeyed"===a.type?"moveCursorUp":"moveCursorDown"]()},_handleSelection:function(a){var b="suggestionSelected"===a.type,d=b?a.data:this.dropdownView.getSuggestionUnderCursor();d&&(this.inputView.setInputValue(d.value),b?this.inputView.focus():a.data.preventDefault(),b&&c.isMsie()?c.defer(this.dropdownView.close):this.dropdownView.close(),this.eventBus.trigger("selected",d.datum,d.dataset))},_getSuggestions:function(){var a=this,b=this.inputView.getQuery();c.isBlankString(b)||c.each(this.datasets,function(c,d){d.getSuggestions(b,function(c){b===a.inputView.getQuery()&&a.dropdownView.renderSuggestions(d,c)})})},_autocomplete:function(a){var b,c,d,e,f;("rightKeyed"!==a.type&&"leftKeyed"!==a.type||(b=this.inputView.isCursorAtEnd(),c="ltr"===this.inputView.getLanguageDirection()?"leftKeyed"===a.type:"rightKeyed"===a.type,b&&!c))&&(d=this.inputView.getQuery(),e=this.inputView.getHintValue(),""!==e&&d!==e&&(f=this.dropdownView.getFirstSuggestion(),this.inputView.setInputValue(f.value),this.eventBus.trigger("autocompleted",f.datum,f.dataset)))},_propagateEvent:function(a){this.eventBus.trigger(a.type)},destroy:function(){this.inputView.destroy(),this.dropdownView.destroy(),f(this.$node),this.$node=null},setQuery:function(a){this.inputView.setQuery(a),this.inputView.setInputValue(a),this._clearHint(),this._clearSuggestions(),this._getSuggestions()}}),b}();!function(){var b,d={},f="ttView";b={initialize:function(b){function g(){var b,d=a(this),g=new e({el:d});b=c.map(h,function(a){return a.initialize()}),d.data(f,new l({input:d,eventBus:g=new e({el:d}),datasets:h})),a.when.apply(a,b).always(function(){c.defer(function(){g.trigger("initialized")})})}var h;return b=c.isArray(b)?b:[b],0===b.length&&a.error("no datasets provided"),h=c.map(b,function(a){var b=d[a.name]?d[a.name]:new i(a);return a.name&&(d[a.name]=b),b}),this.each(g)},destroy:function(){function b(){var b=a(this),c=b.data(f);c&&(c.destroy(),b.removeData(f))}return this.each(b)},setQuery:function(b){function c(){var c=a(this).data(f);c&&c.setQuery(b)}return this.each(c)}},$.fn.typeahead=function(a){return b[a]?b[a].apply(this,[].slice.call(arguments,1)):b.initialize.apply(this,arguments)}}()}(phantom.jQuery);
\ No newline at end of file
diff --git a/phantom/templates/admin/login.html b/phantom/templates/admin/login.html
index 8e58741..48a052d 100644
--- a/phantom/templates/admin/login.html
+++ b/phantom/templates/admin/login.html
@@ -46,6 +46,15 @@ {% trans 'Sign In' %}
{{form.password.errors}}{% endif %}
+