تقديم runio/mde: حزمة محرر Markdown جاهزة لـ RTL لـ Symfony

Mar 15, 2026 10 دقيقة قراءة 91 مشاهدة
تقديم runio/mde: حزمة محرر Markdown جاهزة لـ RTL لـ Symfony

كل مطوّر يبني في نهاية المطاف شيئًا لأن لا شيء آخر يناسبه تمامًا. بالنسبة لي، كان ذلك الشيء حزمة Symfony لمحرر Markdown غني يعمل بشكل صحيح مع المحتوى العربي (RTL). النتيجة هي runio/mde، متاحة على Packagist.

هذا المقال يشرح السبب والكيفية. للسياق الأشمل — لماذا كنت بحاجة إلى هذا أصلًا — راجع بناء محفظة أعمال متعددة اللغات مع Symfony.

ما الذي يفعله runio/mde

في جوهره، يُغلّف runio/mde محرر Toast UI Editor داخل حزمة Symfony منظمة بشكل صحيح. Toast UI Editor مكتبة JavaScript ناضجة ومُصانة بنشاط تدعم:

  • وضعَي التحرير WYSIWYG وMarkdown الخام، قابلَين للتبديل في وقت التشغيل
  • معاينة العرض المقسوم (مصدر Markdown يسارًا، HTML مُصيَّر يمينًا)
  • الجداول، قوائم المهام، كتل الكود، الصور، الروابط
  • شريط أدوات نظيف وسهل الوصول

تُضيف الحزمة ما تفتقده المكتبة الخام في سياق Symfony:

  • نوع نموذج Symfony (MarkdownEditorType) مع جميع الخيارات القابلة للتكوين
  • فلتر Twig |markdown لعرض Markdown المخزَّن في صفحات العرض
  • طبقة CSS لـ RTL/LTR تتعامل بشكل صحيح مع العربية، العبرية، الفارسية، الأردية
  • جميع الأصول تُقدَّم محليًا — لا uicdn.toast.com، لا fonts.googleapis.com
  • توافق مع Symfony Asset Mapper
  • دعم Symfony UX Turbo
  • تخطيط متجاوب للأجهزة المحمولة عبر CSS Media Queries
  • تسجيل صحيح للخدمات (لا حلول بديلة يدوية في services.yaml)

التثبيت

composer require runio/mde
php bin/console assets:install --symlink

إذا كان مشروعك يستخدم Asset Mapper (Symfony 6.3+)، أضف مسار أصول الحزمة إلى config/packages/asset_mapper.yaml:

framework:
    asset_mapper:
        paths:
            assets/: ''
            vendor/runio/mde/public/: bundles/runiomarkdowneditor/

ثم قم بالتجميع:

php bin/console asset-map:compile

سجّل نموذج Theme في config/packages/twig.yaml:

twig:
    form_themes:
        - '@RunioMarkdownEditor/form/markdown_editor_widget.html.twig'

استخدام نوع النموذج

use Runio\MarkdownEditorBundle\Form\Type\MarkdownEditorType;

$builder->add('content', MarkdownEditorType::class, [
    'editor_mode'      => 'wysiwyg',   // wysiwyg | markdown | both
    'rtl_enabled'      => false,
    'language'         => 'en',
    'toolbar'          => 'full',      // full | basic | minimal
    'preview_style'    => 'vertical',  // vertical | tab
    'theme'            => 'light',     // light | dark
    'editor_height'    => '500px',
    'show_mode_switch' => true,
]);

// للمحتوى العربي:
$builder->add('contentAr', MarkdownEditorType::class, [
    'editor_mode'  => 'wysiwyg',
    'rtl_enabled'  => true,
    'language'     => 'ar',
]);

عرض Markdown المخزَّن

في قوالب Twig الخاصة بك، استخدم فلتر |markdown:

<div class="prose">
    {{ post.content | markdown }}
</div>

يُمرر الفلتر المحتوى عبر CommonMark وطبقة تعقيم تُزيل الوسوم والخصائص غير الآمنة. التعقيم قابل للتكوين:

# config/packages/runio_markdown_editor.yaml
runio_markdown_editor:
    sanitization:
        enabled: true
        allowed_tags: [p, br, strong, em, blockquote, ul, ol, li, a, img, h1, h2, h3, h4, h5, h6, pre, code, table, thead, tbody, tr, th, td, hr]
        allowed_attributes: [href, src, alt, title, class, id, dir, lang]

البنية المعمارية

طبقة PHP

تُقدّم الحزمة ثلاث خدمات:

MarkdownParser — يحوّل Markdown إلى HTML باستخدام league/commonmark. قابل للتكوين عبر قسم markdown في تكوين الحزمة:

runio_markdown_editor:
    markdown:
        html_input: escape        # strip | allow | escape
        allow_unsafe_links: false
        enable_table_extension: true
        enable_strikethrough_extension: true
        enable_tasklist_extension: true
        enable_autolink_extension: true

RTLTextDetector — يكتشف ما إذا كانت السلسلة النصية تحتوي على أحرف RTL (نطاقات Unicode للعربية، العبرية، الفارسية، الأردية). يُستخدم للكشف التلقائي عن اتجاه النص.

