email plugin
This commit is contained in:
1 parent
3f4ba3c0f4
commit
cc97754ae6
16 files changed
+2979
-5
No files matched your search
@@ -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 @@
|
||||
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
||||
}[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',
|
||||
|
||||
Reference in new issue
Block a user