A semantic search engine for source code https://bitshift.benkurtovic.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 15 KiB

10 vuotta sitten
10 vuotta sitten
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. /*
  2. * @file Manages all library initialization, jQuery callbacks, query entry
  3. * callbacks, server querying, and results diplay for `index.html`.
  4. */
  5. var advancedSearchDiv = $("div#advanced-search");
  6. var advancedSearchButton = $("button#advanced-search");
  7. FINISH_TYPING_INTERVAL = 650;
  8. var searchBar = $("form#search-bar input[type='text']")[0];
  9. var resultsDiv = $("div#results")[0];
  10. var typingTimer, lastValue;
  11. var searchResultsPage = 1;
  12. /*
  13. * Set all page callbacks.
  14. */
  15. (function setHomePageCallbabacks(){
  16. // Enable infinite scrolling down the results page.
  17. $(window).scroll(function(){
  18. if($(window).scrollTop() + $(window).height() == $(document).height() &&
  19. resultsDiv.querySelectorAll(".result").length > 0)
  20. loadMoreResults();
  21. });
  22. // Toggle the advanced-search form's visibility.
  23. advancedSearchButton.click(function(){
  24. var searchField = $("div#search-field");
  25. if(!advancedSearchDiv.hasClass("visible")){
  26. searchField.addClass("partly-visible");
  27. advancedSearchDiv.fadeIn(500).addClass("visible");
  28. advancedSearchButton.addClass("clicked");
  29. }
  30. else {
  31. advancedSearchDiv.fadeOut(300).removeClass("visible");
  32. advancedSearchButton.removeClass("clicked");
  33. if($("div#results .result").length == 0)
  34. searchField.removeClass("partly-visible");
  35. }
  36. });
  37. // Enable capturing the `enter` key.
  38. $("form#search-bar").submit(function(event){
  39. event.preventDefault();
  40. return false;
  41. });
  42. searchBar.onkeyup = typingTimer;
  43. }());
  44. /*
  45. * Set keyboard shortcut mappings.
  46. */
  47. (function resultsHotkeys(){
  48. /*
  49. * If the currently viewed result is not the first, scroll to the previous
  50. * result.
  51. */
  52. var previousResult = function(){
  53. var currResult = $(".display-all");
  54. if(currResult.length)
  55. currResult.closest(".result").prev(".result").each(function(){
  56. $('html,body').stop().animate({
  57. scrollTop: $(this).offset().top - (
  58. $(window).height() - $(this).outerHeight(true)) / 2
  59. }, 140);
  60. });
  61. };
  62. /*
  63. * If the currently viewed result is not the last, scroll to the next
  64. * result.
  65. */
  66. var nextResult = function(){
  67. var currResult = $(".display-all");
  68. if(currResult.length)
  69. currResult.closest(".result").next(".result").each(function(){
  70. $('html,body').stop().animate({
  71. scrollTop: $(this).offset().top - (
  72. $(window).height() - $(this).outerHeight(true)) / 2
  73. }, 140);
  74. });
  75. };
  76. var hotkeyActions = {
  77. "k" : previousResult,
  78. "j" : nextResult
  79. "h" : previousSymbolMatch,
  80. "l" : nextSymbolMatch
  81. };
  82. $(window).keypress(function(key){
  83. for(var hotkey in hotkeyActions){
  84. var keyChar = String.fromCharCode(key.keyCode);
  85. console.log(keyChar);
  86. if(keyChar == hotkey)
  87. hotkeyActions[keyChar]();
  88. }
  89. });
  90. }());
  91. //Obtained by parsing python file with pygments
  92. var codeExample = '<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 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</pre></div></td><td class="code"><div class="highlight"><pre><span class="sd">&quot;&quot;&quot;</span>\n<span class="sd">Module to contain all the project&#39;s Flask server plumbing.</span>\n<span class="sd">&quot;&quot;&quot;</span>\n\n<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>\n<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">render_template</span><span class="p">,</span> <span class="n">session</span>\n\n<span class="kn">from</span> <span class="nn">bitshift</span> <span class="kn">import</span> <span class="n">assets</span>\n<span class="c"># from bitshift.database import Database</span>\n<span class="c"># from bitshift.query import parse_query</span>\n\n<span class="hll"><span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>\n</span><span class="hll"><span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">from_object</span><span class="p">(</span><span class="s">&quot;bitshift.config&quot;</span><span class="p">)</span>\n</span>\n<span class="hll"><span class="n">app_env</span> <span class="o">=</span> <span class="n">app</span><span class="o">.</span><span class="n">jinja_env</span>\n</span><span class="hll"><span class="n">app_env</span><span class="o">.</span><span class="n">line_statement_prefix</span> <span class="o">=</span> <span class="s">&quot;=&quot;</span>\n</span><span class="hll"><span class="n">app_env</span><span class="o">.</span><span class="n">globals</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">assets</span><span class="o">=</span><span class="n">assets</span><span class="p">)</span>\n</span>\n<span class="c"># database = Database()</span>\n\n<span class="hll"><span class="nd">@app.route</span><span class="p">(</span><span class="s">&quot;/&quot;</span><span class="p">)</span>\n</span><span class="k">def</span> <span class="nf">index</span><span class="p">():</span>\n <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&quot;index.html&quot;</span><span class="p">)</span>\n\n<span class="hll"><span class="nd">@app.route</span><span class="p">(</span><span class="s">&quot;/search/&lt;query&gt;&quot;</span><span class="p">)</span>\n</span><span class="k">def</span> <span class="nf">search</span><span class="p">(</span><span class="n">query</span><span class="p">):</span>\n <span class="c"># tree = parse_query(query)</span>\n <span class="c"># database.search(tree)</span>\n <span class="k">pass</span>\n\n<span class="hll"><span class="nd">@app.route</span><span class="p">(</span><span class="s">&quot;/about&quot;</span><span class="p">)</span>\n</span><span class="k">def</span> <span class="nf">about</span><span class="p">():</span>\n <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&quot;about.html&quot;</span><span class="p">)</span>\n\n<span class="hll"><span class="nd">@app.route</span><span class="p">(</span><span class="s">&quot;/developers&quot;</span><span class="p">)</span>\n</span><span class="k">def</span> <span class="nf">developers</span><span class="p">():</span>\n <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">&quot;developers.html&quot;</span><span class="p">)</span>\n\n<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span>\n<span class="hll"> <span class="n">app</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">debug</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>\n</span></pre></div>\n</td></tr></table>'
  93. searchBar.onkeyup = typingTimer;
  94. var testCodelet = {
  95. 'url': 'https://github.com/earwig/bitshift/blob/develop/app.py',
  96. 'filename': 'app.py',
  97. 'language': 'python',
  98. 'date_created': 'May 10, 2014',
  99. 'date_modified': '2 days ago',
  100. 'origin': ['GitHub', 'https://github.com', ''],
  101. 'authors': ['sevko', 'earwig'],
  102. 'html_code': codeExample
  103. };
  104. // Enable infinite scrolling down the results page.
  105. $(window).scroll(function() {
  106. var searchField = $("div#search-field");
  107. if($(window).scrollTop() + $(window).height() == $(document).height() &&
  108. searchField.hasClass('partly-visible')){
  109. loadMoreResults();
  110. }
  111. });
  112. /*
  113. * Clear the existing timer and set a new one the the user types text into the
  114. * search bar.
  115. */
  116. function typingTimer(event){
  117. clearTimeout(typingTimer);
  118. var enterKeyCode = 13;
  119. if(event.keyCode != enterKeyCode){
  120. if(lastValue != searchBar.value)
  121. typingTimer = setTimeout(finishedTyping, FINISH_TYPING_INTERVAL);
  122. }
  123. else {
  124. event.preventDefault();
  125. finishedTyping();
  126. return false;
  127. }
  128. };
  129. /*
  130. * Callback which queries the server whenver the user stops typing.
  131. *
  132. * Whenever the user doesn't type for a `FINISH_TYPING_INTERVAL` after having
  133. * entered new text in the search bar, send the current query request to the
  134. * server.
  135. */
  136. function finishedTyping(){
  137. lastValue = searchBar.value;
  138. var searchField = $("div#search-field");
  139. clearResults();
  140. if(searchBar.value){
  141. searchField.addClass("partly-visible");
  142. populateResults();
  143. }
  144. else {
  145. searchField.removeClass("partly-visible");
  146. $("div#advanced-search").fadeOut(50);
  147. advancedSearchButton.removeClass("clicked");
  148. }
  149. }
  150. /*
  151. * Removes any child elements of `div#results`.
  152. */
  153. function clearResults(){
  154. while(resultsDiv.firstChild)
  155. resultsDiv.removeChild(resultsDiv.firstChild);
  156. }
  157. /*
  158. * Query the server with the current search string, and populate `div#results`
  159. * with its response.
  160. */
  161. function populateResults(){
  162. searchResultsPage = 1;
  163. var results = queryServer();
  164. for(var result = 0; result < results.length; result++){
  165. var newDiv = results[result];
  166. resultsDiv.appendChild(newDiv);
  167. setTimeout(
  168. (function(divReference){
  169. return function(){
  170. divReference.classList.add("cascade");
  171. };
  172. }(newDiv)), result * 20);
  173. }
  174. }
  175. /*
  176. * Create a result element based upon a codelet instance.
  177. *
  178. * @return {Element} The result element.
  179. */
  180. function createResult(codelet) {
  181. //Level 1
  182. var newDiv = document.createElement("div"),
  183. table = document.createElement("table"),
  184. row = document.createElement("tr");
  185. //Level 2
  186. var displayInfo = document.createElement("div"),
  187. codeElt = document.createElement("td"),
  188. hiddenInfoContainer = document.createElement("td"),
  189. hiddenInfo = document.createElement("div"),
  190. cycle = document.createElement("div");
  191. //Level 3
  192. var title = document.createElement("span"),
  193. site = document.createElement("span"),
  194. nextMatch = document.createElement("a"),
  195. prevMatch = document.createElement("a"),
  196. dateModified = document.createElement("div"),
  197. language = document.createElement("div"),
  198. dateCreated = document.createElement("div"),
  199. authors = document.createElement("div");
  200. //Classes and ID's
  201. newDiv.classList.add('result');
  202. displayInfo.id = 'display-info';
  203. codeElt.id = 'code';
  204. hiddenInfo.id = 'hidden-info';
  205. cycle.id = 'cycle-matches'
  206. title.id = 'title';
  207. site.id = 'site';
  208. nextMatch.id = 'next-match';
  209. nextMatch.href = '#';
  210. prevMatch.id = 'prev-match';
  211. prevMatch.href = '#';
  212. dateModified.id = 'date-modified';
  213. language.id = 'language';
  214. dateCreated.id = 'date-created';
  215. authors.id = 'authors';
  216. //Add the bulk of the html
  217. title.innerHTML = ' &raquo; <a href="' + codelet.url + '">'
  218. + codelet.filename + '</a>';
  219. site.innerHTML = '<a href="' + codelet.origin[1] + '">' + codelet.origin[0] +'</a>';
  220. nextMatch.innerHTML = 'next match';
  221. prevMatch.innerHTML = 'prev match';
  222. language.innerHTML = 'Language: <span>' + codelet.language + '</span>';
  223. dateModified.innerHTML = 'Last modified: <span>' + codelet.date_modified + '</span>';
  224. // Needs to be changed from int to string on the server
  225. dateCreated.innerHTML = 'Created: <span>' + codelet.date_created + '</span>';
  226. var authorsHtml = 'Authors: <span>';
  227. codelet.authors.forEach(function(a, i) {
  228. if (i == codelet.authors.length - 1)
  229. authorsHtml += '<a href=#>' + a + ' </a>';
  230. else
  231. authorsHtml += '<a href=#>' + a + ' </a>, ';
  232. });
  233. authors.innerHTML = authorsHtml;
  234. // Needs to be processed on the server
  235. codeElt.innerHTML = '<div id=tablecontainer>' + codelet.html_code + '</div>';
  236. var matches = codeElt.querySelectorAll('.hll');
  237. $.each(matches, function(i, a) {
  238. a.id = 'match_' + i;
  239. });
  240. //Event binding
  241. $(codeElt).hover(function(e) {
  242. $(row).addClass('display-all');
  243. });
  244. $(newDiv).on('transitionend', function(e) {
  245. $(codeElt).one('mouseleave', function(e) {
  246. $(row).removeClass('display-all');
  247. });
  248. });
  249. var cur_match = -1;
  250. var newMatch = function(e) {
  251. var $code = $(newDiv).find('#tablecontainer'),
  252. $match = $code.find('#match_' + cur_match);
  253. $code.scrollTop($code.scrollTop() - $code.height() / 2 +
  254. $match.position().top + $match.height() / 2);
  255. $match.effect("highlight", {}, 750)
  256. }
  257. $(nextMatch).click(function(e) {
  258. e.stopPropagation()
  259. e.preventDefault()
  260. cur_match = cur_match >= matches.length - 1 ? 0 : cur_match + 1;
  261. newMatch();
  262. });
  263. $(prevMatch).click(function(e) {
  264. e.stopPropagation()
  265. cur_match = cur_match <= 0 ? matches.length - 1 : cur_match - 1;
  266. newMatch();
  267. });
  268. //Finish and append elements to parent elements
  269. hiddenInfo.appendChild(dateCreated);
  270. hiddenInfo.appendChild(dateModified);
  271. hiddenInfo.appendChild(language);
  272. hiddenInfo.appendChild(authors);
  273. hiddenInfoContainer.appendChild(hiddenInfo);
  274. row.appendChild(codeElt);
  275. row.appendChild(hiddenInfoContainer);
  276. table.appendChild(row);
  277. displayInfo.appendChild(site);
  278. displayInfo.appendChild(title);
  279. cycle.appendChild(prevMatch);
  280. cycle.appendChild(nextMatch);
  281. newDiv.appendChild(displayInfo);
  282. newDiv.appendChild(table);
  283. newDiv.appendChild(cycle);
  284. return newDiv;
  285. }
  286. /*
  287. * AJAX the current query string to the server, and return its response.
  288. *
  289. * @return {Array} The server's response in the form of `div.result` DOM
  290. * elements, to fill `div#results`.
  291. */
  292. function queryServer(){
  293. var queryUrl = document.URL + "search.json?" + $.param({
  294. "q" : searchBar.value,
  295. "p" : searchResultsPage++
  296. });
  297. console.log(queryUrl);
  298. var result = $.getJSON(queryUrl, function(result){
  299. $.each(result, function(key, value){
  300. if(key == "error")
  301. errorMessage(value);
  302. else
  303. console.log("Success.");
  304. });
  305. });
  306. // return [];
  307. var resultDivs = [];
  308. for(var result = 0; result < 20; result++){
  309. var newDiv = createResult(testCodelet);
  310. resultDivs.push(newDiv)
  311. }
  312. return resultDivs;
  313. }
  314. /*
  315. * Adds more results to `div#results`.
  316. */
  317. function loadMoreResults(){
  318. var results = queryServer();
  319. for(var result = 0; result < results.length; result++){
  320. var newDiv = results[result];
  321. resultsDiv.appendChild(newDiv);
  322. setTimeout(
  323. (function(divReference){
  324. return function(){
  325. divReference.classList.add("cascade");
  326. };
  327. }(newDiv)),
  328. result * 20);
  329. }
  330. }
  331. /*
  332. * Displays a warning message in the UI.
  333. *
  334. * @param msg (str) The message string.
  335. */
  336. function errorMessage(msg){
  337. alert(msg);
  338. }
  339. loadMoreResults();