Aller au contenu

Markdown

renderMarkdown transforme du Markdown en HTML sûr, MarkdownView l'affiche avec la typographie du thème, et MarkdownEditor l'écrit avec un aperçu et des images.

Une description, une note, une réponse : beaucoup de textes de l'admin s'écrivent en Markdown et s'affichent ensuite sur le site. Le core fournit les trois pièces, sans dépendance : le rendu, l'affichage et l'éditeur.

Import

import { renderMarkdown, markdownToText, safeUrl, escapeHtml } from '@fraza/core/shared/markdown';
import MarkdownView from '@fraza/core/shared/components/MarkdownView';
import MarkdownEditor from '@fraza/core/shared/components/MarkdownEditor';

@fraza/core/shared/markdown et MarkdownView sont neutres : un Server Component, un composant client et un test les importent pareil. MarkdownEditor est un composant client.

La syntaxe reconnue

Celle des commentaires de GitHub et de Gitea, pour ce qu'on écrit vraiment :

SyntaxeRendu
# Titre à ###### TitreTitres 1 à 6.
Une ligne videUn nouveau paragraphe. Un simple retour à la ligne est gardé.
**gras**, __gras__, *italique*, _italique_, ~~barré~~Emphase. Un _ au milieu d'un mot (nom_de_variable) reste tel quel.
`code`, et un bloc entre ``` avec sa langueCode, affiché tel qu'écrit.
- élément, 1. élément, indentés pour imbriquerListes.
- [ ] à faire, - [x] faitListe de tâches, cases non cliquables.
> citationCitation.
[texte](https://…), <https://…>, une adresse nueLiens.
![texte](/chemin.png "titre")Image.
Un tableau avec | et une ligne |---|:--:|Tableau, avec l'alignement des colonnes.
---Filet.
\*Le caractère seul, sans effet.

La sécurité

Le rendu est sûr par construction, pas par un filtre passé après coup : chaque caractère du texte est échappé, et les seules balises du résultat sont celles que le moteur écrit. Du HTML tapé dans le texte s'affiche comme du texte.

Un lien ou une image ne garde son adresse que si elle est relative (/a, a/b, #x) ou utilise un schéma permis : http, https, et mailto pour un lien. javascript:, data:, une adresse qui commence par // ou qui contient une espace ou un caractère de contrôle sont refusés : le lien devient son texte, l'image son texte alternatif. Un lien externe reçoit rel="noopener noreferrer nofollow".

Le résultat peut donc être posé en innerHTML, ce que fait MarkdownView.

renderMarkdown(source, options)

OptionDéfautRôle
headingOffset0Ajouté au niveau de chaque titre, jusqu'à 6 : 1 fait de # un <h2>, pour un texte placé sous le <h1> de la page.
externalLinksInNewTabfalseOuvre les liens externes dans un nouvel onglet.
resolveImage(url) => url : réécrit l'adresse d'une image avant sa vérification.
maxLength100000Caractères lus, au plus.

markdownToText(source, { maxLength })

Le texte sans la syntaxe, pour un extrait ou une méta-description : markdownToText(description, { maxLength: 160 }). Les images et les blocs de code sont retirés, et la coupe tombe sur un mot, suivie de « … ».

MarkdownView

<MarkdownView value={item.description} headingOffset={1} empty={<p>Pas de description.</p>} />

Affiche le Markdown avec la typographie de la charte, en jetons du thème : il suit les couleurs du site et le mode sombre. empty s'affiche quand il n'y a rien à rendre. className s'ajoute au conteneur.

MarkdownEditor

Une barre d'outils au-dessus d'une zone de texte, et un onglet Aperçu qui montre le texte comme MarkdownView le rendra. Les boutons : annuler, rétablir, gras, italique, barré, lien, code, titres 1 à 3, citation, listes à puces, numérotées et de tâches, tableau, et image quand l'envoi est branché.

<MarkdownEditor
  label="Description"
  value={description}
  onChange={setDescription}
  onUploadImage={async (file) => {
    const body = new FormData();
    body.append('file', file);
    const response = await fetch('/za/api/mon-module/admin/images', { method: 'POST', body });
    const data = await response.json();
    if (!response.ok) throw new Error(data.message);
    return { url: data.url };
  }}
/>
PropRôle
value, onChangeLe texte. onChange reçoit la nouvelle valeur.
onUploadImage(file) => Promise<{ url, alt? }>. Branché, une image se colle, se glisse ou se choisit : l'éditeur écrit un repère au curseur, attend l'adresse, puis le remplace par ![alt](url). Un refus levé s'affiche sous le champ. Le core ne choisit pas où l'image est rangée : c'est le module, avec le stockage.
previewfalse retire l'onglet Aperçu.
label, error, disabled, placeholder, rows, minHeightComme les autres champs.

Sur cette page