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.

search.js 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /* global Fuse, Mark */
  2. function ready(fn) {
  3. if (document.readyState !== 'loading') {
  4. fn();
  5. } else {
  6. document.addEventListener('DOMContentLoaded', fn);
  7. }
  8. }
  9. ready(doSearch);
  10. const summaryInclude = 60;
  11. const fuseOptions = {
  12. shouldSort: true,
  13. includeMatches: true,
  14. matchAllTokens: true,
  15. threshold: 0, // for parsing diacritics
  16. tokenize: true,
  17. location: 0,
  18. distance: 100,
  19. maxPatternLength: 32,
  20. minMatchCharLength: 1,
  21. keys: [{
  22. name: 'title',
  23. weight: 0.8
  24. },
  25. {
  26. name: 'contents',
  27. weight: 0.5
  28. },
  29. {
  30. name: 'tags',
  31. weight: 0.3
  32. },
  33. {
  34. name: 'categories',
  35. weight: 0.3
  36. }
  37. ]
  38. };
  39. function param(name) {
  40. return decodeURIComponent((window.location.search.split(`${name}=`)[1] || '').split('&')[0]).replace(/\+/g, ' ');
  41. }
  42. const searchQuery = param('s');
  43. function doSearch() {
  44. if (searchQuery) {
  45. document.getElementById('search-query').value = searchQuery;
  46. executeSearch(searchQuery);
  47. } else {
  48. const para = document.createElement('P');
  49. para.textContent = 'Please enter a word or phrase above';
  50. document.getElementById('search-results').appendChild(para);
  51. }
  52. }
  53. function getJSON(url, fn) {
  54. const request = new XMLHttpRequest();
  55. request.open('GET', url, true);
  56. request.addEventListener('load', () => {
  57. if (request.status >= 200 && request.status < 400) {
  58. const data = JSON.parse(request.responseText);
  59. fn(data);
  60. } else {
  61. console.error(`Target reached on ${url} with error ${request.status}`);
  62. }
  63. });
  64. request.addEventListener('error', () => {
  65. console.error(`Connection error ${request.status}`);
  66. });
  67. request.send();
  68. }
  69. function executeSearch(searchQuery) {
  70. getJSON(`/${document.LANG}/index.json`, (data) => {
  71. const pages = data;
  72. const fuse = new Fuse(pages, fuseOptions);
  73. const result = fuse.search(searchQuery);
  74. document.getElementById('search-results').innerHTML = '';
  75. if (result.length > 0) {
  76. populateResults(result);
  77. } else {
  78. const para = document.createElement('P');
  79. para.textContent = 'No matches found';
  80. document.getElementById('search-results').appendChild(para);
  81. }
  82. });
  83. }
  84. function populateResults(result) {
  85. for (const [key, value] of result.entries()) {
  86. const content = value.item.contents;
  87. let snippet = '';
  88. const snippetHighlights = [];
  89. if (fuseOptions.tokenize) {
  90. snippetHighlights.push(searchQuery);
  91. for (const mvalue of value.matches) {
  92. if (mvalue.key === 'tags' || mvalue.key === 'categories') {
  93. snippetHighlights.push(mvalue.value);
  94. } else if (mvalue.key === 'contents') {
  95. const ind = content.toLowerCase().indexOf(searchQuery.toLowerCase());
  96. const start = ind - summaryInclude > 0 ? ind - summaryInclude : 0;
  97. const end = ind + searchQuery.length + summaryInclude < content.length ? ind + searchQuery.length + summaryInclude : content.length;
  98. snippet += content.substring(start, end);
  99. if (ind > -1) {
  100. snippetHighlights.push(content.substring(ind, ind + searchQuery.length));
  101. } else {
  102. snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1));
  103. }
  104. }
  105. }
  106. }
  107. if (snippet.length < 1) {
  108. snippet += content.substring(0, summaryInclude * 2);
  109. }
  110. // pull template from hugo template definition
  111. const templateDefinition = document.getElementById('search-result-template').innerHTML;
  112. // replace values
  113. const output = render(templateDefinition, {
  114. key,
  115. title: value.item.title,
  116. link: value.item.permalink,
  117. tags: value.item.tags,
  118. categories: value.item.categories,
  119. snippet
  120. });
  121. document.getElementById('search-results').appendChild(htmlToElement(output));
  122. for (const snipvalue of snippetHighlights) {
  123. new Mark(document.getElementById(`summary-${key}`)).mark(snipvalue);
  124. }
  125. }
  126. }
  127. function render(templateString, data) {
  128. let conditionalMatches, copy;
  129. const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g;
  130. // since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop
  131. copy = templateString;
  132. while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
  133. if (data[conditionalMatches[1]]) {
  134. // valid key, remove conditionals, leave content.
  135. copy = copy.replace(conditionalMatches[0], conditionalMatches[2]);
  136. } else {
  137. // not valid, remove entire section
  138. copy = copy.replace(conditionalMatches[0], '');
  139. }
  140. }
  141. templateString = copy;
  142. // now any conditionals removed we can do simple substitution
  143. let key, find, re;
  144. for (key of Object.keys(data)) {
  145. find = `\\$\\{\\s*${key}\\s*\\}`;
  146. re = new RegExp(find, 'g');
  147. templateString = templateString.replace(re, data[key]);
  148. }
  149. return templateString;
  150. }
  151. /**
  152. * By Mark Amery: https://stackoverflow.com/a/35385518
  153. * @param {String} HTML representing a single element
  154. * @return {Element}
  155. */
  156. function htmlToElement(html) {
  157. const template = document.createElement('template');
  158. html = html.trim(); // Never return a text node of whitespace as the result
  159. template.innerHTML = html;
  160. return template.content.firstChild;
  161. }