diff --git a/.gitignore b/.gitignore index bfad355..110f9d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ static/css/* -!static/css/lib/* +!lib *.swp .sass-cache diff --git a/app.py b/app.py index 0dba068..274daac 100644 --- a/app.py +++ b/app.py @@ -22,7 +22,7 @@ database = Database() @app.route("/") def index(): - return render_template("index.html", typeahead_languages=LANGS) + return render_template("index.html", autocomplete_languages=LANGS) @app.route("/search.json") def search(): diff --git a/bitshift/crawler/crawl.py b/bitshift/crawler/crawl.py index 40238af..2e5ace5 100644 --- a/bitshift/crawler/crawl.py +++ b/bitshift/crawler/crawl.py @@ -39,6 +39,7 @@ def crawl(): indexer.GitIndexer(repo_clone_queue, run_event)] parse_servers = start_parse_servers() + time.sleep(5) for thread in threads: thread.start() diff --git a/bitshift/crawler/indexer.py b/bitshift/crawler/indexer.py index 209078d..b35559a 100644 --- a/bitshift/crawler/indexer.py +++ b/bitshift/crawler/indexer.py @@ -33,6 +33,7 @@ class GitRepository(object): repository belongs to (eg, GitHub, BitBucket). :ivar rank: (float) The rank of the repository, as assigned by :class:`crawler.GitHubCrawler`. + :ivar dirname: (str) The repository's on-disk directory name. """ def __init__(self, url, name, framework_name, rank): @@ -54,6 +55,7 @@ class GitRepository(object): self.name = name self.framework_name = framework_name self.rank = rank + self.dirname = name.replace("-", "--").replace("/", "-") class GitIndexer(threading.Thread): """ @@ -125,19 +127,14 @@ class GitIndexer(threading.Thread): :type repo_url: :class:`GitRepository` """ - with _ChangeDir("%s/%s" % (GIT_CLONE_DIR, repo.name)): + with _ChangeDir("%s/%s" % (GIT_CLONE_DIR, repo.dirname)): try: self._insert_repository_codelets(repo) except Exception: self._logger.exception("Exception raised while indexing:") finally: - if os.path.isdir("%s/%s" % (GIT_CLONE_DIR, repo.name)): - if len([obj for obj in os.listdir('.') if - os.path.isdir(obj)]) <= 1: - shutil.rmtree("%s/%s" % ( - GIT_CLONE_DIR, repo.name.split("/")[0])) - else: - shutil.rmtree("%s/%s" % (GIT_CLONE_DIR, repo.name)) + if os.path.isdir("%s/%s" % (GIT_CLONE_DIR, repo.dirname)): + shutil.rmtree("%s/%s" % (GIT_CLONE_DIR, repo.dirname)) def _insert_repository_codelets(self, repo): """ @@ -167,9 +164,9 @@ class GitIndexer(threading.Thread): authors = [(self._decode(author), None) for author in commits_meta[filename]["authors"]] - codelet = Codelet("%s:%s" % (repo.name, filename), source, filename, - None, authors, self._generate_file_url(filename, - repo.url, repo.framework_name), + url = self._generate_file_url(filename, repo.url, repo.framework_name) + codelet = Codelet("%s: %s" % (repo.name, filename), source, + filename, None, authors, url, commits_meta[filename]["time_created"], commits_meta[filename]["time_last_modified"], repo.rank) @@ -437,37 +434,20 @@ class _GitCloner(threading.Thread): """ GIT_CLONE_TIMEOUT = 500 - queue_percent_full = (float(self.index_queue.qsize()) / - self.index_queue.maxsize) * 100 - - exit_code = None - command = ("perl -e 'alarm shift @ARGV; exec @ARGV' %d git clone" - " --single-branch %s %s/%s || pkill -f git") - - command_attempt = 0 - while exit_code is None: - try: - exit_code = subprocess.call(command % (GIT_CLONE_TIMEOUT, - repo.url, GIT_CLONE_DIR, repo.name), shell=True) - except Exception: - time.sleep(1) - command_attempt += 1 - if command_attempt == 20: - break - else: - continue - else: - break - - if exit_code != 0: - if os.path.isdir("%s/%s" % (GIT_CLONE_DIR, repo.name)): - shutil.rmtree("%s/%s" % (GIT_CLONE_DIR, repo.name)) + self.index_queue.maxsize) * 100 + + command = ["perl", "-e", "alarm shift @ARGV; exec @ARGV", + str(GIT_CLONE_TIMEOUT), "git", "clone", "--single-branch", + repo.url, GIT_CLONE_DIR + "/" + repo.dirname] + if subprocess.call(command) != 0: + subprocess.call(["pkill", "-f", "git"]) # This makes Ben K upset + if os.path.isdir("%s/%s" % (GIT_CLONE_DIR, repo.dirname)): + shutil.rmtree("%s/%s" % (GIT_CLONE_DIR, repo.dirname)) return while self.index_queue.full(): time.sleep(THREAD_QUEUE_SLEEP) - self.index_queue.put(repo) class _ChangeDir(object): diff --git a/bitshift/database/__init__.py b/bitshift/database/__init__.py index 5f483f3..23b190c 100644 --- a/bitshift/database/__init__.py +++ b/bitshift/database/__init__.py @@ -131,7 +131,7 @@ class Database(object): def _decompose_url(self, cursor, url): """Break up a URL into an origin (with a URL base) and a suffix.""" - query = """SELECT origin_id, SUBSTR(?, LENGTH(origin_url_base)) + query = """SELECT origin_id, SUBSTR(?, LENGTH(origin_url_base) + 1) FROM origins WHERE origin_url_base IS NOT NULL AND ? LIKE CONCAT(origin_url_base, "%")""" diff --git a/bitshift/database/migration.py b/bitshift/database/migration.py index 9a74a18..730790f 100644 --- a/bitshift/database/migration.py +++ b/bitshift/database/migration.py @@ -3,7 +3,7 @@ Contains information about database schema versions, and SQL queries to update between them. """ -VERSION = 8 +VERSION = 10 MIGRATIONS = [ # 1 -> 2 @@ -100,6 +100,28 @@ MIGRATIONS = [ [ """ALTER TABLE `origins` DROP COLUMN `origin_image`""" + ], + # 8 -> 9 + [ + """DELIMITER // + CREATE PROCEDURE `empty_database`() + BEGIN + DELETE FROM `codelets`; + DELETE FROM `code`; + DELETE FROM `cache`; + ALTER TABLE `codelets` AUTO_INCREMENT = 1; + ALTER TABLE `authors` AUTO_INCREMENT = 1; + ALTER TABLE `symbols` AUTO_INCREMENT = 1; + ALTER TABLE `symbol_locations` AUTO_INCREMENT = 1; + END// + DELIMITER ;""" + ], + # 9 -> 10 + [ + """ALTER TABLE `symbol_locations` + MODIFY COLUMN `sloc_col` INT UNSIGNED DEFAULT NULL, + MODIFY COLUMN `sloc_end_row` INT UNSIGNED DEFAULT NULL, + MODIFY COLUMN `sloc_end_col` INT UNSIGNED DEFAULT NULL""" ] ] diff --git a/bitshift/database/schema.sql b/bitshift/database/schema.sql index 913a365..6102fe8 100644 --- a/bitshift/database/schema.sql +++ b/bitshift/database/schema.sql @@ -1,4 +1,4 @@ --- Schema version 8 +-- Schema version 10 CREATE DATABASE `bitshift` DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci; USE `bitshift`; @@ -6,7 +6,7 @@ USE `bitshift`; CREATE TABLE `version` ( `version` INT UNSIGNED NOT NULL ) ENGINE=InnoDB; -INSERT INTO `version` VALUES (8); +INSERT INTO `version` VALUES (10); CREATE TABLE `origins` ( `origin_id` TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, @@ -77,9 +77,9 @@ CREATE TABLE `symbol_locations` ( `sloc_symbol` BIGINT UNSIGNED NOT NULL, `sloc_type` TINYINT UNSIGNED NOT NULL, `sloc_row` INT UNSIGNED NOT NULL, - `sloc_col` INT UNSIGNED NOT NULL, - `sloc_end_row` INT UNSIGNED NOT NULL, - `sloc_end_col` INT UNSIGNED NOT NULL, + `sloc_col` INT UNSIGNED DEFAULT NULL, + `sloc_end_row` INT UNSIGNED DEFAULT NULL, + `sloc_end_col` INT UNSIGNED DEFAULT NULL, PRIMARY KEY (`sloc_id`), FOREIGN KEY (`sloc_symbol`) REFERENCES `symbols` (`symbol_id`) @@ -107,6 +107,19 @@ CREATE TABLE `cache_data` ( ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB; +DELIMITER // +CREATE PROCEDURE `empty_database`() + BEGIN + DELETE FROM `codelets`; + DELETE FROM `code`; + DELETE FROM `cache`; + ALTER TABLE `codelets` AUTO_INCREMENT = 1; + ALTER TABLE `authors` AUTO_INCREMENT = 1; + ALTER TABLE `symbols` AUTO_INCREMENT = 1; + ALTER TABLE `symbol_locations` AUTO_INCREMENT = 1; + END// +DELIMITER ; + CREATE EVENT `flush_cache` ON SCHEDULE EVERY 1 HOUR DO diff --git a/bitshift/languages.py b/bitshift/languages.py index d6395bb..e809d44 100644 --- a/bitshift/languages.py +++ b/bitshift/languages.py @@ -8,7 +8,8 @@ def _load_langs(): filename = path.join(path.dirname(__file__), "languages.yml") with open(filename) as fp: data = yaml.load(fp)["languages"] - langs = [it.keys()[0] if isinstance(it, dict) else it for it in data] + langs = [(it.keys()[0] if isinstance(it, dict) else it).encode("utf8") + for it in data] all_langs = {} for i, lang in enumerate(data): if isinstance(lang, dict): diff --git a/parsers/java/src/main/java/com/bitshift/parsing/parsers/JavaParser.java b/parsers/java/src/main/java/com/bitshift/parsing/parsers/JavaParser.java index 989c0dd..8b6b2f0 100644 --- a/parsers/java/src/main/java/com/bitshift/parsing/parsers/JavaParser.java +++ b/parsers/java/src/main/java/com/bitshift/parsing/parsers/JavaParser.java @@ -80,7 +80,7 @@ public class JavaParser extends Parser { int sl = this.root.getLineNumber(node.getStartPosition()); int sc = this.root.getColumnNumber(node.getStartPosition()); - Integer el = -1; + Integer el = sl; Integer ec = -1; if (statements.size() > 0) { @@ -110,7 +110,7 @@ public class JavaParser extends Parser { int sl = this.root.getLineNumber(node.getStartPosition()); int sc = this.root.getColumnNumber(node.getStartPosition()); - data.put("coord", Symbols.createCoord(sl, sc, -1, -1)); + data.put("coord", Symbols.createCoord(sl, sc, sl, -1)); data.put("name", name); this._cache.push(data); return true; @@ -140,7 +140,7 @@ public class JavaParser extends Parser { int sl = this.root.getLineNumber(node.getStartPosition()); int sc = this.root.getColumnNumber(node.getStartPosition()); - data.put("coord", Symbols.createCoord(sl, sc, -1, -1)); + data.put("coord", Symbols.createCoord(sl, sc, sl, -1)); this._cache.push(data); return true; } @@ -161,7 +161,7 @@ public class JavaParser extends Parser { int sl = this.root.getLineNumber(node.getStartPosition()); int sc = this.root.getColumnNumber(node.getStartPosition()); - data.put("coord", Symbols.createCoord(sl, sc, -1, -1)); + data.put("coord", Symbols.createCoord(sl, sc, sl, -1)); this._cache.push(data); return true; } diff --git a/static/css/lib/github.css b/static/css/lib/github.css new file mode 100644 index 0000000..723432c --- /dev/null +++ b/static/css/lib/github.css @@ -0,0 +1,65 @@ +td.linenos { background: rgba(65,131,196,0.05); padding-right: 10px; border-right: 1px solid #bbb; } +span.lineno { background: rgba(65,131,196,0.05); padding: 0 5px 0 5px; } +pre { line-height: 125% } +.highlighttable { background-color: #fff; padding-left: 10px; width: inherit; height: inherit; } +.hll { display: block } +.c { color: #999988; font-style: italic } /* Comment */ +.err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.k { color: #000000; font-weight: bold } /* Keyword */ +.o { color: #000000; font-weight: bold } /* Operator */ +.cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ +.c1 { color: #999988; font-style: italic } /* Comment.Single */ +.cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ +.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.ge { color: #000000; font-style: italic } /* Generic.Emph */ +.gr { color: #aa0000 } /* Generic.Error */ +.gh { color: #999999 } /* Generic.Heading */ +.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.go { color: #888888 } /* Generic.Output */ +.gp { color: #555555 } /* Generic.Prompt */ +.gs { font-weight: bold } /* Generic.Strong */ +.gu { color: #aaaaaa } /* Generic.Subheading */ +.gt { color: #aa0000 } /* Generic.Traceback */ +.kc { color: #000000; font-weight: bold } /* Keyword.Constant */ +.kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ +.kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ +.kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ +.kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ +.kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.m { color: #009999 } /* Literal.Number */ +.s { color: #d01040 } /* Literal.String */ +.na { color: #008080 } /* Name.Attribute */ +.nb { color: #0086B3 } /* Name.Builtin */ +.nc { color: #445588; font-weight: bold } /* Name.Class */ +.no { color: #008080 } /* Name.Constant */ +.nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ +.ni { color: #800080 } /* Name.Entity */ +.ne { color: #990000; font-weight: bold } /* Name.Exception */ +.nf { color: #990000; font-weight: bold } /* Name.Function */ +.nl { color: #990000; font-weight: bold } /* Name.Label */ +.nn { color: #555555 } /* Name.Namespace */ +.nt { color: #000080 } /* Name.Tag */ +.nv { color: #008080 } /* Name.Variable */ +.ow { color: #000000; font-weight: bold } /* Operator.Word */ +.w { color: #bbbbbb } /* Text.Whitespace */ +.mf { color: #009999 } /* Literal.Number.Float */ +.mh { color: #009999 } /* Literal.Number.Hex */ +.mi { color: #009999 } /* Literal.Number.Integer */ +.mo { color: #009999 } /* Literal.Number.Oct */ +.sb { color: #d01040 } /* Literal.String.Backtick */ +.sc { color: #d01040 } /* Literal.String.Char */ +.sd { color: #d01040 } /* Literal.String.Doc */ +.s2 { color: #d01040 } /* Literal.String.Double */ +.se { color: #d01040 } /* Literal.String.Escape */ +.sh { color: #d01040 } /* Literal.String.Heredoc */ +.si { color: #d01040 } /* Literal.String.Interpol */ +.sx { color: #d01040 } /* Literal.String.Other */ +.sr { color: #009926 } /* Literal.String.Regex */ +.s1 { color: #d01040 } /* Literal.String.Single */ +.ss { color: #990073 } /* Literal.String.Symbol */ +.bp { color: #999999 } /* Name.Builtin.Pseudo */ +.vc { color: #008080 } /* Name.Variable.Class */ +.vg { color: #008080 } /* Name.Variable.Global */ +.vi { color: #008080 } /* Name.Variable.Instance */ +.il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/static/css/lib/highlight.css b/static/css/lib/highlight.css new file mode 100644 index 0000000..f82e1ca --- /dev/null +++ b/static/css/lib/highlight.css @@ -0,0 +1,64 @@ +td.linenos { background-color: #f0f0f0; padding-right: 10px; } +span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } +pre { line-height: 125% } +.highlighttable { background-color: #49483e; width: inherit; height: inherit; } +.hll { display: block } + { background: #272822; color: #f8f8f2 } +.c { color: #75715e } /* Comment */ +.err { color: #960050; background-color: #1e0010 } /* Error */ +.k { color: #66d9ef } /* Keyword */ +.l { color: #ae81ff } /* Literal */ +.n { color: #f8f8f2 } /* Name */ +.o { color: #f92672 } /* Operator */ +.p { color: #f8f8f2 } /* Punctuation */ +.cm { color: #75715e } /* Comment.Multiline */ +.cp { color: #75715e } /* Comment.Preproc */ +.c1 { color: #75715e } /* Comment.Single */ +.cs { color: #75715e } /* Comment.Special */ +.ge { font-style: italic } /* Generic.Emph */ +.gs { font-weight: bold } /* Generic.Strong */ +.kc { color: #66d9ef } /* Keyword.Constant */ +.kd { color: #66d9ef } /* Keyword.Declaration */ +.kn { color: #f92672 } /* Keyword.Namespace */ +.kp { color: #66d9ef } /* Keyword.Pseudo */ +.kr { color: #66d9ef } /* Keyword.Reserved */ +.kt { color: #66d9ef } /* Keyword.Type */ +.ld { color: #e6db74 } /* Literal.Date */ +.m { color: #ae81ff } /* Literal.Number */ +.s { color: #e6db74 } /* Literal.String */ +.na { color: #a6e22e } /* Name.Attribute */ +.nb { color: #f8f8f2 } /* Name.Builtin */ +.nc { color: #a6e22e } /* Name.Class */ +.no { color: #66d9ef } /* Name.Constant */ +.nd { color: #a6e22e } /* Name.Decorator */ +.ni { color: #f8f8f2 } /* Name.Entity */ +.ne { color: #a6e22e } /* Name.Exception */ +.nf { color: #a6e22e } /* Name.Function */ +.nl { color: #f8f8f2 } /* Name.Label */ +.nn { color: #f8f8f2 } /* Name.Namespace */ +.nx { color: #a6e22e } /* Name.Other */ +.py { color: #f8f8f2 } /* Name.Property */ +.nt { color: #f92672 } /* Name.Tag */ +.nv { color: #f8f8f2 } /* Name.Variable */ +.ow { color: #f92672 } /* Operator.Word */ +.w { color: #f8f8f2 } /* Text.Whitespace */ +.mf { color: #ae81ff } /* Literal.Number.Float */ +.mh { color: #ae81ff } /* Literal.Number.Hex */ +.mi { color: #ae81ff } /* Literal.Number.Integer */ +.mo { color: #ae81ff } /* Literal.Number.Oct */ +.sb { color: #e6db74 } /* Literal.String.Backtick */ +.sc { color: #e6db74 } /* Literal.String.Char */ +.sd { color: #e6db74 } /* Literal.String.Doc */ +.s2 { color: #e6db74 } /* Literal.String.Double */ +.se { color: #ae81ff } /* Literal.String.Escape */ +.sh { color: #e6db74 } /* Literal.String.Heredoc */ +.si { color: #e6db74 } /* Literal.String.Interpol */ +.sx { color: #e6db74 } /* Literal.String.Other */ +.sr { color: #e6db74 } /* Literal.String.Regex */ +.s1 { color: #e6db74 } /* Literal.String.Single */ +.ss { color: #e6db74 } /* Literal.String.Symbol */ +.bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ +.vc { color: #f8f8f2 } /* Name.Variable.Class */ +.vg { color: #f8f8f2 } /* Name.Variable.Global */ +.vi { color: #f8f8f2 } /* Name.Variable.Instance */ +.il { color: #ae81ff } /* Literal.Number.Integer.Long */ diff --git a/static/js/index.advanced-search-form.js b/static/js/index.advanced-search-form.js index 79deb8d..9209a95 100644 --- a/static/js/index.advanced-search-form.js +++ b/static/js/index.advanced-search-form.js @@ -37,6 +37,8 @@ var searchGroups = $("div#search-groups"); searchGroups.append( searchGroup.append(createSearchGroupInput("language"))); $("div#sidebar input[type=checkbox]#language").prop("checked", true); + + searchGroups[0].scrollTop = searchGroups[0].scrollHeight; }); // Remove the currently selected group if it's not the only one, and mark @@ -67,7 +69,7 @@ var searchGroups = $("div#search-groups"); }) }); - // Add an input field to the currently selected search group. + // Toggle the presence of an input field. $("div#sidebar input[type=checkbox]").click(function(){ var fieldId = $(this).prop("id"); if($(this).is(":checked")){ @@ -76,8 +78,13 @@ var searchGroups = $("div#search-groups"); if(fieldId.slice(0, 4) == "date") $(".search-group#selected ." + fieldId).datepicker(); } - else - $("div.search-group#selected #" + fieldId).remove() + else { + if($(".search-group#selected").children("div").length > 1) + $(".search-group#selected #" + fieldId).remove() + else + $(this).prop("checked", true); + } + searchGroups[0].scrollTop = searchGroups[0].scrollHeight; }); var previousAdvancedQuery = ""; @@ -125,7 +132,8 @@ function assembleQuery(){ groupQuery.push(genFieldQueryString( inputFields[field], regexCheckbox[field].checked)); - groupQueries.push(groupQuery.join(" AND ")); + if(groupQuery.length > 0) + groupQueries.push(groupQuery.join(" AND ")); } return groupQueries.join(" OR "); @@ -140,8 +148,5 @@ function assembleQuery(){ function genFieldQueryString(field, hasRegex){ var terms = field.value.replace(/\\/g, "\\\\").replace(/\"/g, "\\\""); var query = field.getAttribute("name") + ":" + (hasRegex?"re:":"") + terms; - if(field.value.indexOf('"') >= 0){ - return '"' + query + '"'; - } - return query; + return '"' + query + '"'; } diff --git a/static/js/index.js b/static/js/index.js index e5a4e14..c49794c 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -5,34 +5,145 @@ var advancedSearchDiv = $("div#advanced-search"); var advancedSearchButton = $("button#advanced-search"); -advancedSearchButton.click(function(){ - var searchField = $("div#search-field"); - if(!advancedSearchDiv.hasClass("visible")){ - searchField.addClass("partly-visible"); - advancedSearchDiv.fadeIn(500).addClass("visible"); - advancedSearchButton.addClass("clicked"); - } - else { - advancedSearchDiv.fadeOut(300).removeClass("visible"); - advancedSearchButton.removeClass("clicked"); - if($("div#results .result").length == 0) - searchField.removeClass("partly-visible"); +FINISH_TYPING_INTERVAL = 650; +var searchBar = $("form#search-bar input[type='text']")[0]; +var resultsDiv = $("div#results")[0]; + +var typingTimer, scrollTimer, lastValue; +var searchResultsPage = 1; + +/* + * Set all page callbacks. + */ +(function setHomePageCallbabacks(){ + var results = $('#results').get(0); + + // Enable infinite scrolling down the results page. + $(window).scroll(function(){ + if($(window).scrollTop() + $(window).height() == $(document).height() && + resultsDiv.querySelectorAll(".result").length > 0) + loadMoreResults(); + + clearTimeout(scrollTimer); + if (!results.classList.contains('disable-hover')) + results.classList.add('disable-hover') + + scrollTimer = setTimeout(function(){ + if (results.classList.contains('disable-hover')) + results.classList.remove('disable-hover'); + }, 200); + }); + + // Toggle the advanced-search form's visibility. + advancedSearchButton.click(function(){ + var searchField = $("div#search-field"); + if(!advancedSearchDiv.hasClass("visible")){ + searchField.addClass("partly-visible"); + advancedSearchDiv.fadeIn(500).addClass("visible"); + advancedSearchButton.addClass("clicked"); + } + else { + advancedSearchDiv.fadeOut(300).removeClass("visible"); + advancedSearchButton.removeClass("clicked"); + if($("div#results .result").length == 0) + searchField.removeClass("partly-visible"); + } + }); + + // Enable capturing the `enter` key. + $("form#search-bar").submit(function(event){ + event.preventDefault(); + return false; + }); + searchBar.onkeyup = typingTimer; +}()); + +/* + * Set keyboard shortcut mappings. + */ +(function resultsHotkeys(){ + /* + * If the currently viewed result is not the first, scroll to the previous + * result. + */ + var previousResult = function(){ + var currResult = $(".display-all"); + if(currResult.length) { + currResult.removeClass("display-all"); + currResult = currResult.closest(".result").prev(".result"); + } else { + currResult = $(document.querySelectorAll(".result")[0]); + } + + currResult.addClass("display-all"); + currResult.each(function(){ + $('html,body').stop().animate({ + scrollTop: $(this).offset().top - ( + $(window).height() - $(this).outerHeight(true)) / 2 + }, 140); + }); + }; + + /* + * If the currently viewed result is not the last, scroll to the next + * result. + */ + var nextResult = function(){ + var currResult = $(".display-all"); + if(currResult.length) { + currResult.removeClass("display-all"); + currResult = currResult.closest(".result").next(".result"); + } else { + currResult = $(document.querySelectorAll(".result")[0]); + } + + currResult.addClass('display-all'); + currResult.each(function(){ + $('html,body').stop().animate({ + scrollTop: $(this).offset().top - ( + $(window).height() - $(this).outerHeight(true)) / 2 + }, 140); + }); + }; + + var displayHotkeyHelp = function(){ + var help = $("div#hotkey-help"); + console.log("H"); + if(help.hasClass("hidden")) + help.fadeIn(420); + else + help.fadeOut(420); + + $("div#body").toggleClass("faded"); + help.toggleClass("hidden"); } -}); -FINISH_TYPING_INTERVAL = 650; -searchBar = $("form#search-bar input[type='text']")[0]; -resultsDiv = $("div#results")[0]; + var hotkeyActions = { + "k" : previousResult, + "j" : nextResult, + "h" : previousSymbolMatch, + "l" : nextSymbolMatch, + "?" : displayHotkeyHelp + }; + + $(window).keypress(function(key){ + for(var hotkey in hotkeyActions){ + var keyChar = String.fromCharCode(key.keyCode); + if(keyChar == hotkey && + !($(key.target).is("textarea") || $(key.target).is("input"))) + hotkeyActions[keyChar](); + } + }); +}()); -var typingTimer, lastValue; //Obtained by parsing python file with pygments -var codeExample = '
 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40
"""\nModule to contain all the project's Flask server plumbing.\n"""\n\nfrom flask import Flask\nfrom flask import render_template, session\n\nfrom bitshift import assets\n# from bitshift.database import Database\n# from bitshift.query import parse_query\n\napp = Flask(__name__)\napp.config.from_object("bitshift.config")\n\napp_env = app.jinja_env\napp_env.line_statement_prefix = "="\napp_env.globals.update(assets=assets)\n\n# database = Database()\n\n@app.route("/")\ndef index():\n    return render_template("index.html")\n\n@app.route("/search/<query>")\ndef search(query):\n    # tree = parse_query(query)\n    # database.search(tree)\n    pass\n\n@app.route("/about")\ndef about():\n    return render_template("about.html")\n\n@app.route("/developers")\ndef developers():\n    return render_template("developers.html")\n\nif __name__ == "__main__":\n    app.run(debug=True)\n
\n
' +var codeExample = '
 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40
"""\nModule to contain all the project's Flask server plumbing.\n"""\n\nfrom flask import Flask\nfrom flask import render_template, session\n\nfrom bitshift import assets\n# from bitshift.database import Database\n# from bitshift.query import parse_query\n\napp = Flask(__name__)\napp.config.from_object("bitshift.config")\n\napp_env = app.jinja_env\napp_env.line_statement_prefix = "="\napp_env.globals.update(assets=assets)\n\n# database = Database()\n\n@app.route("/")\ndef index():\n    return render_template("index.html")\n\n@app.route("/search/<query>")\ndef search(query):\n    # tree = parse_query(query)\n    # database.search(tree)\n    pass\n\n@app.route("/about")\ndef about():\n    return render_template("about.html")\n\n@app.route("/developers")\ndef developers():\n    return render_template("developers.html")\n\nif __name__ == "__main__":\n    app.run(debug=True)\n
\n
' searchBar.onkeyup = typingTimer; var testCodelet = { 'url': 'https://github.com/earwig/bitshift/blob/develop/app.py', 'filename': 'app.py', - 'language': 'Python', + 'language': 'python', 'date_created': 'May 10, 2014', 'date_modified': '2 days ago', 'origin': ['GitHub', 'https://github.com', ''], @@ -43,17 +154,12 @@ var testCodelet = { // Enable infinite scrolling down the results page. $(window).scroll(function() { var searchField = $("div#search-field"); - if($(window).scrollTop() + $(window).height() == $(document).height() && searchField.hasClass('partly-visible')){ + if($(window).scrollTop() + $(window).height() == $(document).height() && + searchField.hasClass('partly-visible')){ loadMoreResults(); } }); -// Enable capturing the `enter` key. -$("form#search-bar").submit(function(event){ - event.preventDefault(); - return false; -}); - /* * Clear the existing timer and set a new one the the user types text into the * search bar. @@ -109,6 +215,7 @@ function clearResults(){ * with its response. */ function populateResults(){ + searchResultsPage = 1; var results = queryServer(); for(var result = 0; result < results.length; result++){ @@ -135,74 +242,108 @@ function createResult(codelet) { row = document.createElement("tr"); //Level 2 var displayInfo = document.createElement("div"), - sidebar = document.createElement("td"), codeElt = document.createElement("td"), - displayButton = document.createElement("td"), hiddenInfoContainer = document.createElement("td"), - hiddenInfo = document.createElement("div"); + hiddenInfo = document.createElement("div"), + cycle = document.createElement("div"); //Level 3 var title = document.createElement("span"), site = document.createElement("span"), - dateModified = document.createElement("span"), - language = document.createElement("span"), - dateCreated = document.createElement("span"), + nextMatch = document.createElement("a"), + prevMatch = document.createElement("a"), + dateModified = document.createElement("div"), + language = document.createElement("div"), + dateCreated = document.createElement("div"), authors = document.createElement("div"); //Classes and ID's newDiv.classList.add('result'); displayInfo.id = 'display-info'; - sidebar.id = 'sidebar'; codeElt.id = 'code'; - displayButton.id = 'display-button'; hiddenInfo.id = 'hidden-info'; + cycle.id = 'cycle-matches' title.id = 'title'; site.id = 'site'; + nextMatch.id = 'next-match'; + nextMatch.href = '#'; + prevMatch.id = 'prev-match'; + prevMatch.href = '#'; dateModified.id = 'date-modified'; language.id = 'language'; dateCreated.id = 'date-created'; authors.id = 'authors'; //Add the bulk of the html - title.innerHTML = 'File ' + title.innerHTML = ' » ' + codelet.filename + ''; - site.innerHTML = 'on ' + codelet.origin[0] +''; - dateModified.innerHTML = 'Last modified ' + codelet.date_modified; + site.innerHTML = '' + codelet.origin[0] +''; + nextMatch.innerHTML = 'next match'; + prevMatch.innerHTML = 'prev match'; + language.innerHTML = 'Language: ' + codelet.language + ''; + dateModified.innerHTML = 'Last modified: ' + codelet.date_modified + ''; // Needs to be changed from int to string on the server - language.innerHTML = codelet.language; - dateCreated.innerHTML = 'Created ' + codelet.date_created; - authors.innerHTML = 'Authors: '; - $.each(codelet.authors, function(i, a) { - authors.innerHTML += '' + a + ' '; + dateCreated.innerHTML = 'Created: ' + codelet.date_created + ''; + + var authorsHtml = 'Authors: '; + codelet.authors.forEach(function(a, i) { + if (i == codelet.authors.length - 1) + authorsHtml += '' + a + ' '; + else + authorsHtml += '' + a + ' , '; }); + authors.innerHTML = authorsHtml; - sidebar.innerHTML = ''; // Needs to be processed on the server codeElt.innerHTML = '
' + codelet.html_code + '
'; //Event binding - $(displayButton).click(function(e) { - $(hiddenInfo).toggleClass('visible'); - $(this).toggleClass('active'); + $(newDiv).on('mousemove', function(e) { + var holdCondition = $('.disable-hover'); + + if(holdCondition.length == 0) { + $(this).siblings().removeClass('display-all'); + $(this).addClass('display-all'); + } + }); + + $(newDiv).on('mouseleave', function(e) { + var holdCondition = $('.disable-hover'); + + if(holdCondition.length == 0) + $(this).removeClass('display-all'); + }); + + $(nextMatch).click(function(e) { + e.stopPropagation(); + e.preventDefault(); + nextSymbolMatch(); + }); + + $(prevMatch).click(function(e) { + e.stopPropagation(); + e.preventDefault(); + previousSymbolMatch(); }); //Finish and append elements to parent elements hiddenInfo.appendChild(dateCreated); hiddenInfo.appendChild(dateModified); - hiddenInfo.appendChild(authors); hiddenInfo.appendChild(language); + hiddenInfo.appendChild(authors); hiddenInfoContainer.appendChild(hiddenInfo); - row.appendChild(sidebar); row.appendChild(codeElt); row.appendChild(hiddenInfoContainer); - row.appendChild(displayButton); table.appendChild(row); - displayInfo.appendChild(title); displayInfo.appendChild(site); + displayInfo.appendChild(title); + + cycle.appendChild(prevMatch); + cycle.appendChild(nextMatch); newDiv.appendChild(displayInfo); newDiv.appendChild(table); @@ -210,6 +351,52 @@ function createResult(codelet) { return newDiv; } +function previousSymbolMatch() { + var currResult = $(".display-all"), + currMatch = currResult.find(".hll.current"), + matches = currResult.find(".hll"), + scrollDiv = currResult.find("#tablecontainer"); + + if (currMatch.length == 0) + currMatch = matches[0]; + else + currMatch.removeClass('current'); + + var index = matches.index(currMatch.get(0)) - 1; + index = index <= 0 ? matches.length - 1 : index; + var newMatch = $(matches[index]); + + scrollDiv.scrollTop(scrollDiv.scrollTop() + - scrollDiv.height() / 2 + + newMatch.position().top + newMatch.height() / 2); + + newMatch.effect("highlight", {color: '#FFF'}, 750) + newMatch.addClass('current'); +}; + +function nextSymbolMatch() { + var currResult = $(".display-all"), + currMatch = currResult.find(".hll.current"), + matches = currResult.find(".hll"), + scrollDiv = currResult.find("#tablecontainer"); + + if (currMatch.length == 0) + currMatch = $(matches[0]); + else + currMatch.removeClass("current"); + + var index = matches.index(currMatch.get(0)) + 1; + index = index >= matches.length ? 0 : index; + var newMatch = $(matches[index]); + + scrollDiv.scrollTop(scrollDiv.scrollTop() + - scrollDiv.height() / 2 + + newMatch.position().top + newMatch.height() / 2); + + newMatch.effect("highlight", {color: "#FFF"}, 750) + newMatch.addClass("current"); +}; + /* * AJAX the current query string to the server, and return its response. * @@ -217,10 +404,23 @@ function createResult(codelet) { * elements, to fill `div#results`. */ function queryServer(){ - var resultDivs = [] + var queryUrl = document.URL + "search.json?" + $.param({ + "q" : searchBar.value, + "p" : searchResultsPage++ + }); + + var resultDivs = []; + $.getJSON(queryUrl, function(result){ + if("error" in result) + insertErrorMessage(result["error"]); + else + for(var codelet = 0; codelet < result["results"].length; codelet++) + resultDivs.push(result["results"][codelet]); + }); + for(var result = 0; result < 20; result++){ var newDiv = createResult(testCodelet); - resultDivs.push(newDiv); + resultDivs.push(newDiv) } return resultDivs; @@ -230,7 +430,7 @@ function queryServer(){ * Adds more results to `div#results`. */ function loadMoreResults(){ - results = queryServer(); + var results = queryServer(); for(var result = 0; result < results.length; result++){ var newDiv = results[result]; resultsDiv.appendChild(newDiv); @@ -243,3 +443,16 @@ function loadMoreResults(){ result * 20); } } + +/* + * Displays a warning message in the UI. + * + * @param msg (str) The message string. + */ +function insertErrorMessage(msg){ + var error = $("
Error:
"); + error.append(msg); + resultsDiv.appendChild(error[0]); +} + +// loadMoreResults(); diff --git a/static/js/lib/typeahead.bundle.min.js b/static/js/lib/typeahead.bundle.min.js deleted file mode 100644 index dff8ef5..0000000 --- a/static/js/lib/typeahead.bundle.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * typeahead.js 0.10.2 - * https://github.com/twitter/typeahead.js - * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT - */ - -!function(a){var b={isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!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,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},noop:function(){}},c="0.10.2",d=function(){function a(a){return a.split(/\s+/)}function b(a){return a.split(/\W+/)}function c(a){return function(b){return function(c){return a(c[b])}}}return{nonword:b,whitespace:a,obj:{nonword:c(b),whitespace:c(a)}}}(),e=function(){function a(a){this.maxSize=a||100,this.size=0,this.hash={},this.list=new c}function c(){this.head=this.tail=null}function d(a,b){this.key=a,this.val=b,this.prev=this.next=null}return b.mixin(a.prototype,{set:function(a,b){var c,e=this.list.tail;this.size>=this.maxSize&&(this.list.remove(e),delete this.hash[e.key]),(c=this.hash[a])?(c.val=b,this.list.moveToFront(c)):(c=new d(a,b),this.list.add(c),this.hash[a]=c,this.size++)},get:function(a){var b=this.hash[a];return b?(this.list.moveToFront(b),b.val):void 0}}),b.mixin(c.prototype,{add:function(a){this.head&&(a.next=this.head,this.head.prev=a),this.head=a,this.tail=this.tail||a},remove:function(a){a.prev?a.prev.next=a.next:this.head=a.next,a.next?a.next.prev=a.prev:this.tail=a.prev},moveToFront:function(a){this.remove(a),this.add(a)}}),a}(),f=function(){function a(a){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+this.prefix)}function c(){return(new Date).getTime()}function d(a){return JSON.stringify(b.isUndefined(a)?null:a)}function e(a){return JSON.parse(a)}var f,g;try{f=window.localStorage,f.setItem("~~~","!"),f.removeItem("~~~")}catch(h){f=null}return g=f&&window.JSON?{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},get:function(a){return this.isExpired(a)&&this.remove(a),e(f.getItem(this._prefix(a)))},set:function(a,e,g){return b.isNumber(g)?f.setItem(this._ttlKey(a),d(c()+g)):f.removeItem(this._ttlKey(a)),f.setItem(this._prefix(a),d(e))},remove:function(a){return f.removeItem(this._ttlKey(a)),f.removeItem(this._prefix(a)),this},clear:function(){var a,b,c=[],d=f.length;for(a=0;d>a;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 b.isNumber(d)&&c()>d?!0:!1}}:{get:b.noop,set:b.noop,remove:b.noop,clear:b.noop,isExpired:b.noop},b.mixin(a.prototype,g),a}(),g=function(){function c(b){b=b||{},this._send=b.transport?d(b.transport):a.ajax,this._get=b.rateLimiter?b.rateLimiter(this._get):this._get}function d(c){return function(d,e){function f(a){b.defer(function(){h.resolve(a)})}function g(a){b.defer(function(){h.reject(a)})}var h=a.Deferred();return c(d,e,f,g),h}}var f=0,g={},h=6,i=new e(10);return c.setMaxPendingRequests=function(a){h=a},c.resetCache=function(){i=new e(10)},b.mixin(c.prototype,{_get:function(a,b,c){function d(b){c&&c(null,b),i.set(a,b)}function e(){c&&c(!0)}function j(){f--,delete g[a],l.onDeckRequestArgs&&(l._get.apply(l,l.onDeckRequestArgs),l.onDeckRequestArgs=null)}var k,l=this;(k=g[a])?k.done(d).fail(e):h>f?(f++,g[a]=this._send(a,b).done(d).fail(e).always(j)):this.onDeckRequestArgs=[].slice.call(arguments,0)},get:function(a,c,d){var e;return b.isFunction(c)&&(d=c,c={}),(e=i.get(a))?b.defer(function(){d&&d(null,e)}):this._get(a,c,d),!!e}}),c}(),h=function(){function c(b){b=b||{},b.datumTokenizer&&b.queryTokenizer||a.error("datumTokenizer and queryTokenizer are both required"),this.datumTokenizer=b.datumTokenizer,this.queryTokenizer=b.queryTokenizer,this.reset()}function d(a){return a=b.filter(a,function(a){return!!a}),a=b.map(a,function(a){return a.toLowerCase()})}function e(){return{ids:[],children:{}}}function f(a){for(var b={},c=[],d=0;db[e]?e++:(f.push(a[d]),d++,e++);return f}return b.mixin(c.prototype,{bootstrap:function(a){this.datums=a.datums,this.trie=a.trie},add:function(a){var c=this;a=b.isArray(a)?a:[a],b.each(a,function(a){var f,g;f=c.datums.push(a)-1,g=d(c.datumTokenizer(a)),b.each(g,function(a){var b,d,g;for(b=c.trie,d=a.split("");g=d.shift();)b=b.children[g]||(b.children[g]=e()),b.ids.push(f)})})},get:function(a){var c,e,h=this;return c=d(this.queryTokenizer(a)),b.each(c,function(a){var b,c,d,f;if(e&&0===e.length)return!1;for(b=h.trie,c=a.split("");b&&(d=c.shift());)b=b.children[d];return b&&0===c.length?(f=b.ids.slice(0),void(e=e?g(e,f):f)):(e=[],!1)}),e?b.map(f(e),function(a){return h.datums[a]}):[]},reset:function(){this.datums=[],this.trie=e()},serialize:function(){return{datums:this.datums,trie:this.trie}}}),c}(),i=function(){function d(a){return a.local||null}function e(d){var e,f;return f={url:null,thumbprint:"",ttl:864e5,filter:null,ajax:{}},(e=d.prefetch||null)&&(e=b.isString(e)?{url:e}:e,e=b.mixin(f,e),e.thumbprint=c+e.thumbprint,e.ajax.type=e.ajax.type||"GET",e.ajax.dataType=e.ajax.dataType||"json",!e.url&&a.error("prefetch requires url to be set")),e}function f(c){function d(a){return function(c){return b.debounce(c,a)}}function e(a){return function(c){return b.throttle(c,a)}}var f,g;return g={url:null,wildcard:"%QUERY",replace:null,rateLimitBy:"debounce",rateLimitWait:300,send:null,filter:null,ajax:{}},(f=c.remote||null)&&(f=b.isString(f)?{url:f}:f,f=b.mixin(g,f),f.rateLimiter=/^throttle$/i.test(f.rateLimitBy)?e(f.rateLimitWait):d(f.rateLimitWait),f.ajax.type=f.ajax.type||"GET",f.ajax.dataType=f.ajax.dataType||"json",delete f.rateLimitBy,delete f.rateLimitWait,!f.url&&a.error("remote requires url to be set")),f}return{local:d,prefetch:e,remote:f}}();!function(c){function e(b){b&&(b.local||b.prefetch||b.remote)||a.error("one of local, prefetch, or remote is required"),this.limit=b.limit||5,this.sorter=j(b.sorter),this.dupDetector=b.dupDetector||k,this.local=i.local(b),this.prefetch=i.prefetch(b),this.remote=i.remote(b),this.cacheKey=this.prefetch?this.prefetch.cacheKey||this.prefetch.url:null,this.index=new h({datumTokenizer:b.datumTokenizer,queryTokenizer:b.queryTokenizer}),this.storage=this.cacheKey?new f(this.cacheKey):null}function j(a){function c(b){return b.sort(a)}function d(a){return a}return b.isFunction(a)?c:d}function k(){return!1}var l,m;return l=c.Bloodhound,m={data:"data",protocol:"protocol",thumbprint:"thumbprint"},c.Bloodhound=e,e.noConflict=function(){return c.Bloodhound=l,e},e.tokenizers=d,b.mixin(e.prototype,{_loadPrefetch:function(b){function c(a){f.clear(),f.add(b.filter?b.filter(a):a),f._saveToStorage(f.index.serialize(),b.thumbprint,b.ttl)}var d,e,f=this;return(d=this._readFromStorage(b.thumbprint))?(this.index.bootstrap(d),e=a.Deferred().resolve()):e=a.ajax(b.url,b.ajax).done(c),e},_getFromRemote:function(a,b){function c(a,c){b(a?[]:f.remote.filter?f.remote.filter(c):c)}var d,e,f=this;return a=a||"",e=encodeURIComponent(a),d=this.remote.replace?this.remote.replace(this.remote.url,a):this.remote.url.replace(this.remote.wildcard,e),this.transport.get(d,this.remote.ajax,c)},_saveToStorage:function(a,b,c){this.storage&&(this.storage.set(m.data,a,c),this.storage.set(m.protocol,location.protocol,c),this.storage.set(m.thumbprint,b,c))},_readFromStorage:function(a){var b,c={};return this.storage&&(c.data=this.storage.get(m.data),c.protocol=this.storage.get(m.protocol),c.thumbprint=this.storage.get(m.thumbprint)),b=c.thumbprint!==a||c.protocol!==location.protocol,c.data&&!b?c.data:null},_initialize:function(){function c(){e.add(b.isFunction(f)?f():f)}var d,e=this,f=this.local;return d=this.prefetch?this._loadPrefetch(this.prefetch):a.Deferred().resolve(),f&&d.done(c),this.transport=this.remote?new g(this.remote):null,this.initPromise=d.promise()},initialize:function(a){return!this.initPromise||a?this._initialize():this.initPromise},add:function(a){this.index.add(a)},get:function(a,c){function d(a){var d=f.slice(0);b.each(a,function(a){var c;return c=b.some(d,function(b){return e.dupDetector(a,b)}),!c&&d.push(a),d.length0||!this.transport)&&c&&c(f)},clear:function(){this.index.reset()},clearPrefetchCache:function(){this.storage&&this.storage.clear()},clearRemoteCache:function(){this.transport&&g.resetCache()},ttAdapter:function(){return b.bind(this.get,this)}}),e}(this);var j={wrapper:'',dropdown:'',dataset:'
',suggestions:'',suggestion:'
'},k={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none"},input:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},inputWithNoHint:{position:"relative",verticalAlign:"top"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"},suggestions:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"},ltr:{left:"0",right:"auto"},rtl:{left:"auto",right:" 0"}};b.isMsie()&&b.mixin(k.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),b.isMsie()&&b.isMsie()<=7&&b.mixin(k.input,{marginTop:"-1px"});var l=function(){function c(b){b&&b.el||a.error("EventBus initialized without el"),this.$el=a(b.el)}var d="typeahead:";return b.mixin(c.prototype,{trigger:function(a){var b=[].slice.call(arguments,1);this.$el.trigger(d+a,b)}}),c}(),m=function(){function a(a,b,c,d){var e;if(!c)return this;for(b=b.split(i),c=d?h(c,d):c,this._callbacks=this._callbacks||{};e=b.shift();)this._callbacks[e]=this._callbacks[e]||{sync:[],async:[]},this._callbacks[e][a].push(c);return this}function b(b,c,d){return a.call(this,"async",b,c,d)}function c(b,c,d){return a.call(this,"sync",b,c,d)}function d(a){var b;if(!this._callbacks)return this;for(a=a.split(i);b=a.shift();)delete this._callbacks[b];return this}function e(a){var b,c,d,e,g;if(!this._callbacks)return this;for(a=a.split(i),d=[].slice.call(arguments,1);(b=a.shift())&&(c=this._callbacks[b]);)e=f(c.sync,this,[b].concat(d)),g=f(c.async,this,[b].concat(d)),e()&&j(g);return this}function f(a,b,c){function d(){for(var d,e=0;!d&&e