feat(settings): the storage folder now decides where books are saved — new books, .md imports, and the export dialog default all land there (was: always Documents); label clarifies it covers books + snapshots
This commit is contained in:
parent
e794f241ba
commit
283bb7c772
3 changed files with 36 additions and 14 deletions
|
|
@ -246,13 +246,30 @@ async function openBookAt(p: string): Promise<BookResult | ErrResult> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new book as a dedicated folder <name>/ inside the user's Documents
|
// Create a new book as a dedicated folder <name>/ inside the user's chosen
|
||||||
// directory, then open it. The folder name is the sanitized book title; if a
|
// storage folder (Settings → Folder), falling back to Documents when none is
|
||||||
// folder with that name already exists we append a numeric suffix.
|
// set or the stored one no longer exists, then open it. The folder name is
|
||||||
|
// the sanitized book title; if a folder with that name already exists we
|
||||||
|
// append a numeric suffix.
|
||||||
|
function getBooksDir(): string {
|
||||||
|
// One storage folder drives everything (snapshots, recovery, new books).
|
||||||
|
// snapshotLocation is the one the Settings modal writes; backupLocation is
|
||||||
|
// the older key — honor both so an existing choice keeps working.
|
||||||
|
for (const key of ["snapshotLocation", "backupLocation"]) {
|
||||||
|
const v = String(getSetting<string>(key, "") || "").trim();
|
||||||
|
try {
|
||||||
|
if (v && fs.statSync(v).isDirectory()) return v;
|
||||||
|
} catch {
|
||||||
|
// stale or unreadable path — fall through to the next candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return app.getPath("documents");
|
||||||
|
}
|
||||||
|
|
||||||
handleIpc("folio:newBookNamed", async (_e, name: string) => {
|
handleIpc("folio:newBookNamed", async (_e, name: string) => {
|
||||||
const title = (name || "").trim();
|
const title = (name || "").trim();
|
||||||
if (!title) return { error: "Please provide a book name." };
|
if (!title) return { error: "Please provide a book name." };
|
||||||
const base = app.getPath("documents");
|
const base = getBooksDir();
|
||||||
const folder = sanitizeBookFolderName(title) || "Untitled";
|
const folder = sanitizeBookFolderName(title) || "Untitled";
|
||||||
let target = path.join(base, folder);
|
let target = path.join(base, folder);
|
||||||
let n = 2;
|
let n = 2;
|
||||||
|
|
@ -591,7 +608,7 @@ function exportDefaultPath(filename: string): string {
|
||||||
const dir =
|
const dir =
|
||||||
last && fs.existsSync(last) && fs.statSync(last).isDirectory()
|
last && fs.existsSync(last) && fs.statSync(last).isDirectory()
|
||||||
? last
|
? last
|
||||||
: app.getPath("documents");
|
: getBooksDir();
|
||||||
return path.join(dir, filename);
|
return path.join(dir, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -972,6 +989,7 @@ function snapshotSettingsSnapshot() {
|
||||||
keep: clampInt(getSetting("snapshotKeep", SNAPSHOT_DEFAULTS.keep), SNAPSHOT_DEFAULTS.keep, 0, 200),
|
keep: clampInt(getSetting("snapshotKeep", SNAPSHOT_DEFAULTS.keep), SNAPSHOT_DEFAULTS.keep, 0, 200),
|
||||||
location,
|
location,
|
||||||
resolvedRoot: resolved,
|
resolvedRoot: resolved,
|
||||||
|
booksDir: getBooksDir(),
|
||||||
snapshotCount: snaps.length,
|
snapshotCount: snaps.length,
|
||||||
lastSnapshot: snaps.length ? snaps[snaps.length - 1] : null,
|
lastSnapshot: snaps.length ? snaps[snaps.length - 1] : null,
|
||||||
};
|
};
|
||||||
|
|
@ -1008,7 +1026,7 @@ handleIpc("folio:setSnapshotSettings", (_e, patch: unknown) => {
|
||||||
handleIpc("folio:pickSnapshotFolder", async () => {
|
handleIpc("folio:pickSnapshotFolder", async () => {
|
||||||
const { canceled, filePaths } = await showOpenDialog({
|
const { canceled, filePaths } = await showOpenDialog({
|
||||||
properties: ["openDirectory"],
|
properties: ["openDirectory"],
|
||||||
title: "Choose snapshot folder",
|
title: "Choose storage folder (new books + snapshots)",
|
||||||
});
|
});
|
||||||
return { canceled, filePaths };
|
return { canceled, filePaths };
|
||||||
});
|
});
|
||||||
|
|
@ -1089,14 +1107,14 @@ handleIpc("folio:importMarkdown", async () => {
|
||||||
filters: [{ name: "Markdown and text", extensions: ["md", "markdown", "txt"] }],
|
filters: [{ name: "Markdown and text", extensions: ["md", "markdown", "txt"] }],
|
||||||
});
|
});
|
||||||
if (canceled || !filePaths.length) return { canceled: true } as const;
|
if (canceled || !filePaths.length) return { canceled: true } as const;
|
||||||
return importMarkdownFiles(filePaths, app.getPath("documents"));
|
return importMarkdownFiles(filePaths, getBooksDir());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open a path that may be either a Folio book directory or a Markdown file
|
// Open a path that may be either a Folio book directory or a Markdown file
|
||||||
// (the desktop launcher can be handed either via file associations).
|
// (the desktop launcher can be handed either via file associations).
|
||||||
handleIpc("folio:openPathOrImport", async (_e, p: string) => {
|
handleIpc("folio:openPathOrImport", async (_e, p: string) => {
|
||||||
if (p && isImportableMarkdown(p)) {
|
if (p && isImportableMarkdown(p)) {
|
||||||
const results = importMarkdownFiles([p], app.getPath("documents"));
|
const results = importMarkdownFiles([p], getBooksDir());
|
||||||
const r = results[0];
|
const r = results[0];
|
||||||
if (!r.ok) return { error: r.error };
|
if (!r.ok) return { error: r.error };
|
||||||
return openBookAt(r.bookPath as string);
|
return openBookAt(r.bookPath as string);
|
||||||
|
|
@ -1300,7 +1318,7 @@ function fileArgFrom(argv: string[]): string | null {
|
||||||
|
|
||||||
function openFromArg(p: string): void {
|
function openFromArg(p: string): void {
|
||||||
if (isImportableMarkdown(p)) {
|
if (isImportableMarkdown(p)) {
|
||||||
const results = importMarkdownFiles([p], app.getPath("documents"));
|
const results = importMarkdownFiles([p], getBooksDir());
|
||||||
const r = results[0];
|
const r = results[0];
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
openBookAt(r.bookPath as string);
|
openBookAt(r.bookPath as string);
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ interface SnapshotSettings {
|
||||||
keep: number;
|
keep: number;
|
||||||
location: string;
|
location: string;
|
||||||
resolvedRoot: string;
|
resolvedRoot: string;
|
||||||
|
/** Where new books are saved (Settings → Storage folder). */
|
||||||
|
booksDir?: string;
|
||||||
snapshotCount: number;
|
snapshotCount: number;
|
||||||
lastSnapshot: string | null;
|
lastSnapshot: string | null;
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +61,10 @@ export function createSettingsFeature(ctx: BookCtx): SettingsFeature {
|
||||||
intervalInput.value = String(s.intervalMin ?? 5);
|
intervalInput.value = String(s.intervalMin ?? 5);
|
||||||
keepInput.value = String(s.keep ?? 20);
|
keepInput.value = String(s.keep ?? 20);
|
||||||
locationInput.value = s.location || "";
|
locationInput.value = s.location || "";
|
||||||
locationInput.placeholder = "Default: <book>/Folio-Backups";
|
locationInput.placeholder = "Default: Documents (books) · <book>/Folio-Backups (snapshots)";
|
||||||
resolvedEl.textContent = `Saving to: ${shortPath(s.resolvedRoot || "—")}`;
|
resolvedEl.textContent = s.booksDir
|
||||||
|
? `New books save to: ${shortPath(s.booksDir)}`
|
||||||
|
: `Saving to: ${shortPath(s.resolvedRoot || "—")}`;
|
||||||
lastSnapEl.textContent = s.lastSnapshot
|
lastSnapEl.textContent = s.lastSnapshot
|
||||||
? `${s.snapshotCount} snapshot${s.snapshotCount === 1 ? "" : "s"} · latest ${s.lastSnapshot.split("/").pop()}`
|
? `${s.snapshotCount} snapshot${s.snapshotCount === 1 ? "" : "s"} · latest ${s.lastSnapshot.split("/").pop()}`
|
||||||
: "No snapshots yet.";
|
: "No snapshots yet.";
|
||||||
|
|
|
||||||
|
|
@ -343,9 +343,9 @@
|
||||||
<label>Keep last <input type="number" id="settingKeep" class="modal-input setting-num" min="0" max="200" value="20" /></label>
|
<label>Keep last <input type="number" id="settingKeep" class="modal-input setting-num" min="0" max="200" value="20" /></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="backup-control">
|
<div class="backup-control">
|
||||||
<span class="backup-label">Folder</span>
|
<span class="backup-label">Storage folder</span>
|
||||||
<input type="text" id="settingLocation" class="backup-input" readonly placeholder="Default: <book>/Folio-Backups" title="Snapshot folder (empty = default)" />
|
<input type="text" id="settingLocation" class="backup-input" readonly placeholder="Default: Documents" title="Where new books and snapshots are saved (empty = Documents / <book>/Folio-Backups)" />
|
||||||
<button type="button" class="button button-ghost backup-browse" id="settingBrowse" title="Choose snapshot folder">…</button>
|
<button type="button" class="button button-ghost backup-browse" id="settingBrowse" title="Choose storage folder">…</button>
|
||||||
<button type="button" class="button button-ghost" id="settingResetLoc" title="Back to the default folder">Reset</button>
|
<button type="button" class="button button-ghost" id="settingResetLoc" title="Back to the default folder">Reset</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="setting-meta" id="settingResolved"></p>
|
<p class="setting-meta" id="settingResolved"></p>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue