محتويات بطاقة المستخدم المثبتة - السمة

لقد أنشأت سمة أكثر تفصيلاً لـ user-card-contents، وأضفت بعض حقول المستخدم المخصصة. أود جعل البطاقة ثابتة مع زر إغلاق، بطريقة مشابهة لمكون “إنشاء موضوع جديد”.

هل صحيح أنني سأحتاج إلى استبدال ملف user-card-contents.js لمنع استدعاء إغلاق العنصر؟ وهل سأتمكن من تضمين هذا داخل السمة؟

شكرًا لك!

مرحبًا يا بيت. أهلاً بك في Meta :wave:

الإجابة المختصرة هي نعم. إذا كانت التغييرات تؤثر فقط على الواجهة الأمامية، فيمكن تنفيذها ضمن سمة أو مكون.

هل يمكنك التوضيح أكثر بما تقصده بـ “ثابت” (sticky)؟ إذا كنت تقصد أنك تريد منه التمرير مع المحتوى مع البقاء في نفس الموقع، فسيكون ذلك تغييرًا في CSS. أيضًا، هل يُقصد تضمين التغيير على كل من سطح المكتب والجوال؟

هل يمكنك وصف الاستدعاء المحدد الذي تشير إليه؟ (مثل عند النقر أو عند التمرير، إلخ)

سيحتاج قالب زر الإغلاق إلى إضافته إلى قالب Handlebars لبطاقة المستخدم. أما المنطق اللازم للتعامل مع الإجراء عند النقر على الزر فيجب إضافته إلى ملف المكون .js.

قد لا يكون الاستبدال الكامل ضروريًا، فهناك بعض الخطافات (hooks) التي يمكنك استخدامها لاستبدال طرق محددة في فئة معينة. يمكنني مشاركة المزيد حول ذلك إذا وصفت ما تريد فعله بمزيد من التفصيل.

شكرًا لك على الترحيب جو!

ما أطمح إليه هو منع معالج الأحداث clickOutsideEventName في mixin card-contents-base.js من إغلاق البطاقة على سطح المكتب. بدلاً من ذلك، أود إجبار المستخدمين على النقر على زر لإغلاقها. سأحتاج على الأرجح إلى فعل شيء مختلف للجوال.

نجحت في جعل قالب الـ Handlebars يعمل، والآن على حل مشكلة ملف الـ .js :slight_smile:

TL;DR أعتقد أن هذا ما تبحث عنه

Theme JS

api.modifyClass("component:user-card-contents", {
  didInsertElement() {
    this._super(...arguments);
    $("html").off(this.clickOutsideEventName);
  },
  actions: {
    closeCard() {
      this._close();
    }
  }
});

ثم أضف هذا إلى قالبك في مكان ما

{{d-button
  class="btn-flat"
  action=(action "closeCard")
  icon="times"
}}

النسخة الطويلة

ربما كنت تعرف معظم هذا بالفعل لأنك عملت بالفعل على سمة (Theme) الخاصة بك، لكنني سأحاول أن أجعلها أكثر تفصيلاً قليلاً للجمهور الأوسع.

أول شيء سأفعله هو البحث إما محليًا أو على Github. على Github، ستحصل على شيء مثل هذا. في معظم الحالات، ستحتوي مصطلح البحث على أكثر من نتيجة واحدة، وسيكون عليك إما أن تكون أكثر تحديدًا أو مسح النتائج يدويًا للعثور على شيء قريب مما تريد.

لذا، الآن انتهى بنا المطاف إلى هذا الملف

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

هذا الملف هو Mixin. لماذا أذكر هذا؟ لأنك بحاجة إلى أن تكون على علم بأن Mixins يمكن مشاركتها في عدد من الأماكن المختلفة. في هذه الحالة، يتم استخدامها لكل من بطاقات المستخدمين وبطاقات المجموعات. لذا، فإن التغييرات التي تجريها هنا ستؤثر على كليهما.

