Compare commits

..

No commits in common. "c064e3aab1dc58d82308107b98632fef605615aa" and "e794f241ba7b709211b0d9d24145fb6f9a2494a3" have entirely different histories.

4 changed files with 15 additions and 37 deletions

View file

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

View file

@ -246,30 +246,13 @@ async function openBookAt(p: string): Promise<BookResult | ErrResult> {
} }
} }
// Create a new book as a dedicated folder <name>/ inside the user's chosen // Create a new book as a dedicated folder <name>/ inside the user's Documents
// storage folder (Settings → Folder), falling back to Documents when none is // directory, then open it. The folder name is the sanitized book title; if a
// set or the stored one no longer exists, then open it. The folder name is // folder with that name already exists we append a numeric suffix.
// 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 = getBooksDir(); const base = app.getPath("documents");
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;
@ -608,7 +591,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
: getBooksDir(); : app.getPath("documents");
return path.join(dir, filename); return path.join(dir, filename);
} }
@ -989,7 +972,6 @@ 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,
}; };
@ -1026,7 +1008,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 storage folder (new books + snapshots)", title: "Choose snapshot folder",
}); });
return { canceled, filePaths }; return { canceled, filePaths };
}); });
@ -1107,14 +1089,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, getBooksDir()); return importMarkdownFiles(filePaths, app.getPath("documents"));
}); });
// 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], getBooksDir()); const results = importMarkdownFiles([p], app.getPath("documents"));
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);
@ -1318,7 +1300,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], getBooksDir()); const results = importMarkdownFiles([p], app.getPath("documents"));
const r = results[0]; const r = results[0];
if (r.ok) { if (r.ok) {
openBookAt(r.bookPath as string); openBookAt(r.bookPath as string);

View file

@ -23,8 +23,6 @@ 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;
} }
@ -61,10 +59,8 @@ 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: Documents (books) · <book>/Folio-Backups (snapshots)"; locationInput.placeholder = "Default: <book>/Folio-Backups";
resolvedEl.textContent = s.booksDir resolvedEl.textContent = `Saving to: ${shortPath(s.resolvedRoot || "—")}`;
? `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.";

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> <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">Storage folder</span> <span class="backup-label">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)" /> <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 storage folder">…</button> <button type="button" class="button button-ghost backup-browse" id="settingBrowse" title="Choose snapshot 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>