← Alle Beiträge

Aktionstypen

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 ? '![' + alt + '](' + 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

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 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.

Probiere es selbst aus

Installiere JustZix und füge ein beliebiges Snippet aus diesem Artikel ein. Zwei Minuten von null bis zu einer funktionierenden Regel auf allen deinen Geräten.

JustZix holen

Funktionen · So funktioniert es · Beispiele · Anwendungsfälle