From 1220009e529eaf9593c7509a480d538f8c2f20de Mon Sep 17 00:00:00 2001 From: CrystalSplitter Date: Tue, 6 Oct 2026 00:25:51 -0700 Subject: [PATCH 1/5] Add autosave feature Once a day, on start up, save the current full state to the userData directory as ffr_autosave.frdata. This addresses https://github.com/FluentFlame/fluentflame-reader/issues/228 somewhat, though it would be nice to surface this info more easily. --- src/bridges/utils.ts | 8 ++++++++ src/index.tsx | 3 +++ src/main/utils.ts | 42 ++++++++++++++++++++++++++++++++++++++-- src/scripts/autosave.tsx | 37 +++++++++++++++++++++++++++++++++++ src/scripts/db.ts | 6 +++++- 5 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 src/scripts/autosave.tsx diff --git a/src/bridges/utils.ts b/src/bridges/utils.ts index 9be7c91f..eef0c929 100644 --- a/src/bridges/utils.ts +++ b/src/bridges/utils.ts @@ -64,6 +64,14 @@ const utilsBridge = { )) as string; }, + autosaveBackupData: (writeContent: string): Promise => { + return ipcRenderer.invoke("write-autosave", writeContent); + }, + + getLastAutosaveTimestamp: (): Promise => { + return ipcRenderer.invoke("get-last-autosave-timestamp"); + }, + getCacheSize: async (): Promise => { return await ipcRenderer.invoke("get-cache"); }, diff --git a/src/index.tsx b/src/index.tsx index 5175036e..090302f8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,6 +11,7 @@ import { initApp, openTextMenu } from "./scripts/models/app"; import { startPool } from "./scripts/fetch-pool"; import { rootStore } from "./scripts/reducer"; import { initThemeUpdateListener } from "./scripts/settings"; +import { renewAutosave } from "./scripts/autosave"; startPool(); // Start global fetch pool. initThemeUpdateListener(); @@ -22,6 +23,8 @@ initializeIcons("icons/"); rootStore.dispatch(initApp()); +renewAutosave(); + window.utils.addMainContextListener((pos, text) => { rootStore.dispatch(openTextMenu(pos, text)); }); diff --git a/src/main/utils.ts b/src/main/utils.ts index f0878b1b..63f3d745 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -7,13 +7,20 @@ import { clipboard, systemPreferences, } from "electron"; +import path from "path"; +import { getFonts } from "font-list"; +import { promises, writeFile } from "fs"; + import pkg from "../../package.json"; import { WindowManager } from "./window"; import { trimContent, trimString } from "./pure-utils"; -import { promises, writeFile } from "fs"; import { ImageCallbackTypes, TouchBarTexts } from "../schema-types"; import { initMainTouchBar } from "./touchbar"; -import { getFonts } from "font-list"; + +function getAutosavePath() { + const userDataPath = app.getPath("userData"); + return `${userDataPath}/autosaves/ffr_autosave.frdata`; +} export function setUtilsListeners(manager: WindowManager) { async function openExternal(url: string, background = false) { @@ -159,6 +166,37 @@ export function setUtilsListeners(manager: WindowManager) { }, ); + ipcMain.handle("write-autosave", async (_, writeContent: string) => { + const autosavePath = getAutosavePath(); + try { + const dirStat = await promises.stat(path.dirname(autosavePath), { + throwIfNoEntry: false, + }); + if (dirStat == undefined) { + await promises.mkdir(path.dirname(autosavePath)); + } + await promises.writeFile(getAutosavePath(), writeContent); + } catch (err: any) { + console.error("Failed to write autosave", err); + } + }); + + ipcMain.handle( + "get-last-autosave-timestamp", + async (): Promise => { + const autosavePath = getAutosavePath(); + try { + const result = await promises.stat(autosavePath, { + throwIfNoEntry: false, + }); + return result?.mtimeMs; + } catch (err: any) { + console.error(`Failed to stat ${autosavePath}`, err); + return undefined; + } + }, + ); + ipcMain.handle("get-cache", async () => { return await session.defaultSession.getCacheSize(); }); diff --git a/src/scripts/autosave.tsx b/src/scripts/autosave.tsx new file mode 100644 index 00000000..f6939e47 --- /dev/null +++ b/src/scripts/autosave.tsx @@ -0,0 +1,37 @@ +import * as db from "./db"; + +/** + * Renew the autosave file in userData if it's been long enough. + */ +export async function renewAutosave() { + const oneDay = 24 * 60 * 60 * 1000; + const autosaveMTimeMs = await window.utils.getLastAutosaveTimestamp(); + const autosaveStartTime = Date.now(); + if (autosaveStartTime < autosaveMTimeMs + oneDay) { + console.debug("Autosave still current, not rewriting."); + return; + } + let sources: db.SourceEntry[]; + let items: db.ItemEntry[]; + const transaction = db.fluentDB.transaction( + "r", + db.fluentDB.sources, + db.fluentDB.items, + async () => { + sources = await db.fluentDB.sources.toArray(); + items = await db.fluentDB.items.toArray(); + }, + ); + const output = window.settings.getAll(); + await transaction; + output["database"] = { + sources: sources, + items: items, + }; + await window.utils.autosaveBackupData(JSON.stringify(output)); + const autosaveEndTime = Date.now(); + console.debug( + "Completed autosave in " + + `${Math.round(autosaveEndTime - autosaveStartTime)}ms.`, + ); +} diff --git a/src/scripts/db.ts b/src/scripts/db.ts index a7ff3dcd..82d55676 100644 --- a/src/scripts/db.ts +++ b/src/scripts/db.ts @@ -294,7 +294,11 @@ function byteLength(str: string) { } export async function init() { - await migrateLovefieldSourcesDB("sourcesDB", 3); + try { + await migrateLovefieldSourcesDB("sourcesDB", 3); + } catch (e: any) { + console.error("Error migrating sources DB", e); + } try { await migrateLovefieldItemsDB("itemsDB", 1); } catch (e) { From 19862911c1f88302a04ceffdd379c5a59b4dcbae Mon Sep 17 00:00:00 2001 From: CrystalSplitter Date: Sun, 11 Oct 2026 22:31:32 +0000 Subject: [PATCH 2/5] Added corruption autosave recovery This commit introduces several new features: - Detecting that the database is corrupted (via the integrity table) - Offering an autosave recovery system for it. --- src/bridges/utils.ts | 4 +++ src/index.tsx | 5 ++- src/main/pure-utils.ts | 7 +++- src/main/utils.ts | 52 ++++++++++++++++++++++++++++- src/scripts/autosave.tsx | 71 +++++++++++++++++++++++++++++++++------- src/scripts/db.ts | 16 +++++++++ src/scripts/settings.ts | 41 ++++++++++++++++++++--- 7 files changed, 174 insertions(+), 22 deletions(-) diff --git a/src/bridges/utils.ts b/src/bridges/utils.ts index eef0c929..9bd4e62c 100644 --- a/src/bridges/utils.ts +++ b/src/bridges/utils.ts @@ -72,6 +72,10 @@ const utilsBridge = { return ipcRenderer.invoke("get-last-autosave-timestamp"); }, + notifyOfRecoveryNeeded: async (): Promise => { + return ipcRenderer.invoke("notify-of-recovery-needed"); + }, + getCacheSize: async (): Promise => { return await ipcRenderer.invoke("get-cache"); }, diff --git a/src/index.tsx b/src/index.tsx index 090302f8..337e73d3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,7 +11,7 @@ import { initApp, openTextMenu } from "./scripts/models/app"; import { startPool } from "./scripts/fetch-pool"; import { rootStore } from "./scripts/reducer"; import { initThemeUpdateListener } from "./scripts/settings"; -import { renewAutosave } from "./scripts/autosave"; +import { handleAutosaves } from "./scripts/autosave"; startPool(); // Start global fetch pool. initThemeUpdateListener(); @@ -21,10 +21,9 @@ applyAnimationMotionPref(); applyThemeSettings(); initializeIcons("icons/"); +handleAutosaves(); rootStore.dispatch(initApp()); -renewAutosave(); - window.utils.addMainContextListener((pos, text) => { rootStore.dispatch(openTextMenu(pos, text)); }); diff --git a/src/main/pure-utils.ts b/src/main/pure-utils.ts index 4cd81a71..aca20e3d 100644 --- a/src/main/pure-utils.ts +++ b/src/main/pure-utils.ts @@ -1,8 +1,13 @@ +/** + * pure-utils.ts is a collection of utilities that are self-contained, and require + * no inter-package imports. None are stateful. + */ + /** Ensure we don't have too long of a message. */ export function trimContent( s: string, charLen: number = 512, - lineLen: number = 5, + lineLen: number = 10, ): string { return limitLines(trimString(s, charLen), lineLen); } diff --git a/src/main/utils.ts b/src/main/utils.ts index 63f3d745..adf4cb50 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -17,9 +17,14 @@ import { trimContent, trimString } from "./pure-utils"; import { ImageCallbackTypes, TouchBarTexts } from "../schema-types"; import { initMainTouchBar } from "./touchbar"; +export type AutosaveNotifyResponse = + | { response: "ignored" } + | { response: "deleted" } + | { response: "recovered"; contents: string }; + function getAutosavePath() { const userDataPath = app.getPath("userData"); - return `${userDataPath}/autosaves/ffr_autosave.frdata`; + return path.join(userDataPath, "autosaves", "ffr_autosave.frdata"); } export function setUtilsListeners(manager: WindowManager) { @@ -197,6 +202,51 @@ export function setUtilsListeners(manager: WindowManager) { }, ); + ipcMain.handle( + "notify-of-recovery-needed", + async (): Promise => { + const title = "Database Corruption Detected"; + const detail = + "IndexedDB database appears to be corrupted, and an autosave file has been detected.\n\nRecover from autosave?"; + if (manager.hasWindow()) { + const dialogResponse = await dialog.showMessageBox( + manager.mainWindow, + { + type: "error", + title: title, + message: title, + detail: detail, + buttons: ["Ignore", "Delete autosave", "Recover"], + cancelId: 0, + defaultId: 2, + }, + ); + const autosavePath = getAutosavePath(); + switch (dialogResponse.response) { + case 0: + return { response: "ignored" }; + case 1: + await promises.rm(autosavePath); + return { response: "deleted" }; + case 2: + const contents = await promises.readFile(autosavePath, { + encoding: "utf8", + }); + return { response: "recovered", contents: contents }; + default: + // Unreachable. + throw Error("Unreachable"); + } + } else { + dialog.showErrorBox( + title, + "Unable to recover automatically, but autosave exists.", + ); + return { response: "ignored" }; + } + }, + ); + ipcMain.handle("get-cache", async () => { return await session.defaultSession.getCacheSize(); }); diff --git a/src/scripts/autosave.tsx b/src/scripts/autosave.tsx index f6939e47..d2c1231d 100644 --- a/src/scripts/autosave.tsx +++ b/src/scripts/autosave.tsx @@ -1,25 +1,71 @@ -import * as db from "./db"; +/** + * Autosaves have somewhat complicated logic, because it would be really bad + * if we autosaved over a good save with a bad save. So, we have to put in + * some very reliable heuristics to figure out if we recently lost some data. + * + * This is the purpose of the `integrity` table in the IndexedDB database. + * Specifically, if we see we had an autosave available, yet the `integrity` + * is empty, it means that we wiped that table. Since that never happens in + * the renderer code paths, it's very likely there was a corruption and it + * was wiped silently by Electron on `IndexedDB.open`. We then can condition + * on that, and recommend recovery. + */ + +import { fluentDB, type ItemEntry, type SourceEntry } from "./db"; +import { loadFromConfig } from "./settings"; + +const ONE_DAY = 24 * 60 * 60 * 1000; + +export enum AutosaveResponse { + SUCCESS = 0, + SKIPPED = 1, + INTEGRITY_FAILED = 2, +} + +export async function handleAutosaves(): Promise { + const autosaveResp = await renewAutosave(); + if (autosaveResp === AutosaveResponse.INTEGRITY_FAILED) { + // Dangerous area! Everything in the DB is invalid! + const response = await window.utils.notifyOfRecoveryNeeded(); + if (response.response === "recovered") { + // Resets integrity table itself. + await loadFromConfig(JSON.parse(response.contents)); + } else if (response.response === "deleted") { + fluentDB.integrity.put({ index: 0, timestamp: new Date() }); + } + } else { + fluentDB.integrity.put({ index: 0, timestamp: new Date() }); + } +} /** * Renew the autosave file in userData if it's been long enough. */ -export async function renewAutosave() { - const oneDay = 24 * 60 * 60 * 1000; +async function renewAutosave(): Promise { const autosaveMTimeMs = await window.utils.getLastAutosaveTimestamp(); + const integrityVals = await fluentDB.integrity.toArray(); + if (autosaveMTimeMs !== undefined && integrityVals.length === 0) { + console.error( + "Prior autosave exists, and current IndexedDB" + + " integrity check failed. Database is corrupt.", + ); + return AutosaveResponse.INTEGRITY_FAILED; + } + const autosaveStartTime = Date.now(); - if (autosaveStartTime < autosaveMTimeMs + oneDay) { + if (autosaveStartTime < autosaveMTimeMs + ONE_DAY) { console.debug("Autosave still current, not rewriting."); - return; + return AutosaveResponse.SKIPPED; } - let sources: db.SourceEntry[]; - let items: db.ItemEntry[]; - const transaction = db.fluentDB.transaction( + let sources: SourceEntry[]; + let items: ItemEntry[]; + const transaction = fluentDB.transaction( "r", - db.fluentDB.sources, - db.fluentDB.items, + fluentDB.sources, + fluentDB.items, async () => { - sources = await db.fluentDB.sources.toArray(); - items = await db.fluentDB.items.toArray(); + sources = await fluentDB.sources.toArray(); + items = await fluentDB.items.toArray(); }, ); const output = window.settings.getAll(); @@ -34,4 +80,5 @@ export async function renewAutosave() { "Completed autosave in " + `${Math.round(autosaveEndTime - autosaveStartTime)}ms.`, ); + return AutosaveResponse.SUCCESS; } diff --git a/src/scripts/db.ts b/src/scripts/db.ts index 82d55676..11301e78 100644 --- a/src/scripts/db.ts +++ b/src/scripts/db.ts @@ -3,6 +3,13 @@ import { SourceRule } from "./models/rule"; import { Dexie, type EntityTable, type Transaction } from "dexie"; import type { ThumbnailAttributes } from "./thumb-utils"; +// Used to determine if the database has been initialised +// or corrupted. +export interface IntegrityEntry { + index: number; + timestamp: Date; +} + export interface SourceEntry { sid: number; url: string; @@ -37,9 +44,11 @@ export interface ItemEntry { } export const fluentDB = new Dexie("MainDB") as Dexie & { + integrity: EntityTable; sources: EntityTable; items: EntityTable; }; + fluentDB.version(4).stores({ sources: `++sid, &url`, items: `++iid, source, date, serviceRef`, @@ -77,6 +86,13 @@ fluentDB }) .upgrade(migrateOpenTarget); +// Introduce Database Integrity Validation Table. +fluentDB.version(9).stores({ + integrity: `++index`, + sources: `++sid, &url`, + items: `++iid, source, date, serviceRef`, +}); + export async function calculateItemSize(): Promise { await fluentDB.open(); let result = 0; diff --git a/src/scripts/settings.ts b/src/scripts/settings.ts index 7537dabf..8f029e88 100644 --- a/src/scripts/settings.ts +++ b/src/scripts/settings.ts @@ -201,6 +201,7 @@ export interface DatabaseConfig { globalRules: any[]; } +/** Interface to represent the current Fluentflame settings as serialised. */ export interface FluentflameSettingConfig { version: string; @@ -214,6 +215,13 @@ export interface FluentflameSettingConfig { viewConfig: ViewConfig; } +/** Interface to represent the old Fluent Reader settings that are omitted. */ +interface FluentReaderMigrationEntries { + nedb?: any; + useNeDB?: any; + lovefield?: any; +} + export async function exportAll() { const filters = [{ name: intl.get("app.frData"), extensions: ["frdata"] }]; const write = await window.utils.showSaveDialog( @@ -235,9 +243,11 @@ function isFluentReaderExport(jsonObject: any) { } /** - * Migrate Fluent Reader exports. + * Migrate Fluent Reader exports to Fluentflame Reader. */ -function migrateExport(input: any): FluentflameSettingConfig { +function migrateExport( + input: FluentflameSettingConfig & FluentReaderMigrationEntries, +): FluentflameSettingConfig { console.log("Identified an input from Fluent Reader, trying to migrate..."); return { version: input.version, // This gets updated in update-scripts. @@ -289,6 +299,9 @@ function migrateExport(input: any): FluentflameSettingConfig { }; } +/** + * Ask the user to load a configuration, then load it. + */ export async function importAll(): Promise { const filters = [{ name: intl.get("app.frData"), extensions: ["frdata"] }]; let data = await window.utils.showOpenDialog(filters); @@ -303,13 +316,25 @@ export async function importAll(): Promise { ); if (!confirmed) return true; let configs = JSON.parse(data); + return loadFromConfig(configs); +} +/** + * Load a configuration. + * + * @jsonObject: A configuration, either from Fluentflame or Fluent Reader. + */ +export async function loadFromConfig( + jsonObject: FluentflameSettingConfig & FluentReaderMigrationEntries, +): Promise { await db.fluentDB.sources.clear(); await db.fluentDB.items.clear(); - const newConfigs: FluentflameSettingConfig = isFluentReaderExport(configs) - ? migrateExport(configs) - : configs; + const newConfigs: FluentflameSettingConfig = isFluentReaderExport( + jsonObject, + ) + ? migrateExport(jsonObject) + : (jsonObject as FluentflameSettingConfig); newConfigs.database.sources.forEach((s) => { s.lastFetched = new Date(s.lastFetched); if (!s.textDir) s.textDir = SourceTextDirection.LTR; @@ -324,7 +349,13 @@ export async function importAll(): Promise { }); await db.fluentDB.items.bulkAdd(newConfigs.database.items); + // Ensure we actually update integrity entry. + const integrityPromise = db.fluentDB.integrity.put({ + index: 0, + timestamp: new Date(), + }); delete newConfigs.database; window.settings.setAll(newConfigs); + await integrityPromise; return false; } From 2ca316028b16c855bc3e066f2686b99155775217 Mon Sep 17 00:00:00 2001 From: CrystalSplitter Date: Sun, 11 Oct 2026 15:36:48 -0700 Subject: [PATCH 3/5] Update dialog test --- tests/main/test_pure-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/test_pure-utils.ts b/tests/main/test_pure-utils.ts index b4117cf3..0080ec7b 100644 --- a/tests/main/test_pure-utils.ts +++ b/tests/main/test_pure-utils.ts @@ -6,7 +6,7 @@ describe("dialog trimming", () => { expect(trimContent("M".repeat(1000))).to.equal("M".repeat(511) + "…"); }); it("trims too many lines", () => { - expect(trimContent("M\n".repeat(10))).to.equal( + expect(trimContent("M\n".repeat(15), 512, 5)).to.equal( "M\n".repeat(4) + "…more lines…", ); }); From 0b5a134338f1314613d0227a0d06b170dd93434a Mon Sep 17 00:00:00 2001 From: CrystalSplitter Date: Sun, 11 Oct 2026 16:24:46 -0700 Subject: [PATCH 4/5] Add intl entries for autosave --- src/bridges/utils.ts | 13 +++++++++++-- src/main/utils.ts | 18 ++++++++++-------- src/scripts/autosave.tsx | 15 ++++++++++++++- src/scripts/i18n/en-US.json | 8 ++++++++ 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/bridges/utils.ts b/src/bridges/utils.ts index 9bd4e62c..e22075e1 100644 --- a/src/bridges/utils.ts +++ b/src/bridges/utils.ts @@ -72,8 +72,17 @@ const utilsBridge = { return ipcRenderer.invoke("get-last-autosave-timestamp"); }, - notifyOfRecoveryNeeded: async (): Promise => { - return ipcRenderer.invoke("notify-of-recovery-needed"); + notifyOfRecoveryNeeded: async ( + title: string, + detail: string, + buttons: string[], + ): Promise => { + return ipcRenderer.invoke( + "notify-of-recovery-needed", + title, + detail, + buttons, + ); }, getCacheSize: async (): Promise => { diff --git a/src/main/utils.ts b/src/main/utils.ts index adf4cb50..4dbaf285 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -204,10 +204,12 @@ export function setUtilsListeners(manager: WindowManager) { ipcMain.handle( "notify-of-recovery-needed", - async (): Promise => { - const title = "Database Corruption Detected"; - const detail = - "IndexedDB database appears to be corrupted, and an autosave file has been detected.\n\nRecover from autosave?"; + async ( + _, + title: string, + detail: string, + buttons: string[], + ): Promise => { if (manager.hasWindow()) { const dialogResponse = await dialog.showMessageBox( manager.mainWindow, @@ -216,18 +218,18 @@ export function setUtilsListeners(manager: WindowManager) { title: title, message: title, detail: detail, - buttons: ["Ignore", "Delete autosave", "Recover"], - cancelId: 0, + buttons: buttons, + cancelId: 1, defaultId: 2, }, ); const autosavePath = getAutosavePath(); switch (dialogResponse.response) { case 0: - return { response: "ignored" }; - case 1: await promises.rm(autosavePath); return { response: "deleted" }; + case 1: + return { response: "ignored" }; case 2: const contents = await promises.readFile(autosavePath, { encoding: "utf8", diff --git a/src/scripts/autosave.tsx b/src/scripts/autosave.tsx index d2c1231d..d261d7c1 100644 --- a/src/scripts/autosave.tsx +++ b/src/scripts/autosave.tsx @@ -11,6 +11,8 @@ * on that, and recommend recovery. */ +import intl from "react-intl-universal"; + import { fluentDB, type ItemEntry, type SourceEntry } from "./db"; import { loadFromConfig } from "./settings"; @@ -26,7 +28,18 @@ export async function handleAutosaves(): Promise { const autosaveResp = await renewAutosave(); if (autosaveResp === AutosaveResponse.INTEGRITY_FAILED) { // Dangerous area! Everything in the DB is invalid! - const response = await window.utils.notifyOfRecoveryNeeded(); + const title = intl.get("autosave.corruption.title"); + const detail = intl.get("autosave.corruption.detail"); + const buttons = [ + intl.get("autosave.corruption.delete"), + intl.get("close"), + intl.get("autosave.corruption.recover"), + ]; + const response = await window.utils.notifyOfRecoveryNeeded( + title, + detail, + buttons, + ); if (response.response === "recovered") { // Resets integrity table itself. await loadFromConfig(JSON.parse(response.contents)); diff --git a/src/scripts/i18n/en-US.json b/src/scripts/i18n/en-US.json index dc1930ca..fc6a121c 100644 --- a/src/scripts/i18n/en-US.json +++ b/src/scripts/i18n/en-US.json @@ -81,6 +81,14 @@ "Vertical": "Vertical", "font": "Font" }, + "autosave": { + "corruption": { + "title": "Database corruption detected", + "detail": "IndexedDB database appears to be corrupted, and an autosave file has been detected.\n\nRecover from autosave?", + "delete": "Delete autosave", + "recover": "Recover" + } + }, "context": { "share": "Share", "read": "Read", From 44e37ec27d806c131bb82762f574acbe769cba07 Mon Sep 17 00:00:00 2001 From: CrystalSplitter Date: Sun, 11 Oct 2026 22:39:18 -0700 Subject: [PATCH 5/5] Move some types for autosave feature --- src/bridges/utils.ts | 6 +++--- src/general-types.ts | 5 +++++ src/main/utils.ts | 6 +----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/bridges/utils.ts b/src/bridges/utils.ts index e22075e1..94f471db 100644 --- a/src/bridges/utils.ts +++ b/src/bridges/utils.ts @@ -5,7 +5,7 @@ import { TouchBarTexts, WindowStateListenerType, } from "../schema-types"; -import { CustomArgs } from "../general-types"; +import { type CustomArgs, type AutosaveNotifyResponse } from "../general-types"; import { IObjectWithKey } from "@fluentui/react"; const utilsBridge = { @@ -72,11 +72,11 @@ const utilsBridge = { return ipcRenderer.invoke("get-last-autosave-timestamp"); }, - notifyOfRecoveryNeeded: async ( + notifyOfRecoveryNeeded: ( title: string, detail: string, buttons: string[], - ): Promise => { + ): Promise => { return ipcRenderer.invoke( "notify-of-recovery-needed", title, diff --git a/src/general-types.ts b/src/general-types.ts index e02497da..e4b075a7 100644 --- a/src/general-types.ts +++ b/src/general-types.ts @@ -4,3 +4,8 @@ export type CustomArgs = { // Force using the window frame, regardless of platform. forceFrame: boolean; }; + +export type AutosaveNotifyResponse = + | { response: "ignored" } + | { response: "deleted" } + | { response: "recovered"; contents: string }; diff --git a/src/main/utils.ts b/src/main/utils.ts index 4dbaf285..8628b528 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -16,11 +16,7 @@ import { WindowManager } from "./window"; import { trimContent, trimString } from "./pure-utils"; import { ImageCallbackTypes, TouchBarTexts } from "../schema-types"; import { initMainTouchBar } from "./touchbar"; - -export type AutosaveNotifyResponse = - | { response: "ignored" } - | { response: "deleted" } - | { response: "recovered"; contents: string }; +import { type AutosaveNotifyResponse } from "../general-types"; function getAutosavePath() { const userDataPath = app.getPath("userData");