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.

145 lines
4.2 KiB

4 years ago
  1. /*!
  2. * Copyright (C) 2019 Josh Habdas <jhabdas@protonmail.com>
  3. *
  4. * This file is part of After Dark.
  5. *
  6. * After Dark is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * After Dark is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. fetchInject([
  20. "{{ "/js/vue.min.js" | relURL }}",
  21. "{{ "/js/lodash.custom.min.js" | relURL }}",
  22. "{{ "/js/fuse.min.js" | relURL }}",
  23. "{{ "/js/mark.min.js" | relURL }}"
  24. ]).then(() => {
  25. (function (window, document, undefined) {
  26. 'use strict';
  27. const getQueryByParam = param => decodeURIComponent(
  28. (location.search.split(param + '=')[1] || '').split('&')[0]
  29. ).replace(/\+/g, ' ');
  30. const queryParam = 's';
  31. const hotkeys = {{ (.Params.form.hotkeys | default (slice "/" "s")) | jsonify }};
  32. const selectors = {
  33. appContainer: '#search-app',
  34. resultContainer: '#search-results',
  35. searchInput: '#query'
  36. };
  37. const fuseOpts = {
  38. shouldSort: true,
  39. tokenize: true,
  40. matchAllTokens: true,
  41. includeScore: true,
  42. includeMatches: true,
  43. keys: [
  44. { name: "title", weight: 0.8 },
  45. { name: "contents", weight: 0.5 },
  46. { name: "tags", weight: 0.3 },
  47. { name: "categories", weight: 0.3 }
  48. ]
  49. };
  50. const getSearchInput = () => document.querySelector(selectors.searchInput);
  51. const focusSearchInput = () => getSearchInput().focus();
  52. const searchQuery = getSearchInput().value = getQueryByParam(queryParam);
  53. const fuse = new Fuse([], fuseOpts);
  54. window.fetch('/index.json').then(response => {
  55. response.text().then(searchData => {
  56. fuse.setCollection(JSON.parse(searchData));
  57. searchQuery && search(searchQuery);
  58. });
  59. });
  60. const getUrl = (query) => {
  61. const encodedQuery = encodeURIComponent(query);
  62. const url = "{{ .RelPermalink }}";
  63. return (encodedQuery)
  64. ? `${url}?${queryParam}=${encodedQuery}`
  65. : url;
  66. };
  67. let mark = new Mark(
  68. document.querySelector(
  69. selectors.resultContainer
  70. )
  71. );
  72. const app = new Vue({
  73. delimiters: ['{', '}'],
  74. el: selectors.appContainer,
  75. data: {
  76. fuse: null,
  77. results: [],
  78. query: getQueryByParam(queryParam),
  79. resultsForSearch: getQueryByParam(queryParam)
  80. },
  81. mounted () {
  82. this.fuse = fuse;
  83. window.onpopstate = (evt) => {
  84. this.query = evt.state.query;
  85. };
  86. const searchInput = getSearchInput();
  87. document.onkeydown = function (evt) {
  88. if (evt.target === searchInput) return;
  89. if (hotkeys.includes(evt.key)) {
  90. evt.preventDefault();
  91. focusSearchInput();
  92. getSearchInput().select();
  93. };
  94. }
  95. focusSearchInput();
  96. },
  97. watch: {
  98. query () {
  99. this.executeSearch();
  100. window.history.replaceState(
  101. {query: this.query},
  102. null,
  103. getUrl(this.query)
  104. );
  105. }
  106. },
  107. beforeUpdate: function () {
  108. mark.unmark();
  109. },
  110. updated: function () {
  111. this.$nextTick(function () {
  112. mark = new Mark(
  113. document.querySelector(
  114. selectors.resultContainer
  115. )
  116. )
  117. mark.mark(this.query.trim());
  118. })
  119. },
  120. methods: {
  121. executeSearch: _.debounce(function () {
  122. const trimmedQuery = this.query.trim();
  123. this.resultsForSearch = trimmedQuery;
  124. this.results = (trimmedQuery)
  125. ? this.fuse.search(trimmedQuery)
  126. : [];
  127. }, 250)
  128. }
  129. });
  130. const search = query => {
  131. app.results = fuse.search(query);
  132. };
  133. })(window, document);
  134. });