让 Icarus Insight 搜索插件支持拼音检索文章

让 Icarus Insight 搜索插件支持拼音检索文章

要是 Icarus 能用拼音搜文章就好了,如果你也有这样的想法,那这篇文章也许会对你有帮助。

效果

前提

  1. Icarus 版本 3.0 以上
  2. 搜索插件使用的是 Insight

方法

  1. 保存下面的文件到 themes/icarus/source/js,并重命名为 pinyin.js
    https://github.com/xmflswood/pinyin-match/blob/master/dist/main.js
  2. 保存下面的文件到 themes/icarus/layout/search,并重命名为 insight.jsx
    https://github.com/ppoffice/hexo-component-inferno/raw/master/src/view/search/insight.jsx
  3. 保存下面的文件到 themes/icarus/source/js,并重命名为 insight.js
    https://github.com/ppoffice/hexo-component-inferno/raw/master/asset/js/insight.js
  4. 修改 themes/icarus/layout/search/insight.jsx,加入拼音检索开关和依赖的 pinyin.js
    insight.jsx.diff >folded
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    diff --git a/src/view/search/insight.jsx b/src/view/search/insight.jsx
    index 05a1c40..cce136d 100644
    --- a/src/view/search/insight.jsx
    +++ b/src/view/search/insight.jsx
    @@ -3,7 +3,7 @@
    * @module view/search/insight
    */
    const { Component, Fragment } = require('inferno');
    -const { cacheComponent } = require('../../util/cache');
    +const { cacheComponent } = require('hexo-component-inferno/lib/util/cache');

    /**
    * Algolia search engine JSX component.
    @@ -36,11 +36,18 @@ class Insight extends Component {
    <div class="searchbox-input-container">
    <input type="text" class="searchbox-input" placeholder={translation.hint}/>
    </div>
    + <div class="searchbox-pinyin">
    + <label class="checkbox">
    + <input id="search-by-pinyin" type="checkbox" checked="checked"/>
    + <span>&nbsp;拼音检索</span>
    + </label>
    + </div>
    <a class="searchbox-close" href="javascript:;">&times;</a>
    </div>
    <div class="searchbox-body"></div>
    </div>
    </div>
    + <script src="/js/pinyin.js" defer={true}></script>
    <script src={jsUrl} defer={true}></script>
    <script dangerouslySetInnerHTML={{ __html: js }}></script>
    </Fragment>;
    修改后完整的 insight.jsx 如下:
    insight.jsx >folded
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    /**
    * Insight search plugin JSX component.
    * @module view/search/insight
    */
    const { Component, Fragment } = require('inferno');
    const { cacheComponent } = require('hexo-component-inferno/lib/util/cache');

    /**
    * Algolia search engine JSX component.
    *
    * @example
    * <Insight
    * translation={{
    * hint: '******',
    * untitled: '******',
    * posts: '******',
    * pages: '******',
    * categories: '******',
    * tags: '******'
    * }}
    * contentUrl="/path/to/content.json"
    * jsUrl="/path/to/insight.js" />
    */
    class Insight extends Component {
    render() {
    const { translation, contentUrl, jsUrl } = this.props;

    const js = `document.addEventListener('DOMContentLoaded', function () {
    loadInsight(${JSON.stringify({ contentUrl })}, ${JSON.stringify(translation)});
    });`;

    return <Fragment>
    <div class="searchbox">
    <div class="searchbox-container">
    <div class="searchbox-header">
    <div class="searchbox-input-container">
    <input type="text" class="searchbox-input" placeholder={translation.hint}/>
    </div>
    <div class="searchbox-pinyin">
    <label class="checkbox">
    <input id="search-by-pinyin" type="checkbox" checked="checked"/>
    <span>&nbsp;拼音检索</span>
    </label>
    </div>
    <a class="searchbox-close" href="javascript:;">&times;</a>
    </div>
    <div class="searchbox-body"></div>
    </div>
    </div>
    <script src="/js/imaegoo/pinyin.js" defer={true}></script>
    <script src={jsUrl} defer={true}></script>
    <script dangerouslySetInnerHTML={{ __html: js }}></script>
    </Fragment>;
    }
    }

    /**
    * Cacheable Insight search plugin JSX component.
    * <p>
    * This class is supposed to be used in combination with the <code>locals</code> hexo filter
    * ({@link module:hexo/filter/locals}).
    *
    * @see module:util/cache.cacheComponent
    * @example
    * <Insight.Cacheable
    * helper={{
    * __: function() {...},
    * cdn: function() {...}
    * }} />
    */
    Insight.Cacheable = cacheComponent(Insight, 'search.insight', props => {
    const { helper } = props;

    return {
    translation: {
    hint: helper.__('search.hint'),
    untitled: helper.__('search.untitled'),
    posts: helper._p('common.post', Infinity),
    pages: helper._p('common.page', Infinity),
    categories: helper._p('common.category', Infinity),
    tags: helper._p('common.tag', Infinity)
    },
    contentUrl: helper.url_for('/content.json'),
    jsUrl: helper.url_for('/js/insight.js')
    };
    });

    module.exports = Insight;
  5. 修改 themes/icarus/source/js/insight.js,在原有匹配代码基础上增加拼音匹配代码
    insight.js.diff >folded
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    diff --git a/asset/js/insight.js b/asset/js/insight.js
    index 33150b3..205a096 100644
    --- a/asset/js/insight.js
    +++ b/asset/js/insight.js
    @@ -6,6 +6,19 @@ function loadInsight(config, translation) { // eslint-disable-line no-unused-var
    const $main = $('.searchbox');
    const $input = $main.find('.searchbox-input');
    const $container = $main.find('.searchbox-body');
    + const $searchByPinyin = $main.find('#search-by-pinyin');
    +
    + /**
    + * 查询匹配拼音的数据。性能低于普通匹配,如果未启用拼音检索模式,直接返回 false。
    + * https://github.com/xmflswood/pinyin-match
    + * @param input {string} 目标字符串
    + * @param keyword {string} 输入的拼音或其他关键词
    + * @returns {[Array]|{Boolean}} 找到返回出现位置,未找到 / 未启用返回 false
    + */
    + function pinyinMatch(input, keyword) {
    + if (!$searchByPinyin.prop("checked")) return false;
    + return PinyinMatch.match(input, keyword);
    + }

    function section(title) {
    return $('<section>').addClass('searchbox-result-section').append($('<header>').text(title));
    @@ -33,10 +46,12 @@ function loadInsight(config, translation) { // eslint-disable-line no-unused-var
    const testText = text.toLowerCase();
    const indices = matches.map(match => {
    const index = testText.indexOf(match.toLowerCase());
    - if (!match || index === -1) {
    - return null;
    + if (match && index !== -1) {
    + return [index, index + match.length];
    }
    - return [index, index + match.length];
    + // Search by pinyin
    + const pinyinIndex = pinyinMatch(testText, match.toLowerCase());
    + return pinyinIndex ? [pinyinIndex[0], pinyinIndex[1] + 1] : null;
    }).filter(match => {
    return match !== null;
    }).sort((a, b) => {
    @@ -140,6 +155,8 @@ function loadInsight(config, translation) { // eslint-disable-line no-unused-var
    }
    if (obj[field].toLowerCase().indexOf(keyword) > -1) {
    return true;
    + } else if (pinyinMatch(obj[field].toLowerCase(), keyword)) {
    + return true;
    }
    return false;
    });
    @@ -266,10 +283,12 @@ function loadInsight(config, translation) { // eslint-disable-line no-unused-var
    if (location.hash.trim() === '#insight-search') {
    $main.addClass('show');
    }
    - $input.on('input', function() {
    - const keywords = $(this).val();
    + function onInputChange() {
    + const keywords = $input.val();
    searchResultToDOM(keywords, search(json, keywords));
    - });
    + }
    + $input.on('input', onInputChange);
    + $searchByPinyin.on('change', onInputChange);
    $input.trigger('input');
    });
    修改后完整的 insight.js 如下:
    insight.js >folded
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    /**
    * Insight search plugin
    * @author PPOffice { @link https://github.com/ppoffice }
    */
    function loadInsight(config, translation) { // eslint-disable-line no-unused-vars
    const $main = $('.searchbox');
    const $input = $main.find('.searchbox-input');
    const $container = $main.find('.searchbox-body');
    const $searchByPinyin = $main.find('#search-by-pinyin');

    /**
    * 查询匹配拼音的数据。性能低于普通匹配,如果未启用拼音检索模式,直接返回 false。
    * https://github.com/xmflswood/pinyin-match
    * @param input {string} 目标字符串
    * @param keyword {string} 输入的拼音或其他关键词
    * @returns {[Array]|{Boolean}}
    */
    function pinyinMatch(input, keyword) {
    if (!$searchByPinyin.prop("checked")) return false;
    return PinyinMatch.match(input, keyword);
    }

    function section(title) {
    return $('<section>').addClass('searchbox-result-section').append($('<header>').text(title));
    }

    function merge(ranges) {
    let last;
    const result = [];

    ranges.forEach(r => {
    if (!last || r[0] > last[1]) {
    result.push(last = r);
    } else if (r[1] > last[1]) {
    last[1] = r[1];
    }
    });

    return result;
    }

    function findAndHighlight(text, matches, maxlen) {
    if (!Array.isArray(matches) || !matches.length || !text) {
    return maxlen ? text.slice(0, maxlen) : text;
    }
    const testText = text.toLowerCase();
    const indices = matches.map(match => {
    const index = testText.indexOf(match.toLowerCase());
    if (match && index !== -1) {
    return [index, index + match.length];
    }
    // Search by pinyin
    const pinyinIndex = pinyinMatch(testText, match.toLowerCase());
    return pinyinIndex ? [pinyinIndex[0], pinyinIndex[1] + 1] : null;
    }).filter(match => {
    return match !== null;
    }).sort((a, b) => {
    return a[0] - b[0] || a[1] - b[1];
    });

    if (!indices.length) {
    return text;
    }

    let result = ''; let last = 0;
    const ranges = merge(indices);
    const sumRange = [ranges[0][0], ranges[ranges.length - 1][1]];
    if (maxlen && maxlen < sumRange[1]) {
    last = sumRange[0];
    }

    for (let i = 0; i < ranges.length; i++) {
    const range = ranges[i];
    result += text.slice(last, Math.min(range[0], sumRange[0] + maxlen));
    if (maxlen && range[0] >= sumRange[0] + maxlen) {
    break;
    }
    result += '<em>' + text.slice(range[0], range[1]) + '</em>';
    last = range[1];
    if (i === ranges.length - 1) {
    if (maxlen) {
    result += text.slice(range[1], Math.min(text.length, sumRange[0] + maxlen + 1));
    } else {
    result += text.slice(range[1]);
    }
    }
    }

    return result;
    }

    function searchItem(icon, title, slug, preview, url) {
    title = title != null && title !== '' ? title : translation.untitled;

    return `<a class="searchbox-result-item" href="${url}">
    <span class="searchbox-result-icon">
    <i class="fa fa-${icon}" />
    </span>
    <span class="searchbox-result-content">
    <span class="searchbox-result-title">
    ${title}
    ${slug ? '<span class="searchbox-result-title-secondary">(' + slug + ')</span>' : ''}
    </span>
    ${preview ? '<span class="searchbox-result-preview">' + preview + '</span>' : ''}
    </span>
    </a>`;
    }

    function sectionFactory(keywords, type, array) {
    let $searchItems;
    if (array.length === 0) return null;
    const sectionTitle = translation[type.toLowerCase()];
    switch (type) {
    case 'POSTS':
    case 'PAGES':
    $searchItems = array.map(item => {
    const title = findAndHighlight(item.title, keywords);
    const text = findAndHighlight(item.text, keywords, 100);
    return searchItem('file', title, null, text, item.link);
    });
    break;
    case 'CATEGORIES':
    case 'TAGS':
    $searchItems = array.map(item => {
    const name = findAndHighlight(item.name, keywords);
    const slug = findAndHighlight(item.slug, keywords);
    return searchItem(type === 'CATEGORIES' ? 'folder' : 'tag', name, slug, null, item.link);
    });
    break;
    default:
    return null;
    }
    return section(sectionTitle).append($searchItems);
    }

    function parseKeywords(keywords) {
    return keywords.split(' ').filter(keyword => {
    return !!keyword;
    }).map(keyword => {
    return keyword.toLowerCase();
    });
    }

    /**
    * Judge if a given post/page/category/tag contains all of the keywords.
    * @param Object obj Object to be weighted
    * @param Array<String> fields Object's fields to find matches
    */
    function filter(keywords, obj, fields) {
    const keywordArray = parseKeywords(keywords);
    const containKeywords = keywordArray.filter(keyword => {
    const containFields = fields.filter(field => {
    if (!Object.prototype.hasOwnProperty.call(obj, field)) {
    return false;
    }
    if (obj[field].toLowerCase().indexOf(keyword) > -1) {
    return true;
    } else if (pinyinMatch(obj[field].toLowerCase(), keyword)) {
    return true;
    }
    return false;
    });
    if (containFields.length > 0) {
    return true;
    }
    return false;
    });
    return containKeywords.length === keywordArray.length;
    }

    function filterFactory(keywords) {
    return {
    post: function(obj) {
    return filter(keywords, obj, ['title', 'text']);
    },
    page: function(obj) {
    return filter(keywords, obj, ['title', 'text']);
    },
    category: function(obj) {
    return filter(keywords, obj, ['name', 'slug']);
    },
    tag: function(obj) {
    return filter(keywords, obj, ['name', 'slug']);
    }
    };
    }

    /**
    * Calculate the weight of a matched post/page/category/tag.
    * @param Object obj Object to be weighted
    * @param Array<String> fields Object's fields to find matches
    * @param Array<Integer> weights Weight of every field
    */
    function weight(keywords, obj, fields, weights) {
    let value = 0;
    parseKeywords(keywords).forEach(keyword => {
    const pattern = new RegExp(keyword, 'img'); // Global, Multi-line, Case-insensitive
    fields.forEach((field, index) => {
    if (Object.prototype.hasOwnProperty.call(obj, field)) {
    const matches = obj[field].match(pattern);
    value += matches ? matches.length * weights[index] : 0;
    }
    });
    });
    return value;
    }

    function weightFactory(keywords) {
    return {
    post: function(obj) {
    return weight(keywords, obj, ['title', 'text'], [3, 1]);
    },
    page: function(obj) {
    return weight(keywords, obj, ['title', 'text'], [3, 1]);
    },
    category: function(obj) {
    return weight(keywords, obj, ['name', 'slug'], [1, 1]);
    },
    tag: function(obj) {
    return weight(keywords, obj, ['name', 'slug'], [1, 1]);
    }
    };
    }

    function search(json, keywords) {
    const weights = weightFactory(keywords);
    const filters = filterFactory(keywords);
    const posts = json.posts;
    const pages = json.pages;
    const tags = json.tags;
    const categories = json.categories;
    return {
    posts: posts.filter(filters.post).sort((a, b) => { return weights.post(b) - weights.post(a); }).slice(0, 5),
    pages: pages.filter(filters.page).sort((a, b) => { return weights.page(b) - weights.page(a); }).slice(0, 5),
    categories: categories.filter(filters.category).sort((a, b) => { return weights.category(b) - weights.category(a); }).slice(0, 5),
    tags: tags.filter(filters.tag).sort((a, b) => { return weights.tag(b) - weights.tag(a); }).slice(0, 5)
    };
    }

    function searchResultToDOM(keywords, searchResult) {
    $container.empty();
    for (const key in searchResult) {
    $container.append(sectionFactory(parseKeywords(keywords),
    key.toUpperCase(), searchResult[key]));
    }
    }

    function scrollTo($item) {
    if ($item.length === 0) return;
    const wrapperHeight = $container[0].clientHeight;
    const itemTop = $item.position().top - $container.scrollTop();
    const itemBottom = $item[0].clientHeight + $item.position().top;
    if (itemBottom > wrapperHeight + $container.scrollTop()) {
    $container.scrollTop(itemBottom - $container[0].clientHeight);
    }
    if (itemTop < 0) {
    $container.scrollTop($item.position().top);
    }
    }

    function selectItemByDiff(value) {
    const $items = $.makeArray($container.find('.searchbox-result-item'));
    let prevPosition = -1;
    $items.forEach((item, index) => {
    if ($(item).hasClass('active')) {
    prevPosition = index;

    }
    });
    const nextPosition = ($items.length + prevPosition + value) % $items.length;
    $($items[prevPosition]).removeClass('active');
    $($items[nextPosition]).addClass('active');
    scrollTo($($items[nextPosition]));
    }

    function gotoLink($item) {
    if ($item && $item.length) {
    location.href = $item.attr('href');
    }
    }

    $.getJSON(config.contentUrl, json => {
    if (location.hash.trim() === '#insight-search') {
    $main.addClass('show');
    }
    function onInputChange() {
    const keywords = $input.val();
    searchResultToDOM(keywords, search(json, keywords));
    }
    $input.on('input', onInputChange);
    $searchByPinyin.on('change', onInputChange);
    $input.trigger('input');
    });

    let touch = false;
    $(document).on('click focus', '.navbar-main .search', () => {
    $main.addClass('show');
    $main.find('.searchbox-input').focus();
    }).on('click touchend', '.searchbox-result-item', function(e) {
    if (e.type !== 'click' && !touch) {
    return;
    }
    gotoLink($(this));
    touch = false;
    }).on('click touchend', '.searchbox-close', e => {
    if (e.type !== 'click' && !touch) {
    return;
    }
    $('.navbar-main').css('pointer-events', 'none');
    setTimeout(() => {
    $('.navbar-main').css('pointer-events', 'auto');
    }, 400);
    $main.removeClass('show');
    touch = false;
    }).on('keydown', e => {
    if (!$main.hasClass('show')) return;
    switch (e.keyCode) {
    case 27: // ESC
    $main.removeClass('show'); break;
    case 38: // UP
    selectItemByDiff(-1); break;
    case 40: // DOWN
    selectItemByDiff(1); break;
    case 13: // ENTER
    gotoLink($container.find('.searchbox-result-item.active').eq(0)); break;
    }
    }).on('touchstart', e => {
    touch = true;
    }).on('touchmove', e => {
    touch = false;
    });
    }
  6. 修改 themes/icarus/include/style/search.styl,调整拼音检索复选框大小和位置
    search.styl.diff >folded
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    diff --git a/include/style/search.styl b/include/style/search.styl
    index 0cdedd9..02d9b5e 100644
    --- a/include/style/search.styl
    +++ b/include/style/search.styl
    @@ -96,6 +96,16 @@ $searchbox-bg-pagination-item-disabled ?= $searchbox-bg-container
    padding: .75em 0 .75em 1.25em
    background: $searchbox-bg-input

    + .searchbox-pinyin
    + display: flex
    + align-items: center
    + user-select: none
    + input
    + vertical-align: middle
    + span
    + position: relative
    + top: 1px
    +
    .searchbox-close
    display: inline-block
    font-size: 1.5em
    修改后完整的 search.styl 如下:
    search.styl >folded
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    /* ---------------------------------
    * Search Box
    * --------------------------------- */
    // container sizes
    $searchbox-container-width ?= 540px
    $searchbox-container-margin ?= 100px
    $searchbox-breakpoint-width ?= 559px
    $searchbox-breakpoint-height ?= 479px
    // overlay and container styles
    $searchbox-box-shadow ?= $card-shadow
    $searchbox-border-radius ?= $radius
    $searchbox-bg-overlay ?= $modal-background-background-color
    $searchbox-bg-container ?= $white-ter
    $searchbox-border ?= $border
    // header styles
    $searchbox-bg-input ?= $white
    $searchbox-bg-close-hover ?= $searchbox-bg-container
    $searchbox-bg-close-active ?= $grey-lighter
    // body styles
    $searchbox-fg-result-header ?= $grey-light
    $searchbox-fg-result-item-secondary ?= $grey-light
    $searchbox-bg-result-item-hover ?= $searchbox-bg-input
    $searchbox-fg-result-item-active ?= findColorInvert($primary)
    $searchbox-bg-result-item-active ?= $primary
    $searchbox-bg-result-item-highlight ?= $yellow
    // footer styles
    $searchbox-bg-pagination-item ?= $searchbox-bg-input
    $searchbox-bg-pagination-item-hover ?= $searchbox-bg-container
    $searchbox-fg-pagination-item-active ?= findColorInvert($primary)
    $searchbox-bg-pagination-item-active ?= $primary
    $searchbox-bg-pagination-item-disabled ?= $searchbox-bg-container

    .searchbox
    display: none
    top: 0
    left: 0
    width: 100%
    height: 100%
    z-index: 100
    font-size: 1rem
    line-height: 0
    background: $searchbox-bg-overlay

    &.show
    display: flex

    a, a:hover
    color: inherit
    text-decoration: none

    input
    font-size: 1rem
    border: none
    outline: none
    box-shadow: none
    border-radius: 0

    &, .searchbox-container
    position: fixed
    align-items: center
    flex-direction: column
    line-height: 1.25em

    .searchbox-container
    z-index: 101
    display: flex
    overflow: hidden
    box-shadow: $searchbox-box-shadow
    border-radius: $searchbox-border-radius
    background-color: $searchbox-bg-container
    width: $searchbox-container-width
    top: $searchbox-container-margin
    bottom: $searchbox-container-margin

    .searchbox-header, .searchbox-body, .searchbox-footer
    width: 100%

    .searchbox-header
    display: flex
    flex-direction: row
    line-height: 1.5em
    font-weight: normal
    background-color: $searchbox-bg-input
    // fix Chrome 71 height issue
    // https://github.com/ppoffice/hexo-theme-icarus/issues/719
    min-height: 3rem

    .searchbox-input-container
    display: flex
    flex-grow: 1

    .searchbox-input
    flex-grow: 1
    color: inherit
    box-sizing: border-box
    padding: .75em 0 .75em 1.25em
    background: $searchbox-bg-input

    .searchbox-close
    display: inline-block
    font-size: 1.5em
    padding: .5em .75em
    cursor: pointer

    &:hover
    background: $searchbox-bg-close-hover

    &:active
    background: $searchbox-bg-close-active

    .searchbox-body
    flex-grow: 1
    overflow-y: auto
    border-top: 1px solid $searchbox-border

    .searchbox-result-section header, .searchbox-result-item
    padding: .75em 1em

    .searchbox-result-section
    border-bottom: 1px solid $searchbox-border

    header
    color: $searchbox-fg-result-header

    .searchbox-result-item
    display: flex
    flex-direction: row

    &:not(.disabled):not(.active):not(:active):hover
    background-color: $searchbox-bg-result-item-hover

    &:active, &.active
    color: $searchbox-fg-result-item-active
    background-color: $searchbox-bg-result-item-active

    em
    font-style: normal
    background: $searchbox-bg-result-item-highlight

    .searchbox-result-icon
    margin-right: 1em

    .searchbox-result-content
    overflow: hidden

    .searchbox-result-title, .searchbox-result-preview
    display: block
    overflow: hidden
    white-space: nowrap
    text-overflow: ellipsis

    .searchbox-result-title-secondary
    color: $searchbox-fg-result-item-secondary

    .searchbox-result-preview
    margin-top: .25em

    .searchbox-result-item:not(:active):not(.active)
    .searchbox-result-preview
    color: $searchbox-fg-result-item-secondary

    .searchbox-footer
    padding: .5em 1em

    .searchbox-pagination
    margin: 0
    padding: 0
    list-style: none
    text-align: center

    .searchbox-pagination-item
    margin: 0 .25rem

    .searchbox-pagination-item, .searchbox-pagination-link
    display: inline-block

    .searchbox-pagination-link
    overflow: hidden
    padding: .5em .8em
    box-shadow: $searchbox-box-shadow
    border-radius: $searchbox-border-radius
    background-color: $searchbox-bg-pagination-item

    .searchbox-pagination-item.active
    .searchbox-pagination-link
    color: $searchbox-fg-pagination-item-active
    background-color: $searchbox-bg-pagination-item-active

    .searchbox-pagination-item.disabled
    .searchbox-pagination-link
    cursor: not-allowed
    background-color: $searchbox-bg-pagination-item-disabled

    .searchbox-pagination-item:not(.active):not(.disabled)
    .searchbox-pagination-link:hover
    background-color: $searchbox-bg-pagination-item-hover

    @media screen and (max-width: $searchbox-breakpoint-width), screen and (max-height: $searchbox-breakpoint-height)
    .searchbox .searchbox-container
    top: 0
    left: 0
    width: 100%
    height: 100%
    border-radius: 0

感谢

xmflswood 提供的 pinyin-match

   Landscape Vectors by Vecteezy

评论