Menulis editor WYSIWYG sederhana dengan ProseMirror

Ketika Sports.ru membutuhkan editor WYSIWYG sendiri , kami memutuskan untuk membuatnya berdasarkan perpustakaan ProseMirror. Salah satu fitur utama dari alat ini adalah modularitas dan kemungkinan penyesuaian yang luas, sehingga dengan bantuannya Anda dapat menyesuaikan editor dengan sangat baik untuk proyek apa pun. Secara khusus, ProseMirror sudah digunakan di The New York Times dan The Guardian . Pada artikel ini, kita akan berbicara tentang cara menulis editor WYSIWYG Anda menggunakan ProseMirror.

Menulis editor WYSIWYG sederhana dengan ProseMirror

Gambaran Umum ProseMirror


Penulis ProseMirror adalah Marijn Haverbeke, yang dikenal di komunitas pengembang frontend terutama sebagai penulis buku populer Eloquent Javascript . Pada awal pekerjaan kami (musim gugur 2018), tidak ada bahan untuk bekerja dengan perpustakaan ini, kecuali untuk dokumentasi resmi dan tutorial dari penulis. Paket dokumentasi dari penulis mencakup beberapa bagian, yang paling berguna di antaranya adalah ProseMirror Guide (deskripsi konsep dasar) dan Manual Referensi (spesifikasi perpustakaan). Di bawah ini adalah ringkasan ide-ide kunci dari ProseMirror Guide.

ProseMirror selalu menyimpan status dokumen dalam struktur datanya sendiri. Dan sudah dari struktur ini dalam runtime elemen DOM yang sesuai dihasilkan pada halaman yang berinteraksi dengan pengguna akhir. Selain itu, ProseMirror tidak hanya menyimpan status saat ini (status), tetapi juga riwayat perubahan sebelumnya, yang dapat dibatalkan jika perlu. Setiap perubahan dalam keadaan harus terjadi melalui transaksi, manipulasi biasa dengan pohon DOM tidak akan bekerja secara langsung di sini. Transaksi adalah abstraksi yang menjelaskan logika perubahan status langkah demi langkah. Inti dari pekerjaan mereka mengingatkan pada pengiriman dan pelaksanaan tindakan di perpustakaan untuk manajemen negara, misalnya, Redux dan Vuex.

Perpustakaan itu sendiri dibangun di atas modul independen yang dapat dinonaktifkan atau ditambahkan tergantung pada kebutuhan. Daftar modul utama yang kami pikir akan dibutuhkan oleh hampir semua orang:

  • prosemirror-model - model dokumen yang menggambarkan semua komponen dokumen, sifat dan tindakannya yang dapat dilakukan pada mereka;
  • prosemirror-state - struktur data yang menggambarkan keadaan dokumen yang dibuat pada titik waktu tertentu, termasuk fragmen dan transaksi yang dipilih untuk berpindah dari satu negara ke negara lain;
  • prosemirror-view - presentasi dokumen di browser dan alat-alat sehingga pengguna dapat berinteraksi dengan dokumen;
  • prosemirror-transform - fungsional untuk menyimpan riwayat transaksi, dengan bantuan transaksi mana yang diimplementasikan dan yang memungkinkan Anda untuk kembali ke keadaan sebelumnya atau melakukan pengembangan bersama dari satu dokumen.

Selain set minimal ini, modul berikut ini mungkin juga berguna:

  • prosemirror-commands - satu set perintah yang sudah jadi untuk mengedit. Sebagai aturan, Anda perlu menulis sesuatu yang lebih kompleks atau sendiri, tetapi Marijn Haverbeke telah melakukan beberapa hal untuk kami, misalnya menghapus sebagian teks yang dipilih;
  • prosemirror-keymap - modul dua metode untuk menentukan pintasan keyboard;
  • prosemirror-history – , .. , , , ;
  • prosemirror-schema-list – , ( DOM-, , ).

ProseMirror


Mari kita mulai dengan membuat garis besar editor. Skema di ProseMirror mendefinisikan daftar elemen yang mungkin ada di dokumen kami, dan propertinya. Setiap elemen memiliki metode toDOM, yang menentukan bagaimana elemen ini akan diwakili di pohon DOM di halaman web.

Prinsip WYSIWYG diwujudkan dengan tepat pada kenyataan bahwa ketika membuat skema kami memiliki kontrol penuh atas bagaimana konten yang dapat diedit ditampilkan pada halaman, dan, oleh karena itu, kami dapat memberikan setiap elemen seperti struktur HTML dan mengatur gaya seperti konten yang akan dilihat. Node dapat dibuat dan disesuaikan dengan kebutuhan Anda, khususnya, kemungkinan besar Anda mungkin memerlukan paragraf, judul, daftar, gambar, paragraf, media.

Misalkan setiap paragraf teks harus dibungkus dengan tag "p" dengan kelas "paragraf", yang menentukan aturan gaya paragraf yang diperlukan untuk tata letak. Maka skema ProseMirror dalam hal ini mungkin terlihat seperti ini:

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

