Schreiben eines einfachen WYSIWYG-Editors mit ProseMirror

Als Sports.ru einen eigenen WYSIWYG- Editor benötigte, haben wir beschlossen, ihn auf der Grundlage der ProseMirror-Bibliothek zu erstellen. Eines der Hauptmerkmale dieses Tools ist seine Modularität und die vielfältigen Anpassungsmöglichkeiten, sodass Sie den Editor sehr genau auf jedes Projekt zuschneiden können. Insbesondere wird ProseMirror bereits in der New York Times und im Guardian verwendet . In diesem Artikel wird erläutert, wie Sie Ihren WYSIWYG-Editor mit ProseMirror schreiben.

Schreiben eines einfachen WYSIWYG-Editors mit ProseMirror

Übersicht über ProseMirror


Der Autor von ProseMirror ist Marijn Haverbeke, der in der Frontend-Entwickler-Community hauptsächlich als Autor des beliebten Buches Eloquent Javascript bekannt ist . Zu Beginn unserer Arbeit (Herbst 2018) gab es keine Materialien zur Arbeit mit dieser Bibliothek, außer offiziellen Dokumentationen und Tutorials des Autors. Das Dokumentationspaket des Autors enthält mehrere Abschnitte, von denen die nützlichsten das ProseMirror-Handbuch (Beschreibung der Grundkonzepte) und das Referenzhandbuch (Bibliotheksspezifikation) sind. Im Folgenden finden Sie eine Zusammenfassung der wichtigsten Ideen aus dem ProseMirror-Handbuch.

ProseMirror speichert den Status eines Dokuments immer in seiner eigenen Datenstruktur. Und bereits aus dieser Struktur werden zur Laufzeit die entsprechenden DOM-Elemente auf der Seite generiert, mit der der Endbenutzer interagiert. Darüber hinaus speichert ProseMirror nicht nur den aktuellen Status (Status), sondern auch den Verlauf früherer Änderungen, die bei Bedarf zurückgesetzt werden können. Jede Statusänderung sollte durch Transaktionen erfolgen, die üblichen Manipulationen mit dem DOM-Baum funktionieren hier nicht direkt. Eine Transaktion ist eine Abstraktion, die die Logik einer schrittweisen Statusänderung beschreibt. Das Wesentliche ihrer Arbeit erinnert an das Senden und Ausführen von Aktionen in Bibliotheken zur Statusverwaltung, z. B. Redux und Vuex.

Die Bibliothek selbst basiert auf unabhängigen Modulen, die je nach Bedarf deaktiviert oder hinzugefügt werden können. Eine Liste der Hauptmodule, von denen wir dachten, dass sie von fast allen benötigt werden:

  • Prosemirror-Modell - ein Dokumentmodell, das alle Komponenten eines Dokuments, ihre Eigenschaften und Aktionen beschreibt, die für sie ausgeführt werden können;
  • Prosemirror-Status - Eine Datenstruktur, die den Status des erstellten Dokuments zu einem bestimmten Zeitpunkt beschreibt, einschließlich ausgewählter Fragmente und Transaktionen für den Wechsel von einem Status in einen anderen.
  • Prosemirror-Ansicht - Präsentation des Dokuments im Browser und in den Tools, damit der Benutzer mit dem Dokument interagieren kann;
  • prosemirror-transform - eine Funktion zum Speichern des Transaktionsverlaufs, mit deren Hilfe Transaktionen implementiert werden und mit der Sie zu früheren Zuständen zurückkehren oder ein Dokument gemeinsam entwickeln können.

Zusätzlich zu diesem minimalen Satz können auch die folgenden Module nützlich sein:

  • prosemirror-befehle - eine Reihe von vorgefertigten Befehlen zum Bearbeiten. In der Regel müssen Sie selbst etwas Komplexeres oder Individuelleres schreiben, aber Marijn Haverbeke hat bereits einige Dinge für uns getan, zum Beispiel das Löschen eines ausgewählten Textfragments.
  • prosemirror-keymap - ein Modul mit zwei Methoden zum Festlegen von Tastaturkürzeln;
  • prosemirror-history – , .. , , , ;
  • prosemirror-schema-list – , ( DOM-, , ).

ProseMirror


Beginnen wir mit der Erstellung einer Editor-Gliederung. Das Schema in ProseMirror definiert eine Liste der Elemente in unserem Dokument und deren Eigenschaften. Jedes Element verfügt über eine toDOM-Methode, die bestimmt, wie dieses Element im DOM-Baum auf der Webseite dargestellt wird.

Das Prinzip von WYSIWYG wird genau dadurch verwirklicht, dass wir beim Erstellen des Schemas die volle Kontrolle darüber haben, wie bearbeitbarer Inhalt auf der Seite angezeigt wird, und dementsprechend können wir jedem Element eine solche HTML-Struktur geben und Stile festlegen, genau wie der anzuzeigende Inhalt. Knoten können erstellt und an Ihre Anforderungen angepasst werden. Insbesondere benötigen Sie höchstwahrscheinlich Absätze, Überschriften, Listen, Bilder, Absätze und Medien.

Angenommen, jeder Absatz des Textes sollte in das Tag "p" mit der Klasse "Absatz" eingeschlossen werden, die die Regeln für Absatzstile festlegt, die für das Layout erforderlich sind. Dann könnte das ProseMirror-Schema in diesem Fall folgendermaßen aussehen:

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];
            },
        },
    },
});

