تحدي محرر Markdown: بناء محفظة أعمال متعددة اللغات مع Symfony

Feb 01, 2026 8 دقيقة قراءة 113 مشاهدة
تحدي محرر Markdown: بناء محفظة أعمال متعددة اللغات مع Symfony

حين شرعت في بناء موقع محفظة أعمالي الشخصية، كان لديّ متطلب واحد لا يقبل التفاوض: أن يدعم اللغات العربية والإنجليزية والألمانية بالتساوي. ليس كفكرة لاحقة، ولا بحلول مؤقتة — بل بشكل صحيح، مع دعم كامل للكتابة من اليمين لليسار (RTL) للعربية، ومن اليسار لليمين (LTR) للغتين الأخريين. ما لم أتوقعه هو مقدار ما سيعقّده هذا المتطلب الواحد في جزء يبدو بسيطًا من المشروع: محرر محتوى المدوّنة.

المشروع

محفظة الأعمال التي أسميتها sym-port هي تطبيق Symfony 7 مبني كموقع شخصي متكامل يضم مدوّنة وعرضًا للمشاريع وشهادات العملاء وترجمات متعددة اللغات ونموذج تواصل. المكدّس التقني حديث بشكل مقصود:

  • Symfony 7.3 مع Asset Mapper (بدون Webpack)
  • Doctrine ORM 3 مع MySQL
  • Symfony UX Turbo للتنقل السريع بين الصفحات
  • Tailwind CSS عبر حزمة SymfonyCasts
  • VichUploaderBundle لرفع الملفات
  • نظام ترجمة مخصص قائم على قاعدة البيانات لنصوص الواجهة

كانت المدوّنة دائمًا ميزة محورية. أكتب عن تطوير الويب بالعربية والإنجليزية، وأردت الوصول إلى القراء الناطقين بالألمانية أيضًا. كل مقالة في المدوّنة لها ثلاثة حقول محتوى منفصلة — واحد لكل لغة — تُخزَّن بتنسيق Markdown وتُعرض بتنسيق HTML عند الطلب.

مشكلة المحرر

لمدوّنة تخزّن Markdown، هناك خياران: كتابة Markdown الخام في حقل textarea بسيط، أو استخدام محرر WYSIWYG غني يعرض المخرجات مسبقًا. للمحتوى العربي تحديدًا، الكتابة في textarea بسيط أمر غير عملي. النص العربي يسير من اليمين لليسار، ومعظم حقول textarea مُهيأة افتراضيًا لليسار-لليمين — يبدأ المؤشر من الجانب الخطأ، يلتف النص بشكل غير صحيح، وقراءة ما كتبته تصبح مرهقة.

كنت بحاجة إلى محرر WYSIWYG لـ Markdown يستطيع:

  1. عرض النص العربي (RTL) بشكل صحيح — محاذاة لليمين، موضع مؤشر صحيح
  2. معالجة النص الإنجليزي والألماني (LTR) في نفس جلسة التحرير
  3. توفير عرض مقسوم: مصدر Markdown على جانب، معاينة مُصيَّرة على الجانب الآخر
  4. العمل مع نظام نماذج Symfony
  5. تقديم جميع الملفات محليًا — لا اعتماد على شبكات CDN في بيئة الإنتاج

المتطلب الأخير أهم مما يبدو. موثوقية بيئة الإنتاج لا ينبغي أن تعتمد على توفر uicdn.toast.com أو fonts.googleapis.com. أردت استضافة كل شيء ذاتيًا.

ما جربته

CKEditor و TinyMCE

كلاهما محرران ناضجان ومستخدمان على نطاق واسع. كلاهما يمتلك بعض دعم RTL. لكن أيًّا منهما ليس محرر Markdown حقيقيًا — فهما ينتجان HTML مباشرة. التحويل بين HTML وMarkdown يُدخل غموضًا وضياعًا في التنسيق. كما أن حزم Symfony الخاصة بهما تُضيف تعقيدًا كبيرًا.

SimpleMDE / EasyMDE

محررا Markdown خالصان، نظيفان وخفيفان. لكن دعم RTL فيهما شبه معدوم. لوحة المعاينة لا تُطبّق اتجاه RTL على النص العربي، وحقل textarea لا يكتشف اتجاه النص، ولا توجد طريقة لتكوين ذلك دون تفرّع من المشروع.

Toast UI Editor (مستقل)

