تغييرات قائمة المشاركات القادمة - كيف تستعد للثيمات والإضافات

كجزء من جهودنا المستمرة لتحسين قاعدة كود Discourse، نقوم بإزالة استخدامات نظام عرض “widget” القديم واستبدالها بمكونات Glimmer.

مؤخرًا، قمنا بتحديث قائمة المنشور، وهي الآن متاحة في Discourse خلف إعداد glimmer_post_menu_mode.

يقبل هذا الإعداد ثلاثة قيم محتملة:

  • disabled: استخدام نظام “widget” القديم
  • auto: سيكتشف توافق الإضافات والمظاهر الحالية لديك. إذا كانت أي منها غير متوافقة، فسيتم استخدام النظام القديم؛ وإلا سيتم استخدام القائمة الجديدة.
  • enabled: سيتم استخدام القائمة الجديدة. إذا كان لديك أي إضافة أو مظهر غير متوافق، فقد يتعطل موقعك.

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

سيتم طباعة تحذيرات في وحدة تحكم المتصفح لتحديد مصدر عدم التوافق.

:timer_clock: جدول زمني للتطبيق

هذه تقديرات تقريبية قابلة للتغيير

الربع الرابع 2024:

  • :white_check_mark: اكتمل التنفيذ الأساسي
  • :white_check_mark: تم تحديث الإضافات الرسمية
  • :white_check_mark: تم التمكين في Meta
  • :white_check_mark: تم تعيين glimmer_post_menu_mode افتراضيًا إلى auto؛ وتم تفعيل رسائل إبطال في وحدة التحكم
  • :white_check_mark: تم نشر نصائح الترقية

الربع الأول 2025:

  • :white_check_mark: يجب تحديث الإضافات والمظاهر التابعة لجهات خارجية
  • :white_check_mark: تبدأ رسائل الإبطال، مما يؤدي إلى ظهور شريط تحذير للمسؤول لأي مشكلات متبقية
  • :white_check_mark: تم تمكين قائمة المنشور الجديدة افتراضيًا

الربع الثاني 2025

  • :white_check_mark: 1 أبريل - إزالة إعداد ميزة العلم والكود القديم

:eyes: ماذا يعني هذا بالنسبة لي؟

إذا كانت إضافة أو مظهر الخاص بك يستخدم أي واجهات برمجة تطبيقات (APIs) لـ ‘widget’ لتخصيص قائمة المنشور، فسيحتاج ذلك إلى التحديث ليكون متوافقًا مع الإصدار الجديد.

:person_tipping_hand: كيف يمكنني تجربة قائمة المنشور الجديدة؟

في أحدث إصدار من Discourse، سيتم تمكين قائمة المنشور الجديدة إذا لم يكن لديك أي إضافة أو مظهر غير متوافق.

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

في الحالة غير المحتملة التي لا يعمل فيها هذا النظام التلقائي كما هو متوقع، يمكنك تجاوز هذا “معلمة الميزة التلقائية” مؤقتًا باستخدام الإعداد المذكور أعلاه. إذا كنت بحاجة إلى ذلك، يرجى إبلاغنا في هذا الموضوع.

:technologist: هل أحتاج إلى تحديث إضافتي ومظهري؟

ستحتاج إلى تحديث الإضافات أو المظاهر الخاصة بك إذا قامت بأي من التخصيصات التالية:

  • استخدام decorateWidget، changeWidgetSetting، reopenWidget أو attachWidgetAction على هذه الـ widgets:

    • post-menu
    • post-user-tip-shim
    • small-user-list
  • استخدام أي من طرق واجهة برمجة التطبيقات (API) التالية:

    • addPostMenuButton
    • removePostMenuButton
    • replacePostMenuButton

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

معرف الإبطال هو: discourse.post-menu-widget-overrides

:warning: إذا كنت تستخدم أكثر من مظهر في مثيلتك، فتأكد من التحقق من جميعها حيث سيتم طباعة التحذيرات فقط للإضافات النشطة والمظاهر والمكونات المظهرية المستخدمة حاليًا.

ما هي البدائل؟

