ask user to install bridge plugin for vault copied to server at runtime
This commit is contained in:
parent
452fb17541
commit
5b01a9cdad
7 changed files with 255 additions and 2 deletions
|
|
@ -1,6 +1,46 @@
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
|
// .ignis metadata helpers
|
||||||
|
async function getIgnisMeta(vaultPath) {
|
||||||
|
const ignisDir = path.join(vaultPath, ".ignis");
|
||||||
|
const metaFile = path.join(ignisDir, "meta.json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fs.promises.readFile(metaFile, "utf-8");
|
||||||
|
return JSON.parse(content);
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setIgnisMeta(vaultPath, data) {
|
||||||
|
const ignisDir = path.join(vaultPath, ".ignis");
|
||||||
|
const metaFile = path.join(ignisDir, "meta.json");
|
||||||
|
|
||||||
|
await fs.promises.mkdir(ignisDir, { recursive: true });
|
||||||
|
await fs.promises.writeFile(metaFile, JSON.stringify(data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkPluginInstalled(vaultPath) {
|
||||||
|
const pluginDir = path.join(
|
||||||
|
vaultPath,
|
||||||
|
".obsidian",
|
||||||
|
"plugins",
|
||||||
|
"ignis-bridge",
|
||||||
|
);
|
||||||
|
const manifestPath = path.join(pluginDir, "manifest.json");
|
||||||
|
const mainPath = path.join(pluginDir, "main.js");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.promises.access(manifestPath);
|
||||||
|
await fs.promises.access(mainPath);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function installPluginInVault(vaultPath) {
|
async function installPluginInVault(vaultPath) {
|
||||||
const obsidianDir = path.join(vaultPath, ".obsidian");
|
const obsidianDir = path.join(vaultPath, ".obsidian");
|
||||||
const pluginDir = path.join(obsidianDir, "plugins", "ignis-bridge");
|
const pluginDir = path.join(obsidianDir, "plugins", "ignis-bridge");
|
||||||
|
|
@ -62,4 +102,10 @@ async function installPluginInAllVaults(vaultRoot) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { installPluginInVault, installPluginInAllVaults };
|
module.exports = {
|
||||||
|
installPluginInVault,
|
||||||
|
installPluginInAllVaults,
|
||||||
|
getIgnisMeta,
|
||||||
|
setIgnisMeta,
|
||||||
|
checkPluginInstalled,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,12 @@ const express = require("express");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const config = require("../config");
|
const config = require("../config");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const {
|
||||||
|
checkPluginInstalled,
|
||||||
|
getIgnisMeta,
|
||||||
|
setIgnisMeta,
|
||||||
|
installPluginInVault,
|
||||||
|
} = require("../install-plugin");
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
@ -19,7 +25,7 @@ router.get("/list", (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /api/vault/info?vault=<id> - returns info for a specific vault
|
// GET /api/vault/info?vault=<id> - returns info for a specific vault
|
||||||
router.get("/info", (req, res) => {
|
router.get("/info", async (req, res) => {
|
||||||
const vaultId = req.query.vault || config.defaultVaultId;
|
const vaultId = req.query.vault || config.defaultVaultId;
|
||||||
const vaultPath = config.getVaultPath(vaultId);
|
const vaultPath = config.getVaultPath(vaultId);
|
||||||
|
|
||||||
|
|
@ -27,12 +33,19 @@ router.get("/info", (req, res) => {
|
||||||
return res.status(404).json({ error: "Vault not found", id: vaultId });
|
return res.status(404).json({ error: "Vault not found", id: vaultId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pluginInstalled = await checkPluginInstalled(vaultPath);
|
||||||
|
const ignisMeta = await getIgnisMeta(vaultPath);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
id: vaultId,
|
id: vaultId,
|
||||||
name: vaultId,
|
name: vaultId,
|
||||||
path: vaultPath,
|
path: vaultPath,
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
version: config.obsidianVersion,
|
version: config.obsidianVersion,
|
||||||
|
ignisPlugin: {
|
||||||
|
installed: pluginInstalled,
|
||||||
|
prompted: ignisMeta.pluginPrompted || false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -143,4 +156,42 @@ router.delete("/remove", async (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /api/vault/install-plugin { vault, dismiss } - install plugin or mark as prompted
|
||||||
|
router.post("/install-plugin", async (req, res) => {
|
||||||
|
const vaultId = req.body?.vault;
|
||||||
|
const dismiss = req.body?.dismiss || false;
|
||||||
|
|
||||||
|
if (!vaultId) {
|
||||||
|
return res.status(400).json({ error: "Missing vault ID" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const vaultPath = config.getVaultPath(vaultId);
|
||||||
|
|
||||||
|
if (!vaultPath) {
|
||||||
|
return res.status(404).json({ error: "Vault not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const meta = await getIgnisMeta(vaultPath);
|
||||||
|
|
||||||
|
if (dismiss) {
|
||||||
|
// User clicked "Don't Ask Again" or "Not Now"
|
||||||
|
meta.pluginPrompted = true;
|
||||||
|
await setIgnisMeta(vaultPath, meta);
|
||||||
|
|
||||||
|
return res.json({ ok: true, prompted: true });
|
||||||
|
} else {
|
||||||
|
// User wants to install the plugin
|
||||||
|
const installed = await installPluginInVault(vaultPath);
|
||||||
|
|
||||||
|
meta.pluginPrompted = true;
|
||||||
|
await setIgnisMeta(vaultPath, meta);
|
||||||
|
|
||||||
|
return res.json({ ok: true, installed, prompted: true });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).json({ error: e.message, code: e.code });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ export const vaultService = {
|
||||||
body: JSON.stringify({ name }),
|
body: JSON.stringify({ name }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._setVaultTrust(name);
|
||||||
|
|
||||||
return this.listVaults();
|
return this.listVaults();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -121,6 +123,10 @@ export const vaultService = {
|
||||||
target.location.href = "/?vault=" + encodeURIComponent(id);
|
target.location.href = "/?vault=" + encodeURIComponent(id);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_setVaultTrust(vaultId, trusted = true) {
|
||||||
|
localStorage.setItem("enable-plugin-" + vaultId, String(trusted));
|
||||||
|
},
|
||||||
|
|
||||||
_migrateLocalStorage(oldId, newId) {
|
_migrateLocalStorage(oldId, newId) {
|
||||||
const pluginKey = "enable-plugin-";
|
const pluginKey = "enable-plugin-";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import * as osShim from "./node/os.js";
|
||||||
import * as netShim from "./node/net.js";
|
import * as netShim from "./node/net.js";
|
||||||
import * as httpShim from "./node/http.js";
|
import * as httpShim from "./node/http.js";
|
||||||
import { vaultService } from "../services/vault-service.js";
|
import { vaultService } from "../services/vault-service.js";
|
||||||
|
import { showPluginInstallDialog } from "../ui/bootstrap.js";
|
||||||
|
|
||||||
const DEBUG = true;
|
const DEBUG = true;
|
||||||
const _accessLog = new Map(); // "module.property" -> count
|
const _accessLog = new Map(); // "module.property" -> count
|
||||||
|
|
@ -208,6 +209,8 @@ window.__currentVaultId =
|
||||||
path: "/",
|
path: "/",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.__ignisPlugin = info.ignisPlugin || null;
|
||||||
|
|
||||||
console.log("[ignis] Vault:", window.__vaultConfig);
|
console.log("[ignis] Vault:", window.__vaultConfig);
|
||||||
console.log("[ignis] Obsidian version:", window.__obsidianVersion);
|
console.log("[ignis] Obsidian version:", window.__obsidianVersion);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -259,4 +262,25 @@ window.__currentVaultId =
|
||||||
|
|
||||||
installRequestUrlShim();
|
installRequestUrlShim();
|
||||||
|
|
||||||
|
// Check if plugin install prompt is needed (once per session, after workspace loads)
|
||||||
|
if (
|
||||||
|
window.__ignisPlugin &&
|
||||||
|
!window.__ignisPlugin.installed &&
|
||||||
|
!window.__ignisPlugin.prompted
|
||||||
|
) {
|
||||||
|
const vaultId = window.__currentVaultId;
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
if (document.querySelector(".workspace")) {
|
||||||
|
observer.disconnect();
|
||||||
|
showPluginInstallDialog(vaultId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log("[ignis] Shim loader initialized");
|
console.log("[ignis] Shim loader initialized");
|
||||||
|
|
|
||||||
36
src/ui/bootstrap.js
vendored
36
src/ui/bootstrap.js
vendored
|
|
@ -48,6 +48,42 @@ export function showConfirmDialog(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showPluginInstallDialog(vaultId) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const dialog = new window.IgnisUI.PluginInstallDialog({
|
||||||
|
target: document.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.$on("install", async () => {
|
||||||
|
try {
|
||||||
|
await fetch("/api/vault/install-plugin", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ vault: vaultId }),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ignis] Failed to install plugin:", e);
|
||||||
|
}
|
||||||
|
dialog.$destroy();
|
||||||
|
resolve("install");
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.$on("dismiss", async () => {
|
||||||
|
try {
|
||||||
|
await fetch("/api/vault/install-plugin", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ vault: vaultId, dismiss: true }),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ignis] Failed to dismiss plugin prompt:", e);
|
||||||
|
}
|
||||||
|
dialog.$destroy();
|
||||||
|
resolve("dismiss");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function showPromptDialog(
|
export function showPromptDialog(
|
||||||
title,
|
title,
|
||||||
label,
|
label,
|
||||||
|
|
|
||||||
89
src/ui/components/layout/PluginInstallDialog.svelte
Normal file
89
src/ui/components/layout/PluginInstallDialog.svelte
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
import Modal from "./Modal.svelte";
|
||||||
|
import Button from "../input/Button.svelte";
|
||||||
|
import { Puzzle, Download, X } from "lucide-svelte";
|
||||||
|
|
||||||
|
export let width = "500px";
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
let modalRef;
|
||||||
|
let installing = false;
|
||||||
|
|
||||||
|
function onInstall() {
|
||||||
|
installing = true;
|
||||||
|
dispatch("install");
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDismiss() {
|
||||||
|
modalRef.dismiss();
|
||||||
|
dispatch("dismiss");
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEscape() {
|
||||||
|
onDismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dismiss() {
|
||||||
|
modalRef.dismiss();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Modal title="Ignis Bridge Plugin" {width} bind:this={modalRef} on:escape={onEscape} closeOnOverlayClick={false}>
|
||||||
|
<svelte:fragment slot="icon">
|
||||||
|
<Puzzle size="1.25rem" />
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<div class="dialog-body">
|
||||||
|
<p class="dialog-message">This vault doesn't have the Ignis Bridge plugin installed.</p>
|
||||||
|
<p class="dialog-description">
|
||||||
|
The plugin adds additional functionality such as file uploads.
|
||||||
|
Obsidian will work without it, but some features will be unavailable.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<svelte:fragment slot="footer">
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<Button variant="secondary" on:click={onDismiss}>
|
||||||
|
<svelte:fragment slot="icon">
|
||||||
|
<X size="0.875rem" />
|
||||||
|
</svelte:fragment>
|
||||||
|
Not Now
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" on:click={onInstall} disabled={installing}>
|
||||||
|
<svelte:fragment slot="icon">
|
||||||
|
<Download size="0.875rem" />
|
||||||
|
</svelte:fragment>
|
||||||
|
{installing ? "Installing..." : "Install Plugin"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</svelte:fragment>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dialog-body {
|
||||||
|
padding: 1.25rem 1.5rem;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-message {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-description {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -2,3 +2,4 @@ export { default as VaultManager } from "./views/VaultManager.svelte";
|
||||||
export { default as MessageDialog } from "./components/layout/MessageDialog.svelte";
|
export { default as MessageDialog } from "./components/layout/MessageDialog.svelte";
|
||||||
export { default as ConfirmDialog } from "./components/layout/ConfirmDialog.svelte";
|
export { default as ConfirmDialog } from "./components/layout/ConfirmDialog.svelte";
|
||||||
export { default as PromptDialog } from "./components/layout/PromptDialog.svelte";
|
export { default as PromptDialog } from "./components/layout/PromptDialog.svelte";
|
||||||
|
export { default as PluginInstallDialog } from "./components/layout/PluginInstallDialog.svelte";
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue