Не удается скомпилировать JS в моем компоненте: SyntaxError: Private field must be used in an enclosing class

В конце концов мне нужна кнопка «Новая тема» в теле списка тем для центральной темы Discourse. Все инженеры жалуются, что им не нравится, когда кнопка «Начать новое обсуждение» находится просто внизу списка тем внутри каждой категории. Они не хотят использовать выбор «Новая тема» в верхнем меню, так как это не создаёт тему в той категории, в которой они находятся в данный момент.

Я также прогнал это через Llama 3.1 405B и GPT-4 через ask.discourse.com, но не смог получить версию JavaScript, которая бы компилировалась. Я постоянно получаю ошибку компиляции: SyntaxError: Private field must be used in an enclosing class.

Также возникает ошибка реестра виджетов, но это происходит потому, что пользовательский виджет никогда не создаётся из-за ошибки компиляции JS.

Я использую центральную тему Discourse.

Дан следующий раздел Header:
"<script type="text/discourse-plugin" version="0.8.18">
  api.createWidget('custom-new-topic-button', {
    tagName: 'div.custom-new-topic-button',

    buildKey: () => `custom-new-topic-button`,

    html() {
      return [
        this.attach('button', {
          className: 'btn btn-primary',
          action: 'createNewTopic',
          contents: 'New Topic'
        })
      ];
    },

    click() {
      const composerController = this.container.lookup("controller:composer");
      const currentCategory = this.container.lookup("controller:navigation/category").get("model.id");

      composerController.open({
        action: require("discourse/models/composer").default.CREATE_TOPIC,
        draftKey: require("discourse/models/composer").default.DRAFT,
        categoryId: currentCategory,
      });

      return false;
    },
  });

  api.decorateWidget('topic-list:before', (helper) => {
    if (api.getCurrentUser()) {
      helper.appendChild(helper.createWidget('custom-new-topic-button'));
    }
  });
</script>

"
с этим разделом Head

