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.

пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
пре 10 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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, scrollTimer, lastValue;
  11. var searchResultsPage = 1;
  12. /*
  13. * Set all page callbacks.
  14. */
  15. (function setHomePageCallbabacks(){
  16. var results = $('#results').get(0);
  17. // Enable infinite scrolling down the results page.
  18. $(window).scroll(function(){
  19. if($(window).scrollTop() + $(window).height() == $(document).height() &&
  20. resultsDiv.querySelectorAll(".result").length > 0)
  21. loadMoreResults();
  22. clearTimeout(scrollTimer);
  23. if (!results.classList.contains('disable-hover'))
  24. results.classList.add('disable-hover')
  25. scrollTimer = setTimeout(function(){
  26. if (results.classList.contains('disable-hover'))
  27. results.classList.remove('disable-hover');
  28. }, 200);
  29. });
  30. // Toggle the advanced-search form's visibility.
  31. advancedSearchButton.click(function(){
  32. var searchField = $("div#search-field");
  33. if(!advancedSearchDiv.hasClass("visible")){
  34. searchField.addClass("partly-visible");
  35. advancedSearchDiv.fadeIn(500).addClass("visible");
  36. advancedSearchButton.addClass("clicked");
  37. }
  38. else {
  39. advancedSearchDiv.hide().removeClass("visible");
  40. advancedSearchButton.removeClass("clicked");
  41. if($("div#results .result").length == 0)
  42. searchField.removeClass("partly-visible");
  43. }
  44. });
  45. // Enable capturing the `enter` key.
  46. $("form#search-bar").submit(function(event){
  47. event.preventDefault();
  48. return false;
  49. });
  50. searchBar.onkeyup = typingTimer;
  51. }());
  52. /*
  53. * Set keyboard shortcut mappings.
  54. */
  55. (function resultsHotkeys(){
  56. /*
  57. * If the currently viewed result is not the first, scroll to the previous
  58. * result.
  59. */
  60. var previousResult = function(){
  61. var currResult = $(".display-all");
  62. if(currResult.length) {
  63. currResult.removeClass("display-all");
  64. currResult = currResult.closest(".result").prev(".result");
  65. } else {
  66. currResult = $(document.querySelectorAll(".result")[0]);
  67. }
  68. currResult.addClass("display-all");
  69. currResult.each(function(){
  70. $('html,body').stop().animate({
  71. scrollTop: $(this).offset().top - (
  72. $(window).height() - $(this).outerHeight(true)) / 2
  73. }, 140);
  74. });
  75. };
  76. /*
  77. * If the currently viewed result is not the last, scroll to the next
  78. * result.
  79. */
  80. var nextResult = function(){
  81. var currResult = $(".display-all");
  82. if(currResult.length) {
  83. currResult.removeClass("display-all");
  84. currResult = currResult.closest(".result").next(".result");
  85. } else {
  86. currResult = $(document.querySelectorAll(".result")[0]);
  87. }
  88. currResult.addClass('display-all');
  89. currResult.each(function(){
  90. $('html,body').stop().animate({
  91. scrollTop: $(this).offset().top - (
  92. $(window).height() - $(this).outerHeight(true)) / 2
  93. }, 140);
  94. });
  95. };
  96. var displayHotkeyHelp = function(){
  97. var help = $("div#hotkey-help");
  98. if(help.hasClass("hidden"))
  99. help.fadeIn(420);
  100. else
  101. help.fadeOut(420);
  102. $("div#body").toggleClass("faded");
  103. help.toggleClass("hidden");
  104. }
  105. var hotkeyActions = {
  106. "k" : previousResult,
  107. "j" : nextResult,
  108. "h" : previousSymbolMatch,
  109. "l" : nextSymbolMatch,
  110. "?" : displayHotkeyHelp
  111. };
  112. $(window).keypress(function(key){
  113. for(var hotkey in hotkeyActions){
  114. var keyChar = String.fromCharCode(key.keyCode);
  115. if(keyChar == hotkey &&
  116. !($(key.target).is("textarea") || $(key.target).is("input")))
  117. hotkeyActions[keyChar]();
  118. }
  119. });
  120. }());
  121. //Obtained by parsing python file with pygments
  122. 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">deaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabug</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>\n</span></pre></div>\n</td></tr></table>'
  123. var testCodelet = {
  124. 'url': 'https://github.com/earwig/bitshift/blob/develop/app.py',
  125. 'name': 'app.py',
  126. 'lang': 'python',
  127. 'created': 'May 10, 2014 aaaaaaaaaa',
  128. 'modified': '2 days ago',
  129. 'origin': ['GitHub', 'https://github.com', ''],
  130. 'authors': ['sevko', 'earwig', 'another author', 'a'],
  131. 'code': codeExample
  132. };
  133. // Enable infinite scrolling down the results page.
  134. $(window).scroll(function() {
  135. var searchField = $("div#search-field");
  136. if($(window).scrollTop() + $(window).height() == $(document).height() &&
  137. searchField.hasClass('partly-visible')){
  138. loadMoreResults();
  139. }
  140. });
  141. /*
  142. * Clear the existing timer and set a new one the the user types text into the
  143. * search bar.
  144. */
  145. function typingTimer(event){
  146. clearTimeout(typingTimer);
  147. var enterKeyCode = 13;
  148. if(event.keyCode != enterKeyCode){
  149. if(lastValue != searchBar.value)
  150. typingTimer = setTimeout(finishedTyping, FINISH_TYPING_INTERVAL);
  151. }
  152. else {
  153. event.preventDefault();
  154. finishedTyping();
  155. return false;
  156. }
  157. };
  158. /*
  159. * Callback which queries the server whenver the user stops typing.
  160. *
  161. * Whenever the user doesn't type for a `FINISH_TYPING_INTERVAL` after having
  162. * entered new text in the search bar, send the current query request to the
  163. * server.
  164. */
  165. function finishedTyping(){
  166. lastValue = searchBar.value;
  167. var searchField = $("div#search-field");
  168. clearResults();
  169. if(searchBar.value){
  170. searchField.addClass("partly-visible");
  171. populateResults();
  172. }
  173. else {
  174. searchField.removeClass("partly-visible");
  175. $("div#advanced-search").fadeOut(50);
  176. advancedSearchButton.removeClass("clicked");
  177. }
  178. }
  179. /*
  180. * Removes any child elements of `div#results`.
  181. */
  182. function clearResults(){
  183. while(resultsDiv.firstChild)
  184. resultsDiv.removeChild(resultsDiv.firstChild);
  185. }
  186. /*
  187. * Create a result element based upon a codelet instance.
  188. *
  189. * @return {Element} The result element.
  190. */
  191. function createResult(codelet) {
  192. var maxAttributeLength = 20;
  193. //Level 1
  194. var newDiv = document.createElement("div"),
  195. table = document.createElement("table"),
  196. row = document.createElement("tr");
  197. //Level 2
  198. var displayInfo = document.createElement("div"),
  199. codeElt = document.createElement("td"),
  200. hiddenInfoContainer = document.createElement("td"),
  201. hiddenInfo = document.createElement("div"),
  202. cycle = document.createElement("div");
  203. //Level 3
  204. var title = document.createElement("span"),
  205. site = document.createElement("span"),
  206. nextMatch = document.createElement("a"),
  207. prevMatch = document.createElement("a"),
  208. dateModified = document.createElement("div"),
  209. language = document.createElement("div"),
  210. dateCreated = document.createElement("div"),
  211. authors = document.createElement("div");
  212. //Classes and ID's
  213. newDiv.classList.add('result');
  214. displayInfo.id = 'display-info';
  215. codeElt.id = 'code';
  216. hiddenInfo.id = 'hidden-info';
  217. cycle.id = 'cycle-matches'
  218. title.id = 'title';
  219. site.id = 'site';
  220. nextMatch.id = 'next-match';
  221. nextMatch.href = '#';
  222. prevMatch.id = 'prev-match';
  223. prevMatch.href = '#';
  224. dateModified.id = 'date-modified';
  225. language.id = 'language';
  226. dateCreated.id = 'date-created';
  227. authors.id = 'authors';
  228. //Add the bulk of the html
  229. title.innerHTML = ' &raquo; <a href="' + codelet.url + '">'
  230. + codelet.name + '</a>';
  231. site.innerHTML = '<a href="' + codelet.origin[1] + '">' +
  232. codelet.origin[0] +'</a>';
  233. nextMatch.innerHTML = 'next match';
  234. prevMatch.innerHTML = 'prev match';
  235. language.innerHTML = 'Language: <span>' + codelet.lang + '</span>';
  236. dateModified.innerHTML = 'Last modified: <span>' + codelet.modified +
  237. '</span>';
  238. // Needs to be changed from int to string on the server
  239. dateCreated.innerHTML = 'Created: <span>' +
  240. codelet.created.substring(0, maxAttributeLength) + '</span>';
  241. var authorsHtml = 'Authors: <span>';
  242. var currLength = 0;
  243. var authorsList = [];
  244. for(var auth = 0; auth < codelet.authors.length; auth++){
  245. currLength += codelet.authors[auth].length;
  246. if(maxAttributeLength < currLength){
  247. authorsList.push("...");
  248. break;
  249. }
  250. else
  251. authorsList.push('<a href=#>' + codelet.authors[auth] + '</a>');
  252. }
  253. authors.innerHTML = "Authors: <span>" + authorsList.join(", ") + "</span>";
  254. // Needs to be processed on the server
  255. codeElt.innerHTML = '<div id=tablecontainer>' + codelet.code + '</div>';
  256. //Event binding
  257. $(newDiv).on('mousemove', function(e) {
  258. var holdCondition = $('.disable-hover');
  259. if(holdCondition.length == 0) {
  260. $(this).siblings().removeClass('display-all');
  261. $(this).addClass('display-all');
  262. }
  263. });
  264. $(newDiv).on('mouseleave', function(e) {
  265. var holdCondition = $('.disable-hover');
  266. if(holdCondition.length == 0)
  267. $(this).removeClass('display-all');
  268. });
  269. $(nextMatch).click(function(e) {
  270. e.stopPropagation();
  271. e.preventDefault();
  272. nextSymbolMatch();
  273. });
  274. $(prevMatch).click(function(e) {
  275. e.stopPropagation();
  276. e.preventDefault();
  277. previousSymbolMatch();
  278. });
  279. //Finish and append elements to parent elements
  280. hiddenInfo.appendChild(dateCreated);
  281. hiddenInfo.appendChild(dateModified);
  282. hiddenInfo.appendChild(language);
  283. hiddenInfo.appendChild(authors);
  284. hiddenInfoContainer.appendChild(hiddenInfo);
  285. row.appendChild(codeElt);
  286. row.appendChild(hiddenInfoContainer);
  287. table.appendChild(row);
  288. displayInfo.appendChild(site);
  289. displayInfo.appendChild(title);
  290. cycle.appendChild(prevMatch);
  291. cycle.appendChild(nextMatch);
  292. newDiv.appendChild(displayInfo);
  293. newDiv.appendChild(table);
  294. return newDiv;
  295. }
  296. function previousSymbolMatch() {
  297. var currResult = $(".display-all"),
  298. currMatch = currResult.find(".hll.current"),
  299. matches = currResult.find(".hll"),
  300. scrollDiv = currResult.find("#tablecontainer");
  301. if (currMatch.length == 0)
  302. currMatch = matches[0];
  303. else
  304. currMatch.removeClass('current');
  305. var index = matches.index(currMatch.get(0)) - 1;
  306. index = index <= 0 ? matches.length - 1 : index;
  307. var newMatch = $(matches[index]);
  308. scrollDiv.scrollTop(scrollDiv.scrollTop()
  309. - scrollDiv.height() / 2
  310. + newMatch.position().top + newMatch.height() / 2);
  311. newMatch.effect("highlight", {color: '#FFF'}, 750)
  312. newMatch.addClass('current');
  313. };
  314. function nextSymbolMatch() {
  315. var currResult = $(".display-all"),
  316. currMatch = currResult.find(".hll.current"),
  317. matches = currResult.find(".hll"),
  318. scrollDiv = currResult.find("#tablecontainer");
  319. if (currMatch.length == 0)
  320. currMatch = $(matches[0]);
  321. else
  322. currMatch.removeClass("current");
  323. var index = matches.index(currMatch.get(0)) + 1;
  324. index = index >= matches.length ? 0 : index;
  325. var newMatch = $(matches[index]);
  326. scrollDiv.scrollTop(scrollDiv.scrollTop()
  327. - scrollDiv.height() / 2
  328. + newMatch.position().top + newMatch.height() / 2);
  329. newMatch.effect("highlight", {color: "#FFF"}, 750)
  330. newMatch.addClass("current");
  331. };
  332. /*
  333. * AJAX the current query string to the server, and return its response.
  334. *
  335. * @return {Array} The server's response in the form of `div.result` DOM
  336. * elements, to fill `div#results`.
  337. */
  338. function queryServer(){
  339. var queryUrl = document.URL + "search.json?" + $.param({
  340. "q" : searchBar.value,
  341. "p" : searchResultsPage++
  342. });
  343. var results = $.Deferred();
  344. $.getJSON(queryUrl, function(result){
  345. var resultDivs = [];
  346. if("error" in result)
  347. insertErrorMessage(result["error"]);
  348. else if(result["results"].length == 0)
  349. insertErrorMessage("No search results.");
  350. else
  351. for(var codelet = 0; codelet < result["results"].length; codelet++)
  352. resultDivs.push(createResult(result["results"][codelet]));
  353. results.resolve(resultDivs);
  354. });
  355. return results;
  356. }
  357. /*
  358. * Query the server with the current search string, and populate `div#results`
  359. * with its response.
  360. */
  361. function populateResults(){
  362. searchResultsPage = 1;
  363. loadMoreResults();
  364. }
  365. /*
  366. * Query the server for the next results page, and add its codelets to
  367. * `div#results`.
  368. */
  369. function loadMoreResults(){
  370. queryServer().done(function(results){
  371. for(var result = 0; result < results.length; result++){
  372. var newDiv = results[result];
  373. resultsDiv.appendChild(newDiv);
  374. setTimeout(
  375. (function(divReference){
  376. return function(){
  377. divReference.classList.add("cascade");
  378. };
  379. }(newDiv)),
  380. result * 20);
  381. }
  382. });
  383. }
  384. /*
  385. * Displays a warning message in the UI.
  386. *
  387. * @param msg (str) The message string.
  388. */
  389. function insertErrorMessage(msg){
  390. var error = $(
  391. [
  392. "<div id='error'><span id='s1'>Error</span> ",
  393. "<span id='s2'>&raquo;</span> </div>"
  394. ].join(""));
  395. error.append(msg);
  396. resultsDiv.appendChild(error[0]);
  397. }