Écrire un simple éditeur WYSIWYG avec ProseMirror

Lorsque Sports.ru avait besoin de son propre éditeur WYSIWYG , nous avons décidé de le faire sur la base de la bibliothèque ProseMirror. L'une des principales caractéristiques de cet outil est sa modularité et ses nombreuses possibilités de personnalisation.Ainsi, avec lui, vous pouvez adapter très finement l'éditeur à n'importe quel projet. En particulier, ProseMirror est déjà utilisé dans le New York Times et The Guardian . Dans cet article, nous expliquerons comment écrire votre éditeur WYSIWYG à l'aide de ProseMirror.

Écrire un simple éditeur WYSIWYG avec ProseMirror

Présentation de ProseMirror


L'auteur de ProseMirror est Marijn Haverbeke, qui est connu dans la communauté des développeurs frontaux principalement comme l'auteur du livre populaire Eloquent Javascript . Au début de notre travail (automne 2018), il n'y avait aucun matériel sur le travail avec cette bibliothèque, à l'exception de la documentation officielle et des tutoriels de l'auteur. Le dossier de documentation de l'auteur comprend plusieurs sections, les plus utiles d'entre elles sont le Guide ProseMirror (description des concepts de base) et le Manuel de référence (spécification de bibliothèque). Vous trouverez ci-dessous un résumé des idées clés du Guide ProseMirror.

ProseMirror stocke toujours l'état d'un document dans sa propre structure de données. Et déjà à partir de cette structure en runtime les éléments DOM correspondants sont générés sur la page avec laquelle l'utilisateur final interagit. De plus, ProseMirror stocke non seulement l'état actuel (état), mais également l'historique des modifications précédentes, qui peuvent être annulées si nécessaire. Tout changement d'état doit se produire par le biais de transactions, les manipulations habituelles avec l'arborescence DOM ne fonctionneront pas directement ici. Une transaction est une abstraction qui décrit la logique d'un changement d'état étape par étape. L'essence de leur travail n'est pas sans rappeler l'envoi et l'exécution d'actions dans les bibliothèques de gestion d'état, par exemple, Redux et Vuex.

La bibliothèque elle-même est construite sur des modules indépendants qui peuvent être désactivés ou ajoutés en fonction des besoins. Une liste des principaux modules qui, selon nous, seraient nécessaires à presque tout le monde:

  • prosemirror-model - un modèle de document qui décrit tous les composants d'un document, leurs propriétés et les actions qui peuvent être effectuées sur eux;
  • prosemirror-state - une structure de données qui décrit l'état du document créé à un certain moment, y compris des fragments et des transactions sélectionnés pour passer d'un état à un autre;
  • prosemirror-view - présentation du document dans le navigateur et les outils pour que l'utilisateur puisse interagir avec le document;
  • prosemirror-transform est une fonction de stockage de l'historique des transactions, à l'aide de laquelle les transactions sont implémentées et qui vous permet de revenir aux états précédents ou de mener le développement conjoint d'un document.

En plus de cet ensemble minimal, les modules suivants peuvent également être utiles:

  • prosemirror-commands - un ensemble de commandes prêtes à l'emploi pour l'édition. En règle générale, vous devez écrire quelque chose de plus complexe ou individuel vous-même, mais Marijn Haverbeke a déjà fait certaines choses pour nous, par exemple, en supprimant un fragment de texte sélectionné;
  • prosemirror-keymap - un module de deux méthodes pour spécifier les raccourcis clavier;
  • prosemirror-history – , .. , , , ;
  • prosemirror-schema-list – , ( DOM-, , ).

ProseMirror


Commençons par créer un aperçu de l'éditeur. Le schéma dans ProseMirror définit une liste d'éléments qui peuvent être dans notre document, et leurs propriétés. Chaque élément a une méthode toDOM, qui détermine comment cet élément sera représenté dans l'arborescence DOM de la page Web.

Le principe de WYSIWYG est réalisé précisément dans le fait que lors de la création du schéma, nous avons un contrôle total sur la façon dont le contenu modifiable est affiché sur la page, et, en conséquence, nous pouvons donner à chaque élément une telle structure HTML et définir des styles tout comme le contenu à afficher. Les nœuds peuvent être créés et personnalisés selon vos besoins, en particulier, vous aurez probablement besoin de paragraphes, en-têtes, listes, images, paragraphes, supports.

Supposons que chaque paragraphe du texte soit enveloppé dans la balise «p» avec la classe «paragraphe», qui détermine les règles pour les styles de paragraphe nécessaires à la mise en page. Dans ce cas, le schéma ProseMirror peut ressembler à ceci:

import { Schema } from "prosemirror-model";

const mySchema = new Schema({
    nodes: {
        doc: {
           content: 'block'
        },
        text: {},
        paragraph: {
            content: 'inline',
            group: 'block',
            toDOM: function toDOM(node) {
                return ['p', {class: 'paragraph'}, 0];
            },
        },
    },
});

Tout d'abord, nous importons le constructeur pour créer notre schéma et lui passons un objet qui décrit les nœuds (ci-après appelés nœuds) dans le futur éditeur. Les nœuds sont des abstractions qui décrivent les types de contenu créés. Par exemple, avec un tel schéma, seuls les nœuds de type texte et paragraphe peuvent se trouver dans l'éditeur. Doc est le nom du nœud de niveau supérieur, qui ne sera composé que d'éléments de bloc, c'est-à-dire dans ce cas uniquement à partir des paragraphes (car nous n'en avons pas décrit d'autres).

