/** * Notes en markdown : rendu et bloc d'édition. * * Deux choses ici, qui vont ensemble. Le **rendu** d'abord — `rendreMarkdown` * transforme l'arbre de `markdown.js` en DOM, et sert aussi bien au panneau * qu'à la vue par mois. Le **bloc de notes** ensuite : l'aperçu, la barre * d'outils et la zone de saisie que les deux panneaux se partagent. * * **Aucun `innerHTML`.** Le DOM est bâti nœud par nœud. Un planning s'échange — * on en reçoit un par courriel, on en ouvre un que quelqu'un d'autre a écrit — * et une note est du texte libre : la seule façon sûre d'en afficher est de ne * jamais laisser le navigateur l'interpréter comme du balisage. Les URL sont * filtrées en amont par `urlSure`. * * **On lit d'abord, on écrit ensuite.** Le bloc s'ouvre sur l'aperçu quand la * note existe déjà, sur la saisie quand elle est vide. C'est l'usage : on ouvre * une phase commentée pour relire pourquoi elle est bloquée, et une phase * vierge pour y consigner quelque chose. Le crayon bascule d'un état à l'autre. */ import { analyserMarkdown, basculerCase } from './markdown.js'; // --------------------------------------------------------------------------- // Rendu // --------------------------------------------------------------------------- /** * Rend une note en fragment de DOM. * * @param {string} texte la note, en markdown. * @param {object} options * - casesActives : rend les cases à cocher cliquables. Faux ailleurs que dans * le panneau — la vue par mois est en lecture seule (décision 24), et une * case cochable y serait la seule chose qu'on puisse y modifier. * @returns {DocumentFragment} */ export function rendreMarkdown(texte, { casesActives = false } = {}) { const fragment = document.createDocumentFragment(); peindreBlocs(analyserMarkdown(texte), fragment, casesActives); return fragment; } function peindreBlocs(blocs, parent, casesActives) { for (const bloc of blocs) { switch (bloc.type) { case 'titre': { // La balise est décalée de deux rangs : le titre du panneau est un // `h2`, et une note ne peut pas ouvrir une section de rang supérieur à // ce qui la contient. Mais le **style** suit `data-niveau`, c'est-à-dire // le nombre de dièses écrits, et non la balise : le plafond à `h6` // écrase les rangs 4 à 6 sur une même balise, et styler par balise // rendait `#` plus petit que `##` — soit exactement l'inverse de ce que // l'auteur de la note a demandé. const titre = document.createElement(`h${Math.min(6, bloc.niveau + 2)}`); titre.className = 'md-titre'; titre.dataset.niveau = String(bloc.niveau); peindreInline(bloc.contenu, titre); parent.append(titre); break; } case 'paragraphe': { const paragraphe = document.createElement('p'); paragraphe.className = 'md-paragraphe'; peindreInline(bloc.contenu, paragraphe); parent.append(paragraphe); break; } case 'liste': parent.append(peindreListe(bloc, casesActives)); break; case 'citation': { const citation = document.createElement('blockquote'); citation.className = 'md-citation'; peindreBlocs(bloc.blocs, citation, casesActives); parent.append(citation); break; } case 'code': { const preforme = document.createElement('pre'); preforme.className = 'md-code'; const code = document.createElement('code'); code.textContent = bloc.lignes.join('\n'); preforme.append(code); parent.append(preforme); break; } case 'separateur': parent.append(document.createElement('hr')); break; } } } function peindreListe(bloc, casesActives) { const liste = document.createElement(bloc.ordonnee ? 'ol' : 'ul'); liste.className = 'md-liste'; // Une liste de cases à cocher perd sa puce : la case *est* la puce, et les // deux côte à côte décalent le texte sans rien ajouter. if (bloc.elements.some((element) => element.coche !== null)) { liste.classList.add('md-liste--taches'); } for (const element of bloc.elements) { const item = document.createElement('li'); if (element.coche === null) { peindreInline(element.contenu, item); } else { item.append(peindreTache(element, casesActives)); } peindreBlocs(element.blocs, item, casesActives); liste.append(item); } return liste; } /** * Élément de liste porteur d'une case. * * La case et son texte sont dans un même `