للمواضيع والإضافات المتقدمة، يوفّر Discourse نظام modifyClass. يتيح لك هذا النظام توسيع وظائف العديد من فئات جافاسكربت الأساسية والتغلب عليها.
متى يجب استخدام modifyClass
يجب أن يكون modifyClass الخيار الأخير، عندما لا يمكن إجراء التخصيص المطلوب عبر واجهات برمجة التطبيقات (APIs) الأكثر استقرارًا في Discourse (مثل دوال plugin-api، ومخارج الإضافات plugin outlets، والمحوّلات transformers).
يمكن أن يتغير الكود الأساسي في أي وقت. وبالتالي، فإن أي تخصيصات تتم عبر modifyClass قد تتعطل في أي وقت. عند استخدام هذه الواجهة، يجب أن تتأكد من وجود ضوابط في مكانها لاكتشاف هذه المشكلات قبل وصولها إلى الموقع الإنتاجي. على سبيل المثال، يمكنك إضافة اختبارات آلية إلى الموضوع/الإضافة، أو يمكنك استخدام موقع تجريبي (staging site) لاختبار تحديثات Discourse الواردة مقابل موضوعك/إضافتك.
الاستخدام الأساسي
يمكن استخدام api.modifyClass لتعديل الدوال والخصائص في أي فئة يمكن الوصول إليها عبر محلل Ember (Ember resolver). ويشمل ذلك مسارات Discourse (routes)، ومُتحكّماتها (controllers)، وخدماتها (services)، ومكوّناتها (components).
تتقبّل modifyClass حجتين:
-
resolverName(نص string) - يُبنى هذا الاسم باستخدام النوع (مثل component/controller/إلخ)، متبوعًا بنقطة وفاصلة، ثم اسم ملف الفئة (المُنقّط dasherized). على سبيل المثال:component:d-button،component:modal/login،controller:user،route:application، إلخ. -
callback(دالة function) - دالة تستقبل تعريف الفئة الموجود، ثم تُعيد نسخة موسّعة منه.
على سبيل المثال، لتعديل إجراء click() على d-button:
api.modifyClass(
"component:d-button",
(Superclass) =>
class extends Superclass {
@action
click() {
console.log("button was clicked");
super.click();
}
}
);
تقلّد صيغة class extends ... تلك الخاصة بفئات جافاسكربت الفرعية. بشكل عام، يمكن تطبيق أي صيغة/ميزة مدعومة من قبل الفئات الفرعية هنا. ويشمل ذلك super، والخصائص/الدوال الثابتة (static properties/functions)، وأكثر من ذلك.
ومع ذلك، هناك بعض القيود. يكتشف نظام modifyClass التغييرات في prototype الخاص بفئة جافاسكربت فقط. عمليًا، يعني ذلك:
-
إدخال أو تعديل
constructor()غير مدعومapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // This is not supported. The constructor will be ignored } } ); -
إدخال أو تعديل حقول الفئة (class fields) غير مدعوم (على الرغم من أن بعض حقول الفئة المزينة، مثل
@tracked، يمكن استخدامها)api.modifyClass( "component:foo", (Superclass) => class extends Superclass { someField = "foo"; // NOT SUPPORTED - do not copy @tracked someOtherField = "foo"; // This is ok } ); -
لا يمكن التغلب على الحقول البسيطة للفئة في التنفيذ الأصلي بأي طريقة (على الرغم من أنه، كما هو موضح أعلاه، يمكن التغلب على حقول
@trackedبحقل@trackedآخر)// Core code: class Foo extends Component { // This core field cannot be overridden someField = "original"; // This core tracked field can be overridden by including // `@tracked someTrackedField =` in the modifyClass call @tracked someTrackedField = "original"; }
إذا وجدت نفسك ترغب في القيام بهذه الأمور، فقد يكون استخدامك أفضل تلبية من خلال تقديم طلب سحبه (PR) لإدخال واجهات برمجة تطبيقات جديدة في الكود الأساسي (مثل مخارج الإضافات، والمحوّلات، أو واجهات مخصصة).
ترقية الصيغة القديمة (Legacy Syntax)
في الماضي، كان يُستدعى modifyClass باستخدام صيغة كائن الحرفي (object-literal syntax) على النحو التالي:
// Outdated syntax - do not use
api.modifyClass("component:some-component", {
someFunction() {
const original = this._super();
return original + " some change";
}
pluginId: "some-unique-id"
});
لم تعد هذه الصيغة موصى بها، ولديها أخطاء معروفة (مثل التغلب على المُرجِّعات getters أو @actions). يجب تحديث أي كود يستخدم هذه الصيغة لاستخدام صيغة الفئة الأصلية (native-class syntax) الموصوفة أعلاه. بشكل عام، يمكن إجراء التحويل من خلال:
- إزالة
pluginId- لم تعد مطلوبة - التحديث إلى صيغة الفئة الأصلية الحديثة الموصوفة أعلاه
- اختبار تغييراتك
استكشاف الأخطاء وإصلاحها
تم تهيئة الفئة مسبقًا (Class already initialized)
عند استخدام modifyClass في مُهيّئ (initializer)، قد ترى هذا التحذير في وحدة التحكم (console):
Attempted to modify "{name}", but it was already initialized earlier in the boot process
في تطوير المواضيع/الإضافات، هناك طريقتان يتم من خلالهما إدخال هذا الخطأ عادةً:
-
إضافة
lookup()تسببت في الخطأإذا قمت بـ
lookup()لحالة مفردة (singleton) مبكرًا جدًا في عملية الإقلاع (boot process)، فسيتسبب ذلك في فشل أي استدعاءاتmodifyClassلاحقة. في هذه الحالة، يجب أن تحاول نقل عملية البحث (lookup) لتحدث في وقت لاحق. على سبيل المثال، ستقوم بتغيير شيء ما مثل هذا:// Lookup service in initializer, then use it at runtime (bad!) export default apiInitializer((api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });إلى هذا:
// 'Just in time' lookup of service (good!) export default apiInitializer((api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
إضافة
modifyClassجديد تسببت في الخطأإذا كان الخطأ ناتجًا عن إضافة موضوعك/إضافتك لاستدعاء
modifyClass، فستحتاج إلى نقله إلى وقت مبكر في عملية الإقلاع. يحدث هذا عادةً عند التغلب على الدوال في الخدمات (مثل topicTrackingState)، وفي النماذج (models) التي يتم تهيئتها مبكرًا في عملية إقلاع التطبيق (مثلmodel:userالذي يتم تهيئته لـservice:current-user).نقل استدعاء modifyClass إلى وقت مبكر في عملية الإقلاع يعني عادةً نقل الاستدعاء إلى مُهيّئ مسبق (pre-initializer)، وتكوينه ليتم تشغيله قبل مُهيّئ ‘inject-discourse-objects’ الخاص بـ Discourse. على سبيل المثال:
// (plugin)/assets/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js // or // (theme)/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js import { withPluginApi } from "discourse/lib/plugin-api"; export default { name: "extend-user-for-my-plugin", before: "inject-discourse-objects", initializeWithApi(api) { api.modifyClass("model:user", (Superclass) => class extends Superclass { myNewUserFunction() { return "hello world"; }, }); }, initialize() { withPluginApi(this.initializeWithApi); }, };يجب أن يعمل هذا التعديل على نموذج المستخدم الآن دون طباعة تحذير، وستكون الدالة الجديدة متاحة على كائن currentUser.
هذا المستند مُتحكَّم به في إصداراته - اقترح تغييرات على github.