<script>
    {{#if currentUser}}
     <div class="topic-list-body-new-topic">
      {{custom-new-topic-button}}
     </div>
   {{/if}}
</script>

и этим CSS

.topic-list-body::before {
    content: "";
    display: block;
    position: relative; /* Важно для абсолютного позиционирования внутри */
  }
  
  .topic-list-body-new-topic {
    position: absolute;
    top: 0;
    left: 0;
    padding: 10px;
    background: #f2f3f5;
    border-bottom: 1px solid #ccc;
  }
  .custom-new-topic-button .btn.btn-primary {
    background-color: #007bff;
    border-color: #007bff;
    color: #fff;
  }

Это был мой исходный код, а последнее рекомендация следующая:

<script type="text/discourse-plugin" version="0.8.18">
  api.createWidget('custom-new-topic-button', {
    tagName: 'div.custom-new-topic-button',

    buildKey() {
      return 'custom-new-topic-button';
    },

    defaultState() {
      return {};
    },

    html() {
      return [
        this.attach('button', {
          className: 'btn btn-primary',
          action: 'createNewTopic',
          contents: 'New Topic'
        })
      ];
    },

    createNewTopic() {
      const composerController = this.container.lookup("controller:composer");
      const currentCategory = this.container.lookup("controller:navigation/category").get("model.id");
      const Composer = require("discourse/models/composer").default;

      composerController.open({
        action: Composer.CREATE_TOPIC,
        draftKey: Composer.DRAFT,
        categoryId: currentCategory,
      });

      return false;
    },
  });

  api.decorateWidget('topic-list:before', (helper) => {
    if (api.getCurrentUser()) {
      helper.appendChild(helper.createWidget('custom-new-topic-button'));
    }
  });
</script>

@steven.webster @vasanth.mohan

Удалось заставить это работать. @sam, я исчерпал свой дневной лимит запросов, чтобы спросить… извините за это. Затем я переключился на нашу LLAMA 405 B и после множества попыток получил рабочий код. Не могу даже представить, сколько бы это мне стоило денег. Возможно, кто-то из участников форума напишет: «Эй, идиот, для этого есть настройка в таком-то месте». Но это было увлекательное упражнение по непрерывной доработке промптов для получения рабочего кода.

Вот несколько ключевых моментов (учтите, что некоторые из них могут быть неточными, так как я являюсь разработчиком JavaScript и разработчиком Discourse всего около двух дней суммарно):

Это форум, размещенный на Discourse, и у меня нет доступа к ОС.
Это плагин, поэтому вы не можете напрямую ссылаться на Discourse.
Вы не можете напрямую ссылаться на Ember.
Если вы собираетесь использовать это, убедитесь, что привязали его к self.

Затем перейдите к необходимым функциональным возможностям.

<script type="text/discourse-plugin" version="1.24.0">
  api.modifyClass('component:topic-list', {
    pluginId: 'your-plugin-id',

    didRender: function() {
      const category = this.get('category');
      if (!category) {
        console.error('Category not found');
        return;
      }

      const currentUser = this.currentUser;
      if (!currentUser) {
        console.error('Current user not found');
        return;
      }

      const canCreateTopic = currentUser.can_create_topic;
      if (canCreateTopic === undefined) {
        console.error('Can create topic not defined');
        return;
      }

      console.log('category:', category);
      console.log('canCreateTopic:', canCreateTopic);

      if (category && canCreateTopic) {
        const topicListBody = document.querySelector('.topic-list-body');
        const listContainer = document.querySelector('.list-container');
        const topicList = document.querySelector('.topic-list');

        let container = topicListBody || listContainer || topicList;

        if (!container) {
          container = this.element; // Используйте элемент компонента как контейнер
        }

        console.log('container:', container);

        const existingButton = container.querySelector('.new-topic-button');
        if (!existingButton) {
          const newTopicButton = document.createElement('a');
          newTopicButton.href = '#';
          newTopicButton.className = 'new-topic-button';
          newTopicButton.setAttribute('data-category-id', category.id);
          newTopicButton.textContent = 'New Topic';

          const self = this; // Сохраните контекст компонента
          newTopicButton.onclick = (e) => {
            e.preventDefault();
            const router = api.container.lookup('router:main');
            const url = router.generate('new-topic', { queryParams: { category: category.slug } });
            router.transitionTo(url);
          };

          container.prepend(newTopicButton);
          console.log('button added');
        }
      }
    }
  });
</script>

Я поспешил с выводами: в той версии была неприятная проблема с методом didRender, из-за которой посты не создавались ни в одной категории, кроме первой. Я попросил Llama исправить это.

<script type="text/discourse-plugin" version="1.24.0">
  api.modifyClass('component:topic-list', {
    pluginId: 'your-plugin-id',

    didRender: function() {
      const category = this.get('category');
      if (!category) {
        console.error('Категория не найдена');
        return;
      }

      const currentUser = this.currentUser;
      if (!currentUser) {
        console.error('Текущий пользователь не найден');
        return;
      }

      const canCreateTopic = currentUser.can_create_topic;
      if (canCreateTopic === undefined) {
        console.error('Свойство can_create_topic не определено');
        return;
      }

      console.log('category:', category);
      console.log('canCreateTopic:', canCreateTopic);

      if (category && canCreateTopic) {
        const topicListBody = document.querySelector('.topic-list-body');
        const listContainer = document.querySelector('.list-container');
        const topicList = document.querySelector('.topic-list');

        let container = topicListBody || listContainer || topicList;

        if (!container) {
          container = this.element; // Используем элемент компонента как контейнер
        }

        console.log('container:', container);

        let newTopicButton = container.querySelector('.new-topic-button');
        if (!newTopicButton) {
          newTopicButton = document.createElement('a');
          newTopicButton.href = '#';
          newTopicButton.className = 'new-topic-button';
          container.prepend(newTopicButton);
          console.log('кнопка добавлена');
        }

        newTopicButton.setAttribute('data-category-id', category.id);
        newTopicButton.textContent = 'Новая тема';

        const self = this; // Сохраняем контекст компонента
        newTopicButton.onclick = (e) => {
          e.preventDefault();
          const router = api.container.lookup('router:main');
          const url = router.generate('new-topic', { queryParams: { category: category.slug } });
          router.transitionTo(url);
        };
      }
    }
  });
</script>

Похоже, вы решили свою проблему, как это часто бывает, когда я составляю запросы на помощь.

New Topic Header Button - #67 by patrickemin содержит пример и может быть именно тем, что вы ищете.

Также скажите вашим инженерам, что они могут нажать клавишу «c», чтобы написать сообщение.

В разделе «Центр» кнопка «Начать новое обсуждение» находится внизу списка тем. Все хотели бы видеть кнопку вверху. Возможно, я изменю её на небольшую кнопку с плюсом, но эти два скриншота показывают текущее состояние.


@pfaffman Я попробовал новую кнопку заголовка темы в качестве первого шага. Однако она не очень хорошо сочетается с новой темой Discourse Central.

Виджеты уходят. Использовать API виджетов — плохая идея.

Используйте компоненты Glimmer.

Нам также придется удалить множество устаревших материалов из AI-даггеров, а также дождаться дообучения моделей для виджетов постов. Даже ask.discourse.com движется в сторону использования виджетов. Чем будут заменены виджеты? Я попробую получить от модели код, который это учитывает. Кстати, основная часть моей задачи — проверить, сможет ли модель выполнить эту работу, так как я почти ничего не знаю о JS :slight_smile:

Понимаю ваши чувства.

Компоненты Glimmer. Где-то есть хорошая тема, описывающая их.

А вот пример создания компонента:

А вот способ внедрения его с помощью JavaScript:

Другой способ внедрения — устаревающий «школьный» метод создания коннектора, который затем внедряет компонент. Однако преимущество метода инициализатора заключается в том, что вы можете передать желаемый плагин- outlet инициализатору, что позволит создать настройку, определяющую, где именно компонент будет отображаться.

Думаю, вам стоит найти пример чего-то, что вызывает shouldDisplay (или что-то подобное), если вы хотите, чтобы он отображался только иногда (например, когда пользователь авторизован).

О, мой финальный код, который llama выдала мне прошлой ночью, не использовал виджеты, но и не использовал Glimmer. Я поэкспериментирую с Glimmer сегодня позже.

<script type="text/discourse-plugin" version="1.24.0">
  api.modifyClass('component:topic-list', {
    pluginId: 'your-plugin-id',

    didRender: function() {
      const category = this.get('category');
      if (!category) {
        console.error('Категория не найдена');
        return;
      }

      const currentUser = this.currentUser;
      if (!currentUser) {
        console.error('Текущий пользователь не найден');
        return;
      }

      const canCreateTopic = currentUser.can_create_topic;
      if (canCreateTopic === undefined) {
        console.error('can_create_topic не определён');
        return;
      }

      console.log('category:', category);
      console.log('canCreateTopic:', canCreateTopic);

      if (category && canCreateTopic) {
        const topicListBody = document.querySelector('.topic-list-body');
        const listContainer = document.querySelector('.list-container');
        const topicList = document.querySelector('.topic-list');

        let container = topicListBody || listContainer || topicList;

        if (!container) {
          container = this.element; // Используйте элемент компонента как контейнер
        }

        console.log('container:', container);

        let newTopicButton = container.querySelector('.new-topic-button');
        if (!newTopicButton) {
          newTopicButton = document.createElement('a');
          newTopicButton.href = '#';
          newTopicButton.className = 'new-topic-button';
          container.prepend(newTopicButton);
          console.log('кнопка добавлена');
        }

        newTopicButton.setAttribute('data-category-id', category.id);
        newTopicButton.textContent = 'Новая тема';

        const self = this; // Сохраните контекст компонента
        newTopicButton.onclick = (e) => {
          e.preventDefault();
          const router = api.container.lookup('router:main');
          const url = router.generate('new-topic', { queryParams: { category: category.slug } });
          router.transitionTo(url);
        };
      }
    }
  });
</script>

Важное замечание: список тем в настоящее время не переведён на Glimmer.

Однако, если вы подключаете компоненты к плагин-аутлетам, следует использовать Glimmer.

Если необходимо подключить к виджету, можно использовать RenderGlimmer для достижения этой цели.

Пример здесь:

Настоятельно не рекомендую полагаться слишком сильно на LLM, не изучив предварительно основы самостоятельно, поскольку именно вы должны быть окончательным контролёром качества (QA) того, что генерирует LLM.

Также необходимо правильно спроектировать решение.

Подробнее об EmberJS читайте здесь:

Спасибо. Я обязательно изучу это подробнее, так как я совсем новичок в Discourse и JavaScript.

Я работаю в компании, занимающейся искусственным интеллектом, поэтому это также было упражнением в инженерии промптов.

Отлично, так что вы получите хорошее представление об их сильных и слабых сторонах.

Я написал первого бота для Discourse под названием Chatbot, поэтому у меня тоже есть некоторые знания в этой области.

Добавим специальную инструкцию в системный промпт, чтобы «ask» никогда не рекомендовал использование виджетов