mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
expose Ignis API, implement shared ws client
This commit is contained in:
@@ -95,64 +95,44 @@ function display(containerEl, app) {
|
||||
addServerStatus(containerEl);
|
||||
}
|
||||
|
||||
function getWsStatus() {
|
||||
const ws = window.__ignisWs;
|
||||
const STATUS_LABELS = {
|
||||
open: "Connected",
|
||||
connecting: "Connecting...",
|
||||
closed: "Disconnected",
|
||||
};
|
||||
|
||||
if (!ws) {
|
||||
return "disconnected";
|
||||
}
|
||||
|
||||
switch (ws.readyState) {
|
||||
case WebSocket.CONNECTING:
|
||||
return "connecting";
|
||||
case WebSocket.OPEN:
|
||||
return "connected";
|
||||
case WebSocket.CLOSING:
|
||||
case WebSocket.CLOSED:
|
||||
return "disconnected";
|
||||
default:
|
||||
return "disconnected";
|
||||
}
|
||||
}
|
||||
|
||||
function statusLabel(status) {
|
||||
switch (status) {
|
||||
case "connected":
|
||||
return "Connected";
|
||||
case "connecting":
|
||||
return "Connecting...";
|
||||
case "disconnected":
|
||||
return "Disconnected";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
const STATUS_DOT_CLASSES = {
|
||||
open: "ignis-status-connected",
|
||||
connecting: "ignis-status-connecting",
|
||||
closed: "ignis-status-disconnected",
|
||||
};
|
||||
|
||||
function addServerStatus(containerEl) {
|
||||
const status = getWsStatus();
|
||||
const ws = window.__ignis.ws;
|
||||
|
||||
const setting = new Setting(containerEl).setName("Server status");
|
||||
|
||||
const dotEl = setting.controlEl.createEl("span", {
|
||||
cls: `ignis-status-dot ignis-status-${status}`,
|
||||
cls: "ignis-status-dot",
|
||||
});
|
||||
|
||||
const labelEl = setting.controlEl.createEl("span", {
|
||||
text: statusLabel(status),
|
||||
cls: "ignis-status-label",
|
||||
});
|
||||
|
||||
const update = () => {
|
||||
const s = getWsStatus();
|
||||
dotEl.className = `ignis-status-dot ignis-status-${s}`;
|
||||
labelEl.textContent = statusLabel(s);
|
||||
};
|
||||
function render(state) {
|
||||
dotEl.className = `ignis-status-dot ${STATUS_DOT_CLASSES[state] || STATUS_DOT_CLASSES.closed}`;
|
||||
labelEl.textContent = STATUS_LABELS[state] || STATUS_LABELS.closed;
|
||||
}
|
||||
|
||||
const pollInterval = setInterval(update, 3000);
|
||||
render(ws.isOpen() ? "open" : "closed");
|
||||
|
||||
const unsub = ws.onStateChange(render);
|
||||
|
||||
// Detach when the settings tab DOM goes away.
|
||||
const observer = new MutationObserver(() => {
|
||||
if (!containerEl.isConnected) {
|
||||
clearInterval(pollInterval);
|
||||
unsub();
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ const generalTab = require("./general-tab");
|
||||
const serverPluginsTab = require("./server-plugins-tab");
|
||||
const { createNavEl, createTab, createGroup } = require("./settings-ui");
|
||||
const {
|
||||
allIgnisNavEls,
|
||||
setupPluginTabs,
|
||||
reconcilePluginTabs,
|
||||
hideIgnisFromCommunityPlugins,
|
||||
@@ -24,10 +25,6 @@ function removeExistingIgnisGroups(tabHeadersEl) {
|
||||
}
|
||||
}
|
||||
|
||||
// All ignis-managed nav elements (both Ignis group and Ignis Core Plugins group).
|
||||
// Collected here so the openTab patch can manage is-active across all of them.
|
||||
const allIgnisNavEls = new Map(); // tab id -> nav element
|
||||
|
||||
function replaceInstallerVersionRow(setting, ignisVersion) {
|
||||
const container = setting.tabContentContainer || setting.contentEl;
|
||||
|
||||
@@ -117,7 +114,7 @@ function injectIgnisSettings(setting, app, plugin) {
|
||||
setting.tabHeadersEl.appendChild(corePlugins.group);
|
||||
|
||||
hideIgnisFromCommunityPlugins(setting);
|
||||
setupPluginTabs(setting, corePlugins.items, allIgnisNavEls);
|
||||
setupPluginTabs(setting, corePlugins.items);
|
||||
}
|
||||
|
||||
function patchSettingsModal(plugin) {
|
||||
@@ -142,7 +139,4 @@ function unpatchSettingsModal(plugin) {
|
||||
clearOwnedPluginIds();
|
||||
}
|
||||
|
||||
window.__ignisReconcilePluginTabs = (setting) =>
|
||||
reconcilePluginTabs(setting, allIgnisNavEls);
|
||||
|
||||
module.exports = { patchSettingsModal, unpatchSettingsModal, reconcilePluginTabs };
|
||||
|
||||
@@ -2,10 +2,14 @@ const { setIcon } = require("obsidian");
|
||||
const { findGroupByTitle } = require("./settings-ui");
|
||||
const { isIgnisPlugin } = require("../plugin-registry");
|
||||
|
||||
// All ignis-managed nav elements (both Ignis group and Ignis Core Plugins group).
|
||||
// Shared with inject.js so the openTab patch can manage is-active across all of them.
|
||||
const allIgnisNavEls = new Map(); // tab id -> nav element
|
||||
|
||||
// Tracks which plugin IDs have nav items we created.
|
||||
const ownedPluginIds = new Set();
|
||||
|
||||
function addPluginNavItem(pluginId, setting, corePluginsItems, ignisNavEls) {
|
||||
function addPluginNavItem(pluginId, setting, corePluginsItems) {
|
||||
const tab = setting.pluginTabs.find((t) => t.id === pluginId);
|
||||
|
||||
if (!tab) {
|
||||
@@ -41,16 +45,16 @@ function addPluginNavItem(pluginId, setting, corePluginsItems, ignisNavEls) {
|
||||
|
||||
corePluginsItems.appendChild(nav);
|
||||
ownedPluginIds.add(pluginId);
|
||||
ignisNavEls.set(pluginId, nav);
|
||||
allIgnisNavEls.set(pluginId, nav);
|
||||
}
|
||||
|
||||
function removePluginNavItem(pluginId, ignisNavEls) {
|
||||
const nav = ignisNavEls.get(pluginId);
|
||||
function removePluginNavItem(pluginId) {
|
||||
const nav = allIgnisNavEls.get(pluginId);
|
||||
|
||||
if (nav && ownedPluginIds.has(pluginId)) {
|
||||
nav.remove();
|
||||
ownedPluginIds.delete(pluginId);
|
||||
ignisNavEls.delete(pluginId);
|
||||
allIgnisNavEls.delete(pluginId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,11 +120,11 @@ function hideIgnisNavFromCommunityGroup(setting) {
|
||||
communityGroup.style.display = hasVisible ? "" : "none";
|
||||
}
|
||||
|
||||
function hideCorePluginsGroupIfEmpty(ignisNavEls) {
|
||||
function hideCorePluginsGroupIfEmpty() {
|
||||
let hasConnected = false;
|
||||
|
||||
for (const id of ownedPluginIds) {
|
||||
const nav = ignisNavEls.get(id);
|
||||
const nav = allIgnisNavEls.get(id);
|
||||
|
||||
if (nav?.isConnected) {
|
||||
hasConnected = true;
|
||||
@@ -140,15 +144,15 @@ function hideCorePluginsGroupIfEmpty(ignisNavEls) {
|
||||
}
|
||||
}
|
||||
|
||||
function setupPluginTabs(setting, corePluginsItems, ignisNavEls) {
|
||||
function setupPluginTabs(setting, corePluginsItems) {
|
||||
for (const tab of setting.pluginTabs) {
|
||||
if (isIgnisPlugin(tab.id) && tab.id !== "ignis-bridge") {
|
||||
addPluginNavItem(tab.id, setting, corePluginsItems, ignisNavEls);
|
||||
addPluginNavItem(tab.id, setting, corePluginsItems);
|
||||
}
|
||||
}
|
||||
|
||||
hideIgnisNavFromCommunityGroup(setting);
|
||||
hideCorePluginsGroupIfEmpty(ignisNavEls);
|
||||
hideCorePluginsGroupIfEmpty();
|
||||
|
||||
const communityGroup = findGroupByTitle(
|
||||
setting.tabHeadersEl,
|
||||
@@ -159,12 +163,12 @@ function setupPluginTabs(setting, corePluginsItems, ignisNavEls) {
|
||||
const observer = new MutationObserver(() => {
|
||||
for (const tab of setting.pluginTabs) {
|
||||
if (isIgnisPlugin(tab.id) && tab.id !== "ignis-bridge") {
|
||||
addPluginNavItem(tab.id, setting, corePluginsItems, ignisNavEls);
|
||||
addPluginNavItem(tab.id, setting, corePluginsItems);
|
||||
}
|
||||
}
|
||||
|
||||
hideIgnisNavFromCommunityGroup(setting);
|
||||
hideCorePluginsGroupIfEmpty(ignisNavEls);
|
||||
hideCorePluginsGroupIfEmpty();
|
||||
});
|
||||
|
||||
observer.observe(communityGroup, { childList: true, subtree: true });
|
||||
@@ -186,7 +190,7 @@ function setupPluginTabs(setting, corePluginsItems, ignisNavEls) {
|
||||
}
|
||||
}
|
||||
|
||||
function reconcilePluginTabs(setting, ignisNavEls) {
|
||||
function reconcilePluginTabs(setting) {
|
||||
const corePluginsGroup = findGroupByTitle(
|
||||
setting.tabHeadersEl,
|
||||
"Ignis Core Plugins",
|
||||
@@ -212,16 +216,16 @@ function reconcilePluginTabs(setting, ignisNavEls) {
|
||||
|
||||
for (const id of ownedPluginIds) {
|
||||
if (!activeIds.has(id)) {
|
||||
removePluginNavItem(id, ignisNavEls);
|
||||
removePluginNavItem(id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of activeIds) {
|
||||
addPluginNavItem(id, setting, corePluginsItems, ignisNavEls);
|
||||
addPluginNavItem(id, setting, corePluginsItems);
|
||||
}
|
||||
|
||||
hideIgnisNavFromCommunityGroup(setting);
|
||||
hideCorePluginsGroupIfEmpty(ignisNavEls);
|
||||
hideCorePluginsGroupIfEmpty();
|
||||
}
|
||||
|
||||
function clearOwnedPluginIds() {
|
||||
@@ -229,6 +233,7 @@ function clearOwnedPluginIds() {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
allIgnisNavEls,
|
||||
setupPluginTabs,
|
||||
reconcilePluginTabs,
|
||||
hideIgnisFromCommunityPlugins,
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
const { Setting, Notice } = require("obsidian");
|
||||
const { reconcilePluginTabs } = require("./plugin-tabs");
|
||||
|
||||
function getVaultId() {
|
||||
return window.__currentVaultId || "";
|
||||
}
|
||||
|
||||
async function refreshPluginCache(bundledPluginId) {
|
||||
const pluginPath = `.obsidian/plugins/${bundledPluginId}`;
|
||||
const fs = require("fs");
|
||||
|
||||
if (fs._refreshSubtree) {
|
||||
await fs._refreshSubtree(pluginPath);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPlugins() {
|
||||
const res = await fetch("/api/plugins");
|
||||
|
||||
@@ -23,7 +15,7 @@ async function fetchPlugins() {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function togglePlugin(pluginId, enable, app) {
|
||||
async function togglePlugin(pluginId, enable) {
|
||||
const action = enable ? "enable" : "disable";
|
||||
const vaultId = getVaultId();
|
||||
|
||||
@@ -41,25 +33,10 @@ async function togglePlugin(pluginId, enable, app) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function activateBundledPlugin(bundledPluginId, enable, app) {
|
||||
if (!bundledPluginId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const plugins = app.plugins;
|
||||
|
||||
if (enable) {
|
||||
await plugins.loadManifests();
|
||||
await plugins.enablePluginAndSave(bundledPluginId);
|
||||
} else {
|
||||
await plugins.disablePluginAndSave(bundledPluginId);
|
||||
}
|
||||
}
|
||||
|
||||
function display(containerEl, app) {
|
||||
containerEl.createEl("h2", { text: "Ignis Core Plugins" });
|
||||
|
||||
const descEl = containerEl.createEl("p", {
|
||||
containerEl.createEl("p", {
|
||||
text:
|
||||
"Ignis plugins extend server functionality and run alongside your vaults. " +
|
||||
"They are separate from Obsidian's built-in plugins.",
|
||||
@@ -92,28 +69,16 @@ function display(containerEl, app) {
|
||||
toggle.setValue(enabled);
|
||||
toggle.onChange(async (value) => {
|
||||
try {
|
||||
await togglePlugin(plugin.id, value, app);
|
||||
|
||||
if (value && plugin.bundledPluginId) {
|
||||
await refreshPluginCache(plugin.bundledPluginId);
|
||||
}
|
||||
|
||||
await activateBundledPlugin(
|
||||
plugin.bundledPluginId,
|
||||
value,
|
||||
app,
|
||||
);
|
||||
await togglePlugin(plugin.id, value);
|
||||
|
||||
new Notice(
|
||||
`${plugin.name} ${value ? "enabled" : "disabled"} for this vault.`,
|
||||
);
|
||||
|
||||
// Give Obsidian a moment to update its plugin tabs,
|
||||
// then reconcile our sidebar groups.
|
||||
// The server's WS broadcast drives the actual load/unload via virtual-plugin-loader.
|
||||
// Reconcile the settings sidebar so the new plugin's settings tab gets grouped correctly.
|
||||
setTimeout(() => {
|
||||
if (typeof window.__ignisReconcilePluginTabs === "function") {
|
||||
window.__ignisReconcilePluginTabs(app.setting);
|
||||
}
|
||||
reconcilePluginTabs(app.setting);
|
||||
}, 100);
|
||||
} catch (e) {
|
||||
new Notice(`Failed: ${e.message}`);
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
function getWsStatus() {
|
||||
const ws = window.__ignisWs;
|
||||
|
||||
if (!ws) {
|
||||
return "disconnected";
|
||||
}
|
||||
|
||||
switch (ws.readyState) {
|
||||
case WebSocket.CONNECTING:
|
||||
return "connecting";
|
||||
case WebSocket.OPEN:
|
||||
return "connected";
|
||||
default:
|
||||
return "disconnected";
|
||||
}
|
||||
}
|
||||
|
||||
const STATUS_LABELS = {
|
||||
connected: "Ignis server: Connected",
|
||||
open: "Ignis server: Connected",
|
||||
connecting: "Ignis server: Connecting...",
|
||||
disconnected: "Ignis server: Disconnected",
|
||||
closed: "Ignis server: Disconnected",
|
||||
};
|
||||
|
||||
const STATUS_DOT_CLASSES = {
|
||||
open: "ignis-statusbar-connected",
|
||||
connecting: "ignis-statusbar-connecting",
|
||||
closed: "ignis-statusbar-disconnected",
|
||||
};
|
||||
|
||||
function initStatusBar(plugin) {
|
||||
const ws = window.__ignis.ws;
|
||||
|
||||
const item = plugin.addStatusBarItem();
|
||||
item.addClass("ignis-statusbar-item");
|
||||
|
||||
@@ -29,20 +20,16 @@ function initStatusBar(plugin) {
|
||||
cls: "ignis-statusbar-dot",
|
||||
});
|
||||
|
||||
item.setAttribute("aria-label", "Ignis: Checking...");
|
||||
item.setAttribute("data-tooltip-position", "top");
|
||||
|
||||
const update = () => {
|
||||
const status = getWsStatus();
|
||||
dot.className = `ignis-statusbar-dot ignis-statusbar-${status}`;
|
||||
item.setAttribute("aria-label", STATUS_LABELS[status] || "Ignis: Unknown");
|
||||
};
|
||||
function render(state) {
|
||||
dot.className = `ignis-statusbar-dot ${STATUS_DOT_CLASSES[state] || STATUS_DOT_CLASSES.closed}`;
|
||||
item.setAttribute("aria-label", STATUS_LABELS[state] || STATUS_LABELS.closed);
|
||||
}
|
||||
|
||||
update();
|
||||
render(ws.isOpen() ? "open" : "closed");
|
||||
|
||||
const interval = setInterval(update, 3000);
|
||||
|
||||
return interval;
|
||||
return ws.onStateChange(render);
|
||||
}
|
||||
|
||||
module.exports = { initStatusBar };
|
||||
|
||||
Reference in New Issue
Block a user