لقد قدمنا محول القيمة post-menu-buttons كواجهة برمجة تطبيقات (API) جديدة لتخصيص قائمة المنشور.

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

تتوقع واجهات برمجة تطبيقات DAG استقبال مكونات Ember إذا كانت واجهة برمجة التطبيقات تحتاج إلى تعريف زر جديد مثل .add و .replace.

كل تخصيص مختلف، ولكن إليك بعض التوجيهات لأكثر حالات الاستخدام شيوعًا:

addPostMenuButton

قبل:

withPluginApi("1.34.0", (api) => {
  api.addPostMenuButton("solved", (attrs) => {
    if (attrs.can_accept_answer) {
      const isOp = currentUser?.id === attrs.topicCreatedById;
      return {
        action: "acceptAnswer",
        icon: "far-check-square",
        className: "unaccepted",
        title: "solved.accept_answer",
        label: isOp ? "solved.solution" : null,
        position: attrs.topic_accepted_answer ? "second-last-hidden" : "first",
      };
    }
  });
});

بعد:

تستخدم الأمثلة أدناه تنسيق قالب Ember Template Tag Format (gjs)

// components/solved-accept-answer-button.gjs
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d_button";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";

export default class SolvedAcceptAnswerButton extends Component {
  // يشير إلى ما إذا كان الزر سيظهر فورًا أو يكون مخفيًا خلف زر إظهار المزيد
  static hidden(args) { 
    return args.post.topic_accepted_answer;
  }

  ...

  <template>
    <DButton
      class="post-action-menu__solved-unaccepted unaccepted"
      ...attributes
      @action={{this.acceptAnswer}}
      @icon="far-check-square"
      @label={{if this.showLabel "solved.solution"}}
      @title="solved.accept_answer"
    />
  </template>
}

// initializer.js
import SolvedAcceptAnswerButton from "../components/solved-accept-answer-button";

...
withPluginApi("1.34.0", (api) => {
  api.registerValueTransformer(
    "post-menu-buttons",
    ({
      value: dag, 
      context: {
        post,
        firstButtonKey, // مفتاح الزر الأول
        secondLastHiddenButtonKey, // مفتاح الزر المخفي الثاني من النهاية
        lastHiddenButtonKey, // مفتاح الزر المخفي الأخير
      },
    }) => {
        dag.add(
          "solved",
          SolvedAcceptAnswerButton,
          post.topic_accepted_answer
            ? {
                before: lastHiddenButtonKey,
                after: secondLastHiddenButtonKey,
              }
            : {
                before: [
                  "assign", // زر تمت إضافته بواسطة إضافة assign
                  firstButtonKey,
                ],
              }
        );
    }
  );
});

:bulb: تنسيق أزرارك

يُوصى بتضمين ...attributes كما هو موضح في المثال أعلاه في مكونك.

عند دمجه مع استخدام مكونات DButton أو DMenu، سيعني ذلك الاهتمام بالفئات الأساسية (boilerplate classes) ويضمن أن يتبع الزر تنسيق الأزرار الأخرى في قائمة المنشور.

يمكن تحديد تنسيق إضافي باستخدام فئاتك المخصصة.

replacePostMenuButton

  • قبل:
withPluginApi("1.34.0", (api) => {
  api.replacePostMenuButton("like", {
    name: "discourse-reactions-actions",
    buildAttrs: (widget) => {
      return { post: widget.findAncestorModel() };
    },
    shouldRender: (widget) => {
      const post = widget.findAncestorModel();
      return post && !post.deleted_at;
    },
  });
});
  • بعد:
import ReactionsActionButton from "../components/discourse-reactions-actions-button";

...

withPluginApi("1.34.0", (api) => {
  api.registerValueTransformer(
    "post-menu-buttons",
    ({ value: dag, context: { buttonKeys } }) => {
      // ReactionsActionButton هو مكون الزر الجديد
      dag.replace(buttonKeys.LIKE, ReactionsActionButton);
    }
  );
});

removePostMenuButton

  • قبل:
withPluginApi("1.34.0", (api) => {
  api.removePostMenuButton('like', (attrs, state, siteSettings, settings, currentUser) => {
    if (attrs.post_number === 1) {
      return true;
    }
  });
});
  • بعد:
