При попытке отобразить компонент 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:
Произошла ошибка:
- Во время рендеринга:
- корневой уровень
application
(неизвестный компонент только с шаблоном)
DiscourseRoot
ModalContainer
FeedbackFormModal
DModal
conditional-in-element:ConditionalInElement
(неизвестный компонент только с шаблоном) index.js:3970:18
Произошла ошибка: index.js:3377:16
Uncaught (in promise) Error: Попытка использовать значение как хелпер, но оно не является объектом или функцией. Определения хелперов должны быть объектами или функциями с ассоциированным менеджером хелперов. Значение было: 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
# ... константы ...
end
require_relative "lib/my_plugin/engine"
after_initialize do
# Загрузка зависимостей (с использованием require_dependency и 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__)
# ... другие зависимости ...
# Добавление методов к User (с использованием class_eval, так как prepend не сработал)
::User.class_eval do
# Определение вспомогательных методов, таких как my_custom_stat и т.д.
def my_custom_stat; # ... реализация ...; end
public :my_custom_stat
# ... другие методы ...
end
# Предварительное добавление расширений Guardian (если есть)
# ::Guardian.prepend(MyPlugin::GuardianExtensions)
# Изменения сериализаторов
reloadable_patch do |plugin|
# Добавление атрибутов к сериализаторам, например:
add_to_serializer(:post, :some_flag_for_button) do
# Логика определения, должна ли отображаться кнопка
true # Пример
end
# ... другие добавления к сериализаторам ...
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"; // Компонент кнопки
// ... импорт других компонентов для других слотов ...
export default apiInitializer("1.13.0", (api) => {
// Использование трансформера значений для кнопки в меню постов
api.registerValueTransformer("post-menu-buttons", ({ value: dag, context }) => {
const { post } = context;
// Логика определения, должен ли рендериться кнопка на основе post.some_flag_for_button
const shouldRenderButton = post?.some_flag_for_button; // Пример флага
if (shouldRenderButton) {
dag.add("leaveMyPluginFeedback", LeaveFeedbackButton, {
after: "like",
args: { post: post },
});
}
return dag;
});
// ... renderInOutlet для других элементов интерфейса ...
});
Компонент кнопки (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"; // Компонент модального окна
export default class LeaveFeedbackButton extends Component {
@service modal;
@service appEvents; // Используется для отображения ошибок
// Аргументы: post
get buttonLabel() { return "Action Button"; } // Закодировано
get buttonTitle() { return "Perform Action"; } // Закодировано
@action
openFeedbackModal() {
console.log("Открытие модального окна...");
try {
// Упрощенная модель для тестирования
const modelData = { post_id: this.args.post.id };
this.modal.show(FeedbackFormModal, { model: modelData });
} catch(e) {
console.error("Ошибка при показе модального окна", e);
this.appEvents.trigger("show:error", "Ошибка открытия модального окна.");
}
}
<template>
<DButton
class="btn-default my-plugin-btn"
@action={{this.openFeedbackModal}}
@icon="star" {{!-- Пример иконки --}}
@label={{this.buttonLabel}}
title={{this.buttonTitle}}
/>
</template>
}
Компонент модального окна (assets/javascripts/discourse/components/feedback-form-modal.gjs - Сверхупрощенный)
import Component from "@glimmer/component"; // Убедитесь, что это импортировано
// Удалены tracked, action, service и т.д., если не нужны для упрощенной версии
import DModal from "discourse/components/d-modal"; // Убедитесь, что это импортировано
import DButton from "discourse/components/d-button"; // Убедитесь, что это импортировано
import { on, preventDefault } from '@ember/modifier'; // Импортируйте встроенные, если используются
export default class FeedbackFormModal extends Component {
// Минимальный JS, необходимый для упрощенного шаблона
// Пример геттера, необходимого шаблону
get modalTitle() { return "My Modal Title"; } // Закодировано
get cancelLabel() { return "Cancel"; }
get submitLabel() { return "Submit"; }
// Фейковое действие, если нужно кнопке
@action submitFeedback() { console.log("Фейковая отправка"); }
{{!-- СВЕРХУПРОЩЕННЫЙ ШАБЛОН, КОТОРЫЙ ВСЕ ЕЩЕ ВЫЗЫВАЕТ ОШИБКУ --}}
<template>
<DModal @title={{this.modalTitle}} @closeModal={{@closeModal}} class="feedback-form-modal">
<:body>
<p>--- ТЕСТ МИНИМАЛЬНОГО МОДАЛЬНОГО ОКНА ---</p>
{{#if this.errorMessage}} {{!-- Использование встроенного '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"}}> {{!-- Использование встроенного '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?
Любые подсказки будут очень признательны!

