/*! * Copyright (C) 2019 Josh Habdas * * This file is part of After Dark. * * After Dark is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * After Dark is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ fetchInject([ "{{ "/js/vue.min.js" | relURL }}", "{{ "/js/lodash.custom.min.js" | relURL }}", "{{ "/js/fuse.min.js" | relURL }}", "{{ "/js/mark.min.js" | relURL }}" ]).then(() => { (function (window, document, undefined) { 'use strict'; const getQueryByParam = param => decodeURIComponent( (location.search.split(param + '=')[1] || '').split('&')[0] ).replace(/\+/g, ' '); const queryParam = 's'; const hotkeys = {{ (.Params.form.hotkeys | default (slice "/" "s")) | jsonify }}; const selectors = { appContainer: '#search-app', resultContainer: '#search-results', searchInput: '#query' }; const fuseOpts = { shouldSort: true, tokenize: true, matchAllTokens: true, includeScore: true, includeMatches: true, keys: [ { name: "title", weight: 0.8 }, { name: "contents", weight: 0.5 }, { name: "tags", weight: 0.3 }, { name: "categories", weight: 0.3 } ] }; const getSearchInput = () => document.querySelector(selectors.searchInput); const focusSearchInput = () => getSearchInput().focus(); const searchQuery = getSearchInput().value = getQueryByParam(queryParam); const fuse = new Fuse([], fuseOpts); window.fetch('/index.json').then(response => { response.text().then(searchData => { fuse.setCollection(JSON.parse(searchData)); searchQuery && search(searchQuery); }); }); const getUrl = (query) => { const encodedQuery = encodeURIComponent(query); const url = "{{ .RelPermalink }}"; return (encodedQuery) ? `${url}?${queryParam}=${encodedQuery}` : url; }; let mark = new Mark( document.querySelector( selectors.resultContainer ) ); const app = new Vue({ delimiters: ['{', '}'], el: selectors.appContainer, data: { fuse: null, results: [], query: getQueryByParam(queryParam), resultsForSearch: getQueryByParam(queryParam) }, mounted () { this.fuse = fuse; window.onpopstate = (evt) => { this.query = evt.state.query; }; const searchInput = getSearchInput(); document.onkeydown = function (evt) { if (evt.target === searchInput) return; if (hotkeys.includes(evt.key)) { evt.preventDefault(); focusSearchInput(); getSearchInput().select(); }; } focusSearchInput(); }, watch: { query () { this.executeSearch(); window.history.replaceState( {query: this.query}, null, getUrl(this.query) ); } }, beforeUpdate: function () { mark.unmark(); }, updated: function () { this.$nextTick(function () { mark = new Mark( document.querySelector( selectors.resultContainer ) ) mark.mark(this.query.trim()); }) }, methods: { executeSearch: _.debounce(function () { const trimmedQuery = this.query.trim(); this.resultsForSearch = trimmedQuery; this.results = (trimmedQuery) ? this.fuse.search(trimmedQuery) : []; }, 250) } }); const search = query => { app.results = fuse.search(query); }; })(window, document); });