withPluginApi("1.34.0", (api) => {
  api.registerValueTransformer(
    "post-menu-buttons",
    ({ value: dag, context: { post, buttonKeys } }) => {
      if (post.post_number === 1) {
        dag.delete(buttonKeys.LIKE);
      }
    }
  );
});

:sos: ماذا عن التخصيصات الأخرى؟

إذا لم يكن بإمكانك تحقيق تخصيصك باستخدام واجهة برمجة التطبيقات (API) الجديدة التي قدمناها، فيرجى إبلاغنا بإنشاء موضوع تطوير جديد لمناقشته.

:sparkles: أنا مؤلف إضافة/مظهر. كيف يمكنني تحديث مظهر/إضافة لدعم كل من القائمة القديمة والجديدة للمنشورات أثناء فترة الانتقال؟

لقد استخدمنا النمط أدناه لدعم كل من الإصدار القديم والجديد من قائمة المنشور في إضافاتنا:

function customizePostMenu(api) {
  const transformerRegistered = api.registerValueTransformer(
    "post-menu-buttons",
    ({ value: dag, context }) => {
      // تخصيصات قائمة المنشور الجديدة
      ...
    }
  );

  const silencedKey =
    transformerRegistered && "discourse.post-menu-widget-overrides";

  withSilencedDeprecations(silencedKey, () => customizeWidgetPostMenu(api));
}

function customizeWidgetPostMenu(api) {
  // تخصيصات كود "widget" القديم هنا
  ...
}

export default {
  name: "my-plugin",

  initialize(container) {
    withPluginApi("1.34.0", customizePostMenu);
  }
};

:star: المزيد من الأمثلة

يمكنك التحقق من إضافاتنا الرسمية للحصول على أمثلة حول كيفية استخدام واجهة برمجة التطبيقات (API) الجديدة:

لقد قمنا بتمكين قائمة Glimmer Post افتراضيًا.

بمجرد ترقية مثيل Discourse الخاص بك، سيؤدي هذا إلى عدم تطبيق التخصيصات الحالية التي لم يتم تحديثها إلى واجهة برمجة التطبيقات الجديدة.

حتى ذلك الحين، لا يزال بإمكان المسؤولين تغيير الإعداد مرة أخرى إلى “معطل” أثناء تحديث التخصيصات المتبقية.

ستتم إزالة التعليمات البرمجية القديمة في بداية الربع الثاني.

أقدر المرونة في توفر واجهة برمجة تطبيقات المكونات الكاملة. تعجبني بنية مكونات Glimmer بشكل عام، وأرى لماذا قد يكون لها فوائد في تقليل التعقيد داخل قاعدة التعليمات البرمجية.

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

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

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

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

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

شيء آخر من شأنه أن يساعد في تطوير المكونات الإضافية هو رؤية سمة على أي عنصر DOM جذري يتم عرضه بواسطة مكون/أداة مصغرة تخبرك ما هو المكون/الأداة المصغرة التي تنظر إليها. مثل “data-widget=foo” يمكن أن تكون هذه ميزة تصحيح أخطاء، أو يمكن تمكينها افتراضيًا. إنها مفتوحة المصدر، لذا لا يبدو أنك تحقق الأمان من خلال الغموض.

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

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

سأكون سعيدًا بتقديم طلب سحب للتكرار عبر سجل الأدوات المصغرة، إذا كان ذلك مقبولًا.

أيضًا، إذا أردت تنفيذ الوظيفة الجديدة فقط، إلى أي إصدار من واجهة برمجة تطبيقات Discourse يجب أن أُثبت توافق الإضافة الخاصة بي؟ أنت تستخدم withPluginApi("1.34.0", ...) في جميع أمثلتك. أعتقد أن هذا إصدار أقدم وغير ممثل لتوقيت حدوث هذا التغيير؟ يرجى التوضيح. شكرًا لك!

إنها النسخة الصحيحة. يمكنك الاطلاع على سجل التغييرات هنا:

https://github.com/discourse/discourse/blob/main/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md?plain=1#L64-L67

