كتابة محرر WYSIWYG بسيط باستخدام ProseMirror

عندما احتاج موقع Sports.ru إلى محرر WYSIWYG الخاص به ، قررنا جعله مستندًا إلى مكتبة ProseMirror. واحدة من الميزات الرئيسية لهذه الأداة هي نمطيتها وإمكانيات التخصيص الواسعة ، لذلك بمساعدتها يمكنك تخصيص المحرر بدقة لأي مشروع. على وجه الخصوص ، يتم استخدام ProseMirror بالفعل في نيويورك تايمز و الغارديان . في هذه المقالة ، سنتحدث عن كيفية كتابة محرر WYSIWYG باستخدام ProseMirror.

كتابة محرر WYSIWYG بسيط باستخدام ProseMirror

نظرة عامة على ProseMirror


مؤلف ProseMirror هو Marijn Haverbeke ، المعروف في مجتمع مطوري الواجهة الأمامية بشكل أساسي كمؤلف الكتاب الشهير Eloquent Javascript . في بداية عملنا (خريف 2018) ، لم تكن هناك مواد حول العمل مع هذه المكتبة ، باستثناء الوثائق الرسمية والبرامج التعليمية من المؤلف. تتضمن حزمة الوثائق من المؤلف عدة أقسام ، وأكثرها فائدة هو دليل ProseMirror (وصف المفاهيم الأساسية) والدليل المرجعي (مواصفات المكتبة). فيما يلي ملخص للأفكار الرئيسية من دليل ProseMirror.

يقوم ProseMirror دائمًا بتخزين حالة المستند في بنية البيانات الخاصة به. ومن هذه البنية في وقت التشغيل ، يتم إنشاء عناصر DOM المقابلة في الصفحة التي يتفاعل معها المستخدم النهائي. علاوة على ذلك ، لا تقوم ProseMirror بتخزين الحالة (الحالة) الحالية فحسب ، بل أيضًا تاريخ التغييرات السابقة ، والتي يمكن التراجع عنها إذا لزم الأمر. يجب أن يحدث أي تغيير في الحالة من خلال المعاملات ، ولن تعمل المعالجات المعتادة مع شجرة DOM مباشرة هنا. المعاملة عبارة عن تجريد يصف منطق تغيير حالة خطوة بخطوة. يذكرنا جوهر عملهم بإرسال وتنفيذ الإجراءات في المكتبات لإدارة الدولة ، على سبيل المثال ، Redux و Vuex.

المكتبة نفسها مبنية على وحدات مستقلة يمكن تعطيلها أو إضافتها حسب الاحتياجات. قائمة الوحدات الرئيسية التي اعتقدنا أنها ستكون مطلوبة من قبل الجميع تقريبًا:

  • prosemirror-model - نموذج مستند يصف جميع مكونات المستند وخصائصه وإجراءاته التي يمكن تنفيذها عليها ؛
  • prosemirror-state - بنية بيانات تصف حالة المستند الذي تم إنشاؤه في وقت معين ، بما في ذلك الأجزاء والمعاملات المحددة للانتقال من حالة إلى أخرى ؛
  • عرض prosemirror - عرض المستند في المستعرض والأدوات حتى يتمكن المستخدم من التفاعل مع المستند ؛
  • prosemirror-convert - وظيفة لتخزين سجل المعاملات ، بمساعدة المعاملات التي يتم تنفيذها والتي تسمح لك بالتراجع إلى الحالات السابقة أو إجراء تطوير مشترك لمستند واحد.

بالإضافة إلى هذه المجموعة الدنيا ، قد تكون الوحدات التالية مفيدة أيضًا:

  • أوامر prosemirror - مجموعة من الأوامر الجاهزة للتحرير. كقاعدة ، تحتاج إلى كتابة شيء أكثر تعقيدًا أو فرديًا بنفسك ، لكن Marijn Haverbeke قام بالفعل ببعض الأشياء لنا ، على سبيل المثال ، حذف جزء محدد من النص ؛
  • prosemirror-keymap - وحدة من طريقتين لتحديد اختصارات لوحة المفاتيح ؛
  • prosemirror-history – , .. , , , ;
  • prosemirror-schema-list – , ( DOM-, , ).

ProseMirror


لنبدأ بإنشاء مخطط محرر. يحدد المخطط في ProseMirror قائمة بالعناصر التي قد تكون في وثيقتنا وخصائصها. يحتوي كل عنصر على طريقة toDOM ، والتي تحدد كيفية تمثيل هذا العنصر في شجرة DOM على صفحة الويب.

يتم تحقيق مبدأ WYSIWYG بدقة في حقيقة أنه عند إنشاء المخطط لدينا السيطرة الكاملة على كيفية عرض المحتوى القابل للتحرير على الصفحة ، وبالتالي ، يمكننا إعطاء كل عنصر مثل بنية HTML وتعيين الأنماط تمامًا مثل المحتوى الذي سيتم عرضه. يمكن إنشاء العقد وتخصيصها لمتطلباتك ، على وجه الخصوص ، على الأرجح قد تحتاج إلى فقرات وعناوين وقوائم وصور وفقرات ووسائط.

افترض أن كل فقرة من النص يجب أن يتم لفها في علامة "p" مع الفئة "فقرة" ، والتي تحدد قواعد أنماط الفقرة اللازمة للتخطيط. قد يبدو مخطط ProseMirror في هذه الحالة كما يلي:

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

