因爲文章越來越多,只靠分類其實也不好找到文章,在「歷史上的今天」之後,弄了搜尋功能!以下分享怎麼在 Hugo 中做到。
- 做出搜尋頁面的基本頁
content/search/_index
---
title: "搜尋"
type: "search"
draft: false
---
- header 在導覽列做搜尋框框 themes/classic/layouts/partials/header.html
<header>
<nav>
<ul>
{{ $title := lower .Title }}
{{ $section := lower .Section }}
<li class="pull-left {{ if .IsHome }}current{{ end }}">
<a href="{{ .Site.BaseURL }}">~/{{ lower .Site.Title}}</a>
</li>
{{ range .Site.Menus.main }}
{{ $name := lower .Name }}
<li class="pull-left {{ if eq $name $title }}current{{ else if eq $section $name }}current{{ else if eq $title (pluralize $name) }}current{{ end }}">
<a href="{{ .URL }}">~/{{ lower .Name }}</a>
</li>
{{end}}
{{ range .Site.Menus.feed }}
{{ $name := lower .Name}}
<li class="pull-right">
<a href="{{ .URL }}">~/{{ lower .Name}}</a>
</li>
{{end}}
<!-- 🔍 Search (final, no more edits needed) -->
<li class="pull-right search-item">
<form action="/search/" method="get" role="search">
<label for="nav-search" class="visually-hidden">Search</label>
<input
id="nav-search"
type="search"
name="q"
placeholder="搜尋…"
autocomplete="off"
>
</form>
</li>
</ul>
</nav>
</header>
- json 抓出所有文章標題、內容等,來讓 js 可以搜尋
themes/classic/layouts/_default/index.json
{{- $pages := .Site.RegularPages -}}
[
{{- range $i, $p := $pages }}
{{- if $i }},{{ end }}
{
"title": {{ $p.Title | jsonify }},
"url": {{ $p.RelPermalink | jsonify }},
"date": {{ $p.Date.Format "2006-01-02" | jsonify }},
"content": {{ $p.Plain | jsonify }}
}
{{- end }}
]
- html 格式 themes/classic/layouts/search/list.html
{{/* 引入網站的通用頁首 */}}
{{ partial "header.html" . }}
{{/* 為了讓 <mark> 標籤有螢光筆效果,可以加上一點點 CSS */}}
<style>
mark {
background-color: #fcf8e3; /* 一個柔和的黃色 */
padding: 0.2em 0.1em;
border-radius: 3px;
}
.search-excerpt {
font-size: 0.9em;
color: #666;
margin-top: 0.3em;
}
</style>
<main>
<h1>{{ .Title }}</h1>
<ul id="search-results">
<li>請在上方搜尋框輸入關鍵字</li>
</ul>
</main>
<script>
document.addEventListener('DOMContentLoaded', function() {
const resultsEl = document.getElementById('search-results');
const params = new URLSearchParams(window.location.search);
const q = params.get('q')?.toLowerCase();
// 如果 URL 中沒有搜尋關鍵字,就不執行任何動作
if (!q) {
return;
}
resultsEl.innerHTML = '<li>正在搜尋中...</li>';
// --- 新增的輔助函式 ---
/**
* 為了安全地使用正規表示式,需要轉義特殊字元
* @param {string} str - 使用者輸入的字串
* @returns {string} - 轉義後的字串
*/
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* 在文字中高亮顯示關鍵字
* @param {string} text - 要處理的文字 (標題或摘要)
* @param {string} query - 要高亮的關鍵字
* @returns {string} - 包含 <mark> 標籤的 HTML 字串
*/
function highlight(text, query) {
if (!text || !query) return text;
const safeQuery = escapeRegExp(query);
const regex = new RegExp(safeQuery, 'gi'); // 'g' for global, 'i' for case-insensitive
return text.replace(regex, match => `<mark>${match}</mark>`);
}
/**
* 從完整內容中建立摘要
* @param {string} content - 文章的純文字內容
* @param {string} query - 搜尋的關鍵字
* @param {number} length - 摘要的目標長度
* @returns {string} - 包含關鍵字的摘要字串
*/
function createExcerpt(content, query, length = 150) {
if (!content) return '';
const index = content.toLowerCase().indexOf(query);
if (index === -1) {
// 如果內容中找不到關鍵字 (可能是在標題中找到的),就直接從頭截取
return content.substring(0, length) + (content.length > length ? '...' : '');
}
const start = Math.max(0, index - Math.floor(length / 2));
let excerpt = content.substring(start, start + length);
// 加上省略號
if (start > 0) excerpt = '... ' + excerpt;
if (start + length < content.length) excerpt = excerpt + ' ...';
return excerpt;
}
fetch('{{ "/index.json" | relURL }}')
.then(res => {
if (!res.ok) throw new Error('Network response was not ok');
return res.json();
})
.then(pages => {
const results = pages.filter(p =>
(p.title && p.title.toLowerCase().includes(q)) ||
(p.content && p.content.toLowerCase().includes(q))
);
if (results.length === 0) {
resultsEl.innerHTML = '<li>找不到結果</li>';
} else {
// --- 這是修改的核心部分 ---
resultsEl.innerHTML = results.map(r => {
// 1. 建立摘要
const excerpt = createExcerpt(r.content, q);
// 2. 高亮標題和摘要
const highlightedTitle = highlight(r.title, q);
const highlightedExcerpt = highlight(excerpt, q);
// 3. 組合最終的 HTML
return `
<li style="margin-bottom:1.5em;">
<a href="${r.url}" style="font-weight:bold; font-size: 1.2em;">${highlightedTitle}</a><br>
<small style="color:gray;">${r.date}</small>
<p class="search-excerpt">${highlightedExcerpt}</p>
</li>
`;
}).join('');
}
})
.catch(err => {
console.error('搜尋時發生錯誤:', err);
resultsEl.innerHTML = '<li>搜尋發生錯誤,請稍後再試</li>';
});
});
</script>
{{/* 引入網站的通用頁尾 */}}
{{ partial "footer.html" . }}