أيضًا، يمكن أن تساعد هذه الميزة: Pinning plugin and theme versions for older Discourse installs (.discourse-compatibility)

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

هل جربت Ember Inspector? أعتقد أنه يجب أن يحل المشكلة التي تصفها، وسيعرض حتى مكونات Ember التي لا تعرض حاليًا أي عناصر DOM.

منذ وقت قريب جدًا، لم يعد هذا الرقم الإصدار مطلوبًا. يمكنك القيام بذلك

export default apiInitializer((api) => {

أو

withPluginApi((api) => {

سنقوم بتحديث المستندات بهذا التغيير قريبًا. التفضيل الحديث لإدارة التوافق بين الإصدارات هو ملف .discourse-compatibility الذي ذكره @Arkshine:

هذا لطيف حقًا! في كل مرة أنسى فيها تحديث هذا الرقم :rofl:.

@david @Arkshine رائع، مشاركات رائعة ومفيدة جداً!

سؤال آخر - بخصوص “السياق” الذي تتم مشاركته عند استدعاء api.registerValueTransformer، كيف يمكنني معرفة السياق الذي سيتم تمريره لي؟ أفترض أنه يمكنني فقط تسجيل السياق في وحدة التحكم (console.log)، ولكن سيكون من الجيد معرفة ما هو متاح مسبقاً.

بالنسبة للمكون الإضافي الذي أكتبه الآن، أمنح امتيازات إشراف خاصة لمؤلف الموضوع. للقيام بذلك، أحتاج إلى معرفة “مؤلف الموضوع الحالي” و “المستخدم الحالي” و “عضوية المجموعة للمستخدم المسجل دخوله”.

ربما يساعد هذا المثال المحدد في توفير سياق لسؤالي.

تعديل:
يبدو الكود النهائي الخاص بي كالتالي، لأي شخص لديه اهتمامات مماثلة:

const currentUser = api.getCurrentUser()
const validGroups = currentUser.groups.filter(g => ['admins', 'staff', 'moderators'].includes(g.name))

// إذا كان المستخدم الحالي هو مؤلف المنشور، أو كان في مجموعة مميزة
if (post?.topic?.details?.created_by?.id === currentUser.id || validGroups.length > 0) {
  // امنحه الوصول إلى الميزة
  ...
}

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

يمكن العثور على وثائق عامة حول المحولات هنا: Using Transformers to customize client-side values and behavior

البحث عن “applyTransformer” يُرجع 0 نتائج. هل أبحث في المكان الخطأ؟

أجد أن البحث عن “api.registerValueTransformer” يُرجع بعض الأمثلة المفيدة. ولكن بالطبع الأمثلة لا توفر توثيقًا شاملاً للسياق الذي يتم إرجاعه - فهي تُظهر فقط تلك التي كانت مفيدة لهذا المثال المحدد.

من console.log لـ context في مثالي المحدد، أرى أن post يتم إرجاعه ولكن user لا يتم إرجاعه. فكيف يمكنني الوصول إلى حالة التطبيق الأخرى التي لا توجد ضمن السياق؟

أتفهم أنه في السابق ربما كان يمكن استدعاء helper.getModel() أو helper.currentUser ضمن سياق api.decorateWidget. أفترض أن هناك طريقة حالية للحصول على نتائج مماثلة.

شكرًا على كل المساعدة هنا.

أوه، أعتقد أنني أجبت على سؤالي بنفسي. هذا المثال يوضح استخدام api.getCurrentUser(). لذا، بشكل أساسي، لم يتغير هذا الجزء من واجهة برمجة التطبيقات، ولا يزال متوافقًا مع نموذج الـ glimmer.

أعتقد أنه كان يقصد applyValueTransformer أو applyBehaviorTransformer. يمكنك العثور على مثل هذه الدوال في الملف التالي: discourse/app/assets/javascripts/discourse/app/lib/transformer.js at main · discourse/discourse · GitHub

تمت إزالة رمز قائمة المشاركات القديمة الآن. شكرًا لكل من عمل على تحديث السمات والإضافات الخاصة بهم :rocket: