Draft.js
Un framework robuste et flexible pour la création d'éditeurs de texte riches dans vos applications React.
Qu'est-ce que Draft.js ?
Imaginez que vous souhaitiez créer un éditeur de texte en ligne, comme celui de Medium, Google Docs ou WordPress. Vous auriez besoin de gérer le formatage (gras, italique, listes), les liens, les images et bien d'autres fonctionnalités. C'est un défi technique considérable.
Draft.js est un framework créé par Facebook qui permet aux développeurs de construire des éditeurs de texte riches dans les applications web. C'est comme une boîte à outils spécialisée qui fournit une base solide pour créer des expériences d'édition de texte sophistiquées et personnalisables.
Pourquoi Draft.js est-il important ?
Puissance et flexibilité
Il permet de construire des expériences d'édition allant des simples commentaires aux outils de création de contenu complets.
Personnalisation
Contrairement aux éditeurs WYSIWYG prêts à l'emploi, Draft.js peut être adapté à n'importe quel besoin ou style de design.
En résumé, Draft.js permet aux entreprises de créer des expériences d'édition de texte sur mesure pour leurs applications web, qu'il s'agisse d'un simple outil de commentaires, d'un CMS complet, ou de toute autre interface où les utilisateurs doivent saisir et formater du texte.
Fonctionnement technique
Draft.js est un framework d'édition de texte riche pour React qui utilise un modèle d'état immuable pour gérer le contenu de l'éditeur. Il est construit autour du concept d'
EditorState
, qui encapsule tout l'état de l'éditeur, y compris le contenu, les sélections et l'historique des opérations.
Les concepts fondamentaux
Éditeur de base
Créer un éditeur Draft.js de base est simple. Voici comment mettre en place un éditeur simple avec React.
import React, { useState } from 'react';
import { Editor, EditorState } from 'draft-js';
import 'draft-js/dist/Draft.css';
function MyEditor() {
const [editorState, setEditorState] = useState(
EditorState.createEmpty()
);
const onChange = (newEditorState) => {
setEditorState(newEditorState);
};
return (
<div className="editor-container">
<Editor
editorState={editorState}
onChange={onChange}
placeholder="Commencez à écrire..."
/>
</div>
);
}
export default MyEditor;
Rich Text Editor
Draft.js brille vraiment lorsqu'il s'agit de créer des éditeurs de texte riche avec des fonctionnalités de formatage. Voici un exemple plus complet qui inclut des contrôles pour le formatage du texte et des types de blocs.
import React, { useState } from 'react';
import {
Editor,
EditorState,
RichUtils,
convertToRaw,
convertFromRaw
} from 'draft-js';
import 'draft-js/dist/Draft.css';
function RichTextEditor() {
const [editorState, setEditorState] = useState(
EditorState.createEmpty()
);
const onChange = (newEditorState) => {
setEditorState(newEditorState);
};
const handleKeyCommand = (command, editorState) => {
const newState = RichUtils.handleKeyCommand(editorState, command);
if (newState) {
onChange(newState);
return 'handled';
}
return 'not-handled';
};
const toggleBlockType = (blockType) => {
onChange(RichUtils.toggleBlockType(editorState, blockType));
};
const toggleInlineStyle = (inlineStyle) => {
onChange(RichUtils.toggleInlineStyle(editorState, inlineStyle));
};
// Boutons pour le formatage
const StyleButton = ({ onToggle, style, active, label }) => {
return (
<button
className={`style-button ${active ? 'active' : ''}`}
onMouseDown={(e) => {
e.preventDefault();
onToggle(style);
}}
>
{label}
</button>
);
};
// Styles de texte disponibles
const INLINE_STYLES = [
{ label: 'Gras', style: 'BOLD' },
{ label: 'Italique', style: 'ITALIC' },
{ label: 'Souligné', style: 'UNDERLINE' },
{ label: 'Monospace', style: 'CODE' }
];
// Types de blocs disponibles
const BLOCK_TYPES = [
{ label: 'H1', style: 'header-one' },
{ label: 'H2', style: 'header-two' },
{ label: 'H3', style: 'header-three' },
{ label: 'Citation', style: 'blockquote' },
{ label: 'Liste à puces', style: 'unordered-list-item' },
{ label: 'Liste numérotée', style: 'ordered-list-item' },
{ label: 'Code Block', style: 'code-block' }
];
// Créer les boutons pour les styles inline
const InlineStyleControls = () => {
const currentStyle = editorState.getCurrentInlineStyle();
return (
<div className="style-controls">
{INLINE_STYLES.map((type) => (
<StyleButton
key={type.style}
active={currentStyle.has(type.style)}
label={type.label}
onToggle={toggleInlineStyle}
style={type.style}
/>
))}
</div>
);
};
// Créer les boutons pour les types de blocs
const BlockStyleControls = () => {
const selection = editorState.getSelection();
const blockType = editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType();
return (
<div className="block-style-controls">
{BLOCK_TYPES.map((type) => (
<StyleButton
key={type.style}
active={type.style === blockType}
label={type.label}
onToggle={toggleBlockType}
style={type.style}
/>
))}
</div>
);
};
// Sauvegarde du contenu sous forme JSON
const saveContent = () => {
const contentState = editorState.getCurrentContent();
const rawContent = convertToRaw(contentState);
const savedData = JSON.stringify(rawContent);
localStorage.setItem('draftEditorContent', savedData);
console.log('Contenu sauvegardé:', savedData);
};
// Chargement du contenu
const loadContent = () => {
const savedData = localStorage.getItem('draftEditorContent');
if (savedData) {
const rawContent = JSON.parse(savedData);
const contentState = convertFromRaw(rawContent);
const newEditorState = EditorState.createWithContent(contentState);
onChange(newEditorState);
}
};
return (
<div className="rich-editor-container">
<div className="toolbar">
<InlineStyleControls />
<BlockStyleControls />
<button onClick={saveContent}>Sauvegarder</button>
<button onClick={loadContent}>Charger</button>
</div>
<div className="editor">
<Editor
editorState={editorState}
onChange={onChange}
handleKeyCommand={handleKeyCommand}
placeholder="Commencez à écrire du texte riche..."
/>
</div>
</div>
);
}
export default RichTextEditor;
Entités personnalisées
Draft.js permet de définir des entités personnalisées comme des liens, des mentions, ou d'autres éléments interactifs dans le texte. Voici un exemple d'implémentation d'entités de lien.
import React, { useState } from 'react';
import {
Editor,
EditorState,
RichUtils,
CompositeDecorator,
Entity,
Modifier,
ContentState
} from 'draft-js';
import 'draft-js/dist/Draft.css';
// Définition du composant pour afficher les liens
const Link = (props) => {
const { url } = props.contentState.getEntity(props.entityKey).getData();
return (
<a href={url} className="editor-link" title={url}>
{props.children}
</a>
);
};
// Fonction pour trouver les entités de lien
const findLinkEntities = (contentBlock, callback, contentState) => {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
contentState.getEntity(entityKey).getType() === 'LINK'
);
},
callback
);
};
function EntityEditor() {
// Création d'un décorateur pour les liens
const decorator = new CompositeDecorator([
{
strategy: findLinkEntities,
component: Link,
},
]);
const [editorState, setEditorState] = useState(
EditorState.createEmpty(decorator)
);
const [urlValue, setUrlValue] = useState('');
const [showURLInput, setShowURLInput] = useState(false);
const onChange = (newEditorState) => {
setEditorState(newEditorState);
};
// Fonction pour ajouter un lien
const promptForLink = (e) => {
e.preventDefault();
const selection = editorState.getSelection();
if (!selection.isCollapsed()) {
setShowURLInput(true);
}
};
const confirmLink = (e) => {
e.preventDefault();
// Création de l'entité lien
const contentState = editorState.getCurrentContent();
const contentStateWithEntity = contentState.createEntity(
'LINK',
'MUTABLE',
{ url: urlValue }
);
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
// Application de l'entité au texte sélectionné
let nextEditorState = EditorState.set(editorState, {
currentContent: contentStateWithEntity
});
nextEditorState = RichUtils.toggleLink(
nextEditorState,
nextEditorState.getSelection(),
entityKey
);
// Mise à jour de l'état
setEditorState(nextEditorState);
setShowURLInput(false);
setUrlValue('');
};
const onURLChange = (e) => {
setUrlValue(e.target.value);
};
return (
<div className="entity-editor-container">
<div className="controls">
<button onMouseDown={promptForLink}>Ajouter un lien</button>
</div>
{showURLInput && (
<div className="url-input">
<input
onChange={onURLChange}
value={urlValue}
placeholder="http://example.com/"
/>
<button onMouseDown={confirmLink}>Confirmer</button>
</div>
)}
<div className="editor">
<Editor
editorState={editorState}
onChange={onChange}
placeholder="Sélectionnez du texte et ajoutez un lien..."
/>
</div>
</div>
);
}
export default EntityEditor;
Concepts clés de Draft.js
- EditorState - Objet immuable qui représente l'état complet de l'éditeur
- ContentState - Représentation du contenu de l'éditeur (texte, blocs, entités)
- SelectionState - Position du curseur et sélection de texte
- ContentBlock - Unité de base du contenu (paragraphe, titre, liste, etc.)
- Decorator - Système pour personnaliser l'affichage de portions spécifiques du texte
- Entity - Métadonnées associées à des portions de texte (liens, mentions, etc.)
- Modifier - Utilitaire pour appliquer des modifications au contenu
Forces et limitations
Forces
- Architecture immuable robuste
- Hautement personnalisable
- Gestion avancée des sélections
- Prise en charge des raccourcis clavier
- Développé et utilisé par Facebook
Limitations
- Courbe d'apprentissage abrupte
- Documentation parfois incomplète
- Complexité pour des fonctionnalités avancées
- Mise en page limitée (pas de tables natives)
- Pas de support natif pour le markdown
Megadraft : Une extension de Draft.js
Megadraft est une extension de Draft.js qui propose une expérience plus complète avec des plugins prêts à l'emploi. Créé par Globo.com, il simplifie l'utilisation de Draft.js en fournissant :
- Une barre d'outils prête à l'emploi
- Un système de plugins extensible
- Des plugins intégrés pour les images, vidéos, et plus
- Une meilleure expérience pour la gestion des blocs
- Un style par défaut moderne et personnalisable
Megadraft est particulièrement utile lorsque vous avez besoin de créer rapidement un éditeur riche sans passer par toute la complexité de la configuration manuelle de Draft.js.
Cas d'usage
Plateformes de blogging
Idéal pour créer des éditeurs WYSIWYG personnalisés pour les CMS et les plateformes de blogging comme Medium ou WordPress.
Systèmes de commentaires
Permet aux utilisateurs de laisser des commentaires formatés avec du texte riche, des liens et des mentions d'utilisateurs.
Outils de prise de notes
Excellent pour créer des applications de prise de notes comme Notion ou Evernote, avec structure flexible et formatage avancé.
Outils collaboratifs
Peut être intégré dans des systèmes d'édition collaborative en temps réel en utilisant des bibliothèques comme Yjs ou ShareDB.
Sites qui utilisent Draft.js
De nombreuses entreprises et plateformes de premier plan font confiance à Draft.js pour leurs éditeurs de texte :