# 安装插件

本地搜索通过 hexo-generator-searchdb 插件实现,需要先安装插件。

如果安装不成功就使用 yarn 安装

npm install hexo-generator-searchdb

# 修改 page.js

shoka/source/js/_app/page.js 中 localSearch () 修改后的文件如下,将整个 localSearch 复制到主题的 shoka/source/js/_app/page.js 中即可。

const localSearch = function(pjax) {
  // 参考 hexo next 主题的配置方法
  // 参考 https://qiuyiwu.github.io/2019/01/25/Hexo-LocalSearch/ 博文
  if(CONFIG.localSearch === null)
    return
  if(!siteSearch) {
    siteSearch = BODY.createChild('div', {
      id: 'search',
      innerHTML: '<div class="inner"><div class="header"><span class="icon"><i class="ic i-search"></i></span><div class="search-input-container"><input class="search-input"autocompvare="off"placeholder="'+LOCAL.search.placeholder+'"spellcheck="false"type="text"id="local-search-input"></div><span class="close-btn"><i class="ic i-times-circle"></i></span></div><div class="results"id="search-results"><div class="inner"><div id="search-stats"></div><div id="search-hits"></div><div id="search-pagination"></div></div></div></div></div>'
    });
  }
  
  var isFetched = false;
  var datas;
  var isXml = true;
  var current_page = 0;
  var pageSize = parseInt(CONFIG.localSearch.pageSize, 10);
  if(isNaN(pageSize)) pageSize = 10;
  var total_pages = 0;
  var max_page_on_show = 7; // 一次最多显示 7 个页码
  var start_page = 0;
  var end_page = 0;
  var resultItems = [];
  // search DB path
  var searchPath = CONFIG.localSearch.path;
  if (searchPath.length == 0) {
    searchPath = 'search.xml';
  } else if (searchPath.endsWith('json')) {
    isXml = false;
  }
  const input = $('.search-input'); // document.querySelector('.search-input');
  const resultContent = document.getElementById('search-hits');
  const paginationContent = document.getElementById('search-pagination');
  const getIndexByWord = function(word, text, caseSensitive) {
    if (CONFIG.localSearch.unescape) {
      var div = document.createElement('div');
      div.innerText = word;
      word = div.innerHTML;
    }
    var wordLen = word.length;
    if (wordLen === 0) {
      return [];
    }
    var startPosition = 0;
    var position = [];
    var index = [];
    if (!caseSensitive) {
      text = text.toLowerCase();
      word = word.toLowerCase();
    }
    while ((position = text.indexOf(word, startPosition)) > -1) {
      index.push({position:position, word:word});
      startPosition = position + wordLen;
    }
    return index;
  };
  // Merge hits into slices
  const mergeIntoSlice = function(start, end, index, searchText) {
    var item = index[index.length - 1];
    var position = item.position;
    var word = item.word;
    var hits = [];
    var searchTextCountInSlice = 0;
    while (position + word.length <= end && index.length !== 0) {
      if (word === searchText) {
        searchTextCountInSlice++;
      }
      hits.push({
        position:position,
        length: word.length
      });
      var wordEnd = position + word.length;
      // Move to next position of hit
      index.pop();
      while (index.length !== 0) {
        item = index[index.length - 1];
        position = item.position;
        word = item.word;
        if (wordEnd > position) {
          index.pop();
        } else {
          break;
        }
      }
    }
    return {
      hits:hits,
      start:start,
      end:end,
      searchTextCount: searchTextCountInSlice
    };
  }
  // Highlight title and content
  const highlightKeyword = function(text, slice) {
    var result = '';
    var prevEnd = slice.start;
    slice.hits.forEach(function(hit)  {
      result += text.substring(prevEnd, hit.position);
      var end = hit.position + hit.length;
      result += '<mark>'+ text.substring(hit.position, end)+'</mark>';
      prevEnd = end;
    });
    result += text.substring(prevEnd, slice.end);
    return result;
  };
  const pagination = function() {
    
    const addPrevPage = function(current_page) {
      var classContent = '';
      var numberContent = '';
      if (current_page === 0) {
        classContent = '#search-pagination pagination-item disabled-item';
        numberContent = '<span class="#search-pagination page-number"><i class="ic i-angle-left"></i></span>';
      } else {
        classContent = '#search-pagination pagination-item';
        numberContent = '<a class="#search-pagination page-number" aria-label="Prev" href="#"><i class="ic i-angle-left"></i></a>';
      }
      var prevPage = '<li class="'+ classContent +'" id="prev-page">'+ numberContent+'</li>';
      return prevPage;
    };
    const addNextPage = function(current_page) {
      var classContent = '';
      var numberContent = '';
      if ((current_page + 1) === total_pages) {
        classContent = '#search-pagination pagination-item disabled-item';
        numberContent = '<span class="#search-pagination page-number"><i class="ic i-angle-right"></i></span>';
      } else {
        classContent = '#search-pagination pagination-item';
        numberContent = '<a class="#search-pagination page-number"aria-label="Next"href="#"><i class="ic i-angle-right"></i></a>';
      }
      var nextPage = '<li class="' + classContent +'"id="next-page">'+ numberContent +'</li>';
      return nextPage;
    };
    const addPage = function(index, current_page)  {
      var classContent = '';
      var numberContent = '<a class="#search-pagination page-number"aria-label="'+ (index + 1) +'"href="#">'+(index+1)+'</a>';
      if (index === current_page) {
        classContent = '#search-pagination pagination-item current';
      } else {
        classContent = '#search-pagination pagination-item';
      }
      var page = '<li class="'+classContent+'" id="page-'+(index + 1)+'">'+numberContent+'</li>';
      return page;
    }
    
    const addPaginationEvents = function(start_page, end_page)  {
      if (total_pages <= 0) {
        return;
      }
      const onPrevPageClick = function(event) {
        if (current_page > 0) {
          current_page -= 1;
        }
        if (current_page < start_page) {
          start_page = current_page;
          end_page = Math.min(end_page, start_page + max_page_on_show);
        }
        pagination();
      };
      const onNextPageClick = function(event)  {
        if ((current_page + 1) < total_pages) {
          current_page += 1;
        }
        if (current_page > end_page) {
          end_page = current_page;
          start_page = Math.max(0, end_page - max_page_on_show);
        }
        pagination();
      };
      const onPageClick = function(event)  {
        var page_number = parseInt(event.target.ariaLabel);
        current_page = page_number - 1; // note minus 1 here
        pagination();
      };
      
      var prevPage = document.getElementById('prev-page');
      if(prevPage != null)prevPage.addEventListener('click', onPrevPageClick);
      
      var nextPage = document.getElementById('next-page');
      if(nextPage != null) nextPage.addEventListener('click', onNextPageClick);
      for (var i = start_page; i < end_page; i += 1) {
        var page = document.getElementById('page-'+(i + 1));
        if(page != null)page.addEventListener('click', onPageClick);
      }
    };
    
    paginationContent.innerHTML = ''; // clear
   
    var begin_index = Math.min(current_page * pageSize, resultItems.length);
    var end_index = Math.min(begin_index + pageSize, resultItems.length);
    resultContent.innerHTML = resultItems.slice(begin_index, end_index).map(function(result) {return result.item}).join('');
  
    start_page = Math.max(0, total_pages - max_page_on_show);
    end_page = start_page + Math.min(total_pages, max_page_on_show);
    var pageContent = '<div class="#search-pagination">';
    pageContent += '<div class="#search-pagination pagination">';
    pageContent += '<ul>';
    if (total_pages > 0) {
      // add prev page arrow, when no prev page not selectable
      pageContent += addPrevPage(current_page);
      for (var i = start_page; i < end_page; i += 1) {
        pageContent += addPage(i, current_page);
      }
      // add next page arrow, when no next page not selectable
      pageContent += addNextPage(current_page);
    }
    pageContent += '</ul>';
    pageContent += '</div>';
    pageContent += '</div>';
    paginationContent.innerHTML = pageContent;
    addPaginationEvents(start_page, end_page);
    resultContent.scrollTop = 0;  // scroll to top
    window.pjax && window.pjax.refresh(resultContent);
  };
  
  const inputEventFunction = function() {
    if (!isFetched) {
      console.log("Data not fetched.");
      return;
    }
    var searchText = input.value.trim().toLowerCase();
    var keywords = searchText.split(/[-\s]+/);
    
    if (keywords.length > 1) {
      keywords.push(searchText);
    }
    
    resultItems = [];
    if (searchText.length > 0) {
      // Perform local searching
      datas.forEach(function(index) {
       
        var categories = index.categories, title=index.title, content=index.content, url=index.url;
        var titleInLowerCase = title.toLowerCase();
        var contentInLowerCase = content.toLowerCase();
        var indexOfTitle = [];
        var indexOfContent = [];
        var searchTextCount = 0;
        keywords.forEach( function(keyword) {
          indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
          indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
        });
        
        
        // Show search results
        if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
          var hitCount = indexOfTitle.length + indexOfContent.length;
          // Sort index by position of keyword
          [indexOfTitle, indexOfContent].forEach(function(index)  {
            index.sort(function(itemLeft, itemRight)  {
              if (itemRight.position !== itemLeft.position) {
                return itemRight.position - itemLeft.position;
              }
              return itemLeft.word.length - item.word.length;
            });
          });
          var slicesOfTitle = [];
          if (indexOfTitle.length !== 0) {
            var tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
            searchTextCount += tmp.searchTextCountInSlice;
            slicesOfTitle.push(tmp);
          }
          
          var slicesOfContent = [];
          while (indexOfContent.length !== 0) {
            var item = indexOfContent[indexOfContent.length - 1];
            var position = item.position;
            var word = item.word;
            // Cut out 100 characters
            var start = position - 20;
            var end = position + 30;
            if (start < 0) {
              start = 0;
            }
            if (end < position + word.length) {
              end = position + word.length;
            }
            if (end > content.length) {
              end = content.length;
            }
            var tmp = mergeIntoSlice(start, end, indexOfContent, searchText);
            searchTextCount += tmp.searchTextCountInSlice;
            slicesOfContent.push(tmp);
          }
          
          
          // Sort slices in content by search text's count and hits' count
          slicesOfContent.sort( function(sliceLeft, sliceRight) {
            if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
              return sliceRight.searchTextCount - sliceLeft.searchTextCount;
            } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
              return sliceRight.hits.length - sliceLeft.hits.length;
            }
            return sliceLeft.start - sliceRight.start;
          });
          // Select top N slices in content
          var upperBound = parseInt(CONFIG.localSearch.pageSize, 10);
          if (upperBound >= 0) {
            slicesOfContent = slicesOfContent.slice(0, upperBound);
          }
          var resultItem = '';
          resultItem += '<div class="#search-hits item">';
          // resultItem += '<div class="#search-hits">';
          // resultItem += '<ol class="item">'
          resultItem += '<li>'
          // resultItem += '<li>';
          var cats = categories !== undefined ? '<span>' + categories.join('<i class="ic i-angle-right"></i>') + '</span>' : '<span>No categories</span>';
          resultItem += '<a href="'+url+'">' + cats;
          if (slicesOfTitle.length !== 0) {
            // resultItem += '<li><a href="'+url}">'+highlightKeyword(title, slicesOfTitle[0])}</a>';
            resultItem += '<b>'+highlightKeyword(title, slicesOfTitle[0])+'</b><br>';
          } else {
            // resultItem += '<li><a href="'+url}">'+title}</a>';
            resultItem += '<b>'+title+'</b><br>';
          }
          
          slicesOfContent.forEach(function(slice)  {
            return resultItem += '<li class="#search-hits subitem">'+highlightKeyword(content, slice)+' ...</li>';
          });
          // resultItem += '</li>';
          resultItem += '</a>';
          resultItem += '</li>';
          // resultItem += '</ol>';
          resultItem += '</div>';
          resultItems.push({
            item: resultItem,
            id  : resultItems.length,
            hitCount:hitCount,
            searchTextCount:searchTextCount
          });
        }
      });
    }
    
    if (keywords.length === 1 && keywords[0] === '') {
      resultContent.innerHTML = '<div id="no-result"><i></i></div>';
    } else if (resultItems.length === 0) {
      resultContent.innerHTML = '<div id="no-result"><i></i></div>';
    } else {
      resultItems.sort(function(resultLeft, resultRight)  {
        if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
          return resultRight.searchTextCount - resultLeft.searchTextCount;
        } else if (resultLeft.hitCount !== resultRight.hitCount) {
          return resultRight.hitCount - resultLeft.hitCount;
        }
        return resultRight.id - resultLeft.id;
      });
    }
    
    // Do pagination
    total_pages = Math.ceil(resultItems.length / pageSize);
    pagination();
  }
  
  const fetchData = function() {
    fetch(CONFIG.root + searchPath)
      .then(function(response) {return response.text()} )
      .then( function(res) {
        // Get the contents from search data
        isFetched = true;
        datas = isXml ? [new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map( function(element) {
          return {
            title  : element.querySelector('title').textContent,
            content: element.querySelector('content').textContent,
            url    : element.querySelector('url').textContent
          };
        }) : JSON.parse(res);
        // Only match articles with not empty titles
        datas = datas.filter(function(data) {return data.title} ).map( function(data) {
          data.title = data.title.trim();
          data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : '';
          data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/');
          return data;
        });
        // Remove loading animation
        document.getElementById('search-hits').innerHTML = '<i></i>';
        inputEventFunction();
      });
  };
  if (CONFIG.localSearch.preload) {
    console.log("fetch data.");
    fetchData();
  }
  if (CONFIG.localSearch.trigger === 'auto') {
    input.addEventListener('input', inputEventFunction);
  } else {
    document.querySelector('.search-icon').addEventListener('click', inputEventFunction);
    input.addEventListener('keypress',function(event) {
      if (event.key === 'Enter') {
        inputEventFunction();
      }
    });
  }
  // Handle and trigger popup window
  document.querySelectorAll('.popup-trigger').forEach( function(element) {
    element.addEventListener('click', function()  {
      document.body.style.overflow = 'hidden';
      document.querySelector('.search-pop-overlay').classList.add('search-active');
      input.focus();
      if (!isFetched) fetchData();
    });
  });
  // Handle and trigger popup window
  $.each('.search', function(element) {
    element.addEventListener('click', function() {
      document.body.style.overflow = 'hidden';
      transition(siteSearch, 'shrinkIn', function() {
          $('.search-input').focus();
        }) // transition.shrinkIn
    });
  });
  // Monitor main search box
  const onPopupClose = function() {
    document.body.style.overflow = '';
    transition(siteSearch, 0); // "transition.shrinkOut"
  };
  siteSearch.addEventListener('click', function(event) {
    if (event.target === siteSearch) {
      onPopupClose();
    }
  });
  $('.close-btn').addEventListener('click', onPopupClose);
  window.addEventListener('pjax:success', onPopupClose);
  window.addEventListener('keyup', function(event) {
    if (event.key === 'Escape') {
      onPopupClose();
    }
  });
};

