Skip to content

Commit d71a87e

Browse files
Move search results to main content area
Search results now render as full-width cards in the article column instead of a small dropdown in the nav. Cards show title, domain badge, and highlighted snippet. Esc or clearing input restores the previous page view.
1 parent f53a617 commit d71a87e

2 files changed

Lines changed: 88 additions & 91 deletions

File tree

‎site/app.js‎

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,21 @@
116116
nav.querySelectorAll(".nav-children").forEach((el) => el.classList.add("open"));
117117
nav.querySelectorAll(".nav-domain").forEach((el) => el.classList.add("expanded"));
118118
}
119-
// Full-text search results dropdown
120-
showSearchResults(e.target.value);
121-
});
122-
searchInput.addEventListener("focus", () => {
123-
if (searchInput.value.trim()) showSearchResults(searchInput.value);
119+
// Full-text search: show results in main area
120+
if (e.target.value.trim()) {
121+
showSearchResults(e.target.value);
122+
} else {
123+
renderPage();
124+
renderRail();
125+
}
124126
});
125-
searchInput.addEventListener("blur", (e) => {
126-
setTimeout(() => removeSearchDropdown(), 200);
127+
searchInput.addEventListener("keydown", (e) => {
128+
if (e.key === "Escape") {
129+
searchInput.value = "";
130+
searchInput.blur();
131+
renderPage();
132+
renderRail();
133+
}
127134
});
128135
}
129136

@@ -690,52 +697,65 @@
690697
}
691698

692699
let searchIndex = null;
693-
let searchDropdown = null;
694700

695701
function initSearch() {
696702
searchIndex = buildSearchIndex();
697703
}
698704

