给你的Jekyll博客加一个站内搜索 2


  使用 Simple-Jekyll-Search 为你的 Jekyll 博客添加站内搜索。

  如前文所述,Jekyll-Search 使用了 jQuery 和 BootStrap,需要很多额外的 js 库,所以我又找到了一个更简单的搜索实现方法 Simple-Jekyll-Search。

  项目地址:https://github.com/christian-fei/Simple-Jekyll-Search

  我在该项目基础上进行了一些修改,修复了一个bug,美化了一下样式。

  js+css 实现无文字时自动隐藏列表,移动设备自动展开列表,效果参考左上角。

  JS 部分:

/*
 * Simple-Jekyll-Search v1.7.5
 * Copyright 2015-2020 Christian Fei
 * Copyright 2020 YexuanXiao
 * Licensed under the MIT License.
 */
 (function() {
    'use strict'
    var _$Templater_7 = {
        compile: compile,
        setOptions: setOptions
    }
    var options = {}
    options.pattern = /\{(.*?)\}/g
    options.template = ''
    options.middleware = function() {}
    function setOptions(_options) {
        options.pattern = _options.pattern || options.pattern
        options.template = _options.template || options.template
        if (typeof _options.middleware === 'function') {
            options.middleware = _options.middleware
        }
    }
    function compile(data) {
        return options.template.replace(options.pattern, function(match, prop) {
            var value = options.middleware(prop, data[prop], options.template)
            if (typeof value !== 'undefined') {
                return value
            }
            return data[prop] || match
        })
    }
    'use strict';
    function fuzzysearch(needle, haystack) {
        var tlen = haystack.length;
        var qlen = needle.length;
        if (qlen > tlen) {
            return false;
        }
        if (qlen === tlen) {
            return needle === haystack;
        }
        outer: for (var i = 0, j = 0; i < qlen; i++) {
            var nch = needle.charCodeAt(i);
            while (j < tlen) {
                if (haystack.charCodeAt(j++) === nch) {
                    continue outer;
                }
            }
            return false;
        }
        return true;
    }
    var _$fuzzysearch_1 = fuzzysearch;
    'use strict'
    /* removed: var _$fuzzysearch_1 = require('fuzzysearch') */
    ;
    var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
    function FuzzySearchStrategy() {
        this.matches = function(string, crit) {
            return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
        }
    }
    'use strict'
    var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
    function LiteralSearchStrategy() {
        this.matches = function(str, crit) {
            if (!str) return false
            str = str.trim().toLowerCase()
            crit = crit.trim().toLowerCase()
            return crit.split(' ').filter(function(word) {
                return str.indexOf(word) >= 0
            }).length === crit.split(' ').length
        }
    }
    'use strict'
    var _$Repository_4 = {
        put: put,
        clear: clear,
        search: search,
        setOptions: __setOptions_4
    }
    /* removed: var _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */
    ;
    /* removed: var _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */
    ;
    function NoSort() {
        return 0
    }
    var data = []
    var opt = {}
    opt.fuzzy = false
    opt.limit = 10
    opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
    opt.sort = NoSort
    function put(data) {
        if (isObject(data)) {
            return addObject(data)
        }
        if (isArray(data)) {
            return addArray(data)
        }
        return undefined
    }
    function clear() {
        data.length = 0
        return data
    }
    function isObject(obj) {
        return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
    }
    function isArray(obj) {
        return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
    }
    function addObject(_data) {
        data.push(_data)
        return data
    }
    function addArray(_data) {
        var added = []
        clear()
        for (var i = 0, len = _data.length; i < len; i++) {
            if (isObject(_data[i])) {
                added.push(addObject(_data[i]))
            }
        }
        return added
    }
    function search(crit) {
        if (!crit) {
            return []
        }
        return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
    }
    function __setOptions_4(_opt) {
        opt = _opt || {}
        opt.fuzzy = _opt.fuzzy || false
        opt.limit = _opt.limit || 10
        opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
        opt.sort = _opt.sort || NoSort
    }
    function findMatches(data, crit, strategy, opt) {
        var matches = []
        for (var i = 0; i < data.length && matches.length < opt.limit; i++) {
            var match = findMatchesInObject(data[i], crit, strategy, opt)
            if (match) {
                matches.push(match)
            }
        }
        return matches
    }
    function findMatchesInObject(obj, crit, strategy, opt) {
        for (var key in obj) {
            if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
                return obj
            }
        }
    }
    function isExcluded(term, excludedTerms) {
        var excluded = false
        excludedTerms = excludedTerms || []
        for (var i = 0, len = excludedTerms.length; i < len; i++) {
            var excludedTerm = excludedTerms[i]
            if (!excluded && new RegExp(term).test(excludedTerm)) {
                excluded = true
            }
        }
        return excluded
    }
    /* globals ActiveXObject:false */
    'use strict'
    var _$JSONLoader_2 = {
        load: load
    }
    function load(location, callback) {
        var xhr = getXHR()
        xhr.open('GET', location, true)
        xhr.onreadystatechange = createStateChangeListener(xhr, callback)
        xhr.send()
    }
    function createStateChangeListener(xhr, callback) {
        return function() {
            if (xhr.readyState === 4 && xhr.status === 200) {
                try {
                    callback(null, JSON.parse(xhr.responseText))
                } catch (err) {
                    callback(err, null)
                }
            }
        }
    }
    function getXHR() {
        return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
    }
    'use strict'
    var _$OptionsValidator_3 = function OptionsValidator(params) {
        if (!validateParams(params)) {
            throw new Error('-- OptionsValidator: required options missing')
        }
        if (!(this instanceof OptionsValidator)) {
            return new OptionsValidator(params)
        }
        var requiredOptions = params.required
        this.getRequiredOptions = function() {
            return requiredOptions
        }
        this.validate = function(parameters) {
            var errors = []
            requiredOptions.forEach(function(requiredOptionName) {
                if (typeof parameters[requiredOptionName] === 'undefined') {
                    errors.push(requiredOptionName)
                }
            })
            return errors
        }
        function validateParams(params) {
            if (!params) {
                return false
            }
            return typeof params.required !== 'undefined' && params.required instanceof Array
        }
    }
    'use strict'
    var _$utils_9 = {
        merge: merge,
        isJSON: isJSON
    }
    function merge(defaultParams, mergeParams) {
        var mergedOptions = {}
        for (var option in defaultParams) {
            mergedOptions[option] = defaultParams[option]
            if (typeof mergeParams[option] !== 'undefined') {
                mergedOptions[option] = mergeParams[option]
            }
        }
        return mergedOptions
    }
    function isJSON(json) {
        try {
            if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
                return true
            }
            return false
        } catch (err) {
            return false
        }
    }
    var _$src_8 = {};
    (function(window) {
        'use strict'
        var options = {
            searchInput: null,
            resultsContainer: null,
            json: [],
            success: Function.prototype,
            searchResultTemplate: '<li class="menu-list" style="margin: .3em 1em 0 1em !important"><a href="{url}">{title}</a></li>',
            templateMiddleware: Function.prototype,
            sortMiddleware: function() {
                return 0
            },
            noResultsText: 'No results found',
            limit: 10,
            fuzzy: false,
            exclude: []
        }
        var requiredOptions = ['searchInput', 'resultsContainer', 'json']
        /* removed: var _$Templater_7 = require('./Templater') */
        ;
        /* removed: var _$Repository_4 = require('./Repository') */
        ;
        /* removed: var _$JSONLoader_2 = require('./JSONLoader') */
        ;
        var optionsValidator = _$OptionsValidator_3({
                required: requiredOptions
            })
            /* removed: var _$utils_9 = require('./utils') */
        ;
        var simpleJekyllSearch = function(_options) {
            var errors = optionsValidator.validate(_options)
            if (errors.length > 0) {
                throwError('You must specify the following required options: ' + requiredOptions)
            }
            options = _$utils_9.merge(options, _options)
            _$Templater_7.setOptions({
                template: options.searchResultTemplate,
                middleware: options.templateMiddleware
            })
            _$Repository_4.setOptions({
                fuzzy: options.fuzzy,
                limit: options.limit,
                sort: options.sortMiddleware
            })
            if (_$utils_9.isJSON(options.json)) {
                initWithJSON(options.json)
            } else {
                initWithURL(options.json)
            }
            return {
                search: search
            }
        }
        window.SimpleJekyllSearch = function(_options) {
            var search = simpleJekyllSearch(_options)
            options.success.call(search)
            return search
        }
        function initWithJSON(json) {
            _$Repository_4.put(json)
            registerInput()
        }
        function initWithURL(url) {
            _$JSONLoader_2.load(url, function(err, json) {
                if (err) {
                    throwError('failed to get JSON (' + url + ')')
                }
                initWithJSON(json)
            })
        }
        function emptyResultsContainer() {
            options.resultsContainer.innerHTML = ''
        }
        function appendToResultsContainer(text) {
            options.resultsContainer.innerHTML += text
        }
        function registerInput() {
            options.searchInput.addEventListener('keyup', function(e) {
                if (isWhitelistedKey(e.which)) {
                    emptyResultsContainer()
                    search(e.target.value)
                }
            })
        }
        function search(query) {
            if (isValidQuery(query)) {
                emptyResultsContainer()
                render(_$Repository_4.search(query), query)
            }
        }
        function render(results, query) {
            var len = results.length;
			document.getElementById('search-menu').removeAttribute("style");
            if (len === 0) {
                var child = document.createElement("li");
                var element = document.getElementById("results-container");
                element.appendChild(child);
                child.setAttribute('class', 'menu-list');
                document.getElementsByClassName('menu-list')[0].innerHTML = options.noResultsText;
            }
            for (var i = 0; i < len; i++) {
                results[i].query = query
                appendToResultsContainer(_$Templater_7.compile(results[i]))
            }
        }
        function isValidQuery(query) {
            return query && query.length > 0
        }
        function isWhitelistedKey(key) {
            return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
        }
        function throwError(message) {
            throw new Error('SimpleJekyllSearch --- ' + message)
        }
    })(window)
}());