注意:并不是将如上代码全部覆盖了原有的代码,而是将上面的代码 拷贝到原有代码的一个位置中即可,如果不知道放哪里看下面的全部代码。

const cardActive = function() {
  if(!$('.index.wrap'))
    return
  if (!window.IntersectionObserver) {
    $.each('.index.wrap article.item, .index.wrap section.item', function(article) {
      if( article.hasClass("show") === false){
          article.addClass("show");
      }
    })
  } else {
    var io = new IntersectionObserver(function(entries) {
        entries.forEach(function(article) {
          if (article.target.hasClass("show")) {
            io.unobserve(article.target)
          } else {
            if (article.isIntersecting || article.intersectionRatio > 0) {
              article.target.addClass("show");
              io.unobserve(article.target);
            }
          }
        })
    }, {
        root: null,
        threshold: [0.3]
    });
    $.each('.index.wrap article.item, .index.wrap section.item', function(article) {
      io.observe(article)
    })
    $('.index.wrap .item:first-child').addClass("show")
  }
  $.each('.cards .item', function(element, index) {
    ['mouseenter', 'touchstart'].forEach(function(item){
      element.addEventListener(item, function(event) {
        if($('.cards .item.active')) {
          $('.cards .item.active').removeClass('active')
        }
        element.addClass('active')
      })
    });
    ['mouseleave'].forEach(function(item){
      element.addEventListener(item, function(event) {
        element.removeClass('active')
      })
    });
  });
}
const registerExtURL = function() {
  $.each('span.exturl', function(element) {
      var link = document.createElement('a');
      // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
      link.href = decodeURIComponent(atob(element.dataset.url).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
      }).join(''));
      link.rel = 'noopener external nofollow noreferrer';
      link.target = '_blank';
      link.className = element.className;
      link.title = element.title || element.innerText;
      link.innerHTML = element.innerHTML;
      if(element.dataset.backgroundImage) {
        link.dataset.backgroundImage = element.dataset.backgroundImage;
      }
      element.parentNode.replaceChild(link, element);
    });
}
const postFancybox = function(p) {
  if($(p + ' .md img')) {
    vendorCss('fancybox');
    vendorJs('fancybox', function() {
      var q = jQuery.noConflict();
      $.each(p + ' p.gallery', function(element) {
        var box = document.createElement('div');
        box.className = 'gallery';
        box.attr('data-height', element.attr('data-height')||220);
        box.innerHTML = element.innerHTML.replace(/<br>/g, "")
        element.parentNode.insertBefore(box, element);
        element.remove();
      });
      $.each(p + ' .md img:not(.emoji):not(.vemoji)', function(element) {
        var $image = q(element);
        var info, captionClass = 'image-info';
        if(!$image.is('a img')) {
          var imageLink = $image.attr('data-src') || $image.attr('src');
          $image.data('safe-src', imageLink)
          var $imageWrapLink = $image.wrap('<a class="fancybox" href="'+imageLink+'" itemscope itemtype="http://schema.org/ImageObject" itemprop="url"></a>').parent('a');
          if (!$image.is('.gallery img')) {
            $imageWrapLink.attr('data-fancybox', 'default').attr('rel', 'default');
          } else {
            captionClass = 'jg-caption'
          }
        }
        if(info = element.attr('title')) {
          $imageWrapLink.attr('data-caption', info);
          var para = document.createElement('span');
          var txt = document.createTextNode(info);
          para.appendChild(txt);
          para.addClass(captionClass);
          element.insertAfter(para);
        }
      });
      $.each(p + ' div.gallery', function (el, i) {
        q(el).justifiedGallery({rowHeight: q(el).data('height')||120, rel: 'gallery-' + i}).on('jg.complete', function () {
          q(this).find('a').each(function(k, ele) {
            ele.attr('data-fancybox', 'gallery-' + i);
          });
        });
      });
      q.fancybox.defaults.hash = false;
      q(p + ' .fancybox').fancybox({
        loop   : true,
        helpers: {
          overlay: {
            locked: false
          }
        }
      });
    }, window.jQuery);
  }
}
const postBeauty = function () {
  loadComments();
  if(!$('.md'))
    return
  postFancybox('.post.block');
  $('.post.block').oncopy = function(event) {
    showtip(LOCAL.copyright)
    if(LOCAL.nocopy) {
      event.preventDefault()
      return
    }
    var copyright = $('#copyright')
    if(window.getSelection().toString().length > 30 && copyright) {
      event.preventDefault();
      var author = "# " + copyright.child('.author').innerText
      var link = "# " + copyright.child('.link').innerText
      var license = "# " + copyright.child('.license').innerText
      var htmlData = author + "<br>" + link + "<br>" + license + "<br><br>" + window.getSelection().toString().replace(/\r\n/g, "<br>");;
      var textData = author + "\n" + link + "\n" + license + "\n\n" + window.getSelection().toString().replace(/\r\n/g, "\n");
      if (event.clipboardData) {
          event.clipboardData.setData("text/html", htmlData);
          event.clipboardData.setData("text/plain", textData);
      } else if (window.clipboardData) {
          return window.clipboardData.setData("text", textData);
      }
    }
  }
  $.each('li ruby', function(element) {
    var parent = element.parentNode;
    if(element.parentNode.tagName != 'LI') {
      parent = element.parentNode.parentNode;
    }
    parent.addClass('ruby');
  })
  $.each('ol[start]', function(element) {
    element.style.counterReset = "counter " + parseInt(element.attr('start') - 1)
  })
  $.each('.md table', function (element) {
    element.wrap({
      className: 'table-container'
    });
  });
  $.each('.highlight > .table-container', function (element) {
    element.className = 'code-container'
  });
  $.each('figure.highlight', function (element) {
    var code_container = element.child('.code-container');
    var caption = element.child('figcaption');
    element.insertAdjacentHTML('beforeend', '<div class="operation"><span class="breakline-btn"><i class="ic i-align-left"></i></span><span class="copy-btn"><i class="ic i-clipboard"></i></span><span class="fullscreen-btn"><i class="ic i-expand"></i></span></div>');
    var copyBtn = element.child('.copy-btn');
    if(LOCAL.nocopy) {
      copyBtn.remove()
    } else {
      copyBtn.addEventListener('click', function (event) {
        var target = event.currentTarget;
        var comma = '', code = '';
        code_container.find('pre').forEach(function(line) {
          code += comma + line.innerText;
          comma = '\n'
        })
        clipBoard(code, function(result) {
          target.child('.ic').className = result ? 'ic i-check' : 'ic i-times';
          target.blur();
          showtip(LOCAL.copyright);
        })
      });
      copyBtn.addEventListener('mouseleave', function (event) {
        setTimeout(function () {
          event.target.child('.ic').className = 'ic i-clipboard';
        }, 1000);
      });
    }
    var breakBtn = element.child('.breakline-btn');
    breakBtn.addEventListener('click', function (event) {
      var target = event.currentTarget;
      if (element.hasClass('breakline')) {
        element.removeClass('breakline');
        target.child('.ic').className = 'ic i-align-left';
      } else {
        element.addClass('breakline');
        target.child('.ic').className = 'ic i-align-justify';
      }
    });
    var fullscreenBtn = element.child('.fullscreen-btn');
    var removeFullscreen = function() {
      element.removeClass('fullscreen');
      element.scrollTop = 0;
      BODY.removeClass('fullscreen');
      fullscreenBtn.child('.ic').className = 'ic i-expand';
    }
    var fullscreenHandle = function(event) {
      var target = event.currentTarget;
      if (element.hasClass('fullscreen')) {
        removeFullscreen();
        hideCode && hideCode();
        pageScroll(element)
      } else {
        element.addClass('fullscreen');
        BODY.addClass('fullscreen');
        fullscreenBtn.child('.ic').className = 'ic i-compress';
        showCode && showCode();
      }
    }
    fullscreenBtn.addEventListener('click', fullscreenHandle);
    caption && caption.addEventListener('click', fullscreenHandle);
    if(code_container && code_container.find("tr").length > 15) {
      
      code_container.style.maxHeight = "300px";
      code_container.insertAdjacentHTML('beforeend', '<div class="show-btn"><i class="ic i-angle-down"></i></div>');
      var showBtn = code_container.child('.show-btn');
      var showCode = function() {
        code_container.style.maxHeight = ""
        showBtn.addClass('open')
      }
      var hideCode = function() {
        code_container.style.maxHeight = "300px"
        showBtn.removeClass('open')
      }
      showBtn.addEventListener('click', function(event) {
        if (showBtn.hasClass('open')) {
          removeFullscreen()
          hideCode()
          pageScroll(code_container)
        } else {
          showCode()
        }
      });
    }
  });
  $.each('pre.mermaid > svg', function (element) {
    element.style.maxWidth = ''
  });
  $.each('.reward button', function (element) {
    element.addEventListener('click', function (event) {
      event.preventDefault();
      var qr = $('#qr')
      if(qr.display() === 'inline-flex') {
        transition(qr, 0)
      } else {
        transition(qr, 1, function() {
          qr.display('inline-flex')
        }) // slideUpBigIn
      }
    });
  });
  //quiz
  $.each('.quiz > ul.options li', function (element) {
    element.addEventListener('click', function (event) {
      if (element.hasClass('correct')) {
        element.toggleClass('right')
        element.parentNode.parentNode.addClass('show')
      } else {
        element.toggleClass('wrong')
      }
    });
  });
  $.each('.quiz > p', function (element) {
    element.addEventListener('click', function (event) {
      element.parentNode.toggleClass('show')
    });
  });
  $.each('.quiz > p:first-child', function (element) {
    var quiz = element.parentNode;
    var type = 'choice'
    if(quiz.hasClass('true') || quiz.hasClass('false'))
      type = 'true_false'
    if(quiz.hasClass('multi'))
      type = 'multiple'
    if(quiz.hasClass('fill'))
      type = 'gap_fill'
    if(quiz.hasClass('essay'))
      type = 'essay'
    element.attr('data-type', LOCAL.quiz[type])
  });
  $.each('.quiz .mistake', function (element) {
    element.attr('data-type', LOCAL.quiz.mistake)
  });
  $.each('div.tags a', function(element) {
    element.className = ['primary', 'success', 'info', 'warning', 'danger'][Math.floor(Math.random() * 5)]
  })
  $.each('.md div.player', function(element) {
    mediaPlayer(element, {
      type: element.attr('data-type'),
      mode: 'order',
      btns: []
    }).player.load(JSON.parse(element.attr('data-src'))).fetch()
  })
}
const tabFormat = function() {
  // tab
  var first_tab
  $.each('div.tab', function(element, index) {
    if(element.attr('data-ready'))
      return
    var id = element.attr('data-id');
    var title = element.attr('data-title');
    var box = $('#' + id);
    if(!box) {
      box = document.createElement('div');
      box.className = 'tabs';
      box.id = id;
      box.innerHTML = '<div class="show-btn"></div>'
      var showBtn = box.child('.show-btn');
      showBtn.addEventListener('click', function(event) {
        pageScroll(box)
      });
      element.parentNode.insertBefore(box, element);
      first_tab = true;
    } else {
      first_tab = false;
    }
    var ul = box.child('.nav ul');
    if(!ul) {
      ul = box.createChild('div', {
        className: 'nav',
        innerHTML: '<ul></ul>'
      }).child('ul');
    }
    var li = ul.createChild('li', {
      innerHTML: title
    });
    if(first_tab) {
      li.addClass('active');
      element.addClass('active');
    }
    li.addEventListener('click', function(event) {
      var target = event.currentTarget;
      box.find('.active').forEach(function(el) {
        el.removeClass('active');
      })
      element.addClass('active');
      target.addClass('active');
    });
    box.appendChild(element);
    element.attr('data-ready', true)
  });
}
const loadComments = function () {
  var element = $('#comments');
  if (!element) {
    goToComment.display("none")
    return;
  } else {
    goToComment.display("")
  }
  if (!window.IntersectionObserver) {
    vendorCss('valine');
  } else {
    var io = new IntersectionObserver(function(entries, observer) {
      var entry = entries[0];
      vendorCss('valine');
      if (entry.isIntersecting || entry.intersectionRatio > 0) {
        transition($('#comments'), 'bounceUpIn');
        observer.disconnect();
      }
    });
    io.observe(element);
  }
}
const algoliaSearch = function(pjax) {
  if(CONFIG.search === null)
    return
  if(!siteSearch) {
    siteSearch = BODY.createChild('div', {
      id: 'search',
      innerHTML: '<div class="inner"><div class="header"><span class="icon"><i class="ic i-search"></i></span><div class="search-input-container"></div><span class="close-btn"><i class="ic i-times-circle"></i></span></div><div class="results"><div class="inner"><div id="search-stats"></div><div id="search-hits"></div><div id="search-pagination"></div></div></div></div>'
    });
  }
  var search = instantsearch({
    indexName: CONFIG.search.indexName,
    searchClient  : algoliasearch(CONFIG.search.appID, CONFIG.search.apiKey),
    searchFunction: function(helper) {
      var searchInput = $('.search-input');
      if (searchInput.value) {
        helper.search();
      }
    }
  });
  search.on('render', function() {
    pjax.refresh($('#search-hits'));
  });
  // Registering Widgets
  search.addWidgets([
    instantsearch.widgets.configure({
      hitsPerPage: CONFIG.search.hits.per_page || 10
    }),
    instantsearch.widgets.searchBox({
      container           : '.search-input-container',
      placeholder         : LOCAL.search.placeholder,
      // Hide default icons of algolia search
      showReset           : false,
      showSubmit          : false,
      showLoadingIndicator: false,
      cssClasses          : {
        input: 'search-input'
      }
    }),
    instantsearch.widgets.stats({
      container: '#search-stats',
      templates: {
        text: function(data) {
          var stats = LOCAL.search.stats
            .replace(/\$\{hits}/, data.nbHits)
            .replace(/\$\{time}/, data.processingTimeMS);
          return stats + '<span class="algolia-powered"></span><hr>';
        }
      }
    }),
    instantsearch.widgets.hits({
      container: '#search-hits',
      templates: {
        item: function(data) {
          var cats = data.categories ? '<span>'+data.categories.join('<i class="ic i-angle-right"></i>')+'</span>' : '';
          return '<a href="' + CONFIG.root + data.path +'">'+cats+data._highlightResult.title.value+'</a>';
        },
        empty: function(data) {
          return '<div id="hits-empty">'+
              LOCAL.search.empty.replace(/\$\{query}/, data.query) +
            '</div>';
        }
      },
      cssClasses: {
        item: 'item'
      }
    }),
    instantsearch.widgets.pagination({
      container: '#search-pagination',
      scrollTo : false,
      showFirst: false,
      showLast : false,
      templates: {
        first   : '<i class="ic i-angle-double-left"></i>',
        last    : '<i class="ic i-angle-double-right"></i>',
        previous: '<i class="ic i-angle-left"></i>',
        next    : '<i class="ic i-angle-right"></i>'
      },
      cssClasses: {
        root        : 'pagination',
        item        : 'pagination-item',
        link        : 'page-number',
        selectedItem: 'current',
        disabledItem: 'disabled-item'
      }
    })
  ]);
  search.start();
  // Handle and trigger popup window
  $.each('.search', function(element) {
    element.addEventListener('click', function() {
      document.body.style.overflow = 'hidden';
      transition(siteSearch, 'shrinkIn', function() {
          $('.search-input').focus();
        }) // transition.shrinkIn
    });
  });
  // Monitor main search box
  const onPopupClose = function() {
    document.body.style.overflow = '';
    transition(siteSearch, 0); // "transition.shrinkOut"
  };
  siteSearch.addEventListener('click', function(event) {
    if (event.target === siteSearch) {
      onPopupClose();
    }
  });
  $('.close-btn').addEventListener('click', onPopupClose);
  window.addEventListener('pjax:success', onPopupClose);
  window.addEventListener('keyup', function(event) {
    if (event.key === 'Escape') {
      onPopupClose();
    }
  });
}
const localSearch = function(pjax) {
  // 参考 hexo next 主题的配置方法
  // 参考 https://qiuyiwu.github.io/2019/01/25/Hexo-LocalSearch/ 博文
  if(CONFIG.localSearch === null)
    return
  if(!siteSearch) {
    siteSearch = BODY.createChild('div', {
      id: 'search',
      innerHTML: '<div class="inner"><div class="header"><span class="icon"><i class="ic i-search"></i></span><div class="search-input-container"><input class="search-input"autocompvare="off"placeholder="'+LOCAL.search.placeholder+'"spellcheck="false"type="text"id="local-search-input"></div><span class="close-btn"><i class="ic i-times-circle"></i></span></div><div class="results"id="search-results"><div class="inner"><div id="search-stats"></div><div id="search-hits"></div><div id="search-pagination"></div></div></div></div></div>'
    });
  }
  
  var isFetched = false;
  var datas;
  var isXml = true;
  var current_page = 0;
  var pageSize = parseInt(CONFIG.localSearch.pageSize, 10);
  if(isNaN(pageSize)) pageSize = 10;
  var total_pages = 0;
  var max_page_on_show = 7; // 一次最多显示 7 个页码
  var start_page = 0;
  var end_page = 0;
  var resultItems = [];
  // search DB path
  var searchPath = CONFIG.localSearch.path;
  if (searchPath.length == 0) {
    searchPath = 'search.xml';
  } else if (searchPath.endsWith('json')) {
    isXml = false;
  }
  const input = $('.search-input'); // document.querySelector('.search-input');
  const resultContent = document.getElementById('search-hits');
  const paginationContent = document.getElementById('search-pagination');
  const getIndexByWord = function(word, text, caseSensitive) {
    if (CONFIG.localSearch.unescape) {
      var div = document.createElement('div');
      div.innerText = word;
      word = div.innerHTML;
    }
    var wordLen = word.length;
    if (wordLen === 0) {
      return [];
    }
    var startPosition = 0;
    var position = [];
    var index = [];
    if (!caseSensitive) {
      text = text.toLowerCase();
      word = word.toLowerCase();
    }
    while ((position = text.indexOf(word, startPosition)) > -1) {
      index.push({position:position, word:word});
      startPosition = position + wordLen;
    }
    return index;
  };
  // Merge hits into slices
  const mergeIntoSlice = function(start, end, index, searchText) {
    var item = index[index.length - 1];
    var position = item.position;
    var word = item.word;
    var hits = [];
    var searchTextCountInSlice = 0;
    while (position + word.length <= end && index.length !== 0) {
      if (word === searchText) {
        searchTextCountInSlice++;
      }
      hits.push({
        position:position,
        length: word.length
      });
      var wordEnd = position + word.length;
      // Move to next position of hit
      index.pop();
      while (index.length !== 0) {
        item = index[index.length - 1];
        position = item.position;
        word = item.word;
        if (wordEnd > position) {
          index.pop();
        } else {
          break;
        }
      }
    }
    return {
      hits:hits,
      start:start,
      end:end,
      searchTextCount: searchTextCountInSlice
    };
  }
  // Highlight title and content
  const highlightKeyword = function(text, slice) {
    var result = '';
    var prevEnd = slice.start;
    slice.hits.forEach(function(hit)  {
      result += text.substring(prevEnd, hit.position);
      var end = hit.position + hit.length;
      result += '<mark>'+ text.substring(hit.position, end)+'</mark>';
      prevEnd = end;
    });
    result += text.substring(prevEnd, slice.end);
    return result;
  };
  const pagination = function() {
    
    const addPrevPage = function(current_page) {
      var classContent = '';
      var numberContent = '';
      if (current_page === 0) {
        classContent = '#search-pagination pagination-item disabled-item';
        numberContent = '<span class="#search-pagination page-number"><i class="ic i-angle-left"></i></span>';
      } else {
        classContent = '#search-pagination pagination-item';
        numberContent = '<a class="#search-pagination page-number" aria-label="Prev" href="#"><i class="ic i-angle-left"></i></a>';
      }
      var prevPage = '<li class="'+ classContent +'" id="prev-page">'+ numberContent+'</li>';
      return prevPage;
    };
    const addNextPage = function(current_page) {
      var classContent = '';
      var numberContent = '';
      if ((current_page + 1) === total_pages) {
        classContent = '#search-pagination pagination-item disabled-item';
        numberContent = '<span class="#search-pagination page-number"><i class="ic i-angle-right"></i></span>';
      } else {
        classContent = '#search-pagination pagination-item';
        numberContent = '<a class="#search-pagination page-number"aria-label="Next"href="#"><i class="ic i-angle-right"></i></a>';
      }
      var nextPage = '<li class="' + classContent +'"id="next-page">'+ numberContent +'</li>';
      return nextPage;
    };
    const addPage = function(index, current_page)  {
      var classContent = '';
      var numberContent = '<a class="#search-pagination page-number"aria-label="'+ (index + 1) +'"href="#">'+(index+1)+'</a>';
      if (index === current_page) {
        classContent = '#search-pagination pagination-item current';
      } else {
        classContent = '#search-pagination pagination-item';
      }
      var page = '<li class="'+classContent+'" id="page-'+(index + 1)+'">'+numberContent+'</li>';
      return page;
    }
    
    const addPaginationEvents = function(start_page, end_page)  {
      if (total_pages <= 0) {
        return;
      }
      const onPrevPageClick = function(event) {
        if (current_page > 0) {
          current_page -= 1;
        }
        if (current_page < start_page) {
          start_page = current_page;
          end_page = Math.min(end_page, start_page + max_page_on_show);
        }
        pagination();
      };
      const onNextPageClick = function(event)  {
        if ((current_page + 1) < total_pages) {
          current_page += 1;
        }
        if (current_page > end_page) {
          end_page = current_page;
          start_page = Math.max(0, end_page - max_page_on_show);
        }
        pagination();
      };
      const onPageClick = function(event)  {
        var page_number = parseInt(event.target.ariaLabel);
        current_page = page_number - 1; // note minus 1 here
        pagination();
      };
      
      var prevPage = document.getElementById('prev-page');
      if(prevPage != null)prevPage.addEventListener('click', onPrevPageClick);
      
      var nextPage = document.getElementById('next-page');
      if(nextPage != null) nextPage.addEventListener('click', onNextPageClick);
      for (var i = start_page; i < end_page; i += 1) {
        var page = document.getElementById('page-'+(i + 1));
        if(page != null)page.addEventListener('click', onPageClick);
      }
    };
    
    paginationContent.innerHTML = ''; // clear
   
    var begin_index = Math.min(current_page * pageSize, resultItems.length);
    var end_index = Math.min(begin_index + pageSize, resultItems.length);
    resultContent.innerHTML = resultItems.slice(begin_index, end_index).map(function(result) {return result.item}).join('');
  
    start_page = Math.max(0, total_pages - max_page_on_show);
    end_page = start_page + Math.min(total_pages, max_page_on_show);
    var pageContent = '<div class="#search-pagination">';
    pageContent += '<div class="#search-pagination pagination">';
    pageContent += '<ul>';
    if (total_pages > 0) {
      // add prev page arrow, when no prev page not selectable
      pageContent += addPrevPage(current_page);
      for (var i = start_page; i < end_page; i += 1) {
        pageContent += addPage(i, current_page);
      }
      // add next page arrow, when no next page not selectable
      pageContent += addNextPage(current_page);
    }
    pageContent += '</ul>';
    pageContent += '</div>';
    pageContent += '</div>';
    paginationContent.innerHTML = pageContent;
    addPaginationEvents(start_page, end_page);
    resultContent.scrollTop = 0;  // scroll to top
    window.pjax && window.pjax.refresh(resultContent);
  };
  
  const inputEventFunction = function() {
    if (!isFetched) {
      console.log("Data not fetched.");
      return;
    }
    var searchText = input.value.trim().toLowerCase();
    var keywords = searchText.split(/[-\s]+/);
    
    if (keywords.length > 1) {
      keywords.push(searchText);
    }
    
    resultItems = [];
    if (searchText.length > 0) {
      // Perform local searching
      datas.forEach(function(index) {
       
        var categories = index.categories, title=index.title, content=index.content, url=index.url;
        var titleInLowerCase = title.toLowerCase();
        var contentInLowerCase = content.toLowerCase();
        var indexOfTitle = [];
        var indexOfContent = [];
        var searchTextCount = 0;
        keywords.forEach( function(keyword) {
          indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
          indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
        });
        
        
        // Show search results
        if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
          var hitCount = indexOfTitle.length + indexOfContent.length;
          // Sort index by position of keyword
          [indexOfTitle, indexOfContent].forEach(function(index)  {
            index.sort(function(itemLeft, itemRight)  {
              if (itemRight.position !== itemLeft.position) {
                return itemRight.position - itemLeft.position;
              }
              return itemLeft.word.length - item.word.length;
            });
          });
          var slicesOfTitle = [];
          if (indexOfTitle.length !== 0) {
            var tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
            searchTextCount += tmp.searchTextCountInSlice;
            slicesOfTitle.push(tmp);
          }
          
          var slicesOfContent = [];
          while (indexOfContent.length !== 0) {
            var item = indexOfContent[indexOfContent.length - 1];
            var position = item.position;
            var word = item.word;
            // Cut out 100 characters
            var start = position - 20;
            var end = position + 30;
            if (start < 0) {
              start = 0;
            }
            if (end < position + word.length) {
              end = position + word.length;
            }
            if (end > content.length) {
              end = content.length;
            }
            var tmp = mergeIntoSlice(start, end, indexOfContent, searchText);
            searchTextCount += tmp.searchTextCountInSlice;
            slicesOfContent.push(tmp);
          }
          
          
          // Sort slices in content by search text's count and hits' count
          slicesOfContent.sort( function(sliceLeft, sliceRight) {
            if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
              return sliceRight.searchTextCount - sliceLeft.searchTextCount;
            } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
              return sliceRight.hits.length - sliceLeft.hits.length;
            }
            return sliceLeft.start - sliceRight.start;
          });
          // Select top N slices in content
          var upperBound = parseInt(CONFIG.localSearch.pageSize, 10);
          if (upperBound >= 0) {
            slicesOfContent = slicesOfContent.slice(0, upperBound);
          }
          var resultItem = '';
          resultItem += '<div class="#search-hits item">';
          // resultItem += '<div class="#search-hits">';
          // resultItem += '<ol class="item">'
          resultItem += '<li>'
          // resultItem += '<li>';
          var cats = categories !== undefined ? '<span>' + categories.join('<i class="ic i-angle-right"></i>') + '</span>' : '<span>No categories</span>';
          resultItem += '<a href="'+url+'">' + cats;
          if (slicesOfTitle.length !== 0) {
            // resultItem += '<li><a href="'+url}">'+highlightKeyword(title, slicesOfTitle[0])}</a>';
            resultItem += '<b>'+highlightKeyword(title, slicesOfTitle[0])+'</b><br>';
          } else {
            // resultItem += '<li><a href="'+url}">'+title}</a>';
            resultItem += '<b>'+title+'</b><br>';
          }
          
          slicesOfContent.forEach(function(slice)  {
            return resultItem += '<li class="#search-hits subitem">'+highlightKeyword(content, slice)+' ...</li>';
          });
          // resultItem += '</li>';
          resultItem += '</a>';
          resultItem += '</li>';
          // resultItem += '</ol>';
          resultItem += '</div>';
          resultItems.push({
            item: resultItem,
            id  : resultItems.length,
            hitCount:hitCount,
            searchTextCount:searchTextCount
          });
        }
      });
    }
    
    if (keywords.length === 1 && keywords[0] === '') {
      resultContent.innerHTML = '<div id="no-result"><i></i></div>';
    } else if (resultItems.length === 0) {
      resultContent.innerHTML = '<div id="no-result"><i></i></div>';
    } else {
      resultItems.sort(function(resultLeft, resultRight)  {
        if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
          return resultRight.searchTextCount - resultLeft.searchTextCount;
        } else if (resultLeft.hitCount !== resultRight.hitCount) {
          return resultRight.hitCount - resultLeft.hitCount;
        }
        return resultRight.id - resultLeft.id;
      });
    }
    
    // Do pagination
    total_pages = Math.ceil(resultItems.length / pageSize);
    pagination();
  }
  
  const fetchData = function() {
    fetch(CONFIG.root + searchPath)
      .then(function(response) {return response.text()} )
      .then( function(res) {
        // Get the contents from search data
        isFetched = true;
        datas = isXml ? [new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map( function(element) {
          return {
            title  : element.querySelector('title').textContent,
            content: element.querySelector('content').textContent,
            url    : element.querySelector('url').textContent
          };
        }) : JSON.parse(res);
        // Only match articles with not empty titles
        datas = datas.filter(function(data) {return data.title} ).map( function(data) {
          data.title = data.title.trim();
          data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : '';
          data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/');
          return data;
        });
        // Remove loading animation
        document.getElementById('search-hits').innerHTML = '<i></i>';
        inputEventFunction();
      });
  };
  if (CONFIG.localSearch.preload) {
    console.log("fetch data.");
    fetchData();
  }
  if (CONFIG.localSearch.trigger === 'auto') {
    input.addEventListener('input', inputEventFunction);
  } else {
    document.querySelector('.search-icon').addEventListener('click', inputEventFunction);
    input.addEventListener('keypress',function(event) {
      if (event.key === 'Enter') {
        inputEventFunction();
      }
    });
  }
  // Handle and trigger popup window
  document.querySelectorAll('.popup-trigger').forEach( function(element) {
    element.addEventListener('click', function()  {
      document.body.style.overflow = 'hidden';
      document.querySelector('.search-pop-overlay').classList.add('search-active');
      input.focus();
      if (!isFetched) fetchData();
    });
  });
  // Handle and trigger popup window
  $.each('.search', function(element) {
    element.addEventListener('click', function() {
      document.body.style.overflow = 'hidden';
      transition(siteSearch, 'shrinkIn', function() {
          $('.search-input').focus();
        }) // transition.shrinkIn
    });
  });
  // Monitor main search box
  const onPopupClose = function() {
    document.body.style.overflow = '';
    transition(siteSearch, 0); // "transition.shrinkOut"
  };
  siteSearch.addEventListener('click', function(event) {
    if (event.target === siteSearch) {
      onPopupClose();
    }
  });
  $('.close-btn').addEventListener('click', onPopupClose);
  window.addEventListener('pjax:success', onPopupClose);
  window.addEventListener('keyup', function(event) {
    if (event.key === 'Escape') {
      onPopupClose();
    }
  });
};

