Skip to content

النصّ والتحرير

التحرير قدرة في المحرّك لا تلفيق في التطبيق

العيب المعتاد في الحلول القائمة على canvas هو وضع عنصر input من HTML فوق الـ canvas عند الحاجة إلى الإدخال، فتتوالى المشكلات: انزياح المؤشّر النصّي، وانحراف نافذة مرشّحات IME، وعدم تزامن التمرير، وانقطاع إمكانية الوصول.

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

استخدام العناصر الجاهزة

ts
import { TextField, TextArea } from "@dopejs/pingo";

TextField({
  value: order.note,
  revision: order.revision,
  semanticLabel: "ملاحظة الطلب",
  inputMode: "text",
  onTransaction: (transaction) => order.apply(transaction),
});

TextArea({ value: description, revision, rows: 4 });

استخدام البدائية

ts
createElement("editableText", {
  value,
  revision,
  multiline: false,
  readOnly: false,
  password: false,
  maxGraphemes: 200,
  inputMode: "email",
  onTransaction: (transaction) => apply(transaction),
  onSubmit: () => moveToNextCell(),
});

أو عبر متحكّم محلّي:

ts
import { useTextEditingController } from "@dopejs/pingo";

const editor = useTextEditingController({ value: cell.value });
createElement("editableText", { controller: editor });

جسر الإدخال والتراجع

يتّصل الخيط الرئيسي بخدمة إدخال النصّ في نظام التشغيل حسب الأولوية:

  1. EditContext — يرتبط بالـ canvas ويستقبل النصّ والتحديد والتركيب، ويوفّر لطريقة الإدخال control و‏selection وحدود المحارف.
  2. وكيل إدخال يديره المحرّك — عند تعذّر EditContext يحتفظ المضيف بعنصر textarea مخفيّ واحد يعالج beforeinput والتركيب ولوحة المفاتيح البرمجية والحافظة معالجةً موحّدة.

والثاني تنفيذ تراجع خاصّ بالمنصّة لا نموذج مكوّنات EmbedDOM: فلا يوجد في الـ Scene عنصر DOM يقابل كلّ عقدة تحرير واحدًا بواحد. ويجتاز المساران مجموعة اختبارات العقد السلوكي نفسها.

معاملات تحرير ذات إصدارات

ملكيّة الحالة صريحة: يملك الغلاف بيانات العمل، وتملك النواة الحالة اللحظية لجلسة التحرير النشطة.

إدخال ← تتحقّق النواة من base_revision ← تطبّق وتعيد الرسم فورًا ← تُصدر عكسيًا EditTransaction ذات إصدار

                                       يؤكّد التطبيق، أو يرسل قيمة مصحَّحة بإصدار جديد

لن يطمس معاملٌ قديم حالةً أحدث أبدًا. بمعنى أنّ كلّ ضغطة مفتاح لا تفرض دورة بناء TSX كاملة، وتبقى مع ذلك البيانات المتحكَّم بها والتحقّق على مستوى العمل قائمَين.

ts
onTransaction: (transaction) => {
  // transaction.baseRevision / revision / delta / selection / kind
  value = applyDelta(value, transaction);
};

نموذج مواضع النصّ

تستعمل واجهات الإدخال في الويب إزاحات UTF-16، وسلاسل Rust بترميز UTF-8، وحدود العناقيد الحرفية وعناقيد التشكيل والمحارف المرسومة مختلفة فيما بينها. لذلك يحتفظ المحرّك بتناظر صريح:

إزاحة UTF-16 ↔ قيمة Unicode ↔ عنقود حرفي ↔ عنقود تشكيل ↔ محرف مرسوم / سطر

وعلى حدّ البروتوكول يُستعمل UTF-16 دائمًا لمواءمة EditContext وInputEvent. ولا يشقّ الحذف ولا التنقّل ولا التحديد عنقودًا حرفيًا ولا متتالية تركيب ولا رمزًا تعبيريًا موصولًا بـ ZWJ ولا عنقود تشكيل، وتحرس ذلك اختبارات الخصائص ومصفوفة تجهيزات التركيب (محارف التركيب، ورموز ZWJ، والاتجاه من اليمين إلى اليسار، ومرشّحات CJK متعدّدة المقاطع).

كلمات المرور والخصوصية

لا يدخل نصّ كلمة المرور في التسجيل وإعادة التشغيل ولا في السجلّات ولا في النصّ الصريح داخل أدوات المطوّرين ولا في قيم إمكانية الوصول، ولا يكتب هدفُ كلمة المرور إلى الحافظة. ولا تُخرج النواة سوى محارف مقنّعة، فلا يصل النصّ الصريح إلى DisplayList أصلًا. وتؤكّد ذلك اختبارات آلية، ويمكنك أيضًا فحص DOM بنفسك في الـ Playground المنشور.

حدود معروفة

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

منشور برخصة MIT