مكون GJS يفشل مع خطأ "helper غير معرف" عند عرضه عبر خدمة النموذج (Discourse 3.5.0)

أواجه خطأ عرض مستمرًا عند محاولة عرض مكون Glimmer (.gjs) كنافذة منبثقة باستخدام this.modal.show(). يتم تشغيل النافذة المنبثقة من مكون GJS آخر تمت إضافته إلى قائمة المشاركات عبر محول القيمة post-menu-buttons. أقوم بتشغيل Discourse v3.5.0.beta3-dev

أحاول إضافة زر إلى قائمة المشاركات باستخدام api.registerValueTransformer("post-menu-buttons", ...). يجب أن يؤدي النقر على هذا الزر إلى فتح نافذة منبثقة يحددها مكون GJS منفصل (FeedbackFormModal) باستخدام this.modal.show(FeedbackFormModal, ...).

عند النقر على الزر واستدعاء this.modal.show(), يتعطل التطبيق مع الخطأ التالي، ويبدو أنه يحدث أثناء عملية عرض FeedbackFormModal

حدث خطأ:

- أثناء العرض:
  - المستوى الأعلى
    التطبيق
      (مكون قالب فقط غير معروف)
        DiscourseRoot
          ModalContainer
            FeedbackFormModal
              DModal
                conditional-in-element:ConditionalInElement
                  (مكون قالب فقط غير معروف) index.js:3970:18

حدث خطأ: index.js:3377:16

Uncaught (in promise) Error: Attempted to use a value as a helper, but it was not an object or function. Helper definitions must be objects or functions with an associated helper manager. The value was: undefined
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:89256
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:90066
    Ember 4
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:90164
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:89224
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:90163
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:90284
    Ember 2
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:92117
    Ember 12
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:94577
    source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:96288
    Ember 34
    show modal.js:73
    openFeedbackModal leave-feedback-button.js:102 // قد يختلف رقم السطر قليلاً
    _triggerAction d-button.gjs:138
    Ember 10

يحدث هذا حتى عندما يتم تجريد قالب FeedbackFormModal إلى الحد الأدنى المطلق، ويحتوي فقط على المكونات الأساسية المستوردة (<DModal>, <DButton>) والمساعدات المضمنة القياسية (if, on, إلخ).

لصق الكود أدناه كمرجع:

plugin.rb

# frozen_string_literal: true
# name: my-plugin

module ::MyPlugin
  # ... constants ...
end

require_relative "lib/my_plugin/engine"

after_initialize do
  # Load Dependencies (using require_dependency and File.expand_path)
  require_dependency File.expand_path("app/controllers/my_plugin/my_controller.rb", __dir__)
  require_dependency File.expand_path("app/models/my_plugin/my_model.rb", __dir__)
  # ... other dependencies ...

  # Add methods to User (using class_eval as prepend failed)
  ::User.class_eval do
    # Define helper methods like my_custom_stat, etc.
    def my_custom_stat; # ... implementation ...; end
    public :my_custom_stat
    # ... other methods ...
  end

  # Prepend Guardian Extensions (if any)
  # ::Guardian.prepend(MyPlugin::GuardianExtensions)

  # Serializer modifications
  reloadable_patch do |plugin|
    # Add attributes to serializers, e.g.:
    add_to_serializer(:post, :some_flag_for_button) do
        # Logic to determine if button should show
        true # Example
    end
    # ... other serializer additions ...
  end
end

assets/javascripts/discourse/initializers/my-plugin-outlets.js

import { apiInitializer } from "discourse/lib/api";
import { hbs } from "ember-cli-htmlbars";
import LeaveFeedbackButton from "../components/leave-feedback-button"; // Button component
// ... import other components for other outlets ...

export default apiInitializer("1.13.0", (api) => {
  // Use Value Transformer for Post Menu Button
  api.registerValueTransformer("post-menu-buttons", ({ value: dag, context }) => {
    const { post } = context;
    // Logic to determine if button should render based on post.some_flag_for_button
    const shouldRenderButton = post?.some_flag_for_button; // Example flag

    if (shouldRenderButton) {
      dag.add("leaveMyPluginFeedback", LeaveFeedbackButton, {
        after: "like",
        args: { post: post },
      });
    }
    return dag;
  });

  // ... renderInOutlet for other UI elements ...
});