إذا بحثت في الملف، ستجد أن clickOutsideEventName يتم تعريفه أولاً هنا

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

ثم يتم تمريره هنا كخاصية

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

وأخيرًا يتم استهلاكه هنا

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

رائع، لكن ماذا يعني كل ذلك؟ حسنًا، إذا نظرت إلى المكان الذي يتم فيه إضافة كل هذا الكود، ستلاحظ أنه داخل didInsertElement

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

توضح دليل Ember ما يلي:

يضمن Ember أنه بحلول الوقت الذي يتم فيه استدعاء didInsertElement():

  1. تم إنشاء عنصر المكون وإدراجه في DOM.
  2. يمكن الوصول إلى عنصر المكون عبر خاصية this.element الخاصة بالمكون.

لماذا نحتاج إلى هذا؟ لأننا نحتاج إلى معالج mousedown مختلف لبطاقات المستخدمين وبطاقات المجموعات. إذا عدنا قليلاً، ستلاحظ الآن أن معرف العنصر يتم استخدامه في clickOutsideEventName

والذي، كما ناقشنا أعلاه، يتم تمريره بعد ذلك كخاصية.

الآن، دعنا ننتقل إلى كيف يرتبط كل هذا بما تفعله.

أنت تحاول منع إغلاق البطاقات عندما ينقر المستخدم خارجها. لذا، دعنا نحاول معرفة طريقة للقيام بذلك. إذا كنت تتذكر، ناقشنا كيف يتم استهلاك clickOutsideEventName في النهاية هنا

باختصار، هذا يضيف معالج mousedown لعنصر HTML عند إدراج بطاقة (في أول عرض للصفحة). ثم نتحقق من هدف حدث mousedown. إذا كان الهدف في مكان ما داخل البطاقة، نتوقف. إذا كان خارج البطاقة، نغلقها عن طريق استدعاء this._close()

يتم تعريف _close() هنا

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

وهذا ما ستحتاج إلى استدعاؤه عند إضافة زر الإغلاق الخاص بك - لكننا سنعود إلى ذلك لاحقًا.

الآن، الهدف هو إزالة معالج mousedown هذا إذا كنت تريد أن لا يؤدي النقر خارج البطاقة إلى إغلاقها. إذن كيف نفعل ذلك؟ حسنًا، سنحتاج إلى تعديل didInsertElement() وإليك كيفية القيام بذلك.

تتمتع سمات Discourse بالوصول إلى واجهة برمجة التطبيقات الإضافية، والتي تحتوي على العديد من الطرق التي يمكنك استخدامها. هناك المزيد من التفاصيل حول الأكثر استخدامًا هنا

إذا كنت تتذكر، فإن الملف الذي نعمل عليه هو Mixin. Mixin هو فئة Ember. لذا، فإن الطريقة التي سنستخدمها هي modifyClass

https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/app/lib/plugin-api.js#L121-L124

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

نريد تعديل didInsertElement() لذا يمكننا فعل شيء مثل هذا

api.modifyClass('mixin:card-contents-base', {
  didInsertElement() {
    console.log("foo");
  }
});

ونجربها…

حسنًا، لم ينجح ذلك.

لماذا حدث ذلك؟ حسنًا، اتضح أن طريقة modifyClass لا تدعم حاليًا تعديل Mixins (بقدر ما اختبرت) سأقوم بتدوين ملاحظة لمعرفة سبب ذلك والتحقق مما إذا كان بإمكاننا إصلاح ذلك. ولكن في الوقت الحالي، دعنا نعود إلى ما تريد فعله.

حسنًا، لا يمكننا تعديل Mixins، لذا أعتقد أننا عالقون، أليس كذلك؟ لا. دعنا نحفر أعمق قليلاً.

