search.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /*************************************************
  2. * Academic: the website framework for Hugo.
  3. * https://github.com/gcushen/hugo-academic
  4. **************************************************/
  5. /* ---------------------------------------------------------------------------
  6. * Configuration.
  7. * --------------------------------------------------------------------------- */
  8. // Configure Fuse.
  9. let fuseOptions = {
  10. shouldSort: true,
  11. includeMatches: true,
  12. tokenize: true,
  13. threshold: 0.0,
  14. location: 0,
  15. distance: 100,
  16. maxPatternLength: 32,
  17. minMatchCharLength: 2,
  18. keys: [
  19. {name:'title', weight:0.8},
  20. {name:'summary', weight:0.6},
  21. {name:'content', weight:0.5},
  22. {name:'tags', weight:0.3}
  23. ]
  24. };
  25. // Configure summary.
  26. let summaryLength = 60;
  27. /* ---------------------------------------------------------------------------
  28. * Functions.
  29. * --------------------------------------------------------------------------- */
  30. // Get query from URI.
  31. function getSearchQuery(name) {
  32. return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' ');
  33. }
  34. // Set query in URI without reloading the page.
  35. function updateURL(url) {
  36. if (history.pushState) {
  37. window.history.pushState({path:url}, '', url);
  38. }
  39. }
  40. // Pre-process new search query.
  41. function initSearch(force, fuse) {
  42. let query = $("#search-query").val();
  43. // If query deleted, clear results.
  44. if ( query.length < 1)
  45. $('#search-hits').empty();
  46. // Check for timer event (enter key not pressed) and query less than minimum length required.
  47. if (!force && query.length < fuseOptions.minMatchCharLength)
  48. return;
  49. // Do search.
  50. $('#search-hits').empty();
  51. searchAcademic(query, fuse);
  52. let newURL = window.location.protocol + "//" + window.location.host + window.location.pathname + '?q=' + encodeURIComponent(query) + window.location.hash;
  53. updateURL(newURL);
  54. }
  55. // Perform search.
  56. function searchAcademic(query, fuse) {
  57. let results = fuse.search(query);
  58. // console.log({"results": results});
  59. if (results.length > 0) {
  60. parseResults(query, results);
  61. } else {
  62. $('#search-hits').append('<div class="search-no-results">' + i18n.no_results + '</div>');
  63. }
  64. }
  65. // Parse search results.
  66. function parseResults(query, results) {
  67. $.each( results, function(key, value) {
  68. let content = value.item.content;
  69. let snippet = "";
  70. let snippetHighlights = [];
  71. if ( fuseOptions.tokenize ) {
  72. snippetHighlights.push(query);
  73. } else {
  74. $.each( value.matches, function(matchKey, matchValue) {
  75. if (matchValue.key == "content") {
  76. let start = (matchValue.indices[0][0]-summaryLength>0) ? matchValue.indices[0][0]-summaryLength : 0;
  77. let end = (matchValue.indices[0][1]+summaryLength<content.length) ? matchValue.indices[0][1]+summaryLength : content.length;
  78. snippet += content.substring(start, end);
  79. snippetHighlights.push(matchValue.value.substring(matchValue.indices[0][0], matchValue.indices[0][1]-matchValue.indices[0][0]+1));
  80. }
  81. });
  82. }
  83. if (snippet.length < 1) {
  84. snippet += content.substring(0, summaryLength*2);
  85. }
  86. // Load template.
  87. var template = $('#search-hit-fuse-template').html();
  88. // Localize content types.
  89. let content_key = value.item.type;
  90. if (content_key in content_type) {
  91. content_key = content_type[content_key];
  92. }
  93. // Parse template.
  94. let templateData = {
  95. key: key,
  96. title: value.item.title,
  97. type: content_key,
  98. relpermalink: value.item.relpermalink,
  99. snippet: snippet
  100. };
  101. let output = render(template, templateData);
  102. $('#search-hits').append(output);
  103. // Highlight search terms in result.
  104. $.each( snippetHighlights, function(hlKey, hlValue){
  105. $("#summary-"+key).mark(hlValue);
  106. });
  107. });
  108. }
  109. function render(template, data) {
  110. // Replace placeholders with their values.
  111. let key, find, re;
  112. for (key in data) {
  113. find = '\\{\\{\\s*' + key + '\\s*\\}\\}'; // Expect placeholder in the form `{{x}}`.
  114. re = new RegExp(find, 'g');
  115. template = template.replace(re, data[key]);
  116. }
  117. return template;
  118. }
  119. /* ---------------------------------------------------------------------------
  120. * Initialize.
  121. * --------------------------------------------------------------------------- */
  122. // Wait for Fuse to initialize.
  123. $.getJSON( search_index_filename, function( search_index ) {
  124. let fuse = new Fuse(search_index, fuseOptions);
  125. // On page load, check for search query in URL.
  126. if (query = getSearchQuery('q')) {
  127. $("#search-query").val(query);
  128. initSearch(true, fuse);
  129. }
  130. // On search box key up, process query.
  131. $('#search-query').keyup(function (e) {
  132. clearTimeout($.data(this, 'searchTimer')); // Ensure only one timer runs!
  133. if (e.keyCode == 13) {
  134. initSearch(true, fuse);
  135. } else {
  136. $(this).data('searchTimer', setTimeout(function () {
  137. initSearch(false, fuse);
  138. }, 250));
  139. }
  140. });
  141. });