Cannot get the js in my component to compile SyntaxError: Private field must be used in an enclosing class

At the end of the day I need a new topic button in the topic list body in the discourse central theme . Every engineer is complaining that they don’t like the start new conversation button just being at the bottom of the topic list inside each category . They dont want to use the new topic menu choice up top because that does not start the topic in the category they are presently in

I have run this through llama 3.1 405B as well as gpt4 via ask.discourse.com but I cannot get a version of a java script that will compile. I am constantly getting Compile error: SyntaxError: Private field must be used in an enclosing class

I also get a widget registry error but that is because the custom widget never gets created due to the js compile error

I am using Discourse Central Theme

Given this Header section:
"<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>

"
with this Head section

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

and this CSS

.topic-list-body::before {
    content: "";
    display: block;
    position: relative; /* Important to allow absolute positioning within */
  }
  
  .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;
  }

That was my origianl code the latest recommendation is

<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

Got it to work. @sam I exhausted my daily calls to ask …sorry about that. And then ran over to our LLAMA 405 B and after a bunch of back and forth I got working code. I cannot imagine how much money this would have cost me . I may hear form other folks in the forum hey idiot there was a setting to do this over at this location . But this was a fun exercise of continuous prompt revision to get working code.

Some of the keys are to say ( mind you some of these may be inaccurate because I’ve been a java script developer and a discourse develoepr for about 2 days total time.)

This is a forum hosted by discourse and I do not have OS access
This is a plugin so you cannot reference Discourse. directly .
You cannot reference Ember directly
If you are going to use this, please ensure you bind it to self

An then go into the functional traits you want.

<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; // Use the component's element as the container
        }

        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; // Store the component's context
          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>


I spoke to soon that version had a sticky little didRender issue where it didn’t want to post into any categories other than the first one . got llama to fix that

<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; // Use the component's element as the container
        }

        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('button added');
        }

        newTopicButton.setAttribute('data-category-id', category.id);
        newTopicButton.textContent = 'New Topic';

        const self = this; // Store the component's context
        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>

Seems like you’ve solved your problem, as I often do when composing requests for help.

New Topic Header Button - #67 by patrickemin would provide an example and may be exactly what you’re looking for.

Also, tell your engineers they can press the c key to compose a message.

3 Likes

In Central the start a new conversation button is at the bottom of the topic list . Everyone wanted a button up top. I may change it to a small plus but these two screen shots show it .


1 Like

@pfaffman I tried the new topic header button as the first attempt. However that does not play well with the new Discourse Central theme

Widgets are going away. It’s not a good idea to use the widget api.

Use Glimmer Components.

2 Likes

We will need to remove a bunch of old content out of AI rags then as well as wait for post widget models to be fine tuned . Even ask.discourse.com pushed toward the widget route. What will be replacing widgets ? Ill see if I can get a model to spit out code that takes that into account. By the way the bulk of my exercise is to see if I can get a model to do this work since I know virtually noting about js :slight_smile:

I know the feeling.

Glimmer components. Somewhere there is a decent topic describing them.

But here’s an example to create the component:

And here is a javascript way to insert it:

The other way to insert it is the becoming-old-school way of creating a connector that then inserts the component, but the advantage of the initializer way is that you can then pass the desired plugin outlet to the initializer so you can have a setting the changes where it gets displayed.

I think you want to find an example of something that calls shouldDisplay ( or something like that) if you want it to display only sometimes (like if they are logged).

4 Likes

OH my final code that I got llama to spit out last night did nto use widgets but it didnt use glimmer either. I’ll fiddle with glimmer later today

<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; // Use the component's element as the container
        }

        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('button added');
        }

        newTopicButton.setAttribute('data-category-id', category.id);
        newTopicButton.textContent = 'New Topic';

        const self = this; // Store the component's context
        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>

Caveat: Topic List is currently not converted to Glimmer.

However, if you are attaching Components to Plugin Outlets you should use Glimmer.

If you have to attach to a widget you can use RenderGlimmer to achieve that.

Example here:

I implore you not to rely too much on LLMs without learning the basics for yourself first, as you need to be the ultimate QA on what the LLM is outputting.

You also need to architect the solution correctly.

Read more about EmberJS here:

3 Likes

Thank you . I definitely will study more as I’m brand new to discourse and js.

I work for an AI company so this was also an exercise in prompt engineering.

3 Likes

Cool so you will have a good idea of their strengths and weaknesses.

I wrote the first AI Bot for Discourse called Chatbot, so I have some insight too.

2 Likes

Will add some special guidance to the system prompt to ensure “ask” never recommends usage of widgets

5 Likes