كما ذكرت من قبل، يتم استخدام هذا Mixin بواسطة كل من المستخدم user-card-contents و group-card-contents مكونات Ember (مرة أخرى، لأن Mixins مصممة لجعل الكود قابلاً لإعادة الاستخدام)

لذا، دعنا ننظر إلى مكون user-card-contents هنا

discourse/app/assets/javascripts/discourse/app/components/user-card-contents.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

إذا قرأت بعناية، ستلاحظ أننا نستورد أولاً Mixin الذي ناقشناه هنا

ثم نقوم بإنشاء مكون Ember جديد ونمرر Mixin إليه.

ماذا يعني ذلك؟ يعني أن didInsertElement() لـ user-card-contents مُورث فعليًا من Mixin. يمكن قول الشيء نفسه عن group-card-contents.

أين يتركنا ذلك؟ حسنًا، هناك أخبار جيدة وأخبار سيئة. الأخبار الجيدة هي أنه إذا كنت تريد إجراء تغييرات على user-card-contents دون التأثير على group-card-contents، فيمكنك ذلك! الأخبار السيئة هي أنه إذا كنت تريد أن تنطبق تغييراتك على كليهما، فستحتاج إلى تكرار بعض الكود.

دعنا نعود إلى modifyClass ونحاول مرة أخرى مع user-card-contents. لذا شيء مثل هذا:

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    console.log("foo");
  }
});

ونرى ما يحدث…

voalá :tada:

تم تسجيل التغيير ويمكننا رؤيته في وحدة التحكم، لكننا لم نصل بعد.

لذا، الآن بعد أن أصبح بإمكاننا تعديل didInsertElement()، دعنا نحاول العودة إلى ما تحاول فعله. إذا كنت تتذكر، يتم تعريف معالج mousedown في didInsertElement هنا

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

إذن ماذا يمكننا أن نفعل حيال ذلك؟ حسنًا، الأمر بسيط مثل هذا

$("html").off(clickOutsideEventName)

هل سيعمل ذلك؟ لا. إذا قمت بذلك

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    $("html").off(clickOutsideEventName)
  }
});

ستنتهي بشيء مكسور. لماذا حدث ذلك؟ لأن هذا ليس تعديلًا للطريقة. هذا تجاوز كامل لطريقة didInsertElement() الأساسية لذلك المكون. لذا، لا يتم تطبيق أي من الكود الأساسي فعليًا إذا قمت بذلك.

كيف نصلح هذا؟ حسنًا، اتضح أن Ember لديها شيء يسمى this._super(...arguments)

ماذا يفعل ذلك؟ يسمح لك بإضافة أو إضافة كود في إضافة إلى ما تحتويه طريقة الفئة بالفعل. على سبيل المثال، إذا قمت بذلك

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    // الكود الذي تريد إضافته
    $("html").off(clickOutsideEventName);
    // الكود من الأساس
    this._super(...arguments);
  }
});

فإن الكود الذي تريد إضافته سيتم تشغيله قبل أي شيء آخر هنا

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

ومع ذلك، إذا قمت بذلك…

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    // الكود من الأساس
    this._super(...arguments);
    // الكود الذي تريد إضافته
    $("html").off(clickOutsideEventName);
  }
});

فإن كودك سيتم تشغيله بعد أن ينتهي كل شيء هنا

discourse/app/assets/javascripts/discourse/app/mixins/card-contents-base.js at e5dc843185feb268c277bb0ee4db9666d6452783 · discourse/discourse · GitHub

هذه طريقة رائعة للحفاظ على سمعتك مرنة تجاه التغييرات في الأساس لأن كل شيء في الأساس يتم تشغيله أولاً، ثم يتم تشغيل كودك بعد ذلك. لذا، دعنا نجرب هذا مرة أخرى ونرى ما يحدث.

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    // الكود من الأساس
    this._super(...arguments);
    // الكود الذي تريد إضافته
    $("html").off(clickOutsideEventName);
  }
});

و…

