Skip to content

Commit 7aef590

Browse files
committed
Implement Pagefind search with context highlighting (Issue #9)
- Install Pagefind v1.4.0 for static site search - Replace Lunr.js search with Pagefind UI - Add custom CSS styling to match site theme - Enable context-based search results with highlighting - Index 75 pages and 4998 words automatically during build - Update build script to run pagefind after Hugo Provides Google-style search results showing: - Highlighted search terms in context - Result excerpts with surrounding text - Better discoverability of content Resolves #9
1 parent 14d597e commit 7aef590

5 files changed

Lines changed: 420 additions & 14 deletions

File tree

assets/css/modern.css

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,3 +624,171 @@ img {
624624
shape-rendering: geometricPrecision;
625625
text-rendering: optimizeLegibility;
626626
}
627+
628+
/* =============================================================================
629+
Pagefind Search Styling (Issue #9)
630+
============================================================================= */
631+
632+
/* Search container in sidebar */
633+
.searchbox {
634+
margin: 1rem 0.5rem;
635+
}
636+
637+
#pagefind-search {
638+
width: 100%;
639+
}
640+
641+
/* Override Pagefind default styles to match theme */
642+
.pagefind-ui__search-input {
643+
background: var(--color-bg-primary, #fff) !important;
644+
color: var(--color-text, #333) !important;
645+
border: 1px solid var(--color-border, #ccc) !important;
646+
border-radius: 4px !important;
647+
padding: 0.5rem 1rem !important;
648+
font-size: 0.9rem !important;
649+
width: 100% !important;
650+
font-family: var(--font-sans) !important;
651+
}
652+
653+
.pagefind-ui__search-input:focus {
654+
outline: none !important;
655+
border-color: var(--color-link, #007bff) !important;
656+
box-shadow: 0 0 0 3px rgba(0,123,255,0.1) !important;
657+
}
658+
659+
/* Search clear button */
660+
.pagefind-ui__search-clear {
661+
color: var(--color-text-muted, #666) !important;
662+
}
663+
664+
/* Results container */
665+
.pagefind-ui__results-area {
666+
margin-top: 0.5rem;
667+
font-family: var(--font-sans) !important;
668+
}
669+
670+
.pagefind-ui__results {
671+
max-height: 60vh;
672+
overflow-y: auto;
673+
}
674+
675+
/* Individual result */
676+
.pagefind-ui__result {
677+
padding: 0.75rem !important;
678+
margin-bottom: 0.5rem !important;
679+
border: 1px solid var(--color-border, #e0e0e0) !important;
680+
border-radius: 4px !important;
681+
background: var(--color-bg-primary, #fff) !important;
682+
transition: box-shadow 0.2s ease !important;
683+
}
684+
685+
.pagefind-ui__result:hover {
686+
box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
687+
border-color: var(--color-link, #007bff) !important;
688+
}
689+
690+
/* Result title */
691+
.pagefind-ui__result-title {
692+
font-size: 1rem !important;
693+
font-weight: 600 !important;
694+
margin-bottom: 0.5rem !important;
695+
font-family: var(--font-sans) !important;
696+
}
697+
698+
.pagefind-ui__result-link {
699+
color: var(--color-link, #007bff) !important;
700+
text-decoration: none !important;
701+
}
702+
703+
.pagefind-ui__result-link:hover {
704+
text-decoration: underline !important;
705+
}
706+
707+
/* Result excerpt with context */
708+
.pagefind-ui__result-excerpt {
709+
color: var(--color-text, #333) !important;
710+
line-height: 1.6 !important;
711+
margin-top: 0.5rem !important;
712+
font-size: 0.9rem !important;
713+
font-family: var(--font-sans) !important;
714+
}
715+
716+
/* Highlighted search terms - the key feature of Issue #9 */
717+
.pagefind-ui__result-excerpt mark {
718+
background-color: #fff3cd !important;
719+
color: #856404 !important;
720+
padding: 0.1rem 0.3rem !important;
721+
border-radius: 2px !important;
722+
font-weight: 600 !important;
723+
}
724+
725+
/* Result URL/breadcrumb */
726+
.pagefind-ui__result-nested {
727+
color: var(--color-text-muted, #666) !important;
728+
font-size: 0.85rem !important;
729+
margin-top: 0.25rem !important;
730+
}
731+
732+
/* Loading state */
733+
.pagefind-ui__loading {
734+
text-align: center !important;
735+
padding: 1.5rem !important;
736+
color: var(--color-text-muted, #666) !important;
737+
}
738+
739+
/* No results message */
740+
.pagefind-ui__message {
741+
padding: 1.5rem !important;
742+
text-align: center !important;
743+
color: var(--color-text-muted, #666) !important;
744+
}
745+
746+
/* Result count */
747+
.pagefind-ui__results-heading {
748+
font-size: 0.85rem !important;
749+
color: var(--color-text-muted, #666) !important;
750+
margin-bottom: 0.5rem !important;
751+
font-weight: normal !important;
752+
}
753+
754+
/* Mobile responsive adjustments */
755+
@media only screen and (max-width: 59.938em) {
756+
.searchbox {
757+
margin: 0.75rem 0.5rem;
758+
}
759+
760+
.pagefind-ui__results {
761+
max-height: 50vh;
762+
}
763+
764+
.pagefind-ui__result {
765+
padding: 0.5rem !important;
766+
}
767+
768+
.pagefind-ui__result-title {
769+
font-size: 0.95rem !important;
770+
}
771+
772+
.pagefind-ui__result-excerpt {
773+
font-size: 0.85rem !important;
774+
}
775+
}
776+
777+
/* Dark mode support (if theme has dark mode) */
778+
@media (prefers-color-scheme: dark) {
779+
.pagefind-ui__search-input {
780+
background: var(--color-bg-secondary, #2d2d2d) !important;
781+
color: var(--color-text, #e0e0e0) !important;
782+
border-color: var(--color-border, #444) !important;
783+
}
784+
785+
.pagefind-ui__result {
786+
background: var(--color-bg-secondary, #2d2d2d) !important;
787+
border-color: var(--color-border, #444) !important;
788+
}
789+
790+
.pagefind-ui__result-excerpt mark {
791+
background-color: #4a4a00 !important;
792+
color: #ffeb3b !important;
793+
}
794+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<!-- Pagefind Search UI -->
2+
<div id="pagefind-search-container">
3+
<div id="pagefind-search"></div>
4+
</div>
5+
6+
<!-- Load Pagefind UI -->
7+
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
8+
<script src="/pagefind/pagefind-ui.js"></script>
9+
<script>
10+
window.addEventListener('DOMContentLoaded', (event) => {
11+
new PagefindUI({
12+
element: "#pagefind-search",
13+
showSubResults: true,
14+
showImages: false,
15+
excerptLength: 15,
16+
resetStyles: false,
17+
processResult: function(result) {
18+
// Customize result display if needed
19+
return result;
20+
}
21+
});
22+
});
23+
</script>
24+
25+
<style>
26+
/* Pagefind Search Styling */
27+
#pagefind-search-container {
28+
margin: 1rem 0;
29+
}
30+
31+
/* Make search input match theme */
32+
.pagefind-ui__search-input {
33+
background: var(--color-bg-primary, #fff);
34+
color: var(--color-text, #333);
35+
border: 1px solid var(--color-border, #ccc);
36+
border-radius: 4px;
37+
padding: 0.5rem 1rem;
38+
font-size: 1rem;
39+
width: 100%;
40+
}
41+
42+
.pagefind-ui__search-input:focus {
43+
outline: none;
44+
border-color: var(--color-link, #007bff);
45+
box-shadow: 0 0 0 3px rgba(0,123,255,0.1);
46+
}
47+
48+
/* Results container */
49+
.pagefind-ui__results {
50+
margin-top: 1rem;
51+
max-height: 70vh;
52+
overflow-y: auto;
53+
}
54+
55+
/* Individual result */
56+
.pagefind-ui__result {
57+
padding: 1rem;
58+
margin-bottom: 0.5rem;
59+
border: 1px solid var(--color-border, #e0e0e0);
60+
border-radius: 4px;
61+
background: var(--color-bg-primary, #fff);
62+
transition: box-shadow 0.2s ease;
63+
}
64+
65+
.pagefind-ui__result:hover {
66+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
67+
}
68+
69+
/* Result title */
70+
.pagefind-ui__result-title {
71+
font-size: 1.1rem;
72+
font-weight: 600;
73+
margin-bottom: 0.5rem;
74+
}
75+
76+
.pagefind-ui__result-link {
77+
color: var(--color-link, #007bff);
78+
text-decoration: none;
79+
}
80+
81+
.pagefind-ui__result-link:hover {
82+
text-decoration: underline;
83+
}
84+
85+
/* Result excerpt with context */
86+
.pagefind-ui__result-excerpt {
87+
color: var(--color-text, #333);
88+
line-height: 1.6;
89+
margin-top: 0.5rem;
90+
}
91+
92+
/* Highlighted search terms */
93+
.pagefind-ui__result-excerpt mark {
94+
background-color: #fff3cd;
95+
color: #856404;
96+
padding: 0.1rem 0.2rem;
97+
border-radius: 2px;
98+
font-weight: 600;
99+
}
100+
101+
/* Loading state */
102+
.pagefind-ui__loading {
103+
text-align: center;
104+
padding: 2rem;
105+
color: var(--color-text-muted, #666);
106+
}
107+
108+
/* No results message */
109+
.pagefind-ui__message {
110+
padding: 2rem;
111+
text-align: center;
112+
color: var(--color-text-muted, #666);
113+
}
114+
115+
/* Mobile responsive */
116+
@media only screen and (max-width: 59.938em) {
117+
.pagefind-ui__results {
118+
max-height: 60vh;
119+
}
120+
121+
.pagefind-ui__result {
122+
padding: 0.75rem;
123+
}
124+
125+
.pagefind-ui__result-title {
126+
font-size: 1rem;
127+
}
128+
}
129+
</style>

layouts/partials/search.html

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1+
<!-- Pagefind Search -->
12
<div class="searchbox">
2-
<label for="search-by"><i class="fas fa-search"></i></label>
3-
<input data-search-input id="search-by" type="search" placeholder="{{T "Search-placeholder"}}" aria-label="search">
4-
<span data-search-clear=""><i class="fas fa-times"></i></span>
3+
<div id="pagefind-search"></div>
54
</div>
6-
{{ $assetBusting := not .Site.Params.disableAssetsBusting }}
7-
<script type="text/javascript" src="{{"js/lunr.min.js" | relURL}}{{ if $assetBusting }}?{{ now.Unix }}{{ end }}"></script>
8-
<script type="text/javascript" src="{{"js/auto-complete.js" | relURL}}{{ if $assetBusting }}?{{ now.Unix }}{{ end }}"></script>
9-
<script type="text/javascript">
10-
{{ if hugo.IsMultilingual }}
11-
var baseurl = "{{.Site.BaseURL}}{{.Site.LanguagePrefix}}";
12-
{{ else }}
13-
var baseurl = "{{.Site.BaseURL}}";
14-
{{ end }}
5+
6+
<!-- Load Pagefind UI -->
7+
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
8+
<script src="/pagefind/pagefind-ui.js"></script>
9+
<script>
10+
window.addEventListener('DOMContentLoaded', (event) => {
11+
new PagefindUI({
12+
element: "#pagefind-search",
13+
showSubResults: true,
14+
showImages: false,
15+
excerptLength: 15,
16+
processResult: function(result) {
17+
return result;
18+
}
19+
});
20+
});
1521
</script>
16-
<script type="text/javascript" src="{{"js/search.js" | relURL}}{{ if $assetBusting }}?{{ now.Unix }}{{ end }}"></script>

0 commit comments

Comments
 (0)