├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── assets ├── js │ ├── fuse-search.js │ └── fuse.6.4.0.js └── scss │ ├── _fuse-search-colors.scss │ └── fuse-search.scss ├── harvest.example.sh ├── layouts ├── _default │ └── index.json ├── partials │ └── fuse-search │ │ ├── footer.html │ │ └── head.html └── shortcodes │ └── fuse-search │ └── inline-searchbar.html ├── meta ├── anatole.gif ├── fullscreen-searchbar.png ├── inline-searchbar.png └── top-searchbar.png └── tests └── js ├── .gitignore ├── fuse-search.test.js ├── index.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **.mov 3 | harvest.sh 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 14 4 | script: 5 | - cd tests/js/ 6 | - yarn install 7 | - yarn test 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Thijs Havinga, Software Engineering 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hugo fuse-search [![Build Status](https://travis-ci.org/Theys96/hugo-fuse-search.svg?branch=master)](https://travis-ci.org/Theys96/hugo-fuse-search) 2 | 3 | Drop-in ready-to-use search solution for static site generator [Hugo](https://github.com/gohugoio/hugo). 4 | 5 | The goal of this project is to create a ready-to-use package to paste into your Hugo theme to allow for dynamic search 6 | functionality within your Hugo website. Various customizable components are included. 7 | 8 | The basis of this project is [this GitHub Gist](https://gist.github.com/cmod/5410eae147e4318164258742dd053993) by Craig Mod. 9 | 10 | ## Preview 11 | 12 | The current implementation is heavily under construction. It includes for example a dynamic search bar which can be opened at the top of the page. In the [anatole](https://github.com/lxndrblz/anatole/) theme this looks like the following. 13 | 14 | ![Anatole preview](https://raw.githubusercontent.com/theys96/hugo-fuse-search/master/meta/anatole.gif) 15 | 16 | Here, to open the searchbar enter Cmd+/ and use the arrow keys and Enter to navigate the results (or just click them). 17 | 18 | ## Installation 19 | 20 | [(Wiki page)](https://github.com/Theys96/hugo-fuse-search/wiki/Installation) 21 | 22 | 1. First, download or clone this repository. 23 | 2. Merge the `assets` and `layouts` folder from this repository into your active Hugo theme or project. Merging folders can be done on Mac with e.g. `ditto hugo-fuse-search/layouts /layouts` and on Linux with `rsync -a hugo-fuse-search/layouts /layouts`. 24 | 3. Add the necessary components in your templates: 25 | 26 | - In the ``: 27 | 28 | ``` 29 | {{ partial "fuse-search/head.html" . }} 30 | ``` 31 | - At the end but within the ``: 32 | 33 | ``` 34 | {{- partial "fuse-search/footer.html" . -}} 35 | ``` 36 | 37 | ## Configuration 38 | 39 | 1. Activate search in your `config.toml` or `config.yaml` (example in toml): 40 | 41 | ``` 42 | [outputs] 43 | home = ["HTML", "RSS", "JSON"] 44 | 45 | [params.search] 46 | enabled = true 47 | keyboardControlled = "topSearchbar" 48 | 49 | [params.search.topSearchbar] 50 | enabled = true 51 | ``` 52 | 53 | For more configuration specifics, check out the Wiki page on [Configuration and Customization](https://github.com/Theys96/hugo-fuse-search/wiki/Configuration-and-Customization). 54 | 55 | ## Searchbars 56 | 57 | Currently, there are 3 different "searchbars" included in this project: 58 | 59 | * The `top-searchbar`: 60 | 61 | ![top searchbar](https://raw.githubusercontent.com/theys96/hugo-fuse-search/master/meta/top-searchbar.png) 62 | 63 | It can be enabled with `params.search.topSearchbar.enabled = true`. 64 | 65 | * The `fullscreen-searchbar`: 66 | 67 | ![fullscreen searchbar](https://raw.githubusercontent.com/theys96/hugo-fuse-search/master/meta/fullscreen-searchbar.png) 68 | 69 | It can be enabled with `params.search.fullscreenSearchbar = true`. 70 | 71 | * The `inline-searchbar`: 72 | 73 | ![inline searchbar](https://raw.githubusercontent.com/theys96/hugo-fuse-search/master/meta/inline-searchbar.png) 74 | 75 | It can be included in a page with the `{{< fuse-search/inline-searchbar >}}` shortcode. This components still needs a lot of improvement. 76 | 77 | The top-searchbar and fullscreen-searchbar can be controlled with the keyboard if configured as such. Alternatively, the javascript `fusesearchTopSearchbar.initSearch()` and `fusesearchFullscreenSearchbar.initSearch()` can be used to open those searchbars programmatically. Note that any element (such as a button) which has the class `fuse-search-element`, can be clicked without the searchbar being closed (regularly, the searchbar is closed when the user clicks outside it). 78 | 79 | 80 | -------------------------------------------------------------------------------- /assets/js/fuse-search.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of the hugo-fuse-search project 3 | * https://github.com/theys96/hugo-fuse-search/ 4 | * License: https://github.com/Theys96/hugo-fuse-search/blob/master/LICENSE 5 | * 6 | * Note: contains parts of code still remaining from the original code this 7 | * program is based on. Author: Craig Mod. 8 | * https://gist.github.com/cmod/5410eae147e4318164258742dd053993 9 | */ 10 | 11 | // ========================================== 12 | // BASICS 13 | // 14 | 15 | function setupSearch() { return globalThis.fusesearch = new FuseSearch(); } 16 | function setupTopSearchbar() { 17 | if ('fusesearch' in globalThis) { 18 | return new TopSearchbar(globalThis.fusesearch); 19 | } 20 | return {}; 21 | } 22 | function setupFullscreenSearchbar() { 23 | if ('fusesearch' in globalThis) { 24 | return new FullscreenSearchbar(globalThis.fusesearch); 25 | } 26 | return {}; 27 | } 28 | function setupInlineSearchbar() { 29 | if ('fusesearch' in globalThis) { 30 | return new InlineSearchbar(globalThis.fusesearch); 31 | } 32 | return {}; 33 | } 34 | function setupKeyboardHandler(searchComponent) { 35 | return new FuseSearchKeyboardHandler(searchComponent); 36 | } 37 | 38 | 39 | // ========================================== 40 | // CLASSES 41 | // 42 | 43 | 44 | /* Core search class containing logic for the search engine */ 45 | class FuseSearch { 46 | isInit = false; 47 | index = "/index.json"; 48 | fuse = false; 49 | fuseConfig = { 50 | shouldSort: true, 51 | location: 0, 52 | distance: 100, 53 | threshold: 0.4, 54 | minMatchCharLength: 2, 55 | keys: ['title', 'permalink', 'contents'] 56 | }; 57 | 58 | constructor() { 59 | console.log("hugo-fuse-search: fuse-search created."); 60 | } 61 | 62 | // Init search if it wasn't already. 63 | // Initialization is only executed once a user 64 | // starts searching. 65 | init() { 66 | if (!this.isInit) { 67 | return this.loadSearch(); 68 | } 69 | } 70 | 71 | // Loads the JSON site index and creates the Fuse.io engine object for search 72 | async loadSearch() { 73 | let fs = this; 74 | return fetchJSONFile(fs.index, function(data) { 75 | //data = data.filter(function(page) { 76 | // return page.lang == document.documentElement.lang; 77 | //}) 78 | fs.fuse = new Fuse(data, fs.fuseConfig); 79 | fs.isInit = true; 80 | console.log("hugo-fuse-search: Fuse.js was succesfuly instantiated."); 81 | }, function(status, statusText) { 82 | console.log("hugo-fuse-search: retrieval of index file was unsuccesful (\"" + fs.index + "\": " + status + " - " + statusText + ")") 83 | }); 84 | } 85 | 86 | toString() { return "FuseSearch"; } 87 | } 88 | 89 | 90 | /* Gives one search component keyboard functionality 91 | */ 92 | class FuseSearchKeyboardHandler { 93 | 94 | constructor(component) { 95 | this.searchComponent = component; 96 | if (this.validateComponent() == false) { 97 | console.log("hugo-fuse-search: KeyboardHandler not initialized correctly: Invalid search component " + this.searchComponent.toString() + "."); 98 | console.log(component); 99 | return; 100 | } else { 101 | // Listen for keyboard commands 102 | document.addEventListener('keydown', (e) => this.keyboardHandler(e, this.searchComponent)); 103 | 104 | console.log("hugo-fuse-search: KeyboardHandler initiated with search component " + this.searchComponent.toString() + "."); 105 | } 106 | } 107 | 108 | // This component is controlled with the keyboard: 109 | keyboardHandler(e, component) { 110 | if (event.metaKey && event.which === 191) { // Cmd + / 111 | component.initSearch(); 112 | } else { 113 | switch(e.keyCode) { 114 | case 27: // ESC 115 | component.closeSearch(); 116 | break; 117 | 118 | case 40: // DOWN 119 | component.navigateDown(e); 120 | break; 121 | 122 | case 38: // UP 123 | component.navigateUp(e); 124 | break; 125 | } 126 | } 127 | } 128 | 129 | // Quick live check on the compatibility of the search component 130 | // with this keyboard handler 131 | // It must have the 5 controlled functions: 132 | // - initSearch, openSearch, closeSearch, navigateUp, navigateDown 133 | validateComponent() { 134 | let requiredFunctions = ['initSearch', 'openSearch', 'closeSearch', 'navigateUp', 'navigateDown']; 135 | for (var f in requiredFunctions) { 136 | if (typeof(this.searchComponent[requiredFunctions[f]]) != "function") { 137 | return false; } 138 | } 139 | return true; 140 | } 141 | 142 | toString() { return "FuseSearchKeyboardHandler"; } 143 | } 144 | 145 | 146 | /* Base code for multiple searchbar implementations 147 | */ 148 | class AbstractSearchbar { 149 | constructor() { 150 | this.closable = true; 151 | } 152 | 153 | init() { 154 | this.top_result = this.element_results.firstChild; 155 | this.bottom_result = this.element_results.lastChild; 156 | this.visible = false; 157 | this.resultsAvailable = false; 158 | 159 | // Renew search whenever the user types 160 | this.element_input.addEventListener('keyup', (e) => { this.executeSearch(this.element_input.value); }); 161 | 162 | // Close the searchbar when the user clicks outside it 163 | document.addEventListener('click', (e) => { 164 | if (!e.target.classList.contains("fuse-search-element") && !this.element_main.contains(e.target) && this.visible) { 165 | this.closeSearch() 166 | } 167 | }); 168 | 169 | console.log("hugo-fuse-search: " + this.toString() + " initiated."); 170 | } 171 | 172 | // Open the search component, check if fuse-search is 173 | // already initiated, do so if necessary 174 | initSearch() { 175 | this.search.init(); 176 | 177 | if (this.visible) { 178 | this.closeSearch(); 179 | } else { 180 | this.openSearch(); 181 | } 182 | } 183 | 184 | // Make the component visible 185 | openSearch() { 186 | this.element_main.style.visibility = "visible"; // show search box 187 | this.element_input.focus(); // put focus in input box so you can just start typing 188 | this.visible = true; // search visible 189 | } 190 | 191 | // Make the component invisible 192 | closeSearch() { 193 | if (this.closable) { 194 | this.element_main.style.visibility = "hidden"; // hide search box 195 | document.activeElement.blur(); // remove focus from search box 196 | this.visible = false; // search not visible 197 | } 198 | } 199 | 200 | // Move the focus down between results and the searchbar 201 | navigateDown(event) { 202 | if (this.visible && this.resultsAvailable) { 203 | event.preventDefault(); // stop window from scrolling 204 | if ( document.activeElement == this.element_input) { this.top_result.firstElementChild.focus(); } // if the currently focused element is the main input --> focus the first
  • 205 | else if ( document.activeElement.parentElement == this.bottom_result ) { this.bottom_result.firstElementChild.focus(); } // if we're at the bottom, stay there 206 | else { document.activeElement.parentElement.nextSibling.firstElementChild.focus(); } // otherwise select the next search result 207 | } 208 | } 209 | 210 | // Move the focus up between results and the searchbar 211 | navigateUp(event) { 212 | if (this.visible && this.resultsAvailable) { 213 | event.preventDefault(); // stop window from scrolling 214 | if ( document.activeElement == this.element_input) { this.element_input.focus(); } // If we're in the input box, do nothing 215 | else if ( document.activeElement.parentElement == this.top_result) { this.element_input.focus(); } // If we're at the first item, go to input box 216 | else { document.activeElement.parentElement.previousSibling.firstElementChild.focus(); } // Otherwise, select the search result above the current active one 217 | } 218 | } 219 | 220 | // Run the search (which happens whenever the user types) 221 | executeSearch(term) { 222 | var results; 223 | try { 224 | results = this.search.fuse.search(term); // the actual query being run using fuse.js 225 | } catch (err) { 226 | if (err instanceof TypeError) { 227 | console.log("hugo-fuse-search: search failed because Fuse.js was not instantiated properly.") 228 | } else { 229 | console.log("hugo-fuse-search: search failed: " + err) 230 | } 231 | return; 232 | } 233 | let searchitems = ''; 234 | 235 | if (results.length === 0) { // no results based on what was typed into the input box 236 | this.resultsAvailable = false; 237 | searchitems = ''; 238 | } else { // we got results, show 5 239 | for (let item in results.slice(0,5)) { 240 | let result = results[item]; 241 | if ('item' in result) { 242 | let item = result.item; 243 | searchitems += this.itemHtml(item); 244 | } 245 | } 246 | this.resultsAvailable = true; 247 | } 248 | 249 | this.element_results.innerHTML = searchitems; 250 | if (results.length > 0) { 251 | this.top_result = this.element_results.firstChild; 252 | this.bottom_result = this.element_results.lastChild; 253 | } 254 | } 255 | } 256 | 257 | 258 | /* Class for the top searchbar component 259 | * (absolutely positioned in the page and controlled with the keyboard) 260 | */ 261 | class TopSearchbar extends AbstractSearchbar { 262 | 263 | constructor(fusesearch) { 264 | super(); 265 | this.search = fusesearch; 266 | this.element_main = document.getElementById("fuse-search-top-searchbar"); 267 | this.element_input = document.getElementById('fuse-search-top-searchbar-input'); 268 | this.element_results = document.getElementById('fuse-search-top-searchbar-results'); 269 | super.init(); 270 | } 271 | 272 | itemHtml(item) { 273 | return '
  • ' + 274 | '' + item.title + '' + 275 | '' + item.permalink + '' + 276 | '
  • '; 277 | } 278 | 279 | toString() { return "TopSearchbar"; } 280 | } 281 | 282 | 283 | /* Class for the top searchbar component 284 | * (absolutely positioned in the page and controlled with the keyboard) 285 | */ 286 | class FullscreenSearchbar extends AbstractSearchbar { 287 | 288 | constructor(fusesearch) { 289 | super(); 290 | this.search = fusesearch; 291 | this.element_main = document.getElementById("fuse-search-fullscreen-searchbar"); 292 | this.element_input = document.getElementById('fuse-search-fullscreen-searchbar-input'); 293 | this.element_results = document.getElementById('fuse-search-fullscreen-searchbar-results'); 294 | this.element_close = document.getElementById('fuse-search-fullscreen-searchbar-close'); 295 | super.init(); 296 | 297 | // Close the searchbar when the user clicks the close button 298 | this.element_close.addEventListener('click', (e) => { 299 | if (this.visible) { 300 | this.closeSearch() 301 | } 302 | }); 303 | } 304 | 305 | itemHtml(item) { 306 | return '
  • ' + 307 | '' + item.title + '' + 308 | '' + item.permalink + '' + 309 | '
  • '; 310 | } 311 | 312 | toString() { return "FullscreenSearchbar"; } 313 | } 314 | 315 | /* Class for the inline searchbar component 316 | */ 317 | class InlineSearchbar extends AbstractSearchbar { 318 | 319 | constructor(fusesearch) { 320 | super(); 321 | this.search = fusesearch; 322 | this.element_main = document.getElementById("fuse-search-inline-searchbar"); 323 | this.element_input = document.getElementById('fuse-search-inline-searchbar-input'); 324 | this.element_results = document.getElementById('fuse-search-inline-searchbar-results'); 325 | this.closable = false; 326 | super.init(); 327 | this.initSearch(); 328 | } 329 | 330 | itemHtml(item) { 331 | return '
  • ' + 332 | '' + item.title + '' + 333 | '' + item.permalink + '' + 334 | '
  • '; 335 | } 336 | 337 | toString() { return "InlineSearchbar"; } 338 | } 339 | 340 | 341 | // ========================================== 342 | // HELPER FUNCTIONS 343 | // 344 | 345 | /* Fetches JSON file and returns the parsed contents in the callback */ 346 | function fetchJSONFile(path, callback, errorCallback) { 347 | return new Promise(function (resolve, reject) { 348 | var httpRequest = new XMLHttpRequest(); 349 | httpRequest.onreadystatechange = function() { 350 | if (httpRequest.readyState === 4) { 351 | if (httpRequest.status === 200) { 352 | var data = JSON.parse(httpRequest.responseText); 353 | if (callback) { callback(data); } 354 | resolve(); 355 | } else { 356 | if (errorCallback) { errorCallback(httpRequest.status, httpRequest.statusText) } 357 | reject(); 358 | } 359 | } 360 | }; 361 | httpRequest.open('GET', path); 362 | httpRequest.send(); 363 | }) 364 | } 365 | 366 | 367 | // EXPORTS 368 | if (module) { 369 | module.exports = { setupSearch } 370 | } 371 | 372 | -------------------------------------------------------------------------------- /assets/js/fuse.6.4.0.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Fuse.js v6.4.0 - Lightweight fuzzy-search (http://fusejs.io) 3 | * 4 | * Copyright (c) 2020 Kiro Risk (http://kiro.me) 5 | * All Rights Reserved. Apache Software License 2.0 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | */ 9 | 10 | (function (global, factory) { 11 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 12 | typeof define === 'function' && define.amd ? define(factory) : 13 | (global = global || self, global.Fuse = factory()); 14 | }(this, (function () { 'use strict'; 15 | 16 | function _typeof(obj) { 17 | "@babel/helpers - typeof"; 18 | 19 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 20 | _typeof = function (obj) { 21 | return typeof obj; 22 | }; 23 | } else { 24 | _typeof = function (obj) { 25 | return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 26 | }; 27 | } 28 | 29 | return _typeof(obj); 30 | } 31 | 32 | function _classCallCheck(instance, Constructor) { 33 | if (!(instance instanceof Constructor)) { 34 | throw new TypeError("Cannot call a class as a function"); 35 | } 36 | } 37 | 38 | function _defineProperties(target, props) { 39 | for (var i = 0; i < props.length; i++) { 40 | var descriptor = props[i]; 41 | descriptor.enumerable = descriptor.enumerable || false; 42 | descriptor.configurable = true; 43 | if ("value" in descriptor) descriptor.writable = true; 44 | Object.defineProperty(target, descriptor.key, descriptor); 45 | } 46 | } 47 | 48 | function _createClass(Constructor, protoProps, staticProps) { 49 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 50 | if (staticProps) _defineProperties(Constructor, staticProps); 51 | return Constructor; 52 | } 53 | 54 | function _defineProperty(obj, key, value) { 55 | if (key in obj) { 56 | Object.defineProperty(obj, key, { 57 | value: value, 58 | enumerable: true, 59 | configurable: true, 60 | writable: true 61 | }); 62 | } else { 63 | obj[key] = value; 64 | } 65 | 66 | return obj; 67 | } 68 | 69 | function ownKeys(object, enumerableOnly) { 70 | var keys = Object.keys(object); 71 | 72 | if (Object.getOwnPropertySymbols) { 73 | var symbols = Object.getOwnPropertySymbols(object); 74 | if (enumerableOnly) symbols = symbols.filter(function (sym) { 75 | return Object.getOwnPropertyDescriptor(object, sym).enumerable; 76 | }); 77 | keys.push.apply(keys, symbols); 78 | } 79 | 80 | return keys; 81 | } 82 | 83 | function _objectSpread2(target) { 84 | for (var i = 1; i < arguments.length; i++) { 85 | var source = arguments[i] != null ? arguments[i] : {}; 86 | 87 | if (i % 2) { 88 | ownKeys(Object(source), true).forEach(function (key) { 89 | _defineProperty(target, key, source[key]); 90 | }); 91 | } else if (Object.getOwnPropertyDescriptors) { 92 | Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); 93 | } else { 94 | ownKeys(Object(source)).forEach(function (key) { 95 | Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); 96 | }); 97 | } 98 | } 99 | 100 | return target; 101 | } 102 | 103 | function _inherits(subClass, superClass) { 104 | if (typeof superClass !== "function" && superClass !== null) { 105 | throw new TypeError("Super expression must either be null or a function"); 106 | } 107 | 108 | subClass.prototype = Object.create(superClass && superClass.prototype, { 109 | constructor: { 110 | value: subClass, 111 | writable: true, 112 | configurable: true 113 | } 114 | }); 115 | if (superClass) _setPrototypeOf(subClass, superClass); 116 | } 117 | 118 | function _getPrototypeOf(o) { 119 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { 120 | return o.__proto__ || Object.getPrototypeOf(o); 121 | }; 122 | return _getPrototypeOf(o); 123 | } 124 | 125 | function _setPrototypeOf(o, p) { 126 | _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 127 | o.__proto__ = p; 128 | return o; 129 | }; 130 | 131 | return _setPrototypeOf(o, p); 132 | } 133 | 134 | function _isNativeReflectConstruct() { 135 | if (typeof Reflect === "undefined" || !Reflect.construct) return false; 136 | if (Reflect.construct.sham) return false; 137 | if (typeof Proxy === "function") return true; 138 | 139 | try { 140 | Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); 141 | return true; 142 | } catch (e) { 143 | return false; 144 | } 145 | } 146 | 147 | function _assertThisInitialized(self) { 148 | if (self === void 0) { 149 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 150 | } 151 | 152 | return self; 153 | } 154 | 155 | function _possibleConstructorReturn(self, call) { 156 | if (call && (typeof call === "object" || typeof call === "function")) { 157 | return call; 158 | } 159 | 160 | return _assertThisInitialized(self); 161 | } 162 | 163 | function _createSuper(Derived) { 164 | var hasNativeReflectConstruct = _isNativeReflectConstruct(); 165 | 166 | return function _createSuperInternal() { 167 | var Super = _getPrototypeOf(Derived), 168 | result; 169 | 170 | if (hasNativeReflectConstruct) { 171 | var NewTarget = _getPrototypeOf(this).constructor; 172 | 173 | result = Reflect.construct(Super, arguments, NewTarget); 174 | } else { 175 | result = Super.apply(this, arguments); 176 | } 177 | 178 | return _possibleConstructorReturn(this, result); 179 | }; 180 | } 181 | 182 | function _toConsumableArray(arr) { 183 | return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); 184 | } 185 | 186 | function _arrayWithoutHoles(arr) { 187 | if (Array.isArray(arr)) return _arrayLikeToArray(arr); 188 | } 189 | 190 | function _iterableToArray(iter) { 191 | if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); 192 | } 193 | 194 | function _unsupportedIterableToArray(o, minLen) { 195 | if (!o) return; 196 | if (typeof o === "string") return _arrayLikeToArray(o, minLen); 197 | var n = Object.prototype.toString.call(o).slice(8, -1); 198 | if (n === "Object" && o.constructor) n = o.constructor.name; 199 | if (n === "Map" || n === "Set") return Array.from(o); 200 | if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); 201 | } 202 | 203 | function _arrayLikeToArray(arr, len) { 204 | if (len == null || len > arr.length) len = arr.length; 205 | 206 | for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; 207 | 208 | return arr2; 209 | } 210 | 211 | function _nonIterableSpread() { 212 | throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); 213 | } 214 | 215 | function isArray(value) { 216 | return !Array.isArray ? Object.prototype.toString.call(value) === '[object Array]' : Array.isArray(value); 217 | } // Adapted from: 218 | // https://github.com/lodash/lodash/blob/f4ca396a796435422bd4fd41fadbd225edddf175/.internal/baseToString.js 219 | 220 | var INFINITY = 1 / 0; 221 | function baseToString(value) { 222 | // Exit early for strings to avoid a performance hit in some environments. 223 | if (typeof value == 'string') { 224 | return value; 225 | } 226 | 227 | var result = value + ''; 228 | return result == '0' && 1 / value == -INFINITY ? '-0' : result; 229 | } 230 | function toString(value) { 231 | return value == null ? '' : baseToString(value); 232 | } 233 | function isString(value) { 234 | return typeof value === 'string'; 235 | } 236 | function isNumber(value) { 237 | return typeof value === 'number'; 238 | } 239 | function isObject(value) { 240 | return _typeof(value) === 'object'; 241 | } 242 | function isDefined(value) { 243 | return value !== undefined && value !== null; 244 | } 245 | function isBlank(value) { 246 | return !value.trim().length; 247 | } 248 | 249 | var EXTENDED_SEARCH_UNAVAILABLE = 'Extended search is not available'; 250 | var INCORRECT_INDEX_TYPE = "Incorrect 'index' type"; 251 | var LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = function LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key) { 252 | return "Invalid value for key ".concat(key); 253 | }; 254 | var PATTERN_LENGTH_TOO_LARGE = function PATTERN_LENGTH_TOO_LARGE(max) { 255 | return "Pattern length exceeds max of ".concat(max, "."); 256 | }; 257 | var MISSING_KEY_PROPERTY = function MISSING_KEY_PROPERTY(name) { 258 | return "Missing ".concat(name, " property in key"); 259 | }; 260 | var INVALID_KEY_WEIGHT_VALUE = function INVALID_KEY_WEIGHT_VALUE(key) { 261 | return "Property 'weight' in key '".concat(key, "' must be a positive integer"); 262 | }; 263 | 264 | var hasOwn = Object.prototype.hasOwnProperty; 265 | 266 | var KeyStore = /*#__PURE__*/function () { 267 | function KeyStore(keys) { 268 | var _this = this; 269 | 270 | _classCallCheck(this, KeyStore); 271 | 272 | this._keys = []; 273 | this._keyMap = {}; 274 | var totalWeight = 0; 275 | keys.forEach(function (key) { 276 | var obj = createKey(key); 277 | totalWeight += obj.weight; 278 | 279 | _this._keys.push(obj); 280 | 281 | _this._keyMap[obj.id] = obj; 282 | totalWeight += obj.weight; 283 | }); // Normalize weights so that their sum is equal to 1 284 | 285 | this._keys.forEach(function (key) { 286 | key.weight /= totalWeight; 287 | }); 288 | } 289 | 290 | _createClass(KeyStore, [{ 291 | key: "get", 292 | value: function get(keyId) { 293 | return this._keyMap[keyId]; 294 | } 295 | }, { 296 | key: "keys", 297 | value: function keys() { 298 | return this._keys; 299 | } 300 | }, { 301 | key: "toJSON", 302 | value: function toJSON() { 303 | return JSON.stringify(this._keys); 304 | } 305 | }]); 306 | 307 | return KeyStore; 308 | }(); 309 | function createKey(key) { 310 | var path = null; 311 | var id = null; 312 | var src = null; 313 | var weight = 1; 314 | 315 | if (isString(key) || isArray(key)) { 316 | src = key; 317 | path = createKeyPath(key); 318 | id = createKeyId(key); 319 | } else { 320 | if (!hasOwn.call(key, 'name')) { 321 | throw new Error(MISSING_KEY_PROPERTY('name')); 322 | } 323 | 324 | var name = key.name; 325 | src = name; 326 | 327 | if (hasOwn.call(key, 'weight')) { 328 | weight = key.weight; 329 | 330 | if (weight <= 0) { 331 | throw new Error(INVALID_KEY_WEIGHT_VALUE(name)); 332 | } 333 | } 334 | 335 | path = createKeyPath(name); 336 | id = createKeyId(name); 337 | } 338 | 339 | return { 340 | path: path, 341 | id: id, 342 | weight: weight, 343 | src: src 344 | }; 345 | } 346 | function createKeyPath(key) { 347 | return isArray(key) ? key : key.split('.'); 348 | } 349 | function createKeyId(key) { 350 | return isArray(key) ? key.join('.') : key; 351 | } 352 | 353 | function get(obj, path) { 354 | var list = []; 355 | var arr = false; 356 | 357 | var deepGet = function deepGet(obj, path, index) { 358 | if (!path[index]) { 359 | // If there's no path left, we've arrived at the object we care about. 360 | list.push(obj); 361 | } else { 362 | var key = path[index]; 363 | var value = obj[key]; 364 | 365 | if (!isDefined(value)) { 366 | return; 367 | } 368 | 369 | if (index === path.length - 1 && (isString(value) || isNumber(value))) { 370 | list.push(toString(value)); 371 | } else if (isArray(value)) { 372 | arr = true; // Search each item in the array. 373 | 374 | for (var i = 0, len = value.length; i < len; i += 1) { 375 | deepGet(value[i], path, index + 1); 376 | } 377 | } else if (path.length) { 378 | // An object. Recurse further. 379 | deepGet(value, path, index + 1); 380 | } 381 | } 382 | }; // Backwards compatibility (since path used to be a string) 383 | 384 | 385 | deepGet(obj, isString(path) ? path.split('.') : path, 0); 386 | return arr ? list : list[0]; 387 | } 388 | 389 | var MatchOptions = { 390 | // Whether the matches should be included in the result set. When `true`, each record in the result 391 | // set will include the indices of the matched characters. 392 | // These can consequently be used for highlighting purposes. 393 | includeMatches: false, 394 | // When `true`, the matching function will continue to the end of a search pattern even if 395 | // a perfect match has already been located in the string. 396 | findAllMatches: false, 397 | // Minimum number of characters that must be matched before a result is considered a match 398 | minMatchCharLength: 1 399 | }; 400 | var BasicOptions = { 401 | // When `true`, the algorithm continues searching to the end of the input even if a perfect 402 | // match is found before the end of the same input. 403 | isCaseSensitive: false, 404 | // When true, the matching function will continue to the end of a search pattern even if 405 | includeScore: false, 406 | // List of properties that will be searched. This also supports nested properties. 407 | keys: [], 408 | // Whether to sort the result list, by score 409 | shouldSort: true, 410 | // Default sort function: sort by ascending score, ascending index 411 | sortFn: function sortFn(a, b) { 412 | return a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1; 413 | } 414 | }; 415 | var FuzzyOptions = { 416 | // Approximately where in the text is the pattern expected to be found? 417 | location: 0, 418 | // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match 419 | // (of both letters and location), a threshold of '1.0' would match anything. 420 | threshold: 0.6, 421 | // Determines how close the match must be to the fuzzy location (specified above). 422 | // An exact letter match which is 'distance' characters away from the fuzzy location 423 | // would score as a complete mismatch. A distance of '0' requires the match be at 424 | // the exact location specified, a threshold of '1000' would require a perfect match 425 | // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. 426 | distance: 100 427 | }; 428 | var AdvancedOptions = { 429 | // When `true`, it enables the use of unix-like search commands 430 | useExtendedSearch: false, 431 | // The get function to use when fetching an object's properties. 432 | // The default will search nested paths *ie foo.bar.baz* 433 | getFn: get, 434 | // When `true`, search will ignore `location` and `distance`, so it won't matter 435 | // where in the string the pattern appears. 436 | // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score 437 | ignoreLocation: false, 438 | // When `true`, the calculation for the relevance score (used for sorting) will 439 | // ignore the field-length norm. 440 | // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm 441 | ignoreFieldNorm: false 442 | }; 443 | var Config = _objectSpread2({}, BasicOptions, {}, MatchOptions, {}, FuzzyOptions, {}, AdvancedOptions); 444 | 445 | var SPACE = /[^ ]+/g; // Field-length norm: the shorter the field, the higher the weight. 446 | // Set to 3 decimals to reduce index size. 447 | 448 | function norm() { 449 | var mantissa = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 3; 450 | var cache = new Map(); 451 | return { 452 | get: function get(value) { 453 | var numTokens = value.match(SPACE).length; 454 | 455 | if (cache.has(numTokens)) { 456 | return cache.get(numTokens); 457 | } 458 | 459 | var n = parseFloat((1 / Math.sqrt(numTokens)).toFixed(mantissa)); 460 | cache.set(numTokens, n); 461 | return n; 462 | }, 463 | clear: function clear() { 464 | cache.clear(); 465 | } 466 | }; 467 | } 468 | 469 | var FuseIndex = /*#__PURE__*/function () { 470 | function FuseIndex() { 471 | var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 472 | _ref$getFn = _ref.getFn, 473 | getFn = _ref$getFn === void 0 ? Config.getFn : _ref$getFn; 474 | 475 | _classCallCheck(this, FuseIndex); 476 | 477 | this.norm = norm(3); 478 | this.getFn = getFn; 479 | this.isCreated = false; 480 | this.setIndexRecords(); 481 | } 482 | 483 | _createClass(FuseIndex, [{ 484 | key: "setSources", 485 | value: function setSources() { 486 | var docs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 487 | this.docs = docs; 488 | } 489 | }, { 490 | key: "setIndexRecords", 491 | value: function setIndexRecords() { 492 | var records = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 493 | this.records = records; 494 | } 495 | }, { 496 | key: "setKeys", 497 | value: function setKeys() { 498 | var _this = this; 499 | 500 | var keys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 501 | this.keys = keys; 502 | this._keysMap = {}; 503 | keys.forEach(function (key, idx) { 504 | _this._keysMap[key.id] = idx; 505 | }); 506 | } 507 | }, { 508 | key: "create", 509 | value: function create() { 510 | var _this2 = this; 511 | 512 | if (this.isCreated || !this.docs.length) { 513 | return; 514 | } 515 | 516 | this.isCreated = true; // List is Array 517 | 518 | if (isString(this.docs[0])) { 519 | this.docs.forEach(function (doc, docIndex) { 520 | _this2._addString(doc, docIndex); 521 | }); 522 | } else { 523 | // List is Array 524 | this.docs.forEach(function (doc, docIndex) { 525 | _this2._addObject(doc, docIndex); 526 | }); 527 | } 528 | 529 | this.norm.clear(); 530 | } // Adds a doc to the end of the index 531 | 532 | }, { 533 | key: "add", 534 | value: function add(doc) { 535 | var idx = this.size(); 536 | 537 | if (isString(doc)) { 538 | this._addString(doc, idx); 539 | } else { 540 | this._addObject(doc, idx); 541 | } 542 | } // Removes the doc at the specified index of the index 543 | 544 | }, { 545 | key: "removeAt", 546 | value: function removeAt(idx) { 547 | this.records.splice(idx, 1); // Change ref index of every subsquent doc 548 | 549 | for (var i = idx, len = this.size(); i < len; i += 1) { 550 | this.records[i].i -= 1; 551 | } 552 | } 553 | }, { 554 | key: "getValueForItemAtKeyId", 555 | value: function getValueForItemAtKeyId(item, keyId) { 556 | return item[this._keysMap[keyId]]; 557 | } 558 | }, { 559 | key: "size", 560 | value: function size() { 561 | return this.records.length; 562 | } 563 | }, { 564 | key: "_addString", 565 | value: function _addString(doc, docIndex) { 566 | if (!isDefined(doc) || isBlank(doc)) { 567 | return; 568 | } 569 | 570 | var record = { 571 | v: doc, 572 | i: docIndex, 573 | n: this.norm.get(doc) 574 | }; 575 | this.records.push(record); 576 | } 577 | }, { 578 | key: "_addObject", 579 | value: function _addObject(doc, docIndex) { 580 | var _this3 = this; 581 | 582 | var record = { 583 | i: docIndex, 584 | $: {} 585 | }; // Iterate over every key (i.e, path), and fetch the value at that key 586 | 587 | this.keys.forEach(function (key, keyIndex) { 588 | // console.log(key) 589 | var value = _this3.getFn(doc, key.path); 590 | 591 | if (!isDefined(value)) { 592 | return; 593 | } 594 | 595 | if (isArray(value)) { 596 | (function () { 597 | var subRecords = []; 598 | var stack = [{ 599 | nestedArrIndex: -1, 600 | value: value 601 | }]; 602 | 603 | while (stack.length) { 604 | var _stack$pop = stack.pop(), 605 | nestedArrIndex = _stack$pop.nestedArrIndex, 606 | _value = _stack$pop.value; 607 | 608 | if (!isDefined(_value)) { 609 | continue; 610 | } 611 | 612 | if (isString(_value) && !isBlank(_value)) { 613 | var subRecord = { 614 | v: _value, 615 | i: nestedArrIndex, 616 | n: _this3.norm.get(_value) 617 | }; 618 | subRecords.push(subRecord); 619 | } else if (isArray(_value)) { 620 | _value.forEach(function (item, k) { 621 | stack.push({ 622 | nestedArrIndex: k, 623 | value: item 624 | }); 625 | }); 626 | } 627 | } 628 | 629 | record.$[keyIndex] = subRecords; 630 | })(); 631 | } else if (!isBlank(value)) { 632 | var subRecord = { 633 | v: value, 634 | n: _this3.norm.get(value) 635 | }; 636 | record.$[keyIndex] = subRecord; 637 | } 638 | }); 639 | this.records.push(record); 640 | } 641 | }, { 642 | key: "toJSON", 643 | value: function toJSON() { 644 | return { 645 | keys: this.keys, 646 | records: this.records 647 | }; 648 | } 649 | }]); 650 | 651 | return FuseIndex; 652 | }(); 653 | function createIndex(keys, docs) { 654 | var _ref2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, 655 | _ref2$getFn = _ref2.getFn, 656 | getFn = _ref2$getFn === void 0 ? Config.getFn : _ref2$getFn; 657 | 658 | var myIndex = new FuseIndex({ 659 | getFn: getFn 660 | }); 661 | myIndex.setKeys(keys.map(createKey)); 662 | myIndex.setSources(docs); 663 | myIndex.create(); 664 | return myIndex; 665 | } 666 | function parseIndex(data) { 667 | var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 668 | _ref3$getFn = _ref3.getFn, 669 | getFn = _ref3$getFn === void 0 ? Config.getFn : _ref3$getFn; 670 | 671 | var keys = data.keys, 672 | records = data.records; 673 | var myIndex = new FuseIndex({ 674 | getFn: getFn 675 | }); 676 | myIndex.setKeys(keys); 677 | myIndex.setIndexRecords(records); 678 | return myIndex; 679 | } 680 | 681 | function transformMatches(result, data) { 682 | var matches = result.matches; 683 | data.matches = []; 684 | 685 | if (!isDefined(matches)) { 686 | return; 687 | } 688 | 689 | matches.forEach(function (match) { 690 | if (!isDefined(match.indices) || !match.indices.length) { 691 | return; 692 | } 693 | 694 | var indices = match.indices, 695 | value = match.value; 696 | var obj = { 697 | indices: indices, 698 | value: value 699 | }; 700 | 701 | if (match.key) { 702 | obj.key = match.key.src; 703 | } 704 | 705 | if (match.idx > -1) { 706 | obj.refIndex = match.idx; 707 | } 708 | 709 | data.matches.push(obj); 710 | }); 711 | } 712 | 713 | function transformScore(result, data) { 714 | data.score = result.score; 715 | } 716 | 717 | function computeScore(pattern) { 718 | var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 719 | _ref$errors = _ref.errors, 720 | errors = _ref$errors === void 0 ? 0 : _ref$errors, 721 | _ref$currentLocation = _ref.currentLocation, 722 | currentLocation = _ref$currentLocation === void 0 ? 0 : _ref$currentLocation, 723 | _ref$expectedLocation = _ref.expectedLocation, 724 | expectedLocation = _ref$expectedLocation === void 0 ? 0 : _ref$expectedLocation, 725 | _ref$distance = _ref.distance, 726 | distance = _ref$distance === void 0 ? Config.distance : _ref$distance, 727 | _ref$ignoreLocation = _ref.ignoreLocation, 728 | ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation; 729 | 730 | var accuracy = errors / pattern.length; 731 | 732 | if (ignoreLocation) { 733 | return accuracy; 734 | } 735 | 736 | var proximity = Math.abs(expectedLocation - currentLocation); 737 | 738 | if (!distance) { 739 | // Dodge divide by zero error. 740 | return proximity ? 1.0 : accuracy; 741 | } 742 | 743 | return accuracy + proximity / distance; 744 | } 745 | 746 | function convertMaskToIndices() { 747 | var matchmask = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 748 | var minMatchCharLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Config.minMatchCharLength; 749 | var indices = []; 750 | var start = -1; 751 | var end = -1; 752 | var i = 0; 753 | 754 | for (var len = matchmask.length; i < len; i += 1) { 755 | var match = matchmask[i]; 756 | 757 | if (match && start === -1) { 758 | start = i; 759 | } else if (!match && start !== -1) { 760 | end = i - 1; 761 | 762 | if (end - start + 1 >= minMatchCharLength) { 763 | indices.push([start, end]); 764 | } 765 | 766 | start = -1; 767 | } 768 | } // (i-1 - start) + 1 => i - start 769 | 770 | 771 | if (matchmask[i - 1] && i - start >= minMatchCharLength) { 772 | indices.push([start, i - 1]); 773 | } 774 | 775 | return indices; 776 | } 777 | 778 | // Machine word size 779 | var MAX_BITS = 32; 780 | 781 | function search(text, pattern, patternAlphabet) { 782 | var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}, 783 | _ref$location = _ref.location, 784 | location = _ref$location === void 0 ? Config.location : _ref$location, 785 | _ref$distance = _ref.distance, 786 | distance = _ref$distance === void 0 ? Config.distance : _ref$distance, 787 | _ref$threshold = _ref.threshold, 788 | threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold, 789 | _ref$findAllMatches = _ref.findAllMatches, 790 | findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches, 791 | _ref$minMatchCharLeng = _ref.minMatchCharLength, 792 | minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng, 793 | _ref$includeMatches = _ref.includeMatches, 794 | includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches, 795 | _ref$ignoreLocation = _ref.ignoreLocation, 796 | ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation; 797 | 798 | if (pattern.length > MAX_BITS) { 799 | throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS)); 800 | } 801 | 802 | var patternLen = pattern.length; // Set starting location at beginning text and initialize the alphabet. 803 | 804 | var textLen = text.length; // Handle the case when location > text.length 805 | 806 | var expectedLocation = Math.max(0, Math.min(location, textLen)); // Highest score beyond which we give up. 807 | 808 | var currentThreshold = threshold; // Is there a nearby exact match? (speedup) 809 | 810 | var bestLocation = expectedLocation; // Performance: only computer matches when the minMatchCharLength > 1 811 | // OR if `includeMatches` is true. 812 | 813 | var computeMatches = minMatchCharLength > 1 || includeMatches; // A mask of the matches, used for building the indices 814 | 815 | var matchMask = computeMatches ? Array(textLen) : []; 816 | var index; // Get all exact matches, here for speed up 817 | 818 | while ((index = text.indexOf(pattern, bestLocation)) > -1) { 819 | var score = computeScore(pattern, { 820 | currentLocation: index, 821 | expectedLocation: expectedLocation, 822 | distance: distance, 823 | ignoreLocation: ignoreLocation 824 | }); 825 | currentThreshold = Math.min(score, currentThreshold); 826 | bestLocation = index + patternLen; 827 | 828 | if (computeMatches) { 829 | var i = 0; 830 | 831 | while (i < patternLen) { 832 | matchMask[index + i] = 1; 833 | i += 1; 834 | } 835 | } 836 | } // Reset the best location 837 | 838 | 839 | bestLocation = -1; 840 | var lastBitArr = []; 841 | var finalScore = 1; 842 | var binMax = patternLen + textLen; 843 | var mask = 1 << patternLen - 1; 844 | 845 | for (var _i = 0; _i < patternLen; _i += 1) { 846 | // Scan for the best match; each iteration allows for one more error. 847 | // Run a binary search to determine how far from the match location we can stray 848 | // at this error level. 849 | var binMin = 0; 850 | var binMid = binMax; 851 | 852 | while (binMin < binMid) { 853 | var _score2 = computeScore(pattern, { 854 | errors: _i, 855 | currentLocation: expectedLocation + binMid, 856 | expectedLocation: expectedLocation, 857 | distance: distance, 858 | ignoreLocation: ignoreLocation 859 | }); 860 | 861 | if (_score2 <= currentThreshold) { 862 | binMin = binMid; 863 | } else { 864 | binMax = binMid; 865 | } 866 | 867 | binMid = Math.floor((binMax - binMin) / 2 + binMin); 868 | } // Use the result from this iteration as the maximum for the next. 869 | 870 | 871 | binMax = binMid; 872 | var start = Math.max(1, expectedLocation - binMid + 1); 873 | var finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen; // Initialize the bit array 874 | 875 | var bitArr = Array(finish + 2); 876 | bitArr[finish + 1] = (1 << _i) - 1; 877 | 878 | for (var j = finish; j >= start; j -= 1) { 879 | var currentLocation = j - 1; 880 | var charMatch = patternAlphabet[text.charAt(currentLocation)]; 881 | 882 | if (computeMatches) { 883 | // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`) 884 | matchMask[currentLocation] = +!!charMatch; 885 | } // First pass: exact match 886 | 887 | 888 | bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch; // Subsequent passes: fuzzy match 889 | 890 | if (_i) { 891 | bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1]; 892 | } 893 | 894 | if (bitArr[j] & mask) { 895 | finalScore = computeScore(pattern, { 896 | errors: _i, 897 | currentLocation: currentLocation, 898 | expectedLocation: expectedLocation, 899 | distance: distance, 900 | ignoreLocation: ignoreLocation 901 | }); // This match will almost certainly be better than any existing match. 902 | // But check anyway. 903 | 904 | if (finalScore <= currentThreshold) { 905 | // Indeed it is 906 | currentThreshold = finalScore; 907 | bestLocation = currentLocation; // Already passed `loc`, downhill from here on in. 908 | 909 | if (bestLocation <= expectedLocation) { 910 | break; 911 | } // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`. 912 | 913 | 914 | start = Math.max(1, 2 * expectedLocation - bestLocation); 915 | } 916 | } 917 | } // No hope for a (better) match at greater error levels. 918 | 919 | 920 | var _score = computeScore(pattern, { 921 | errors: _i + 1, 922 | currentLocation: expectedLocation, 923 | expectedLocation: expectedLocation, 924 | distance: distance, 925 | ignoreLocation: ignoreLocation 926 | }); 927 | 928 | if (_score > currentThreshold) { 929 | break; 930 | } 931 | 932 | lastBitArr = bitArr; 933 | } 934 | 935 | var result = { 936 | isMatch: bestLocation >= 0, 937 | // Count exact matches (those with a score of 0) to be "almost" exact 938 | score: Math.max(0.001, finalScore) 939 | }; 940 | 941 | if (computeMatches) { 942 | var indices = convertMaskToIndices(matchMask, minMatchCharLength); 943 | 944 | if (!indices.length) { 945 | result.isMatch = false; 946 | } else if (includeMatches) { 947 | result.indices = indices; 948 | } 949 | } 950 | 951 | return result; 952 | } 953 | 954 | function createPatternAlphabet(pattern) { 955 | var mask = {}; 956 | 957 | for (var i = 0, len = pattern.length; i < len; i += 1) { 958 | var char = pattern.charAt(i); 959 | mask[char] = (mask[char] || 0) | 1 << len - i - 1; 960 | } 961 | 962 | return mask; 963 | } 964 | 965 | var BitapSearch = /*#__PURE__*/function () { 966 | function BitapSearch(pattern) { 967 | var _this = this; 968 | 969 | var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 970 | _ref$location = _ref.location, 971 | location = _ref$location === void 0 ? Config.location : _ref$location, 972 | _ref$threshold = _ref.threshold, 973 | threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold, 974 | _ref$distance = _ref.distance, 975 | distance = _ref$distance === void 0 ? Config.distance : _ref$distance, 976 | _ref$includeMatches = _ref.includeMatches, 977 | includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches, 978 | _ref$findAllMatches = _ref.findAllMatches, 979 | findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches, 980 | _ref$minMatchCharLeng = _ref.minMatchCharLength, 981 | minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng, 982 | _ref$isCaseSensitive = _ref.isCaseSensitive, 983 | isCaseSensitive = _ref$isCaseSensitive === void 0 ? Config.isCaseSensitive : _ref$isCaseSensitive, 984 | _ref$ignoreLocation = _ref.ignoreLocation, 985 | ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation; 986 | 987 | _classCallCheck(this, BitapSearch); 988 | 989 | this.options = { 990 | location: location, 991 | threshold: threshold, 992 | distance: distance, 993 | includeMatches: includeMatches, 994 | findAllMatches: findAllMatches, 995 | minMatchCharLength: minMatchCharLength, 996 | isCaseSensitive: isCaseSensitive, 997 | ignoreLocation: ignoreLocation 998 | }; 999 | this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); 1000 | this.chunks = []; 1001 | 1002 | if (!this.pattern.length) { 1003 | return; 1004 | } 1005 | 1006 | var addChunk = function addChunk(pattern, startIndex) { 1007 | _this.chunks.push({ 1008 | pattern: pattern, 1009 | alphabet: createPatternAlphabet(pattern), 1010 | startIndex: startIndex 1011 | }); 1012 | }; 1013 | 1014 | var len = this.pattern.length; 1015 | 1016 | if (len > MAX_BITS) { 1017 | var i = 0; 1018 | var remainder = len % MAX_BITS; 1019 | var end = len - remainder; 1020 | 1021 | while (i < end) { 1022 | addChunk(this.pattern.substr(i, MAX_BITS), i); 1023 | i += MAX_BITS; 1024 | } 1025 | 1026 | if (remainder) { 1027 | var startIndex = len - MAX_BITS; 1028 | addChunk(this.pattern.substr(startIndex), startIndex); 1029 | } 1030 | } else { 1031 | addChunk(this.pattern, 0); 1032 | } 1033 | } 1034 | 1035 | _createClass(BitapSearch, [{ 1036 | key: "searchIn", 1037 | value: function searchIn(text) { 1038 | var _this$options = this.options, 1039 | isCaseSensitive = _this$options.isCaseSensitive, 1040 | includeMatches = _this$options.includeMatches; 1041 | 1042 | if (!isCaseSensitive) { 1043 | text = text.toLowerCase(); 1044 | } // Exact match 1045 | 1046 | 1047 | if (this.pattern === text) { 1048 | var _result = { 1049 | isMatch: true, 1050 | score: 0 1051 | }; 1052 | 1053 | if (includeMatches) { 1054 | _result.indices = [[0, text.length - 1]]; 1055 | } 1056 | 1057 | return _result; 1058 | } // Otherwise, use Bitap algorithm 1059 | 1060 | 1061 | var _this$options2 = this.options, 1062 | location = _this$options2.location, 1063 | distance = _this$options2.distance, 1064 | threshold = _this$options2.threshold, 1065 | findAllMatches = _this$options2.findAllMatches, 1066 | minMatchCharLength = _this$options2.minMatchCharLength, 1067 | ignoreLocation = _this$options2.ignoreLocation; 1068 | var allIndices = []; 1069 | var totalScore = 0; 1070 | var hasMatches = false; 1071 | this.chunks.forEach(function (_ref2) { 1072 | var pattern = _ref2.pattern, 1073 | alphabet = _ref2.alphabet, 1074 | startIndex = _ref2.startIndex; 1075 | 1076 | var _search = search(text, pattern, alphabet, { 1077 | location: location + startIndex, 1078 | distance: distance, 1079 | threshold: threshold, 1080 | findAllMatches: findAllMatches, 1081 | minMatchCharLength: minMatchCharLength, 1082 | includeMatches: includeMatches, 1083 | ignoreLocation: ignoreLocation 1084 | }), 1085 | isMatch = _search.isMatch, 1086 | score = _search.score, 1087 | indices = _search.indices; 1088 | 1089 | if (isMatch) { 1090 | hasMatches = true; 1091 | } 1092 | 1093 | totalScore += score; 1094 | 1095 | if (isMatch && indices) { 1096 | allIndices = [].concat(_toConsumableArray(allIndices), _toConsumableArray(indices)); 1097 | } 1098 | }); 1099 | var result = { 1100 | isMatch: hasMatches, 1101 | score: hasMatches ? totalScore / this.chunks.length : 1 1102 | }; 1103 | 1104 | if (hasMatches && includeMatches) { 1105 | result.indices = allIndices; 1106 | } 1107 | 1108 | return result; 1109 | } 1110 | }]); 1111 | 1112 | return BitapSearch; 1113 | }(); 1114 | 1115 | var BaseMatch = /*#__PURE__*/function () { 1116 | function BaseMatch(pattern) { 1117 | _classCallCheck(this, BaseMatch); 1118 | 1119 | this.pattern = pattern; 1120 | } 1121 | 1122 | _createClass(BaseMatch, [{ 1123 | key: "search", 1124 | value: function search() 1125 | /*text*/ 1126 | {} 1127 | }], [{ 1128 | key: "isMultiMatch", 1129 | value: function isMultiMatch(pattern) { 1130 | return getMatch(pattern, this.multiRegex); 1131 | } 1132 | }, { 1133 | key: "isSingleMatch", 1134 | value: function isSingleMatch(pattern) { 1135 | return getMatch(pattern, this.singleRegex); 1136 | } 1137 | }]); 1138 | 1139 | return BaseMatch; 1140 | }(); 1141 | 1142 | function getMatch(pattern, exp) { 1143 | var matches = pattern.match(exp); 1144 | return matches ? matches[1] : null; 1145 | } 1146 | 1147 | var ExactMatch = /*#__PURE__*/function (_BaseMatch) { 1148 | _inherits(ExactMatch, _BaseMatch); 1149 | 1150 | var _super = _createSuper(ExactMatch); 1151 | 1152 | function ExactMatch(pattern) { 1153 | _classCallCheck(this, ExactMatch); 1154 | 1155 | return _super.call(this, pattern); 1156 | } 1157 | 1158 | _createClass(ExactMatch, [{ 1159 | key: "search", 1160 | value: function search(text) { 1161 | var isMatch = text === this.pattern; 1162 | return { 1163 | isMatch: isMatch, 1164 | score: isMatch ? 0 : 1, 1165 | indices: [0, this.pattern.length - 1] 1166 | }; 1167 | } 1168 | }], [{ 1169 | key: "type", 1170 | get: function get() { 1171 | return 'exact'; 1172 | } 1173 | }, { 1174 | key: "multiRegex", 1175 | get: function get() { 1176 | return /^="(.*)"$/; 1177 | } 1178 | }, { 1179 | key: "singleRegex", 1180 | get: function get() { 1181 | return /^=(.*)$/; 1182 | } 1183 | }]); 1184 | 1185 | return ExactMatch; 1186 | }(BaseMatch); 1187 | 1188 | var InverseExactMatch = /*#__PURE__*/function (_BaseMatch) { 1189 | _inherits(InverseExactMatch, _BaseMatch); 1190 | 1191 | var _super = _createSuper(InverseExactMatch); 1192 | 1193 | function InverseExactMatch(pattern) { 1194 | _classCallCheck(this, InverseExactMatch); 1195 | 1196 | return _super.call(this, pattern); 1197 | } 1198 | 1199 | _createClass(InverseExactMatch, [{ 1200 | key: "search", 1201 | value: function search(text) { 1202 | var index = text.indexOf(this.pattern); 1203 | var isMatch = index === -1; 1204 | return { 1205 | isMatch: isMatch, 1206 | score: isMatch ? 0 : 1, 1207 | indices: [0, text.length - 1] 1208 | }; 1209 | } 1210 | }], [{ 1211 | key: "type", 1212 | get: function get() { 1213 | return 'inverse-exact'; 1214 | } 1215 | }, { 1216 | key: "multiRegex", 1217 | get: function get() { 1218 | return /^!"(.*)"$/; 1219 | } 1220 | }, { 1221 | key: "singleRegex", 1222 | get: function get() { 1223 | return /^!(.*)$/; 1224 | } 1225 | }]); 1226 | 1227 | return InverseExactMatch; 1228 | }(BaseMatch); 1229 | 1230 | var PrefixExactMatch = /*#__PURE__*/function (_BaseMatch) { 1231 | _inherits(PrefixExactMatch, _BaseMatch); 1232 | 1233 | var _super = _createSuper(PrefixExactMatch); 1234 | 1235 | function PrefixExactMatch(pattern) { 1236 | _classCallCheck(this, PrefixExactMatch); 1237 | 1238 | return _super.call(this, pattern); 1239 | } 1240 | 1241 | _createClass(PrefixExactMatch, [{ 1242 | key: "search", 1243 | value: function search(text) { 1244 | var isMatch = text.startsWith(this.pattern); 1245 | return { 1246 | isMatch: isMatch, 1247 | score: isMatch ? 0 : 1, 1248 | indices: [0, this.pattern.length - 1] 1249 | }; 1250 | } 1251 | }], [{ 1252 | key: "type", 1253 | get: function get() { 1254 | return 'prefix-exact'; 1255 | } 1256 | }, { 1257 | key: "multiRegex", 1258 | get: function get() { 1259 | return /^\^"(.*)"$/; 1260 | } 1261 | }, { 1262 | key: "singleRegex", 1263 | get: function get() { 1264 | return /^\^(.*)$/; 1265 | } 1266 | }]); 1267 | 1268 | return PrefixExactMatch; 1269 | }(BaseMatch); 1270 | 1271 | var InversePrefixExactMatch = /*#__PURE__*/function (_BaseMatch) { 1272 | _inherits(InversePrefixExactMatch, _BaseMatch); 1273 | 1274 | var _super = _createSuper(InversePrefixExactMatch); 1275 | 1276 | function InversePrefixExactMatch(pattern) { 1277 | _classCallCheck(this, InversePrefixExactMatch); 1278 | 1279 | return _super.call(this, pattern); 1280 | } 1281 | 1282 | _createClass(InversePrefixExactMatch, [{ 1283 | key: "search", 1284 | value: function search(text) { 1285 | var isMatch = !text.startsWith(this.pattern); 1286 | return { 1287 | isMatch: isMatch, 1288 | score: isMatch ? 0 : 1, 1289 | indices: [0, text.length - 1] 1290 | }; 1291 | } 1292 | }], [{ 1293 | key: "type", 1294 | get: function get() { 1295 | return 'inverse-prefix-exact'; 1296 | } 1297 | }, { 1298 | key: "multiRegex", 1299 | get: function get() { 1300 | return /^!\^"(.*)"$/; 1301 | } 1302 | }, { 1303 | key: "singleRegex", 1304 | get: function get() { 1305 | return /^!\^(.*)$/; 1306 | } 1307 | }]); 1308 | 1309 | return InversePrefixExactMatch; 1310 | }(BaseMatch); 1311 | 1312 | var SuffixExactMatch = /*#__PURE__*/function (_BaseMatch) { 1313 | _inherits(SuffixExactMatch, _BaseMatch); 1314 | 1315 | var _super = _createSuper(SuffixExactMatch); 1316 | 1317 | function SuffixExactMatch(pattern) { 1318 | _classCallCheck(this, SuffixExactMatch); 1319 | 1320 | return _super.call(this, pattern); 1321 | } 1322 | 1323 | _createClass(SuffixExactMatch, [{ 1324 | key: "search", 1325 | value: function search(text) { 1326 | var isMatch = text.endsWith(this.pattern); 1327 | return { 1328 | isMatch: isMatch, 1329 | score: isMatch ? 0 : 1, 1330 | indices: [text.length - this.pattern.length, text.length - 1] 1331 | }; 1332 | } 1333 | }], [{ 1334 | key: "type", 1335 | get: function get() { 1336 | return 'suffix-exact'; 1337 | } 1338 | }, { 1339 | key: "multiRegex", 1340 | get: function get() { 1341 | return /^"(.*)"\$$/; 1342 | } 1343 | }, { 1344 | key: "singleRegex", 1345 | get: function get() { 1346 | return /^(.*)\$$/; 1347 | } 1348 | }]); 1349 | 1350 | return SuffixExactMatch; 1351 | }(BaseMatch); 1352 | 1353 | var InverseSuffixExactMatch = /*#__PURE__*/function (_BaseMatch) { 1354 | _inherits(InverseSuffixExactMatch, _BaseMatch); 1355 | 1356 | var _super = _createSuper(InverseSuffixExactMatch); 1357 | 1358 | function InverseSuffixExactMatch(pattern) { 1359 | _classCallCheck(this, InverseSuffixExactMatch); 1360 | 1361 | return _super.call(this, pattern); 1362 | } 1363 | 1364 | _createClass(InverseSuffixExactMatch, [{ 1365 | key: "search", 1366 | value: function search(text) { 1367 | var isMatch = !text.endsWith(this.pattern); 1368 | return { 1369 | isMatch: isMatch, 1370 | score: isMatch ? 0 : 1, 1371 | indices: [0, text.length - 1] 1372 | }; 1373 | } 1374 | }], [{ 1375 | key: "type", 1376 | get: function get() { 1377 | return 'inverse-suffix-exact'; 1378 | } 1379 | }, { 1380 | key: "multiRegex", 1381 | get: function get() { 1382 | return /^!"(.*)"\$$/; 1383 | } 1384 | }, { 1385 | key: "singleRegex", 1386 | get: function get() { 1387 | return /^!(.*)\$$/; 1388 | } 1389 | }]); 1390 | 1391 | return InverseSuffixExactMatch; 1392 | }(BaseMatch); 1393 | 1394 | var FuzzyMatch = /*#__PURE__*/function (_BaseMatch) { 1395 | _inherits(FuzzyMatch, _BaseMatch); 1396 | 1397 | var _super = _createSuper(FuzzyMatch); 1398 | 1399 | function FuzzyMatch(pattern) { 1400 | var _this; 1401 | 1402 | var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 1403 | _ref$location = _ref.location, 1404 | location = _ref$location === void 0 ? Config.location : _ref$location, 1405 | _ref$threshold = _ref.threshold, 1406 | threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold, 1407 | _ref$distance = _ref.distance, 1408 | distance = _ref$distance === void 0 ? Config.distance : _ref$distance, 1409 | _ref$includeMatches = _ref.includeMatches, 1410 | includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches, 1411 | _ref$findAllMatches = _ref.findAllMatches, 1412 | findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches, 1413 | _ref$minMatchCharLeng = _ref.minMatchCharLength, 1414 | minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng, 1415 | _ref$isCaseSensitive = _ref.isCaseSensitive, 1416 | isCaseSensitive = _ref$isCaseSensitive === void 0 ? Config.isCaseSensitive : _ref$isCaseSensitive; 1417 | 1418 | _classCallCheck(this, FuzzyMatch); 1419 | 1420 | _this = _super.call(this, pattern); 1421 | _this._bitapSearch = new BitapSearch(pattern, { 1422 | location: location, 1423 | threshold: threshold, 1424 | distance: distance, 1425 | includeMatches: includeMatches, 1426 | findAllMatches: findAllMatches, 1427 | minMatchCharLength: minMatchCharLength, 1428 | isCaseSensitive: isCaseSensitive 1429 | }); 1430 | return _this; 1431 | } 1432 | 1433 | _createClass(FuzzyMatch, [{ 1434 | key: "search", 1435 | value: function search(text) { 1436 | return this._bitapSearch.searchIn(text); 1437 | } 1438 | }], [{ 1439 | key: "type", 1440 | get: function get() { 1441 | return 'fuzzy'; 1442 | } 1443 | }, { 1444 | key: "multiRegex", 1445 | get: function get() { 1446 | return /^"(.*)"$/; 1447 | } 1448 | }, { 1449 | key: "singleRegex", 1450 | get: function get() { 1451 | return /^(.*)$/; 1452 | } 1453 | }]); 1454 | 1455 | return FuzzyMatch; 1456 | }(BaseMatch); 1457 | 1458 | var IncludeMatch = /*#__PURE__*/function (_BaseMatch) { 1459 | _inherits(IncludeMatch, _BaseMatch); 1460 | 1461 | var _super = _createSuper(IncludeMatch); 1462 | 1463 | function IncludeMatch(pattern) { 1464 | _classCallCheck(this, IncludeMatch); 1465 | 1466 | return _super.call(this, pattern); 1467 | } 1468 | 1469 | _createClass(IncludeMatch, [{ 1470 | key: "search", 1471 | value: function search(text) { 1472 | var location = 0; 1473 | var index; 1474 | var indices = []; 1475 | var patternLen = this.pattern.length; // Get all exact matches 1476 | 1477 | while ((index = text.indexOf(this.pattern, location)) > -1) { 1478 | location = index + patternLen; 1479 | indices.push([index, location - 1]); 1480 | } 1481 | 1482 | var isMatch = !!indices.length; 1483 | return { 1484 | isMatch: isMatch, 1485 | score: isMatch ? 1 : 0, 1486 | indices: indices 1487 | }; 1488 | } 1489 | }], [{ 1490 | key: "type", 1491 | get: function get() { 1492 | return 'include'; 1493 | } 1494 | }, { 1495 | key: "multiRegex", 1496 | get: function get() { 1497 | return /^'"(.*)"$/; 1498 | } 1499 | }, { 1500 | key: "singleRegex", 1501 | get: function get() { 1502 | return /^'(.*)$/; 1503 | } 1504 | }]); 1505 | 1506 | return IncludeMatch; 1507 | }(BaseMatch); 1508 | 1509 | var searchers = [ExactMatch, IncludeMatch, PrefixExactMatch, InversePrefixExactMatch, InverseSuffixExactMatch, SuffixExactMatch, InverseExactMatch, FuzzyMatch]; 1510 | var searchersLen = searchers.length; // Regex to split by spaces, but keep anything in quotes together 1511 | 1512 | var SPACE_RE = / +(?=([^\"]*\"[^\"]*\")*[^\"]*$)/; 1513 | var OR_TOKEN = '|'; // Return a 2D array representation of the query, for simpler parsing. 1514 | // Example: 1515 | // "^core go$ | rb$ | py$ xy$" => [["^core", "go$"], ["rb$"], ["py$", "xy$"]] 1516 | 1517 | function parseQuery(pattern) { 1518 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1519 | return pattern.split(OR_TOKEN).map(function (item) { 1520 | var query = item.trim().split(SPACE_RE).filter(function (item) { 1521 | return item && !!item.trim(); 1522 | }); 1523 | var results = []; 1524 | 1525 | for (var i = 0, len = query.length; i < len; i += 1) { 1526 | var queryItem = query[i]; // 1. Handle multiple query match (i.e, once that are quoted, like `"hello world"`) 1527 | 1528 | var found = false; 1529 | var idx = -1; 1530 | 1531 | while (!found && ++idx < searchersLen) { 1532 | var searcher = searchers[idx]; 1533 | var token = searcher.isMultiMatch(queryItem); 1534 | 1535 | if (token) { 1536 | results.push(new searcher(token, options)); 1537 | found = true; 1538 | } 1539 | } 1540 | 1541 | if (found) { 1542 | continue; 1543 | } // 2. Handle single query matches (i.e, once that are *not* quoted) 1544 | 1545 | 1546 | idx = -1; 1547 | 1548 | while (++idx < searchersLen) { 1549 | var _searcher = searchers[idx]; 1550 | 1551 | var _token = _searcher.isSingleMatch(queryItem); 1552 | 1553 | if (_token) { 1554 | results.push(new _searcher(_token, options)); 1555 | break; 1556 | } 1557 | } 1558 | } 1559 | 1560 | return results; 1561 | }); 1562 | } 1563 | 1564 | // to a singl match 1565 | 1566 | var MultiMatchSet = new Set([FuzzyMatch.type, IncludeMatch.type]); 1567 | /** 1568 | * Command-like searching 1569 | * ====================== 1570 | * 1571 | * Given multiple search terms delimited by spaces.e.g. `^jscript .python$ ruby !java`, 1572 | * search in a given text. 1573 | * 1574 | * Search syntax: 1575 | * 1576 | * | Token | Match type | Description | 1577 | * | ----------- | -------------------------- | -------------------------------------- | 1578 | * | `jscript` | fuzzy-match | Items that fuzzy match `jscript` | 1579 | * | `=scheme` | exact-match | Items that are `scheme` | 1580 | * | `'python` | include-match | Items that include `python` | 1581 | * | `!ruby` | inverse-exact-match | Items that do not include `ruby` | 1582 | * | `^java` | prefix-exact-match | Items that start with `java` | 1583 | * | `!^earlang` | inverse-prefix-exact-match | Items that do not start with `earlang` | 1584 | * | `.js$` | suffix-exact-match | Items that end with `.js` | 1585 | * | `!.go$` | inverse-suffix-exact-match | Items that do not end with `.go` | 1586 | * 1587 | * A single pipe character acts as an OR operator. For example, the following 1588 | * query matches entries that start with `core` and end with either`go`, `rb`, 1589 | * or`py`. 1590 | * 1591 | * ``` 1592 | * ^core go$ | rb$ | py$ 1593 | * ``` 1594 | */ 1595 | 1596 | var ExtendedSearch = /*#__PURE__*/function () { 1597 | function ExtendedSearch(pattern) { 1598 | var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 1599 | _ref$isCaseSensitive = _ref.isCaseSensitive, 1600 | isCaseSensitive = _ref$isCaseSensitive === void 0 ? Config.isCaseSensitive : _ref$isCaseSensitive, 1601 | _ref$includeMatches = _ref.includeMatches, 1602 | includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches, 1603 | _ref$minMatchCharLeng = _ref.minMatchCharLength, 1604 | minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng, 1605 | _ref$findAllMatches = _ref.findAllMatches, 1606 | findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches, 1607 | _ref$location = _ref.location, 1608 | location = _ref$location === void 0 ? Config.location : _ref$location, 1609 | _ref$threshold = _ref.threshold, 1610 | threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold, 1611 | _ref$distance = _ref.distance, 1612 | distance = _ref$distance === void 0 ? Config.distance : _ref$distance; 1613 | 1614 | _classCallCheck(this, ExtendedSearch); 1615 | 1616 | this.query = null; 1617 | this.options = { 1618 | isCaseSensitive: isCaseSensitive, 1619 | includeMatches: includeMatches, 1620 | minMatchCharLength: minMatchCharLength, 1621 | findAllMatches: findAllMatches, 1622 | location: location, 1623 | threshold: threshold, 1624 | distance: distance 1625 | }; 1626 | this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); 1627 | this.query = parseQuery(this.pattern, this.options); 1628 | } 1629 | 1630 | _createClass(ExtendedSearch, [{ 1631 | key: "searchIn", 1632 | value: function searchIn(text) { 1633 | var query = this.query; 1634 | 1635 | if (!query) { 1636 | return { 1637 | isMatch: false, 1638 | score: 1 1639 | }; 1640 | } 1641 | 1642 | var _this$options = this.options, 1643 | includeMatches = _this$options.includeMatches, 1644 | isCaseSensitive = _this$options.isCaseSensitive; 1645 | text = isCaseSensitive ? text : text.toLowerCase(); 1646 | var numMatches = 0; 1647 | var allIndices = []; 1648 | var totalScore = 0; // ORs 1649 | 1650 | for (var i = 0, qLen = query.length; i < qLen; i += 1) { 1651 | var searchers = query[i]; // Reset indices 1652 | 1653 | allIndices.length = 0; 1654 | numMatches = 0; // ANDs 1655 | 1656 | for (var j = 0, pLen = searchers.length; j < pLen; j += 1) { 1657 | var searcher = searchers[j]; 1658 | 1659 | var _searcher$search = searcher.search(text), 1660 | isMatch = _searcher$search.isMatch, 1661 | indices = _searcher$search.indices, 1662 | score = _searcher$search.score; 1663 | 1664 | if (isMatch) { 1665 | numMatches += 1; 1666 | totalScore += score; 1667 | 1668 | if (includeMatches) { 1669 | var type = searcher.constructor.type; 1670 | 1671 | if (MultiMatchSet.has(type)) { 1672 | allIndices = [].concat(_toConsumableArray(allIndices), _toConsumableArray(indices)); 1673 | } else { 1674 | allIndices.push(indices); 1675 | } 1676 | } 1677 | } else { 1678 | totalScore = 0; 1679 | numMatches = 0; 1680 | allIndices.length = 0; 1681 | break; 1682 | } 1683 | } // OR condition, so if TRUE, return 1684 | 1685 | 1686 | if (numMatches) { 1687 | var result = { 1688 | isMatch: true, 1689 | score: totalScore / numMatches 1690 | }; 1691 | 1692 | if (includeMatches) { 1693 | result.indices = allIndices; 1694 | } 1695 | 1696 | return result; 1697 | } 1698 | } // Nothing was matched 1699 | 1700 | 1701 | return { 1702 | isMatch: false, 1703 | score: 1 1704 | }; 1705 | } 1706 | }], [{ 1707 | key: "condition", 1708 | value: function condition(_, options) { 1709 | return options.useExtendedSearch; 1710 | } 1711 | }]); 1712 | 1713 | return ExtendedSearch; 1714 | }(); 1715 | 1716 | var registeredSearchers = []; 1717 | function register() { 1718 | registeredSearchers.push.apply(registeredSearchers, arguments); 1719 | } 1720 | function createSearcher(pattern, options) { 1721 | for (var i = 0, len = registeredSearchers.length; i < len; i += 1) { 1722 | var searcherClass = registeredSearchers[i]; 1723 | 1724 | if (searcherClass.condition(pattern, options)) { 1725 | return new searcherClass(pattern, options); 1726 | } 1727 | } 1728 | 1729 | return new BitapSearch(pattern, options); 1730 | } 1731 | 1732 | var LogicalOperator = { 1733 | AND: '$and', 1734 | OR: '$or' 1735 | }; 1736 | var KeyType = { 1737 | PATH: '$path', 1738 | PATTERN: '$val' 1739 | }; 1740 | 1741 | var isExpression = function isExpression(query) { 1742 | return !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]); 1743 | }; 1744 | 1745 | var isPath = function isPath(query) { 1746 | return !!query[KeyType.PATH]; 1747 | }; 1748 | 1749 | var isLeaf = function isLeaf(query) { 1750 | return !isArray(query) && isObject(query) && !isExpression(query); 1751 | }; 1752 | 1753 | var convertToExplicit = function convertToExplicit(query) { 1754 | return _defineProperty({}, LogicalOperator.AND, Object.keys(query).map(function (key) { 1755 | return _defineProperty({}, key, query[key]); 1756 | })); 1757 | }; // When `auto` is `true`, the parse function will infer and initialize and add 1758 | // the appropriate `Searcher` instance 1759 | 1760 | 1761 | function parse(query, options) { 1762 | var _ref3 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, 1763 | _ref3$auto = _ref3.auto, 1764 | auto = _ref3$auto === void 0 ? true : _ref3$auto; 1765 | 1766 | var next = function next(query) { 1767 | var keys = Object.keys(query); 1768 | var isQueryPath = isPath(query); 1769 | 1770 | if (!isQueryPath && keys.length > 1 && !isExpression(query)) { 1771 | return next(convertToExplicit(query)); 1772 | } 1773 | 1774 | if (isLeaf(query)) { 1775 | var key = isQueryPath ? query[KeyType.PATH] : keys[0]; 1776 | var pattern = isQueryPath ? query[KeyType.PATTERN] : query[key]; 1777 | 1778 | if (!isString(pattern)) { 1779 | throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key)); 1780 | } 1781 | 1782 | var obj = { 1783 | keyId: createKeyId(key), 1784 | pattern: pattern 1785 | }; 1786 | 1787 | if (auto) { 1788 | obj.searcher = createSearcher(pattern, options); 1789 | } 1790 | 1791 | return obj; 1792 | } 1793 | 1794 | var node = { 1795 | children: [], 1796 | operator: keys[0] 1797 | }; 1798 | keys.forEach(function (key) { 1799 | var value = query[key]; 1800 | 1801 | if (isArray(value)) { 1802 | value.forEach(function (item) { 1803 | node.children.push(next(item)); 1804 | }); 1805 | } 1806 | }); 1807 | return node; 1808 | }; 1809 | 1810 | if (!isExpression(query)) { 1811 | query = convertToExplicit(query); 1812 | } 1813 | 1814 | return next(query); 1815 | } 1816 | 1817 | var Fuse = /*#__PURE__*/function () { 1818 | function Fuse(docs) { 1819 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1820 | var index = arguments.length > 2 ? arguments[2] : undefined; 1821 | 1822 | _classCallCheck(this, Fuse); 1823 | 1824 | this.options = _objectSpread2({}, Config, {}, options); 1825 | 1826 | if (this.options.useExtendedSearch && !true) { 1827 | throw new Error(EXTENDED_SEARCH_UNAVAILABLE); 1828 | } 1829 | 1830 | this._keyStore = new KeyStore(this.options.keys); 1831 | this.setCollection(docs, index); 1832 | } 1833 | 1834 | _createClass(Fuse, [{ 1835 | key: "setCollection", 1836 | value: function setCollection(docs, index) { 1837 | this._docs = docs; 1838 | 1839 | if (index && !(index instanceof FuseIndex)) { 1840 | throw new Error(INCORRECT_INDEX_TYPE); 1841 | } 1842 | 1843 | this._myIndex = index || createIndex(this.options.keys, this._docs, { 1844 | getFn: this.options.getFn 1845 | }); 1846 | } 1847 | }, { 1848 | key: "add", 1849 | value: function add(doc) { 1850 | if (!isDefined(doc)) { 1851 | return; 1852 | } 1853 | 1854 | this._docs.push(doc); 1855 | 1856 | this._myIndex.add(doc); 1857 | } 1858 | }, { 1859 | key: "remove", 1860 | value: function remove() { 1861 | var predicate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () { 1862 | return ( 1863 | /* doc, idx */ 1864 | false 1865 | ); 1866 | }; 1867 | var results = []; 1868 | 1869 | for (var i = 0, len = this._docs.length; i < len; i += 1) { 1870 | var doc = this._docs[i]; 1871 | 1872 | if (predicate(doc, i)) { 1873 | this.removeAt(i); 1874 | i -= 1; 1875 | results.push(doc); 1876 | } 1877 | } 1878 | 1879 | return results; 1880 | } 1881 | }, { 1882 | key: "removeAt", 1883 | value: function removeAt(idx) { 1884 | this._docs.splice(idx, 1); 1885 | 1886 | this._myIndex.removeAt(idx); 1887 | } 1888 | }, { 1889 | key: "getIndex", 1890 | value: function getIndex() { 1891 | return this._myIndex; 1892 | } 1893 | }, { 1894 | key: "search", 1895 | value: function search(query) { 1896 | var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 1897 | _ref$limit = _ref.limit, 1898 | limit = _ref$limit === void 0 ? -1 : _ref$limit; 1899 | 1900 | var _this$options = this.options, 1901 | includeMatches = _this$options.includeMatches, 1902 | includeScore = _this$options.includeScore, 1903 | shouldSort = _this$options.shouldSort, 1904 | sortFn = _this$options.sortFn, 1905 | ignoreFieldNorm = _this$options.ignoreFieldNorm; 1906 | var results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query); 1907 | computeScore$1(results, { 1908 | ignoreFieldNorm: ignoreFieldNorm 1909 | }); 1910 | 1911 | if (shouldSort) { 1912 | results.sort(sortFn); 1913 | } 1914 | 1915 | if (isNumber(limit) && limit > -1) { 1916 | results = results.slice(0, limit); 1917 | } 1918 | 1919 | return format(results, this._docs, { 1920 | includeMatches: includeMatches, 1921 | includeScore: includeScore 1922 | }); 1923 | } 1924 | }, { 1925 | key: "_searchStringList", 1926 | value: function _searchStringList(query) { 1927 | var searcher = createSearcher(query, this.options); 1928 | var records = this._myIndex.records; 1929 | var results = []; // Iterate over every string in the index 1930 | 1931 | records.forEach(function (_ref2) { 1932 | var text = _ref2.v, 1933 | idx = _ref2.i, 1934 | norm = _ref2.n; 1935 | 1936 | if (!isDefined(text)) { 1937 | return; 1938 | } 1939 | 1940 | var _searcher$searchIn = searcher.searchIn(text), 1941 | isMatch = _searcher$searchIn.isMatch, 1942 | score = _searcher$searchIn.score, 1943 | indices = _searcher$searchIn.indices; 1944 | 1945 | if (isMatch) { 1946 | results.push({ 1947 | item: text, 1948 | idx: idx, 1949 | matches: [{ 1950 | score: score, 1951 | value: text, 1952 | norm: norm, 1953 | indices: indices 1954 | }] 1955 | }); 1956 | } 1957 | }); 1958 | return results; 1959 | } 1960 | }, { 1961 | key: "_searchLogical", 1962 | value: function _searchLogical(query) { 1963 | var _this = this; 1964 | 1965 | var expression = parse(query, this.options); 1966 | 1967 | var evaluate = function evaluate(node, item, idx) { 1968 | if (!node.children) { 1969 | var keyId = node.keyId, 1970 | searcher = node.searcher; 1971 | 1972 | var matches = _this._findMatches({ 1973 | key: _this._keyStore.get(keyId), 1974 | value: _this._myIndex.getValueForItemAtKeyId(item, keyId), 1975 | searcher: searcher 1976 | }); 1977 | 1978 | if (matches && matches.length) { 1979 | return [{ 1980 | idx: idx, 1981 | item: item, 1982 | matches: matches 1983 | }]; 1984 | } 1985 | 1986 | return []; 1987 | } 1988 | /*eslint indent: [2, 2, {"SwitchCase": 1}]*/ 1989 | 1990 | 1991 | switch (node.operator) { 1992 | case LogicalOperator.AND: 1993 | { 1994 | var res = []; 1995 | 1996 | for (var i = 0, len = node.children.length; i < len; i += 1) { 1997 | var child = node.children[i]; 1998 | var result = evaluate(child, item, idx); 1999 | 2000 | if (result.length) { 2001 | res.push.apply(res, _toConsumableArray(result)); 2002 | } else { 2003 | return []; 2004 | } 2005 | } 2006 | 2007 | return res; 2008 | } 2009 | 2010 | case LogicalOperator.OR: 2011 | { 2012 | var _res = []; 2013 | 2014 | for (var _i = 0, _len = node.children.length; _i < _len; _i += 1) { 2015 | var _child = node.children[_i]; 2016 | 2017 | var _result = evaluate(_child, item, idx); 2018 | 2019 | if (_result.length) { 2020 | _res.push.apply(_res, _toConsumableArray(_result)); 2021 | 2022 | break; 2023 | } 2024 | } 2025 | 2026 | return _res; 2027 | } 2028 | } 2029 | }; 2030 | 2031 | var records = this._myIndex.records; 2032 | var resultMap = {}; 2033 | var results = []; 2034 | records.forEach(function (_ref3) { 2035 | var item = _ref3.$, 2036 | idx = _ref3.i; 2037 | 2038 | if (isDefined(item)) { 2039 | var expResults = evaluate(expression, item, idx); 2040 | 2041 | if (expResults.length) { 2042 | // Dedupe when adding 2043 | if (!resultMap[idx]) { 2044 | resultMap[idx] = { 2045 | idx: idx, 2046 | item: item, 2047 | matches: [] 2048 | }; 2049 | results.push(resultMap[idx]); 2050 | } 2051 | 2052 | expResults.forEach(function (_ref4) { 2053 | var _resultMap$idx$matche; 2054 | 2055 | var matches = _ref4.matches; 2056 | 2057 | (_resultMap$idx$matche = resultMap[idx].matches).push.apply(_resultMap$idx$matche, _toConsumableArray(matches)); 2058 | }); 2059 | } 2060 | } 2061 | }); 2062 | return results; 2063 | } 2064 | }, { 2065 | key: "_searchObjectList", 2066 | value: function _searchObjectList(query) { 2067 | var _this2 = this; 2068 | 2069 | var searcher = createSearcher(query, this.options); 2070 | var _this$_myIndex = this._myIndex, 2071 | keys = _this$_myIndex.keys, 2072 | records = _this$_myIndex.records; 2073 | var results = []; // List is Array 2074 | 2075 | records.forEach(function (_ref5) { 2076 | var item = _ref5.$, 2077 | idx = _ref5.i; 2078 | 2079 | if (!isDefined(item)) { 2080 | return; 2081 | } 2082 | 2083 | var matches = []; // Iterate over every key (i.e, path), and fetch the value at that key 2084 | 2085 | keys.forEach(function (key, keyIndex) { 2086 | matches.push.apply(matches, _toConsumableArray(_this2._findMatches({ 2087 | key: key, 2088 | value: item[keyIndex], 2089 | searcher: searcher 2090 | }))); 2091 | }); 2092 | 2093 | if (matches.length) { 2094 | results.push({ 2095 | idx: idx, 2096 | item: item, 2097 | matches: matches 2098 | }); 2099 | } 2100 | }); 2101 | return results; 2102 | } 2103 | }, { 2104 | key: "_findMatches", 2105 | value: function _findMatches(_ref6) { 2106 | var key = _ref6.key, 2107 | value = _ref6.value, 2108 | searcher = _ref6.searcher; 2109 | 2110 | if (!isDefined(value)) { 2111 | return []; 2112 | } 2113 | 2114 | var matches = []; 2115 | 2116 | if (isArray(value)) { 2117 | value.forEach(function (_ref7) { 2118 | var text = _ref7.v, 2119 | idx = _ref7.i, 2120 | norm = _ref7.n; 2121 | 2122 | if (!isDefined(text)) { 2123 | return; 2124 | } 2125 | 2126 | var _searcher$searchIn2 = searcher.searchIn(text), 2127 | isMatch = _searcher$searchIn2.isMatch, 2128 | score = _searcher$searchIn2.score, 2129 | indices = _searcher$searchIn2.indices; 2130 | 2131 | if (isMatch) { 2132 | matches.push({ 2133 | score: score, 2134 | key: key, 2135 | value: text, 2136 | idx: idx, 2137 | norm: norm, 2138 | indices: indices 2139 | }); 2140 | } 2141 | }); 2142 | } else { 2143 | var text = value.v, 2144 | norm = value.n; 2145 | 2146 | var _searcher$searchIn3 = searcher.searchIn(text), 2147 | isMatch = _searcher$searchIn3.isMatch, 2148 | score = _searcher$searchIn3.score, 2149 | indices = _searcher$searchIn3.indices; 2150 | 2151 | if (isMatch) { 2152 | matches.push({ 2153 | score: score, 2154 | key: key, 2155 | value: text, 2156 | norm: norm, 2157 | indices: indices 2158 | }); 2159 | } 2160 | } 2161 | 2162 | return matches; 2163 | } 2164 | }]); 2165 | 2166 | return Fuse; 2167 | }(); // Practical scoring function 2168 | 2169 | function computeScore$1(results, _ref8) { 2170 | var _ref8$ignoreFieldNorm = _ref8.ignoreFieldNorm, 2171 | ignoreFieldNorm = _ref8$ignoreFieldNorm === void 0 ? Config.ignoreFieldNorm : _ref8$ignoreFieldNorm; 2172 | results.forEach(function (result) { 2173 | var totalScore = 1; 2174 | result.matches.forEach(function (_ref9) { 2175 | var key = _ref9.key, 2176 | norm = _ref9.norm, 2177 | score = _ref9.score; 2178 | var weight = key ? key.weight : null; 2179 | totalScore *= Math.pow(score === 0 && weight ? Number.EPSILON : score, (weight || 1) * (ignoreFieldNorm ? 1 : norm)); 2180 | }); 2181 | result.score = totalScore; 2182 | }); 2183 | } 2184 | 2185 | function format(results, docs) { 2186 | var _ref10 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, 2187 | _ref10$includeMatches = _ref10.includeMatches, 2188 | includeMatches = _ref10$includeMatches === void 0 ? Config.includeMatches : _ref10$includeMatches, 2189 | _ref10$includeScore = _ref10.includeScore, 2190 | includeScore = _ref10$includeScore === void 0 ? Config.includeScore : _ref10$includeScore; 2191 | 2192 | var transformers = []; 2193 | if (includeMatches) transformers.push(transformMatches); 2194 | if (includeScore) transformers.push(transformScore); 2195 | return results.map(function (result) { 2196 | var idx = result.idx; 2197 | var data = { 2198 | item: docs[idx], 2199 | refIndex: idx 2200 | }; 2201 | 2202 | if (transformers.length) { 2203 | transformers.forEach(function (transformer) { 2204 | transformer(result, data); 2205 | }); 2206 | } 2207 | 2208 | return data; 2209 | }); 2210 | } 2211 | 2212 | Fuse.version = '6.4.0'; 2213 | Fuse.createIndex = createIndex; 2214 | Fuse.parseIndex = parseIndex; 2215 | Fuse.config = Config; 2216 | 2217 | { 2218 | Fuse.parseQuery = parse; 2219 | } 2220 | 2221 | { 2222 | register(ExtendedSearch); 2223 | } 2224 | 2225 | return Fuse; 2226 | 2227 | }))); 2228 | -------------------------------------------------------------------------------- /assets/scss/_fuse-search-colors.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of the hugo-fuse-search project 3 | * https://github.com/theys96/hugo-fuse-search/ 4 | * License: https://github.com/Theys96/hugo-fuse-search/blob/master/LICENSE 5 | */ 6 | 7 | 8 | 9 | /* 10 | * INLINE SEARCHBAR COLORS 11 | */ 12 | $inlineS-background: rgba(0,0,0,0); 13 | $inlineS-inputText: black; 14 | $inlineS-inputBackground: white; 15 | $inlineS-resultsTitle: #fff; 16 | $inlineS-resultsText: #777; 17 | $inlineS-resultsText-hovered: #fff; 18 | $inlineS-resultsBackground: #333; 19 | $inlineS-hoveredResultBackground: #222; 20 | $inlineS-resultsRuler: 1px dashed white; 21 | 22 | 23 | 24 | /* 25 | * TOP SEARCHBAR COLORS 26 | */ 27 | $topS-background: rgba(0,0,0,0.8); 28 | $topS-inputText: #eee; 29 | $topS-inputBackground: rgba(0,0,0,0); 30 | $topS-resultsTitle: #fff; 31 | $topS-resultsText: #777; 32 | $topS-resultsText-hovered: #fff; 33 | $topS-hoveredResultbackground: #777; 34 | 35 | 36 | 37 | /* 38 | * FULLSCREEN SEARCHBAR COLORS 39 | */ 40 | 41 | // DARK THEME 42 | $fsS-dark-background: rgba(0,0,0,0.85); 43 | $fsS-dark-inputText: #fff; 44 | $fsS-dark-inputBackground: black; 45 | $fsS-dark-closeBtn-icon: white; 46 | $fsS-dark-closeBtn-background: rgba(0,0,0,0); 47 | $fsS-dark-closeBtn-border: 1px solid white; 48 | $fsS-dark-closeBtn-icon-hovered: white; 49 | $fsS-dark-closeBtn-background-hovered: #777; 50 | $fsS-dark-closeBtn-border-hovered: 1px solid white; 51 | $fsS-dark-resultsTitle: #fff; 52 | $fsS-dark-resultsText: #777; 53 | $fsS-dark-resultsText-hovered: #fff; 54 | $fsS-dark-hoveredResultbackground: #333; 55 | 56 | // LIGHT THEME 57 | $fsS-light-background: rgba(255,255,255,0.85); 58 | $fsS-light-inputText: black; 59 | $fsS-light-inputBackground: white; 60 | $fsS-light-closeBtn-icon: white; 61 | $fsS-light-closeBtn-background: #777; 62 | $fsS-light-closeBtn-border: 1px solid white; 63 | $fsS-light-closeBtn-icon-hovered: white; 64 | $fsS-light-closeBtn-background-hovered: black; 65 | $fsS-light-closeBtn-border-hovered: 1px solid white; 66 | $fsS-light-resultsTitle: black; 67 | $fsS-light-resultsText: #444; 68 | $fsS-light-resultsText-hovered: black; 69 | $fsS-light-hoveredResultbackground: white; 70 | -------------------------------------------------------------------------------- /assets/scss/fuse-search.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Part of the hugo-fuse-search project 3 | * https://github.com/theys96/hugo-fuse-search/ 4 | * License: https://github.com/Theys96/hugo-fuse-search/blob/master/LICENSE 5 | * 6 | * Note: contains parts of code still remaining from the original code this 7 | * program is based on. Author: Craig Mod. 8 | * https://gist.github.com/cmod/5410eae147e4318164258742dd053993 9 | */ 10 | 11 | @import 'fuse-search-colors'; 12 | 13 | /* 14 | * topSearchbar 15 | */ 16 | $topSearchbar: fuse-search-top-searchbar; 17 | 18 | ##{$topSearchbar} { 19 | visibility: hidden; 20 | position: absolute; 21 | display: block; 22 | width: 320px; 23 | z-index: 50; 24 | padding: 5px; 25 | box-sizing: content-box; 26 | background-color: $topS-background; 27 | 28 | input##{$topSearchbar}-input { 29 | width: 100%; 30 | font-size: 1.5em; 31 | color: $topS-inputText; 32 | font-weight: bold; 33 | background-color: $topS-inputBackground; 34 | border: none; 35 | } 36 | 37 | ##{$topSearchbar}-results::before { 38 | font-weight: bold; 39 | color: $topS-resultsTitle; 40 | content: "..."; 41 | text-align: center; 42 | display: inline-block; 43 | width: 100%; 44 | } 45 | 46 | ##{$topSearchbar}-results { 47 | visibility: inherit; 48 | width: 100%; 49 | margin: 0; 50 | padding: 0; 51 | 52 | li { 53 | list-style: none; 54 | margin-left: 0em; 55 | 56 | .title { 57 | color: $topS-resultsTitle; 58 | font-size: 1.1em; 59 | width: 100%; 60 | display: inline-block; 61 | box-sizing: content-box; 62 | } 63 | 64 | .text { 65 | display: inline-block; 66 | padding-left: 20px; 67 | box-sizing: content-box; 68 | } 69 | 70 | a { 71 | width: 100%; 72 | display: inline-block; 73 | text-decoration: none; 74 | color: $topS-resultsText; 75 | padding-bottom: 10px; 76 | } 77 | 78 | a:hover, a:focus { 79 | color: $topS-resultsText-hovered !important; 80 | background-color: $topS-hoveredResultbackground !important; 81 | } 82 | } 83 | } 84 | } 85 | 86 | /* Positioning */ 87 | .#{$topSearchbar}-, .#{$topSearchbar}-right { 88 | right: 5px; 89 | top: 5px; 90 | } 91 | 92 | .#{$topSearchbar}-left { 93 | left: 5px; 94 | top: 5px; 95 | } 96 | 97 | .#{$topSearchbar}-center { 98 | top: 5px; 99 | left: 50%; 100 | transform: translate(-50%, 0); 101 | } 102 | 103 | 104 | 105 | /* 106 | * fullscreenSearchbar 107 | */ 108 | $fullscreenSearchbar: fuse-search-fullscreen-searchbar; 109 | 110 | ##{$fullscreenSearchbar} { 111 | visibility: hidden; 112 | position: fixed; 113 | display: block; 114 | width: 100%; 115 | height: 100%; 116 | padding: 10vw; 117 | z-index: 50; 118 | box-sizing: border-box; 119 | text-align: center; 120 | } 121 | 122 | input##{$fullscreenSearchbar}-input { 123 | width: 80%; 124 | font-size: 1.5em; 125 | font-weight: bold; 126 | border: 2px solid #777; 127 | border-radius: 5px; 128 | padding: 3px; 129 | } 130 | 131 | a##{$fullscreenSearchbar}-close { 132 | float: left; 133 | font-size: 32px; 134 | line-height: 28px; 135 | border-radius: 50% 50%; 136 | display: block; 137 | width: 40px; 138 | height: 40px; 139 | padding: 3px; 140 | box-sizing: border-box; 141 | } 142 | 143 | a##{$fullscreenSearchbar}-close:hover { 144 | background-color: #666; 145 | cursor: pointer; 146 | } 147 | 148 | ##{$fullscreenSearchbar}-results { 149 | visibility: inherit; 150 | width: 100%; 151 | margin: 0; 152 | margin-top: 10px; 153 | padding: 0; 154 | 155 | li { 156 | list-style: none; 157 | margin-left: 0em; 158 | 159 | .title { 160 | color: #fff; 161 | font-size: 1.1em; 162 | width: 100%; 163 | display: inline-block; 164 | box-sizing: content-box; 165 | } 166 | 167 | .text { 168 | display: inline-block; 169 | padding-left: 20px; 170 | box-sizing: content-box; 171 | } 172 | } 173 | 174 | a { 175 | width: 100%; 176 | display: inline-block; 177 | text-decoration: none; 178 | padding-bottom: 10px; 179 | } 180 | } 181 | 182 | ##{$fullscreenSearchbar}-results::before { 183 | font-weight: bold; 184 | color: #fff; 185 | content: "..."; 186 | text-align: center; 187 | display: inline-block; 188 | width: 100%; 189 | } 190 | 191 | /* fullscreen-searchbar Themes */ 192 | .#{$fullscreenSearchbar}-, .#{$fullscreenSearchbar}-dark { 193 | background-color: $fsS-dark-background; 194 | 195 | input##{$fullscreenSearchbar}-input { 196 | color: $fsS-dark-inputText; 197 | background-color: $fsS-dark-inputBackground; 198 | } 199 | 200 | a##{$fullscreenSearchbar}-close { 201 | color: $fsS-dark-closeBtn-icon; 202 | border: $fsS-dark-closeBtn-border; 203 | background-color: $fsS-dark-closeBtn-background; 204 | } 205 | 206 | a##{$fullscreenSearchbar}-close:hover { 207 | color: $fsS-dark-closeBtn-icon-hovered; 208 | border: $fsS-dark-closeBtn-border-hovered; 209 | background-color: $fsS-dark-closeBtn-background-hovered; 210 | } 211 | 212 | ##{$fullscreenSearchbar}-results { 213 | li .title { 214 | color: $fsS-dark-resultsTitle; 215 | } 216 | 217 | a { 218 | color: $fsS-dark-resultsText; 219 | } 220 | 221 | a:hover, a:focus { 222 | color: $fsS-dark-resultsText-hovered !important; 223 | background-color: $fsS-dark-hoveredResultbackground !important; 224 | } 225 | } 226 | 227 | ##{$fullscreenSearchbar}-results::before { 228 | color: $fsS-dark-resultsTitle; 229 | } 230 | } 231 | 232 | .#{$fullscreenSearchbar}-light { 233 | background-color: $fsS-light-background; 234 | 235 | input##{$fullscreenSearchbar}-input { 236 | color: $fsS-light-inputText; 237 | background-color: $fsS-light-inputBackground; 238 | } 239 | 240 | a##{$fullscreenSearchbar}-close { 241 | color: $fsS-light-closeBtn-icon; 242 | border: 1px solid $fsS-light-closeBtn-border; 243 | background-color: $fsS-light-closeBtn-background; 244 | } 245 | 246 | a##{$fullscreenSearchbar}-close:hover { 247 | color: $fsS-light-closeBtn-icon-hovered; 248 | border: 1px solid $fsS-light-closeBtn-border-hovered; 249 | background-color: $fsS-light-closeBtn-background-hovered; 250 | } 251 | 252 | ##{$fullscreenSearchbar}-results { 253 | 254 | 255 | li .title { 256 | color: $fsS-light-resultsTitle; 257 | font-weight: bold; 258 | } 259 | 260 | a { 261 | color: $fsS-light-resultsText; 262 | } 263 | 264 | a:hover, a:focus { 265 | color: $fsS-light-resultsText-hovered; 266 | background-color: $fsS-light-hoveredResultbackground; 267 | } 268 | } 269 | 270 | ##{$fullscreenSearchbar}-results::before { 271 | color: $fsS-light-resultsTitle; 272 | } 273 | } 274 | 275 | 276 | 277 | 278 | /* 279 | * inlineSearchbar 280 | */ 281 | $inlineSearchbar: fuse-search-inline-searchbar; 282 | 283 | ##{$inlineSearchbar} { 284 | visibility: hidden; 285 | position: absolute; 286 | display: block; 287 | width: 280px; 288 | z-index: 50; 289 | padding: 5px; 290 | box-sizing: content-box; 291 | background-color: $inlineS-background; 292 | 293 | input##{$inlineSearchbar}-input { 294 | width: 100%; 295 | font-size: 1.5em; 296 | font-weight: bold; 297 | border: 1px solid #333; 298 | padding: 3px; 299 | border-radius: 3px; 300 | box-sizing: border-box; 301 | color: $inlineS-inputText; 302 | background-color: $inlineS-inputBackground; 303 | } 304 | 305 | ##{$inlineSearchbar}-results { 306 | visibility: inherit; 307 | width: 100%; 308 | margin: 0px; 309 | margin-bottom: 10px; 310 | padding: 0; 311 | background-color: $inlineS-resultsBackground; 312 | border-bottom-left-radius: 3px; 313 | border-bottom-right-radius: 3px; 314 | 315 | li { 316 | list-style: none; 317 | margin-left: 15px; 318 | padding-left: 5px; 319 | border-left: $inlineS-resultsRuler; 320 | padding-top: 6px; 321 | padding-bottom: 6px; 322 | 323 | .title { 324 | color: $inlineS-resultsTitle; 325 | font-size: 1.1em; 326 | width: 100%; 327 | display: inline-block; 328 | box-sizing: content-box; 329 | } 330 | 331 | .text { 332 | display: inline-block; 333 | padding-left: 20px; 334 | box-sizing: content-box; 335 | } 336 | 337 | a { 338 | width: 100%; 339 | display: inline-block; 340 | text-decoration: none; 341 | color: $inlineS-resultsText; 342 | } 343 | 344 | a:hover, a:focus { 345 | color: $inlineS-resultsText-hovered !important; 346 | } 347 | } 348 | 349 | li:hover { 350 | background-color: $inlineS-hoveredResultBackground; 351 | } 352 | } 353 | } 354 | 355 | 356 | -------------------------------------------------------------------------------- /harvest.example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Takes files related to this project out of a Hugo 3 | # project which can be used to develop the fuse-search 4 | # code in a live Hugo project server 5 | 6 | # Hugo directory and files. Change as necessary. 7 | hugoProject="../some-hugo-project/themes/some-theme/" 8 | files=( \ 9 | "assets/scss/fuse-search.scss" \ 10 | "assets/scss/_fuse-search-colors.scss" \ 11 | "assets/js/fuse-search.js" \ 12 | "layouts/_default/index.json" \ 13 | "layouts/partials/fuse-search/footer.html" \ 14 | "layouts/partials/fuse-search/head.html" \ 15 | "layouts/shortcodes/fuse-search/inline-searchbar.html" 16 | ) 17 | 18 | # Confirmation 19 | echo "Note: This script will attempt to overwrite the files in this project with files from the $hugoProject directory." 20 | read -p "Are you sure? " -n 1 -r 21 | if [[ ! $REPLY =~ ^[Yy]$ ]] 22 | then 23 | echo 24 | exit 1 25 | fi 26 | echo 27 | 28 | # Execute 29 | for file in "${files[@]}" 30 | do 31 | cp "$hugoProject$file" "$file" 32 | done 33 | 34 | -------------------------------------------------------------------------------- /layouts/_default/index.json: -------------------------------------------------------------------------------- 1 | {{ if eq ($.Param "search.enabled") true }} 2 | {{- $.Scratch.Add "index" slice -}} 3 | {{- range (where .Site.AllPages "Kind" "page") -}} 4 | {{- $page := . -}} 5 | {{- $entry := dict "permalink" .RelPermalink "lang" .Language.Lang "title" .Title "contents" .Plain -}} 6 | {{- range ($.Param "search.searchableParams") -}} 7 | {{- $entry = merge $entry (dict . ($page.Param .)) }} 8 | {{- end -}} 9 | {{- $.Scratch.Add "index" $entry -}} 10 | {{- end -}} 11 | {{- $.Scratch.Get "index" | jsonify -}} 12 | {{- end -}} 13 | -------------------------------------------------------------------------------- /layouts/partials/fuse-search/footer.html: -------------------------------------------------------------------------------- 1 | {{ if eq (.Param "search.enabled") true }} 2 | 3 | 4 | {{ $js := resources.Get "js/fuse.6.4.0.js" }} 5 | {{- $secureJS := $js | resources.Minify | resources.Fingerprint -}} 6 | 7 | {{ $js := resources.Get "js/fuse-search.js" }} 8 | {{- $secureJS := $js | resources.Minify | resources.Fingerprint -}} 9 | 10 | 11 | 12 | 13 | {{- if eq (.Param "search.topSearchbar.enabled") true -}} 14 | 18 | 19 | {{- if eq (.Param "search.keyboardControlled") "topSearchbar" -}} 20 | 21 | {{- end -}} 22 | {{- end -}} 23 | 24 | 25 | {{- if eq (.Param "search.fullscreenSearchbar.enabled") true -}} 26 | 31 | 32 | {{- if eq (.Param "search.keyboardControlled") "fullscreenSearchbar" -}} 33 | 34 | {{- end -}} 35 | {{- end -}} 36 | 37 | {{- end -}} 38 | -------------------------------------------------------------------------------- /layouts/partials/fuse-search/head.html: -------------------------------------------------------------------------------- 1 | {{- if eq (.Param "search.enabled") true -}} 2 | 3 | {{ $style := resources.Get "scss/fuse-search.scss" | resources.ToCSS (dict "outputStyle" "compressed") | resources.Fingerprint }} 4 | 5 | 6 | {{- end -}} 7 | -------------------------------------------------------------------------------- /layouts/shortcodes/fuse-search/inline-searchbar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /meta/anatole.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theys96/hugo-fuse-search/b5b7c4446d762c294bcb15606bd8aa34d290bf00/meta/anatole.gif -------------------------------------------------------------------------------- /meta/fullscreen-searchbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theys96/hugo-fuse-search/b5b7c4446d762c294bcb15606bd8aa34d290bf00/meta/fullscreen-searchbar.png -------------------------------------------------------------------------------- /meta/inline-searchbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theys96/hugo-fuse-search/b5b7c4446d762c294bcb15606bd8aa34d290bf00/meta/inline-searchbar.png -------------------------------------------------------------------------------- /meta/top-searchbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Theys96/hugo-fuse-search/b5b7c4446d762c294bcb15606bd8aa34d290bf00/meta/top-searchbar.png -------------------------------------------------------------------------------- /tests/js/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | yarn-error.log 3 | yarn.lock 4 | 5 | -------------------------------------------------------------------------------- /tests/js/fuse-search.test.js: -------------------------------------------------------------------------------- 1 | // External 2 | const fs = require("fs"); 3 | 4 | // Internal 5 | fuseSearch = require("../../assets/js/fuse-search.js"); 6 | Fuse = require("../../assets/js/fuse.6.4.0.js") 7 | const index = fs.readFileSync('index.json', 'utf8'); 8 | 9 | // Mock HTTP 10 | xhrMock = () => { 11 | mock = { 12 | open : jest.fn(), 13 | send : jest.fn(() => { 14 | setTimeout(() => { 15 | if ("onreadystatechange" in mock) { 16 | mock.onreadystatechange() 17 | } 18 | }, 1000) 19 | }), 20 | setRequestHeader : jest.fn(), 21 | readyState : 4, 22 | status : 200, 23 | responseText : index 24 | } 25 | return mock; 26 | } 27 | window.XMLHttpRequest = jest.fn().mockImplementation(xhrMock) 28 | 29 | // Setup 30 | fuseSearch.setupSearch() 31 | 32 | describe('before initialization', () => { 33 | test('fuseSearch is not init at first', () => { 34 | expect(globalThis.fusesearch.isInit).toBe(false) 35 | }); 36 | }) 37 | 38 | describe('once initialized', () => { 39 | beforeAll(() => { 40 | return globalThis.fusesearch.init() 41 | }); 42 | 43 | test('fuseSearch is init after calling FuseSearch.init()', () => { 44 | expect(globalThis.fusesearch.isInit).toBe(true) 45 | }); 46 | 47 | test('fuse is now available', () => { 48 | expect(globalThis.fusesearch.fuse).not.toBe(false) 49 | }); 50 | 51 | test('there are records', () => { 52 | expect(globalThis.fusesearch.fuse.getIndex().records.length).toBeGreaterThan(0) 53 | }); 54 | 55 | test('search for \'guide\' yields 1 result', () => { 56 | expect(globalThis.fusesearch.fuse.search("guide").length).toBe(1) 57 | }); 58 | 59 | }) 60 | -------------------------------------------------------------------------------- /tests/js/index.json: -------------------------------------------------------------------------------- 1 | [{"categories":null,"contents":"","lang":"en","permalink":"/archives/","tags":null,"title":""},{"categories":["themes","syntax"],"contents":"This article offers a sample of basic Markdown syntax that can be used in Hugo content files, also it shows whether basic HTML elements are decorated with CSS in a Hugo theme.\nHeadings The following HTML \u0026lt;h1\u0026gt;—\u0026lt;h6\u0026gt; elements represent six levels of section headings. \u0026lt;h1\u0026gt; is the highest section level while \u0026lt;h6\u0026gt; is the lowest.\nH1 H2 H3 H4 H5 H6 Paragraph Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.\nItatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.\nBlockquotes The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a footer or cite element, and optionally with in-line changes such as annotations and abbreviations.\nBlockquote without attribution Tiam, ad mint andaepu dandae nostion secatur sequo quae. Note that you can use Markdown syntax within a blockquote.\n Blockquote with attribution Don\u0026rsquo;t communicate by sharing memory, share memory by communicating.— Rob Pike1 Tables Tables aren\u0026rsquo;t part of the core Markdown spec, but Hugo supports supports them out-of-the-box.\n Name Age Bob 27 Alice 23 Inline Markdown within tables Inline  Markdown  In  Table italics bold strikethrough  code Code Blocks Code block with backticks html \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Example HTML5 Document\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt;Test\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Code block indented with four spaces \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt; \u0026lt;title\u0026gt;Example HTML5 Document\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt;Test\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Code block with Hugo\u0026rsquo;s internal highlight shortcode \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Example HTML5 Document\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p\u0026gt;Test\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; List Types Ordered List First item Second item Third item Unordered List List item Another item And another item Nested list Fruit Apple Orange Banana Dairy Milk Cheese Other Elements — abbr, sub, sup, kbd, mark GIFis a bitmap image format.\nH2O\nXn+ Yn= ZnPress CTRL+ALT+Deleteto end the session.\nMost salamandersare nocturnal, and hunt for insects, worms, and other small creatures.\n The above quote is excerpted from Rob Pike\u0026rsquo;s talk during Gopherfest, November 18, 2015. \u0026#x21a9;\u0026#xfe0e;\n ","lang":"en","permalink":"/post/markdown-syntax/","tags":["markdown","css","html","themes"],"title":"Markdown Syntax Guide"},{"categories":null,"contents":"Hugo ships with several Built-in Shortcodes for rich content, along with a Privacy Config and a set of Simple Shortcodes that enable static and no-JS versions of various social media embeds.\n Instagram Simple Shortcode .__h_instagram.card { font-family: -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif; font-size: 14px; border: 1px solid rgb(219, 219, 219); padding: 0; margin-top: 30px; } .__h_instagram.card .card-header, .__h_instagram.card .card-body { padding: 10px 10px 10px; } .__h_instagram.card img { width: 100%; height: auto; } koloot.design View More on Instagram YouTube Privacy Enhanced Shortcode Twitter Simple Shortcode .twitter-tweet { font: 14px/1.45 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif; border-left: 4px solid #2b7bb9; padding-left: 1.5em; color: #555; } .twitter-tweet a { color: #2b7bb9; text-decoration: none; } blockquote.twitter-tweet a:hover, blockquote.twitter-tweet a:focus { text-decoration: underline; } “In addition to being more logical, asymmetry has the advantage that its complete appearance is far more optically effective than symmetry.”\n— Jan Tschichold pic.twitter.com/gcv7SrhvJb\n\u0026mdash; Graphic Design History (@DesignReviewed) January 17, 2019 Vimeo Simple Shortcode .__h_video { position: relative; padding-bottom: 56.23%; height: 0; overflow: hidden; width: 100%; background: #000; } .__h_video img { width: 100%; height: auto; color: #000; } .__h_video .play { height: 72px; width: 72px; left: 50%; top: 50%; margin-left: -36px; margin-top: -36px; position: absolute; cursor: pointer; } ","lang":"en","permalink":"/post/rich-content/","tags":["shortcodes","privacy"],"title":"Rich Content"},{"categories":null,"contents":"Lorem est tota propiore conpellat pectoribus de pectora summo.\nRedit teque digerit hominumque toris verebor lumina non cervice subde tollit usus habet Arctonque, furores quas nec ferunt. Quoque montibus nunc caluere tempus inhospita parcite confusaque translucet patri vestro qui optatis lumine cognoscere flos nubis! Fronde ipsamque patulos Dryopen deorum.\n Exierant elisi ambit vivere dedere Duce pollice Eris modo Spargitque ferrea quos palude Rursus nulli murmur; hastile inridet ut ab gravi sententia! Nomine potitus silentia flumen, sustinet placuit petis in dilapsa erat sunt. Atria tractus malis.\n Comas hunc haec pietate fetum procerum dixit Post torum vates letum Tiresia Flumen querellas Arcanaque montibus omnes Quidem et Vagus elidunt The Van de Graaf Canon\nMane refeci capiebant unda mulcebat Victa caducifer, malo vulnere contra dicere aurato, ludit regale, voca! Retorsit colit est profanae esse virescere furit nec; iaculi matertera et visa est, viribus. Divesque creatis, tecta novat collumque vulnus est, parvas. Faces illo pepulere tempus adest. Tendit flamma, ab opes virum sustinet, sidus sequendo urbis.\nIubar proles corpore raptos vero auctor imperium; sed et huic: manus caeli Lelegas tu lux. Verbis obstitit intus oblectamina fixis linguisque ausus sperare Echionides cornuaque tenent clausit possit. Omnia putatur. Praeteritae refert ausus; ferebant e primus lora nutat, vici quae mea ipse. Et iter nil spectatae vulnus haerentia iuste et exercebat, sui et.\nEurytus Hector, materna ipsumque ut Politen, nec, nate, ignari, vernum cohaesit sequitur. Vel mitis temploque vocatus, inque alis, oculos nomen non silvis corpore coniunx ne displicet illa. Crescunt non unus, vidit visa quantum inmiti flumina mortis facto sic: undique a alios vincula sunt iactata abdita! Suspenderat ego fuit tendit: luna, ante urbem Propoetides parte.\n","lang":"en","permalink":"/post/placeholder-text/","tags":["markdown","text"],"title":"Placeholder Text"},{"categories":null,"contents":"Mathematical notation in a Hugo project can be enabled by using third party JavaScript libraries.\nIn this example we will be using KaTeX\n Create a partial under /layouts/partials/math.html Within this partial reference the Auto-render Extension or host these scripts locally. Include the partial in your templates like so: {{ if or .Params.math .Site.Params.math }} {{ partial \u0026quot;math.html\u0026quot; . }} {{ end }} To enable KaTex globally set the parameter math to true in a project\u0026rsquo;s configuration To enable KaTex on a per page basis include the parameter math: true in content files. Note: Use the online reference of Supported TeX Functions Examples Block math: $$ \\varphi = 1+\\frac{1} {1+\\frac{1} {1+\\frac{1} {1+\\cdots} } } $$\n","lang":"en","permalink":"/post/math-typesetting/","tags":null,"title":"Math Typesetting"},{"categories":null,"contents":"Emoji can be enabled in a Hugo project in a number of ways.\nThe emojify function can be called directly in templates or Inline Shortcodes.\nTo enable emoji globally, set enableEmoji to true in your site’s configuration and then you can type emoji shorthand codes directly in content files; e.g.\nThe Emoji cheat sheet is a useful reference for emoji shorthand codes.\n N.B. The above steps enable Unicode Standard emoji characters and sequences in Hugo, however the rendering of these glyphs depends on the browser and the platform. To style the emoji you can either use a third party emoji font or a font stack; e.g.\n.emoji { font-family: Apple Color Emoji,Segoe UI Emoji,NotoColorEmoji,Segoe UI Symbol,Android Emoji,EmojiSymbols; }","lang":"en","permalink":"/post/emoji-support/","tags":["emoji"],"title":"Emoji Support"},{"categories":null,"contents":"Written in Go, Hugo is an open source static site generator available under the Apache Licence 2.0. Hugo supports TOML, YAML and JSON data file types, Markdown and HTML content files and uses shortcodes to add rich content. Other notable features are taxonomies, multilingual mode, image processing, custom output formats, HTML/CSS/JS minification and support for Sass SCSS workflows.\nHugo makes use of a variety of open source projects including:\n https://github.com/yuin/goldmark https://github.com/alecthomas/chroma https://github.com/muesli/smartcrop https://github.com/spf13/cobra https://github.com/spf13/viper Hugo is ideal for blogs, corporate websites, creative portfolios, online magazines, single page applications or even a website with thousands of pages.\nHugo is for people who want to hand code their own website without worrying about setting up complicated runtimes, dependencies and databases.\nWebsites built with Hugo are extremelly fast, secure and can be deployed anywhere including, AWS, GitHub Pages, Heroku, Netlify and any other hosting provider.\nLearn more and contribute on GitHub.\n","lang":"en","permalink":"/about/","tags":null,"title":"About"}] -------------------------------------------------------------------------------- /tests/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hugo-fuse-search-js-tests", 3 | "version": "1.0.0", 4 | "description": "Tests on the JavaScript in this hugo-fuse-search project (https://github.com/Theys96/hugo-fuse-search)", 5 | "main": "index.js", 6 | "repository": "https://github.com/Theys96/hugo-fuse-search", 7 | "author": "Thijs Havinga", 8 | "license": "MIT", 9 | "devDependencies": { 10 | "jest": "^26.1.0" 11 | }, 12 | "scripts": { 13 | "test": "jest" 14 | }, 15 | "jest": { 16 | "testURL": "http://localhost:1313" 17 | } 18 | } 19 | --------------------------------------------------------------------------------