Pagefind 整合 Jekyll 静态搜索

2024-08-28 08:00:00   技术   javascript jquery

其实我很早就想把这里的搜索给重写一下。

  • 旧方案:Lunr
  • 初步了解Pagefind
  • 我的使用步骤
    • 搜索页面的api调用
    • 一些问题和调整
    • 限定搜索的标签范围
    • pagefind_extended的中文支持
    • Github Action和pagefind的整合

旧方案:Lunr

之前使用的是静态生成一个search_data.json,大概全站生成的格式是这样的:

{
  
    "blog-post-107": {
      "post_id" : "/blog/post/107",
      "title": "算术题生成器",
      "url": "/blog/post/107/",
      "date": "2024-08-23 08:00:00",
      "category": "技术",
      "tags": ["javascript"]
    }
    ,
  
    "blog-post-106": {
      "post_id" : "/blog/post/106",
      "title": "从MP4里面提取音频",
      "url": "/blog/post/106/",
      "date": "2024-07-30 08:00:00",
      "category": "技术",
      "tags": ["python","ffmpeg"]
    }
    ,
    .............

我并没有把全文给放进去,所以搜索起来有很大的限制。

然后再利用Lunr来进行检索。其中我还用了lunr-language进行了一下分词。虽然不明白当时为什么用了jp的库而不是zh的:face_in_clouds:,很久远的事情了,实在记不得了。

用下来搜索的效果大概是这样的

旧版搜索效果

说白了就是只能用标题、标签和分类来搜索。

虽然说Lunr其实很强大,如果我把全文都塞进去不见得搜不出来,但是我实在不高兴折腾了。

备份下当时的代码

window.idx = lunr(function () {
    this.use(lunr.jp);
    this.field('post_id');
    this.field('title', { boost: 10 });
    this.field('category');
    this.field('tags');
});
window.data = $.getJSON('/search_data.json');
window.data.then(function(loaded_data){
    $.each(loaded_data, function(index, value){
        window.idx.add(
            $.extend({ "id": index }, value)
        );
    });
    var results = window.idx.search(decodeURIComponent(params['string']));
});

之后我还搜索过一些其他方案,比如Algolia,bootstrapt网站就用的这个,看上去特别酷炫,但是要收钱……所有需要收钱的方案在我这里基本都被干掉了

初步了解Pagefind

最近又没事搜索了一下,搜到了这篇文章基于Pagefind实现静态博客站内搜索,非常感兴趣。

于是就上手试了一下,感觉非常不错。

试用效果

首先Pagefind是先扫描站点生成index,然后提供默认的ui或者api进行检索。

对于我的基于Jekyll,部署在Github Page的白嫖式网站非常友好。

我的使用步骤

按照https://pagefind.app/docs/的步骤,比较简单,但是我之后微调之后有很多不一样的地方。

首先我在上文《基于Pagefind实现静态博客站内搜索》里面发现搜索框和结果框可以分开,但是现在我拿到的版本并不能分开(或者我没有找到分开设置的方法),这造成了default UI和我现有页面的嵌入配合成了灾难。虽然说我可以把result部分设成一个悬浮的div,但是调整了很久的CSS,感觉还是非常丑。我思考了一下还是用原来的比较古板/傻的search.html页面,用调用api的方式在特定页面显示搜索的结果。

生成索引

我自己并不是很熟悉npm和npx,所以直接从它Release的里面拉了个已经编译好的win版本来索引。

我的blog的本地目录大概是这样的,真正应该被检索的文章其实是在编译之后以全静态的格式放在_site下面的blog/post下面,其他都是一些照片/分类/标签/索引之类的杂七杂八的没有搜索价值的东西。

├─_data
├─_includes
├─_layouts
├─_posts
└─_site
    ├─blog
    │  ├─index
    │  └─post
    ├─photo
    ├─photos
    ├─search
    ├─tag
    └─type

所以我的本地生成index的命令是这样的

pagefind.exe --site C:\codes\JiYouMCC.github.io\_site --glob blog/post/**/*.html

在生成index之后,会在_site下面生成一个pagefind文件夹,所有的索引/界面/js东西都在里面。放在_site里面并不会真正生效,所以我需要拷到根目录下(C:\codes\JiYouMCC.github.io\pagefind),让jekyll视为一堆静态库来引用。

搜索页面的api调用

大体上也是跟着文档走,稍微做了一些细节上的配置。

var pagefind = await import("/pagefind/pagefind.js");
await pagefind.options({
    showSubResults: false,
    excerptLength: 15
});
pagefind.init();
var search = await pagefind.search(searchString);
var results = await Promise.all(search.results.map(r => r.data()));

其实我还想加一些什么根据日期/相关性排序之类的东西,这个稍后再说。

一些问题和调整

然后我就发现了一些问题,相应做了一些调整。

限定搜索的标签范围

首先,我发现搜索结果的meta的图片总是用了我link里面欧拉项目的图片,这八竿子打不着啊:scream:。根据文档image will contain the src of the first img that follows the h1,所以我需要把这个图片丢出搜索的范围。参考了一下Configuring what content is indexed,我把blog的right部分全用<aside data-pagefind-ignore="all">扔了出去,以防止图片的干扰,用<main data-pagefind-body>包裹了一下所有post的真正的文章部分,然后就生效了。这样也防止了我搜索“评论”结果把所有的文章都搜索出来的窘境。

然后,在UI上做了一些调整,还是用jquery写了一推的append,处理了一下图片的格式,大概是这样的效果

搜索效果

感觉好像大功告成了(并不是!)

pagefind_extended的中文支持

然后我就发现了一个很麻烦的问题,中文分词非常糟糕。

搜索效果

同样是上面那个搜索,明明有一句话说的是“根据像素点的灰色深度”blablablab,我就搜索“像素”,结果什么都搜不出来。

这个问题看上去已经非常致命了,我的感觉是,肯定有什么我不知道的配置还没搞定。

不过除此以外,搜索标题、标签、分类已经不是问题了,体验还比原来的好很多,所以先将就着用一下。

后来我在Installing and running Pagefind上面终于找到了蛛丝马迹。

./pagefind --site "public"
# or
./pagefind_extended --site "public"

Pagefind publishes two releases, pagefind and pagefind_extended. The extended release is a larger binary, but includes specialized support for indexing Chinese and Japanese pages.

The extended release is a larger binary, but includes specialized support for indexing Chinese and Japanese pages.

居然为中文和日文特地搞了个版本!解决了分词的问题!

相应的使用的现成可执行文件就是pagefind_extended-v1.1.0-x86_64-pc-windows-msvc.tar.gz,命令行更新为

pagefind_extended.exe --site C:\codes\JiYouMCC.github.io\_site --glob blog/post/**/*.html

Github Action和pagefind的整合

另一个小问题就是,每次jekyll有新的文章,都需要jekyll serve出_site,然后进行重新索引。从DevOps的角度来说,理论上可以在部署的时候加入这个index+拷贝文件夹的步骤,但是我有点懒,先不折腾了。什么时候我高兴了研究下Github Action,和jekyll的编译部署都整合起来,说不定就成了。

(第二天)我就研究下了Github Action然后也搞定了!

期间也有一些小波折,稍微记录一下!

之前部署Jekyll的时候用的是Github自己默认的。我自己本身没有太关注Github的更新,就是(工作中)突然发现有一天它出了Action的功能,又突然有一天发现Jekyll的部署就是走的Action。

不管怎么说,反正现在可以自定义。

Github Action自带的deploy jekyll的workflow 来自于jekyll-build-pages。从原理上来说,我只要在它上传artifacts之前把pagefind索引的步骤塞进去就行。

然后先碰到了一个奇奇怪怪的问题。

......
[Building search indexes]
Total: 
  Indexed 1 language
  Indexed 101 pages
  Indexed 7937 words
  Indexed 0 filters
  Indexed 0 sorts
thread 'main' panicked at /home/runner/work/pagefind/pagefind/pagefind/src/output/mod.rs:350:46:
called `Result::unwrap()` on an `Err` value: Os { code: 13, kind: PermissionDenied, message: "Permission denied" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: Process completed with exit code 101.

一开始我还以为是pagefind的可执行文件权限不对,来了一波chmod 755之类的,一点儿都不管用。然后我ll了一下,才发现

-rw-r--r--  1 runner docker     3265 Aug 29 01:56 404.html
-rw-r--r--  1 runner docker      209 Aug 29 01:56 Gemfile
-rw-r--r--  1 runner docker      244 Aug 29 01:56 README.md
-rw-r--r--  1 runner docker      684 Aug 29 01:56 _config.yml
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 _data
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 _includes
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 _layouts
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 _posts
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 _sass
drwxr-xr-x 19 root   root       4096 Aug 29 01:56 _site
-rw-r--r--  1 runner docker     2202 Aug 29 01:56 archive.html
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 ascii_art
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 css
-rw-r--r--  1 runner docker    12862 Aug 29 01:56 favicon.ico
-rw-r--r--  1 runner docker     1352 Aug 29 01:56 feed.xml
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 fonts
-rw-r--r--  1 runner docker     5657 Aug 29 01:56 guestbook.html
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 img
-rw-r--r--  1 runner docker     4466 Aug 29 01:56 index.html
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 js
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 lib
-rw-r--r--  1 runner docker     1948 Aug 29 01:56 lot.html
-rwxr-xr-x  1 runner docker 66446392 Apr  2 19:25 pagefind_extended
-rw-r--r--  1 runner docker 48695886 Apr  2 19:37 pagefind_extended-v1.1.0-x86_64-unknown-linux-musl.tar.gz
drwxr-xr-x 15 runner docker     4096 Aug 29 01:56 photo
-rw-r--r--  1 runner docker      950 Aug 29 01:56 photos.html
drwxr-xr-x  2 runner docker     4096 Aug 29 01:56 play
-rw-r--r--  1 runner docker       97 Aug 29 01:56 robots.txt
-rw-r--r--  1 runner docker      336 Aug 29 01:56 search.html
-rw-r--r--  1 runner docker     1748 Aug 29 01:56 tag.html
-rw-r--r--  1 runner docker     2733 Aug 29 01:56 type.html

中间这_site的root:root怎么就那么刺眼呢:facepalm:。原来jekyll build在docker里面出来的文件夹本身就root了。

接着我就折腾了一波什么chown -R runner:docker之类的,各种不管用,也没找到actions/jekyll-build-pages@v1有什么配置可以设置docker的arg的地方,只有输出文件夹的地址啥的。

最后搜了一圈,找到了Reset Workspace Ownership Action,终于好用了

最后我的workflow大致是这样:

name: Build Jekyll Site with Pagefind
on:
 push:
   branches: ["master"]
permissions:
  contents: read
  pages: write
  id-token: write
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup Pages
        uses: actions/configure-pages@v5
      - name: Build
        uses: actions/jekyll-build-pages@v1
      - name: Get Actions user id
        id: get_uid
        run: |
          actions_user_id=`id -u $USER`
          echo $actions_user_id
          echo "uid=$actions_user_id" >> $GITHUB_OUTPUT
      - name: Correct Ownership in GITHUB_WORKSPACE directory
        uses: peter-murray/reset-workspace-ownership-action@v1
        with:
          user_id: ${{ steps.get_uid.outputs.uid }}        
      - name: Index pagefind
        run: |
          wget https://github.com/CloudCannon/pagefind/releases/download/${{ vars.PAGEFIND_VERSION }}/${{ vars.PAGEFIND_PACKAGE_NAME }}
          tar -xvzf ${{ vars.PAGEFIND_PACKAGE_NAME }}
          ./pagefind_extended --site ${{ github.workspace }}/_site --glob blog/post/**/*.html
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}

另带2个环境变量,想着方便以后维护吧,毕竟是从别人Release现成拉下来的。

PAGEFIND_PACKAGE_NAME=pagefind_extended-v1.1.0-x86_64-unknown-linux-musl.tar.gz
PAGEFIND_VERSION=v1.1.0

接着就愉快地把repo里面的pagefind文件夹给干掉了!我可以在线编辑markdown不用管编译这种乱糟糟的事情了!

感觉Github Action真心强大,不知道能不能把我的评论系统之每次新建post都要手动建Issue这个问题也给解决了呢?

现在还有一个小问题,就是搜索的结果里面会把emoji作为图片放在meta里,觉得怪怪的。我试了ignore却并不好用,回头想想怎么整。

在Pagefind的Github发了相关的Issue: exclude-selectors does not work,虽然说结果并不能解决我的问题(反而得到了不太理想的解释),不过这个活跃的项目给我一种友好+富有活力的感觉,真的越来越喜欢它了!

评论共