# 修改 script.js

shoka/scripts/generaters/script.js 中主要是读取配置,添加如下代码

主要添加的代码:

...........  // 省略若干代码
if(config.algolia) {
    siteConfig.search = {
      appID    : config.algolia.appId,
      apiKey   : config.algolia.apiKey,
      indexName: config.algolia.indexName,
      hits     : theme.search.hits
    }
  }
// 以下为需要添加的代码
if(config.search) {
    siteConfig.localSearch = {
      enable: config.search.enable,
      path: config.search.path,
      field: config.search.field,
      format: config.search.format,
      limit: config.search.limit,
      content: config.search.content,
      unescape: config.search.unescape,
      preload:  config.search.preload,
      trigger: config.search.trigger,
      pageSize: config.search.pageSize
    }
  }

下面是全部的代码:

'use strict';
const fs = require('hexo-fs');
const url = require('url');
hexo.extend.generator.register('script', function(locals){
  const config = hexo.config;
  const theme = hexo.theme.config;
  var env = require('../../package.json')
  var siteConfig = {
    version: env['version'],
    hostname: config.url,
    root: config.root,
    statics: theme.statics,
    favicon: {
      normal: theme.images + "/favicon.ico",
      hidden: theme.images + "/failure.ico"
    },
    darkmode: theme.darkmode,
    auto_scroll: theme.auto_scroll,
    js: {
      valine: theme.vendors.js.valine,
      chart: theme.vendors.js.chart,
      copy_tex: theme.vendors.js.copy_tex,
      fancybox: theme.vendors.js.fancybox
    },
    css: {
      valine: theme.css + "/comment.css",
      katex: theme.vendors.css.katex,
      mermaid: theme.css + "/mermaid.css",
      fancybox: theme.vendors.css.fancybox
    },
    loader: theme.loader,
    search : null,
    valine: theme.valine,
    quicklink: {
      timeout : theme.quicklink.timeout,
      priority: theme.quicklink.priority
    }
  };
  // if(config.algolia) {
  //   siteConfig.search = {
  //     appID    : config.algolia.appId,
  //     apiKey   : config.algolia.apiKey,
  //     indexName: config.algolia.indexName,
  //     hits     : theme.search.hits
  //   }
  // }
// 以下为需要添加的代码
if(config.search) {
    siteConfig.localSearch = {
      enable: config.search.enable,
      path: config.search.path,
      field: config.search.field,
      format: config.search.format,
      limit: config.search.limit,
      content: config.search.content,
      unescape: config.search.unescape,
      preload:  config.search.preload,
      trigger: config.search.trigger,
      pageSize: config.search.pageSize
    }
  }
  if(theme.audio) {
    siteConfig.audio = theme.audio
  }
  var text = '';
  ['utils', 'dom', 'player', 'global', 'sidebar', 'page', 'pjax'].forEach(function(item) {
    text += fs.readFileSync('themes/shoka/source/js/_app/'+item+'.js').toString();
  });
  if(theme.fireworks && theme.fireworks.enable) {
    text += fs.readFileSync('themes/shoka/source/js/_app/fireworks.js').toString();
    siteConfig.fireworks = theme.fireworks.color || ["rgba(255,182,185,.9)", "rgba(250,227,217,.9)", "rgba(187,222,214,.9)", "rgba(138,198,209,.9)"]
  }
  text = 'var CONFIG = ' + JSON.stringify(siteConfig) + ';' + text;
  return {
      path: theme.js + '/app.js',
      data: function(){
        return hexo.render.renderSync({text:  text, engine: 'js'});
      }
    };
});