699705
function showSearchResults(query) {
700706
if (!searchIndex) initSearch();
701-
removeSearchDropdown();
702-
703-
if (!query.trim()) return;
707+
if (!query.trim()) { renderPage(); renderRail(); return; }
704708

705709
const results = searchQuery(query, searchIndex);
710+
711+
// Clear the concept rail during search
712+
rail.innerHTML = `
713+
<div class="rail-header">${lang === 'cn' ? '搜索' : 'Search'}</div>
714+
<div class="rail-empty">${results.length} ${lang === 'cn' ? '个结果' : 'result(s)'}</div>`;
715+
706716
if (results.length === 0) {
707-
const dd = createSearchDropdown();
708-
dd.innerHTML = `<div class="search-empty">${lang === 'cn' ? '未找到结果' : 'No results found'}</div>`;
717+
main.innerHTML = `
718+
<div class="search-results-page">
719+
<h1 class="article-title">${lang === 'cn' ? '搜索结果' : 'Search Results'}</h1>
720+
<p class="search-query-label">"${escapeHtml(query)}"</p>
721+
<div class="search-no-results">
722+
<p>${lang === 'cn' ? '未找到匹配的内容。' : 'No matching content found.'}</p>
723+
<p style="color:var(--c-text-tertiary);font-size:0.875rem;">${lang === 'cn' ? '尝试不同的关键词或更短的查询。' : 'Try different keywords or a shorter query.'}</p>
724+
</div>
725+
</div>`;
709726
return;
710727
}
711728

712-
const dd = createSearchDropdown();
713-
let html = "";
729+
let html = `<div class="search-results-page">`;
730+
html += `<h1 class="article-title">${lang === 'cn' ? '搜索结果' : 'Search Results'}</h1>`;
731+
html += `<p class="search-query-label">"${escapeHtml(query)}" &mdash; ${results.length} ${lang === 'cn' ? '个匹配' : 'match(es)'}</p>`;
732+
html += `<div class="search-results-list">`;
733+
714734
for (const r of results) {
715735
const icon = r.type === "page" ? "📄" : "💡";
716-
const snippet = getSnippet(r.text, query);
736+
const snippet = getSnippet(r.text, query, 200);
717737
const meta = r.type === "page" ? r.domain : (lang === 'cn' ? '概念' : 'Concept');
718-
html += `<div class="search-result" data-type="${r.type}" data-id="${r.id}">`;
719-
html += `<div class="search-result-header">`;
738+
html += `<div class="search-result-card" data-type="${r.type}" data-id="${r.id}">`;
739+
html += `<div class="search-result-card-header">`;
720740
html += `<span class="search-result-icon">${icon}</span>`;
721741
html += `<span class="search-result-title">${r.title}</span>`;
722-
html += `<span class="search-result-meta">${meta}</span>`;
742+
html += `<span class="search-result-badge">${meta}</span>`;
723743
html += `</div>`;
724-
html += `<div class="search-result-snippet">${snippet}</div>`;
744+
html += `<div class="search-result-body">${snippet}</div>`;
725745
html += `</div>`;
726746
}
727-
dd.innerHTML = html;
728747

729-
dd.querySelectorAll(".search-result").forEach((el) => {
748+
html += `</div></div>`;
749+
main.innerHTML = html;
750+
751+
main.querySelectorAll(".search-result-card").forEach((el) => {
730752
el.addEventListener("click", () => {
731753
const type = el.dataset.type;
732754
const id = el.dataset.id;
733-
removeSearchDropdown();
734755
document.getElementById("nav-search-input").value = "";
735756
if (type === "page") {
736757
navigate(id);
737758
} else {
738-
// For concepts, navigate to the first page that uses it
739759
const concept = WIKI.concepts?.[id];
740760
if (concept?.usedIn?.length > 0) {
741761
navigate(concept.usedIn[0]);
@@ -745,20 +765,8 @@
745765
});
746766
}
747767

748-
function createSearchDropdown() {
749-
removeSearchDropdown();
750-
const dd = document.createElement("div");
751-
dd.className = "search-dropdown";
752-
dd.id = "search-dropdown";
753-
nav.appendChild(dd);
754-
searchDropdown = dd;
755-
return dd;
756-
}
757-
758-
function removeSearchDropdown() {
759-
const existing = document.getElementById("search-dropdown");
760-
if (existing) existing.remove();
761-
searchDropdown = null;
768+
function escapeHtml(str) {
769+
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
762770
}
763771

764772
// Global keyboard shortcuts
@@ -768,11 +776,6 @@
768776
const input = document.getElementById("nav-search-input");
769777
if (input) { input.focus(); input.select(); }
770778
}
771-
if (e.key === "Escape") {
772-
removeSearchDropdown();
773-
const input = document.getElementById("nav-search-input");
774-
if (input) input.blur();
775-
}
776779
});
777780

778781
// === Init ===

‎site/styles.css‎

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -190,93 +190,87 @@ body {
190190
background: var(--c-surface);
191191
}
192192

193-
/* Search Results Dropdown */
194-
.search-dropdown {
195-
position: absolute;
196-
left: var(--space-md);
197-
right: var(--space-md);
198-
top: auto;
199-
max-height: 400px;
200-
overflow-y: auto;
201-
background: var(--c-surface);
202-
border: 1px solid var(--c-border);
203-
border-radius: var(--radius-lg);
204-
box-shadow: 0 8px 32px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06);
205-
z-index: 1000;
206-
padding: var(--space-xs);
207-
margin-top: var(--space-xs);
193+
/* Search Results (in main area) */
194+
.search-results-page {
195+
padding: var(--space-xl) 0;
208196
}
209197

210-
.search-empty {
211-
padding: var(--space-lg);
212-
text-align: center;
213-
color: var(--c-text-tertiary);
214-
font-size: 0.8125rem;
198+
.search-query-label {
199+
font-size: 0.875rem;
200+
color: var(--c-text-secondary);
201+
margin-bottom: var(--space-xl);
215202
font-style: italic;
216203
}
217204

218-
.search-result {
219-
padding: var(--space-sm) var(--space-md);
220-
border-radius: var(--radius);
221-
cursor: pointer;
222-
transition: background 0.1s;
205+
.search-no-results {
206+
padding: var(--space-2xl);
207+
text-align: center;
208+
color: var(--c-text-secondary);
223209
}
224210

225-
.search-result:hover {
226-
background: var(--c-nav-hover);
211+
.search-results-list {
212+
display: flex;
213+
flex-direction: column;
214+
gap: var(--space-md);
227215
}
228216

229-
.search-result + .search-result {
230-
border-top: 1px solid var(--c-border);
217+
.search-result-card {
218+
padding: var(--space-lg);
219+
border: 1px solid var(--c-border);
220+
border-radius: var(--radius-lg);
221+
cursor: pointer;
222+
transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
223+
}
224+
225+
.search-result-card:hover {
226+
border-color: var(--c-accent-muted);
227+
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
228+
background: var(--c-surface-alt);
231229
}
232230

233-
.search-result-header {
231+
.search-result-card-header {
234232
display: flex;
235233
align-items: center;
236-
gap: var(--space-xs);
237-
margin-bottom: 2px;
234+
gap: var(--space-sm);
235+
margin-bottom: var(--space-sm);
238236
}
239237

240238
.search-result-icon {
241-
font-size: 0.75rem;
239+
font-size: 1rem;
242240
flex-shrink: 0;
243241
}
244242

245243
.search-result-title {
246244
font-family: var(--font-ui);
247-
font-size: 0.8125rem;
245+
font-size: 1rem;
248246
font-weight: 600;
249247
color: var(--c-text);
250-
white-space: nowrap;
251-
overflow: hidden;
252-
text-overflow: ellipsis;
253248
}
254249

255-
.search-result-meta {
250+
.search-result-badge {
256251
font-size: 0.625rem;
257-
color: var(--c-text-tertiary);
258252
font-weight: 500;
259253
text-transform: uppercase;
260254
letter-spacing: 0.04em;
255+
color: var(--c-accent);
256+
background: var(--c-tag-bg);
257+
padding: 2px 6px;
258+
border-radius: 3px;
261259
margin-left: auto;
262260
flex-shrink: 0;
263261
}
264262

265-
.search-result-snippet {
266-
font-size: 0.75rem;
263+
.search-result-body {
264+
font-size: 0.875rem;
267265
color: var(--c-text-secondary);
268-
line-height: 1.4;
269-
overflow: hidden;
270-
display: -webkit-box;
271-
-webkit-line-clamp: 2;
272-
-webkit-box-orient: vertical;
266+
line-height: 1.6;
273267
}
274268

275-
.search-result-snippet mark {
269+
.search-result-body mark {
276270
background: #fef08a;
277271
color: inherit;
278272
border-radius: 2px;
279-
padding: 0 1px;
273+
padding: 0 2px;
280274
}
281275

282276
.nav-section-label {

0 commit comments

Comments
 (0)