Hugo Dark Theme Site Generator https://after-dark.habd.as
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.html 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. {{ define "title" -}}
  2. {{ .Title }} | {{ .Site.Title }}
  3. {{- end }}
  4. {{ define "header" }}
  5. {{ partial "menu.html" . }}
  6. {{ end }}
  7. {{ define "main" }}
  8. <header>
  9. <h1>{{ .Title }}</h1>
  10. </header>
  11. <noscript>
  12. Aw-shucks! Search requires use of JavaScript to function properly.
  13. </noscript>
  14. <div id="search-app">
  15. <div v-cloak>
  16. <section>
  17. <form v-on:submit.prevent class="form" action="{{ "search" | absURL }}">
  18. <fieldset class="form-group">
  19. <input v-model="query" id="query" name="s" type="search" class="form-control" maxlength="32" autocomplete="off">
  20. <div class="help-block">Press <kbd>s</kbd> to focus search input anytime.</div>
  21. </fieldset>
  22. </form>
  23. </section>
  24. <section v-if="results.length">
  25. <p><i>Showing results for “{ resultsForSearch }”.</i></p>
  26. <div id="search-results">
  27. <article v-for="result in results" itemscope itemtype="http://schema.org/CreativeWork">
  28. <header itemprop="name">
  29. <h2 itemprop="name"><a :href="result.item.url"><span v-html="result.item.title"></span></a></h2>
  30. </header>
  31. <div v-html=result.item.summary itemprop="description"></div>
  32. <nav class="readmore"><p><a itemprop="url" :href="result.item.url">Read More&nbsp;&raquo;</a></p></nav>
  33. </article>
  34. </div>
  35. </section>
  36. </div>
  37. </div>
  38. {{ end }}
  39. {{ define "footer" }}
  40. {{ partial "powered-by.html" . }}
  41. <script>
  42. fetchInject([
  43. {{ "/js/vue.min.js" | relURL }},
  44. {{ "/js/lodash.custom.min.js" | relURL }},
  45. {{ "/js/fuse.min.js" | relURL }},
  46. {{ "/js/mark.min.js" | relURL }}
  47. ]).then(() => {
  48. (function (window, document, undefined) {
  49. "use strict";
  50. const getQueryByParam = param => decodeURIComponent(
  51. (location.search.split(param + '=')[1] || '').split('&')[0]
  52. ).replace(/\+/g, ' ');
  53. const queryParam = 's';
  54. const selectors = {
  55. appContainer: '#search-app',
  56. resultContainer: '#search-results',
  57. searchInput: '#query'
  58. };
  59. const fuseOpts = {
  60. shouldSort: true,
  61. tokenize: true,
  62. matchAllTokens: true,
  63. includeScore: true,
  64. includeMatches: true,
  65. keys: [
  66. { name: "title", weight: 0.8 },
  67. { name: "contents", weight: 0.5 },
  68. { name: "tags", weight: 0.3 },
  69. { name: "categories", weight: 0.3 }
  70. ]
  71. };
  72. const getSearchInput = () => document.querySelector(selectors.searchInput);
  73. const focusSearchInput = () => getSearchInput().focus();
  74. const searchQuery = getSearchInput().value = getQueryByParam(queryParam);
  75. const fuse = new Fuse([], fuseOpts);
  76. window.fetch('/index.json').then(response => {
  77. response.text().then(searchData => {
  78. fuse.setCollection(JSON.parse(searchData));
  79. searchQuery && search(searchQuery);
  80. });
  81. });
  82. const getUrl = (query) => {
  83. const encodedQuery = encodeURIComponent(query);
  84. const url = {{ .URL }};
  85. return (encodedQuery)
  86. ? `${url}?${queryParam}=${encodedQuery}`
  87. : url;
  88. };
  89. let mark = new Mark(
  90. document.querySelector(
  91. selectors.resultContainer
  92. )
  93. );
  94. const app = new Vue({
  95. delimiters: ['{', '}'],
  96. el: selectors.appContainer,
  97. data: {
  98. fuse: null,
  99. results: [],
  100. query: getQueryByParam(queryParam),
  101. resultsForSearch: getQueryByParam(queryParam)
  102. },
  103. mounted () {
  104. this.fuse = fuse;
  105. window.onpopstate = (evt) => {
  106. this.query = evt.state.query;
  107. };
  108. document.onkeyup = function (evt) {
  109. evt.key === 's' && focusSearchInput();
  110. }
  111. focusSearchInput();
  112. },
  113. watch: {
  114. query () {
  115. this.executeSearch();
  116. window.history.replaceState(
  117. {query: this.query},
  118. null,
  119. getUrl(this.query)
  120. );
  121. }
  122. },
  123. beforeUpdate: function () {
  124. mark.unmark();
  125. },
  126. updated: function () {
  127. this.$nextTick(function () {
  128. mark = new Mark(
  129. document.querySelector(
  130. selectors.resultContainer
  131. )
  132. )
  133. mark.mark(this.query.trim());
  134. })
  135. },
  136. methods: {
  137. executeSearch: _.debounce(function () {
  138. const trimmedQuery = this.query.trim();
  139. this.resultsForSearch = trimmedQuery;
  140. this.results = (trimmedQuery)
  141. ? this.fuse.search(trimmedQuery)
  142. : [];
  143. }, 250)
  144. }
  145. });
  146. const search = query => {
  147. app.results = fuse.search(query);
  148. };
  149. })(window, document);
  150. });
  151. </script>
  152. {{ end }}