Files
blog/posts/f287c563/index.html
biss cdf41750e2 Merge pull request 'Configure Renovate' (#1) from renovate/configure into master
Reviewed-on: #1
··[CST 2026-02-22 Sunday 19:37:28]
2026-02-22 19:37:28 +08:00

563 lines
145 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover"><title>添加typesense搜索 | Bi's Blog</title><meta name="author" content="biss"><meta name="copyright" content="biss"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="最近在构建班级博客用ghost cms在构建搜索时发现了typesense所以把他移植到这个博客上。 安装typesense直接用docker-compose: 1234567891011services: typesense: image: typesense/typesense:30.1 restart: always ports: - &quot;8108">
<meta property="og:type" content="article">
<meta property="og:title" content="添加typesense搜索">
<meta property="og:url" content="https://blog.biss.click/posts/f287c563/index.html">
<meta property="og:site_name" content="Bi's Blog">
<meta property="og:description" content="最近在构建班级博客用ghost cms在构建搜索时发现了typesense所以把他移植到这个博客上。 安装typesense直接用docker-compose: 1234567891011services: typesense: image: typesense/typesense:30.1 restart: always ports: - &quot;8108">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://free.picui.cn/free/2025/08/10/689845496a283.png">
<meta property="article:published_time" content="2026-02-05T05:14:16.000Z">
<meta property="article:modified_time" content="2026-02-22T11:37:10.097Z">
<meta property="article:author" content="biss">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://free.picui.cn/free/2025/08/10/689845496a283.png"><script type="application/ld+json">{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "添加typesense搜索",
"url": "https://blog.biss.click/posts/f287c563/",
"image": "https://free.picui.cn/free/2025/08/10/689845496a283.png",
"datePublished": "2026-02-05T05:14:16.000Z",
"dateModified": "2026-02-22T11:37:10.097Z",
"author": [
{
"@type": "Person",
"name": "biss",
"url": "https://blog.biss.click"
}
]
}</script><link rel="shortcut icon" href="/images/Bi.ico"><link rel="canonical" href="https://blog.biss.click/posts/f287c563/index.html"><link rel="preconnect" href="//unpkg.com"><link rel="preconnect" href="//busuanzi.ibruce.info"><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://unpkg.com/@fortawesome/fontawesome-free/css/all.min.css"><link rel="stylesheet" href="https://unpkg.com/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://unpkg.com/@fancyapps/ui/dist/fancybox/fancybox.css" media="print" onload="this.media='all'"><script>
(() => {
const saveToLocal = {
set: (key, value, ttl) => {
if (!ttl) return
const expiry = Date.now() + ttl * 86400000
localStorage.setItem(key, JSON.stringify({ value, expiry }))
},
get: key => {
const itemStr = localStorage.getItem(key)
if (!itemStr) return undefined
const { value, expiry } = JSON.parse(itemStr)
if (Date.now() > expiry) {
localStorage.removeItem(key)
return undefined
}
return value
}
}
window.btf = {
saveToLocal,
getScript: (url, attr = {}) => new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = url
script.async = true
Object.entries(attr).forEach(([key, val]) => script.setAttribute(key, val))
script.onload = script.onreadystatechange = () => {
if (!script.readyState || /loaded|complete/.test(script.readyState)) resolve()
}
script.onerror = reject
document.head.appendChild(script)
}),
getCSS: (url, id) => new Promise((resolve, reject) => {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = url
if (id) link.id = id
link.onload = link.onreadystatechange = () => {
if (!link.readyState || /loaded|complete/.test(link.readyState)) resolve()
}
link.onerror = reject
document.head.appendChild(link)
}),
addGlobalFn: (key, fn, name = false, parent = window) => {
if (!true && key.startsWith('pjax')) return
const globalFn = parent.globalFn || {}
globalFn[key] = globalFn[key] || {}
globalFn[key][name || Object.keys(globalFn[key]).length] = fn
parent.globalFn = globalFn
}
}
const activateDarkMode = () => {
document.documentElement.setAttribute('data-theme', 'dark')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
}
}
const activateLightMode = () => {
document.documentElement.setAttribute('data-theme', 'light')
if (document.querySelector('meta[name="theme-color"]') !== null) {
document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
}
}
btf.activateDarkMode = activateDarkMode
btf.activateLightMode = activateLightMode
const theme = saveToLocal.get('theme')
theme === 'dark' ? activateDarkMode() : theme === 'light' ? activateLightMode() : null
const asideStatus = saveToLocal.get('aside-status')
if (asideStatus !== undefined) {
document.documentElement.classList.toggle('hide-aside', asideStatus === 'hide')
}
const detectApple = () => {
if (/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)) {
document.documentElement.classList.add('apple')
}
}
detectApple()
})()
</script><script>const GLOBAL_CONFIG = {
root: '/',
algolia: undefined,
localSearch: undefined,
translate: {"defaultEncoding":2,"translateDelay":0,"msgToTraditionalChinese":"繁","msgToSimplifiedChinese":"簡"},
highlight: {"plugin":"highlight.js","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":200,"highlightFullpage":true,"highlightMacStyle":false},
copy: {
success: '复制成功',
error: '复制失败',
noSupport: '浏览器不支持'
},
relativeDate: {
homepage: false,
post: false
},
runtime: '',
dateSuffix: {
just: '刚刚',
min: '分钟前',
hour: '小时前',
day: '天前',
month: '个月前'
},
copyright: undefined,
lightbox: 'fancybox',
Snackbar: {"chs_to_cht":"已切换为繁体中文","cht_to_chs":"已切换为简体中文","day_to_night":"已切换为深色模式","night_to_day":"已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
infinitegrid: {
js: 'https://unpkg.com/@egjs/infinitegrid/dist/infinitegrid.min.js',
buttonText: '加载更多'
},
isPhotoFigcaption: false,
islazyloadPlugin: false,
isAnchor: false,
percent: {
toc: true,
rightside: false,
},
autoDarkmode: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
title: '添加typesense搜索',
isHighlightShrink: false,
isToc: true,
pageType: 'post'
}</script><link rel="stylesheet" href="/css/shuoshuo.css"><link rel="stylesheet" href="/css/shuoshuoshouye.css"><link rel="stylesheet" href="/css/nav.css"><link rel="stylesheet" href="/css/style.css"><link rel="stylesheet" href="/css/poem.css"><link rel="stylesheet" href="/css/swiper.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/instantsearch.css/themes/reset-min.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/welcomemessage/welcome.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/sidecalendar/calendar.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/rightmenu/rightmenu.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/gh/bishshi/webfont/font.css"><link rel="stylesheet" href="https://cdn.jsdmirror.com/npm/aplayer/dist/APlayer.min.css" media="all" onload="this.media=&quot;all&quot;"><!-- hexo injector head_end start --><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.4.5/css/swiper.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn1.tianli0.top/npm/hexo-butterfly-swiper/lib/swiperstyle.css" media="print" onload="this.media='all'"><!-- hexo injector head_end end --><style type="text/css">
.spoiler {
display: inline;
}
p.spoiler {
display: flex;
}
.spoiler a {
pointer-events: none;
}
.spoiler-blur, .spoiler-blur > * {
transition: text-shadow .5s ease;
}
.spoiler .spoiler-blur, .spoiler .spoiler-blur > * {
color: rgba(0, 0, 0, 0);
background-color: rgba(0, 0, 0, 0);
text-shadow: 0 0 10px grey;
cursor: pointer;
}
.spoiler .spoiler-blur:hover, .spoiler .spoiler-blur:hover > * {
text-shadow: 0 0 5px grey;
}
.spoiler-box, .spoiler-box > * {
transition: color .5s ease,
background-color .5s ease;
}
.spoiler .spoiler-box, .spoiler .spoiler-box > * {
color: black;
background-color: black;
text-shadow: none;
}</style><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="/atom.xml" title="Bi's Blog" type="application/atom+xml">
</head><body><div class="bg-animation" id="web_bg" style="background-image: url(/images/background.png);"></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img text-center"><img src="https://free.picui.cn/free/2025/08/10/689845496a283.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"></div><div class="site-data text-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">30</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">10</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">5</div></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 存档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/shuoshuo/"><i class="fa-fw fa-regular fa-comment"></i><span> 说说</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="post-bg fixed" id="page-header" style="background-image: url(/images/background.png);"><nav id="nav"><span id="blog-info"><div id="ls-menu-container"><i class="fas fa-fingerprint"></i><div id="ls-menu-panel"><div class="ls-section"><div class="ls-title">😀 个人网站</div><div class="ls-grid"><a href="/"><i class="fas fa-rss"></i> 个人博客</a><a target="_blank" rel="noopener" href="https://github.com/bishshi"><i class="fab fa-github"></i> Github</a></div></div><div class="ls-section"><div class="ls-title">😎 常用服务</div><div class="ls-grid"><a target="_blank" rel="noopener" href="https://git.biss.click/biss"><i class="fas fa-code"></i> 代码仓库</a><a target="_blank" rel="noopener" href="https://mm.biss.click"><i class="fas fa-pen-nib"></i> 日常yy</a><a target="_blank" rel="noopener" href="https://statstic.biss.click"><i class="fas fa-users"></i> 访客统计</a><a target="_blank" rel="noopener" href="https://pic.biss.click"><i class="fas fa-image"></i> 图床</a><a target="_blank" rel="noopener" href="https://chat.biss.click"><i class="fas fa-robot"></i> AI网站</a><a target="_blank" rel="noopener" href="https://git.biss.click"><i class="fas fa-code-branch"></i> 代码仓库</a></div></div><div class="ls-section"><div class="ls-title">🛸 实用工具</div><div class="ls-grid"><a target="_blank" rel="noopener" href="https://cover.biss.click"><i class="fas fa-palette"></i> 封面设计</a><a target="_blank" rel="noopener" href="https://doc.biss.click"><i class="fas fa-file"></i> 文档服务</a><a target="_blank" rel="noopener" href="https://doc.biss.click"><i class="fas fa-server"></i>服务监测</a><a target="_blank" rel="noopener" href="https://typesense.biss.click"><i class="fas fa-magnifying-glass"></i> 搜索后端</a></div></div></div></div><a class="nav-site-title" href="/"><span class="site-name">Bi's Blog</span></a></span><a class="nav-page-title" href="javascript:void(0);" onclick="btf.scrollToDest(0, 500)"><span class="site-name">添加typesense搜索</span></a><div id="nav-right"><div id="menus"><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 存档</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div><div class="menus_item"><a class="site-page" href="/shuoshuo/"><i class="fa-fw fa-regular fa-comment"></i><span> 说说</span></a></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 友链</span></a></div><div class="menus_item"><a class="site-page" href="/about/"><i class="fa-fw fas fa-heart"></i><span> 关于</span></a></div></div></div><div id="random-post-button"><a class="site-page social-icon" id="random-post-link" href="javascript:void(0);" onclick="randomPost()"><i class="fas fa-solid fa-shuffle"></i></a></div><div id="search-button"><a class="site-page social-icon search-typesense-trigger"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><span class="site-page"><i class="fas fa-bars fa-fw"></i></span></div></div></nav><div id="post-info"><h1 class="post-title">添加typesense搜索</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2026-02-05T05:14:16.000Z" title="发表于 2026-02-05 13:14:16">2026-02-05</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2026-02-22T11:37:10.097Z" title="更新于 2026-02-22 19:37:10">2026-02-22</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/technology/">技术</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/technology/website/">建站手札</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">总字数:</span><span class="word-count">3.2k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>18分钟</span></span><span class="post-meta-separator">|</span><span id="" data-flag-title=""><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">浏览量:</span><span id="twikoo_visitors"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div></header><main class="layout" id="content-inner"><div id="post"><article class="container post-content" id="article-container"><div class="ai-summary"><div class="ai-explanation" style="display: block;" data-summary="这篇文章介绍了如何在博客中集成Typesense搜索引擎包括安装Typesense、添加数据集以及同步数据的详细步骤。首先通过命令行安装Typesense并配置相关参数如API密钥和数据目录。接着安装必要的npm包并在Hexo博客中配置搜索功能指定存储搜索数据的JSON文件路径。然后编写了一个同步脚本用于读取XML文件、解析数据、检查或创建集合以及导入数据到Typesense。最后在博客底部添加了InstantSearch和Typesense的JavaScript库并根据实际情况修改了配置。">AI正在绞尽脑汁想思路ING···</div><div class="ai-title"> <div class="ai-title-left"> <i class="fa-brands fa-slack"></i><div class="ai-title-text">BiのAI摘要</div></div><div class="ai-tag" id="ai-tag">HunYuan-Lite</div></div></div><div id="post-outdate-notice" data="{&quot;limitDay&quot;:365,&quot;messagePrev&quot;:&quot;It has been&quot;,&quot;messageNext&quot;:&quot;days since the last update, the content of the article may be outdated.&quot;,&quot;postUpdate&quot;:&quot;2026-02-22 19:37:10&quot;}" hidden=""></div><p>最近在构建班级博客,用<code>ghost cms</code>在构建搜索时发现了typesense所以把他移植到这个博客上。</p>
<h1 id="安装typesense"><a href="#安装typesense" class="headerlink" title="安装typesense"></a>安装typesense</h1><p>直接用<code>docker-compose</code>:</p>
<figure class="highlight yaml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="attr">services:</span></span><br><span class="line"> <span class="attr">typesense:</span></span><br><span class="line"> <span class="attr">image:</span> <span class="string">typesense/typesense:30.1</span></span><br><span class="line"> <span class="attr">restart:</span> <span class="string">always</span></span><br><span class="line"> <span class="attr">ports:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">"8108:8108"</span></span><br><span class="line"> <span class="attr">volumes:</span></span><br><span class="line"> <span class="bullet">-</span> <span class="string">./typesense-data:/data</span></span><br><span class="line"> <span class="attr">command:</span> <span class="string">'--data-dir /data --api-key=填写key --enable-cors'</span></span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure>
<p>然后就是反向代理之类的,不过多写了。</p>
<h1 id="添加数据集"><a href="#添加数据集" class="headerlink" title="添加数据集"></a>添加数据集</h1><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 先安装库</span></span><br><span class="line">npm install hexo-generator-search</span><br><span class="line">npm install typesense xml2js</span><br></pre></td></tr></tbody></table></figure>
<p>然后在<code>config.yml</code>配置就是把文章生成json</p>
<figure class="highlight yml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">search:</span></span><br><span class="line"> <span class="attr">path:</span> <span class="string">search.json</span></span><br><span class="line"> <span class="attr">field:</span> <span class="string">post</span></span><br><span class="line"> <span class="attr">content:</span> <span class="literal">true</span></span><br></pre></td></tr></tbody></table></figure>
<p>创建一个数据同步脚本<code>sync_typesense.js</code>:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">Typesense</span> = <span class="built_in">require</span>(<span class="string">'typesense'</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">const</span> xml2js = <span class="built_in">require</span>(<span class="string">'xml2js'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// --- 配置区域 ---</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">CONFIG</span> = {</span><br><span class="line"> <span class="attr">apiKey</span>: <span class="string">'你的Admin-API-Key'</span>, <span class="comment">// 必须是 Admin Key</span></span><br><span class="line"> <span class="attr">host</span>: <span class="string">'你的Typesense主机地址'</span>, </span><br><span class="line"> <span class="attr">port</span>: <span class="number">443</span>,</span><br><span class="line"> <span class="attr">protocol</span>: <span class="string">'https'</span>,</span><br><span class="line"> <span class="attr">collectionName</span>: <span class="string">'blogs'</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> client = <span class="keyword">new</span> <span class="title class_">Typesense</span>.<span class="title class_">Client</span>({</span><br><span class="line"> <span class="string">'nodes'</span>: [{ <span class="string">'host'</span>: <span class="variable constant_">CONFIG</span>.<span class="property">host</span>, <span class="string">'port'</span>: <span class="variable constant_">CONFIG</span>.<span class="property">port</span>, <span class="string">'protocol'</span>: <span class="variable constant_">CONFIG</span>.<span class="property">protocol</span> }],</span><br><span class="line"> <span class="string">'apiKey'</span>: <span class="variable constant_">CONFIG</span>.<span class="property">apiKey</span>,</span><br><span class="line"> <span class="string">'connectionTimeoutSeconds'</span>: <span class="number">5</span></span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">sync</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 1. 读取并解析 XML</span></span><br><span class="line"> <span class="keyword">const</span> xml = fs.<span class="title function_">readFileSync</span>(<span class="string">'./public/search.xml'</span>, <span class="string">'utf8'</span>);</span><br><span class="line"> <span class="keyword">const</span> parser = <span class="keyword">new</span> xml2js.<span class="title class_">Parser</span>({ <span class="attr">explicitArray</span>: <span class="literal">false</span> });</span><br><span class="line"> <span class="keyword">const</span> result = <span class="keyword">await</span> parser.<span class="title function_">parseStringPromise</span>(xml);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 提取文章列表 (处理单篇文章和多篇文章的情况)</span></span><br><span class="line"> <span class="keyword">let</span> entries = result.<span class="property">search</span>.<span class="property">entry</span>;</span><br><span class="line"> <span class="keyword">if</span> (!<span class="title class_">Array</span>.<span class="title function_">isArray</span>(entries)) entries = [entries];</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 格式化数据以适配 Typesense</span></span><br><span class="line"> <span class="keyword">const</span> documents = entries.<span class="title function_">map</span>(<span class="function"><span class="params">post</span> =&gt;</span> ({</span><br><span class="line"> <span class="attr">title</span>: post.<span class="property">title</span>,</span><br><span class="line"> <span class="attr">url</span>: post.<span class="property">url</span>,</span><br><span class="line"> <span class="attr">content</span>: post.<span class="property">content</span>,</span><br><span class="line"> <span class="attr">categories</span>: post.<span class="property">categories</span> ? (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(post.<span class="property">categories</span>.<span class="property">category</span>) ? post.<span class="property">categories</span>.<span class="property">category</span> : [post.<span class="property">categories</span>.<span class="property">category</span>]) : [],</span><br><span class="line"> <span class="attr">tags</span>: post.<span class="property">tags</span> ? (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(post.<span class="property">tags</span>.<span class="property">tag</span>) ? post.<span class="property">tags</span>.<span class="property">tag</span> : [post.<span class="property">tags</span>.<span class="property">tag</span>]) : [],</span><br><span class="line"> }));</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2. 检查或创建 Collection (Schema)</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">await</span> client.<span class="title function_">collections</span>(<span class="variable constant_">CONFIG</span>.<span class="property">collectionName</span>).<span class="title function_">retrieve</span>();</span><br><span class="line"> } <span class="keyword">catch</span> (err) {</span><br><span class="line"> <span class="keyword">const</span> schema = {</span><br><span class="line"> <span class="attr">name</span>: <span class="variable constant_">CONFIG</span>.<span class="property">collectionName</span>,</span><br><span class="line"> <span class="attr">fields</span>: [</span><br><span class="line"> { <span class="attr">name</span>: <span class="string">'title'</span>, <span class="attr">type</span>: <span class="string">'string'</span>, <span class="attr">locale</span>: <span class="string">'zh'</span>},</span><br><span class="line"> { <span class="attr">name</span>: <span class="string">'content'</span>, <span class="attr">type</span>: <span class="string">'string'</span>, <span class="attr">locale</span>: <span class="string">'zh'</span>},</span><br><span class="line"> { <span class="attr">name</span>: <span class="string">'url'</span>, <span class="attr">type</span>: <span class="string">'string'</span> },</span><br><span class="line"> { <span class="attr">name</span>: <span class="string">'categories'</span>, <span class="attr">type</span>: <span class="string">'string[]'</span>, <span class="attr">facet</span>: <span class="literal">true</span> },</span><br><span class="line"> { <span class="attr">name</span>: <span class="string">'tags'</span>, <span class="attr">type</span>: <span class="string">'string[]'</span>, <span class="attr">facet</span>: <span class="literal">true</span> }</span><br><span class="line"> ]</span><br><span class="line"> };</span><br><span class="line"> <span class="keyword">await</span> client.<span class="title function_">collections</span>().<span class="title function_">create</span>(schema);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Collection created!'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3. 导入数据 (使用 upsert 模式:存在则更新,不存在则创建)</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Syncing <span class="subst">${documents.length}</span> posts to Typesense...`</span>);</span><br><span class="line"> <span class="keyword">await</span> client.<span class="title function_">collections</span>(<span class="variable constant_">CONFIG</span>.<span class="property">collectionName</span>).<span class="title function_">documents</span>().<span class="keyword">import</span>(documents, { <span class="attr">action</span>: <span class="string">'upsert'</span> });</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'Sync complete!'</span>);</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">'Sync failed:'</span>, error);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="title function_">sync</span>();</span><br></pre></td></tr></tbody></table></figure>
<p>然后运行<code>node sync_typesense.js</code></p>
<h1 id="创建只读key"><a href="#创建只读key" class="headerlink" title="创建只读key"></a>创建只读key</h1><p>把下面代码存储成jsnode运行就行。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'https'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> data = <span class="title class_">JSON</span>.<span class="title function_">stringify</span>({</span><br><span class="line"> <span class="string">"description"</span>: <span class="string">"Public search only key"</span>,</span><br><span class="line"> <span class="string">"actions"</span>: [<span class="string">"documents:search"</span>],</span><br><span class="line"> <span class="string">"collections"</span>: [<span class="string">"blogs"</span>]</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> options = {</span><br><span class="line"> <span class="attr">hostname</span>: <span class="string">''</span>, <span class="comment">// 不要带 https://</span></span><br><span class="line"> <span class="attr">port</span>: <span class="number">443</span>,</span><br><span class="line"> <span class="attr">path</span>: <span class="string">'/keys'</span>,</span><br><span class="line"> <span class="attr">method</span>: <span class="string">'POST'</span>,</span><br><span class="line"> <span class="attr">headers</span>: {</span><br><span class="line"> <span class="string">'X-TYPESENSE-API-KEY'</span>: <span class="string">'你的admin key'</span>,</span><br><span class="line"> <span class="string">'Content-Type'</span>: <span class="string">'application/json'</span>,</span><br><span class="line"> <span class="string">'Content-Length'</span>: data.<span class="property">length</span></span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> req = http.<span class="title function_">request</span>(options, <span class="function"><span class="params">res</span> =&gt;</span> {</span><br><span class="line"> res.<span class="title function_">on</span>(<span class="string">'data'</span>, <span class="function"><span class="params">d</span> =&gt;</span> { process.<span class="property">stdout</span>.<span class="title function_">write</span>(d); });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">req.<span class="title function_">on</span>(<span class="string">'error'</span>, <span class="function"><span class="params">error</span> =&gt;</span> { <span class="variable language_">console</span>.<span class="title function_">error</span>(error); });</span><br><span class="line">req.<span class="title function_">write</span>(data);</span><br><span class="line">req.<span class="title function_">end</span>();</span><br></pre></td></tr></tbody></table></figure>
<h1 id="博客添加搜索"><a href="#博客添加搜索" class="headerlink" title="博客添加搜索"></a>博客添加搜索</h1><p><code>config.yaml</code>inject bottom添加</p>
<figure class="highlight yaml"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="string">&lt;script</span> <span class="string">src="https://cdn.jsdmirror.com/npm/instantsearch.js@4.56.0"&gt;&lt;/script&gt;</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">&lt;script</span> <span class="string">src="https://cdn.jsdmirror.com/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"&gt;&lt;/script&gt;</span></span><br></pre></td></tr></tbody></table></figure>
<p>为了方便,我直接修改了<code>\themes\butterfly\source\js\search\local_search.js</code></p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br><span class="line">251</span><br><span class="line">252</span><br><span class="line">253</span><br><span class="line">254</span><br><span class="line">255</span><br><span class="line">256</span><br><span class="line">257</span><br><span class="line">258</span><br><span class="line">259</span><br><span class="line">260</span><br><span class="line">261</span><br><span class="line">262</span><br><span class="line">263</span><br><span class="line">264</span><br><span class="line">265</span><br><span class="line">266</span><br><span class="line">267</span><br><span class="line">268</span><br><span class="line">269</span><br><span class="line">270</span><br><span class="line">271</span><br><span class="line">272</span><br><span class="line">273</span><br><span class="line">274</span><br><span class="line">275</span><br><span class="line">276</span><br><span class="line">277</span><br><span class="line">278</span><br><span class="line">279</span><br><span class="line">280</span><br><span class="line">281</span><br><span class="line">282</span><br><span class="line">283</span><br><span class="line">284</span><br><span class="line">285</span><br><span class="line">286</span><br><span class="line">287</span><br><span class="line">288</span><br><span class="line">289</span><br><span class="line">290</span><br><span class="line">291</span><br><span class="line">292</span><br><span class="line">293</span><br><span class="line">294</span><br><span class="line">295</span><br><span class="line">296</span><br><span class="line">297</span><br><span class="line">298</span><br><span class="line">299</span><br><span class="line">300</span><br><span class="line">301</span><br><span class="line">302</span><br><span class="line">303</span><br><span class="line">304</span><br><span class="line">305</span><br><span class="line">306</span><br><span class="line">307</span><br><span class="line">308</span><br><span class="line">309</span><br><span class="line">310</span><br><span class="line">311</span><br><span class="line">312</span><br><span class="line">313</span><br><span class="line">314</span><br><span class="line">315</span><br><span class="line">316</span><br><span class="line">317</span><br><span class="line">318</span><br><span class="line">319</span><br><span class="line">320</span><br><span class="line">321</span><br><span class="line">322</span><br><span class="line">323</span><br><span class="line">324</span><br><span class="line">325</span><br><span class="line">326</span><br><span class="line">327</span><br><span class="line">328</span><br><span class="line">329</span><br><span class="line">330</span><br><span class="line">331</span><br><span class="line">332</span><br><span class="line">333</span><br><span class="line">334</span><br><span class="line">335</span><br><span class="line">336</span><br><span class="line">337</span><br><span class="line">338</span><br><span class="line">339</span><br><span class="line">340</span><br><span class="line">341</span><br><span class="line">342</span><br><span class="line">343</span><br><span class="line">344</span><br><span class="line">345</span><br><span class="line">346</span><br><span class="line">347</span><br><span class="line">348</span><br><span class="line">349</span><br><span class="line">350</span><br><span class="line">351</span><br><span class="line">352</span><br><span class="line">353</span><br><span class="line">354</span><br><span class="line">355</span><br><span class="line">356</span><br><span class="line">357</span><br><span class="line">358</span><br><span class="line">359</span><br><span class="line">360</span><br><span class="line">361</span><br><span class="line">362</span><br><span class="line">363</span><br><span class="line">364</span><br><span class="line">365</span><br><span class="line">366</span><br><span class="line">367</span><br><span class="line">368</span><br><span class="line">369</span><br><span class="line">370</span><br><span class="line">371</span><br><span class="line">372</span><br><span class="line">373</span><br><span class="line">374</span><br><span class="line">375</span><br><span class="line">376</span><br><span class="line">377</span><br><span class="line">378</span><br><span class="line">379</span><br><span class="line">380</span><br><span class="line">381</span><br><span class="line">382</span><br><span class="line">383</span><br><span class="line">384</span><br><span class="line">385</span><br><span class="line">386</span><br><span class="line">387</span><br><span class="line">388</span><br><span class="line">389</span><br><span class="line">390</span><br><span class="line">391</span><br><span class="line">392</span><br><span class="line">393</span><br><span class="line">394</span><br><span class="line">395</span><br><span class="line">396</span><br><span class="line">397</span><br><span class="line">398</span><br><span class="line">399</span><br><span class="line">400</span><br><span class="line">401</span><br><span class="line">402</span><br><span class="line">403</span><br><span class="line">404</span><br><span class="line">405</span><br><span class="line">406</span><br><span class="line">407</span><br><span class="line">408</span><br><span class="line">409</span><br><span class="line">410</span><br><span class="line">411</span><br><span class="line">412</span><br><span class="line">413</span><br><span class="line">414</span><br><span class="line">415</span><br><span class="line">416</span><br><span class="line">417</span><br><span class="line">418</span><br><span class="line">419</span><br><span class="line">420</span><br><span class="line">421</span><br><span class="line">422</span><br><span class="line">423</span><br><span class="line">424</span><br><span class="line">425</span><br><span class="line">426</span><br><span class="line">427</span><br><span class="line">428</span><br><span class="line">429</span><br><span class="line">430</span><br><span class="line">431</span><br><span class="line">432</span><br><span class="line">433</span><br><span class="line">434</span><br><span class="line">435</span><br><span class="line">436</span><br><span class="line">437</span><br><span class="line">438</span><br><span class="line">439</span><br><span class="line">440</span><br><span class="line">441</span><br><span class="line">442</span><br><span class="line">443</span><br><span class="line">444</span><br><span class="line">445</span><br><span class="line">446</span><br><span class="line">447</span><br><span class="line">448</span><br><span class="line">449</span><br><span class="line">450</span><br><span class="line">451</span><br><span class="line">452</span><br><span class="line">453</span><br><span class="line">454</span><br><span class="line">455</span><br><span class="line">456</span><br><span class="line">457</span><br><span class="line">458</span><br><span class="line">459</span><br><span class="line">460</span><br><span class="line">461</span><br><span class="line">462</span><br><span class="line">463</span><br><span class="line">464</span><br><span class="line">465</span><br><span class="line">466</span><br><span class="line">467</span><br><span class="line">468</span><br><span class="line">469</span><br><span class="line">470</span><br><span class="line">471</span><br><span class="line">472</span><br><span class="line">473</span><br><span class="line">474</span><br><span class="line">475</span><br><span class="line">476</span><br><span class="line">477</span><br><span class="line">478</span><br><span class="line">479</span><br><span class="line">480</span><br><span class="line">481</span><br><span class="line">482</span><br><span class="line">483</span><br><span class="line">484</span><br><span class="line">485</span><br><span class="line">486</span><br><span class="line">487</span><br><span class="line">488</span><br><span class="line">489</span><br><span class="line">490</span><br><span class="line">491</span><br><span class="line">492</span><br><span class="line">493</span><br><span class="line">494</span><br><span class="line">495</span><br><span class="line">496</span><br><span class="line">497</span><br><span class="line">498</span><br><span class="line">499</span><br><span class="line">500</span><br><span class="line">501</span><br><span class="line">502</span><br><span class="line">503</span><br><span class="line">504</span><br><span class="line">505</span><br><span class="line">506</span><br><span class="line">507</span><br><span class="line">508</span><br><span class="line">509</span><br><span class="line">510</span><br><span class="line">511</span><br><span class="line">512</span><br><span class="line">513</span><br><span class="line">514</span><br><span class="line">515</span><br><span class="line">516</span><br><span class="line">517</span><br><span class="line">518</span><br><span class="line">519</span><br><span class="line">520</span><br><span class="line">521</span><br><span class="line">522</span><br><span class="line">523</span><br><span class="line">524</span><br><span class="line">525</span><br><span class="line">526</span><br><span class="line">527</span><br><span class="line">528</span><br><span class="line">529</span><br><span class="line">530</span><br><span class="line">531</span><br><span class="line">532</span><br><span class="line">533</span><br><span class="line">534</span><br><span class="line">535</span><br><span class="line">536</span><br><span class="line">537</span><br><span class="line">538</span><br><span class="line">539</span><br><span class="line">540</span><br><span class="line">541</span><br><span class="line">542</span><br><span class="line">543</span><br><span class="line">544</span><br><span class="line">545</span><br></pre></td><td class="code"><pre><span class="line">(<span class="keyword">function</span> (<span class="params"></span>) {</span><br><span class="line"> <span class="string">'use strict'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="comment">// 配置区域 - 请根据实际情况修改</span></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="keyword">const</span> <span class="variable constant_">CONFIG</span> = {</span><br><span class="line"> <span class="attr">apiKey</span>: <span class="string">""</span>, <span class="comment">// ⚠️ 建议使用 Search-Only API Key</span></span><br><span class="line"> <span class="attr">server</span>: {</span><br><span class="line"> <span class="attr">host</span>: <span class="string">"host"</span>,</span><br><span class="line"> <span class="attr">port</span>: <span class="string">"443"</span>,</span><br><span class="line"> <span class="attr">protocol</span>: <span class="string">"https"</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">indexName</span>: <span class="string">"blogs"</span>,</span><br><span class="line"> <span class="attr">searchParams</span>: {</span><br><span class="line"> <span class="attr">query_by</span>: <span class="string">"title,content"</span>,</span><br><span class="line"> <span class="attr">highlight_full_fields</span>: <span class="string">"title,content"</span>,</span><br><span class="line"> <span class="attr">per_page</span>: <span class="number">8</span>,</span><br><span class="line"> <span class="attr">num_typos</span>: <span class="number">1</span>,</span><br><span class="line"> <span class="attr">typo_tokens_threshold</span>: <span class="number">1</span>,</span><br><span class="line"> <span class="attr">prefix</span>: <span class="literal">true</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">ui</span>: {</span><br><span class="line"> <span class="attr">maxRetries</span>: <span class="number">10</span>,</span><br><span class="line"> <span class="attr">retryDelay</span>: <span class="number">100</span>,</span><br><span class="line"> <span class="attr">animationDuration</span>: <span class="number">300</span></span><br><span class="line"> }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="comment">// 状态管理</span></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="keyword">let</span> searchInstance = <span class="literal">null</span>;</span><br><span class="line"> <span class="keyword">let</span> isInitialized = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">let</span> isSearchOpen = <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">let</span> initRetryCount = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">const</span> <span class="variable constant_">MAX_INIT_RETRIES</span> = <span class="number">30</span>; <span class="comment">// 最多重试30次 (3秒)</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="comment">// 错误提示函数</span></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">showErrorMessage</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">const</span> hitsContainer = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'hits'</span>);</span><br><span class="line"> <span class="keyword">if</span> (!hitsContainer) <span class="keyword">return</span>;</span><br><span class="line"> </span><br><span class="line"> hitsContainer.<span class="property">innerHTML</span> = </span><br><span class="line"> <span class="string">'&lt;div class="ts-empty"&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;div style="color: #f44336;"&gt;&lt;i class="fas fa-exclamation-triangle" style="font-size: 3rem;"&gt;&lt;/i&gt;&lt;/div&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;div style="font-size: 1.1rem; font-weight: bold; margin: 15px 0;"&gt;搜索服务加载失败&lt;/div&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;div style="font-size: 0.9rem; color: #666; line-height: 1.8;"&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;p&gt;依赖库未能正确加载,请检查以下配置:&lt;/p&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;ol style="text-align: left; max-width: 500px; margin: 15px auto;"&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;li&gt;确认已在 &lt;code&gt;_config.butterfly.yml&lt;/code&gt; 中正确引入依赖&lt;/li&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;li&gt;检查 JS 文件加载顺序(先 instantsearch.js再 adapter&lt;/li&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;li&gt;尝试更换 CDN 或使用本地文件&lt;/li&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;li&gt;打开浏览器控制台查看详细错误信息&lt;/li&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;/ol&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;/div&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;div style="margin-top: 20px;"&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;button onclick="location.reload()" style="padding: 10px 20px; background: #49b1f5; color: white; border: none; border-radius: 5px; cursor: pointer;"&gt;重新加载页面&lt;/button&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;/div&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;/div&gt;'</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="comment">// 1. 动态插入 HTML 结构</span></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="keyword">const</span> searchHTML = <span class="string">`</span></span><br><span class="line"><span class="string"> &lt;div id="typesense-search-mask" class="ts-mask" style="display:none;"&gt;</span></span><br><span class="line"><span class="string"> &lt;div id="typesense-search-container" class="ts-container"&gt;</span></span><br><span class="line"><span class="string"> &lt;div class="ts-header"&gt;</span></span><br><span class="line"><span class="string"> &lt;span class="ts-title"&gt;</span></span><br><span class="line"><span class="string"> &lt;i class="fas fa-search"&gt;&lt;/i&gt; 本站搜索</span></span><br><span class="line"><span class="string"> &lt;/span&gt;</span></span><br><span class="line"><span class="string"> &lt;span id="close-typesense" class="ts-close" aria-label="关闭搜索"&gt;&amp;times;&lt;/span&gt;</span></span><br><span class="line"><span class="string"> &lt;/div&gt;</span></span><br><span class="line"><span class="string"> &lt;div id="searchbox"&gt;&lt;/div&gt;</span></span><br><span class="line"><span class="string"> &lt;div id="stats" class="ts-stats"&gt;&lt;/div&gt;</span></span><br><span class="line"><span class="string"> &lt;div id="hits" class="ts-hits"&gt;&lt;/div&gt;</span></span><br><span class="line"><span class="string"> &lt;div id="pagination" class="ts-pagination"&gt;&lt;/div&gt;</span></span><br><span class="line"><span class="string"> &lt;div class="ts-footer"&gt;</span></span><br><span class="line"><span class="string"> &lt;small&gt;Search powered by &lt;strong&gt;Typesense&lt;/strong&gt;&lt;/small&gt;</span></span><br><span class="line"><span class="string"> &lt;/div&gt;</span></span><br><span class="line"><span class="string"> &lt;/div&gt;</span></span><br><span class="line"><span class="string"> &lt;/div&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"> &lt;style&gt;</span></span><br><span class="line"><span class="string"> .ts-mask {</span></span><br><span class="line"><span class="string"> position: fixed;</span></span><br><span class="line"><span class="string"> top: 0;</span></span><br><span class="line"><span class="string"> left: 0;</span></span><br><span class="line"><span class="string"> width: 100%;</span></span><br><span class="line"><span class="string"> height: 100%;</span></span><br><span class="line"><span class="string"> background: rgba(0, 0, 0, 0.7);</span></span><br><span class="line"><span class="string"> z-index: 10000;</span></span><br><span class="line"><span class="string"> backdrop-filter: blur(5px);</span></span><br><span class="line"><span class="string"> -webkit-backdrop-filter: blur(5px);</span></span><br><span class="line"><span class="string"> opacity: 0;</span></span><br><span class="line"><span class="string"> transition: opacity 300ms ease;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-mask.active { opacity: 1; }</span></span><br><span class="line"><span class="string"> .ts-container {</span></span><br><span class="line"><span class="string"> margin: 5% auto;</span></span><br><span class="line"><span class="string"> width: 90%;</span></span><br><span class="line"><span class="string"> max-width: 650px;</span></span><br><span class="line"><span class="string"> background: var(--search-bg, var(--card-bg, #fff));</span></span><br><span class="line"><span class="string"> padding: 25px;</span></span><br><span class="line"><span class="string"> border-radius: 12px;</span></span><br><span class="line"><span class="string"> box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);</span></span><br><span class="line"><span class="string"> position: relative;</span></span><br><span class="line"><span class="string"> z-index: 10001;</span></span><br><span class="line"><span class="string"> transform: translateY(-50px);</span></span><br><span class="line"><span class="string"> opacity: 0;</span></span><br><span class="line"><span class="string"> transition: all 300ms ease;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-mask.active .ts-container {</span></span><br><span class="line"><span class="string"> transform: translateY(0);</span></span><br><span class="line"><span class="string"> opacity: 1;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-header {</span></span><br><span class="line"><span class="string"> display: flex;</span></span><br><span class="line"><span class="string"> justify-content: space-between;</span></span><br><span class="line"><span class="string"> align-items: center;</span></span><br><span class="line"><span class="string"> margin-bottom: 20px;</span></span><br><span class="line"><span class="string"> border-bottom: 2px solid var(--text-highlight-color, #49b1f5);</span></span><br><span class="line"><span class="string"> padding-bottom: 10px;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-title {</span></span><br><span class="line"><span class="string"> font-size: 1.2rem;</span></span><br><span class="line"><span class="string"> font-weight: bold;</span></span><br><span class="line"><span class="string"> color: var(--text-highlight-color, #49b1f5);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-close {</span></span><br><span class="line"><span class="string"> cursor: pointer;</span></span><br><span class="line"><span class="string"> font-size: 28px;</span></span><br><span class="line"><span class="string"> color: var(--font-color, #333);</span></span><br><span class="line"><span class="string"> line-height: 1;</span></span><br><span class="line"><span class="string"> transition: color 0.2s, transform 0.2s;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-close:hover {</span></span><br><span class="line"><span class="string"> color: var(--text-highlight-color, #49b1f5);</span></span><br><span class="line"><span class="string"> transform: scale(1.1);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ais-SearchBox-input {</span></span><br><span class="line"><span class="string"> position: relative;</span></span><br><span class="line"><span class="string"> z-index: 10002;</span></span><br><span class="line"><span class="string"> cursor: text;</span></span><br><span class="line"><span class="string"> padding: 12px 40px 12px 15px !important;</span></span><br><span class="line"><span class="string"> border-radius: 8px !important;</span></span><br><span class="line"><span class="string"> border: 2px solid #eee !important;</span></span><br><span class="line"><span class="string"> width: 100%;</span></span><br><span class="line"><span class="string"> outline: none;</span></span><br><span class="line"><span class="string"> transition: border-color 0.3s, box-shadow 0.3s;</span></span><br><span class="line"><span class="string"> background: var(--card-bg, #fff);</span></span><br><span class="line"><span class="string"> color: var(--font-color, #333);</span></span><br><span class="line"><span class="string"> font-size: 1rem;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ais-SearchBox-input:focus {</span></span><br><span class="line"><span class="string"> border-color: var(--text-highlight-color, #49b1f5) !important;</span></span><br><span class="line"><span class="string"> box-shadow: 0 0 0 3px rgba(73, 177, 245, 0.1);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-stats {</span></span><br><span class="line"><span class="string"> margin: 10px 0;</span></span><br><span class="line"><span class="string"> font-size: 0.85rem;</span></span><br><span class="line"><span class="string"> color: var(--font-color, #666);</span></span><br><span class="line"><span class="string"> opacity: 0.8;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-hits {</span></span><br><span class="line"><span class="string"> max-height: 55vh;</span></span><br><span class="line"><span class="string"> overflow-y: auto;</span></span><br><span class="line"><span class="string"> margin-top: 15px;</span></span><br><span class="line"><span class="string"> padding-right: 5px;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-hits::-webkit-scrollbar { width: 6px; }</span></span><br><span class="line"><span class="string"> .ts-hits::-webkit-scrollbar-track {</span></span><br><span class="line"><span class="string"> background: var(--card-bg, #f1f1f1);</span></span><br><span class="line"><span class="string"> border-radius: 10px;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-hits::-webkit-scrollbar-thumb {</span></span><br><span class="line"><span class="string"> background: var(--text-highlight-color, #49b1f5);</span></span><br><span class="line"><span class="string"> border-radius: 10px;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-empty {</span></span><br><span class="line"><span class="string"> text-align: center;</span></span><br><span class="line"><span class="string"> padding: 40px 20px;</span></span><br><span class="line"><span class="string"> color: var(--font-color, #999);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-empty i {</span></span><br><span class="line"><span class="string"> font-size: 3rem;</span></span><br><span class="line"><span class="string"> margin-bottom: 15px;</span></span><br><span class="line"><span class="string"> opacity: 0.3;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-empty code {</span></span><br><span class="line"><span class="string"> background: #f5f5f5;</span></span><br><span class="line"><span class="string"> padding: 2px 6px;</span></span><br><span class="line"><span class="string"> border-radius: 3px;</span></span><br><span class="line"><span class="string"> font-family: monospace;</span></span><br><span class="line"><span class="string"> color: #e91e63;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-empty ol {</span></span><br><span class="line"><span class="string"> padding-left: 20px;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-empty li {</span></span><br><span class="line"><span class="string"> margin: 8px 0;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-result-item {</span></span><br><span class="line"><span class="string"> border-radius: 8px;</span></span><br><span class="line"><span class="string"> transition: all 0.2s ease;</span></span><br><span class="line"><span class="string"> margin-bottom: 10px;</span></span><br><span class="line"><span class="string"> padding: 15px;</span></span><br><span class="line"><span class="string"> border: 1px solid transparent;</span></span><br><span class="line"><span class="string"> text-decoration: none;</span></span><br><span class="line"><span class="string"> display: block;</span></span><br><span class="line"><span class="string"> background: var(--card-bg, #fff);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-result-item:hover {</span></span><br><span class="line"><span class="string"> background: var(--text-bg-hover, rgba(73, 177, 245, 0.05));</span></span><br><span class="line"><span class="string"> border-color: var(--text-highlight-color, #49b1f5);</span></span><br><span class="line"><span class="string"> transform: translateX(5px);</span></span><br><span class="line"><span class="string"> box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-result-title {</span></span><br><span class="line"><span class="string"> font-weight: bold;</span></span><br><span class="line"><span class="string"> color: var(--text-highlight-color, #49b1f5);</span></span><br><span class="line"><span class="string"> font-size: 1.1rem;</span></span><br><span class="line"><span class="string"> margin-bottom: 8px;</span></span><br><span class="line"><span class="string"> display: block;</span></span><br><span class="line"><span class="string"> line-height: 1.4;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-result-content {</span></span><br><span class="line"><span class="string"> font-size: 0.9rem;</span></span><br><span class="line"><span class="string"> color: var(--font-color, #666);</span></span><br><span class="line"><span class="string"> line-height: 1.6;</span></span><br><span class="line"><span class="string"> opacity: 0.85;</span></span><br><span class="line"><span class="string"> display: -webkit-box;</span></span><br><span class="line"><span class="string"> -webkit-line-clamp: 2;</span></span><br><span class="line"><span class="string"> -webkit-box-orient: vertical;</span></span><br><span class="line"><span class="string"> overflow: hidden;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-result-item mark {</span></span><br><span class="line"><span class="string"> background: #ffeb3b;</span></span><br><span class="line"><span class="string"> color: #000;</span></span><br><span class="line"><span class="string"> padding: 2px 4px;</span></span><br><span class="line"><span class="string"> border-radius: 3px;</span></span><br><span class="line"><span class="string"> font-weight: 500;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-pagination {</span></span><br><span class="line"><span class="string"> margin-top: 20px;</span></span><br><span class="line"><span class="string"> display: flex;</span></span><br><span class="line"><span class="string"> justify-content: center;</span></span><br><span class="line"><span class="string"> gap: 5px;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ais-Pagination-list {</span></span><br><span class="line"><span class="string"> display: flex;</span></span><br><span class="line"><span class="string"> list-style: none;</span></span><br><span class="line"><span class="string"> padding: 0;</span></span><br><span class="line"><span class="string"> margin: 0;</span></span><br><span class="line"><span class="string"> gap: 5px;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ais-Pagination-link {</span></span><br><span class="line"><span class="string"> display: block;</span></span><br><span class="line"><span class="string"> padding: 8px 12px;</span></span><br><span class="line"><span class="string"> border: 1px solid #ddd;</span></span><br><span class="line"><span class="string"> border-radius: 5px;</span></span><br><span class="line"><span class="string"> color: var(--font-color, #333);</span></span><br><span class="line"><span class="string"> text-decoration: none;</span></span><br><span class="line"><span class="string"> transition: all 0.2s;</span></span><br><span class="line"><span class="string"> background: var(--card-bg, #fff);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ais-Pagination-link:hover {</span></span><br><span class="line"><span class="string"> background: var(--text-highlight-color, #49b1f5);</span></span><br><span class="line"><span class="string"> color: #fff;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ais-Pagination-item--selected .ais-Pagination-link {</span></span><br><span class="line"><span class="string"> background: var(--text-highlight-color, #49b1f5);</span></span><br><span class="line"><span class="string"> color: #fff;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-footer {</span></span><br><span class="line"><span class="string"> text-align: right;</span></span><br><span class="line"><span class="string"> margin-top: 15px;</span></span><br><span class="line"><span class="string"> border-top: 1px solid var(--border-color, #eee);</span></span><br><span class="line"><span class="string"> padding-top: 10px;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> @media (max-width: 768px) {</span></span><br><span class="line"><span class="string"> .ts-container {</span></span><br><span class="line"><span class="string"> margin: 10px;</span></span><br><span class="line"><span class="string"> width: calc(100% - 20px);</span></span><br><span class="line"><span class="string"> padding: 20px 15px;</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> .ts-hits { max-height: 50vh; }</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> [data-theme="dark"] .ts-mask,</span></span><br><span class="line"><span class="string"> .dark-mode .ts-mask {</span></span><br><span class="line"><span class="string"> background: rgba(0, 0, 0, 0.85);</span></span><br><span class="line"><span class="string"> }</span></span><br><span class="line"><span class="string"> &lt;/style&gt;</span></span><br><span class="line"><span class="string"> `</span>;</span><br><span class="line"></span><br><span class="line"> <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">insertAdjacentHTML</span>(<span class="string">'beforeend'</span>, searchHTML);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">const</span> mask = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'typesense-search-mask'</span>);</span><br><span class="line"> <span class="keyword">const</span> closeBtn = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'close-typesense'</span>);</span><br><span class="line"> <span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">'typesense-search-container'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="comment">// 搜索控制</span></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">openSearch</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">if</span> (isSearchOpen) <span class="keyword">return</span>;</span><br><span class="line"> isSearchOpen = <span class="literal">true</span>;</span><br><span class="line"> mask.<span class="property">style</span>.<span class="property">display</span> = <span class="string">'block'</span>;</span><br><span class="line"> <span class="keyword">void</span> mask.<span class="property">offsetWidth</span>;</span><br><span class="line"> mask.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">'active'</span>);</span><br><span class="line"> <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">style</span>.<span class="property">overflow</span> = <span class="string">'hidden'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (!isInitialized) {</span><br><span class="line"> <span class="title function_">initTypesense</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="title function_">focusSearchInput</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">closeSearch</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">if</span> (!isSearchOpen) <span class="keyword">return</span>;</span><br><span class="line"> isSearchOpen = <span class="literal">false</span>;</span><br><span class="line"> mask.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">'active'</span>);</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> mask.<span class="property">style</span>.<span class="property">display</span> = <span class="string">'none'</span>;</span><br><span class="line"> <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">style</span>.<span class="property">overflow</span> = <span class="string">''</span>;</span><br><span class="line"> }, <span class="variable constant_">CONFIG</span>.<span class="property">ui</span>.<span class="property">animationDuration</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">focusSearchInput</span>(<span class="params">retryCount</span>) {</span><br><span class="line"> retryCount = retryCount || <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">const</span> input = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">'.ais-SearchBox-input'</span>);</span><br><span class="line"> <span class="keyword">if</span> (input) {</span><br><span class="line"> input.<span class="title function_">focus</span>();</span><br><span class="line"> input.<span class="title function_">select</span>();</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (retryCount &lt; <span class="variable constant_">CONFIG</span>.<span class="property">ui</span>.<span class="property">maxRetries</span>) {</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">focusSearchInput</span>(retryCount + <span class="number">1</span>);</span><br><span class="line"> }, <span class="variable constant_">CONFIG</span>.<span class="property">ui</span>.<span class="property">retryDelay</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="comment">// 事件监听</span></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">function</span>(<span class="params">e</span>) {</span><br><span class="line"> <span class="keyword">if</span> (e.<span class="property">target</span>.<span class="title function_">closest</span>(<span class="string">'.search-typesense-trigger'</span>)) {</span><br><span class="line"> e.<span class="title function_">preventDefault</span>();</span><br><span class="line"> <span class="title function_">openSearch</span>();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> closeBtn.<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, closeSearch);</span><br><span class="line"> mask.<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">function</span>(<span class="params">e</span>) {</span><br><span class="line"> <span class="keyword">if</span> (e.<span class="property">target</span> === mask) <span class="title function_">closeSearch</span>();</span><br><span class="line"> });</span><br><span class="line"> container.<span class="title function_">addEventListener</span>(<span class="string">'click'</span>, <span class="keyword">function</span>(<span class="params">e</span>) {</span><br><span class="line"> e.<span class="title function_">stopPropagation</span>();</span><br><span class="line"> });</span><br><span class="line"> <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">'keydown'</span>, <span class="keyword">function</span>(<span class="params">e</span>) {</span><br><span class="line"> <span class="keyword">if</span> (e.<span class="property">key</span> === <span class="string">'Escape'</span> &amp;&amp; isSearchOpen) {</span><br><span class="line"> <span class="title function_">closeSearch</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> ((e.<span class="property">ctrlKey</span> || e.<span class="property">metaKey</span>) &amp;&amp; e.<span class="property">key</span> === <span class="string">'k'</span>) {</span><br><span class="line"> e.<span class="title function_">preventDefault</span>();</span><br><span class="line"> <span class="title function_">openSearch</span>();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="comment">// Typesense 初始化(带重试限制)</span></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">initTypesense</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">if</span> (isInitialized || searchInstance) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">'Typesense 搜索已初始化'</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">var</span> instantsearchLoaded = <span class="keyword">typeof</span> instantsearch !== <span class="string">'undefined'</span>;</span><br><span class="line"> <span class="keyword">var</span> adapterLoaded = <span class="keyword">typeof</span> <span class="title class_">TypesenseInstantSearchAdapter</span> !== <span class="string">'undefined'</span> || </span><br><span class="line"> <span class="keyword">typeof</span> <span class="variable language_">window</span>.<span class="property">TypesenseInstantSearchAdapter</span> !== <span class="string">'undefined'</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'📦 依赖库检查 ('</span> + (initRetryCount + <span class="number">1</span>) + <span class="string">'/'</span> + <span class="variable constant_">MAX_INIT_RETRIES</span> + <span class="string">'):'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">' instantsearch.js:'</span>, instantsearchLoaded ? <span class="string">'✅ 已加载'</span> : <span class="string">'❌ 未加载'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">' TypesenseAdapter:'</span>, adapterLoaded ? <span class="string">'✅ 已加载'</span> : <span class="string">'❌ 未加载'</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (!instantsearchLoaded || !adapterLoaded) {</span><br><span class="line"> initRetryCount++;</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (initRetryCount &gt;= <span class="variable constant_">MAX_INIT_RETRIES</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">''</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">'❌ Typesense 依赖库加载失败!'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">''</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">'🔧 请检查 _config.butterfly.yml 配置:'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">''</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">'inject:'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">' bottom: # ⚠️ 使用 bottom 而不是 head'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">' - &lt;script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4.56.0"&gt;&lt;/script&gt;'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">' - &lt;script src="https://cdn.jsdelivr.net/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"&gt;&lt;/script&gt;'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">' - &lt;script src="/js/typesense-search-fixed.js"&gt;&lt;/script&gt;'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">''</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">'💡 或在控制台手动检查:'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">' typeof instantsearch'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">' typeof TypesenseInstantSearchAdapter'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">''</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="title function_">showErrorMessage</span>();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">'⏳ 100ms 后重试...'</span>);</span><br><span class="line"> <span class="built_in">setTimeout</span>(initTypesense, <span class="number">100</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> initRetryCount = <span class="number">0</span>;</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'🚀 开始初始化 Typesense...'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> typesenseAdapter = <span class="keyword">new</span> <span class="title class_">TypesenseInstantSearchAdapter</span>({</span><br><span class="line"> <span class="attr">server</span>: {</span><br><span class="line"> <span class="attr">apiKey</span>: <span class="variable constant_">CONFIG</span>.<span class="property">apiKey</span>,</span><br><span class="line"> <span class="attr">nodes</span>: [{</span><br><span class="line"> <span class="attr">host</span>: <span class="variable constant_">CONFIG</span>.<span class="property">server</span>.<span class="property">host</span>,</span><br><span class="line"> <span class="attr">port</span>: <span class="variable constant_">CONFIG</span>.<span class="property">server</span>.<span class="property">port</span>,</span><br><span class="line"> <span class="attr">protocol</span>: <span class="variable constant_">CONFIG</span>.<span class="property">server</span>.<span class="property">protocol</span></span><br><span class="line"> }],</span><br><span class="line"> <span class="attr">cacheSearchResultsForSeconds</span>: <span class="number">120</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">additionalSearchParameters</span>: <span class="variable constant_">CONFIG</span>.<span class="property">searchParams</span></span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> searchInstance = <span class="title function_">instantsearch</span>({</span><br><span class="line"> <span class="attr">searchClient</span>: typesenseAdapter.<span class="property">searchClient</span>,</span><br><span class="line"> <span class="attr">indexName</span>: <span class="variable constant_">CONFIG</span>.<span class="property">indexName</span>,</span><br><span class="line"> <span class="attr">routing</span>: <span class="literal">false</span></span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> searchInstance.<span class="title function_">addWidgets</span>([</span><br><span class="line"> instantsearch.<span class="property">widgets</span>.<span class="title function_">searchBox</span>({</span><br><span class="line"> <span class="attr">container</span>: <span class="string">'#searchbox'</span>,</span><br><span class="line"> <span class="attr">placeholder</span>: <span class="string">'输入关键词寻找故事...'</span>,</span><br><span class="line"> <span class="attr">autofocus</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">showReset</span>: <span class="literal">true</span>,</span><br><span class="line"> <span class="attr">showSubmit</span>: <span class="literal">false</span>,</span><br><span class="line"> <span class="attr">showLoadingIndicator</span>: <span class="literal">true</span></span><br><span class="line"> }),</span><br><span class="line"> instantsearch.<span class="property">widgets</span>.<span class="title function_">stats</span>({</span><br><span class="line"> <span class="attr">container</span>: <span class="string">'#stats'</span>,</span><br><span class="line"> <span class="attr">templates</span>: {</span><br><span class="line"> <span class="attr">text</span>: <span class="keyword">function</span>(<span class="params">data</span>) {</span><br><span class="line"> <span class="keyword">if</span> (!data.<span class="property">query</span>) <span class="keyword">return</span> <span class="string">''</span>;</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'找到 &lt;strong&gt;'</span> + data.<span class="property">nbHits</span> + <span class="string">'&lt;/strong&gt; 条结果 ('</span> + data.<span class="property">processingTimeMS</span> + <span class="string">'ms)'</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }),</span><br><span class="line"> instantsearch.<span class="property">widgets</span>.<span class="title function_">hits</span>({</span><br><span class="line"> <span class="attr">container</span>: <span class="string">'#hits'</span>,</span><br><span class="line"> <span class="attr">templates</span>: {</span><br><span class="line"> <span class="attr">empty</span>: <span class="keyword">function</span>(<span class="params">results</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="string">'&lt;div class="ts-empty"&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;div&gt;&lt;i class="fas fa-search"&gt;&lt;/i&gt;&lt;/div&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;div&gt;找不到与 "&lt;strong&gt;'</span> + results.<span class="property">query</span> + <span class="string">'&lt;/strong&gt;" 相关的内容&lt;/div&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;div style="margin-top: 10px;"&gt;试试其他关键词吧 (´·ω·`)&lt;/div&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;/div&gt;'</span>;</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">item</span>: <span class="keyword">function</span>(<span class="params">hit</span>) {</span><br><span class="line"> <span class="comment">// 使用 _highlightResult 获取高亮文本</span></span><br><span class="line"> <span class="keyword">var</span> titleHighlight = hit.<span class="property">_highlightResult</span> &amp;&amp; hit.<span class="property">_highlightResult</span>.<span class="property">title</span> </span><br><span class="line"> ? hit.<span class="property">_highlightResult</span>.<span class="property">title</span>.<span class="property">value</span> </span><br><span class="line"> : (hit.<span class="property">title</span> || <span class="string">''</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">var</span> contentHighlight = hit.<span class="property">_highlightResult</span> &amp;&amp; hit.<span class="property">_highlightResult</span>.<span class="property">content</span> </span><br><span class="line"> ? hit.<span class="property">_highlightResult</span>.<span class="property">content</span>.<span class="property">value</span> </span><br><span class="line"> : (hit.<span class="property">content</span> || <span class="string">''</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 截取内容长度</span></span><br><span class="line"> <span class="keyword">if</span> (contentHighlight.<span class="property">length</span> &gt; <span class="number">200</span>) {</span><br><span class="line"> contentHighlight = contentHighlight.<span class="title function_">substring</span>(<span class="number">0</span>, <span class="number">200</span>) + <span class="string">'...'</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="keyword">return</span> <span class="string">'&lt;a href="'</span> + hit.<span class="property">url</span> + <span class="string">'" class="ts-result-item"&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;div class="ts-result-title"&gt;'</span> + titleHighlight + <span class="string">'&lt;/div&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;div class="ts-result-content"&gt;'</span> + contentHighlight + <span class="string">'&lt;/div&gt;'</span> +</span><br><span class="line"> <span class="string">'&lt;/a&gt;'</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }),</span><br><span class="line"> instantsearch.<span class="property">widgets</span>.<span class="title function_">pagination</span>({</span><br><span class="line"> <span class="attr">container</span>: <span class="string">'#pagination'</span>,</span><br><span class="line"> <span class="attr">padding</span>: <span class="number">2</span>,</span><br><span class="line"> <span class="attr">showFirst</span>: <span class="literal">false</span>,</span><br><span class="line"> <span class="attr">showLast</span>: <span class="literal">false</span></span><br><span class="line"> })</span><br><span class="line"> ]);</span><br><span class="line"></span><br><span class="line"> searchInstance.<span class="title function_">start</span>();</span><br><span class="line"> isInitialized = <span class="literal">true</span>;</span><br><span class="line"> </span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'✅ Typesense 初始化成功!'</span>);</span><br><span class="line"></span><br><span class="line"> searchInstance.<span class="title function_">on</span>(<span class="string">'render'</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">if</span> (isSearchOpen) {</span><br><span class="line"> <span class="keyword">const</span> input = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">'.ais-SearchBox-input'</span>);</span><br><span class="line"> <span class="keyword">if</span> (input &amp;&amp; <span class="variable language_">document</span>.<span class="property">activeElement</span> !== input) {</span><br><span class="line"> input.<span class="title function_">focus</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">catch</span> (error) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">'❌ 初始化失败:'</span>, error);</span><br><span class="line"> <span class="title function_">showErrorMessage</span>();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="comment">// 初始化</span></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="keyword">function</span> <span class="title function_">init</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'🔍 Typesense 搜索已准备就绪'</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">'💡 快捷键: Ctrl/Cmd + K'</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">readyState</span> === <span class="string">'loading'</span>) {</span><br><span class="line"> <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">'DOMContentLoaded'</span>, init);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="title function_">init</span>();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="comment">// 全局接口</span></span><br><span class="line"> <span class="comment">// ============================================================================</span></span><br><span class="line"> <span class="variable language_">window</span>.<span class="property">TypesenseSearch</span> = {</span><br><span class="line"> <span class="attr">open</span>: openSearch,</span><br><span class="line"> <span class="attr">close</span>: closeSearch,</span><br><span class="line"> <span class="attr">isOpen</span>: <span class="keyword">function</span>(<span class="params"></span>) { <span class="keyword">return</span> isSearchOpen; },</span><br><span class="line"> <span class="attr">getInstance</span>: <span class="keyword">function</span>(<span class="params"></span>) { <span class="keyword">return</span> searchInstance; }</span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line">})();</span><br></pre></td></tr></tbody></table></figure>
<h1 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h1><div class="note red flat"><p>indexName: “blogs” 和 collectionName: posts 要一致!!!</p>
</div></article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta"><i class="fas fa-circle-user fa-fw"></i>文章作者: </span><span class="post-copyright-info"><a href="https://blog.biss.click">biss</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta"><i class="fas fa-square-arrow-up-right fa-fw"></i>文章链接: </span><span class="post-copyright-info"><a href="https://blog.biss.click/posts/f287c563/">https://blog.biss.click/posts/f287c563/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta"><i class="fas fa-circle-exclamation fa-fw"></i>版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外,均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来源 <a href="https://blog.biss.click" target="_blank">Bi's Blog</a></span></div></div><div class="tag_share"><div class="post-share"><style>#web-share-btn {
background: var(--btn-bg);
color: var(--btn-color);
border: none;
padding: 0.5rem 1rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}
#web-share-btn:hover {
background: var(--btn-hover-color);
}
</style><div id="web-share-component"><button id="web-share-btn" title="分享本文"><i class="fas fa-share-alt"></i><span> 分享</span></button></div><script>(() => {
const setupWebShare = () => {
const btn = document.getElementById('web-share-btn')
if (!btn) return
// 点击事件处理
btn.addEventListener('click', async () => {
if (navigator.share) {
try {
await navigator.share({
title: '添加typesense搜索',
text: '...',
url: window.location.href
})
} catch (err) {
console.log('分享取消或失败', err)
}
} else {
// 降级处理:比如弹出提示或复制链接
const shareData = window.location.href
navigator.clipboard.writeText(shareData).then(() => {
btf.snackbarShow('系统不支持分享,已将链接复制到剪贴板')
})
}
})
}
// 考虑到 Butterfly 的 Pjax 跳转,需要重新绑定
setupWebShare()
document.addEventListener('pjax:complete', setupWebShare)
})()</script></div></div><nav class="pagination-post" id="pagination"><a class="pagination-related" href="/posts/34725d47/" title="安装gitea"><img class="cover" src="https://pic.biss.click/image/961bc881-cb0a-4ab7-ace5-9990e71c30a0.webp" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="info"><div class="info-1"><div class="info-item-1">上一篇</div><div class="info-item-2">安装gitea</div></div><div class="info-2"><div class="info-item-1">今天想把网站的源码转移到自建git仓所以先来安装gitea吧gitlab过于庞大服务器配置不够PS:我的服务器为2C2G 安装gitea这里用二进制文件安装 获取二进制文件123wget -O gitea https://dl.gitea.com/gitea/1.25.4/gitea-1.25.4-linux-amd64chmod +x giteacp gitea /usr/local/bin/gitea 创建用户这一步不是必须的但是推荐这样用root用户很容易出问题。 1234567891011121314151617181920# On Ubuntu/Debian:adduser \ --system \ --shell /bin/bash \ --gecos 'Git Version Control' \ --group \ --disabled-password \ --home /home/git \ git# On Fedora/RHEL/CentOS:groupadd --system gitaddus...</div></div></div></a><a class="pagination-related" href="/posts/b5601a7e/" title="自定义字体"><img class="cover" src="https://pic.biss.click/image/6c4f9470-3917-44fb-ba10-30f3c4147592.webp" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="info text-right"><div class="info-1"><div class="info-item-1">下一篇</div><div class="info-item-2">自定义字体</div></div><div class="info-2"><div class="info-item-1">今天感觉网站的字体有些不好看想换一下搜索发现网站用woff或者woff2字体在手机端和电脑都能完美显示 2026年2月3日更新好像谷歌字体库可以在大陆直连了速度也很快所以这篇文章好像没什么必要了。 选择字体首先在网上查找自己喜欢的字体,这里有一个网站 🪧引用站外地址,不保证站点的可用性和安全性 中文开源字体集 Open Source Fonts Collection for Chinese 开源字体 找到一个自己喜欢的如果有woff或者woff2格式下载下载保存。没有也没关系转换网站 🪧引用站外地址,不保证站点的可用性和安全性 转换网站 ttf2woff2 利用这个网站把下载的ttf字体文件转换成woff2格式。 添加字体新建一个css文件 ...</div></div></div></a></nav><hr class="custom-hr"><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div id="twikoo-wrap"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info text-center"><div class="avatar-img"><img src="https://free.picui.cn/free/2025/08/10/689845496a283.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"></div><div class="author-info-name">biss</div><div class="author-info-description"></div><div class="site-data"><a href="/archives/"><div class="headline">文章</div><div class="length-num">30</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">10</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">5</div></a></div><a id="card-info-btn" target="_blank" rel="noopener" href="https://github.com/bishshi"><i class="fab fa-github"></i><span>Follow Me</span></a><div class="card-info-social-icons"><a class="social-icon" href="https://github.com/bishshi" target="_blank" title="Github"><i class="fab fa-github" style="color: #24292e;"></i></a><a class="social-icon" href="mailto:bishsh2006@gmail.com" target="_blank" title="Email"><i class="fas fa-envelope" style="color: #4a7dbe;"></i></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content"></div><div id="welcome-ip-location-info"></div></div><div class="card-widget" id="card-poem"><div id="poem_sentence"></div><div id="poem_info"><div id="poem_dynasty"></div><div id="poem_author"></div></div></div><script src="https://cdn.liumingye.cn/npm/js-heo@1.0.11/poem/jinrishici.js" charset="utf-8"></script><script type="text/javascript">jinrishici.load(function(result) {
var sentence = document.querySelector("#poem_sentence")
var author = document.querySelector("#poem_author")
var dynasty = document.querySelector("#poem_dynasty")
var sentenceText = result.data.content
sentenceText = sentenceText.substr(0, sentenceText.length - 1);
sentence.innerHTML = sentenceText
dynasty.innerHTML = result.data.origin.dynasty
author.innerHTML = result.data.origin.author + '《' + result.data.origin.title + '》'
});</script><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%AE%89%E8%A3%85typesense"><span class="toc-number">1.</span> <span class="toc-text">安装typesense</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%B7%BB%E5%8A%A0%E6%95%B0%E6%8D%AE%E9%9B%86"><span class="toc-number">2.</span> <span class="toc-text">添加数据集</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%88%9B%E5%BB%BA%E5%8F%AA%E8%AF%BBkey"><span class="toc-number">3.</span> <span class="toc-text">创建只读key</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%8D%9A%E5%AE%A2%E6%B7%BB%E5%8A%A0%E6%90%9C%E7%B4%A2"><span class="toc-number">4.</span> <span class="toc-text">博客添加搜索</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%B3%A8%E6%84%8F"><span class="toc-number">5.</span> <span class="toc-text">注意</span></a></li></ol></div></div><div class="card-widget card-post-series"><div class="item-headline"><i class="fa-solid fa-layer-group"></i><span>系列文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/posts/7e921903/" title="添加网站左上角菜单"><img src="https://pic.biss.click/image/a40baba1-b296-4b3b-a781-da45bffb1b95.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="添加网站左上角菜单"></a><div class="content"><a class="title" href="/posts/7e921903/" title="添加网站左上角菜单">添加网站左上角菜单</a><time datetime="2026-02-09T23:11:14.000Z" title="发表于 2026-02-10 07:11:14">2026-02-10</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/f287c563/" title="添加typesense搜索">添加typesense搜索</a><time datetime="2026-02-05T05:14:16.000Z" title="发表于 2026-02-05 13:14:16">2026-02-05</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/b5601a7e/" title="自定义字体"><img src="https://pic.biss.click/image/6c4f9470-3917-44fb-ba10-30f3c4147592.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="自定义字体"></a><div class="content"><a class="title" href="/posts/b5601a7e/" title="自定义字体">自定义字体</a><time datetime="2026-01-15T23:47:15.000Z" title="发表于 2026-01-16 07:47:15">2026-01-16</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/5ed2f1e6/" title="在侧边栏添加日历和倒计时"><img src="https://pic.biss.click/image/232af9c6-97ce-42f8-85a8-9338963066e6.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="在侧边栏添加日历和倒计时"></a><div class="content"><a class="title" href="/posts/5ed2f1e6/" title="在侧边栏添加日历和倒计时">在侧边栏添加日历和倒计时</a><time datetime="2026-01-15T23:47:07.000Z" title="发表于 2026-01-16 07:47:07">2026-01-16</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/ce1ec3fe/" title="使用GitHub推送Hexo到服务器"><img src="https://pic.biss.click/image/e0a08509-dea4-4af7-bafa-c81ca9d1cf8d.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="使用GitHub推送Hexo到服务器"></a><div class="content"><a class="title" href="/posts/ce1ec3fe/" title="使用GitHub推送Hexo到服务器">使用GitHub推送Hexo到服务器</a><time datetime="2025-08-18T09:10:18.000Z" title="发表于 2025-08-18 17:10:18">2025-08-18</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/c6143ad3/" title="自定义页脚(新)"><img src="https://pic.biss.click/image/a49d986a-7430-4fa5-89dd-13dee416632f.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="自定义页脚(新)"></a><div class="content"><a class="title" href="/posts/c6143ad3/" title="自定义页脚(新)">自定义页脚(新)</a><time datetime="2025-08-14T09:05:27.000Z" title="发表于 2025-08-14 17:05:27">2025-08-14</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/33249733/" title="自定义分类条">自定义分类条</a><time datetime="2025-08-14T08:25:49.000Z" title="发表于 2025-08-14 16:25:49">2025-08-14</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/3e61a389/" title="自定义导航栏">自定义导航栏</a><time datetime="2025-08-13T08:12:25.000Z" title="发表于 2025-08-13 16:12:25">2025-08-13</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/b5866b9e/" title="利用 SiteMap 随机访问站内页面"><img src="https://pic.biss.click/image/2f13dc34-ccef-46f9-96d9-531e049fede5.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="利用 SiteMap 随机访问站内页面"></a><div class="content"><a class="title" href="/posts/b5866b9e/" title="利用 SiteMap 随机访问站内页面">利用 SiteMap 随机访问站内页面</a><time datetime="2025-08-12T07:15:32.000Z" title="发表于 2025-08-12 15:15:32">2025-08-12</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/41b9aff7/" title="博客添加AI总结"><img src="https://pic.biss.click/image/0d2f2a46-8aa9-42b6-a853-4794222a1492.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="博客添加AI总结"></a><div class="content"><a class="title" href="/posts/41b9aff7/" title="博客添加AI总结">博客添加AI总结</a><time datetime="2025-08-12T01:46:25.000Z" title="发表于 2025-08-12 09:46:25">2025-08-12</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/a6ab0925/" title="利用插件自定义页脚菜单"><img src="https://pic.biss.click/image/a49d986a-7430-4fa5-89dd-13dee416632f.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="利用插件自定义页脚菜单"></a><div class="content"><a class="title" href="/posts/a6ab0925/" title="利用插件自定义页脚菜单">利用插件自定义页脚菜单</a><time datetime="2025-08-12T01:19:16.000Z" title="发表于 2025-08-12 09:19:16">2025-08-12</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/8bdb35fb/" title="自定义右键菜单"><img src="https://pic.biss.click/image/9c4021c3-3111-4e19-acca-52ec899aa0b4.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="自定义右键菜单"></a><div class="content"><a class="title" href="/posts/8bdb35fb/" title="自定义右键菜单">自定义右键菜单</a><time datetime="2025-08-11T08:02:12.000Z" title="发表于 2025-08-11 16:02:12">2025-08-11</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/da17d00/" title="在侧边栏中添加欢迎信息"><img src="https://pic.biss.click/image/c0211558-17e4-46d2-9a35-e12f16f3f900.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="在侧边栏中添加欢迎信息"></a><div class="content"><a class="title" href="/posts/da17d00/" title="在侧边栏中添加欢迎信息">在侧边栏中添加欢迎信息</a><time datetime="2025-08-10T11:30:40.000Z" title="发表于 2025-08-10 19:30:40">2025-08-10</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/ad244066/" title="配置说说页面"><img src="https://pic.biss.click/image/0f3b8150-af77-426f-afc1-36df5ab27b36.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="配置说说页面"></a><div class="content"><a class="title" href="/posts/ad244066/" title="配置说说页面">配置说说页面</a><time datetime="2025-08-10T00:25:39.000Z" title="发表于 2025-08-10 08:25:39">2025-08-10</time></div></div></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/5785bd01/" title="新年快乐!">新年快乐!</a><time datetime="2026-02-13T22:56:18.000Z" title="发表于 2026-02-14 06:56:18">2026-02-14</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/7e921903/" title="添加网站左上角菜单"><img src="https://pic.biss.click/image/a40baba1-b296-4b3b-a781-da45bffb1b95.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="添加网站左上角菜单"></a><div class="content"><a class="title" href="/posts/7e921903/" title="添加网站左上角菜单">添加网站左上角菜单</a><time datetime="2026-02-09T23:11:14.000Z" title="发表于 2026-02-10 07:11:14">2026-02-10</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/d2c8521/" title="将博客仓库转移到gitea"><img src="https://pic.biss.click/image/f9767ecf-b8de-461b-8e62-8f7444297ea6.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="将博客仓库转移到gitea"></a><div class="content"><a class="title" href="/posts/d2c8521/" title="将博客仓库转移到gitea">将博客仓库转移到gitea</a><time datetime="2026-02-07T04:30:39.000Z" title="发表于 2026-02-07 12:30:39">2026-02-07</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/34725d47/" title="安装gitea"><img src="https://pic.biss.click/image/961bc881-cb0a-4ab7-ace5-9990e71c30a0.webp" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="安装gitea"></a><div class="content"><a class="title" href="/posts/34725d47/" title="安装gitea">安装gitea</a><time datetime="2026-02-06T22:32:04.000Z" title="发表于 2026-02-07 06:32:04">2026-02-07</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/f287c563/" title="添加typesense搜索">添加typesense搜索</a><time datetime="2026-02-05T05:14:16.000Z" title="发表于 2026-02-05 13:14:16">2026-02-05</time></div></div></div></div></div></div></main><footer id="footer"><div class="footer-other"><div class="footer-copyright"><span class="copyright">©&nbsp;2025 - 2026 By biss</span><span class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></span></div><div class="footer_custom_text"><p> <a style="margin-inline:5px" target="_blank" href="https://hexo.io/zh-cn/"><img src="https://img.shields.io/badge/Frame-Hexo-blue?style=flat&amp;logo=hexo" title="hexo 8.1"></a> <a style="margin-inline:5px" target="_blank" href="https://butterfly.js.org"><img src="https://img.shields.io/badge/Theme-Butterfly-pink?style=flat" title="butterfly主题"></a> <a style="margin-inline:5px" target="_blank" href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><img src="https://img.shields.io/badge/Copyright-BY--NC--SA-red?style=flat&amp;logo=alchemy" title="CC BY-NC-SA 4.0"></a> </p></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="translateLink" type="button" title="简繁转换"></button><button id="darkmode" type="button" title="日间和夜间模式切换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside-config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><a id="to_comment" href="#post-comment" title="前往评论"><i class="fas fa-comments"></i></a><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="/js/tw_cn.js"></script><script src="https://unpkg.com/@fancyapps/ui/dist/fancybox/fancybox.umd.js"></script><script src="https://unpkg.com/node-snackbar/dist/snackbar.min.js"></script><div class="js-pjax"><script>(() => {
const loadMathjax = () => {
if (!window.MathJax) {
window.MathJax = {
loader: {
load: [
// Four font extension packages (optional)
//- '[tex]/bbm',
//- '[tex]/bboldx',
//- '[tex]/dsfont',
'[tex]/mhchem'
],
paths: {
'mathjax-newcm': '[mathjax]/../@mathjax/mathjax-newcm-font',
//- // Four font extension packages (optional)
//- 'mathjax-bbm-extension': '[mathjax]/../@mathjax/mathjax-bbm-font-extension',
//- 'mathjax-bboldx-extension': '[mathjax]/../@mathjax/mathjax-bboldx-font-extension',
//- 'mathjax-dsfont-extension': '[mathjax]/../@mathjax/mathjax-dsfont-font-extension',
'mathjax-mhchem-extension': '[mathjax]/../@mathjax/mathjax-mhchem-font-extension'
}
},
output: {
font: 'mathjax-newcm',
},
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
tags: 'none',
packages: {
'[+]': [
'mhchem'
]
}
},
chtml: {
scale: 1.1
},
options: {
enableMenu: true,
menuOptions: {
settings: {
enrich: false // Turn off Braille and voice narration text automatic generation
}
},
renderActions: {
findScript: [10, doc => {
for (const node of document.querySelectorAll('script[type^="math/tex"]')) {
const display = !!node.type.match(/; *mode=display/)
const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display)
const text = document.createTextNode('')
node.parentNode.replaceChild(text, node)
math.start = {node: text, delim: '', n: 0}
math.end = {node: text, delim: '', n: 0}
doc.math.push(math)
}
}, '']
}
}
}
const script = document.createElement('script')
script.src = 'https://unpkg.com/mathjax/tex-mml-chtml.js'
script.id = 'MathJax-script'
script.async = true
document.head.appendChild(script)
} else {
MathJax.startup.document.state(0)
MathJax.texReset()
MathJax.typesetPromise()
}
}
btf.addGlobalFn('encrypt', loadMathjax, 'mathjax')
window.pjax ? loadMathjax() : window.addEventListener('load', loadMathjax)
})()</script><script>(() => {
const applyThemeDefaultsConfig = theme => {
if (theme === 'dark-mode') {
Chart.defaults.color = "rgba(255, 255, 255, 0.8)"
Chart.defaults.borderColor = "rgba(255, 255, 255, 0.2)"
Chart.defaults.scale.ticks.backdropColor = "transparent"
} else {
Chart.defaults.color = "rgba(0, 0, 0, 0.8)"
Chart.defaults.borderColor = "rgba(0, 0, 0, 0.1)"
Chart.defaults.scale.ticks.backdropColor = "transparent"
}
}
// Recursively traverse the config object and automatically apply theme-specific color schemes
const applyThemeConfig = (obj, theme) => {
if (typeof obj !== 'object' || obj === null) return
Object.keys(obj).forEach(key => {
const value = obj[key]
// If the property is an object and has theme-specific options, apply them
if (typeof value === 'object' && value !== null) {
if (value[theme]) {
obj[key] = value[theme] // Apply the value for the current theme
} else {
// Recursively process child objects
applyThemeConfig(value, theme)
}
}
})
}
const runChartJS = ele => {
window.loadChartJS = true
Array.from(ele).forEach((item, index) => {
const chartSrc = item.firstElementChild
const chartID = item.getAttribute('data-chartjs-id') || ('chartjs-' + index) // Use custom ID or default ID
const width = item.getAttribute('data-width')
const existingCanvas = document.getElementById(chartID)
// If a canvas already exists, remove it to avoid rendering duplicates
if (existingCanvas) {
existingCanvas.parentNode.remove()
}
const chartDefinition = chartSrc.textContent
const canvas = document.createElement('canvas')
canvas.id = chartID
const div = document.createElement('div')
div.className = 'chartjs-wrap'
if (width) {
div.style.width = width
}
div.appendChild(canvas)
chartSrc.insertAdjacentElement('afterend', div)
const ctx = document.getElementById(chartID).getContext('2d')
const config = JSON.parse(chartDefinition)
const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark-mode' : 'light-mode'
// Set default styles (initial setup)
applyThemeDefaultsConfig(theme)
// Automatically traverse the config and apply dual-mode color schemes
applyThemeConfig(config, theme)
new Chart(ctx, config)
})
}
const loadChartJS = () => {
const chartJSEle = document.querySelectorAll('#article-container .chartjs-container')
if (chartJSEle.length === 0) return
window.loadChartJS ? runChartJS(chartJSEle) : btf.getScript('https://unpkg.com/chart.js/dist/chart.umd.js').then(() => runChartJS(chartJSEle))
}
// Listen for theme change events
btf.addGlobalFn('themeChange', loadChartJS, 'chartjs')
btf.addGlobalFn('encrypt', loadChartJS, 'chartjs')
window.pjax ? loadChartJS() : document.addEventListener('DOMContentLoaded', loadChartJS)
})()</script><script>(() => {
const isShuoshuo = GLOBAL_CONFIG_SITE.pageType === 'shuoshuo'
const option = null
const getCount = () => {
const countELement = document.getElementById('twikoo-count')
if(!countELement) return
twikoo.getCommentsCount({
envId: 'https://comment.biss.click',
region: '',
urls: [window.location.pathname],
includeReply: false
}).then(res => {
countELement.textContent = res[0].count
}).catch(err => {
console.error(err)
})
}
const init = (el = document, path = location.pathname) => {
twikoo.init({
el: el.querySelector('#twikoo-wrap'),
envId: 'https://comment.biss.click',
region: '',
onCommentLoaded: () => {
btf.loadLightbox(document.querySelectorAll('#twikoo .tk-content img:not(.tk-owo-emotion)'))
},
...option,
path: isShuoshuo ? path : (option && option.path) || path
})
isShuoshuo && (window.shuoshuoComment.destroyTwikoo = () => {
if (el.children.length) {
el.innerHTML = ''
el.classList.add('no-comment')
}
})
}
const loadTwikoo = (el, path) => {
if (typeof twikoo === 'object') setTimeout(() => init(el, path), 0)
else btf.getScript('https://unpkg.com/twikoo/dist/twikoo.all.min.js').then(() => init(el, path))
}
if (isShuoshuo) {
'Twikoo' === 'Twikoo'
? window.shuoshuoComment = { loadComment: loadTwikoo }
: window.loadOtherComment = loadTwikoo
return
}
if ('Twikoo' === 'Twikoo' || !false) {
if (false) btf.loadComment(document.getElementById('twikoo-wrap'), loadTwikoo)
else loadTwikoo()
} else {
window.loadOtherComment = loadTwikoo
}
})()</script></div><script src="/js/random.js"></script><script src="/js/shuoshuoshouye.js"></script><script src="/js/ai-summary.js"></script><script src="/js/typesense-search.js"></script><script src="/js/statistic.js"></script><script src="/js/footer.js" <script=""></script><script src="https://cdn.jsdmirror.com/npm/echarts@4.9.0/dist/echarts.min.js"></script><script src="https://cdn.jsdmirror.com/npm/aplayer/dist/APlayer.min.js"></script><script src="https://cdn.jsdmirror.com/npm/meting/dist/Meting.min.js"></script><script src="https://cdn.jsdmirror.com/gh/bishshi/welcomemessage/txmap.js"></script><script src="https://cdn.jsdmirror.com/gh/bishshi/rightmenu@1.2/rightmenu.js"></script><script src="https://cdn.jsdmirror.com/gh/bishshi/sidecalendar/calendar.js"></script><script src="https://cdn.jsdmirror.com/npm/chinese-lunar@0.1.4/lib/chinese-lunar.js"></script><script src="https://cdn.jsdmirror.com/npm/instantsearch.js@4.56.0"></script><script src="https://cdn.jsdmirror.com/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"></script><script src="https://unpkg.com/pjax/pjax.min.js" defer="defer"></script><script>document.addEventListener('DOMContentLoaded', () => {
const pjaxSelectors = ["head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"]
window.pjax = new Pjax({
elements: 'a:not([target="_blank"])',
selectors: pjaxSelectors,
cacheBust: false,
analytics: false,
scrollRestoration: false
})
const triggerPjaxFn = (val) => {
if (!val) return
Object.values(val).forEach(fn => {
try {
fn()
} catch (err) {
console.debug('Pjax callback failed:', err)
}
})
}
document.addEventListener('pjax:send', () => {
// removeEventListener
btf.removeGlobalFnEvent('pjaxSendOnce')
btf.removeGlobalFnEvent('themeChange')
// reset readmode
const $bodyClassList = document.body.classList
if ($bodyClassList.contains('read-mode')) $bodyClassList.remove('read-mode')
triggerPjaxFn(window.globalFn.pjaxSend)
})
document.addEventListener('pjax:complete', () => {
btf.removeGlobalFnEvent('pjaxCompleteOnce')
document.querySelectorAll('script[data-pjax]').forEach(item => {
const newScript = document.createElement('script')
const content = item.text || item.textContent || item.innerHTML || ""
Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
newScript.appendChild(document.createTextNode(content))
item.parentNode.replaceChild(newScript, item)
})
triggerPjaxFn(window.globalFn.pjaxComplete)
})
document.addEventListener('pjax:error', e => {
if (e.request.status === 404) {
true
? pjax.loadUrl('/404.html')
: window.location.href = e.request.responseURL
}
})
})</script><script async="" data-pjax="" src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div><div class="js-pjax" id="rightMenu"><div class="rightMenu-group rightMenu-small"><a class="rightMenu-item" href="javascript:window.history.back();"><i class="fa fa-arrow-left"></i></a><a class="rightMenu-item" href="javascript:window.history.forward();"><i class="fa fa-arrow-right"></i></a><a class="rightMenu-item" href="javascript:window.location.reload();"><i class="fa fa-refresh"></i></a><a class="rightMenu-item" href="javascript:window.scrollTo(0, 0);"><i class="fa fa-arrow-up"></i></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-text"><a class="rightMenu-item" href="javascript:rmf.copySelect();"><i class="fa fa-copy"></i><span>复制</span></a><a class="rightMenu-item" href="javascript:rmf.searchinThisPage();"><i class="fas fa-search"></i><span>站内搜索</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-too"><a class="rightMenu-item" href="javascript:window.open(window.getSelection().toString());window.location.reload();"><i class="fa fa-link"></i><span>转到链接</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-paste"><a class="rightMenu-item" href="javascript:rmf.paste()"><i class="fa fa-copy"></i><span>粘贴</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-post"><a class="rightMenu-item" href="javascript:rmf.copyWordsLink()"><i class="fa fa-link"></i><span>复制本文地址</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-to"><a class="rightMenu-item" href="javascript:rmf.openWithNewTab()"><i class="fa fa-window-restore"></i><span>新窗口打开</span></a><a class="rightMenu-item" href="javascript:rmf.open()"><i class="fa fa-link"></i><span>转到链接</span></a><a class="rightMenu-item" href="javascript:rmf.copyLink()"><i class="fa fa-copy"></i><span>复制链接</span></a></div><div class="rightMenu-group rightMenu-line hide" id="menu-img"><a class="rightMenu-item" href="javascript:rmf.saveAs()"><i class="fa fa-download"></i><span>保存图片</span></a><a class="rightMenu-item" href="javascript:rmf.openWithNewTab()"><i class="fa fa-window-restore"></i><span>在新窗口打开</span></a><a class="rightMenu-item" href="javascript:rmf.click()"><i class="fa fa-arrows-alt"></i><span>全屏显示</span></a><a class="rightMenu-item" href="javascript:rmf.copyLink()"><i class="fa fa-copy"></i><span>复制图片链接</span></a></div><div class="rightMenu-group rightMenu-line"><a class="rightMenu-item" href="javascript:randomPost()"><i class="fa fa-paper-plane"></i><span>随便逛逛</span></a><a class="rightMenu-item" href="javascript:rmf.switchDarkMode();"><i class="fa fa-moon"></i><span>昼夜切换</span></a><a class="rightMenu-item" href="javascript:rmf.translate();"><i class="iconfont icon-fanti"></i><span>繁简转换</span></a><a class="rightMenu-item" href="javascript:rmf.switchReadMode();"><i class="fa fa-book"></i><span>阅读模式</span></a><a class="rightMenu-item" href="javascript:pjax.loadUrl(&quot;/privacy/&quot;);"><i class="fa fa-info-circle"></i><span>隐私声明</span></a><a class="rightMenu-item" href="javascript:pjax.loadUrl(&quot;/cookie/&quot;);"><i class="fa fa-info-circle"></i><span>Cookie协议</span></a><a class="rightMenu-item" href="javascript:pjax.loadUrl(&quot;/cc/&quot;);"><i class="fa fa-info-circle"></i><span>版权声明</span></a></div></div><!-- hexo injector body_end start --><script data-pjax="">
function butterfly_swiper_injector_config(){
var parent_div_git = document.getElementById('recent-posts');
var item_html = '<div class="recent-post-item" style="height: auto;width: 100%"><div class="blog-slider swiper-container-fade swiper-container-horizontal" id="swiper_container"><div class="blog-slider__wrp swiper-wrapper" style="transition-duration: 0ms;"><div class="blog-slider__item swiper-slide" style="background:url(https://pic.biss.click/image/63a5c345-cb40-4e92-bc7b-7dc4daaf5b74.webp);border-radius:12px;opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><div class="blog-slider__content"><span class="blog-slider__code">2025-09-28</span><a class="blog-slider__title" onclick="pjax.loadUrl(&quot;posts/b57500e9/&quot;);" href="javascript:void(0);" alt="">在Openwrt上安装AdguardHome</a><div class="blog-slider__text">还不知道怎么描述哦</div></div></div><div class="blog-slider__item swiper-slide" style="background:url(https://pic.biss.click/image/961bc881-cb0a-4ab7-ace5-9990e71c30a0.webp);border-radius:12px;opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;"><div class="blog-slider__content"><span class="blog-slider__code">2026-02-07</span><a class="blog-slider__title" onclick="pjax.loadUrl(&quot;posts/34725d47/&quot;);" href="javascript:void(0);" alt="">安装gitea</a><div class="blog-slider__text">还不知道怎么描述哦</div></div></div></div><div class="blog-slider__pagination swiper-pagination-clickable swiper-pagination-bullets"></div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div></div>';
console.log('已挂载butterfly_swiper')
parent_div_git.insertAdjacentHTML("afterbegin",item_html)
}
var elist = 'undefined'.split(',');
var cpage = location.pathname;
var epage = '/';
var flag = 0;
for (var i=0;i<elist.length;i++){
if (cpage.includes(elist[i])){
flag++;
}
}
if ((epage ==='all')&&(flag == 0)){
butterfly_swiper_injector_config();
}
else if (epage === cpage){
butterfly_swiper_injector_config();
}
</script><script defer="" src="https://cdnjs.cloudflare.com/ajax/libs/Swiper/5.4.5/js/swiper.min.js"></script><script defer="" data-pjax="" src="https://npm.elemecdn.com/hexo-butterfly-swiper-lyx/lib/swiper_init.js"></script><!-- hexo injector body_end end --></body></html>