Skip to content

Commit 8f39c6b

Browse files
committed
feat: add search
1 parent 7982673 commit 8f39c6b

6 files changed

Lines changed: 152 additions & 1 deletion

File tree

assets/scss/_search.scss

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
.search-container {
2+
position: relative;
3+
padding: 10px 15px;
4+
}
5+
6+
#search-input {
7+
width: 100px;
8+
padding: 8px 12px;
9+
border: 1px solid #ddd;
10+
border-radius: 4px;
11+
font-size: 14px;
12+
}
13+
14+
.search-results {
15+
display: none;
16+
position: absolute;
17+
top: 100%;
18+
left: 15px;
19+
right: -200px;
20+
min-width: 300px;
21+
background: white;
22+
border: 1px solid #ddd;
23+
border-radius: 4px;
24+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
25+
max-height: 400px;
26+
overflow-y: auto;
27+
z-index: 1000;
28+
29+
.search-result-item {
30+
display: block;
31+
padding: 10px;
32+
text-decoration: none;
33+
color: inherit;
34+
border-bottom: 1px solid #eee;
35+
36+
&:hover {
37+
background: #f5f5f5;
38+
}
39+
40+
.search-result-title {
41+
font-weight: bold;
42+
margin-bottom: 5px;
43+
}
44+
45+
.search-result-summary {
46+
font-size: 0.9em;
47+
color: #666;
48+
}
49+
}
50+
51+
.no-results {
52+
padding: 10px;
53+
text-align: center;
54+
color: #666;
55+
}
56+
}

assets/scss/style.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ VERSION: Versoin Number
2323

2424
@import 'templates/main.scss';
2525

26-
@import 'custom';
26+
@import 'custom';
27+
28+
@import 'search';

config/_default/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ frontmatter:
2121
date:
2222
- report_publish_date
2323
- :default
24+
outputs:
25+
home: ["HTML", "RSS", "JSON"]

layouts/_default/index.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"data": [
3+
{{- $pages := .Site.AllPages -}}
4+
{{- $len := (len $pages) -}}
5+
6+
{{- range $index, $e := (where .Site.AllPages "Section" "in" (slice "learn" "about" "events" "stories" "patterns" "isc-101")) -}}
7+
{{- if and (not .Params.hidden) (not .Draft) -}}
8+
{{- if $index -}},{{- end }}
9+
{
10+
"id": {{ $index | add 1 }},
11+
"len": {{ $len }},
12+
"url": "{{ .Permalink }}",
13+
"title": {{ .Title | jsonify }},
14+
"summary": {{ .Summary | jsonify }},
15+
"lang": "{{ .Language.Lang }}"
16+
}
17+
{{- end -}}
18+
{{- end -}}
19+
]
20+
}

layouts/partials/menu.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
</div>
1212
</div>
1313
</div>
14+
{{ partial "search" . }}
1415
</li>
1516

1617
<script>

layouts/partials/search.html

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<li class="menu-item" id="search-menu-item">
2+
<div class="search-container">
3+
<input
4+
type="text"
5+
id="search-input"
6+
placeholder="Search..."
7+
autocomplete="off"
8+
>
9+
<div id="search-results" class="search-results"></div>
10+
</div>
11+
</li>
12+
13+
<script>
14+
document.addEventListener('DOMContentLoaded', function() {
15+
const searchInput = document.getElementById('search-input');
16+
const searchResults = document.getElementById('search-results');
17+
18+
function createSearchResultHTML(item) {
19+
const summary = item.summary || '';
20+
const truncatedSummary = summary.length > 100 ?
21+
`${summary.substring(0, 100)}...` :
22+
summary;
23+
24+
return `
25+
<a href="${item.url}" class="search-result-item">
26+
<div class="search-result-title">${item.title || ''}</div>
27+
<div class="search-result-summary">${truncatedSummary}</div>
28+
</a>
29+
`;
30+
}
31+
32+
function handleSearch() {
33+
const searchQuery = searchInput.value.trim();
34+
35+
if (searchQuery.length < 2) {
36+
searchResults.style.display = 'none';
37+
return;
38+
}
39+
40+
fetch('/index.json')
41+
.then(response => response.json())
42+
.then(response => {
43+
const data = Object.values(response.data || {});
44+
const currentLang = document.documentElement.lang || 'en';
45+
46+
const results = data.filter(item => {
47+
if (item.lang !== currentLang) return false;
48+
49+
const regex = new RegExp(searchQuery, 'i');
50+
return (item.title && item.title.match(regex)) ||
51+
(item.content && item.content.match(regex));
52+
});
53+
54+
searchResults.innerHTML = results.length ?
55+
results.map(createSearchResultHTML).join('') :
56+
'<div class="no-results">No results found</div>';
57+
58+
searchResults.style.display = 'block';
59+
});
60+
}
61+
62+
searchInput.addEventListener('input', handleSearch);
63+
64+
document.addEventListener('click', function(e) {
65+
if (!e.target.closest('.search-container')) {
66+
searchResults.style.display = 'none';
67+
}
68+
});
69+
});
70+
</script>

0 commit comments

Comments
 (0)