Componente GJS falha com erro "helper indefinido" ao ser exibido via serviço de modal (Discourse 3.5.0

Estou encontrando um erro persistente de renderização ao tentar exibir um componente Glimmer (.gjs) como um modal usando this.modal.show(). O modal é acionado a partir de outro componente GJS adicionado ao menu de postagem via transformador de valor post-menu-buttons. Estou executando o Discourse v3.5.0.beta3-dev.

Estou tentando adicionar um botão ao menu de postagem usando api.registerValueTransformer("post-menu-buttons", ...). Clicar neste botão deve abrir um modal definido por um componente GJS separado (FeedbackFormModal) usando this.modal.show(FeedbackFormModal, ...).

Quando o botão é clicado e this.modal.show() é chamado, a aplicação trava com o seguinte erro, aparentemente durante o processo de renderização do 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

Isso acontece mesmo quando o template FeedbackFormModal é reduzido ao seu mínimo absoluto, contendo apenas componentes principais importados (<DModal>, <DButton>) e helpers integrados padrão (if, on, etc.).

Colando o código abaixo para referência:

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

Botão Componente (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 o erro Attempted to use a value as a helper... undefined persiste mesmo ao renderizar um template de componente GJS ultra-simplificado (contendo apenas componentes principais importados como <DModal>/<DButton> e helpers integrados como if) via this.modal.show() acionado de um componente adicionado via registerValueTransformer("post-menu-buttons", ...), o que poderia estar causando isso?

Existe algum problema conhecido ou limitação com a resolução de escopo de helpers/componentes em modais acionados dessa forma em versões recentes do Discourse (especificamente 3.5.0.beta3-dev)? Existem padrões alternativos recomendados para mostrar formulários modais a partir de botões de menu de postagem em GJS?

Qualquer dica seria muito apreciada!

1 curtida

Eu testei seu código mínimo e ele funciona para mim:

As únicas alterações que fiz foram adicionar uma importação de action ausente, remover um comentário fora do template e forçar a exibição do botão.

Você pode criar um TC rápido no GitHub que acione esse erro?

1 curtida

Obrigado por dar uma olhada. Após sua confirmação, tentei configurar um TC no GitHub com um plugin novo e acabei isolando o problema sozinho.

Ele ocorre ao usar o helper if dentro de um argumento de componente (@icon={{if...}}) no template modal. Remover esse uso específico faz com que o modal seja renderizado corretamente.

É isso que eu estava tentando fazer @icon={{if this.isSubmitting \"spinner\"}}

Aliás, adoro este novo tema Horizon.

1 curtida

Olá,

Dando seguimento a este problema: Criei o plugin minimal-modal-test conforme sugerido.

  • Sucesso: Quando o componente modal do plugin de teste (minimal-modal.gjs) continha apenas elementos básicos (como <p>, <textarea>, o DModal principal, DButton, e até mesmo helpers integrados como #if para blocos condicionais ou if usado para o ícone do botão de envio), ele foi renderizado corretamente sem erros quando aberto via modal.show() acionado pelo componente de botão adicionado através do transformador de valor post-menu-buttons. Isso confirma que o serviço modal básico, a renderização de componentes, o transformador de valor e os helpers integrados parecem estar funcionando isoladamente em meu ambiente (Discourse v3.5.0.beta3-dev).

  • Falha: O erro (TypeError: userProvidedCallback is undefined / anteriormente Attempted to use a value as a helper... undefined) reaparece consistentemente no momento em que adiciono o componente filho <StarRatingInput> de volta ao template do modal (minimal-modal.gjs ou feedback-form-modal.gjs).

Certifiquei-me de que o próprio componente StarRatingInput usa workarounds identificados anteriormente (SVG inline em vez do helper dIcon, arrow functions para métodos como starClass para corrigir o contexto this, Array.from padrão do JS em vez do helper range). O código interno do StarRatingInput parece correto agora.

Isso aponta que o erro está sendo acionado pela renderização do componente aninhado <StarRatingInput> (que contém um loop #each, bindings de classe dinâmicos e manipuladores de eventos) dentro do componente modal pai neste contexto de renderização específico.

Passos para Reproduzir

  1. Você está executando a versão mais recente do ambiente de desenvolvimento do Discourse.

  2. Faça login como qualquer usuário.

  3. Navegue até qualquer tópico e visualize o primeiro post.

  4. Clique no botão “Test Failing Modal” adicionado ao menu do post.

  5. Abra o console do desenvolvedor do navegador.

Resultado Esperado

Um diálogo modal simples deve aparecer contendo um input de classificação por estrelas.

Resultado Atual

O modal falha ao renderizar completamente. O console do navegador mostra um erro.

1 curtida

Obrigado pelo repositório, ajudou!
Encontrei seu problema. Não é sobre o componente StartRatInput, é na verdade porque você importa eq do caminho errado.

Falha aqui:

checked={{eq this.testRating ratingValue}}

E você precisa mudar a importação:

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

import { eq } from "truth-helpers";

Resultado:

3 curtidas

Muito obrigado por isso! Metade do tempo que perdi lutando com importações. :roll_eyes:

1 curtida

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