# 修改 pjax.js

shoka/source/js/_app/pjax.js 中是启动搜索功能的部分,这里在两个配置都有的情况下默认使用本地搜索而不是 Algolia

if (CONFIG.localSearch != null) {
    localSearch(pjax)
  }else if(CONFIG.search != null) {
    algoliaSearch(pjax)
  }

注意,这里配置需要将 Algolia 的注释掉 否则可能导致页面进不去

全部代码如下:

const domInit = function() {
  $.each('.overview .menu > .item', function(el) {
    siteNav.child('.menu').appendChild(el.cloneNode(true));
  })
  loadCat.addEventListener('click', Loader.vanish);
  menuToggle.addEventListener('click', sideBarToggleHandle);
  $('.dimmer').addEventListener('click', sideBarToggleHandle);
  quickBtn.child('.down').addEventListener('click', goToBottomHandle);
  quickBtn.child('.up').addEventListener('click', backToTopHandle);
  if(!toolBtn) {
    toolBtn = siteHeader.createChild('div', {
      id: 'tool',
      innerHTML: '<div class="item player"></div><div class="item contents"><i class="ic i-list-ol"></i></div><div class="item chat"><i class="ic i-comments"></i></div><div class="item back-to-top"><i class="ic i-arrow-up"></i><span>0%</span></div>'
    });
  }
  
  toolPlayer = toolBtn.child('.player');
  backToTop = toolBtn.child('.back-to-top');
  goToComment = toolBtn.child('.chat');
  showContents = toolBtn.child('.contents');
  backToTop.addEventListener('click', backToTopHandle);
  goToComment.addEventListener('click', goToCommentHandle);
  showContents.addEventListener('click', sideBarToggleHandle);
  mediaPlayer(toolPlayer)
  $('main').addEventListener('click', function() {
    toolPlayer.player.mini()
  })
}
const pjaxReload = function () {
  pagePosition()
  if(sideBar.hasClass('on')) {
    transition(sideBar, function () {
        sideBar.removeClass('on');
        menuToggle.removeClass('close');
      }); // 'transition.slideRightOut'
  }
  $('#main').innerHTML = ''
  $('#main').appendChild(loadCat.lastChild.cloneNode(true));
  pageScroll(0);
}
const siteRefresh = function (reload) {
  LOCAL_HASH = 0
  LOCAL_URL = window.location.href
  vendorCss('katex');
  vendorJs('copy_tex');
  vendorCss('mermaid');
  vendorJs('chart');
  vendorJs('valine', function() {
    var options = Object.assign({}, CONFIG.valine);
    options = Object.assign(options, LOCAL.valine||{});
    options.el = '#comments';
    options.pathname = LOCAL.path;
    options.pjax = pjax;
    options.lazyload = lazyload;
    new MiniValine(options);
    setTimeout(function(){
      positionInit(1);
      postFancybox('.v');
    }, 1000);
  }, window.MiniValine);
  if(!reload) {
    $.each('script[data-pjax]', pjaxScript);
  }
  originTitle = document.title
  resizeHandle()
  menuActive()
  sideBarTab()
  sidebarTOC()
  registerExtURL()
  postBeauty()
  tabFormat()
  toolPlayer.player.load(LOCAL.audio || CONFIG.audio || {})
  Loader.hide()
  setTimeout(function(){
    positionInit()
  }, 500);
  cardActive()
  lazyload.observe()
}
const siteInit = function () {
  domInit()
  pjax = new Pjax({
            selectors: [
              'head title',
              '.languages',
              '.pjax',
              'script[data-config]'
            ],
            analytics: false,
            cacheBust: false
          })
  CONFIG.quicklink.ignores = LOCAL.ignores
  quicklink.listen(CONFIG.quicklink)
  visibilityListener()
  themeColorListener()
  
  if (CONFIG.localSearch != null) {
    localSearch(pjax)
  }else if(CONFIG.search != null) {
    algoliaSearch(pjax)
  }
  algoliaSearch(pjax)
  window.addEventListener('scroll', scrollHandle)
  window.addEventListener('resize', resizeHandle)
  window.addEventListener('pjax:send', pjaxReload)
  window.addEventListener('pjax:success', siteRefresh)
  window.addEventListener('beforeunload', function() {
    pagePosition()
  })
  siteRefresh(1)
}
window.addEventListener('DOMContentLoaded', siteInit);
console.log('%c Theme.Shoka v' + CONFIG.version + ' %c https://shoka.lostyu.me/ ', 'color: white; background: #e9546b; padding:5px 0;', 'padding:4px;border:1px solid #e9546b;')

# 添加配置

最后在 hexo 配置(最外层的 _config.yml )中添加 search 配置,就大功告成了!

search:
  enable: true
  path: search.json # search.xml
  field: post
  format: html
  limit: 10000
  content: true
  unescape: true  
  preload:  true  
  trigger: "auto" 
  pageSize: 10
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

D 微信支付

微信支付

D 支付宝

支付宝

D 贝宝

贝宝