Pertama, kami mengimpor konstruktor untuk membuat skema kami dan meneruskan objek ke sana yang menggambarkan node (selanjutnya disebut sebagai node) di editor masa depan. Node adalah abstraksi yang menggambarkan jenis konten yang dibuat. Misalnya, dengan skema seperti itu, hanya node jenis teks dan paragraf yang dapat berada di editor. Doc adalah nama simpul tingkat atas, yang hanya terdiri dari elemen blok, mis. dalam hal ini hanya dari paragraf (karena kami belum menggambarkan yang lain).

Teks adalah simpul teks, agak mirip dengan simpul teks DOM. Menggunakan properti grup, kita dapat mengelompokkan node kita dengan cara yang berbeda sehingga mereka dapat lebih mudah diakses dalam kode. Grup dapat diatur dengan cara apa pun yang nyaman bagi kami. Dalam kasus kami, kami membagi node hanya menjadi blok dan inline. Teks sebaris secara default, jadi ini bisa dihilangkan secara eksplisit.

Hal yang sama sekali berbeda adalah paragraf (paragraf). Kami mengumumkan bahwa paragraf terdiri dari elemen teks sebaris, dan juga memiliki perwakilannya sendiri di DOM. Paragraf akan menjadi elemen blok, kira-kira dalam arti elemen blok DOM. Menurut skema ini, paragraf pada halaman akan disajikan dalam editor online sebagai berikut:

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

Sekarang Anda dapat membuat editor itu sendiri:

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

Pada awalnya, seperti biasa, kami mengimpor semua konstruktor yang diperlukan dari modul yang sesuai dan mengambil skema yang dijelaskan di atas. Kami membuat editor sebagai kelas dengan serangkaian metode yang diperlukan. Agar halaman web dapat mengedit dan membuat konten, Anda perlu membuat negara, menyatakan, menggunakan tata letak dan konten artikel, dan menyajikan konten dari keadaan saat ini di elemen root yang diberikan. Kami menempatkan tindakan ini dalam metode setArticle, kecuali yang sejauh ini tidak diperlukan.

Pada saat yang sama, konten bersifat opsional. Jika tidak, maka Anda mendapatkan editor kosong, dan konten sudah dapat dibuat langsung di tempat. Katakanlah kita memiliki file HTML dengan markup ini:

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

Untuk membuat editor WYSIWYG kosong pada halaman, Anda hanya perlu beberapa baris dalam skrip yang berjalan pada halaman dengan markup ini:

//     
import Wysiwyg from 'wysiwyg';

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

Dengan menggunakan kode ini, Anda dapat menulis teks apa pun dari awal. Pada titik ini, Anda sudah dapat melihat cara kerja ProseMirror.

Ketika pengguna mengetik teks di editor ini, transaksi terjadi. Misalnya, jika Anda mengetik frasa β€œIni editor WYSIWYG keren baru saya” di editor dengan skema di atas, ProseMirror akan merespons input keyboard dengan menggunakan set transaksi yang sesuai, dan setelah input selesai, konten dalam status dokumen akan terlihat seperti ini:

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

Jika kita ingin beberapa teks, misalnya, konten artikel yang sudah dibuat sebelumnya, dibuka di editor untuk diedit, maka konten ini harus sesuai dengan skema yang dibuat sebelumnya. Maka kode inisialisasi editor akan terlihat sedikit berbeda:

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

Hore! Kami membuat editor pertama kami, yang dapat membuat halaman bersih untuk membuat konten baru dan membuka yang sudah ada untuk diedit.

Apa yang harus dilakukan dengan editor selanjutnya


Tetapi bahkan dengan set lengkap node, editor kami masih tidak memiliki fungsi penting - memformat teks, paragraf. Untuk ini, selain node, objek dengan pengaturan untuk pemformatan, tanda, juga harus ditransfer ke sirkuit. Untuk kesederhanaan, kami menyebutnya merek - begitu. Dan untuk mengontrol penambahan, penghapusan dan pemformatan semua elemen ini, pengguna akan membutuhkan menu. Menu dapat ditambahkan menggunakan plugin kustom yang menyesuaikan dgn mode objek objek dan menggambarkan perubahan dalam keadaan dokumen ketika memilih tindakan tertentu.

Dengan menggunakan plugin, Anda dapat membuat desain apa pun yang memperluas fitur bawaan editor. Gagasan utama plugin adalah mereka memproses tindakan pengguna tertentu untuk menghasilkan transaksi yang sesuai dengannya. Misalnya, jika Anda ingin klik pada ikon daftar di menu untuk membuat daftar kosong baru dan memindahkan kursor ke awal elemen pertama, maka kita harus menggambarkan transaksi ini di plugin yang sesuai.

Anda dapat membaca lebih lanjut tentang pengaturan format dan plugin dalam dokumentasi resmi , serta contoh-contoh berguna yang sangat kecil untuk menggunakan fitur ProseMirror bisa sangat berguna .

UPDJika Anda tertarik pada bagaimana kami mengintegrasikan editor baru berdasarkan ProseMirror ke dalam proyek yang ada, maka kami membicarakan hal ini di artikel lain .

All Articles