mirror of
https://github.com/Nystik-gh/ignis.git
synced 2026-06-17 04:35:53 +00:00
refactor bridge plugin, minor improvement to plugin tracking.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
const { Plugin, TFile, TFolder } = require("obsidian");
|
const { Plugin, TFile, TFolder } = require("obsidian");
|
||||||
const { showFilePicker, addFileMenuItems, addFolderMenuItems } = require("./file-actions");
|
const { showFilePicker, addFileMenuItems, addFolderMenuItems } = require("./file-actions");
|
||||||
const { patchSettingsModal, unpatchSettingsModal } = require("./settings/inject");
|
const { patchSettingsModal, unpatchSettingsModal } = require("./settings/inject");
|
||||||
|
const pluginRegistry = require("./plugin-registry");
|
||||||
|
|
||||||
window.__obsidianAPI = require("obsidian");
|
window.__obsidianAPI = require("obsidian");
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ class IgnisBridgePlugin extends Plugin {
|
|||||||
async onload() {
|
async onload() {
|
||||||
console.log("[ignis-bridge] Plugin loaded");
|
console.log("[ignis-bridge] Plugin loaded");
|
||||||
|
|
||||||
|
await pluginRegistry.refresh();
|
||||||
patchSettingsModal(this);
|
patchSettingsModal(this);
|
||||||
|
|
||||||
this.addRibbonIcon("upload", "Upload file", () => {
|
this.addRibbonIcon("upload", "Upload file", () => {
|
||||||
|
|||||||
37
plugin/src/plugin-registry.js
Normal file
37
plugin/src/plugin-registry.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Maintains a set of known ignis plugin IDs for filtering.
|
||||||
|
// Populated on bridge plugin load and updated when plugins are enabled/disabled.
|
||||||
|
|
||||||
|
const knownIds = new Set(["ignis-bridge"]);
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/plugins");
|
||||||
|
const plugins = await res.json();
|
||||||
|
|
||||||
|
// Keep ignis-bridge, add all bundled plugin IDs.
|
||||||
|
knownIds.clear();
|
||||||
|
knownIds.add("ignis-bridge");
|
||||||
|
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
if (plugin.bundledPluginId) {
|
||||||
|
knownIds.add(plugin.bundledPluginId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Keep whatever we had.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIgnisPlugin(pluginId) {
|
||||||
|
return knownIds.has(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addId(pluginId) {
|
||||||
|
knownIds.add(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKnownIds() {
|
||||||
|
return knownIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { refresh, isIgnisPlugin, addId, getKnownIds };
|
||||||
@@ -1,197 +1,13 @@
|
|||||||
const { setIcon } = require("obsidian");
|
|
||||||
const generalTab = require("./general-tab");
|
const generalTab = require("./general-tab");
|
||||||
const serverPluginsTab = require("./server-plugins-tab");
|
const serverPluginsTab = require("./server-plugins-tab");
|
||||||
|
const { createNavEl, createTab, createGroup } = require("./settings-ui");
|
||||||
// Tracks our own nav items in the "Ignis Core Plugins" group, keyed by plugin ID.
|
const {
|
||||||
const ownedNavItems = new Map();
|
setupPluginTabs,
|
||||||
|
reconcilePluginTabs,
|
||||||
function createNavEl(tab, setting) {
|
hideIgnisFromCommunityPlugins,
|
||||||
const nav = document.createElement("div");
|
restoreCommunityPlugins,
|
||||||
nav.className = "vertical-tab-nav-item tappable";
|
clearOwnedNavItems,
|
||||||
|
} = require("./plugin-tabs");
|
||||||
if (tab.icon) {
|
|
||||||
const iconEl = document.createElement("div");
|
|
||||||
iconEl.className = "vertical-tab-nav-item-icon";
|
|
||||||
|
|
||||||
if (tab.icon.startsWith("<svg") || tab.icon.startsWith("<img")) {
|
|
||||||
iconEl.innerHTML = tab.icon;
|
|
||||||
} else if (tab.icon.endsWith(".svg") || tab.icon.endsWith(".webp") || tab.icon.endsWith(".png")) {
|
|
||||||
iconEl.innerHTML = `<img src="${tab.icon}" class="svg-icon" width="24" height="24" />`;
|
|
||||||
} else {
|
|
||||||
setIcon(iconEl, tab.icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.appendChild(iconEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = document.createElement("div");
|
|
||||||
title.className = "vertical-tab-nav-item-title";
|
|
||||||
title.textContent = tab.name;
|
|
||||||
nav.appendChild(title);
|
|
||||||
|
|
||||||
const chevron = document.createElement("div");
|
|
||||||
chevron.className = "vertical-tab-nav-item-chevron";
|
|
||||||
nav.appendChild(chevron);
|
|
||||||
|
|
||||||
nav.addEventListener("click", () => {
|
|
||||||
setting.openTab(tab);
|
|
||||||
});
|
|
||||||
|
|
||||||
return nav;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTab(id, name, displayFn, app, icon) {
|
|
||||||
const tab = {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
icon: icon || null,
|
|
||||||
containerEl: createDiv("vertical-tab-content"),
|
|
||||||
navEl: null,
|
|
||||||
|
|
||||||
display() {
|
|
||||||
this.containerEl.empty();
|
|
||||||
displayFn(this.containerEl, app);
|
|
||||||
},
|
|
||||||
|
|
||||||
hide() {
|
|
||||||
this.containerEl.empty();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = name;
|
|
||||||
group.appendChild(title);
|
|
||||||
|
|
||||||
const items = document.createElement("div");
|
|
||||||
items.className = "vertical-tab-header-group-items";
|
|
||||||
group.appendChild(items);
|
|
||||||
|
|
||||||
return { group, items };
|
|
||||||
}
|
|
||||||
|
|
||||||
function findGroupByTitle(tabHeadersEl, title) {
|
|
||||||
const groups = tabHeadersEl.querySelectorAll(".vertical-tab-header-group");
|
|
||||||
|
|
||||||
for (const g of groups) {
|
|
||||||
const t = g.querySelector(".vertical-tab-header-group-title");
|
|
||||||
|
|
||||||
if (t?.textContent === title) {
|
|
||||||
return g;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideIgnisFromCommunityPlugins(setting) {
|
|
||||||
const cpTab = setting.settingTabs.find((t) => t.id === "community-plugins");
|
|
||||||
|
|
||||||
if (!cpTab || cpTab._ignisPatched) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const origRender = cpTab.renderInstalledPlugin;
|
|
||||||
|
|
||||||
cpTab.renderInstalledPlugin = function (manifest, ...rest) {
|
|
||||||
if (manifest.id.startsWith("ignis-")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return origRender.call(this, manifest, ...rest);
|
|
||||||
};
|
|
||||||
|
|
||||||
cpTab._ignisPatched = true;
|
|
||||||
cpTab._origRenderInstalledPlugin = origRender;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideIgnisNavFromCommunityGroup(setting) {
|
|
||||||
const communityGroup = findGroupByTitle(
|
|
||||||
setting.tabHeadersEl,
|
|
||||||
"Community plugins",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!communityGroup) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = communityGroup.querySelector(".vertical-tab-header-group-items");
|
|
||||||
|
|
||||||
if (!items) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide any ignis plugin nav items that Obsidian placed here.
|
|
||||||
for (const tab of setting.pluginTabs) {
|
|
||||||
if (tab.id.startsWith("ignis-") && tab.navEl?.parentElement === items) {
|
|
||||||
tab.navEl.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide the entire group if no visible items remain.
|
|
||||||
const hasVisible = Array.from(items.children).some(
|
|
||||||
(el) => el.style.display !== "none",
|
|
||||||
);
|
|
||||||
|
|
||||||
communityGroup.style.display = hasVisible ? "" : "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
function addPluginNavItem(pluginId, setting, corePluginsItems) {
|
|
||||||
// Find the tab object Obsidian created for this plugin.
|
|
||||||
const tab = setting.pluginTabs.find((t) => t.id === pluginId);
|
|
||||||
|
|
||||||
if (!tab) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't add if we already have one for this ID.
|
|
||||||
if (ownedNavItems.has(pluginId)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create our own nav item that delegates to Obsidian's tab.
|
|
||||||
const nav = document.createElement("div");
|
|
||||||
nav.className = "vertical-tab-nav-item tappable";
|
|
||||||
|
|
||||||
if (tab.icon) {
|
|
||||||
const iconEl = document.createElement("div");
|
|
||||||
iconEl.className = "vertical-tab-nav-item-icon";
|
|
||||||
setIcon(iconEl, tab.icon);
|
|
||||||
nav.appendChild(iconEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = document.createElement("div");
|
|
||||||
title.className = "vertical-tab-nav-item-title";
|
|
||||||
title.textContent = tab.name;
|
|
||||||
nav.appendChild(title);
|
|
||||||
|
|
||||||
const chevron = document.createElement("div");
|
|
||||||
chevron.className = "vertical-tab-nav-item-chevron";
|
|
||||||
nav.appendChild(chevron);
|
|
||||||
|
|
||||||
nav.addEventListener("click", () => {
|
|
||||||
setting.openTab(tab);
|
|
||||||
});
|
|
||||||
|
|
||||||
corePluginsItems.appendChild(nav);
|
|
||||||
ownedNavItems.set(pluginId, nav);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removePluginNavItem(pluginId) {
|
|
||||||
const nav = ownedNavItems.get(pluginId);
|
|
||||||
|
|
||||||
if (nav) {
|
|
||||||
nav.remove();
|
|
||||||
ownedNavItems.delete(pluginId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeExistingIgnisGroups(tabHeadersEl) {
|
function removeExistingIgnisGroups(tabHeadersEl) {
|
||||||
const groups = tabHeadersEl.querySelectorAll(".vertical-tab-header-group");
|
const groups = tabHeadersEl.querySelectorAll(".vertical-tab-header-group");
|
||||||
@@ -210,7 +26,7 @@ function removeExistingIgnisGroups(tabHeadersEl) {
|
|||||||
|
|
||||||
function injectIgnisSettings(setting, app) {
|
function injectIgnisSettings(setting, app) {
|
||||||
removeExistingIgnisGroups(setting.tabHeadersEl);
|
removeExistingIgnisGroups(setting.tabHeadersEl);
|
||||||
ownedNavItems.clear();
|
clearOwnedNavItems();
|
||||||
|
|
||||||
const ignis = createGroup("Ignis");
|
const ignis = createGroup("Ignis");
|
||||||
|
|
||||||
@@ -236,119 +52,7 @@ function injectIgnisSettings(setting, app) {
|
|||||||
setting.tabHeadersEl.appendChild(corePlugins.group);
|
setting.tabHeadersEl.appendChild(corePlugins.group);
|
||||||
|
|
||||||
hideIgnisFromCommunityPlugins(setting);
|
hideIgnisFromCommunityPlugins(setting);
|
||||||
|
setupPluginTabs(setting, corePlugins.items);
|
||||||
// Create our own nav items for ignis plugin tabs.
|
|
||||||
for (const tab of setting.pluginTabs) {
|
|
||||||
if (tab.id.startsWith("ignis-") && tab.id !== "ignis-bridge") {
|
|
||||||
addPluginNavItem(tab.id, setting, corePlugins.items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hideIgnisNavFromCommunityGroup(setting);
|
|
||||||
hideCorePluginsGroupIfEmpty();
|
|
||||||
|
|
||||||
// Watch the community group for changes. When Obsidian adds new ignis
|
|
||||||
// plugin nav items (async after enable), hide them and create our own.
|
|
||||||
const communityGroup = findGroupByTitle(
|
|
||||||
setting.tabHeadersEl,
|
|
||||||
"Community plugins",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (communityGroup) {
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
// Re-check for new ignis plugin tabs and create nav items.
|
|
||||||
for (const tab of setting.pluginTabs) {
|
|
||||||
if (tab.id.startsWith("ignis-") && tab.id !== "ignis-bridge") {
|
|
||||||
addPluginNavItem(tab.id, setting, corePlugins.items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hideIgnisNavFromCommunityGroup(setting);
|
|
||||||
hideCorePluginsGroupIfEmpty();
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(communityGroup, { childList: true, subtree: true });
|
|
||||||
|
|
||||||
const cleanupObserver = new MutationObserver(() => {
|
|
||||||
if (!setting.tabHeadersEl.isConnected) {
|
|
||||||
observer.disconnect();
|
|
||||||
cleanupObserver.disconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cleanupObserver.observe(document.body, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reconcilePluginTabs(setting) {
|
|
||||||
const corePluginsGroup = findGroupByTitle(
|
|
||||||
setting.tabHeadersEl,
|
|
||||||
"Ignis Core Plugins",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!corePluginsGroup) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const corePluginsItems = corePluginsGroup.querySelector(
|
|
||||||
".vertical-tab-header-group-items",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!corePluginsItems) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current set of ignis plugin IDs from pluginTabs.
|
|
||||||
const activeIds = new Set(
|
|
||||||
setting.pluginTabs
|
|
||||||
.filter((t) => t.id.startsWith("ignis-") && t.id !== "ignis-bridge")
|
|
||||||
.map((t) => t.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove nav items for plugins that are no longer active.
|
|
||||||
for (const [id] of ownedNavItems) {
|
|
||||||
if (!activeIds.has(id)) {
|
|
||||||
removePluginNavItem(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add nav items for newly active plugins.
|
|
||||||
for (const id of activeIds) {
|
|
||||||
addPluginNavItem(id, setting, corePluginsItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
hideIgnisNavFromCommunityGroup(setting);
|
|
||||||
hideCorePluginsGroupIfEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideCorePluginsGroupIfEmpty() {
|
|
||||||
for (const [, nav] of ownedNavItems) {
|
|
||||||
if (nav.isConnected) {
|
|
||||||
const group = nav.closest(".vertical-tab-header-group");
|
|
||||||
|
|
||||||
if (group) {
|
|
||||||
group.style.display = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No items - find and hide the group by walking owned nav items' last known parent,
|
|
||||||
// or just search the DOM.
|
|
||||||
const groups = document.querySelectorAll(".vertical-tab-header-group");
|
|
||||||
|
|
||||||
for (const g of groups) {
|
|
||||||
const title = g.querySelector(".vertical-tab-header-group-title");
|
|
||||||
|
|
||||||
if (title?.textContent === "Ignis Core Plugins") {
|
|
||||||
g.style.display = ownedNavItems.size > 0 ? "" : "none";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchSettingsModal(plugin) {
|
function patchSettingsModal(plugin) {
|
||||||
@@ -367,17 +71,8 @@ function unpatchSettingsModal(plugin) {
|
|||||||
plugin.app.setting.onOpen = plugin._originalOnOpen;
|
plugin.app.setting.onOpen = plugin._originalOnOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cpTab = plugin.app.setting.settingTabs.find(
|
restoreCommunityPlugins(plugin.app.setting);
|
||||||
(t) => t.id === "community-plugins",
|
clearOwnedNavItems();
|
||||||
);
|
|
||||||
|
|
||||||
if (cpTab?._origRenderInstalledPlugin) {
|
|
||||||
cpTab.renderInstalledPlugin = cpTab._origRenderInstalledPlugin;
|
|
||||||
delete cpTab._origRenderInstalledPlugin;
|
|
||||||
delete cpTab._ignisPatched;
|
|
||||||
}
|
|
||||||
|
|
||||||
ownedNavItems.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__ignisReconcilePluginTabs = reconcilePluginTabs;
|
window.__ignisReconcilePluginTabs = reconcilePluginTabs;
|
||||||
|
|||||||
246
plugin/src/settings/plugin-tabs.js
Normal file
246
plugin/src/settings/plugin-tabs.js
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
const { setIcon } = require("obsidian");
|
||||||
|
const { findGroupByTitle } = require("./settings-ui");
|
||||||
|
const { isIgnisPlugin } = require("../plugin-registry");
|
||||||
|
|
||||||
|
// Tracks our own nav items in the "Ignis Core Plugins" group, keyed by plugin ID.
|
||||||
|
const ownedNavItems = new Map();
|
||||||
|
|
||||||
|
function addPluginNavItem(pluginId, setting, corePluginsItems) {
|
||||||
|
// Find the tab object Obsidian created for this plugin.
|
||||||
|
const tab = setting.pluginTabs.find((t) => t.id === pluginId);
|
||||||
|
|
||||||
|
if (!tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't add if we already have one for this ID.
|
||||||
|
if (ownedNavItems.has(pluginId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create our own nav item that delegates to Obsidian's tab.
|
||||||
|
const nav = document.createElement("div");
|
||||||
|
nav.className = "vertical-tab-nav-item tappable";
|
||||||
|
|
||||||
|
if (tab.icon) {
|
||||||
|
const iconEl = document.createElement("div");
|
||||||
|
iconEl.className = "vertical-tab-nav-item-icon";
|
||||||
|
setIcon(iconEl, tab.icon);
|
||||||
|
nav.appendChild(iconEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = document.createElement("div");
|
||||||
|
title.className = "vertical-tab-nav-item-title";
|
||||||
|
title.textContent = tab.name;
|
||||||
|
nav.appendChild(title);
|
||||||
|
|
||||||
|
const chevron = document.createElement("div");
|
||||||
|
chevron.className = "vertical-tab-nav-item-chevron";
|
||||||
|
nav.appendChild(chevron);
|
||||||
|
|
||||||
|
nav.addEventListener("click", () => {
|
||||||
|
setting.openTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
corePluginsItems.appendChild(nav);
|
||||||
|
ownedNavItems.set(pluginId, nav);
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePluginNavItem(pluginId) {
|
||||||
|
const nav = ownedNavItems.get(pluginId);
|
||||||
|
|
||||||
|
if (nav) {
|
||||||
|
nav.remove();
|
||||||
|
ownedNavItems.delete(pluginId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideIgnisFromCommunityPlugins(setting) {
|
||||||
|
const cpTab = setting.settingTabs.find((t) => t.id === "community-plugins");
|
||||||
|
|
||||||
|
if (!cpTab || cpTab._ignisPatched) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const origRender = cpTab.renderInstalledPlugin;
|
||||||
|
|
||||||
|
cpTab.renderInstalledPlugin = function (manifest, ...rest) {
|
||||||
|
if (isIgnisPlugin(manifest.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return origRender.call(this, manifest, ...rest);
|
||||||
|
};
|
||||||
|
|
||||||
|
cpTab._ignisPatched = true;
|
||||||
|
cpTab._origRenderInstalledPlugin = origRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreCommunityPlugins(setting) {
|
||||||
|
const cpTab = setting.settingTabs.find(
|
||||||
|
(t) => t.id === "community-plugins",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cpTab?._origRenderInstalledPlugin) {
|
||||||
|
cpTab.renderInstalledPlugin = cpTab._origRenderInstalledPlugin;
|
||||||
|
delete cpTab._origRenderInstalledPlugin;
|
||||||
|
delete cpTab._ignisPatched;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideIgnisNavFromCommunityGroup(setting) {
|
||||||
|
const communityGroup = findGroupByTitle(
|
||||||
|
setting.tabHeadersEl,
|
||||||
|
"Community plugins",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!communityGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = communityGroup.querySelector(".vertical-tab-header-group-items");
|
||||||
|
|
||||||
|
if (!items) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide any ignis plugin nav items that Obsidian placed here.
|
||||||
|
for (const tab of setting.pluginTabs) {
|
||||||
|
if (isIgnisPlugin(tab.id) && tab.navEl?.parentElement === items) {
|
||||||
|
tab.navEl.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the entire group if no visible items remain.
|
||||||
|
const hasVisible = Array.from(items.children).some(
|
||||||
|
(el) => el.style.display !== "none",
|
||||||
|
);
|
||||||
|
|
||||||
|
communityGroup.style.display = hasVisible ? "" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideCorePluginsGroupIfEmpty() {
|
||||||
|
for (const [, nav] of ownedNavItems) {
|
||||||
|
if (nav.isConnected) {
|
||||||
|
const group = nav.closest(".vertical-tab-header-group");
|
||||||
|
|
||||||
|
if (group) {
|
||||||
|
group.style.display = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No connected items -- find and hide the group.
|
||||||
|
const groups = document.querySelectorAll(".vertical-tab-header-group");
|
||||||
|
|
||||||
|
for (const g of groups) {
|
||||||
|
const title = g.querySelector(".vertical-tab-header-group-title");
|
||||||
|
|
||||||
|
if (title?.textContent === "Ignis Core Plugins") {
|
||||||
|
g.style.display = ownedNavItems.size > 0 ? "" : "none";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupPluginTabs(setting, corePluginsItems) {
|
||||||
|
// Create our own nav items for ignis plugin tabs.
|
||||||
|
for (const tab of setting.pluginTabs) {
|
||||||
|
if (isIgnisPlugin(tab.id) && tab.id !== "ignis-bridge") {
|
||||||
|
addPluginNavItem(tab.id, setting, corePluginsItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideIgnisNavFromCommunityGroup(setting);
|
||||||
|
hideCorePluginsGroupIfEmpty();
|
||||||
|
|
||||||
|
// Watch the community group for changes. When Obsidian adds new ignis
|
||||||
|
// plugin nav items (async after enable), hide them and create our own.
|
||||||
|
const communityGroup = findGroupByTitle(
|
||||||
|
setting.tabHeadersEl,
|
||||||
|
"Community plugins",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (communityGroup) {
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
for (const tab of setting.pluginTabs) {
|
||||||
|
if (isIgnisPlugin(tab.id) && tab.id !== "ignis-bridge") {
|
||||||
|
addPluginNavItem(tab.id, setting, corePluginsItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-evaluate visibility since non-ignis items may have appeared.
|
||||||
|
hideIgnisNavFromCommunityGroup(setting);
|
||||||
|
hideCorePluginsGroupIfEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(communityGroup, { childList: true, subtree: true });
|
||||||
|
|
||||||
|
const cleanupObserver = new MutationObserver(() => {
|
||||||
|
if (!setting.tabHeadersEl.isConnected) {
|
||||||
|
observer.disconnect();
|
||||||
|
cleanupObserver.disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanupObserver.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reconcilePluginTabs(setting) {
|
||||||
|
const corePluginsGroup = findGroupByTitle(
|
||||||
|
setting.tabHeadersEl,
|
||||||
|
"Ignis Core Plugins",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!corePluginsGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const corePluginsItems = corePluginsGroup.querySelector(
|
||||||
|
".vertical-tab-header-group-items",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!corePluginsItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current set of ignis plugin IDs from pluginTabs.
|
||||||
|
const activeIds = new Set(
|
||||||
|
setting.pluginTabs
|
||||||
|
.filter((t) => isIgnisPlugin(t.id) && t.id !== "ignis-bridge")
|
||||||
|
.map((t) => t.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove nav items for plugins that are no longer active.
|
||||||
|
for (const [id] of ownedNavItems) {
|
||||||
|
if (!activeIds.has(id)) {
|
||||||
|
removePluginNavItem(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add nav items for newly active plugins.
|
||||||
|
for (const id of activeIds) {
|
||||||
|
addPluginNavItem(id, setting, corePluginsItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideIgnisNavFromCommunityGroup(setting);
|
||||||
|
hideCorePluginsGroupIfEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearOwnedNavItems() {
|
||||||
|
ownedNavItems.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
setupPluginTabs,
|
||||||
|
reconcilePluginTabs,
|
||||||
|
hideIgnisFromCommunityPlugins,
|
||||||
|
restoreCommunityPlugins,
|
||||||
|
clearOwnedNavItems,
|
||||||
|
};
|
||||||
89
plugin/src/settings/settings-ui.js
Normal file
89
plugin/src/settings/settings-ui.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
const { setIcon } = require("obsidian");
|
||||||
|
|
||||||
|
function createNavEl(tab, setting) {
|
||||||
|
const nav = document.createElement("div");
|
||||||
|
nav.className = "vertical-tab-nav-item tappable";
|
||||||
|
|
||||||
|
if (tab.icon) {
|
||||||
|
const iconEl = document.createElement("div");
|
||||||
|
iconEl.className = "vertical-tab-nav-item-icon";
|
||||||
|
|
||||||
|
if (tab.icon.startsWith("<svg") || tab.icon.startsWith("<img")) {
|
||||||
|
iconEl.innerHTML = tab.icon;
|
||||||
|
} else if (tab.icon.endsWith(".svg") || tab.icon.endsWith(".webp") || tab.icon.endsWith(".png")) {
|
||||||
|
iconEl.innerHTML = `<img src="${tab.icon}" class="svg-icon" width="24" height="24" />`;
|
||||||
|
} else {
|
||||||
|
setIcon(iconEl, tab.icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.appendChild(iconEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = document.createElement("div");
|
||||||
|
title.className = "vertical-tab-nav-item-title";
|
||||||
|
title.textContent = tab.name;
|
||||||
|
nav.appendChild(title);
|
||||||
|
|
||||||
|
const chevron = document.createElement("div");
|
||||||
|
chevron.className = "vertical-tab-nav-item-chevron";
|
||||||
|
nav.appendChild(chevron);
|
||||||
|
|
||||||
|
nav.addEventListener("click", () => {
|
||||||
|
setting.openTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
return nav;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTab(id, name, displayFn, app, icon) {
|
||||||
|
const tab = {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
icon: icon || null,
|
||||||
|
containerEl: createDiv("vertical-tab-content"),
|
||||||
|
navEl: null,
|
||||||
|
|
||||||
|
display() {
|
||||||
|
this.containerEl.empty();
|
||||||
|
displayFn(this.containerEl, app);
|
||||||
|
},
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.containerEl.empty();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = name;
|
||||||
|
group.appendChild(title);
|
||||||
|
|
||||||
|
const items = document.createElement("div");
|
||||||
|
items.className = "vertical-tab-header-group-items";
|
||||||
|
group.appendChild(items);
|
||||||
|
|
||||||
|
return { group, items };
|
||||||
|
}
|
||||||
|
|
||||||
|
function findGroupByTitle(tabHeadersEl, title) {
|
||||||
|
const groups = tabHeadersEl.querySelectorAll(".vertical-tab-header-group");
|
||||||
|
|
||||||
|
for (const g of groups) {
|
||||||
|
const t = g.querySelector(".vertical-tab-header-group-title");
|
||||||
|
|
||||||
|
if (t?.textContent === title) {
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { createNavEl, createTab, createGroup, findGroupByTitle };
|
||||||
Reference in New Issue
Block a user