SanitizationService — يُزيل وسوم HTML غير المسموح بها وخصائصها من المخرجات المُحلَّلة.

MarkdownRuntime — وقت تشغيل Twig الذي يُسجّل فلترَي |markdown و|markdown_inline. هذا مُسجَّل كخدمة صحيحة في services.yaml الخاص بالحزمة — لا يلزم تسجيل يدوي في التطبيق.

طبقة JavaScript

JavaScript (public/js/markdown-editor.js) هو غلاف رفيع حول toastui.Editor. يقوم بـ:

  1. التحقق من توفر toastui قبل التهيئة — يعود بأناقة إلى textarea بسيط إذا فشل تحميل JS
  2. اختيار الارتفاع المتجاوب وأسلوب المعاينة بناءً على نافذة العرض عند التحميل الأول
  3. مزامنة قيمة <textarea> المخفية عند كل تغيير في المحرر وعند إرسال النموذج
  4. معالجة رفع الصور عبر fetch إذا كان URL الرفع مكوَّنًا
  5. دعم تنقل Turbo عبر أحداث دورة الحياة turbo:load وturbo:before-cache

يُهيَّأ المحرر تلقائيًا على أي عنصر .markdown-editor-rtl-wrapper في DOM، ويُعيد التهيئة على المحتوى المضاف ديناميكيًا عبر MutationObserver.

طبقة CSS

يُطبّق CSS الخاص بـ RTL (public/css/markdown-editor-rtl.css) التنسيق الصحيح عندما يمتلك الغلاف dir="rtl":

  • محاذاة النص واتجاهه لجميع مناطق المحتوى
  • حدود الاقتباس مُقلوبة إلى الجانب الأيمن
  • حشو القوائم مُقلوب إلى الجانب الأيمن
  • كتل الكود دائمًا LTR بغض النظر عن الاتجاه المحيط
  • الفاصل (خط تقسيم العرض المقسوم) مُوضَّع بشكل صحيح — هذا كان إصلاح الخلل الرئيسي

يتعامل قسم الاستجابة للأجهزة المحمولة مع:

  • تحجيم أيقونات شريط الأدوات عند <768px و<480px
  • شريط أدوات القائمة المنسدلة محدود بعرض المحرر (يمنع التجاوز)
  • لوحات العرض المقسوم تتراكم عموديًا على الأجهزة المحمولة
  • تصحيح موضع النوافذ المنبثقة لنوافذ العرض الصغيرة

الأخطاء التي أصلحتها

كان للتكامل السابق مفتوح المصدر مع Symfony لـ Toast UI خطآن مهمان:

1. الفاصل غير المرئي في وضع RTL

في وضع العرض المقسوم مع تفعيل RTL، كان خط التقسيم بين حقل إدخال Markdown والمعاينة غير مرئي. السبب: قاعدة CSS تُضيف left: auto; right: 0 إلى .toastui-editor-md-splitter. ذلك نقل العنصر إلى left: 859px في حاوية بعرض 860px — بكسل واحد خارج الشاشة. إزالة هذا التجاوز يتيح لـ Toast UI تحديد موضعه بشكل صحيح عند 50%.

2. وقت تشغيل Twig غير المُسجَّل

ملف services.yaml في الحزمة السابقة كان تسجيل خدمة MarkdownRuntime مُعلَّقًا بتعليق. كان فلتر |markdown يبدو أنه موجود (الإضافة كانت مُسجَّلة) لكن استدعاءه كان يُطلق خطأ وقت التشغيل. الحل هو ببساطة التأكد من تسجيل الخدمة بشكل صحيح — سطر واحد في services.yaml.

التكوين متعدد اللغات

التكوين الافتراضي يستهدف المحتوى العربي، لكن أي لغة RTL تعمل:

runio_markdown_editor:
    default_config:
        rtl_enabled: true
        language: ar
    rtl:
        languages: [ar, he, fa, ur]
        auto_detect: true
        arabic_font: 'Noto Naskh Arabic'

تُتيح التجاوزات على مستوى الحقل خلط محررات RTL وLTR في نفس النموذج — وهو بالضبط ما أفعله في نموذج مقالة المدوّنة في sym-port — محرر واحد لكل لغة، مُكوَّن بشكل مناسب.

ما القادم

الحزمة وظيفية ومستخدمة بنشاط على محفظتي الشخصية. التحسينات المُخطَّطة:

  • واجهة رفع الصور — نافذة حوار رفع مناسبة بدلًا من الاعتماد على نقطة نهاية خارجية
  • أزرار شريط أدوات مخصصة — السماح للتطبيقات بتسجيل عناصر شريط أدوات إضافية
  • مساعد Atom Feed — دالة Twig لعرض محتوى Markdown في سياقات التغذية

الكود المصدري على GitHub والحزمة على Packagist تحت runio/mde. المشكلات وطلبات السحب مرحب بها.

شارك هذا المنشور

AM

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

مطور متكامل مع أكثر من سنتين من الخبرة في تطوير PHP الكائني (Symfony) و JavaScript و MySQL. متخصص في حلول التجارة الإلكترونية (Shopware 5/6) وتطوير REST API وأتمتة العمليات في الفرق الرشيقة.

تواصل مع الكاتب