Button Component (assets/javascripts/discourse/components/leave-feedback-button.gjs)

import Component from "@glimmer/component";
import { action } from "@ember/object";
import { service } from "@ember/service";
import DButton from "discourse/components/d-button";
import FeedbackFormModal from "./feedback-form-modal"; // The modal component

export default class LeaveFeedbackButton extends Component {
  @service modal;
  @service appEvents; // Used for error display

  // Args: post

  get buttonLabel() { return "Action Button"; } // Hardcoded
  get buttonTitle() { return "Perform Action"; } // Hardcoded

  @action
  openFeedbackModal() {
    console.log("Opening Modal...");
    try {
       // Simplified model for testing
       const modelData = { post_id: this.args.post.id };
       this.modal.show(FeedbackFormModal, { model: modelData });
    } catch(e) {
        console.error("Error showing modal", e);
        this.appEvents.trigger("show:error", "Error opening modal.");
    }
  }

  <template>
    <DButton
      class="btn-default my-plugin-btn"
      @action={{this.openFeedbackModal}}
      @icon="star" {{!-- Example icon --}}
      @label={{this.buttonLabel}}
      title={{this.buttonTitle}}
    />
  </template>
}

Modal Component (assets/javascripts/discourse/components/feedback-form-modal.gjs - Ultra-Simplified)

import Component from "@glimmer/component"; // Ensure this is imported
// Removed tracked, action, service etc. if not needed by simplified version
import DModal from "discourse/components/d-modal"; // Ensure this is imported
import DButton from "discourse/components/d-button"; // Ensure this is imported
import { on, preventDefault } from '@ember/modifier'; // Import built-ins if used

export default class FeedbackFormModal extends Component {
  // Minimal JS needed for simplified template

  // Example getter needed by template
  get modalTitle() { return "My Modal Title"; } // Hardcoded
  get cancelLabel() { return "Cancel"; }
  get submitLabel() { return "Submit"; }

  // Dummy action if needed by button
  @action submitFeedback() { console.log("Dummy submit"); }

