mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
vault management
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
// sandbox → void - open sandbox vault
|
||||
// copy-asar → boolean - install update
|
||||
|
||||
import { showVaultManager } from "../ui/vault-manager.js";
|
||||
|
||||
const listeners = new Map();
|
||||
|
||||
// Sync channel handlers - must return values synchronously
|
||||
@@ -25,7 +27,8 @@ const syncHandlers = {
|
||||
vault: () => window.__vaultConfig || { id: "default-vault", path: "/" },
|
||||
version: () => "1.8.9",
|
||||
"is-dev": () => false,
|
||||
"file-url": () => "/vault-files/",
|
||||
"file-url": () =>
|
||||
"/vault-files/" + encodeURIComponent(window.__currentVaultId || "") + "/",
|
||||
"disable-update": () => true,
|
||||
update: () => "",
|
||||
"disable-gpu": () => false,
|
||||
@@ -36,7 +39,10 @@ const syncHandlers = {
|
||||
window.location.reload();
|
||||
return null;
|
||||
},
|
||||
starter: () => null,
|
||||
starter: () => {
|
||||
showVaultManager();
|
||||
return null;
|
||||
},
|
||||
help: () => {
|
||||
window.open("https://help.obsidian.md/", "_blank");
|
||||
return null;
|
||||
@@ -44,6 +50,55 @@ const syncHandlers = {
|
||||
sandbox: () => null,
|
||||
"copy-asar": () => false,
|
||||
"check-update": () => null,
|
||||
"vault-list": () => {
|
||||
// Starter expects an object keyed by ID: {id: {path, ts, name}}
|
||||
const result = {};
|
||||
for (const v of window.__vaultList || []) {
|
||||
result[v.id] = {
|
||||
path: "/" + v.id,
|
||||
ts: Date.now(),
|
||||
open: v.id === (window.__currentVaultId || ""),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
},
|
||||
"vault-open": (vaultPath, newWindow) => {
|
||||
const id = (vaultPath || "").replace(/^\/+/, "");
|
||||
const vault = (window.__vaultList || []).find((v) => v.id === id);
|
||||
if (!vault && id) {
|
||||
// New vault created by starter - create it on the server
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/api/vault/create", false);
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify({ name: id }));
|
||||
if (xhr.status >= 400) return "Failed to create vault";
|
||||
}
|
||||
// Navigate - use parent if in iframe, otherwise current window
|
||||
const target = window.parent !== window ? window.parent : window;
|
||||
target.location.href = "/?vault=" + encodeURIComponent(id);
|
||||
return true;
|
||||
},
|
||||
"vault-remove": (vaultPath) => {
|
||||
const id = (vaultPath || "").replace(/^\/+/, "");
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
"DELETE",
|
||||
"/api/vault/remove?vault=" + encodeURIComponent(id),
|
||||
false,
|
||||
);
|
||||
xhr.send();
|
||||
return xhr.status < 400;
|
||||
},
|
||||
"vault-move": (oldPath, newPath) => {
|
||||
// Not supported in web context
|
||||
return "Moving vaults is not supported in the web version";
|
||||
},
|
||||
"vault-message": () => null,
|
||||
"get-default-vault-path": () => "/My Vault",
|
||||
"get-documents-path": () => "/",
|
||||
"desktop-dir": () => "/desktop",
|
||||
"documents-dir": () => "/documents",
|
||||
resources: () => "",
|
||||
};
|
||||
|
||||
export const ipcRenderer = {
|
||||
|
||||
@@ -20,18 +20,23 @@ function uint8ToBase64(bytes) {
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
function vaultId() {
|
||||
return window.__currentVaultId || "";
|
||||
}
|
||||
|
||||
async function request(method, endpoint, params = {}) {
|
||||
const url = new URL(API_BASE + endpoint, window.location.origin);
|
||||
|
||||
const options = { method };
|
||||
|
||||
if (method === "GET" || method === "DELETE") {
|
||||
if (vaultId()) url.searchParams.set("vault", vaultId());
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
url.searchParams.set(key, val);
|
||||
}
|
||||
} else {
|
||||
options.headers = { "Content-Type": "application/json" };
|
||||
options.body = JSON.stringify(params);
|
||||
options.body = JSON.stringify({ vault: vaultId(), ...params });
|
||||
}
|
||||
|
||||
const res = await fetch(url.toString(), options);
|
||||
@@ -57,6 +62,7 @@ function requestSync(method, endpoint, params = {}) {
|
||||
const url = new URL(API_BASE + endpoint, window.location.origin);
|
||||
|
||||
if (method === "GET") {
|
||||
if (vaultId()) url.searchParams.set("vault", vaultId());
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
url.searchParams.set(key, val);
|
||||
}
|
||||
@@ -67,7 +73,7 @@ function requestSync(method, endpoint, params = {}) {
|
||||
|
||||
if (method !== "GET") {
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify(params));
|
||||
xhr.send(JSON.stringify({ vault: vaultId(), ...params }));
|
||||
} else {
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
@@ -114,8 +114,14 @@ if (typeof window.Buffer === "undefined") {
|
||||
}
|
||||
|
||||
// Prevent app.js from closing the window (browser blocks this anyway, but suppress the error)
|
||||
// In an iframe (starter modal), close the modal overlay instead.
|
||||
const _origClose = window.close;
|
||||
window.close = function () {
|
||||
if (window.parent !== window) {
|
||||
const modal = window.parent.document.getElementById("ignis-starter-modal");
|
||||
if (modal) modal.remove();
|
||||
return;
|
||||
}
|
||||
console.log("[obsidian-bridge] window.close() blocked");
|
||||
};
|
||||
|
||||
@@ -132,17 +138,60 @@ window.addEventListener(
|
||||
true,
|
||||
);
|
||||
|
||||
// Read vault ID from URL query param (?vault=my-notes)
|
||||
const _urlParams = new URLSearchParams(window.location.search);
|
||||
window.__currentVaultId = _urlParams.get("vault") || "";
|
||||
|
||||
// Fetch vault config from server synchronously (before metadata cache)
|
||||
(function initVaultConfig() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
? "?vault=" + encodeURIComponent(window.__currentVaultId)
|
||||
: "";
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/api/vault/info" + vaultParam, false);
|
||||
xhr.send();
|
||||
if (xhr.status === 200) {
|
||||
const info = JSON.parse(xhr.responseText);
|
||||
window.__currentVaultId = info.id;
|
||||
window.__vaultConfig = {
|
||||
id: info.id,
|
||||
path: "/",
|
||||
};
|
||||
console.log("[obsidian-bridge] Vault:", window.__vaultConfig);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[obsidian-bridge] Failed to fetch vault config:", e);
|
||||
}
|
||||
})();
|
||||
|
||||
// Fetch vault list for IPC handlers
|
||||
(function initVaultList() {
|
||||
try {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/api/vault/list", false);
|
||||
xhr.send();
|
||||
if (xhr.status === 200) {
|
||||
window.__vaultList = JSON.parse(xhr.responseText);
|
||||
}
|
||||
} catch (e) {
|
||||
window.__vaultList = [];
|
||||
}
|
||||
})();
|
||||
|
||||
// Pre-populate fs metadata cache synchronously before app.js runs.
|
||||
// This ensures existsSync() works for the vault path during startup.
|
||||
(function initMetadataCache() {
|
||||
try {
|
||||
const vaultParam = window.__currentVaultId
|
||||
? "?vault=" + encodeURIComponent(window.__currentVaultId)
|
||||
: "";
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/api/fs/tree", false); // synchronous
|
||||
xhr.open("GET", "/api/fs/tree" + vaultParam, false);
|
||||
xhr.send();
|
||||
if (xhr.status === 200) {
|
||||
const tree = JSON.parse(xhr.responseText);
|
||||
fsShim._metadataCache.populate(tree);
|
||||
// Also add the root path itself
|
||||
fsShim._metadataCache.set("", { type: "directory" });
|
||||
fsShim._metadataCache.set("/", { type: "directory" });
|
||||
console.log(
|
||||
@@ -161,24 +210,4 @@ window.addEventListener(
|
||||
}
|
||||
})();
|
||||
|
||||
// Fetch vault config from server synchronously
|
||||
(function initVaultConfig() {
|
||||
try {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/api/vault/info", false); // synchronous
|
||||
xhr.send();
|
||||
if (xhr.status === 200) {
|
||||
const info = JSON.parse(xhr.responseText);
|
||||
// Set the vault config that sendSync('vault') will return
|
||||
window.__vaultConfig = {
|
||||
id: info.name || "default-vault",
|
||||
path: "/",
|
||||
};
|
||||
console.log("[obsidian-bridge] Vault config:", window.__vaultConfig);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[obsidian-bridge] Failed to fetch vault config:", e);
|
||||
}
|
||||
})();
|
||||
|
||||
console.log("[obsidian-bridge] Shim loader initialized");
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
// Path shim - delegates to path-browserify (bundled via esbuild alias)
|
||||
// Configured for posix mode since vault paths are normalized to forward slashes.
|
||||
|
||||
import pathBrowserify from 'path';
|
||||
import pathBrowserify from "path";
|
||||
|
||||
export const pathShim = pathBrowserify;
|
||||
const _origBasename = pathBrowserify.basename;
|
||||
|
||||
export const pathShim = {
|
||||
...pathBrowserify,
|
||||
basename(p, ext) {
|
||||
// Vault root "/" should return the vault name for display purposes
|
||||
if (p === "/" && window.__currentVaultId) {
|
||||
return window.__currentVaultId;
|
||||
}
|
||||
return _origBasename(p, ext);
|
||||
},
|
||||
};
|
||||
|
||||
163
shims/ui/vault-manager.js
Normal file
163
shims/ui/vault-manager.js
Normal file
@@ -0,0 +1,163 @@
|
||||
// Custom vault manager modal - vanilla JS (will migrate to Svelte later)
|
||||
// Shows list of vaults, create new, delete, switch.
|
||||
|
||||
export function showVaultManager() {
|
||||
if (!document.querySelector(".workspace")) return;
|
||||
if (document.getElementById("ignis-starter-modal")) return;
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.id = "ignis-starter-modal";
|
||||
overlay.style.cssText =
|
||||
"position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.6);" +
|
||||
"display:flex;align-items:center;justify-content:center;font-family:var(--font-interface);";
|
||||
|
||||
const modal = document.createElement("div");
|
||||
modal.style.cssText =
|
||||
"background:var(--background-primary);color:var(--text-normal);border-radius:12px;" +
|
||||
"padding:24px;width:min(480px,90vw);max-height:80vh;overflow-y:auto;box-shadow:0 16px 48px rgba(0,0,0,0.4);";
|
||||
|
||||
const title = document.createElement("h2");
|
||||
title.textContent = "Vaults";
|
||||
title.style.cssText = "margin:0 0 16px 0;font-size:18px;font-weight:600;";
|
||||
modal.appendChild(title);
|
||||
|
||||
const listEl = document.createElement("div");
|
||||
listEl.style.cssText =
|
||||
"display:flex;flex-direction:column;gap:4px;margin-bottom:16px;";
|
||||
modal.appendChild(listEl);
|
||||
|
||||
function renderList() {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/api/vault/list", false);
|
||||
xhr.send();
|
||||
const vaults = xhr.status === 200 ? JSON.parse(xhr.responseText) : [];
|
||||
listEl.innerHTML = "";
|
||||
if (vaults.length === 0) {
|
||||
const empty = document.createElement("div");
|
||||
empty.textContent = "No vaults yet. Create one below.";
|
||||
empty.style.cssText = "color:var(--text-muted);padding:12px 0;";
|
||||
listEl.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
for (const v of vaults) {
|
||||
const isCurrent = v.id === (window.__currentVaultId || "");
|
||||
|
||||
const row = document.createElement("div");
|
||||
row.style.cssText =
|
||||
"display:flex;align-items:center;justify-content:space-between;" +
|
||||
"padding:8px 12px;border-radius:6px;cursor:pointer;" +
|
||||
"background:var(--background-secondary);";
|
||||
row.addEventListener(
|
||||
"mouseenter",
|
||||
() => (row.style.background = "var(--background-modifier-hover)"),
|
||||
);
|
||||
row.addEventListener(
|
||||
"mouseleave",
|
||||
() => (row.style.background = "var(--background-secondary)"),
|
||||
);
|
||||
|
||||
const name = document.createElement("span");
|
||||
name.textContent = v.name;
|
||||
name.style.cssText = "font-weight:500;flex:1;";
|
||||
if (isCurrent) {
|
||||
const badge = document.createElement("span");
|
||||
badge.textContent = " current";
|
||||
badge.style.cssText =
|
||||
"font-size:11px;color:var(--text-muted);font-weight:400;";
|
||||
name.appendChild(badge);
|
||||
}
|
||||
|
||||
const del = document.createElement("button");
|
||||
del.textContent = "Delete";
|
||||
del.style.cssText =
|
||||
"background:none;border:1px solid var(--background-modifier-border);" +
|
||||
"color:var(--text-muted);border-radius:4px;padding:2px 8px;font-size:12px;cursor:pointer;";
|
||||
del.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (
|
||||
!confirm(
|
||||
'Delete vault "' + v.name + '"? This removes all files.',
|
||||
)
|
||||
)
|
||||
return;
|
||||
const xhr2 = new XMLHttpRequest();
|
||||
xhr2.open(
|
||||
"DELETE",
|
||||
"/api/vault/remove?vault=" + encodeURIComponent(v.id),
|
||||
false,
|
||||
);
|
||||
xhr2.send();
|
||||
renderList();
|
||||
if (isCurrent) window.location.href = "/";
|
||||
});
|
||||
|
||||
row.addEventListener("click", () => {
|
||||
if (isCurrent) {
|
||||
overlay.remove();
|
||||
return;
|
||||
}
|
||||
window.location.href = "/?vault=" + encodeURIComponent(v.id);
|
||||
});
|
||||
|
||||
row.appendChild(name);
|
||||
row.appendChild(del);
|
||||
listEl.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
renderList();
|
||||
|
||||
const form = document.createElement("div");
|
||||
form.style.cssText = "display:flex;gap:8px;";
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.placeholder = "New vault name...";
|
||||
input.style.cssText =
|
||||
"flex:1;padding:6px 10px;border-radius:6px;border:1px solid var(--background-modifier-border);" +
|
||||
"background:var(--background-secondary);color:var(--text-normal);font-size:14px;outline:none;";
|
||||
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = "Create";
|
||||
btn.style.cssText =
|
||||
"padding:6px 16px;border-radius:6px;border:none;" +
|
||||
"background:var(--interactive-accent);color:var(--text-on-accent);font-size:14px;cursor:pointer;font-weight:500;";
|
||||
|
||||
function createVault() {
|
||||
const name = input.value.trim();
|
||||
if (!name) return;
|
||||
const xhr3 = new XMLHttpRequest();
|
||||
xhr3.open("POST", "/api/vault/create", false);
|
||||
xhr3.setRequestHeader("Content-Type", "application/json");
|
||||
xhr3.send(JSON.stringify({ name }));
|
||||
if (xhr3.status >= 400) {
|
||||
alert(
|
||||
"Failed to create vault: " +
|
||||
(JSON.parse(xhr3.responseText).error || "Unknown error"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
input.value = "";
|
||||
window.location.href = "/?vault=" + encodeURIComponent(name);
|
||||
}
|
||||
|
||||
btn.addEventListener("click", createVault);
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") createVault();
|
||||
});
|
||||
|
||||
form.appendChild(input);
|
||||
form.appendChild(btn);
|
||||
modal.appendChild(form);
|
||||
|
||||
overlay.addEventListener("click", (e) => {
|
||||
if (e.target === overlay) overlay.remove();
|
||||
});
|
||||
overlay.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") overlay.remove();
|
||||
});
|
||||
|
||||
overlay.appendChild(modal);
|
||||
document.body.appendChild(overlay);
|
||||
input.focus();
|
||||
}
|
||||
Reference in New Issue
Block a user