feat(ui): Search+Full Book nav row under Home bar; search matches chapter titles
This commit is contained in:
parent
b453681898
commit
6479656680
3 changed files with 92 additions and 30 deletions
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,14 @@
|
|||
<span class="book-title" id="bookTitle"></span>
|
||||
<span class="book-path" id="bookPath" title=""></span>
|
||||
</div>
|
||||
<ul class="chapter-list" id="chapterList"></ul>
|
||||
<div class="full-book-nav hidden" id="fullBookNav">
|
||||
<button class="full-book-btn" id="fullBookBtn" title="View the entire book as one continuous manuscript">Full Book</button>
|
||||
<div class="sidebar-nav">
|
||||
<button class="button button-ghost nav-btn" id="searchBtn" title="Search chapter titles and text (Ctrl+F)">Search</button>
|
||||
<div class="full-book-nav hidden" id="fullBookNav">
|
||||
<button class="full-book-btn nav-btn" id="fullBookBtn" title="View the entire book as one continuous manuscript">Full Book</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="chapter-list" id="chapterList"></ul>
|
||||
<div class="sidebar-footer">
|
||||
<button class="button button-ghost" id="searchBtn" title="Search all chapters (Ctrl+F)">Search</button>
|
||||
<div class="theme-control">
|
||||
<label class="theme-label" for="themeSelect">Theme</label>
|
||||
<select id="themeSelect" class="theme-select" title="Interface theme">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue