55{{ define "main" }}
66 {{- $tagOrder := slice "languages" "ai" "testing" "cicd" "security" "databases" "deployment" "admin" "labs" }}
77
8- < div class ="not-prose ">
8+ < div
9+ class ="not-prose mx-auto flex min-h-[calc(100dvh-7rem)] max-w-7xl flex-col "
10+ x-data ="{
11+ activeTag: 'languages',
12+ query: '',
13+ words() { return this.query.trim().toLowerCase().split(/\s+/).filter(Boolean) },
14+ filtering() { return this.query.trim().length > 0 },
15+ matchText(text) { return this.words().every(w => text.includes(w)) },
16+ rowVisible(el) { return !this.filtering() || this.matchText(el.dataset.search) },
17+ sectionVisible(el) { return !this.filtering() || Array.from(el.querySelectorAll('[data-guide]')).some(r => this.matchText(r.dataset.search)) },
18+ resultCount() { return Array.from(document.querySelectorAll('[data-guide]')).filter(r => this.matchText(r.dataset.search)).length },
19+ init() {
20+ const obs = new IntersectionObserver(
21+ entries => entries.forEach(e => { if (e.isIntersecting) this.activeTag = e.target.id }),
22+ { rootMargin: '-20% 0px -70% 0px', threshold: 0 }
23+ );
24+ document.querySelectorAll('section[data-tag]').forEach(s => obs.observe(s));
25+ }
26+ } "
27+ >
928 {{- partial "breadcrumbs.html" . }}
1029
11- < div class ="mb-12 mt-4 ">
12- < p class ="text-xs font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500 ">
30+ < div class ="mt-4 ">
31+ < p class ="text-xs font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500 ">
1332 {{- len .Pages }} guides
1433 </ p >
1534 < h1
2342 {{- end }}
2443 </ div >
2544
26- < div
27- class ="flex gap-12 "
28- x-data ="{
29- activeTag: 'languages',
30- init() {
31- const obs = new IntersectionObserver(
32- entries => entries.forEach(e => { if (e.isIntersecting) this.activeTag = e.target.id }),
33- { rootMargin: '-20% 0px -70% 0px', threshold: 0 }
34- );
35- document.querySelectorAll('section[data-tag]').forEach(s => obs.observe(s));
36- }
37- } "
38- >
39- <!-- Sticky jump nav -->
40- < nav class ="hidden xl:flex w-52 flex-none flex-col self-start sticky top-20 ">
41- < p class ="mb-3 text-xs font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500 ">
45+ <!-- Text filter -->
46+ < div class ="relative mt-8 mb-10 max-w-xl ">
47+ < span
48+ class ="icon-svg icon-sm pointer-events-none absolute top-1/2 left-3.5 -translate-y-1/2 text-gray-400 dark:text-gray-500 "
49+ >
50+ {{ partialCached "icon" "magnifying-glass" "magnifying-glass" }}
51+ </ span >
52+ < input
53+ type ="search "
54+ x-model ="query "
55+ @keydown.escape ="query = '' "
56+ aria-label ="Filter guides "
57+ placeholder ="Filter guides by name, topic, or tag… "
58+ class ="w-full rounded-xl border border-gray-300 bg-white py-2.5 pr-10 pl-11 text-sm text-gray-900 shadow-sm transition placeholder:text-gray-400 focus:border-blue-400 focus:ring-2 focus:ring-blue-400/40 focus:outline-none dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500 "
59+ />
60+ < button
61+ x-show ="query "
62+ x-cloak
63+ @click ="query = '' "
64+ aria-label ="Clear filter "
65+ class ="icon-svg icon-sm absolute top-1/2 right-2.5 -translate-y-1/2 rounded-md p-1 text-gray-400 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-200 "
66+ >
67+ {{ partialCached "icon" "x-mark" "x-mark" }}
68+ </ button >
69+ </ div >
70+
71+ < div class ="flex flex-1 gap-16 xl:gap-24 ">
72+ <!-- Sticky jump nav (hidden while filtering) -->
73+ < nav
74+ x-show ="!filtering() "
75+ class ="sticky top-20 hidden w-52 flex-none flex-col self-start xl:flex "
76+ >
77+ < p class ="mb-3 text-xs font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500 ">
4278 Jump to
4379 </ p >
4480 {{- range $i, $tag := $tagOrder }}
6399 {{- end }}
64100 </ nav >
65101
66- <!-- Sections -->
102+ <!-- Guide list -->
67103 < div class ="min-w-0 flex-1 ">
104+ <!-- Filtering meta bar -->
105+ < div
106+ x-show ="filtering() "
107+ x-cloak
108+ class ="mb-8 flex items-center justify-between border-b border-gray-200 pb-4 dark:border-gray-800 "
109+ >
110+ < p class ="text-sm text-gray-500 dark:text-gray-400 ">
111+ < span class ="font-semibold text-gray-900 dark:text-gray-100 " x-text ="resultCount() "> </ span >
112+ of {{ len .Pages }} guides
113+ </ p >
114+ < button
115+ @click ="query = '' "
116+ class ="text-sm font-medium text-blue-600 transition hover:underline dark:text-blue-400 "
117+ >
118+ Clear filter
119+ </ button >
120+ </ div >
121+
68122 {{- range $i, $tag := $tagOrder }}
69123 {{- $tagData := index hugo.Data.tags $tag }}
70124 {{- $pages := where $.Pages "Params.tags" "intersect" (slice $tag) }}
71125 {{- if $pages }}
72126 < section
73127 id ="{{ $tag }} "
74128 data-tag ="{{ $tag }} "
75- class ="scroll-mt-20 border-t border-gray-200 py-12 first:border-t-0 first:pt-0 dark:border-gray-800 "
129+ x-show ="sectionVisible($el) "
130+ class ="scroll-mt-16 "
131+ :class ="filtering() ? '' : 'border-t border-gray-200 py-12 first:border-t-0 first:pt-0 dark:border-gray-800' "
76132 >
77- < div class ="mb-6 ">
78- < p class ="mb-1 text-xs font-semibold uppercase tracking-widest text-gray-400 dark:text-gray-500 ">
133+ < div class ="mb-6 " x-show =" !filtering() " >
134+ < p class ="mb-1 text-xs font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500 ">
79135 {{- printf "%02d" (add $i 1) -}}
80136 </ p >
81137 < h2 class ="text-2xl font-bold "> {{ $tagData.title }}</ h2 >
@@ -85,18 +141,50 @@ <h2 class="text-2xl font-bold">{{ $tagData.title }}</h2>
85141 </ div >
86142 < div class ="divide-y divide-gray-200 dark:divide-gray-800 ">
87143 {{- range $pages }}
88- < div class ="-mx-3 grid grid-cols-[5fr_6fr] gap-6 rounded px-3 py-4 hover:bg-gray-50 dark:hover:bg-gray-900 ">
89- < a
90- href ="{{ .Permalink }} "
91- class ="font-medium leading-snug hover:underline "
92- > {{ .Title }}</ a >
93- < p class ="text-sm leading-relaxed text-gray-500 dark:text-gray-400 "> {{ .Summary }}</ p >
94- </ div >
144+ {{- $tagTitles := slice }}
145+ {{- range .Params.tags }}{{ $tagTitles = $tagTitles | append (index hugo.Data.tags .).title }}{{ end }}
146+ {{- $search := lower (printf "%s %s %s %s" .Title .Summary (delimit .Params.tags " ") (delimit $tagTitles " ")) }}
147+ {{- $search = $search | replaceRE "\\s+" " " }}
148+ < a
149+ href ="{{ .Permalink }} "
150+ data-guide
151+ data-search ="{{ $search }} "
152+ x-show ="rowVisible($el) "
153+ class ="group -mx-3 grid grid-cols-[5fr_6fr_auto] items-start gap-6 rounded-lg px-3 py-4 transition-colors hover:bg-gray-50 dark:hover:bg-gray-900 "
154+ >
155+ < span class ="font-medium leading-snug text-gray-900 transition-colors group-hover:text-blue-600 dark:text-gray-100 dark:group-hover:text-blue-400 "> {{ .Title }}</ span >
156+ < span class ="text-sm leading-relaxed text-gray-500 dark:text-gray-400 "> {{ .Summary }}</ span >
157+ < span
158+ class ="icon-svg icon-sm mt-0.5 self-center text-gray-300 opacity-0 transition-all duration-200 group-hover:translate-x-0.5 group-hover:text-blue-600 group-hover:opacity-100 dark:text-gray-600 dark:group-hover:text-blue-400 "
159+ >
160+ {{ partialCached "icon" "arrow-right" "arrow-right" }}
161+ </ span >
162+ </ a >
95163 {{- end }}
96164 </ div >
97165 </ section >
98166 {{- end }}
99167 {{- end }}
168+
169+ <!-- No matches -->
170+ < div
171+ x-show ="filtering() && resultCount() === 0 "
172+ x-cloak
173+ class ="flex flex-col items-center justify-center gap-3 py-24 text-center "
174+ >
175+ < span class ="icon-svg icon-lg text-gray-300 dark:text-gray-600 ">
176+ {{ partialCached "icon" "magnifying-glass" "magnifying-glass" }}
177+ </ span >
178+ < p class ="text-gray-500 dark:text-gray-400 ">
179+ No guides match “< span class ="font-medium text-gray-700 dark:text-gray-200 " x-text ="query "> </ span > ”.
180+ </ p >
181+ < button
182+ @click ="query = '' "
183+ class ="text-sm font-medium text-blue-600 transition hover:underline dark:text-blue-400 "
184+ >
185+ Clear filter
186+ </ button >
187+ </ div >
100188 </ div >
101189 </ div >
102190 </ div >
0 commit comments