Component to add a custom message to the Composer based on user and categories ID

AI-generated summary

alltiagocom developed a Discourse component to display a custom reminder message in the Composer for specific users and categories. The script, written in JavaScript, checks if the current user’s ID is in a predefined list (TARGET_USER_IDS) and if the selected category ID matches any in TARGET_CATEGORY_IDS. If both conditions are met and the Composer is empty, it automatically inserts a configurable message (e.g., “:stop_sign: Add the appropriate tag to this post!”). The message is removed if the user starts typing or changes categories. The component uses apiInitializer to hook into Composer events (composer:opened, composer:closed) and polls every 300ms to detect category changes. This workaround addresses the lack of native support for user- and category-specific tag requirements as discussed here. Screenshots show the message appearing only when the user and category match the configured IDs. The code is shared for others to implement by pasting it into a component’s JS tab.

Today I was facing this issue:

but for now, until the team works on it (hopefully), I decided to ask Claude for some help building a Component that allows me to have a custom message in the Composer to remind me to add a tag. I can add one or more users, by ID, and I can limit it to certain categories, also by ID.

If the ID (user and categories) is NOT on the list, I see this:

If the ID is on the list, I see the message:

If this is helpful to others, here it is (just create a component and add the script to the JS tab):

import { apiInitializer } from "discourse/lib/api";

export default apiInitializer("0.8.31", (api) => {
  // Configuration: Add your user IDs and category IDs here
  const TARGET_USER_IDS = [2]; // Replace with actual user IDs
  const TARGET_CATEGORY_IDS = [4,49]; // Replace with actual category IDs
  const REMINDER_MESSAGE = "🛑 Add the appropriate tag to this post!";
  
  let previousCategoryId = null;
  let checkInterval = null;
  
  function checkAndUpdateMessage() {
    const composer = api.container.lookup("controller:composer");
    if (!composer || !composer.model) {
      // Composer is gone, stop checking
      if (checkInterval) {
        clearInterval(checkInterval);
        checkInterval = null;
      }
      return;
    }
    
    const currentUser = api.getCurrentUser();
    if (!currentUser || !TARGET_USER_IDS.includes(currentUser.id)) return;
    
    const model = composer.model;
    const categoryId = model.categoryId;
    
    // Only act if category has changed
    if (categoryId === previousCategoryId) return;
    previousCategoryId = categoryId;
    
    const currentReply = model.reply || "";
    
    // Remove message if it was added
    if (currentReply.startsWith(REMINDER_MESSAGE)) {
      model.set("reply", currentReply.replace(REMINDER_MESSAGE, "").trim());
    }
    
    // Add message if in target category and composer is empty
    if (categoryId && TARGET_CATEGORY_IDS.includes(categoryId)) {
      const cleanReply = model.reply || "";
      if (cleanReply.trim().length === 0) {
        model.set("reply", REMINDER_MESSAGE);
      }
    }
  }
  
  // Check on composer open
  api.onAppEvent("composer:opened", () => {
    previousCategoryId = null;
    checkAndUpdateMessage();
    
    // Start polling for category changes
    if (checkInterval) clearInterval(checkInterval);
    checkInterval = setInterval(checkAndUpdateMessage, 300);
  });
  
  // Stop checking when composer closes
  api.onAppEvent("composer:closed", () => {
    if (checkInterval) {
      clearInterval(checkInterval);
      checkInterval = null;
    }
    previousCategoryId = null;
  });
});
3 Likes