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 ? '' : '';
}
// 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
- Les tableaux — ajoutez un cas
TABLEqui émet des lignes délimitées par des barres verticales avec un séparateur---après l'en-tête. - Le langage du code — beaucoup de sites mettent le langage dans une classe comme
language-js; lisez-le et ajoutez-le après les triples accents graves d'ouverture. - Le front matter — préfixez avec le titre de la page et l'URL en petit en-tête pour savoir d'où vient la note.
- Le retrait des images — si vous ne voulez que du texte, supprimez entièrement le cas
IMG.
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
- Une feuille de style d'impression sur mesure pour de meilleurs PDF — une autre façon d'extraire du contenu propre d'une page en désordre.
- Une surcouche d'audit d'accessibilité en direct — plus de JavaScript pratique de parcours du DOM.
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.