Eine Ein-Klick-Aktion, die jede Seite als sauberes Markdown kopiert
Du liest etwas, das es wert ist, behalten zu werden, und willst es in deinen Notizen haben. Kopieren-und-Einfügen aus dem Browser schleppt das Navigationsmenü, drei Anzeigen, eine Cookie-Leiste und ein Gewirr aus Inline-Stilen mit. Was du eigentlich willst, ist sauberes Markdown: die Überschrift, die Absätze, die Links, die Codeblöcke — nichts sonst. Dieser Artikel baut einen JustZix-Aktions-Button, der genau das tut, mit einem Klick.
Warum Markdown und warum ein Aktions-Button
Markdown ist das universelle Notizformat — es fügt sich sauber in Obsidian, Notion, GitHub-Issues, deinen Editor, eine reine Textdatei ein. Das Ziel hier ist ein Button in der JustZix-Aktionsleiste, der entweder die aktuelle Textauswahl oder den ganzen Artikel in Markdown umwandelt und auf deine Zwischenablage legt. Keine DevTools, keine zusätzliche App, kein manuelles Aufräumen.
Eine Aktion vom Typ BUTTON ist dafür perfekt: Sie zeigt einen beschrifteten Button in der Aktionsleiste, und ein Klick führt dein JavaScript aus. Dieses JavaScript schreiben wir jetzt.
Die Umwandlungsstrategie
Wir durchlaufen das DOM rekursiv. Für jeden Knoten entscheiden wir: Ist es Text (gib ihn aus) oder ein Element, das wir umzuwandeln wissen (gib Markdown dafür aus) oder etwas zum Überspringen (Navigation, Skript, Anzeigen). Alles, was wir nicht erkennen, durchlaufen wir rekursiv — sodass unbekannte Hüllen ihren Inhalt nicht verlieren. Dieses Standardmäßig-rekursiv-Verhalten ist der Rückfall auf reinen Text.
// Elements we never want in the output
const SKIP = new Set([
'SCRIPT', 'STYLE', 'NAV', 'HEADER', 'FOOTER', 'ASIDE',
'NOSCRIPT', 'IFRAME', 'FORM', 'BUTTON', 'SVG'
]);
function isHidden(el) {
const s = getComputedStyle(el);
return s.display === 'none' || s.visibility === 'hidden';
}
Inline-Elemente umwandeln
Die Inline-Umwandlung behandelt die Formatierung im Textfluss: fett, kursiv, Code, Links. Sie gibt einen String zurück und durchläuft Kindelemente rekursiv, damit verschachtelte Inline-Tags funktionieren.
function inline(node) {
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent.replace(/\s+/g, ' ');
}
if (node.nodeType !== Node.ELEMENT_NODE) return '';
if (SKIP.has(node.tagName) || isHidden(node)) return '';
const inner = [...node.childNodes].map(inline).join('');
switch (node.tagName) {
case 'STRONG': case 'B': return '**' + inner.trim() + '**';
case 'EM': case 'I': return '*' + inner.trim() + '*';
case 'CODE': return '`' + inner.trim() + '`';
case 'BR': return ' \n';
case 'A': {
const href = node.getAttribute('href') || '';
const abs = href ? new URL(href, location.href).href : '';
return abs ? '[' + inner.trim() + '](' + abs + ')' : inner;
}
default: return inner;
}
}
Beachte die Link-Behandlung: Wir lösen relative hrefs mit dem URL-Konstruktor gegen location.href auf, sodass ein kopierter Link auch funktioniert, wenn du ihn anderswo einfügst.
Block-Elemente umwandeln
Die Block-Umwandlung behandelt die strukturellen Teile — Überschriften, Absätze, Listen, Codeblöcke, Zitate — und verbindet sie mit Leerzeilen.
function block(node) {
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent.trim();
}
if (node.nodeType !== Node.ELEMENT_NODE) return '';
if (SKIP.has(node.tagName) || isHidden(node)) return '';
const t = node.tagName;
if (/^H[1-6]$/.test(t)) {
return '#'.repeat(+t[1]) + ' ' + inline(node).trim();
}
if (t === 'P') return inline(node).trim();
if (t === 'BLOCKQUOTE') {
return inline(node).trim()
.split('\n').map(l => '> ' + l).join('\n');
}
if (t === 'PRE') {
return '```\n' + node.textContent.replace(/\n$/, '') + '\n```';
}
if (t === 'UL' || t === 'OL') {
const ordered = t === 'OL';
return [...node.children]
.filter(li => li.tagName === 'LI')
.map((li, i) => (ordered ? (i + 1) + '. ' : '- ')
+ inline(li).trim())
.join('\n');
}
if (t === 'HR') return '---';
if (t === 'IMG') {
const alt = node.getAttribute('alt') || '';
const src = node.src || '';
return src ? '' : '';
}
// Unknown wrapper: recurse, keep the children
return [...node.childNodes].map(block)
.filter(Boolean).join('\n\n');
}
Die letzte Zeile ist der Rückfall: Ein <div>, <section> oder <article>, für das wir keine Spezialregel haben, durchläuft einfach seine Kindelemente. Schlimmstenfalls degradiert ein exotisches Element zu seinem reinen Text — nie zu nichts.
Erst die Auswahl, dann der Artikel
Der Button sollte schlau sein: Hast du Text ausgewählt, wandle nur den um; sonst wandle den Hauptartikel um. Die Selection-API gibt uns einen Bereich, den wir in ein Fragment klonen können.
function getRoot() {
const sel = window.getSelection();
if (sel && sel.rangeCount && !sel.isCollapsed) {
const frag = sel.getRangeAt(0).cloneContents();
const wrap = document.createElement('div');
wrap.appendChild(frag);
return wrap;
}
// No selection: best guess at the main content
return document.querySelector(
'article, main, [role="main"], .post, .content'
) || document.body;
}
Ein geklontes Auswahl-Fragment kann mitten in einem Element beginnen, sodass die Block-Umwandlung Teilknoten sehen kann — das ist in Ordnung, unsere Standardmäßig-rekursiv-Regel behandelt es elegant.
An eine BUTTON-Aktion verdrahten und kopieren
Jetzt setze die Teile zusammen, bau den finalen String und schreib ihn in die Zwischenablage. Das ist der Körper deiner BUTTON-Aktion.
const root = getRoot();
const md = [...root.childNodes]
.map(block)
.filter(Boolean)
.join('\n\n')
.replace(/\n{3,}/g, '\n\n') // collapse extra blank lines
.trim();
navigator.clipboard.writeText(md)
.then(() => JZ.toast('Copied ' + md.length + ' chars of Markdown'))
.catch(() => {
// Fallback for older clipboard restrictions
const ta = document.createElement('textarea');
ta.value = md;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
JZ.toast('Copied (fallback)');
});
Um daraus einen Button zu machen: Füge der Regel eine Aktion vom Typ BUTTON hinzu, beschrifte sie mit „Als Markdown kopieren" und füge den Code oben als ihren Handler ein. JZ.toast() ist der JustZix-Helfer für eine schnelle Bestätigung auf der Seite — praktisch, weil Schreibvorgänge in die Zwischenablage sonst lautlos sind.
Verfeinerungen, die sich zu ergänzen lohnen
- Tabellen — füge einen
TABLE-Fall hinzu, der pipe-getrennte Zeilen mit einem----Trenner nach der Kopfzeile ausgibt. - Code-Sprache — viele Seiten legen die Sprache in eine Klasse wie
language-js; lies sie aus und hänge sie nach den öffnenden drei Backticks an. - Front Matter — stell den Seitentitel und die URL als kleinen Kopf voran, damit du weißt, woher die Notiz kam.
- Bilder entfernen — willst du nur Text, lass den
IMG-Fall ganz weg.
Warum die Zwischenablage, kein Download
Du könntest stattdessen einen .md-Datei-Download auslösen, aber die Zwischenablage gewinnt fürs Notizenmachen: Du klickst den Button, wechselst zu deiner Notizen-App, fügst ein. Keine Datei zu finden, kein Umbenennen, kein Aufräumen. Der Aufruf navigator.clipboard.writeText braucht eine Nutzergeste — und ein Button-Klick ist eine, also funktioniert es einfach.
Siehe auch
- Ein eigenes Druck-Stylesheet für bessere PDFs — ein anderer Weg, sauberen Inhalt aus einer unübersichtlichen Seite zu extrahieren.
- Ein Live-Barrierefreiheits-Audit-Overlay — mehr praktisches DOM-durchlaufendes JavaScript.
Ein Button, sauberes Markdown, null Aufräumen. Installiere JustZix, füge eine BUTTON-Aktion mit dem Code oben hinzu und beginne, das Web so zu sammeln, wie deine Notizen-App es tatsächlich haben will.
Bewerte diesen Beitrag
Noch keine Bewertungen — sei der Erste.