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 :
| Syntaxe | Rendu |
|---|---|
# Titre à ###### Titre | Titres 1 à 6. |
| Une ligne vide | Un 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 langue | Code, affiché tel qu'écrit. |
- élément, 1. élément, indentés pour imbriquer | Listes. |
- [ ] à faire, - [x] fait | Liste de tâches, cases non cliquables. |
> citation | Citation. |
[texte](https://…), <https://…>, une adresse nue | Liens. |
 | 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)
| Option | Défaut | Rôle |
|---|---|---|
headingOffset | 0 | Ajouté au niveau de chaque titre, jusqu'à 6 : 1 fait de # un <h2>, pour un texte placé sous le <h1> de la page. |
externalLinksInNewTab | false | Ouvre les liens externes dans un nouvel onglet. |
resolveImage | (url) => url : réécrit l'adresse d'une image avant sa vérification. | |
maxLength | 100000 | Caractè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 };
}}
/>| Prop | Rôle |
|---|---|
value, onChange | Le 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 . 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. |
preview | false retire l'onglet Aperçu. |
label, error, disabled, placeholder, rows, minHeight | Comme les autres champs. |