  {{!-- ULTRA-SIMPLIFIED TEMPLATE THAT STILL CAUSES ERROR --}}
  <template>
    <DModal @title={{this.modalTitle}} @closeModal={{@closeModal}} class="feedback-form-modal">
      <:body>
          <p>--- MINIMAL MODAL TEST ---</p>
          {{#if this.errorMessage}} {{!-- Using built-in 'if' --}}
              <div class="alert alert-error" role="alert">{{this.errorMessage}}</div>
          {{/if}}
      </:body>
      <:footer>
          <DButton @action={{@closeModal}} class="btn-flat"> {{this.cancelLabel}} </DButton>
          <DButton @action={{this.submitFeedback}} class="btn-primary" @icon={{if this.isSubmitting "spinner"}}> {{!-- Using built-in 'if' --}}
              {{this.submitLabel}}
          </DButton>
      </:footer>
    </DModal>
  </template>
}

بالنظر إلى أن الخطأ Attempted to use a value as a helper... undefined لا يزال مستمرًا حتى عند عرض قالب مكون GJS مبسط للغاية (يحتوي فقط على مكونات أساسية مستوردة مثل <DModal>/<DButton> والمساعدات المضمنة مثل if) عبر this.modal.show() الذي تم تشغيله من مكون تمت إضافته عبر registerValueTransformer("post-menu-buttons", ...), فما الذي يمكن أن يسبب هذا؟

هل هناك مشكلة معروفة أو قيد مع حل نطاق المساعد/المكون في النوافذ المنبثقة التي يتم تشغيلها بهذه الطريقة في إصدارات Discourse الحديثة (خاصة 3.5.0.beta3-dev)؟ هل هناك أنماط بديلة موصى بها لعرض نماذج النوافذ المنبثقة من أزرار قائمة المشاركات في GJS؟

أي إرشادات ستكون محل تقدير كبير!

إعجاب واحد (1)

لقد جربت رمز الاختبار المصغر الخاص بك وهو يعمل لدي:

التغييرات الوحيدة التي أجريتها هي إضافة استيراد action مفقود، وإزالة تعليق خارج القالب، وإجبار الزر على الظهور.

هل يمكنك إنشاء اختبار سريع على GitHub يؤدي إلى هذا الخطأ؟

إعجاب واحد (1)

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

يحدث هذا عند استخدام المساعد if ضمن وسيط للمكون (@icon={{if...}}) داخل قالب النافذة المنبثقة. إزالة هذا الاستخدام المحدد يجعل النافذة المنبثقة تُعرض بشكل صحيح.

هذا ما كنت أحاول فعله @icon={{if this.isSubmitting \"spinner\"}}

بالمناسبة، أنا أحب سمة Horizon الجديدة هذه.

إعجاب واحد (1)

مرحباً،

متابعة لهذه المشكلة: لقد أنشأت المكون الإضافي minimal-modal-test كما هو مقترح.

  • نجاح: عندما احتوى مكون النافذة المنبثقة للمكون الإضافي للاختبار (minimal-modal.gjs) على عناصر أساسية فقط (مثل <p>, <textarea>, DModal الأساسي، DButton، وحتى المساعدات المضمنة مثل #if للكتل الشرطية أو if المستخدمة لأيقونة زر الإرسال)، فقد تم عرضه بشكل صحيح دون أخطاء عند فتحه عبر modal.show() الذي تم تشغيله من مكون الزر المضاف عبر محوّل القيمة post-menu-buttons. يؤكد هذا أن خدمة النافذة المنبثقة الأساسية، وعرض المكون، ومحوّل القيمة، والمساعدات المضمنة تبدو وظيفية بشكل منفصل في بيئتي (Discourse v3.5.0.beta3-dev).

  • فشل: الخطأ (TypeError: userProvidedCallback is undefined / سابقاً Attempted to use a value as a helper... undefined) يعاود الظهور باستمرار بمجرد إضافة المكون الفرعي <StarRatingInput> مرة أخرى إلى قالب النافذة المنبثقة (minimal-modal.gjs أو feedback-form-modal.gjs).

لقد تأكدت من أن مكون StarRatingInput نفسه يستخدم الحلول البديلة التي تم تحديدها سابقاً (SVG مضمن بدلاً من مساعد dIcon، دوال سهمية للطرق مثل starClass لإصلاح سياق this، Array.from القياسي في جافاسكريبت بدلاً من مساعد range). يبدو أن الكود الداخلي لـ StarRatingInput صحيح الآن.

يشير هذا إلى أن الخطأ يتم تشغيله بواسطة عرض المكون المتداخل <StarRatingInput> (الذي يحتوي على حلقة #each، وربط الفئات الديناميكية، ومعالجات الأحداث) داخل مكون النافذة المنبثقة الأصل في سياق العرض المحدد هذا.

خطوات لإعادة الإنتاج

  1. أنت تقوم بتشغيل أحدث بيئة تطوير Discourse.

  2. قم بتسجيل الدخول كمستخدم.

  3. انتقل إلى أي موضوع وشاهد المنشور الأول.

  4. انقر فوق الزر “Test Failing Modal” المضاف إلى قائمة المنشورات.

  5. افتح وحدة تحكم مطوري المتصفح.

النتيجة المتوقعة

يجب أن يظهر مربع حوار بسيط يحتوي على مدخل لتقييم النجوم.

النتيجة الفعلية

فشل عرض النافذة المنبثقة بالكامل. تعرض وحدة تحكم المتصفح خطأ.

إعجاب واحد (1)

شكرًا على المستودع، لقد ساعد!
لقد وجدت مشكلتك. لا يتعلق الأمر بمكون StartRatInput، بل هو في الواقع لأنك تستورد eq من المسار الخاطئ.

يفشل هنا:

checked={{eq this.testRating ratingValue}}

وتحتاج إلى تغيير الاستيراد:

import { eq } from "@ember/helper";

import { eq } from "truth-helpers";

النتيجة:

3 إعجابات

شكرا جزيلا على هذا! نصف الوقت الذي أهدرته في النضال مع الاستيراد. :roll_eyes:

إعجاب واحد (1)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.