From 647965668015b3bde4019e8137c55f4a2912f182 Mon Sep 17 00:00:00 2001 From: avi Date: Tue, 22 Sep 2026 17:18:16 -0500 Subject: [PATCH] feat(ui): Search+Full Book nav row under Home bar; search matches chapter titles --- src/renderer/book/search.ts | 31 +++++++++---- src/renderer/index.css | 87 ++++++++++++++++++++++++++++--------- src/renderer/index.html | 10 +++-- 3 files changed, 95 insertions(+), 33 deletions(-) diff --git a/src/renderer/book/search.ts b/src/renderer/book/search.ts index c19c9da..e6295c5 100644 --- a/src/renderer/book/search.ts +++ b/src/renderer/book/search.ts @@ -95,27 +95,40 @@ export function createSearchFeature(ctx: BookCtx, editor: Editor): SearchFeature void (async () => { const list = await api.getChapterList(); const chapters = Array.isArray(list) ? list : []; - const byChapter: { id: string; title: string; plain: string; positions: number[] }[] = []; + const byChapter: { id: string; title: string; plain: string; positions: number[]; titleHits: number }[] = []; let total = 0; for (const ch of chapters) { if (total >= 200) break; + // Chapter titles count as matches too: a title hit links to the + // chapter itself (no in-body scroll position). + const title = ch.title || ch.id; + const titleHits = findMatches(title, term, matchCase).length; const res = await api.getChapterContent(ch.id); const md = typeof res === "string" ? res : ""; const plain = markdownToPlain(md); const positions = findMatches(plain, term, matchCase); - if (positions.length) { - byChapter.push({ id: ch.id, title: ch.title || ch.id, plain, positions }); - total += positions.length; + if (positions.length || titleHits) { + byChapter.push({ id: ch.id, title, plain, positions, titleHits }); + total += positions.length + titleHits; } } searchMeta.textContent = total ? `${total} match${total === 1 ? "" : "es"} in ${byChapter.length} chapter${byChapter.length === 1 ? "" : "s"}` : "No matches."; - for (const r of byChapter) { - const head = document.createElement("li"); - head.className = "search-chapter-head"; - head.textContent = `${r.title} (${r.positions.length})`; - searchResults.appendChild(head); + for (const r of byChapter) { + const head = document.createElement("li"); + head.className = "search-chapter-head"; + const counts = [ + r.titleHits ? `title: ${r.titleHits}` : "", + r.positions.length ? `text: ${r.positions.length}` : "", + ].filter(Boolean).join(", "); + head.textContent = `${r.title} (${counts})`; + if (r.titleHits) { + head.classList.add("search-result"); + head.title = "Open chapter"; + head.addEventListener("click", () => void goToResult(r.id, 0, term, matchCase)); + } + searchResults.appendChild(head); for (const pos of r.positions) { const li = document.createElement("li"); li.className = "search-result"; diff --git a/src/renderer/index.css b/src/renderer/index.css index 9495beb..ad9509d 100644 --- a/src/renderer/index.css +++ b/src/renderer/index.css @@ -188,6 +188,13 @@ html, body { margin-bottom: 10px; } +.update-control { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 10px; +} + .backup-label { font-size: 0.72rem; font-weight: 700; @@ -281,11 +288,24 @@ html, body { word-break: break-all; } +/* ---- sidebar nav row (Search / Full Book, under the Home bar) ---- */ +.sidebar-nav { + display: flex; + gap: 8px; + margin-bottom: var(--gap); +} + +.sidebar-nav .nav-btn { + flex: 1; + min-width: 0; + width: auto; +} + /* ---- full book nav ---- */ .full-book-nav { - margin-top: var(--gap); - padding-top: var(--gap); - border-top: 1px solid var(--color-border); + display: flex; + flex: 1; + min-width: 0; } .full-book-btn { @@ -301,7 +321,8 @@ html, body { padding: 6px 12px; cursor: pointer; transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease; - min-width: 100px; + min-width: 0; + flex: 1; } .full-book-btn:hover { @@ -588,6 +609,47 @@ html, body { color: var(--color-on-accent); } +.recent-list li.recent-item.selected { + background: var(--color-accent); + color: var(--color-on-accent); + outline: 1px solid var(--color-accent); +} + +.recent-list li.recent-item:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: -2px; +} + +/* Row actions appear only when the row is selected. */ +.recent-item .recent-actions { + flex: 0 0 auto; + display: none; + align-items: center; + gap: 4px; +} + +.recent-item.selected .recent-actions { + display: flex; +} + +.recent-item .recent-action { + height: 20px; + line-height: 1; + font-size: 0.72rem; + padding: 0 6px; + border: none; + background: transparent; + color: inherit; + cursor: pointer; + border-radius: var(--radius); + opacity: 0.85; +} + +.recent-item .recent-action:hover { + opacity: 1; + background: rgba(255, 255, 255, 0.18); +} + .recent-item .recent-title { flex: 1 1 auto; min-width: 0; @@ -597,24 +659,9 @@ html, body { user-select: none; } -.recent-item .recent-delete { - flex: 0 0 auto; - width: 20px; - height: 20px; - line-height: 1; - font-size: 1rem; - padding: 0; - border: none; - background: transparent; - color: inherit; - cursor: pointer; - border-radius: var(--radius); - opacity: 0.6; -} - .recent-item .recent-delete:hover { opacity: 1; - background: rgba(220, 53, 69, 0.18); + background: rgba(220, 53, 69, 0.35); color: #ff6b6b; } diff --git a/src/renderer/index.html b/src/renderer/index.html index 952824a..d41d81b 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -24,12 +24,14 @@ - -