plugin infrastruktur
This commit is contained in:
1 parent
1545b90f39
commit
3f4ba3c0f4
35 files changed
+1303
-195
No files matched your search
@@ -0,0 +1,90 @@
|
||||
// ── Plugin Settings UI ──
|
||||
// Renders the list of installed plugins with on/off toggles in the
|
||||
// settings overlay. Disabled plugins are skipped at PHP render time;
|
||||
// toggling requires a reload to take effect (page must re-fetch the
|
||||
// new script/css/html tag set).
|
||||
|
||||
import { $, esc, api } from './util.js';
|
||||
|
||||
let pluginsCache = null;
|
||||
let dirtyAfterToggle = false;
|
||||
|
||||
async function fetchPlugins() {
|
||||
const res = await fetch('api/plugins');
|
||||
if (!res.ok) throw new Error('failed to load plugins');
|
||||
pluginsCache = await res.json();
|
||||
return pluginsCache;
|
||||
}
|
||||
|
||||
function render(list) {
|
||||
const container = $('plugins-list');
|
||||
if (!container) return;
|
||||
if (!list.length) {
|
||||
container.innerHTML = '<p class="text-xs text-t-dim italic">No plugins installed. Drop a plugin folder into <code>public/plugins/</code> to add one.</p>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = list.map(p => `
|
||||
<div class="flex items-start gap-3 px-3 py-2 border border-t-border bg-t-bg rounded-sm" data-plugin="${esc(p.name)}">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-pixel text-[11px] text-t-bright">${esc(p.name)}</span>
|
||||
<span class="font-pixel text-[9px] text-t-dim">v${esc(p.version || '?')}</span>
|
||||
${p.hasBackend ? '<span class="font-pixel text-[8px] px-1 py-0.5 border border-t-border text-t-muted uppercase">php</span>' : ''}
|
||||
</div>
|
||||
${p.description ? `<div class="text-[10px] text-t-muted leading-snug mt-1">${esc(p.description)}</div>` : ''}
|
||||
</div>
|
||||
<label class="flex items-center cursor-pointer shrink-0 mt-0.5">
|
||||
<input type="checkbox" class="plugin-toggle accent-t-accent w-4 h-4" ${p.enabled ? 'checked' : ''} data-name="${esc(p.name)}">
|
||||
</label>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
container.querySelectorAll('.plugin-toggle').forEach(input => {
|
||||
input.addEventListener('change', onToggle);
|
||||
});
|
||||
}
|
||||
|
||||
async function onToggle(e) {
|
||||
const input = e.currentTarget;
|
||||
const name = input.dataset.name;
|
||||
const enabled = input.checked;
|
||||
input.disabled = true;
|
||||
try {
|
||||
const res = await api('api/plugins/toggle', {
|
||||
method: 'POST',
|
||||
body: { name, enabled },
|
||||
});
|
||||
if (res.error) throw new Error(res.error);
|
||||
pluginsCache = res.plugins;
|
||||
dirtyAfterToggle = true;
|
||||
$('plugins-reload-notice')?.classList.remove('hidden');
|
||||
} catch (err) {
|
||||
// Revert UI on failure
|
||||
input.checked = !enabled;
|
||||
console.error('[plugins] toggle failed:', err);
|
||||
} finally {
|
||||
input.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function openPluginSettings() {
|
||||
const container = $('plugins-list');
|
||||
if (!container) return;
|
||||
container.innerHTML = '<p class="text-xs text-t-dim italic">Loading...</p>';
|
||||
if (!dirtyAfterToggle) {
|
||||
$('plugins-reload-notice')?.classList.add('hidden');
|
||||
}
|
||||
try {
|
||||
const list = await fetchPlugins();
|
||||
render(list);
|
||||
} catch (err) {
|
||||
container.innerHTML = `<p class="text-xs text-t-danger">Failed to load plugins: ${esc(err.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Wire reload button once at module load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
$('plugins-reload-btn')?.addEventListener('click', () => location.reload());
|
||||
}, { once: true });
|
||||
// In case DOMContentLoaded already fired (modules are deferred but safe to retry)
|
||||
$('plugins-reload-btn')?.addEventListener('click', () => location.reload());
|
||||
Reference in new issue
Block a user