محرر نص منسق بنمط Notion - متطلبات التنفيذ

هذا المستند موجود بجانب الكود ويجب أن يظل محدثًا كلما تغير تنفيذ المحرر.

بيان المشكلة

  • يلتف المكون <MDXEditorComponent> الحالي حول @mdxeditor/editor مما يمنحنا تحرير Markdown الكلاسيكي.
  • يرغب المؤلفون في تجربة شبيهة بـ Notion، قائمة على الكتل: أوامر الشرطة المائلة، سحب الكتل لإعادة ترتيبها، قوائم المهام، الجداول، التضمينات، إلخ.
  • يجب أن يتكامل البديل بسلاسة مع محملات/إجراءات React Router، ويستمر في الحفظ التلقائي للمسودات، ويعمل في كل من تدفقات الإنشاء الجديد والتحرير.

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

  • استبدال MDXEditor بـ TipTap + ProseMirror – محرر مفتوح المصدر، بدون واجهة، قابل للتوسيع، يستخدمه Craft و GitLab و ChatGPT وغيرهم.
  • الحفاظ على مكون غلاف رفيع (NotionEditor.tsx) يخفي تفاصيل TipTap ويعرض واجهة برمجة تطبيقات مألوفة (value, onChange).
  • الاحتفاظ بـ JSON أو Markdown (مصفوفة القرار أدناه) في عمود blog_posts.content الحالي؛ تساعد أدوات التحويل في الحفاظ على التوافق مع الإصدارات السابقة.

الحزم المراد إضافتها

pnpm --filter web add @tiptap/react @tiptap/starter-kit @tiptap/extension-placeholder \ @tiptap/extension-link @tiptap/extension-task-list @tiptap/extension-task-item \ @tiptap/extension-underline @tiptap/extension-table @tiptap/extension-table-row \ @tiptap/extension-table-cell @tiptap/extension-table-header \ @tiptap/extension-code-block-lowlight lowlight classnames

إضافات اختيارية (عمل مستقبلي): @hocuspocus/provider للمزامنة في الوقت الفعلي، tiptap-extension-command لقائمة الشرطة المائلة، tiptap-extension-placeholder للذكاء الاصطناعي.

تخطيط الملفات

apps/web/app/components/rich-text/ NotionEditor.tsx <-- غلاف TipTap قابل لإعادة الاستخدام (export default) EditorBubbleMenu.tsx <-- شريط أدوات التنسيق المضمن EditorSlashMenu.tsx <-- قائمة إدراج الكتل editor-extensions.ts <-- مصنع يعيد الامتدادات الشائعة serializer.ts <-- مساعدات JSON ⇆ Markdown (showdown / remark)

ملاحظة: احتفظ بالمجلد mdx-editor/ الحالي حتى اكتمال الترحيل؛ ثم قم بإيقافه.

قائمة التحقق من التنفيذ

  1. إنشاء مكون المحرر ✅

    • استخدام useEditor من @tiptap/react.
    • تحميل StarterKit + الامتدادات الإضافية (المعرفة في editor-extensions.ts).
    • عرض الخصائص: defaultContent, readOnly, onUpdate (يعيد كلاً من JSON و Markdown).
    • تطبيق فئات Tailwind لمظهر Notion (prose, focus:outline-none, إلخ).
  2. BubbleMenu & SlashMenu ✅

    • BubbleMenu: غامق، مائل، تسطير، رابط.
    • SlashMenu: /h1, /h2, /table, /code, /todo, /image.
    • استخدام Headless UI / Radix Command لقائمة سهولة الوصول (a11y).
  3. تكامل الحفظ التلقائي ✅

    • في triggerAutosave() استبدل
      const currentContent = editor.getJSON();
    • إرسال كـ JSON.stringify(currentContent).
  4. تغييرات المسار (posts/new & posts/[id]) ✅

    • تبديل <MDXEditorComponent> ← <NotionEditor>.
    • يحتوي <input name="content" /> المخفي الآن على الحمولة المسلسلة.
    • الحفاظ على منطق Sheet الحالي والحوارات والحفظ التلقائي.
  5. قرار قاعدة البيانات ✅

الخيارالإيجابياتالسلبيات
عمود text، تخزين Markdownلا تغيير في المخططفقدان ثراء الكتل (الجداول، التضمينات)
عمود jsonb، تخزين TipTap JSONدقة كاملة، مقاومة للمستقبليتطلب ترحيل وتسلسل عند القراءة للمشاركات القديمة

الخطة الحالية: الترحيل إلى jsonb ولكن شحن محول قراءة/كتابة Markdown لتجنب كسر المدونة العامة على الفور.

  1. نص الترحيل البرمجي ✅

    • ترحيل Drizzle بإضافة عمود content_json jsonb جديد (قابل للإلغاء).
    • التعبئة الخلفية: content_json = markdownToTiptapJSON(content).
    • في النهاية، إسقاط عمود content القديم.
  2. اعتبارات SSR والبناء

    • يستخدم TipTap window؛ قم بتغليف الخطاف في حارس useClient أو استيراد كسول داخل useEffect لمسارات SSR.
    • الاستمرار في التحميل الكسول للحفاظ على حجم الحزمة صغيرًا.
  3. إمكانية الوصول واختصارات لوحة المفاتيح

    • Cmd/Ctrl + b/i/u للعلامات.
    • Cmd/Ctrl + s → يفتح مربع حوار حفظ المسودة (موجود بالفعل).
    • Cmd/Ctrl + Shift + p → نشر.
  4. الاختبار وضمان الجودة

    • اختبار وحدة لمصنع الامتدادات – يعيد القائمة المتوقعة.
    • Cypress e2e: إنشاء مشاركة، إضافة كتل، حفظ تلقائي، تحديث – المسودة تستمر.
    • الانحدار البصري باستخدام Storybook (اختياري).
  5. التحسينات المستقبلية

    • مؤشرات تعاونية مع Y.js.
    • وضع التعليق والاقتراح.
    • أمر شريط مائل للذكاء الاصطناعي لتوليد المحتوى.
    • معاينات التضمين (Twitter، Loom، Figma، ...).

مقتطف مرجعي – NotionEditor.tsx

import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import Placeholder from "@tiptap/extension-placeholder"; import TaskList from "@tiptap/extension-task-list"; import TaskItem from "@tiptap/extension-task-item"; import Link from "@tiptap/extension-link"; export function NotionEditor({ defaultContent, onUpdate }) { const editor = useEditor({ extensions: [ StarterKit.configure({ codeBlock: false }), Placeholder.configure({ placeholder: "اكتب / للكتل" }), TaskList, TaskItem, Link.configure({ openOnClick: false }) ], content: defaultContent, onUpdate: ({ editor }) => { onUpdate?.({ json: editor.getJSON(), markdown: editor.getHTML() // أو مُسلسل مخصص }); } }); return ( <div className="prose leading-relaxed max-w-none border rounded-md p-4 focus:outline-none"> <EditorContent editor={editor} /> </div> ); }

الجدول الزمني للترحيل

الأسبوعالمهمة
1بناء مكون المحرر والامتدادات الأساسية
2التكامل في مسار المشاركة الجديدة، الحفاظ على MDXEditor جنبًا إلى جنب كخيار احتياطي
3تحديث صفحة التحرير، تمكين تحويل Markdown→JSON
4ترحيل قاعدة البيانات، التعبئة الخلفية، إزالة كود المحرر القديم

المشرف: @samishukri • آخر تحديث: ٢٣‏/١٠‏/٢٠٢٦