لماذا يحدث هذا؟ ذلك بسبب سياق كود مختلف. في ملف مكون Ember، يتم تعريف clickOutsideEventName بالفعل بحلول الوقت الذي يتم فيه استهلاكه. سمة (Theme) الخاصة بك في ملف مختلف، لذا clickOutsideEventName غير معرف هناك.

كيف نصلح ذلك؟ هل تتذكر هذا؟

clickOutsideEventName هي خاصية للمكون، لذا إذا استخدمت this.clickOutsideEventName فيجب أن يعمل. دعنا نجرب ذلك.

api.modifyClass('component:user-card-contents', {
  didInsertElement() {
    // الكود من الأساس
    this._super(...arguments);
    // الكود الذي تريد إضافته
    $("html").off(this.clickOutsideEventName);
  },
});

وفي الواقع يعمل :tada:

تفتح بطاقات المستخدمين الآن دون أي أخطاء والنقر في أي مكان خارجها لا يفعل شيئًا.

يمكنك فعل نفس الشيء تمامًا لبطاقات المجموعات مثل هذا

api.modifyClass('component:group-card-contents', {
  didInsertElement() {
    // الكود من الأساس
    this._super(...arguments);
    // الكود الذي تريد إضافته
    $("html").off(this.clickOutsideEventName);
  },
});

الشيء الوحيد المتبقي هو ربط زر الإغلاق هذا بطريقة _close() التي ناقشناها سابقًا وهناك ثلاث خطوات لذلك.

  1. إضافة إجراء closeCard (يمكنك تسميته بما تريد)
  2. إضافة زر إلى قالب بطاقة المستخدم
  3. استدعاء ذلك الإجراء عند النقر على الزر.

سألتزم ببطاقات المستخدمين للتبسيط. لذا، نضيف هذا (إلى جانب ما ناقشناه بالفعل)

api.modifyClass("component:user-card-contents", {
  didInsertElement() {
    this._super(...arguments);
    $("html").off(this.clickOutsideEventName);
  },
  // أشياء جديدة
  actions: {
    closeCard() {
      this._close();
    }
  }
});

كل ما يفعله هو استدعاء الطريقة الأساسية _close() في كل مرة يتم فيها تشغيل الإجراء المخصص closeCard.

بعد ذلك نحتاج إلى إضافة زر إلى قالب بطاقة المستخدم أو شيء مثل هذا

{{d-button
  class="btn-flat"
  action=(action "closeCard")
  icon="times"
}}

تبدو نتائجي التقريبية هكذا

وبالطبع يمكنك فعل شيء مماثل لبطاقات المجموعات كما ذكرت أعلاه.

سأترك بطاقة المجموعة والتنفيذ للجوال كتمرين لك لأنك ستستخدم فعليًا نفس المفاهيم التي ناقشناها أعلاه إذا كنت تريد إجراء تغييرات على تلك ولكن يرجى إعلامي إذا واجهت أي مشاكل.

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

أمر لم يكن واضحًا على الفور أعلاه هو تغليف تغييرات JavaScript الخاصة بالمظهر في وسوم script ووضعها في ملف common/head_tag.html الخاص بالإضافة:

<script type="text/discourse-plugin" version="0.2">
</script>

فقط بدافع الفضول، هل رقم الإصدار في الوسم مهم هنا؟ وهل من الأفضل دائمًا وضع هذه الملفات في head_tag.html بدلاً من header.html أم أن الأمر لا يهم حقًا؟

شكرًا لك!

@Johani يمكنني إنشاء موضوع آخر لهذا إذا فضلت ذلك، لكنني أحاول تتبع وحدة التحكم user-card على جانب العميل والتي:

  • تقوم بتحميل بطاقة المستخدم عند النقر الأول
  • وتعيد التوجيه إلى ملخص المستخدم عند النقر الثاني

هدفي هو إزالة وظيفة النقر الثاني. شكرًا لك!