Le texte est des nœuds de texte quelque peu similaires aux nœuds DOM de texte. En utilisant la propriété group, nous pouvons regrouper nos nœuds de différentes manières afin qu'ils soient plus facilement accessibles dans le code. Les groupes peuvent être définis de la manière qui nous convient. Dans notre cas, nous avons divisé les nœuds uniquement en blocs et en ligne. Le texte est en ligne par défaut, donc cela peut être omis explicitement.

Une chose complètement différente est le paragraphe (paragraphe). Nous avons annoncé que les paragraphes se composent d'éléments de texte en ligne et ont également leur propre représentation dans le DOM. Un paragraphe sera un élément de bloc, à peu près dans le sens où les éléments de bloc DOM le sont. Selon ce schéma, les paragraphes de la page seront présentés dans l'éditeur en ligne comme suit:

<p class="paragraph">Content of the paragraph</p>

Vous pouvez maintenant créer l'éditeur lui-même:

import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Schema } from "prosemirror-model";

const mySchema = new Schema({
    nodes: {
        doc: {
            content: 'block+'
        },
        text: {
            group: 'inline',
            inline: true
        },
        paragraph: {
            content: 'inline*',
            group: 'block',
            toDOM: function toDOM(node) {
                return ['p', {class: 'paragraph'}, 0];
            },
        },
    },
});

/**
 * @classdesc  
 * @param {Object} el DOM-,    
 */
export default class Wysiwyg {
    constructor(el) {
        this.el = el;
    }

    /**
      * @description     
      * @param {Object} content ,    
      */
    setArticle(content) {
        const state = EditorState.fromJSON(
            {schema: mySchema},
            content,
        );
        const view = new EditorView(this.el, {state: state});
    }
}

Au début, comme d'habitude, nous importons tous les constructeurs nécessaires des modules correspondants et prenons le schéma décrit ci-dessus. Nous créons l'éditeur en tant que classe avec un ensemble de méthodes nécessaires. Pour que la page Web puisse modifier et créer du contenu, vous devez créer un état, un état, en utilisant la mise en page et le contenu de l'article, et en présentant le contenu de l'état actuel dans l'élément racine donné. Nous plaçons ces actions dans la méthode setArticle, sauf pour lesquelles rien n'est nécessaire jusqu'à présent.

Dans le même temps, le contenu est facultatif. Si ce n'est pas le cas, vous obtenez un éditeur vide et le contenu peut déjà être créé directement sur place. Disons que nous avons un fichier HTML avec ce balisage:

<div id="editor"></div>

Pour créer un éditeur WYSIWYG vide sur une page, vous n'avez besoin que de quelques lignes dans un script qui s'exécute sur une page avec ce balisage:

//     
import Wysiwyg from 'wysiwyg';

const root = document.getElementById('editor');
const myWysiwyg = new Wysiwyg(root);
myWysiwyg.setArticle();

En utilisant ce code, vous pouvez écrire n'importe quel texte à partir de zéro. À ce stade, vous pouvez déjà voir comment ProseMirror fonctionne.

Lorsqu'un utilisateur tape du texte dans cet éditeur, des transactions d'état se produisent. Par exemple, si vous tapez la phrase «Ceci est mon nouvel éditeur WYSIWYG cool» dans l'éditeur avec le schéma ci-dessus, ProseMirror répondra à la saisie au clavier en appelant l'ensemble de transactions approprié, et une fois la saisie terminée, le contenu dans l'état du document ressemblera à ceci:

content: [
    {
        type: 'paragraph',
        value: '    WYSIWYG-',
    },
]

Si nous voulons que du texte, par exemple, le contenu d'un article déjà créé précédemment, soit ouvert dans l'éditeur pour être édité, alors ce contenu doit correspondre au schéma créé précédemment. Ensuite, le code d'initialisation de l'éditeur sera un peu différent:

//     
import Wysiwyg from 'wysiwyg';

const root = document.getElementById('editor');
const myWysiwyg = new Wysiwyg(root);
const content = {
    type: 'doc',
    content: [
        {
            type: 'paragraph',
            value: 'Hello, world!',
        },
        {
            type: 'paragraph',
            value: 'This is my first wysiwyg-editor',
        }
    ],
};
myWysiwyg.setArticle(content);

Hourra! Nous avons créé notre premier éditeur, qui peut créer une page propre pour créer un nouveau contenu et ouvrir l'existant pour le modifier.

Que faire avec l'éditeur ensuite


Mais même avec un ensemble complet de nœuds, notre éditeur manque encore de fonctions importantes - mise en forme du texte, des paragraphes. Pour cela, en plus des nœuds, un objet avec des paramètres de formatage, des repères, doit également être transféré au circuit. Pour plus de simplicité, nous les appelons des «marques». Et pour contrôler l'ajout, la suppression et la mise en forme de tous ces éléments, les utilisateurs auront besoin d'un menu. Les menus peuvent être ajoutés à l'aide de plug-ins personnalisés qui stylisent les objets de menu et décrivent le changement d'état d'un document lors du choix de certaines actions.

À l'aide de plugins, vous pouvez créer n'importe quelle conception qui étend les fonctionnalités intégrées de l'éditeur. L'idée principale des plugins est qu'ils traitent certaines actions des utilisateurs afin de générer des transactions qui leur correspondent. Par exemple, si vous voulez cliquer sur l'icône de liste dans le menu pour créer une nouvelle liste vide et déplacer le curseur au début du premier élément, alors nous devons certainement décrire cette transaction dans le plugin correspondant.

Vous pouvez en savoir plus sur les paramètres de formatage et les plugins dans la documentation officielle , ainsi que de très petits exemples utiles d' utilisation des fonctionnalités de ProseMirror peuvent être très utiles .

UPDSi vous êtes intéressé par la façon dont nous avons intégré un nouvel éditeur basé sur ProseMirror dans un projet existant, nous en avons parlé dans un autre article .

All Articles