← Tous les articles

Types d'actions

Une action en un clic qui copie n'importe quelle page en Markdown propre

Vous lisez quelque chose qui vaut la peine d'être gardé et vous le voulez dans vos notes. Le copier-coller depuis le navigateur entraîne avec lui le menu de navigation, trois publicités, une barre de cookies et un fouillis de styles en ligne. Ce que vous voulez vraiment, c'est du Markdown propre : le titre, les paragraphes, les liens, les blocs de code — rien d'autre. Cet article construit un bouton d'action JustZix qui fait exactement cela, en un clic.

Pourquoi le Markdown, et pourquoi un bouton d'action

Le Markdown est le format de notes universel — il se colle proprement dans Obsidian, Notion, les issues GitHub, votre éditeur, un fichier texte simple. L'objectif ici est un bouton dans la barre d'actions JustZix qui convertit soit la sélection de texte actuelle, soit l'article entier en Markdown et le dépose sur votre presse-papiers. Pas de DevTools, pas d'application supplémentaire, pas de nettoyage manuel.

Une action de type BUTTON est parfaite pour cela : elle affiche un bouton étiqueté dans la barre d'actions, et cliquer dessus exécute votre JavaScript. Nous allons écrire ce JavaScript maintenant.

La stratégie de conversion

Nous parcourons le DOM récursivement. Pour chaque nœud, nous décidons : est-ce du texte (on l'émet), ou un élément que nous savons convertir (on émet le Markdown correspondant), ou quelque chose à ignorer (navigation, script, publicités). Tout ce que nous ne reconnaissons pas, nous y descendons en récursion — donc les conteneurs inconnus ne perdent pas leur contenu. Ce comportement de récursion par défaut est le repli vers le texte brut.

// Elements qu'on ne veut jamais dans la sortie
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';
}

Convertir les éléments en ligne

La conversion en ligne gère le formatage du fil de texte : gras, italique, code, liens. Elle renvoie une chaîne, en descendant en récursion dans les enfants pour que les balises en ligne imbriquées fonctionnent.

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;
  }
}

Notez la gestion des liens : nous résolvons les href relatifs contre location.href avec le constructeur URL, pour qu'un lien copié fonctionne encore quand vous le collez ailleurs.

Convertir les éléments de bloc

La conversion de bloc gère les pièces structurelles — titres, paragraphes, listes, blocs de code, citations — et les joint avec des lignes vides.

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 + ')' : '';
  }

  // Conteneur inconnu : recursion, on garde les enfants
  return [...node.childNodes].map(block)
    .filter(Boolean).join('\n\n');
}

La dernière ligne est le repli : un <div>, <section> ou <article> pour lequel nous n'avons aucune règle spéciale descend simplement en récursion dans ses enfants. Au pire, un élément exotique se dégrade en son texte brut — jamais en rien.

La sélection d'abord, l'article ensuite

Le bouton devrait être intelligent : si vous avez du texte sélectionné, convertissez juste cela ; sinon convertissez l'article principal. L'API Selection nous donne une plage que nous pouvons cloner dans un fragment.

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;
  }
  // Pas de selection : meilleure supposition du contenu principal
  return document.querySelector(
    'article, main, [role="main"], .post, .content'
  ) || document.body;
}

Un fragment de sélection cloné peut commencer en plein milieu d'un élément, donc la conversion de bloc peut voir des nœuds partiels — ce n'est pas un problème, notre règle de récursion par défaut le gère avec élégance.

Le câbler à une action BUTTON et copier

Maintenant assemblez les pièces, construisez la chaîne finale, et écrivez-la dans le presse-papiers. C'est le corps de votre action BUTTON.

const root = getRoot();
const md = [...root.childNodes]
  .map(block)
  .filter(Boolean)
  .join('\n\n')
  .replace(/\n{3,}/g, '\n\n')   // reduit les lignes vides supplementaires
  .trim();

navigator.clipboard.writeText(md)
  .then(() => JZ.toast('Copied ' + md.length + ' chars of Markdown'))
  .catch(() => {
    // Repli pour les restrictions de presse-papiers anciennes
    const ta = document.createElement('textarea');
    ta.value = md;
    document.body.appendChild(ta);
    ta.select();
    document.execCommand('copy');
    ta.remove();
    JZ.toast('Copied (fallback)');
  });

Pour transformer ceci en bouton : ajoutez une action de type BUTTON à la règle, étiquetez-la « Copier en Markdown », et collez le code ci-dessus comme gestionnaire. JZ.toast() est l'assistant JustZix pour une confirmation rapide sur la page — pratique car les écritures dans le presse-papiers sont autrement silencieuses.

Améliorations qui valent la peine d'être ajoutées

Pourquoi le presse-papiers, pas un téléchargement

Vous pourriez déclencher un téléchargement de fichier .md à la place, mais le presse-papiers gagne pour la prise de notes : vous cliquez le bouton, basculez vers votre application de notes, collez. Pas de fichier à trouver, pas de renommage, pas de nettoyage. L'appel navigator.clipboard.writeText a besoin d'un geste utilisateur — ce qu'un clic de bouton est, donc ça marche tout simplement.

À voir aussi

Un bouton, du Markdown propre, zéro nettoyage. Installez JustZix, ajoutez une action BUTTON avec le code ci-dessus, et commencez à collecter le web de la manière que votre application de notes veut vraiment.

Notez cet article

Aucune note — soyez le premier.

Essayez vous-même

Installez JustZix et collez n'importe quel snippet de cet article. Deux minutes de zéro à une règle fonctionnelle sur tous vos appareils.

Obtenir JustZix

Fonctionnalités · Comment ça marche · Exemples · Cas d'usage