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.

176 lines
5.8 KiB

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