Compare commits

...

2 commits

4 changed files with 37 additions and 15 deletions

View file

@ -1,6 +1,6 @@
{
"name": "folio",
"version": "0.1.7",
"version": "0.1.8",
"description": "A simple, local, open-source desktop writing app.",
"productName": "Folio",
"author": "Folio Contributors",

View file

@ -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
// directory, then open it. The folder name is the sanitized book title; if a
// folder with that name already exists we append a numeric suffix.
// Create a new book as a dedicated folder <name>/ inside the user's chosen
// storage folder (Settings → Folder), falling back to Documents when none is
// 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) => {
const title = (name || "").trim();
if (!title) return { error: "Please provide a book name." };
const base = app.getPath("documents");
const base = getBooksDir();
const folder = sanitizeBookFolderName(title) || "Untitled";
let target = path.join(base, folder);
let n = 2;
@ -591,7 +608,7 @@ function exportDefaultPath(filename: string): string {
const dir =
last && fs.existsSync(last) && fs.statSync(last).isDirectory()
? last
: app.getPath("documents");
: getBooksDir();
return path.join(dir, filename);
}
@ -972,6 +989,7 @@ function snapshotSettingsSnapshot() {
keep: clampInt(getSetting("snapshotKeep", SNAPSHOT_DEFAULTS.keep), SNAPSHOT_DEFAULTS.keep, 0, 200),
location,
resolvedRoot: resolved,
booksDir: getBooksDir(),
snapshotCount: snaps.length,
lastSnapshot: snaps.length ? snaps[snaps.length - 1] : null,
};
@ -1008,7 +1026,7 @@ handleIpc("folio:setSnapshotSettings", (_e, patch: unknown) => {
handleIpc("folio:pickSnapshotFolder", async () => {
const { canceled, filePaths } = await showOpenDialog({
properties: ["openDirectory"],
title: "Choose snapshot folder",
title: "Choose storage folder (new books + snapshots)",
});
return { canceled, filePaths };
});
@ -1089,14 +1107,14 @@ handleIpc("folio:importMarkdown", async () => {
filters: [{ name: "Markdown and text", extensions: ["md", "markdown", "txt"] }],
});
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
// (the desktop launcher can be handed either via file associations).
handleIpc("folio:openPathOrImport", async (_e, p: string) => {
if (p && isImportableMarkdown(p)) {
const results = importMarkdownFiles([p], app.getPath("documents"));
const results = importMarkdownFiles([p], getBooksDir());
const r = results[0];
if (!r.ok) return { error: r.error };
return openBookAt(r.bookPath as string);
@ -1300,7 +1318,7 @@ function fileArgFrom(argv: string[]): string | null {
function openFromArg(p: string): void {
if (isImportableMarkdown(p)) {
const results = importMarkdownFiles([p], app.getPath("documents"));
const results = importMarkdownFiles([p], getBooksDir());
const r = results[0];
if (r.ok) {
openBookAt(r.bookPath as string);

View file

@ -23,6 +23,8 @@ interface SnapshotSettings {
keep: number;
location: string;
resolvedRoot: string;
/** Where new books are saved (Settings → Storage folder). */
booksDir?: string;
snapshotCount: number;
lastSnapshot: string | null;
}
@ -59,8 +61,10 @@ export function createSettingsFeature(ctx: BookCtx): SettingsFeature {
intervalInput.value = String(s.intervalMin ?? 5);
keepInput.value = String(s.keep ?? 20);
locationInput.value = s.location || "";
locationInput.placeholder = "Default: <book>/Folio-Backups";
resolvedEl.textContent = `Saving to: ${shortPath(s.resolvedRoot || "—")}`;
locationInput.placeholder = "Default: Documents (books) · <book>/Folio-Backups (snapshots)";
resolvedEl.textContent = s.booksDir
? `New books save to: ${shortPath(s.booksDir)}`
: `Saving to: ${shortPath(s.resolvedRoot || "—")}`;
lastSnapEl.textContent = s.lastSnapshot
? `${s.snapshotCount} snapshot${s.snapshotCount === 1 ? "" : "s"} · latest ${s.lastSnapshot.split("/").pop()}`
: "No snapshots yet.";

View file

@ -343,9 +343,9 @@
<label>Keep last <input type="number" id="settingKeep" class="modal-input setting-num" min="0" max="200" value="20" /></label>
</div>
<div class="backup-control">
<span class="backup-label">Folder</span>
<input type="text" id="settingLocation" class="backup-input" readonly placeholder="Default: <book>/Folio-Backups" title="Snapshot folder (empty = default)" />
<button type="button" class="button button-ghost backup-browse" id="settingBrowse" title="Choose snapshot folder">…</button>
<span class="backup-label">Storage folder</span>
<input type="text" id="settingLocation" class="backup-input" readonly placeholder="Default: Documents" title="Where new books and snapshots are saved (empty = Documents / &lt;book&gt;/Folio-Backups)" />
<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>
</div>
<p class="setting-meta" id="settingResolved"></p>