حقّق Toast UI Editor معظم المتطلبات: إنه محرر Markdown حقيقي (يخزّن ويقرأ تنسيق .md)، يمتلك وضع WYSIWYG، يدعم المعاينة المقسومة، ويُصان بنشاط. المشكلة: تكاملات Symfony الموجودة التي وجدتها كلها كانت تحمّل الملفات من CDN، ولم يعالج أيٌّ منها RTL بشكل صحيح من البداية.

خط تقسيم العرض المقسوم — الخط الفاصل المرئي بين حقل إدخال Markdown ولوحة المعاينة — كان غير مرئي تمامًا في وضع RTL. تبيّن أن السبب خطأ في CSS: قاعدة تضبط left: auto; right: 0 على عنصر الفاصل، تدفعه إلى الحافة البعيدة من الحاوية بدلًا من المنتصف. خطأ صغير، لكنه يُعطّل العرض المقسوم كليًا.

كانت هناك أيضًا مشكلة PHP أخطر: حزمة Symfony الوحيدة الموجودة لـ Toast UI Editor كانت خدمة MarkdownRuntime الخاصة بـ Twig مُعلَّقة بتعليق في ملف services.yaml الخاص بها. سيفشل الفلتر |markdown في القوالب بصمت ما لم تُسجّل الخدمة يدويًا في services.yaml الخاص بتطبيقك. اكتشفت ذلك فقط بعد النشر حين تساءلت لماذا كانت مقالات المدوّنة تُعرض كنص Markdown خام.

قرار بناء runio/mde

بعد أن أمضيت وقتًا في إصلاح هذه المشاكل في مشروع متفرّع، أدركت أنني أُدير شيئًا سيتباعد عن المصدر الأصلي ويصبح عبئًا للصيانة. الطريق الأفضل كان بناء حزمة Symfony منظمة بشكل صحيح تقوم بـ:

  • إصلاح خلل فاصل RTL بشكل دائم
  • تسجيل جميع الخدمات بشكل صحيح — لا حلول بديلة يدوية
  • تجميع Toast UI Editor وخطوطه محليًا
  • إضافة استجابة للأجهزة المحمولة (CSS الموجود كان يفتقر فعليًا لأي دعم للأجهزة المحمولة)
  • التكامل الصحيح مع Symfony Asset Mapper
  • دعم Symfony UX Turbo من البداية

النتيجة هي runio/mde — حزمة Symfony منشورة على Packagist. للقصة التقنية الكاملة حول كيفية بنائها وما هي المشاكل التي تحلّها تحت الغطاء، راجع مقالتي التالية: تقديم runio/mde: حزمة محرر Markdown جاهزة لـ RTL لـ Symfony.

كيف يستخدمها sym-port

في نموذج مقالة المدوّنة، كل حقل لغة يستخدم MarkdownEditorType:

$builder->add('content', MarkdownEditorType::class, [
    'editor_mode'  => 'wysiwyg',
    'rtl_enabled'  => false,
    'language'     => 'en',
    'toolbar'      => 'full',
]);

$builder->add('contentAr', MarkdownEditorType::class, [
    'editor_mode'  => 'wysiwyg',
    'rtl_enabled'  => true,
    'language'     => 'ar',
    'toolbar'      => 'full',
]);

علامة RTL تعكس المحرر بالكامل — اتجاه شريط الأدوات، محاذاة النص، جانب حدود الاقتباس، حشو القوائم — عبر CSS مطبّق على عنصر الغلاف. تبقى كتل الكود دائمًا LTR بغض النظر عن اتجاه المحتوى المحيط.

تُخزَّن مقالات المدوّنة كـ Markdown في قاعدة البيانات. عند العرض، يحوّل فلتر Twig |markdown (المقدَّم من الحزمة) المحتوى إلى HTML مُعقَّم. طبقة التعقيم تُزيل الوسوم والخصائص غير الآمنة، لذا يأمن عرض المحتوى المُولَّد من المحرر مباشرة.

دروس مستفادة

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

اعتمادات CDN خطر على النشر. استضافة JavaScript المحرر الحيوي على uicdn.toast.com يعني أن انقطاع CDN أو تغيير سياسة الشبكة لدى مزوّد الاستضافة يمكنه تعطيل لوحة التحكم الخاصة بك بصمت. استضِف ما يهمك بنفسك.

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

المحفظة متاحة على runio.dev. يتعامل محرر المدوّنة الآن مع المحتوى العربي والإنجليزي والألماني بشكل صحيح في نموذج واحد، مع معاينة عرض مقسوم تعمل بشكل صحيح ودون اعتمادات CDN. هذا هو المستوى الأساسي الذي كان ينبغي أن أبدأ به من البداية.

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

AM

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

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

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