- Automation tab: settings form (define_settings) beside the editor, stacking above it when narrow; script/settings save split. - Set location modal: current location + reset checkbox. - saveScriptSettings/setSavePath API wiring; plugins panel. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 lines
3.2 KiB
JavaScript
99 lines
3.2 KiB
JavaScript
// Frontend plugin registry.
|
|
// Plugin modules call window.Naut.registerPlugin({...}) after they are imported.
|
|
|
|
const registry = {
|
|
plugins: [],
|
|
views: [],
|
|
sidebarSections: [],
|
|
detailTabs: [],
|
|
detailPanels: [],
|
|
};
|
|
|
|
let sharedContext = {};
|
|
|
|
function normalizeList(value) {
|
|
return Array.isArray(value) ? value : [];
|
|
}
|
|
|
|
function registerPlugin(plugin) {
|
|
if (!plugin || !plugin.id || registry.plugins.some((p) => p.id === plugin.id)) return;
|
|
registry.plugins.push(plugin);
|
|
for (const view of normalizeList(plugin.views)) registry.views.push({ plugin, ...view });
|
|
for (const section of normalizeList(plugin.sidebarSections)) registry.sidebarSections.push({ plugin, ...section });
|
|
for (const tab of normalizeList(plugin.detailTabs)) registry.detailTabs.push({ plugin, ...tab });
|
|
for (const panel of normalizeList(plugin.detailPanels)) registry.detailPanels.push({ plugin, ...panel });
|
|
}
|
|
|
|
window.Naut = {
|
|
...(window.Naut || {}),
|
|
registerPlugin,
|
|
plugins: registry,
|
|
context: () => sharedContext,
|
|
};
|
|
|
|
export async function loadPlugins(api, context) {
|
|
sharedContext = context || {};
|
|
let manifest;
|
|
try { manifest = await api.plugins(); } catch { manifest = { modules: [] }; }
|
|
for (const modulePath of manifest.modules || []) {
|
|
try { await import(modulePath); }
|
|
catch (e) { console.error(`Failed to load plugin ${modulePath}`, e); }
|
|
}
|
|
return registry.plugins;
|
|
}
|
|
|
|
export function pluginViews() {
|
|
return registry.views.filter((v) => v.id && v.label && typeof v.render === 'function');
|
|
}
|
|
|
|
export function getPluginView(id) {
|
|
return pluginViews().find((v) => v.id === id);
|
|
}
|
|
|
|
export function renderPluginView(id, host, context) {
|
|
const view = getPluginView(id);
|
|
if (!view) return false;
|
|
view.render(host, context);
|
|
return true;
|
|
}
|
|
|
|
export function renderPluginSidebarSections(context) {
|
|
return registry.sidebarSections
|
|
.filter((s) => s.id && s.title && typeof s.render === 'function')
|
|
.map((s) => `<div class="side-group plugin-side" data-plugin-sidebar="${s.plugin.id}:${s.id}">
|
|
<div class="side-head">${context.f.esc(s.title)}</div>${s.render(context) || ''}
|
|
</div>`)
|
|
.join('');
|
|
}
|
|
|
|
export function mountPluginSidebarSections(root, context) {
|
|
for (const section of registry.sidebarSections) {
|
|
const el = root.querySelector(`[data-plugin-sidebar="${CSS.escape(`${section.plugin.id}:${section.id}`)}"]`);
|
|
if (el && typeof section.onMount === 'function') section.onMount(el, context);
|
|
}
|
|
}
|
|
|
|
export function pluginDetailTabs() {
|
|
return registry.detailTabs.filter((t) => t.id && t.label && typeof t.render === 'function');
|
|
}
|
|
|
|
export async function renderPluginDetailTab(id, host, context) {
|
|
const tab = pluginDetailTabs().find((t) => t.id === id);
|
|
if (!tab) return false;
|
|
await tab.render(host, context);
|
|
return true;
|
|
}
|
|
|
|
export function renderPluginDetailPanels(torrent, context) {
|
|
return registry.detailPanels
|
|
.filter((p) => p.title && typeof p.render === 'function')
|
|
.map((p) => {
|
|
try {
|
|
return `<div class="section-h">${context.f.esc(p.title)}</div>${p.render(torrent, context) || ''}`;
|
|
} catch (e) {
|
|
console.error(`Failed to render plugin panel ${p.plugin.id}:${p.id || p.title}`, e);
|
|
return '';
|
|
}
|
|
})
|
|
.join('');
|
|
}
|