diff --git a/images/favicon.png b/images/favicon.png index 264c7e2..6492925 100644 Binary files a/images/favicon.png and b/images/favicon.png differ diff --git a/images/ignis.png b/images/ignis.png index 7837234..e07a54c 100644 Binary files a/images/ignis.png and b/images/ignis.png differ diff --git a/images/ignis.webp b/images/ignis.webp new file mode 100644 index 0000000..3339952 Binary files /dev/null and b/images/ignis.webp differ diff --git a/plugin/manifest.json b/plugin/manifest.json index 2640f3c..5d4e417 100644 --- a/plugin/manifest.json +++ b/plugin/manifest.json @@ -1,10 +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", + "version": "0.6.4", + "minAppVersion": "1.12.4", + "description": "Additional Ignis specific functionality and ignis plugin management.", + "author": "Nystik", "authorUrl": "https://github.com/Nystik-gh/ignis", "isDesktopOnly": false } diff --git a/plugin/src/settings/general-tab.js b/plugin/src/settings/general-tab.js index 9f2498d..a9c84c9 100644 --- a/plugin/src/settings/general-tab.js +++ b/plugin/src/settings/general-tab.js @@ -1,17 +1,158 @@ const { Setting } = require("obsidian"); -function display(containerEl) { - containerEl.createEl("h2", { text: "Ignis General Settings" }); +const GITHUB_URL = "https://github.com/Nystik-gh/ignis"; +const GITHUB_API_LATEST = + "https://api.github.com/repos/Nystik-gh/ignis/releases/latest"; - new Setting(containerEl) - .setName("Example toggle") - .setDesc("This is a test toggle to prove the Setting API works.") - .addToggle((toggle) => { - toggle.setValue(false); - toggle.onChange((value) => { - console.log("[ignis] Toggle:", value); - }); - }); +function getVersion(app) { + try { + const manifest = app.plugins.getPlugin("ignis-bridge")?.manifest; + return manifest?.version || "unknown"; + } catch { + return "unknown"; + } +} + +async function checkForUpdate(currentVersion) { + try { + const res = await fetch(GITHUB_API_LATEST); + + if (!res.ok) { + return null; + } + + const data = await res.json(); + const latest = data.tag_name?.replace(/^v/, ""); + + if (latest && latest !== currentVersion) { + return latest; + } + + return null; + } catch { + return null; + } +} + +function display(containerEl, app) { + const version = getVersion(app); + + const header = containerEl.createDiv("ignis-header"); + + const logo = header.createEl("img", { + cls: "ignis-header-logo", + attr: { src: "/assets/ignis.webp", alt: "Ignis" }, + }); + + const info = header.createDiv("ignis-header-info"); + info.createEl("div", { text: "Ignis", cls: "ignis-header-title" }); + info.createEl("div", { + text: "Obsidian server bridge", + cls: "ignis-header-subtitle", + }); + + const right = header.createDiv("ignis-header-right"); + + const versionCol = right.createDiv("ignis-header-version-col"); + versionCol.createEl("span", { + text: `Version ${version}`, + cls: "ignis-header-version", + }); + + const updateIndicator = versionCol.createEl("span", { + text: "Checking...", + cls: "ignis-update-indicator", + }); + + const githubLink = right.createEl("a", { + cls: "ignis-github-link", + href: GITHUB_URL, + attr: { target: "_blank", "aria-label": "GitHub" }, + }); + + const githubIcon = githubLink.createEl("img", { + cls: "ignis-github-icon", + attr: { src: "/assets/github.svg", alt: "GitHub" }, + }); + + checkForUpdate(version).then((latestVersion) => { + if (latestVersion) { + updateIndicator.textContent = `v${latestVersion} available`; + updateIndicator.addClass("ignis-update-available"); + } else { + updateIndicator.textContent = "Up to date"; + } + }); + + addServerStatus(containerEl); +} + +function getWsStatus() { + const ws = window.__ignisWs; + + 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"; + } +} + +function addServerStatus(containerEl) { + const status = getWsStatus(); + + const setting = new Setting(containerEl).setName("Server status"); + + const dotEl = setting.controlEl.createEl("span", { + cls: `ignis-status-dot ignis-status-${status}`, + }); + + 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); + }; + + const pollInterval = setInterval(update, 3000); + + const observer = new MutationObserver(() => { + if (!containerEl.isConnected) { + clearInterval(pollInterval); + observer.disconnect(); + } + }); + + observer.observe(containerEl.parentElement || document.body, { + childList: true, + subtree: true, + }); } module.exports = { display }; diff --git a/plugin/src/settings/inject.js b/plugin/src/settings/inject.js index b7968c5..de17ba2 100644 --- a/plugin/src/settings/inject.js +++ b/plugin/src/settings/inject.js @@ -41,30 +41,44 @@ function createTab(id, name, displayFn, app) { return tab; } -function injectIgnisSettings(setting, app) { +function createGroup(name) { const group = document.createElement("div"); group.className = "vertical-tab-header-group"; const title = document.createElement("div"); title.className = "vertical-tab-header-group-title"; - title.textContent = "Ignis"; + title.textContent = name; group.appendChild(title); const items = document.createElement("div"); items.className = "vertical-tab-header-group-items"; group.appendChild(items); + return { group, items }; +} + +function injectIgnisSettings(setting, app) { + const ignis = createGroup("Ignis"); + const tabs = [ createTab("ignis-general", "General", generalTab.display, app), - createTab("ignis-server-plugins", "Server Plugins", serverPluginsTab.display, app), + createTab( + "ignis-core-plugins", + "Core plugins", + serverPluginsTab.display, + app, + ), ]; for (const tab of tabs) { tab.navEl = createNavEl(tab, setting); - items.appendChild(tab.navEl); + ignis.items.appendChild(tab.navEl); } - setting.tabHeadersEl.appendChild(group); + setting.tabHeadersEl.appendChild(ignis.group); + + const corePlugins = createGroup("Ignis Core Plugins"); + setting.tabHeadersEl.appendChild(corePlugins.group); } function patchSettingsModal(plugin) { diff --git a/plugin/src/settings/server-plugins-tab.js b/plugin/src/settings/server-plugins-tab.js index 5e34621..10c4ac9 100644 --- a/plugin/src/settings/server-plugins-tab.js +++ b/plugin/src/settings/server-plugins-tab.js @@ -48,7 +48,14 @@ async function activateBundledPlugin(bundledPluginId, enable, app) { } function display(containerEl, app) { - containerEl.createEl("h2", { text: "Server Plugins" }); + containerEl.createEl("h2", { text: "Ignis Core Plugins" }); + + const descEl = containerEl.createEl("p", { + text: + "Ignis plugins extend server functionality and run alongside your vaults. " + + "They are separate from Obsidian's built-in plugins.", + cls: "ignis-plugins-description", + }); const loadingEl = containerEl.createEl("p", { text: "Loading plugins..." }); diff --git a/plugin/styles.css b/plugin/styles.css new file mode 100644 index 0000000..0d0bf35 --- /dev/null +++ b/plugin/styles.css @@ -0,0 +1,113 @@ +.ignis-header { + display: flex; + align-items: center; + gap: 16px; + padding: 16px 0 12px; + margin-bottom: 12px; + border-bottom: 1px solid var(--background-modifier-border); +} + +.ignis-header-logo { + width: 48px; + height: 48px; + flex-shrink: 0; +} + +.ignis-header-info { + flex: 1; + min-width: 0; +} + +.ignis-header-title { + font-size: var(--font-ui-large); + font-weight: var(--font-semibold); + line-height: 1.2; + margin: 0; +} + +.ignis-header-subtitle { + font-size: var(--font-ui-small); + color: var(--text-muted); + line-height: 1.2; + margin: 4px 0 0; +} + +.ignis-header-right { + display: flex; + align-items: center; + gap: 12px; + flex-shrink: 0; +} + +.ignis-header-version-col { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; +} + +.ignis-header-version { + font-size: var(--font-ui-small); + color: var(--text-muted); +} + +.ignis-update-indicator { + font-size: var(--font-ui-smaller); + color: var(--text-faint); +} + +.ignis-update-indicator.ignis-update-available { + color: var(--text-accent); +} + +.ignis-github-link { + color: var(--text-muted); + display: flex; + align-items: center; +} + +.ignis-github-link:hover { + color: var(--text-normal); +} + +.ignis-github-link:hover .ignis-github-icon { + opacity: 1; +} + +.ignis-github-icon { + width: 32px; + height: 32px; + opacity: 0.6; +} + +.ignis-status-dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 6px; +} + +.ignis-status-connected { + background-color: var(--color-green); +} + +.ignis-status-connecting { + background-color: var(--color-yellow); +} + +.ignis-status-disconnected { + background-color: var(--color-red); +} + +.ignis-status-label { + font-size: var(--font-ui-small); + color: var(--text-muted); +} + +.ignis-plugins-description { + padding: 0 16px; + color: var(--text-muted); + font-size: var(--font-ui-small); + margin-bottom: 16px; +} diff --git a/server/assets/github.svg b/server/assets/github.svg new file mode 100644 index 0000000..24b8a06 --- /dev/null +++ b/server/assets/github.svg @@ -0,0 +1 @@ + diff --git a/server/assets/ignis.webp b/server/assets/ignis.webp new file mode 100644 index 0000000..3339952 Binary files /dev/null and b/server/assets/ignis.webp differ diff --git a/server/index.js b/server/index.js index fc0eedc..f303cb2 100644 --- a/server/index.js +++ b/server/index.js @@ -50,6 +50,8 @@ const vaultRoutes = require("./routes/vault"); const proxyRoutes = require("./routes/proxy"); const versionRoutes = require("./routes/version"); +app.use("/assets", express.static(path.join(__dirname, "assets"))); + app.use("/api/fs", fsRoutes); app.use("/api/vault", vaultRoutes); app.use("/api/proxy", proxyRoutes); diff --git a/src/shims/fs/watcher-client.js b/src/shims/fs/watcher-client.js index fd56008..e357bab 100644 --- a/src/shims/fs/watcher-client.js +++ b/src/shims/fs/watcher-client.js @@ -25,6 +25,7 @@ export function createWatcherClient(metadataCache, contentCache, fsWatch) { try { ws = new WebSocket(url); + window.__ignisWs = ws; } catch (e) { console.error("[watcher] Failed to create WebSocket:", e); scheduleReconnect();