Compare commits

...

3 commits

5 changed files with 124 additions and 59 deletions

View file

@ -80,32 +80,34 @@ export function buildMenu(
},
{ type: "separator" },
{
label: "Export Markdown…",
accelerator: "CmdOrCtrl+Shift+E",
label: "Export",
enabled: !!getState().bookPath,
submenu: [
{
label: "Markdown…",
accelerator: "CmdOrCtrl+Shift+E",
click: () => getWindow()?.webContents.send("folio:export-markdown-requested"),
},
{
label: "Export Bundle (ZIP)…",
enabled: !!getState().bookPath,
label: "Bundle (ZIP)…",
click: () => getWindow()?.webContents.send("folio:export-zip-requested"),
},
{ type: "separator" },
{
label: "Export Full Book (PDF)…",
label: "Full Book (PDF)…",
accelerator: "CmdOrCtrl+Shift+P",
enabled: !!getState().bookPath,
click: () => getWindow()?.webContents.send("folio:export-pdf-requested"),
},
{
label: "Export Full Book (DOCX)…",
enabled: !!getState().bookPath,
label: "Full Book (DOCX)…",
click: () => getWindow()?.webContents.send("folio:export-docx-requested"),
},
{
label: "Export Full Book (ODT)…",
enabled: !!getState().bookPath,
label: "Full Book (ODT)…",
click: () => getWindow()?.webContents.send("folio:export-odt-requested"),
},
],
},
{ type: "separator" },
{
label: "Check for Updates…",

View file

@ -95,17 +95,21 @@ 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
@ -114,7 +118,16 @@ export function createSearchFeature(ctx: BookCtx, editor: Editor): SearchFeature
for (const r of byChapter) {
const head = document.createElement("li");
head.className = "search-chapter-head";
head.textContent = `${r.title} (${r.positions.length})`;
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");

View file

@ -164,8 +164,8 @@ export function createSettingsFeature(ctx: BookCtx): SettingsFeature {
if (e.key === "Escape" && !modal.classList.contains("hidden")) closeSettings();
});
// Sidebar + File-menu entry points.
$("settingsBtn")?.addEventListener("click", openSettings);
// File-menu entry point (the redundant sidebar button was removed Sep 22).
$("settingCheckUpdate")?.addEventListener("click", () => hooks.checkForUpdates());
api.onSettingsRequested(() => openSettings());
// Refresh the resolved-path line whenever a book opens (the default root

View file

@ -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;
}

View file

@ -24,13 +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="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" id="fullBookBtn" title="View the entire book as one continuous manuscript">Full Book</button>
<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>
<button class="button button-ghost" id="settingsBtn" title="Settings (autosave, backups)">Settings</button>
<div class="theme-control">
<label class="theme-label" for="themeSelect">Theme</label>
<select id="themeSelect" class="theme-select" title="Interface theme">
@ -57,8 +58,6 @@
<div class="welcome-actions">
<button class="button" id="welcomeNewBook">New</button>
<button class="button button-ghost" id="welcomeOpenBook">Open</button>
<button class="button button-ghost" id="welcomeOpenMd" title="Open a Markdown or text file as a new book">Open file</button>
<button class="button button-ghost" id="welcomeUpdate" title="Check for a new version of Folio">Update</button>
</div>
<div class="recents-block">
<div class="recents-title">Recent</div>
@ -351,6 +350,10 @@
</div>
<p class="setting-meta" id="settingResolved"></p>
<p class="setting-meta" id="settingLastSnap"></p>
<div class="update-control">
<span class="backup-label">Updates</span>
<button type="button" class="button button-ghost" id="settingCheckUpdate" title="Check for a new version of Folio">Check now</button>
</div>
<div class="modal-actions">
<button class="button button-ghost" id="settingSnapNow" title="Take a snapshot right now">Snapshot now</button>
<button class="button" id="settingsClose">Close</button>