مرحباً بالجميع،
أقوم بتطوير إضافة يانصيب لـ Discourse حيث يمكن للمستخدمين المشاركة عن طريق الرد على موضوع يانصيب. إفصاح كامل: أنا لست مبرمجًا - أقوم ببناء هذا بالكامل بمساعدة الذكاء الاصطناعي (Claude Code)، لذلك قد أفتقد المفاهيم الأساسية. الوظائف الأساسية تعمل، لكنني أواجه مشاكل في التحديثات في الوقت الفعلي باستخدام MessageBus. أقدر أي توجيه حول النهج الصحيح.
ما أحاول تحقيقه
- إنشاء منشور يانصيب باستخدام BBCode
[lottery]...[/lottery] - عرض بطاقة يانصيب في المنشور توضح عدد المشاركين
- عندما يرد المستخدمون للمشاركة، يجب أن يرى جميع المشاهدين عدد المشاركين يتحدث في الوقت الفعلي دون تحديث الصفحة
- على غرار كيفية قيام إضافة discourse-calendar بتحديث معلومات الحدث
التنفيذ الحالي
الواجهة الخلفية (Ruby)
نموذج Lottery (app/models/lottery.rb):
def publish_update!
channel = "/lottery/#{self.post.topic_id}"
message = { id: self.id }
MessageBus.publish(channel, message)
end
نموذج LotteryParticipant (app/models/lottery_participant.rb):
after_commit :publish_lottery_update, on: [:create, :destroy]
private
def publish_lottery_update
self.lottery.publish_update!
end
تؤكد سجلات الواجهة الخلفية أنه تم استدعاء MessageBus.publish بنجاح:
[Lottery] 📡 Publishing MessageBus update:
Channel: /lottery/27
Message: {:id=>52}
[Lottery] ✅ MessageBus.publish completed
الواجهة الأمامية (JavaScript)
المزين (discourse-lottery-decorator.gjs):
api.decorateCookedElement((cooked, helper) => {
const post = helper.getModel();
if (!post?.lottery_data) return;
// Check if already decorated (AI suggested using data attribute)
if (cooked.dataset.lotteryDecorated === "true") return;
const lotteryNode = cooked.querySelector(".discourse-lottery");
if (!lotteryNode) return;
const wrapper = document.createElement("div");
lotteryNode.before(wrapper);
const lottery = Lottery.create(post.lottery_data);
helper.renderGlimmer(
wrapper,
`<template><DiscourseLottery @lotteryData={{lottery}} /></template>`
);
lotteryNode.remove();
cooked.dataset.lotteryDecorated = "true";
}, { id: "discourse-lottery" });
المكون (discourse-lottery/index.gjs):
export default class DiscourseLottery extends Component {
@service messageBus;
@service lotteryApi;
constructor() {
super(...arguments);
const { lotteryData } = this.args;
if (lotteryData?.topicId) {
this.lotteryPath = `/lottery/${lotteryData.topicId}`;
this.lotteryData = lotteryData;
// AI suggested manually binding the callback
this._boundOnLotteryUpdate = this._onLotteryUpdate.bind(this);
this.messageBus.subscribe(this.lotteryPath, this._boundOnLotteryUpdate);
registerDestructor(this, () => {
this.messageBus.unsubscribe(this.lotteryPath, this._boundOnLotteryUpdate);
});
}
}
async _onLotteryUpdate(msg) {
console.log('[Lottery Component] 🔔 MessageBus message received!');
const updatedData = await this.lotteryApi.lottery(this.lotteryData.topicId);
this.lotteryData.updateFromLottery(updatedData);
}
}
النموذج (models/lottery.js):
export default class Lottery {
@tracked _stats;
set stats(stats) {
this._stats = LotteryStats.create(stats || {});
}
updateFromLottery(lottery) {
// Update all properties including stats
this.stats = lottery.stats || {};
// ... other properties
}
}
المشكلة
صفحة المشارك فقط هي التي تتحدث في الوقت الفعلي، لكن صفحات المشاهدين الآخرين لا تتحدث دون تحديث.
ما يعمل:
عندما يرد مستخدم للمشاركة، تظهر صفحته الخاصة تحديث عدد المشاركين في الوقت الفعلي
الواجهة الخلفية تنشر رسائل MessageBus بنجاح
يتم استلام رسالة MessageBus في صفحة المشارك (تظهر السجلات “
MessageBus message received!”)
يتم تحديث النموذج بعدد المشاركين الجديد (تظهر السجلات زيادة العدد من 0 إلى 1)
ما لا يعمل:
لا يرى المشاهدون الآخرون الذين يتابعون موضوع اليانصيب العدد يتحدث في الوقت الفعلي
صفحة منشئ اليانصيب لا تتحدث
أي صفحة مستخدم أخرى لديها موضوع اليانصيب مفتوح لا تتحدث- يحتاجون جميعًا إلى تحديث الصفحة يدويًا لرؤية عدد المشاركين المحدث
بالإضافة إلى ذلك، أرى المكون يتم تدميره وإعادة إنشائه عدة مرات في وحدة التحكم:
[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27
[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27
هذا يشير إلى أن decorateCookedElement يتم تشغيله عدة مرات، مما يتسبب في تدمير المكونات وإعادة إنشائها، وهذا قد يكسر اشتراك MessageBus للمشاهدين الآخرين.
أسئلة
-
هل استخدام سمة
data-decoratedعلى العنصر المطبوخ هو الطريقة الصحيحة لمنع التزيين المزدوج؟ اقترح الذكاء الاصطناعي هذا النهج، لكنني لم أجد وثائق رسمية تؤكد أنه النمط الموصى به. -
لماذا لا يتحدث واجهة المستخدم حتى على الرغم من تغير خاصية النموذج المتعقبة؟ يجب أن يؤدي
@tracked _statsإلى إعادة العرض عند تحديثه عبرthis.stats = lottery.stats، لكن عدد المشاركين المعروض يظل كما هو. هل هناك خطأ في كيفية استخدام@tracked؟ -
هل يجب أن أستخدم نهجًا مختلفًا تمامًا؟ طلبت من الذكاء الاصطناعي اتباع نمط discourse-calendar، ولكن ربما أفتقد شيئًا أساسيًا حول كيفية تعامل Discourse مع التحديثات في الوقت الفعلي.
-
هل إزالة
lotteryNodeالأصلي يسبب المشكلة؟ حاول الذكاء الاصطناعي في البداية عدم إزالته، لكن ذلك تسبب في إخفاء بطاقة اليانصيب بواسطة CSS (.cooked > .discourse-lottery { display: none; }). هل هناك نمط أفضل؟
نظرًا لأنني لست مبرمجًا، فقد أطرح أسئلة أساسية - أي إشارات إلى وثائق أو أمثلة ستكون مفيدة للغاية! شكرا لك!