136 lines
5.0 KiB
JavaScript
136 lines
5.0 KiB
JavaScript
// ── 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 => ({
|
|
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
|
}[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',
|
|
};
|
|
})();
|