import "dotenv/config"; import { electronApp, optimizer, is } from "@electron-toolkit/utils"; import { app, ipcMain, BrowserWindow, shell } from "electron"; import filesystemPlugin from "@takerofnotes/plugin-filesystem"; import supabasePlugin from "@takerofnotes/plugin-supabase"; import "libsodium-wrappers"; import "uuid"; import "flexsearch"; import fs from "fs/promises"; import path, { join } from "path"; import __cjs_mod__ from "node:module"; const __filename = import.meta.filename; const __dirname = import.meta.dirname; const require2 = __cjs_mod__.createRequire(import.meta.url); class PluginRegistry { constructor(environment = "web") { this.plugins = /* @__PURE__ */ new Map(); this.environment = environment; } register(plugin) { if (!plugin.id) { throw new Error("Plugin must have an id"); } const environments = plugin.environments || ["electron", "web"]; if (!environments.includes(this.environment)) { return; } this.plugins.set(plugin.id, plugin); } get(id) { return this.plugins.get(id); } list() { return Array.from(this.plugins.values()).map((plugin) => ({ id: plugin.id, name: plugin.name, description: plugin.description, configSchema: plugin.configSchema })); } } function createIpcStorage() { return { async load() { return await window.api.getConfig(); }, async save(data) { await window.api.setConfig(data); } }; } function getDefaultConfig() { return { activeAdapter: "supabase", adapters: { supabase: { supabaseUrl: "https://example.supabase.co", supabaseKey: "", bucket: "notes" } }, theme: "light" }; } let config = null; let configResolve = null; const configPromise = new Promise((resolve) => { configResolve = resolve; }); function createConfigManager(environment, customStorage = null) { let storage; if (customStorage) { storage = customStorage; } else { storage = createIpcStorage(); } const loadConfig = async () => { if (config !== null) { return config; } const stored = await storage.load(); config = stored || getDefaultConfig(); if (!stored) { await storage.save(config); } configResolve(config); return config; }; const writeConfig = async (newConfig) => { await storage.save(newConfig); config = newConfig; }; const getConfig = () => { if (config !== null) { return config; } return configPromise; }; const setConfig = async (newConfig) => { config = newConfig; await writeConfig(newConfig); }; const refreshConfig = async () => { config = await storage.load(); configResolve(config); }; return { loadConfig, getConfig, setConfig, refreshConfig }; } const USER_DATA_STRING = "__DEFAULT_USER_DATA__"; function createNodeStorage(defaultPlugin = null) { const configPath = path.join(app.getPath("userData"), "config.json"); function resolveDefaults(obj) { if (Array.isArray(obj)) { return obj.map(resolveDefaults); } else if (obj && typeof obj === "object") { const resolved = {}; for (const [key, value] of Object.entries(obj)) { resolved[key] = resolveDefaults(value); } return resolved; } else if (typeof obj === "string" && obj.includes(USER_DATA_STRING)) { return obj.replace(USER_DATA_STRING, app.getPath("userData")); } return obj; } return { async load() { let parsed; try { const raw = await fs.readFile(configPath, "utf8"); parsed = JSON.parse(raw); } catch { parsed = null; } if (!parsed || !parsed.activeAdapter) { const defaultConfig = {}; if (defaultPlugin) { for (const field of defaultPlugin.configSchema) { defaultConfig[field.key] = field.default ?? null; } } parsed = { ...parsed ? parsed : {}, activeAdapter: defaultPlugin?.id || "supabase", adapters: {}, theme: "dark" }; if (defaultPlugin) { parsed.adapters[defaultPlugin.id] = defaultConfig; } await this.save(parsed); } else { parsed.adapters = resolveDefaults(parsed.adapters); } return parsed; }, async save(configObject) { const dir = path.dirname(configPath); await fs.mkdir(dir, { recursive: true }); const resolved = { ...configObject, adapters: resolveDefaults(configObject.adapters) }; await fs.writeFile( configPath, JSON.stringify(resolved, null, 2), "utf8" ); } }; } const DEFAULT_WINDOW_SIZE = { width: 354, height: 549 }; const DEFAULT_MOVE_WINDOW_SIZE = { width: 708, height: 549 }; const preloadPath = join(__dirname, "../preload/index.mjs"); const rendererPath = join(__dirname, "../renderer/index.html"); let activeAdapter = null; function createWindow() { const mainWindow = new BrowserWindow({ width: DEFAULT_WINDOW_SIZE.width, height: DEFAULT_WINDOW_SIZE.height, show: false, autoHideMenuBar: true, webPreferences: { preload: preloadPath, sandbox: false } }); mainWindow.on("ready-to-show", () => { mainWindow.show(); }); mainWindow.webContents.setWindowOpenHandler((details) => { shell.openExternal(details.url); return { action: "deny" }; }); if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]); } else { mainWindow.loadFile(rendererPath); } } function createNoteWindow(noteId) { const noteWindow = new BrowserWindow({ width: DEFAULT_WINDOW_SIZE.width, height: DEFAULT_WINDOW_SIZE.height, autoHideMenuBar: true, webPreferences: { preload: preloadPath, contextIsolation: true, nodeIntegration: false, sandbox: false } }); if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { noteWindow.loadURL( `${process.env["ELECTRON_RENDERER_URL"]}/#/note/${noteId}` ); } else { noteWindow.loadFile(rendererPath, { hash: `/note/${noteId}` }); } } app.whenReady().then(async () => { ipcMain.on("open-note-window", (_, noteId) => { createNoteWindow(noteId); }); const broadcastNoteChange = (event, data) => { BrowserWindow.getAllWindows().forEach((win) => { win.webContents.send(event, data); }); }; const registry = new PluginRegistry("electron"); registry.register(filesystemPlugin); registry.register(supabasePlugin); const nodeStorage = createNodeStorage(filesystemPlugin); const configManager = createConfigManager("electron", nodeStorage); const initialConfig = await configManager.loadConfig(); const setActivePlugin = async (pluginId) => { const currentConfig = await configManager.loadConfig(); await configManager.setConfig({ ...currentConfig, activeAdapter: pluginId }); const plugin = registry.get(pluginId); const adapterConfig = currentConfig.adapters[pluginId] || {}; activeAdapter = plugin.createAdapter(adapterConfig); await activeAdapter.init(); ipcMain.removeHandler("adapter:call"); ipcMain.handle("adapter:call", async (_, method, args) => { if (!activeAdapter[method]) { throw new Error(`Invalid adapter method: ${method}`); } return await activeAdapter[method](...args); }); broadcastNoteChange("plugin-changed", pluginId); return true; }; await setActivePlugin(initialConfig.activeAdapter); ipcMain.handle("getConfig", async () => { return await configManager.loadConfig(); }); ipcMain.handle("setConfig", async (_, newConfig) => { await configManager.setConfig(newConfig); }); ipcMain.handle("listPlugins", async () => { return registry.list(); }); ipcMain.handle("setActivePlugin", async (_, pluginId) => { return await setActivePlugin(pluginId); }); ipcMain.on("note-changed", (_, event, data) => { broadcastNoteChange(event, data); }); ipcMain.handle("move-opened", (_) => { const activeWindow = BrowserWindow.getFocusedWindow(); const windowSize = activeWindow.getSize(); if (windowSize[0] < DEFAULT_MOVE_WINDOW_SIZE.width) { activeWindow.setSize( DEFAULT_MOVE_WINDOW_SIZE.width, DEFAULT_MOVE_WINDOW_SIZE.height ); } }); ipcMain.handle("move-closed", (_) => { const activeWindow = BrowserWindow.getFocusedWindow(); const windowSize = activeWindow.getSize(); if (windowSize[0] === 708) { activeWindow.setSize( DEFAULT_WINDOW_SIZE.width, DEFAULT_WINDOW_SIZE.height ); } }); electronApp.setAppUserModelId("com.electron"); app.on("browser-window-created", (_, window2) => { optimizer.watchWindowShortcuts(window2); }); createWindow(); app.on("activate", function() { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on("window-all-closed", () => { if (process.platform !== "darwin") { app.quit(); } });