email plugin

This commit is contained in:
th committed 2026-04-10 10:42:18 +02:00
1 parent 3f4ba3c0f4
commit cc97754ae6
16 files changed
+2979 -5

No files matched your search

+103
View File
@@ -98,6 +98,7 @@
};
// ── Util helpers (mirror of internal util.js) ───────────────────
let csrfToken = '';
const util = {
$(idOrSel) {
if (typeof idOrSel !== 'string') return null;
@@ -114,6 +115,105 @@
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
}[c]));
},
// CSRF: core calls setCsrf() after auth so plugins can read it
setCsrf(token) { csrfToken = String(token || ''); },
csrf() { return csrfToken; },
};
/** Generic JSON API call with automatic CSRF header. */
async function api(path, opts = {}) {
const headers = { 'Content-Type': 'application/json' };
if (csrfToken) headers['X-CSRF-Token'] = csrfToken;
const res = await fetch(path, {
method: opts.method || 'GET',
headers,
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
});
return res.json();
}
// ── Settings registry ───────────────────────────────────────────
// Plugins can inject their own tabs into the main settings overlay.
// The plugin's onShow callback is called whenever the tab becomes visible.
const settings = {
registerTab({ id, label, icon, onShow }) {
if (!id) return;
const nav = document.getElementById('settings-nav');
const panelsContainer = document.querySelector('#settings-overlay .overflow-y-auto.p-5');
if (!nav || !panelsContainer) {
// DOM not ready — defer to next tick
requestAnimationFrame(() => settings.registerTab({ id, label, icon, onShow }));
return;
}
if (nav.querySelector(`[data-tab="${id}"]`)) return; // already registered
// Create tab button
const btn = document.createElement('button');
btn.className = 'settings-tab';
btn.dataset.tab = id;
const iconClass = icon || 'ph-puzzle-piece';
btn.innerHTML = `<i class="ph ${iconClass} text-sm"></i><span class="font-pixel text-[10px]">${label || id}</span>`;
nav.appendChild(btn);
// Create empty panel container
let panel = document.getElementById(`settings-panel-${id}`);
if (!panel) {
panel = document.createElement('div');
panel.id = `settings-panel-${id}`;
panel.className = 'hidden';
panelsContainer.appendChild(panel);
}
// Click handler — duplicates core tab logic (theme.js may also
// attach its own handler later, but identical work is harmless).
btn.addEventListener('click', () => {
nav.querySelectorAll('.settings-tab').forEach(t => t.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('[id^="settings-panel-"]').forEach(p => p.classList.add('hidden'));
panel.classList.remove('hidden');
if (typeof onShow === 'function') {
try { onShow(panel); } catch (err) { console.error(`[settings tab ${id}]`, err); }
}
hooks.emit('settings.tab.shown', { tab: id, panel });
});
},
/** Open the main settings overlay. */
open() {
document.getElementById('settings-btn')?.click();
},
/** Open the settings overlay and switch to a specific tab. */
openTab(id) {
this.open();
// Settings opens via click; the tab might need a frame to render
requestAnimationFrame(() => {
document.querySelector(`#settings-nav [data-tab="${id}"]`)?.click();
});
},
};
// ── Dialog API (filled in by core dialog.js after load) ─────────
// Plugins call hyprcrd.dialog.confirm(...) / hyprcrd.dialog.show(...)
// instead of native browser confirm()/prompt() so the UI stays themed
// and consistent.
const dialog = {
show: null, // (opts) => Promise<value|null> — set by core dialog.js
/** Convenience confirm wrapper. Returns Promise<true|null>. */
async confirm(message, opts = {}) {
if (typeof dialog.show !== 'function') {
// Core dialog not loaded yet — fall back to native
return window.confirm(message) ? true : null;
}
return dialog.show({
title: opts.title || 'confirm',
message,
input: false,
danger: opts.danger ?? true,
okLabel: opts.okLabel || 'ok',
cancelLabel: opts.cancelLabel || 'cancel',
});
},
};
// ── Plugin metadata registry ────────────────────────────────────
@@ -128,6 +228,9 @@
editor,
markdown,
util,
api,
settings,
dialog,
plugins,
registerPlugin,
version: '1.0',