El componente GJS falla con error de "helper indefinido" cuando se muestra a través del servicio de modal (Discourse 3.5.0)

Estoy encontrando un error de renderizado persistente al intentar mostrar un componente Glimmer (.gjs) como modal usando this.modal.show(). El modal se activa desde otro componente GJS agregado al menú de publicaciones a través del transformador de valor post-menu-buttons. Estoy ejecutando Discourse v3.5.0.beta3-dev.

Estoy intentando agregar un botón al menú de publicaciones usando api.registerValueTransformer("post-menu-buttons", ...). Al hacer clic en este botón, se debería abrir un modal definido por un componente GJS separado (FeedbackFormModal) usando this.modal.show(FeedbackFormModal, ...).

Cuando se hace clic en el botón y se llama a this.modal.show(), la aplicación falla con el siguiente error, aparentemente durante el proceso de renderizado de FeedbackFormModal:

Error occurred:

- While rendering:
  -top-level
    application
      (unknown template-only component)
        DiscourseRoot
          ModalContainer
            FeedbackFormModal
              DModal
                conditional-in-element:ConditionalInElement
                  (unknown template-only component) index.js:3970:18

Error occurred: 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 // Line number might differ slightly
    _triggerAction d-button.gjs:138
    Ember 10

Esto ocurre incluso cuando la plantilla de FeedbackFormModal se reduce a su mínima expresión, conteniendo solo componentes centrales importados (<DModal>, <DButton>) y ayudantes integrados estándar (if, on, etc.).

Pego el código a continuación como referencia:

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 ...
});

Componente Botón (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>
}

Componente Modal (assets/javascripts/discourse/components/feedback-form-modal.gjs - Ultra-Simplificado)

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>
}

Dado que el error Attempted to use a value as a helper... undefined persiste incluso al renderizar una plantilla de componente GJS ultra-simplificada (que contiene solo componentes centrales importados como <DModal>/<DButton> y ayudantes integrados como if) a través de this.modal.show() activado desde un componente agregado a través de registerValueTransformer("post-menu-buttons", ...), ¿qué podría estar causando esto?

¿Existe algún problema o limitación conocida con la resolución del alcance de ayudantes/componentes en modales activados de esta manera en versiones recientes de Discourse (específicamente 3.5.0.beta3-dev)? ¿Existen patrones alternativos recomendados para mostrar formularios modales desde botones del menú de publicaciones en GJS?

¡Cualquier indicación sería muy apreciada!

1 me gusta

Probé tu código de prueba mínimo y me funciona:

Los únicos cambios que hice fueron añadir una importación de action que faltaba, eliminar un comentario fuera de la plantilla y forzar la visualización del botón.

¿Puedes crear una TC rápida en GitHub que active este error?

1 me gusta

Gracias por echar un vistazo. Después de tu confirmación, intenté configurar un TC de GitHub con un plugin nuevo y terminé aislando el problema yo mismo.

Ocurre al usar el ayudante if dentro de un argumento de componente (@icon={{if...}}) dentro de la plantilla modal. Eliminar este uso específico hace que el modal se renderice correctamente.

Esto es lo que estaba intentando hacer @icon={{if this.isSubmitting \"spinner\"}}

Por cierto, me encanta este nuevo tema Horizon.

1 me gusta

Hola,

Dando seguimiento a este problema: creé el plugin minimal-modal-test como se sugirió.

  • Éxito: Cuando el componente modal del plugin de prueba (minimal-modal.gjs) contenía solo elementos básicos (como <p>, <textarea>, el DModal principal, DButton, e incluso ayudantes integrados como #if para bloques condicionales o if usado para el icono del botón de envío), se renderizó correctamente sin errores al abrirse a través de modal.show() activado desde el componente de botón agregado a través del transformador de valor post-menu-buttons. Esto confirma que el servicio modal básico, la renderización de componentes, el transformador de valor y los ayudantes integrados parecen funcionar de forma aislada en mi entorno (Discourse v3.5.0.beta3-dev).

  • Fallo: El error (TypeError: userProvidedCallback is undefined / anteriormente Attempted to use a value as a helper... undefined) reaparece consistentemente en el momento en que agrego el componente hijo <StarRatingInput> de nuevo a la plantilla del modal (minimal-modal.gjs o feedback-form-modal.gjs).

Me he asegurado de que el componente StarRatingInput en sí mismo utilice las soluciones alternativas identificadas anteriormente (SVG en línea en lugar del ayudante dIcon, funciones de flecha para métodos como starClass para corregir el contexto de this, Array.from de JS estándar en lugar del ayudante range). El código interno de StarRatingInput parece correcto ahora.

Esto apunta a que el error es activado por renderizar el componente anidado <StarRatingInput> (que contiene un bucle #each, enlaces de clase dinámicos y manejadores de eventos) dentro del componente modal padre en este contexto de renderización específico.

Pasos para Reproducir

  1. Estás ejecutando la última versión del entorno de desarrollo de Discourse.

  2. Inicia sesión como cualquier usuario.

  3. Navega a cualquier tema y visualiza el primer post.

  4. Haz clic en el botón “Test Failing Modal” agregado al menú del post.

  5. Abre la consola de desarrollador del navegador.

Resultado Esperado

Debería aparecer un diálogo modal simple que contenga un campo de calificación por estrellas.

Resultado Actual

El modal falla al renderizarse por completo. La consola del navegador muestra un error.

1 me gusta

¡Gracias por el repositorio, fue de ayuda!
Encontré tu problema. No se trata del componente StartRatInput, sino de que importas eq de la ruta incorrecta.

Falla aquí:

checked={{eq this.testRating ratingValue}}

Y necesitas cambiar la importación:

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

import { eq } from "truth-helpers";

Resultado:

3 Me gusta

¡Muchas gracias por esto! La mitad del tiempo lo he perdido luchando con las importaciones. :roll_eyes:

1 me gusta

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