plugin infrastruktur

This commit is contained in:
th committed 2026-04-10 07:58:07 +02:00
1 parent 1545b90f39
commit 3f4ba3c0f4
35 files changed
+1303 -195

No files matched your search

+135
View File
@@ -0,0 +1,135 @@
// ── hyprcrd Plugin API ──
//
// Loaded synchronously BEFORE any module script. Plugins and core both
// register against window.hyprcrd. Plugins MUST NOT import from core
// modules — use this global API instead so they remain decoupled.
//
// Available namespaces:
// hyprcrd.hooks — generic event bus (on/emit)
// hyprcrd.editor — toolbar command registry
// hyprcrd.markdown — fenced block + post-processing registry
// hyprcrd.util — DOM helpers exposed for plugin use
//
// Plugins are loaded as <script type="module"> tags after this script
// but before app.js. They self-register on module evaluation; the core
// then queries the registries when needed.
(function () {
'use strict';
// ── Generic event bus ───────────────────────────────────────────
const hookListeners = Object.create(null);
const hooks = {
on(name, fn) {
if (typeof fn !== 'function') return;
(hookListeners[name] ||= []).push(fn);
},
off(name, fn) {
const arr = hookListeners[name];
if (!arr) return;
const idx = arr.indexOf(fn);
if (idx >= 0) arr.splice(idx, 1);
},
emit(name, data) {
const arr = hookListeners[name];
if (!arr) return data;
for (const fn of arr.slice()) {
try {
const r = fn(data);
if (r !== undefined) data = r;
} catch (err) {
console.error(`[hyprcrd hook ${name}]`, err);
}
}
return data;
},
};
// ── Editor command registry ─────────────────────────────────────
// Toolbar buttons with `data-md="<name>"` dispatch to handlers
// registered here. Plugins inject buttons + register handlers.
const editorCommands = Object.create(null);
const editor = {
registerCommand(name, fn) {
if (typeof fn !== 'function') return;
editorCommands[name] = fn;
},
unregisterCommand(name) {
delete editorCommands[name];
},
getCommand(name) {
return editorCommands[name];
},
// Set by editor.js after init — gives plugins access to
// insertMd(before, after, placeholder) and the textarea body
// without forcing an import dependency.
api: null,
};
// ── Markdown fenced block registry ──────────────────────────────
// Each entry: { lang: string, handler: (content) => string }
// The handler receives the raw text inside ```<lang> ... ``` and
// must return safe HTML (it will pass through DOMPurify after).
const fencedBlocks = [];
// Pre/post processors run on the full source text, before/after
// marked.parse(). Useful for non-fenced syntax extensions.
const preProcessors = [];
const postProcessors = [];
const markdown = {
registerFencedBlock(lang, handler) {
if (typeof handler !== 'function') return;
fencedBlocks.push({ lang, handler });
},
getFencedBlocks() {
return fencedBlocks.slice();
},
registerPreProcessor(fn) {
if (typeof fn === 'function') preProcessors.push(fn);
},
getPreProcessors() {
return preProcessors.slice();
},
registerPostProcessor(fn) {
if (typeof fn === 'function') postProcessors.push(fn);
},
getPostProcessors() {
return postProcessors.slice();
},
};
// ── Util helpers (mirror of internal util.js) ───────────────────
const util = {
$(idOrSel) {
if (typeof idOrSel !== 'string') return null;
if (idOrSel.startsWith('#') || idOrSel.includes(' ') || idOrSel.includes('.') || idOrSel.includes('[')) {
return document.querySelector(idOrSel);
}
return document.getElementById(idOrSel);
},
$$(sel) {
return Array.from(document.querySelectorAll(sel));
},
esc(s) {
return String(s ?? '').replace(/[&<>"']/g, c => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
}[c]));
},
};
// ── Plugin metadata registry ────────────────────────────────────
// Plugins can register themselves here for introspection / debug.
const plugins = [];
const registerPlugin = (meta) => {
if (meta && meta.name) plugins.push(meta);
};
window.hyprcrd = {
hooks,
editor,
markdown,
util,
plugins,
registerPlugin,
version: '1.0',
};
})();