Browse Source

fix(layouts/search): unblock blocking scripts and add command terminators

fixes #14
Josh Habdas 1 year ago
parent
commit
7802937091
Signed by: Josh Habdas <jhabdas@protonmail.com> GPG Key ID: B148B31154C75A74
2 changed files with 118 additions and 115 deletions
  1. 10
    10
      README.md
  2. 108
    105
      layouts/_default/search.html

+ 10
- 10
README.md View File

@@ -139,9 +139,9 @@ Reference the Hugo [Syntax Highlighting](https://gohugo.io/content-management/sy
139 139
 
140 140
 ### Fuzzy Search
141 141
 
142
-If a web crawler can find it, so can you. Search for indexable content site-wide using the inbuilt search app built with [Vue](https://vuejs.org/), [Fuse](http://fusejs.io/) and [Mark](https://markjs.io). Searches are fuzzy and typos encouraged.
142
+If a web crawler can find it, so can you. Search for indexable content site-wide using the inbuilt JavaScript search app built with [Vue](https://vuejs.org/), [Fuse](http://fusejs.io/) and [Mark](https://markjs.io). Searches are fuzzy and typos are forgiven.
143 143
 
144
-To use it simply create a section called `search` using the After Dark search layout like so:
144
+To begin using fuzzy search simply create a section called `search` using the After Dark search layout like so:
145 145
 
146 146
 ```
147 147
 └── content
@@ -149,9 +149,9 @@ To use it simply create a section called `search` using the After Dark search la
149 149
         └── _index.md
150 150
 ```
151 151
 
152
-With `_index.md` like:
152
+With an `_index.md` using the search layout:
153 153
 
154
-```
154
+```toml
155 155
 +++
156 156
 title = "Search"
157 157
 layout = "search"
@@ -159,21 +159,21 @@ noindex = true
159 159
 +++
160 160
 ```
161 161
 
162
-Then tell Hugo to output an `index.json` file along with your site when building by adding the following to the config:
162
+Then tell Hugo to output an `index.json` file along with your site when building by adding the following to your `config.toml`:
163 163
 
164
-```
164
+```toml
165 165
 [outputs]
166 166
   home = ["HTML", "RSS", "JSON"]
167 167
   section = ["HTML", "RSS", "JSON"]
168 168
 ```
169 169
 
170
-**Note:** If you don't see `index.json` in your `public` folder after building try running a `hugo --gc` to cajole the generator into creating the JSON file.
170
+The above configuration tells Hugo to create an `index.json` file in addition to `index.xml` and `index.html` when building your site. The JSON file will be consumed by the search app when the layout is used and will update automatically whenever your site is built.
171 171
 
172
-After that navigate to the `/search/` path on your site and let the fun begin.
172
+**Tip:** While updating the config consider enabling the After Dark [section menu](#section-menu) to expose search to users.
173 173
 
174
-**Tip:** Consider enabling the After Dark [section menu](#section-menu) to expose the search section to users.
174
+Finally, go ahead and serve your site, and navigate to the `/search/` path to begin using fuzzy search. Notice how the page URL updates when queries are entered and search terms highlighted in results. Try refreshing the page then copying the URL and opening it in a new window. Notice how the search query and results are persisted across page loads.
175 175
 
176
-While deep link searches are supported, please note Fuzzy Search will only return results for [Regular Pages](https://gohugo.io/variables/site/#site-variables-list) and intentionally omits any page tagged for [index blocking](#index-blocking). In other words it's easy to find stuff. But only if you want it to be found.
176
+Fuzzy Search will only return results for [Regular Pages](https://gohugo.io/variables/site/#site-variables-list) and intentionally omits pages tagged for [index blocking](#index-blocking). Anything you see in search results is also indexable to well-behaved web crawlers and anything you can't search is not.
177 177
 
178 178
 ### Post Images
179 179
 

+ 108
- 105
layouts/_default/search.html View File

@@ -38,122 +38,125 @@
38 38
 {{ end }}
39 39
 {{ define "footer" }}
40 40
   {{ partial "powered-by.html" . }}
41
-  <script src={{ "/js/vue.min.js" | relURL }}></script>
42
-  <script src={{ "/js/lodash.custom.min.js" | relURL }}></script>
43
-  <script src={{ "/js/fuse.min.js" | relURL }}></script>
44
-  <script src={{ "/js/mark.min.js" | relURL }}></script>
45 41
   <script>
46
-    (function (window, document, undefined) {
47
-      "use strict"
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";
48 50
 
49
-      const getQueryByParam = param => decodeURIComponent(
50
-        (location.search.split(param + '=')[1] || '').split('&')[0]
51
-      ).replace(/\+/g, ' ')
51
+        const getQueryByParam = param => decodeURIComponent(
52
+          (location.search.split(param + '=')[1] || '').split('&')[0]
53
+        ).replace(/\+/g, ' ');
52 54
 
53
-      const queryParam = 's'
54
-      const selectors = {
55
-        appContainer: '#search-app',
56
-        resultContainer: '#search-results',
57
-        searchInput: '#query'
58
-      }
55
+        const queryParam = 's';
56
+        const selectors = {
57
+          appContainer: '#search-app',
58
+          resultContainer: '#search-results',
59
+          searchInput: '#query'
60
+        };
59 61
 
60
-      const fuseOpts = {
61
-        shouldSort: true,
62
-        tokenize: true,
63
-        matchAllTokens: true,
64
-        includeScore: true,
65
-        includeMatches: true,
66
-        keys: [
67
-          { name: "title", weight: 0.8 },
68
-          { name: "contents", weight: 0.5 },
69
-          { name: "tags", weight: 0.3 },
70
-          { name: "categories", weight: 0.3 }
71
-        ]
72
-      }
62
+        const fuseOpts = {
63
+          shouldSort: true,
64
+          tokenize: true,
65
+          matchAllTokens: true,
66
+          includeScore: true,
67
+          includeMatches: true,
68
+          keys: [
69
+            { name: "title", weight: 0.8 },
70
+            { name: "contents", weight: 0.5 },
71
+            { name: "tags", weight: 0.3 },
72
+            { name: "categories", weight: 0.3 }
73
+          ]
74
+        };
73 75
 
74
-      const getSearchInput = () => document.querySelector(selectors.searchInput)
75
-      const focusSearchInput = () => getSearchInput().focus()
76
-      const searchQuery = getSearchInput().value = getQueryByParam(queryParam)
76
+        const getSearchInput = () => document.querySelector(selectors.searchInput);
77
+        const focusSearchInput = () => getSearchInput().focus();
78
+        const searchQuery = getSearchInput().value = getQueryByParam(queryParam);
77 79
 
78
-      const fuse = new Fuse([], fuseOpts)
79
-      window.fetch('/index.json').then(response => {
80
-        response.text().then(searchData => {
81
-          fuse.setCollection(JSON.parse(searchData))
82
-          if (searchQuery) search(searchQuery)
83
-        })
84
-      })
80
+        const fuse = new Fuse([], fuseOpts);
81
+        window.fetch('/index.json').then(response => {
82
+          response.text().then(searchData => {
83
+            fuse.setCollection(JSON.parse(searchData));
84
+            searchQuery && search(searchQuery);
85
+          });
86
+        });
85 87
 
86
-      const getUrl = (query) => {
87
-        const encodedQuery = encodeURIComponent(query)
88
-        const url = {{ .URL }}
89
-        return (encodedQuery)
90
-          ? `${url}?${queryParam}=${encodedQuery}`
91
-          : url
92
-      }
88
+        const getUrl = (query) => {
89
+          const encodedQuery = encodeURIComponent(query);
90
+          const url = {{ .URL }};
91
+          return (encodedQuery)
92
+            ? `${url}?${queryParam}=${encodedQuery}`
93
+            : url;
94
+        };
93 95
 
94
-      let mark = new Mark(
95
-        document.querySelector(
96
-          selectors.resultContainer
97
-        )
98
-      )
96
+        let mark = new Mark(
97
+          document.querySelector(
98
+            selectors.resultContainer
99
+          )
100
+        );
99 101
 
100
-      const app = new Vue({
101
-        delimiters: ['{', '}'],
102
-        el: selectors.appContainer,
103
-        data: {
104
-          fuse: null,
105
-          results: [],
106
-          query: getQueryByParam(queryParam),
107
-          resultsForSearch: getQueryByParam(queryParam)
108
-        },
109
-        mounted () {
110
-          this.fuse = fuse
111
-          window.onpopstate = (evt) => {
112
-            this.query = evt.state.query
113
-          }
114
-          document.onkeyup = function (evt) {
115
-            evt.key === 's' && focusSearchInput()
116
-          }
117
-          focusSearchInput()
118
-        },
119
-        watch: {
120
-          query () {
121
-            this.executeSearch()
122
-            window.history.replaceState(
123
-              {query: this.query},
124
-              null,
125
-              getUrl(this.query)
126
-            )
127
-          }
128
-        },
129
-        beforeUpdate: function () {
130
-          mark.unmark()
131
-        },
132
-        updated: function () {
133
-          this.$nextTick(function () {
134
-            mark = new Mark(
135
-              document.querySelector(
136
-                selectors.resultContainer
102
+        const app = new Vue({
103
+          delimiters: ['{', '}'],
104
+          el: selectors.appContainer,
105
+          data: {
106
+            fuse: null,
107
+            results: [],
108
+            query: getQueryByParam(queryParam),
109
+            resultsForSearch: getQueryByParam(queryParam)
110
+          },
111
+          mounted () {
112
+            this.fuse = fuse;
113
+            window.onpopstate = (evt) => {
114
+              this.query = evt.state.query;
115
+            };
116
+            document.onkeyup = function (evt) {
117
+              evt.key === 's' && focusSearchInput();
118
+            }
119
+            focusSearchInput();
120
+          },
121
+          watch: {
122
+            query () {
123
+              this.executeSearch();
124
+              window.history.replaceState(
125
+                {query: this.query},
126
+                null,
127
+                getUrl(this.query)
128
+              );
129
+            }
130
+          },
131
+          beforeUpdate: function () {
132
+            mark.unmark();
133
+          },
134
+          updated: function () {
135
+            this.$nextTick(function () {
136
+              mark = new Mark(
137
+                document.querySelector(
138
+                  selectors.resultContainer
139
+                )
137 140
               )
138
-            )
139
-            mark.mark(this.query.trim())
140
-          })
141
-        },
142
-        methods: {
143
-          executeSearch: _.debounce(function () {
144
-            const trimmedQuery = this.query.trim()
145
-            this.resultsForSearch = trimmedQuery
146
-            this.results = (trimmedQuery)
147
-              ? this.fuse.search(trimmedQuery)
148
-              : []
149
-          }, 250)
150
-        }
151
-      })
141
+              mark.mark(this.query.trim());
142
+            })
143
+          },
144
+          methods: {
145
+            executeSearch: _.debounce(function () {
146
+              const trimmedQuery = this.query.trim();
147
+              this.resultsForSearch = trimmedQuery;
148
+              this.results = (trimmedQuery)
149
+                ? this.fuse.search(trimmedQuery)
150
+                : [];
151
+            }, 250)
152
+          }
153
+        });
152 154
 
153
-      const search = query => {
154
-        app.results = fuse.search(query)
155
-      }
155
+        const search = query => {
156
+          app.results = fuse.search(query);
157
+        };
156 158
 
157
-    })(window, document)
159
+      })(window, document);
160
+    });
158 161
   </script>
159 162
 {{ end }}

Loading…
Cancel
Save