diff --git a/Dockerfile b/Dockerfile index 8717b40..57aad64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,6 +27,7 @@ RUN npm ci --omit=dev --ignore-scripts COPY server/ ./server/ COPY scripts/ ./scripts/ COPY images/ ./images/ +COPY plugin/ ./plugin/ COPY --from=build /build/dist ./dist RUN chmod +x /app/scripts/entrypoint.sh diff --git a/plugin/main.js b/plugin/main.js new file mode 100644 index 0000000..2e89a7e --- /dev/null +++ b/plugin/main.js @@ -0,0 +1,79 @@ +const { Plugin, Notice, TFolder } = require("obsidian"); + +class IgnisBridgePlugin extends Plugin { + async onload() { + console.log("[ignis-bridge] Plugin loaded"); + + this.addRibbonIcon("upload", "Upload file", () => { + this.showFilePicker(); + }); + + this.registerEvent( + this.app.workspace.on("file-menu", (menu, file) => { + if (file instanceof TFolder) { + menu.addItem((item) => { + item + .setTitle("Upload file") + .setIcon("upload") + .onClick(() => { + this.showFilePicker(file); + }); + }); + } + }) + ); + } + + showFilePicker(targetFolder = null) { + const input = document.createElement("input"); + input.type = "file"; + input.multiple = true; + input.style.display = "none"; + + input.addEventListener("change", async () => { + const files = Array.from(input.files || []); + if (files.length === 0) return; + + const folder = targetFolder || this.app.vault.getRoot(); + const folderPath = folder.path; + + new Notice(`Uploading ${files.length} file(s)...`); + + let successCount = 0; + let errorCount = 0; + + for (const file of files) { + try { + const arrayBuffer = await file.arrayBuffer(); + const targetPath = folderPath + ? `${folderPath}/${file.name}` + : file.name; + + await this.app.vault.createBinary(targetPath, arrayBuffer); + successCount++; + } catch (e) { + console.error("[ignis-bridge] Upload failed:", file.name, e); + errorCount++; + } + } + + if (successCount > 0) { + new Notice(`Uploaded ${successCount} file(s) successfully`); + } + if (errorCount > 0) { + new Notice(`Failed to upload ${errorCount} file(s)`, 5000); + } + + input.remove(); + }); + + document.body.appendChild(input); + input.click(); + } + + onunload() { + console.log("[ignis-bridge] Plugin unloaded"); + } +} + +module.exports = IgnisBridgePlugin; diff --git a/plugin/manifest.json b/plugin/manifest.json new file mode 100644 index 0000000..2640f3c --- /dev/null +++ b/plugin/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "ignis-bridge", + "name": "Ignis Bridge", + "version": "1.0.0", + "minAppVersion": "1.0.0", + "description": "Upload files from your device to the vault", + "author": "Ignis", + "authorUrl": "https://github.com/Nystik-gh/ignis", + "isDesktopOnly": false +} diff --git a/server/index.js b/server/index.js index 0d3fab0..30785a9 100644 --- a/server/index.js +++ b/server/index.js @@ -2,6 +2,7 @@ const express = require("express"); const path = require("path"); const config = require("./config"); const { setupWebSocket } = require("./ws"); +const { installPluginInAllVaults } = require("./install-plugin"); const ANSI_RED = "\x1b[31m"; const ANSI_YELLOW = "\x1b[33m"; @@ -73,10 +74,12 @@ app.use(express.static(path.join(__dirname, "..", "dist"))); app.use(express.static(config.obsidianAssetsPath)); -const server = app.listen(config.port, () => { +const server = app.listen(config.port, async () => { console.log(`[ignis] Server running on http://localhost:${config.port}`); console.log(`[ignis] Vault root: ${config.vaultRoot}`); console.log(`[ignis] Vaults: ${Object.keys(config.vaults).join(", ")}`); + + await installPluginInAllVaults(config.vaultRoot); }); setupWebSocket(server); diff --git a/server/install-plugin.js b/server/install-plugin.js new file mode 100644 index 0000000..c8cbb81 --- /dev/null +++ b/server/install-plugin.js @@ -0,0 +1,65 @@ +const fs = require("fs"); +const path = require("path"); + +async function installPluginInVault(vaultPath) { + const obsidianDir = path.join(vaultPath, ".obsidian"); + const pluginDir = path.join(obsidianDir, "plugins", "ignis-bridge"); + + if (!(await fs.promises.stat(obsidianDir).catch(() => null))) { + return false; + } + + if (!(await fs.promises.stat(pluginDir).catch(() => null))) { + await fs.promises.mkdir(pluginDir, { recursive: true }); + + const pluginSrcDir = path.join(__dirname, "..", "plugin"); + await fs.promises.copyFile( + path.join(pluginSrcDir, "manifest.json"), + path.join(pluginDir, "manifest.json"), + ); + await fs.promises.copyFile( + path.join(pluginSrcDir, "main.js"), + path.join(pluginDir, "main.js"), + ); + } + + const pluginsConfig = path.join(obsidianDir, "community-plugins.json"); + let plugins = []; + + if (await fs.promises.stat(pluginsConfig).catch(() => null)) { + try { + plugins = JSON.parse(await fs.promises.readFile(pluginsConfig, "utf8")); + } catch (e) { + plugins = []; + } + } + + if (!plugins.includes("ignis-bridge")) { + plugins.push("ignis-bridge"); + await fs.promises.writeFile(pluginsConfig, JSON.stringify(plugins)); + return true; + } + + return false; +} + +async function installPluginInAllVaults(vaultRoot) { + if (!(await fs.promises.stat(vaultRoot).catch(() => null))) { + return; + } + + const entries = await fs.promises.readdir(vaultRoot, { withFileTypes: true }); + + for (const entry of entries) { + if (entry.isDirectory()) { + const vaultPath = path.join(vaultRoot, entry.name); + const installed = await installPluginInVault(vaultPath); + + if (installed) { + console.log(`[ignis] Installed plugin in vault: ${entry.name}`); + } + } + } +} + +module.exports = { installPluginInVault, installPluginInAllVaults }; diff --git a/server/routes/vault.js b/server/routes/vault.js index 8fa93d7..94c5105 100644 --- a/server/routes/vault.js +++ b/server/routes/vault.js @@ -52,6 +52,31 @@ router.post("/create", async (req, res) => { recursive: false, }); + // Install ignis-bridge plugin + const pluginDir = path.join( + vaultPath, + ".obsidian", + "plugins", + "ignis-bridge", + ); + await fs.promises.mkdir(pluginDir, { recursive: true }); + + const pluginSrcDir = path.join(__dirname, "..", "..", "plugin"); + await fs.promises.copyFile( + path.join(pluginSrcDir, "manifest.json"), + path.join(pluginDir, "manifest.json"), + ); + await fs.promises.copyFile( + path.join(pluginSrcDir, "main.js"), + path.join(pluginDir, "main.js"), + ); + + // Enable the plugin + await fs.promises.writeFile( + path.join(vaultPath, ".obsidian", "community-plugins.json"), + JSON.stringify(["ignis-bridge"]), + ); + config.refreshVaults(); res.json({ ok: true, id: name, path: vaultPath });