Zuerst importieren wir den Konstruktor, um unser Schema zu erstellen, und übergeben ihm ein Objekt, das die Knoten (im Folgenden als Knoten bezeichnet) im zukünftigen Editor beschreibt. Knoten sind Abstraktionen, die die Arten von Inhalten beschreiben, die erstellt werden. Bei einem solchen Schema können sich beispielsweise nur Knoten vom Typ Text und Absatz im Editor befinden. Doc ist der Name des Knotens der obersten Ebene, der nur aus Blockelementen besteht, d. H. in diesem Fall nur aus Absätzen (weil wir andere nicht beschrieben haben).

Text sind Textknoten, ähnlich wie Text-DOM-Knoten. Mithilfe der Gruppeneigenschaft können wir unsere Knoten auf verschiedene Arten gruppieren, damit im Code leichter auf sie zugegriffen werden kann. Gruppen können auf jede für uns bequeme Weise festgelegt werden. In unserem Fall haben wir die Knoten nur in Block und Inline unterteilt. Text ist standardmäßig inline, daher kann dies explizit weggelassen werden.

Eine ganz andere Sache ist Absatz (Absatz). Wir haben angekündigt, dass Absätze aus Inline-Textelementen bestehen und auch eine eigene Darstellung im DOM haben. Ein Absatz ist ein Blockelement, ungefähr in dem Sinne, wie es die DOM-Blockelemente sind. Nach diesem Schema werden die Absätze auf der Seite im Online-Editor wie folgt dargestellt:

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

Jetzt können Sie den Editor selbst erstellen:

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});
    }
}

Zu Beginn importieren wir wie gewohnt alle notwendigen Konstruktoren aus den entsprechenden Modulen und verwenden das oben beschriebene Schema. Wir erstellen den Editor als Klasse mit einer Reihe notwendiger Methoden. Damit die Webseite Inhalte bearbeiten und erstellen kann, müssen Sie einen Status erstellen, den Layout und den Inhalt des Artikels verwenden und den Inhalt aus dem aktuellen Status im angegebenen Stammelement darstellen. Wir fügen diese Aktionen in die setArticle-Methode ein, außer für die bisher nichts benötigt wird.

Gleichzeitig ist der Inhalt optional. Ist dies nicht der Fall, erhalten Sie einen leeren Editor, und der Inhalt kann bereits direkt vor Ort erstellt werden. Angenommen, wir haben eine HTML-Datei mit diesem Markup:

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

Um einen leeren WYSIWYG-Editor auf einer Seite zu erstellen, benötigen Sie nur wenige Zeilen in einem Skript, das auf einer Seite mit diesem Markup ausgeführt wird:

//     
import Wysiwyg from 'wysiwyg';

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

Mit diesem Code können Sie jeden Text von Grund auf neu schreiben. Zu diesem Zeitpunkt können Sie bereits sehen, wie ProseMirror funktioniert.

Wenn ein Benutzer in diesem Editor Text eingibt, werden Status-Transaktionen ausgeführt. Wenn Sie beispielsweise im Editor mit dem obigen Schema den Ausdruck "Dies ist mein neuer cooler WYSIWYG-Editor" eingeben, reagiert ProseMirror auf Tastatureingaben mit dem Aufrufen der entsprechenden Transaktionen. Nach Abschluss der Eingabe sieht der Inhalt im Dokumentstatus folgendermaßen aus:

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

Wenn Text, z. B. der Inhalt eines bereits zuvor erstellten Artikels, im Editor zum Bearbeiten geöffnet werden soll, muss dieser Inhalt dem zuvor erstellten Schema entsprechen. Dann sieht der Initialisierungscode des Editors etwas anders aus:

//     
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);

Hurra! Wir haben unseren ersten Editor erstellt, mit dem eine saubere Seite erstellt werden kann, um neue Inhalte zu erstellen und vorhandene zur Bearbeitung zu öffnen.

Was tun als nächstes mit dem Editor?


Aber selbst mit einem vollständigen Satz von Knoten fehlen unserem Editor wichtige Funktionen - Formatieren von Text, Absätze. Dazu muss neben den Knoten auch ein Objekt mit Formatierungseinstellungen, Markierungen, auf die Schaltung übertragen werden. Der Einfachheit halber nennen wir sie so - Marken. Um das Hinzufügen, Löschen und Formatieren all dieser Elemente zu steuern, benötigen Benutzer ein Menü. Menüs können mithilfe benutzerdefinierter Plugins hinzugefügt werden, die Menüobjekte stilisieren und die Änderung des Status eines Dokuments bei der Auswahl bestimmter Aktionen beschreiben.

Mit Plugins können Sie jedes Design erstellen, das die integrierten Funktionen des Editors erweitert. Die Hauptidee von Plugins ist, dass sie bestimmte Benutzeraktionen verarbeiten, um entsprechende Transaktionen zu generieren. Wenn Sie beispielsweise auf das Listensymbol im Menü klicken möchten, um eine neue leere Liste zu erstellen und den Cursor an den Anfang des ersten Elements zu bewegen, müssen Sie diese Transaktion unbedingt im entsprechenden Plugin beschreiben.

Weitere Informationen zu Formatierungseinstellungen und Plugins finden Sie in der offiziellen Dokumentation . Sehr kleine nützliche Beispiele für die Verwendung von ProseMirror-Funktionen können sehr nützlich sein .

UPDWenn Sie daran interessiert sind, wie wir einen neuen Editor basierend auf ProseMirror in ein vorhandenes Projekt integriert haben, haben wir in einem anderen Artikel darüber gesprochen .

All Articles