自定义部分中的表情符号

我在侧边栏的“类别”部分使用了表情符号,但我也有一个公开的自定义部分。我想在那个部分使用相同的彩色图标,这样它就不会像“类别”部分那样“平淡无奇”。

这可能吗?

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,
    // 在此处添加更多部分
  };
  
  // [sectionId, itemName] 到 emoji 名称的映射
  const iconReplacements = {
    // 社区部分 (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",
    
    // 个人部分 (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;
        }
      }
      
      // 监视侧边栏部分中的 childList 更改
      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,然后创建每个部分的映射。

这样做的原因是,我可能有 2 个标题为“Journal”的部分,但我可能希望为每个部分使用不同的表情符号。

我敢肯定会有人说有现成的组件或插件可以做到这一点 :wink: 但与 ChatGPT 和 Claude 来回调试直到一切如我所愿仍然很有趣。

希望这对其他用户有所帮助。

如果您发现任何可以调整/改进的地方,请分享 :raising_hands:

3 个赞

我刚更新了 JS 代码,因为我注意到它在移动设备上仍然显示默认图标。所有功能在移动设备和桌面设备上都按预期工作。

又一个编辑:当我折叠和展开该部分时,表情符号会消失,并被替换为原始图标。现在可以正常工作了,我再次更新了 JS 代码。

很高兴你找到了实现这一功能的方法——我认为这对于核心功能来说是一个非常自然的选择,因为我们现在已经支持分类的 emoji 了。

3 个赞

我同意。希望这最终能成为一个原生功能。在此之前,这个自定义组件正在发挥它的作用 :slight_smile:

2 个赞

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