Замена стандартных SVG-иконок Discourse на кастомные в теме

Вы можете заменить стандартные SVG-иконки Discourse индивидуально или целиком на собственные кастомные SVG и переопределить их внутри темы или компонента темы.

Шаг 1 — Создание SVG-спрайта

Для начала вам необходимо создать SVG-спрайт. Он может содержать как одну дополнительную кастомную SVG-иконку, так и целый набор из сотен иконок для полной замены.

Спрайт следует сохранить в формате SVG-файла. По сути, вы помещаете содержимое тега <svg> из исходного файла SVG-иконки внутрь тегов <symbol> и присваиваете им удобные идентификаторы.

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="my-theme-icon-1">
    <!--
      Код внутри тега <svg> из исходного файла SVG-иконки
      обычно это всё содержимое между тегами <svg>
      (но не сам тег SVG, он заменяется тегом <symbol> выше)
      Вы можете перенести любые атрибуты (например, ViewBox="0 0 0 0") в тег <symbol>
      -->
  </symbol>

  <symbol id="my-theme-icon-2">
    <!-- Код SVG здесь. Добавляйте дополнительные блоки <symbol> по мере необходимости.
      -->
  </symbol>
</svg>
  • Убедитесь, что каждому символу в спрайте присвоен уникальный ID. Вероятно, для вашего душевного равновесия будет полезно использовать префикс с названием вашей темы, например my-theme-icon.

  • Чтобы цвет иконки был динамическим, как у существующих иконок, установите значение fill в currentColor, а не в жёстко заданный цвет (например, #333).

  • Для масштабирования или правильного центрирования вашей иконки используйте атрибут viewBox в теге <symbol>. Дополнительную информацию можно найти здесь: How to Scale SVG | CSS-Tricks.

  • Следите за конфликтами стилей внутри ваших SVG. Например, в SVG-файлах часто встречается инлайн-стиль вроде .st0{fill:#FF0000;}. Если у вас несколько SVG используют одни и те же классы, это может вызвать проблемы (чтобы исправить это, отредактируйте классы, сделав их уникальными для каждой иконки).

  • Если у вас много иконок, существуют способы автоматизировать этот процесс. https://www.npmjs.com/package/svg-sprite-generator — это простой инструмент командной строки для объединения SVG в спрайт.

Пример — спрайт с одной кастомной иконкой

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="bat-icon" viewBox="6 6 36 36">
    <path
      fill="currentColor"
      d="M24,18.2c0.7,0,0.9,0.2,0.9,0.2l0.4-1.7c0,0,0.4,1.5,0.4,2.8c0.2,1.1,2.2,0.4,3.9,0C31.4,19.1,32,16,32,16h16c0,0-9.4,3.5-7,10c0,0-14.8-2-17,7l0,0c-2.2-9-17-7-17-7c2.4-6.5-7-10-7-10h16c0,0,0.6,3.1,2.3,3.5c1.7,0.4,3.9,1.1,3.9,0c0.2-1.1,0.4-2.8,0.4-2.8l0.4,1.7C23.1,18.4,23.4,18.2,24,18.2L24,18.2L24,18.2z"
    />
  </symbol>
</svg>

Шаг 2 — Добавление спрайта в вашу тему

После того как ваш спрайт готов, вам нужно добавить SVG-файл в ваш компонент/тему. Это легко сделать через интерфейс, или вы можете захардкодить его в компонент/тему.

:information_source: Как только файл будет загружен в любой установленный компонент/тему, он будет доступен во всей вашей инстанции с использованием ID из тега <symbol>.

Через интерфейс

Перейдите в раздел Uploads (Загрузки) в настройках темы/компонента и добавьте ваш файл спрайта с именем SCSS-переменной icons-sprite:

Захардкодить в Тему / Компонент

Добавьте файл спрайта в папку /assets темы. Затем обновите файл assets.json в корневой папке.
Для SVG-спрайта с названием my-icons.svg ваш файл about.json должен содержать следующее:

"assets": {
  "icons-sprite": "/assets/my-icons.svg"
}

Шаг 3 (необязательно) — Переопределение стандартных иконок

Теперь, когда ваш спрайт настроен, вы можете указать Discourse заменить иконки. Вот как это сделать из api-initializer:

// {theme}/javascripts/discourse/api-initializers/init-theme.gjs

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

export default apiInitializer((api) => {
  api.replaceIcon("bars", "my-theme-icon-bars");
  api.replaceIcon("link", "my-theme-icon-link");
  // и т.д.
});

Первый ID, bars, — это стандартный ID иконки в Discourse, а второй — ID вашей заменяющей иконки. Самый простой способ найти ID одной из наших иконок — исследовать иконку в вашем браузере.

Здесь имя иконки следует за префиксом d-icon-. Таким образом, в этом примере это d-unliked.

Большинство наших иконок следуют именам иконок с https://fontawesome.com/, но есть исключения (именно поэтому проверка ID в инспекторе является наиболее надежным методом). Все исключения можно увидеть в блоке const REPLACEMENTS здесь на github.

Всё. Теперь вы можете стилизовать Discourse с помощью собственных кастомных иконок!


Этот документ находится под контролем версий — предложите изменения на github.

58 лайков

Как можно выбрать конкретную иконку в конкретном элементе? В моём случае я хочу заменить иконку Docs в боковом меню на другую иконку FA.

1 лайк

Я бы скрыл это с помощью CSS и добавил новую кнопку.

Общие / CSS

.sidebar-section-wrapper {
  li[data-list-item-name=docs] {
    display: none !important;
  }
}

Добавьте новую кнопку в разделе «Ещё» > «Настроить этот раздел»

4 лайка

Это не работает. Я попробовал сделать следующее:

<script type="text/discourse-plugin" version="0.8">
    api.replaceIcon("shield-halved", "hat-wizard");
</script>

отсюда, но, похоже, это тоже не сработало. Мне кажется, что метод с тегом script сломан, так как это не работает с превью-ссылкой. Честно говоря, не уверен.

1 лайк

У меня работает :woman_shrugging:t2:

Вы добавляете это во вкладку «Head»? Я также заменил «robot» в своём заголовке:

Возможно, вам потребуется добавить иконку в настройку администратора «Подмножество SVG-иконок».

2 лайка

Да, во вкладку head. И во вкладку header, так как в руководстве так указано.

Готово. Теперь это работает. Спасибо!

1 лайк

@NateDhaliwal Не могли бы вы написать мне в личные сообщения? Мне нужна помощь с чем-то, и я не вижу опцию чата в вашем профиле. Спасибо!

1 лайк
Для левого меню мы переопределяем фон элемента с классом prefix-span внутри категории Audi */
.navigation-category [data-category-id="6"] .prefix-span {
  background: url("https://raw.githubusercontent.com/tima4502/car-icons/bb0d0fae3e5b66c512a27a130b219ec0ee342ada/audi.svg") center/contain no-repeat !important;
}

При переходе на главную страницу иконка-квадрат снова появляется! Подскажите, пожалуйста, что я делаю не так? А на странице самой категории всё работает.

Привет! Не могли бы вы прояснить связь между именем темы/компонента, именем файла, именем переменной SCSS и идентификатором символа…?

Я пытаюсь заменить иконку модератора shield-halved на свою собственную, но инструкции немного неясны.

На шаге 2:

  • На скриншоте раздела «Через интерфейс» показано имя файла baticonsprite.svg и имя переменной SCSS icons-sprite.
  • Однако в разделе «Записать в тему/компонент вручную» говорится, что нужно записать это вручную в тему или компонент.
    • Но как? В редакторе я не вижу файла assets.json. Если я экспортирую компонент, то вижу файл about.json, в котором действительно отображается спрайт, загруженный через интерфейс.
    • Однако в этом примере также указано другое имя файла — /assets/my-icons.svg. Это тот же файл, что и baticonsprite.svg?
    • Являются ли эти два способа альтернативными вариантами выполнения одной и той же задачи, то есть нужно сделать что-то одно, а не оба сразу…?

На шаге 3:

  • Теперь в api.replaceIcon() второй параметр не использует ни один из предыдущих идентификаторов: ни icons-sprite, ни bat-icon, ни baticonsprite.svg, ни my-icons.svg. Вместо этого мы получаем совершенно новый идентификатор my-theme-icon-bars… я запутался.
    • Обязателен ли префикс my-theme? Если да, то откуда берется строка с «именем темы»? Например, должно ли быть my-theme-bat-icon? А что, если это компонент, а не тема?
    • И что должно стоять в части icon-bars:
      • Идентификатор символа из XML-файла спрайта SVG?
      • Имя файла SVG?
      • Имя переменной SCSS, которое вы ему присвоили?
      • Какая-то комбинация вышеперечисленного (например, icons-sprite-bat-icon)?

И где именно нужно размещать вызов api.replaceIcon()? Можно ли поместить его во вкладку «JS» пользовательского компонента, где уже есть шаблонный код:

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

export default apiInitializer((api) => {
   // ваш код здесь
});

Или необходимо создать пользовательский тег <script type="discourse/plugin"> и разместить его во вкладке <head>?


Извините за мою путаницу.

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

Мой XML-файл спрайта выглядит так:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">


<symbol id="my-logo" viewBox="0 0 94.652 95.261"><defs><linearGradient id="a" y1="47.631" x2="94.652" y2="47.631" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ff593d"/><stop offset="1" stop-color="#ff7751"/></linearGradient></defs><title>d_only</title><path d="M47.326,0H0V95.261H47.326c23.67,0,47.326-21.326,47.326-47.624S71,0,47.326,0Zm0,69.274a21.644,21.644,0,1,1,21.65-21.637A21.635,21.635,0,0,1,47.326,69.274Z" fill="url(#a)"/></symbol>

</svg>

Имя файла — my-logo.svg, а имя переменной SCSS также my-logo.

А во вкладке JS пользовательского компонента у меня написано:

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

export default apiInitializer((api) => {
    api.replaceIcon("shield-halved", "my-logo")
});

Но ничего не отображается. Не хватает ли мне какого-то шага, или я неправильно понимаю какой-то вид магической интерполяции строк…?

1 лайк

Кто-нибудь смог разобраться? Всё ещё мучаюсь с этим…

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

export default apiInitializer((api) => {
    api.replaceIcon("shield-halved", "my-logo")
});

Непонятно, откуда берётся второй ID (“my-logo”), если загружать файл через интерфейс:

Ни $my-logo, ни $test, ни my-logo.svg, ни абсолютный URL к SVG на диске и т.д. не работают. Щит заменяется, но на пустоту. SVG просто превращается в пустой <use href="#my-logo"> без содержимого.

1 лайк

Наконец-то разобрался (спасибо, Claude)!

Итак:

  1. Имя файла SVG не имеет значения.

  2. Внутри спрайта SVG именно id символа определяет окончательное имя иконки, например <symbol id="my-logo" …>.

  3. Но имя переменной SCSS ОБЯЗАТЕЛЬНО должно быть именно icons-sprite, а не иметь никакого отношения к финальным ID спрайта:

  4. После загрузки это будет выглядеть так:


    (Обратите внимание, что имя спрайта / переменной нигде не встречается. Оно должно быть в точности $icons-sprite, где $ автоматически добавляется в начало и не является частью того, что вы вводили на предыдущем шаге).

  5. Затем, наконец, во вкладке «JS» темы (а не в <head>), именно там вы используете ID спрайта из шага 1:

    import { apiInitializer } from "discourse/lib/api";
    
    export default apiInitializer((api) => {
    api.replaceIcon("shield-halved", "my-logo")
    });
    

Итак, имя переменной SCSS должно быть в точности icons-sprite, имя файла не имеет значения, а имя иконки в API определяется ID символа внутри спрайта.

3 лайка