Эмодзи в пользовательском разделе

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

Возможно ли это?

2 лайка

С помощью ChatGPT и Claude мне удалось заставить это работать, и теперь решение очень гибкое:

Если вы хотите сделать то же самое, создайте новый компонент и добавьте следующий код во вкладку CSS:

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

В моём случае 1rem работает отлично. Настройте значение под свой форум или сообщество.

Затем во вкладку JS добавьте:

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

export default apiInitializer("0.11.1", (api) => {
  // Сопоставление названий секций с их ID
  const sectionIds = {
    "community": 1,
    "tiago": 2,
    "personal-section": 3,
    // Добавьте больше секций здесь
  };
  
  // Сопоставление [ID секции, название элемента] с именами эмодзи
  const iconReplacements = {
    // Секция "Community" (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" (ID: 2)
    "2,Journal": "notebook_with_decorative_cover",
    "2,Music": "musical_note",
    "2,About": "bust_in_silhouette",
    
    // Секция "Personal" (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(',');
        
        // Обрабатываем только если замена относится к текущей секции
        if (parseInt(keyId) === sectionId) {
          const url = emojiUrlFor(emojiName);
          
          // Пробуем несколько селекторов, чтобы охватить и меню-гамбургер, и фиксированную боковую панель
          const selectors = [
            // Селектор для фиксированной боковой панели
            `div[data-section-name="${sectionName}"] li[data-list-item-name="${itemName}"] .sidebar-section-link-prefix.icon`,
            // Селектор для меню-гамбургер (более специфичный)
            `.menu-panel div[data-section-name="${sectionName}"] li[data-list-item-name="${itemName}"] .sidebar-section-link-prefix.icon`,
            // Универсальный резервный вариант
            `[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) {
              // Проверяем, не была ли иконка уже заменена, чтобы избежать лишних манипуляций с DOM
              if (!prefix.querySelector('.prefix-emoji')) {
                prefix.innerHTML = `
                  <img src="${url}"
                       title="${emojiName}"
                       alt="${emojiName}"
                       class="emoji prefix-emoji">
                `;
              }
              break; // Выходим из цикла, как только нашли и заменили иконку
            }
          }
        }
      });
    });
  }

  // Запуск при изменении страницы
  api.onPageChange(replaceIcons);
  
  // Также запускаем с задержкой, чтобы перехватить динамически загружаемый контент
  api.onPageChange(() => {
    setTimeout(replaceIcons, 100);
    setTimeout(replaceIcons, 500);
  });

  // Улучшенный MutationObserver для отслеживания изменений боковой панели
  const observer = new MutationObserver((mutations) => {
    let shouldReplace = false;
    
    mutations.forEach((mutation) => {
      // Отслеживаем добавленные узлы (основная функциональность)
      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;
          }
        }
      });
      
      // Отслеживаем изменения атрибутов в секциях боковой панели (сворачивание/разворачивание)
      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;
        }
      }
      
      // Отслеживаем изменения списка дочерних элементов в секциях боковой панели
      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);
    }
  });

  // Начинаем наблюдение с улучшенными опциями
  observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: true, // Отслеживаем изменения атрибутов
    attributeFilter: ['class', 'style', 'aria-expanded'], // Распространённые атрибуты, меняющиеся при сворачивании/разворачивании
  });

  // Дополнительные обработчики событий для типичных взаимодействий с боковой панелью
  document.addEventListener('click', (event) => {
    // Проверяем, был ли клик по заголовку секции боковой панели или кнопке переключения
    if (event.target.closest('.sidebar-section-header') ||
        event.target.closest('[data-section-name]') ||
        event.target.matches('.sidebar-section-toggle')) {
      setTimeout(replaceIcons, 100);
    }
  });
});

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

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

Я почти уверен, что кто-то скажет, что для этого уже есть компонент или плагин :wink: но всё равно было весело перебирать варианты с ChatGPT и Claude, пока всё не заработало так, как я хотел.

Надеюсь, это поможет другим пользователям.

Если вы видите что-то, что можно настроить или улучшить, пожалуйста, поделитесь :raising_hands:

4 лайка

Я только что обновил код JS, так как заметил, что на мобильных устройствах всё ещё отображались иконки по умолчанию. Всё работает как положено и на мобильных, и на настольных устройствах.

Ещё одно исправление: при сворачивании и разворачивании раздела эмодзи исчезали и заменялись исходными иконками. Сейчас всё работает, и я снова обновил JS-код.

Рад, что вы нашли способ это сделать. Думаю, теперь, когда мы поддерживаем эмодзи для категорий, это станет естественным дополнением к основной функциональности.

3 лайка

Согласен. Надеюсь, что в будущем это станет нативной функцией. До тех пор этот кастомный компонент отлично справляется со своей задачей :slight_smile:

2 лайка

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.