var sjs = SimpleJekyllSearch({
    searchInput: document.getElementById('search-text'), //定义搜索框
    resultsContainer: document.getElementById('results-container'),
    json: '/assets/search.json' //定义json位置
})

/*
 * Copyright 2016 - 2020 YexuanXiao
 * Licensed under the MIT License.
 */
// 自动控制搜索结果菜单隐藏与显示

document.getElementById('search-menu').style.setProperty('display', 'none')
function checkInput() {
    var inputValue = document.getElementById("search-text").value;
    var searchMenu = document.getElementById('search-menu');
    if (inputValue == "" || inputValue == null || inputValue == undefined) {
        searchMenu.style.display = "none";
    } else {
        searchMenu.style.display = "block";
    }
}
// 空白处单击隐藏菜单
document.addEventListener("click", event => {
	var cDom = document.querySelector("#search-panel");
	var tDom = event.target;
	var searchMenu = document.getElementById('search-menu');
	if (cDom == tDom || cDom.contains(tDom)) {
	} else {
		searchMenu.style.display = "none";
	}
});

  HTML 部分:

<div class="panel-block" style="border:none">
	<span class="control" id="search-panel">
		<input class="input is-small is-primary is-info" type="text" placeholder="Find an article"
		id="search-text" onkeyup="checkInput()">
		<aside id="search-menu" class="menu">
			<div class="menu-lebal">
				<ul id="results-container">
				</ul>
			</div>
		</aside>
	</span>
