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

+60 -20
View File
@@ -11,6 +11,32 @@ marked.use({ breaks: true, gfm: true, renderer: {
}
}});
// ── Built-in fenced block handlers ──
// Registered through the same plugin API so plugins and core use one path.
if (typeof window !== 'undefined' && window.hyprcrd?.markdown) {
const md = window.hyprcrd.markdown;
// ```gallery — image grid
md.registerFencedBlock('gallery', (content, { esc: e }) => {
const imgs = [];
const imgRe = /!\[([^\]]*)\]\(([^)]+)\)/g;
let m;
while ((m = imgRe.exec(content)) !== null) imgs.push({ alt: m[1], src: m[2].split(/\s/)[0] });
if (!imgs.length) return '';
return '<div class="md-gallery">' + imgs.map(i =>
`<div class="md-gallery-item"><img src="${i.src}" alt="${e(i.alt)}" loading="lazy"></div>`
).join('') + '</div>';
});
// ```tree / ```bullets — custom list rendering (handled via post-processor)
const listHandler = (kind) => (content, { normalizeListBlock: norm }) => {
const normalized = norm(content);
return `\n<!-- list:${kind} -->\n${normalized}\n`;
};
md.registerFencedBlock('tree', listHandler('tree'));
md.registerFencedBlock('bullets', listHandler('bullets'));
}
const purifyConfig = {
ALLOWED_TAGS: ['h1','h2','h3','h4','h5','h6','p','br','hr','ul','ol','li','a','strong','em','del','code','pre','blockquote','table','thead','tbody','tr','th','td','img','input','span','div','audio','source'],
ALLOWED_ATTR: ['href','src','alt','width','height','type','checked','disabled','class','style','data-card-id','title','controls','preload','target','rel','align'],
@@ -79,27 +105,32 @@ export function renderMd(src) {
'</tbody></table>\n';
});
// ── Fenced block registry ─────────────────────────────────────
// Apply all registered ```<lang> handlers (built-in + plugins).
// Plugins register via window.hyprcrd.markdown.registerFencedBlock().
const fencedBlocks = (typeof window !== 'undefined' && window.hyprcrd?.markdown?.getFencedBlocks)
? window.hyprcrd.markdown.getFencedBlocks()
: [];
for (const { lang, handler } of fencedBlocks) {
// Escape regex metachars in lang (allow simple identifiers)
const safeLang = String(lang).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp('```' + safeLang + '\\s*\\n([\\s\\S]*?)(?:```|$)', 'g');
processed = processed.replace(re, (_, content) => {
try { return handler(content, { esc, normalizeListBlock }); }
catch (err) { console.error(`[markdown fenced ${lang}]`, err); return ''; }
});
}
// ── Plugin pre-processors ─────────────────────────────────────
const preProcessors = (typeof window !== 'undefined' && window.hyprcrd?.markdown?.getPreProcessors)
? window.hyprcrd.markdown.getPreProcessors()
: [];
for (const fn of preProcessors) {
try { processed = fn(processed) ?? processed; }
catch (err) { console.error('[markdown pre]', err); }
}
processed = processed
// ASCII canvas block: ```ascii
.replace(/```ascii\s*\n([\s\S]*?)(?:```|$)/g, (_, content) => {
return `<pre class="ascii-art">${esc(content.replace(/\s+$/, ''))}</pre>`;
})
// Gallery fenced block: ```gallery
.replace(/```gallery\s*\n([\s\S]*?)(?:```|$)/g, (_, content) => {
const imgs = [];
const imgRe = /!\[([^\]]*)\]\(([^)]+)\)/g;
let m;
while ((m = imgRe.exec(content)) !== null) imgs.push({ alt: m[1], src: m[2].split(/\s/)[0] });
if (!imgs.length) return '';
return '<div class="md-gallery">' + imgs.map(i =>
`<div class="md-gallery-item"><img src="${i.src}" alt="${esc(i.alt)}" loading="lazy"></div>`
).join('') + '</div>';
})
// Custom fenced list blocks: ```tree / ```bullets with or without closing ```
.replace(/```(tree|bullets)\s*\n([\s\S]*?)(?:```|$)/g, (_, kind, content) => {
const normalized = normalizeListBlock(content);
return `\n<!-- list:${kind} -->\n${normalized}\n`;
})
// Help parser: ensure a list can start after text without requiring manual blank line
.replace(/([^\n])\n(?=(?: {0,3}[-*+]\s| {0,3}\d+\.\s))/g, '$1\n\n')
// Preserve extra blank lines as <br> spacers (3+ newlines = 1+ extra blank lines)
@@ -124,6 +155,15 @@ export function renderMd(src) {
html = html.replace(/<!--\s*list:tree\s*-->\s*<ul>/g, '<ul class="tree-list">');
html = html.replace(/<!--\s*list:bullets\s*-->\s*<ul>/g, '<ul class="bullet-list">');
// ── Plugin post-processors (run on HTML, before sanitize) ──
const postProcessors = (typeof window !== 'undefined' && window.hyprcrd?.markdown?.getPostProcessors)
? window.hyprcrd.markdown.getPostProcessors()
: [];
for (const fn of postProcessors) {
try { html = fn(html) ?? html; }
catch (err) { console.error('[markdown post]', err); }
}
html = typeof DOMPurify !== 'undefined' ? DOMPurify.sanitize(html, purifyConfig) : html;
return html;
}