Lorsque l'on crée un système de gestion de contenu, l'édition de page est toujours une étape complexe. C'est une problèmatique que j'ai rencontré en travaillant sur Boxraiser (une plateforme à la shopify, mais spécialisé sur l'abonnement en plus du e-commerce traditionnel). Le site disposait d'un éditeur de page fonctionnant avec tinyMCE et le module bootstrap mais l'ergonomie n'était pas évidente et créer des pages complexes était impossible.
Aussi, dans le cadre de la refonte j'ai voulu améliorer l'expérience d'édition de page et je me suis lancé dans la création d'un éditeur de page visuel open source pour l'occasion.
Pourquoi un nouvel éditeur ?
Avant de me lancer dans une telle aventure j'ai envisagé d'autres solutions mais je n'ai pas forcément été satisfait par ce qui existait.
- TinyMCE, CKEditor ou Editor.js sont des éditeurs visuels centrés autour de l'édition de textes riches. Ils sont très efficaces pour éditer du contenu (article) mais il ce ne sont pas des solutions viable pour créer des pages complètes.
- Craft.JS est un éditeur de page visuel mais offre un trop grand niveau de contrôle sur les éléments. Cette approche implique une certaine sensibilitée de la part de l'administrateur pour créer des pages cohérentes.
Ma solution
Lorsque je travaillais sur des sites WordPress une approche qui fonctionnait bien était de préparer des blocs en amont, puis de permettre aux utilisateurs de gérer le contenu de ces blocs à l'aide de plugins comme ACF. Mon objectif était donc de reproduire une expérience similaire mais avec un éditeur qui ne dépende pas d'un framework ou d'un CMS.
La solution a donc été de créer l'éditeur dont j'avais besoin et avec l'accord de Boxraiser on a décidé de le rendre open source. Aussi, j'ai le plaisir de vous présenter @boxraiser/visual-editor !
Une approche "data first"
L'objectif de l'éditeur est donc de séparer le contenu (qui sera administré par l'utilisateur) et le design qui sera géré par le développeur. On offre à l'administrateur la possibilité de sélectionner les blocs à utiliser dans sa page et de choisir les contenu associés. Prenons un exemple concret avec un bandeau :
On commence par enregistrer notre bloc avec les champs que l'on souhaite pouvoir gérer.
import { VisualEditor, HTMLText, Repeater, Text, Row, Select, Range } from '@boxraiser/visual-editor'
let editor = new VisualEditor()
editor.registerComponent('hero', {
title: 'Hero',
category: 'Banner',
fields: [
Text('title', {multiline: false}),
HTMLText('content'),
Repeater('buttons', {
title: 'Boutons',
addLabel: 'Add a new button',
fields: [
Row([
Text('label', { label: 'Label', default: 'Call to action' }),
Text('url', { label: 'Link' }),
Select('type', {
default: 'primary',
label: 'type',
options: [
{ label: 'Primaire', value: 'primary' },
{ label: 'Secondaire', value: 'secondary' }
]
})
])
]
}),
Range('padding', {
label: 'Espacement',
default: 5,
max: 5
})
]
})
// On enregistre le custom element
editor.defineElement()
Ensuite, on peut utiliser notre éditeur où on le souhaite à l'aide du custom element <visual-editor>
<visual-editor
name="content"
preview="/preview"
iconsUrl="/assets/editor/[name].svg"
value="[]"
></visual-editor>
On peut utiliser l'attribut hidden
pour gérer sa visibilité
Le champs se comportera comme un textarea et pourra être utilisé dans un formulaire classique. Les données seront envoyées sous forme de tableau d'objet JSON contenant les valeurs des champs avec un attribut supplémentaire _name
contenant le nom du bloc utilisé.
[
{
"title": "Album example",
"titleAlign": "center",
"content": "<p>Something short and leading about the collection below—its contents, the creator, etc. Make it short and sweet, but not too short so folks don't simply skip over it entirely.</p>",
"buttons": [
{
"label": "Main call to action",
"url": "#",
"type": "primary"
},
{
"label": "Secondary action",
"url": "#",
"type": "secondary"
}
],
"padding": 5,
"_name": "hero",
}
]
A vous ensuite d'utiliser ces données pour construire votre page.
L'aperçu
Que serait un éditeur de page sans un aperçu visuel de la page ? Pour cet aperçu je voulais éviter de devoir faire le travail en double aussi c'est le serveur qui va s'occuper de rendre l'aperçu. Lorsque vous ouvrez l'éditeur pour la première fois l'URL passée dans l'attribut preview
sera appelée en POST
et recevra l'ensemble des données de l'éditeur.
curl 'https://preview.url/' \
--data-raw $'[<BLOC_DATA>]' \
--compressed
La page rendue devra être un document HTML valide qui devra respecter les règles suivantes :
- Un élément avec l'id
ve-components
devra entourer les blocs rendu. - Chaque bloc devra être un enfant direct de cet
#ve-components
et n'avoir qu'un élément racine (cela permet à l'éditeur de retrouver les blocs dans l'aperçu).
Ensuite lorsqu'un bloc est modifié, un appel à l'URL de preview est fait avec seulement les données du bloc modifié
curl 'https://visual-editor.droapp.com/' \
--data-raw $'{"title":"Album example","_name":"hero","_id":"0","preview":true}' \
--compressed
Le serveur devra alors ne rendre que ce bloc au format HTML. L'éditeur se chargera d'injecter ce nouveau bloc dans l'aperçu de la page.
Rendez vous sur la doc
Si vous voulez en apprendre plus je vous invite à vous rendre sur la documentation.