</div>

  CSS 部分:

#search-menu {
	position:absolute;
	background:white;
	border-left:1px solid #dbdbdb;
	border-bottom:1px solid #dbdbdb;
	border-right:1px solid #dbdbdb;
	border-radius:0 0 3px 3px;
	padding:0 !important;
	padding:0 .5em 0 .5em;
}
#search-panel {
	min-width:12em;
	max-width:25em;
	position:relative;
	display:block;
}
.menu-list {
	font-size:1em !important;
	text-align:left !important;
	margin:0 .5em 0 .5em !important;
	white-space:nowrap;
}
#results-container {
	padding:0 0 .5em 0;
}
@media screen and (max-width:768px) {
	#search-menu {
	left:0;
	right:0;
}
#search-panel {
	position:initial;
}

  JSON 部分

  将该 json 放入任意静态文件夹,并且修改 js 部分注释位置的定义 json 位置,引入 CSS, HTML, JS 即可实现搜索,注意 CSS 样式的覆盖。


---
layout: null
---
[
  {% for post in site.posts %} {
      "title"    : "{{ post.title | escape }}",
      "category" : "{{ post.categories }}",
      "tags"     : "{{ post.tags | join: ', ' }}",
      "url"      : "{{ site.baseurl }}{{ post.url }}",
      "date"     : "{{ post.date }}",
      "content"  : "{{ post.excerpt | strip_html | escape | strip_newlines | remove: "  " }}"
    } {% unless forloop.last %},{% endunless %}{% endfor %} 
]