أولاً ، نقوم باستيراد المُنشئ لإنشاء مخططنا وتمرير كائن إليه يصف العقد (المشار إليها فيما يلي بالعُقد) في المحرر المستقبلي. العقد عبارة عن ملخصات تصف أنواع المحتوى الذي يتم إنشاؤه. على سبيل المثال ، مع مثل هذا المخطط ، يمكن فقط أن تكون العقد من أنواع النص والفقرة في المحرر. Doc هو اسم عقدة المستوى الأعلى ، والتي ستتكون فقط من عناصر الكتلة ، أي في هذه الحالة فقط من الفقرات (لأننا لم نصف الآخرين).

النص هو عقد نصية ، تشبه إلى حد ما عقد DOM النصية. باستخدام خاصية المجموعة ، يمكننا تجميع عقدنا بطرق مختلفة بحيث يمكن الوصول إليها بسهولة أكبر في التعليمات البرمجية. يمكن تعيين المجموعات بأي طريقة تناسبنا. في حالتنا ، قمنا بتقسيم العقد فقط إلى كتلة ومضمنة. يكون النص مضمنًا بشكل افتراضي ، لذلك يمكن حذف هذا بشكل صريح.

شيء مختلف تمامًا هو الفقرة (الفقرة). أعلنا أن الفقرات تتكون من عناصر نص مضمنة ، ولها أيضًا تمثيلها الخاص في DOM. ستكون الفقرة عنصر كتلة ، تقريبًا بمعنى أن عناصر كتلة DOM تكون. وفقًا لهذا المخطط ، سيتم عرض الفقرات على الصفحة في المحرر عبر الإنترنت على النحو التالي:

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

الآن يمكنك إنشاء المحرر نفسه:

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

في البداية ، كالعادة ، نستورد جميع المنشئات اللازمة من الوحدات المقابلة ونأخذ المخطط الموضح أعلاه. نقوم بإنشاء المحرر كفئة مع مجموعة من الأساليب الضرورية. لكي تتمكن صفحة الويب من تحرير المحتوى وإنشائه ، تحتاج إلى إنشاء حالة وحالة باستخدام تخطيط ومحتوى المقالة وتقديم المحتوى من الحالة الحالية في عنصر الجذر المحدد. نضع هذه الإجراءات في طريقة setArticle ، باستثناء التي لا حاجة لها حتى الآن.

في الوقت نفسه ، المحتوى اختياري. إذا لم يكن كذلك ، فستحصل على محرر فارغ ، ويمكن بالفعل إنشاء المحتوى مباشرة على الفور. لنفترض أن لدينا ملف HTML بهذا الترميز:

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

لإنشاء محرر WYSIWYG فارغ على الصفحة ، تحتاج فقط إلى بضعة أسطر في برنامج نصي يتم تشغيله على صفحة بهذا الترميز:

//     
import Wysiwyg from 'wysiwyg';

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

باستخدام هذا الرمز ، يمكنك كتابة أي نص من البداية. عند هذه النقطة ، يمكنك بالفعل رؤية كيفية عمل ProseMirror.

عندما يكتب المستخدم النص في هذا المحرر ، تحدث معاملات الدولة. على سبيل المثال ، إذا كتبت عبارة "هذا هو محرر WYSIWYG الرائع الجديد الخاص بي" في المحرر بالمخطط أعلاه ، فسوف يستجيب ProseMirror لإدخال لوحة المفاتيح من خلال استدعاء مجموعة المعاملات المناسبة ، وبعد اكتمال الإدخال ، سيبدو المحتوى في حالة المستند كما يلي:

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

إذا أردنا أن يتم فتح بعض النصوص ، على سبيل المثال ، محتوى مقال تم إنشاؤه سابقًا في المحرر للتحرير ، فيجب أن يتوافق هذا المحتوى مع المخطط الذي تم إنشاؤه سابقًا. ثم سيبدو رمز تهيئة المحرر مختلفًا قليلاً:

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

مرحى! لقد أنشأنا محررنا الأول ، والذي يمكنه إنشاء صفحة نظيفة لإنشاء محتوى جديد وفتح القائمة للتحرير.

ماذا تفعل مع المحرر بعد ذلك


ولكن حتى مع وجود مجموعة كاملة من العقد ، لا يزال محررنا يفتقر إلى وظائف مهمة - تنسيق النص والفقرات. لهذا ، بالإضافة إلى العقد ، يجب أيضًا نقل كائن يحتوي على إعدادات التنسيق والعلامات إلى الدائرة. من أجل البساطة ، نسميها - العلامات التجارية. وللتحكم في إضافة جميع هذه العناصر وحذفها وتنسيقها ، سيحتاج المستخدمون إلى قائمة. يمكن إضافة القوائم باستخدام المكونات الإضافية المخصصة التي تسرد كائنات القائمة وتصف التغيير في حالة المستند عند اختيار إجراءات معينة.

باستخدام الإضافات ، يمكنك إنشاء أي تصميم يوسع الميزات المضمنة للمحرر. تتمثل الفكرة الرئيسية للمكوّنات الإضافية في أنها تعالج إجراءات مستخدم معيّنة من أجل إنشاء المعاملات المقابلة لها. على سبيل المثال ، إذا كنت ترغب في النقر فوق رمز القائمة في القائمة لإنشاء قائمة فارغة جديدة ونقل المؤشر إلى بداية العنصر الأول ، فإننا نحتاج بالتأكيد إلى وصف هذه المعاملة في المكون الإضافي المقابل.

يمكنك قراءة المزيد حول إعدادات التنسيق والمكونات الإضافية في الوثائق الرسمية ، كما يمكن أن تكون الأمثلة المفيدة الصغيرة جدًا لاستخدام ميزات ProseMirror مفيدة جدًا .

UPDإذا كنت مهتمًا بكيفية دمج محرر جديد يعتمد على ProseMirror في مشروع موجود ، فقد تحدثنا عن هذا في مقال آخر .

All Articles