رموز تعبيرية في قسم مخصص

I’m using Emojis in the Categories section in the sidebar, but I also have a custom section that is public. I wanted to have the same colorful icons in that section so it doesn’t get so “bland” compared to the Categories section.

Is this possible?

إعجابَين (2)

With the help of both ChatGPT and Claude, I was able to make it work and very customizable:

If you want to do it, create a new component and add this to the CSS tab:

.sidebar-section-link-prefix .emoji.prefix-emoji {
  width: 1rem !important;
  height: 1rem !important;
}

For my particular case 1rem works great. Adjust to your forum/community

Then in the JS tab:

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

export default apiInitializer("0.11.1", (api) => {
  // Map section names to IDs
  const sectionIds = {
    "community": 1,
    "tiago": 2,
    "personal-section": 3,
    // Add more sections here
  };
  
  // Map of [sectionId, itemName] to emoji names
  const iconReplacements = {
    // Community section (ID: 1)
    "1,admin": "wrench",
    "1,review": "triangular_flag_on_post",
    "1,everything": "books",
    "1,my-posts": "writing_hand",
    "1,my-messages": "envelope_with_arrow",
    
    // Tiago section (ID: 2)
    "2,Journal": "notebook_with_decorative_cover",
    "2,Music": "musical_note",
    "2,About": "bust_in_silhouette",
    
    // Personal section (ID: 3)
    "3,Backups": "floppy_disk",
    "3,Scheduled": "clock3",
    "3,Staff": "lock",
    "3,Components": "electric_plug",
  };

  function replaceIcons() {
    Object.entries(sectionIds).forEach(([sectionName, sectionId]) => {
      Object.entries(iconReplacements).forEach(([key, emojiName]) => {
        const [keyId, itemName] = key.split(',');
        
        // Only process if this replacement is for the current section
        if (parseInt(keyId) === sectionId) {
          const url = emojiUrlFor(emojiName);
          
          // Try multiple selectors to catch both hamburger menu and fixed sidebar
          const selectors = [
            // Fixed sidebar selector
            `div[data-section-name="${sectionName}"] li[data-list-item-name="${itemName}"] .sidebar-section-link-prefix.icon`,
            // Hamburger menu selector (more specific)
            `.menu-panel div[data-section-name="${sectionName}"] li[data-list-item-name="${itemName}"] .sidebar-section-link-prefix.icon`,
            // Generic fallback
            `[data-section-name="${sectionName}"] [data-list-item-name="${itemName}"] .sidebar-section-link-prefix.icon`
          ];
          
          for (const selector of selectors) {
            const prefix = document.querySelector(selector);
            
            if (prefix && url) {
              // Check if it's already replaced to avoid unnecessary DOM manipulation
              if (!prefix.querySelector('.prefix-emoji')) {
                prefix.innerHTML = `
                  <img src="${url}"
                       title="${emojiName}"
                       alt="${emojiName}"
                       class="emoji prefix-emoji">
                `;
              }
              break; // Exit loop once we find and replace the icon
            }
          }
        }
      });
    });
  }

  // Run on page change
  api.onPageChange(replaceIcons);
  
  // Also run with delay to catch dynamically loaded content
  api.onPageChange(() => {
    setTimeout(replaceIcons, 100);
    setTimeout(replaceIcons, 500);
  });

  // Enhanced MutationObserver to catch sidebar changes
  const observer = new MutationObserver((mutations) => {
    let shouldReplace = false;
    
    mutations.forEach((mutation) => {
      // Watch for added nodes (original functionality)
      mutation.addedNodes.forEach((node) => {
        if (node.nodeType === Node.ELEMENT_NODE) {
          if (node.classList?.contains('menu-panel') || 
              node.querySelector?.('.sidebar-sections') ||
              node.classList?.contains('sidebar-sections') ||
              node.querySelector?.('[data-section-name]')) {
            shouldReplace = true;
          }
        }
      });
      
      // Watch for attribute changes on sidebar sections (collapse/expand)
      if (mutation.type === 'attributes' && mutation.target.nodeType === Node.ELEMENT_NODE) {
        const target = mutation.target;
        if (target.matches('[data-section-name]') || 
            target.closest('[data-section-name]') ||
            target.matches('.sidebar-section') ||
            target.closest('.sidebar-section')) {
          shouldReplace = true;
        }
      }
      
      // Watch for childList changes in sidebar sections
      if (mutation.type === 'childList' && mutation.target.nodeType === Node.ELEMENT_NODE) {
        const target = mutation.target;
        if (target.matches('[data-section-name]') || 
            target.closest('[data-section-name]') ||
            target.querySelector('[data-section-name]')) {
          shouldReplace = true;
        }
      }
    });
    
    if (shouldReplace) {
      setTimeout(replaceIcons, 50);
    }
  });

  // Start observing with enhanced options
  observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: true, // Watch for attribute changes
    attributeFilter: ['class', 'style', 'aria-expanded'], // Common attributes that change on collapse/expand
  });

  // Additional event listeners for common sidebar interactions
  document.addEventListener('click', (event) => {
    // Check if click was on a sidebar section header or toggle button
    if (event.target.closest('.sidebar-section-header') ||
        event.target.closest('[data-section-name]') ||
        event.target.matches('.sidebar-section-toggle')) {
      setTimeout(replaceIcons, 100);
    }
  });
});

I made it so I can easily add new custom sections by name, attributing it an id and then creating each section’s mapping.

The reason for that is because I may have 2 sections with the title Journal, for example, but maybe I want 2 different emojis for each.

I’m pretty sure there will be someone saying that there’s a component or plugin for this :wink: but it was still fun to go back and forth with ChatGPT and Claude until everything worked the way I wanted.

Hope this helps other users.

If you see anything that can be tweaked/improved, please share :raising_hands:

إعجابَين (2)

I just updated the JS code, because I noticed that on mobile it was still showing the default icons. Everything is working as expected on both mobile and desktop.

Another edit: when I would collapse and expand the section, the emojis would be gone and replaced with the original icons. It’s working now and I updated the JS code again.

Glad you found a way to do this – I think this would be a pretty natural fit for a core feature, now that we support emoji for categories.

3 إعجابات

I agree. Hope this becomes a native feature eventually. Until that happens, this custom component is doing its job :slight_smile:

إعجاب واحد (1)