# Изменить один экземпляр иконки

**URL:** https://meta.discourse.org/t/change-single-instance-of-icon/336356
**Category:** Feature
**Created:** [15.Ноябрь.2024 15:31:56 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356 "2024-11-15T15:31:56Z")
**Posts on this page:** 15
**Page:** 1

<div class="post-metadata">

### Author: ![king\_laurel](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/king_laurel/32/461779_2.png) [@king\_laurel](https://meta.discourse.org/u/king_laurel)
#### Post date: [15.Ноябрь.2024 15:31:57 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/1 "2024-11-15T15:31:57Z")

</div>

Верните к жизни этот старый пост: [Changing a single instance of an icon](https://meta.discourse.org/t/changing-a-single-instance-of-an-icon/251514)

Мне нужно изменить отдельные вхождения иконок на всём сайте. Я не хочу использовать `replaceIcon()` и заменять все экземпляры этой иконки. Судя по прошлому опыту, это было возможно для конкретных областей сайта, или, по крайней мере, так я это понимаю, но не для всего сайта в целом.

Например, я хочу изменить ОДНУ иконку шестерёнки в поиске, чтобы она лучше отражала то, что произойдёт при клике пользователя. Я не хочу менять все иконки шестерёнок.

Спасибо!

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [15.Ноябрь.2024 15:42:28 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/2 "2024-11-15T15:42:28Z")

</div>

Посмотрите на этот PR — возможно, вы сможете сделать что-то подобное, используя только CSS:

> <https://github.com/discourse/discourse/pull/29439>
>
> Extending @merefield's PR a bit (https://github.com/discourse/discourse/pull/285…93) 
> 
> This gives the ranked choice polls a distinctive preview style, rather than the default radio buttons. This more closely mimics the button styles in the live post: 
> 
> The live poll: 
> 
> !\[image\](https://github.com/user-attachments/assets/672c0b2d-c79f-43d6-a355-71a1e7ae1d2b)
> 
> 
> 
> Preview before: 
> 
> !\[image\](https://github.com/user-attachments/assets/876ef249-be1f-4dd7-90f8-c7b10cc90121)
> 
> 
> Preview after:
> 
> !\[image\](https://github.com/user-attachments/assets/3223f06e-e9c6-4acb-af20-805f4c720273)
> 
> !\[image\](https://github.com/user-attachments/assets/d6acaa36-79c5-4384-8e96-d3f449393a16)

---

<div class="post-metadata">

### Author: ![king\_laurel](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/king_laurel/32/461779_2.png) [@king\_laurel](https://meta.discourse.org/u/king_laurel)
#### Post date: [15.Ноябрь.2024 15:47:18 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/3 "2024-11-15T15:47:18Z")

</div>

Это выглядит как костыль. Как это работает при навигации с помощью клавиатуры?

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [15.Ноябрь.2024 15:51:43 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/4 "2024-11-15T15:51:43Z")

</div>

Когда ты в пустыне и, казалось бы, нет воды, хаки могут очень пригодиться!

---

<div class="post-metadata">

### Author: ![king\_laurel](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/king_laurel/32/461779_2.png) [@king\_laurel](https://meta.discourse.org/u/king_laurel)
#### Post date: [15.Ноябрь.2024 15:54:26 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/5 "2024-11-15T15:54:26Z")

</div>

Увы, мы не в пустыне… мы платим за использование платформы для размещения публичного сайта.

---

<div class="post-metadata">

### Author: ![awesomerobot](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/awesomerobot/32/142900_2.png) [@awesomerobot](https://meta.discourse.org/u/awesomerobot)
#### Post date: [15.Ноябрь.2024 18:58:55 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/6 "2024-11-15T18:58:55Z")

</div>

Какая конкретно это иконка (где она отображается)? Если есть разумные основания полагать, что это может быть полезно другим, мы можем создать для неё алиас, чтобы разрешить её отдельную замену.

---

<div class="post-metadata">

### Author: ![king\_laurel](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/king_laurel/32/461779_2.png) [@king\_laurel](https://meta.discourse.org/u/king_laurel)
#### Post date: [15.Ноябрь.2024 19:23:11 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/7 "2024-11-15T19:23:11Z")

</div>

Вот один пример, где шестеренка не отражает действие кнопки открытия расширенной страницы поиска:

 ![Screenshot 2024-11-15 at 12.22.11 PM](https://global.discourse-cdn.com/meta/original/4X/8/6/5/8657064300755e868def3ec99399e7c024d2a6b8.png)

---

<div class="post-metadata">

### Author: ![awesomerobot](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/awesomerobot/32/142900_2.png) [@awesomerobot](https://meta.discourse.org/u/awesomerobot)
#### Post date: [19.Ноябрь.2024 21:37:43 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/8 "2024-11-19T21:37:43Z")

</div>

По умолчанию здесь используется иконка “sliders” (и это единственное её использование в Discourse по умолчанию), поэтому в данном случае безопасно использовать `replaceIcon()`.

![image](https://global.discourse-cdn.com/meta/original/4X/e/b/c/ebc46612145eb023c6a2748adb8ded0dc191bdc5.png)

Поскольку у нас нет API для замены отдельных иконок, лучший способ поддержки этой функции сегодня — это рассмотрение запросов на добавление новых алиасов, объединяющих схожие случаи использования.

Например, мы используем `d-liked` как алиас для `heart`, чтобы `replaceIcon()` можно было применить к `d-liked` и изменить иконку в контексте лайков, вместо того чтобы заменять каждое вхождение иконки сердца во всём приложении.

> <https://github.com/discourse/discourse/blob/7d8974d02f7360b324b446868463e950fe92883f/app/assets/javascripts/discourse-common/addon/lib/icon-library.js>

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

---

<div class="post-metadata">

### Author: ![king\_laurel](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/king_laurel/32/461779_2.png) [@king\_laurel](https://meta.discourse.org/u/king_laurel)
#### Post date: [19.Ноябрь.2024 21:54:08 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/9 "2024-11-19T21:54:08Z")

</div>

Круто, похоже, команда Discourse изменила нашу иконку на шестерёнку настроек, так что я всё исправил! Спасибо 🙏

---

<div class="post-metadata">

### Author: ![digitaldominica](https://avatars.discourse-cdn.com/v4/letter/d/97f17d/32.png) [@digitaldominica](https://meta.discourse.org/u/digitaldominica)
#### Post date: [14.Май.2025 13:35:27 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/10 "2025-05-14T13:35:27Z")

</div>

Хорошо, теперь, кажется, понял, каков вердикт по этому вопросу. Грустно осознавать, что это станет доступно только после обновления API, поэтому пока мне придётся использовать тот метод, который я применял ранее.

---

<div class="post-metadata">

### Author: ![digitaldominica](https://avatars.discourse-cdn.com/v4/letter/d/97f17d/32.png) [@digitaldominica](https://meta.discourse.org/u/digitaldominica)
#### Post date: [15.Май.2025 15:10:02 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/11 "2025-05-15T15:10:02Z")

</div>

@awesomerobot Вот обновлённый код того, что я сделал, чтобы изменить иконку создания темы на «+», не затрагивая все остальные «+»:

```plaintext
<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() => {
        const button = document.querySelector("#create-topic");

        if (button) {
            // Временно скрываем кнопку, пока заменяем иконку
            button.style.display = "none";

            // Ищем SVG внутри кнопки
            const oldIcon = button.querySelector("svg");
            if (oldIcon) {
                // Удаляем существующую иконку (SVG)
                oldIcon.remove();
            }

            // Создаём новый SVG для иконки Font Awesome (fa-feather-pointed)
            const newIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
            newIcon.setAttribute("class", "fa d-icon d-icon-feather-pointed svg-icon svg-string");
            newIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg");
            newIcon.innerHTML = '<use href="#feather-pointed"></use>';

            // Вставляем новую иконку внутрь кнопки
            button.insertBefore(newIcon, button.firstChild);

            // Показываем кнопку снова после замены иконки
            button.style.display = "block";
        }
    });
</script>

```

Нужно немного доработать его для замены нескольких иконок:

```plaintext
<script type="text/discourse-plugin" version="0.8">
    api.onPageChange(() => {
        // Список селекторов кнопок и соответствующего кастомного SVG-контента
        const iconsToReplace = [
            { selector: "#create-topic", newSVG: `
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
                    <path d="M12 2L15 5H13V9H11V5H9L12 2ZM3 12L5 10V13H9V15H5V18L3 16V12ZM21 12L19 10V13H15V15H19V18L21 16V12Z"/>
                </svg>
            ` },
            { selector: "#some-other-button", newSVG: `
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
                    <path d="M21 3H3C2.44772 3 2 3.44772 2 4V20C2 20.5523 2.44772 21 3 21H21C21.5523 21 22 20.5523 22 20V4C22 3.44772 21.5523 3 21 3ZM20 19H4V5H20V19Z"/>
                </svg>
            ` },
            { selector: "#another-button", newSVG: `
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
                    <path d="M12 2L8 6H10V10H14V6H16L12 2ZM12 12L8 16H10V20H14V16H16L12 12Z"/>
                </svg>
            ` }
        ];

        iconsToReplace.forEach(icon => {
            const button = document.querySelector(icon.selector);

            if (button) {
                // Временно скрываем кнопку, пока заменяем иконку
                button.style.display = "none";

                // Ищем существующий SVG внутри кнопки
                const oldIcon = button.querySelector("svg");
                if (oldIcon) {
                    // Удаляем существующую иконку (SVG)
                    oldIcon.remove();
                }

                // Создаём новый элемент SVG и устанавливаем его содержимое
                const newIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
                newIcon.innerHTML = icon.newSVG; // Устанавливаем кастомный SVG-контент

                // Вставляем новый SVG-иконку внутрь кнопки
                button.insertBefore(newIcon, button.firstChild);

                // Показываем кнопку снова после замены иконки
                button.style.display = "block";
            }
        });
    });
</script>

```

---

<div class="post-metadata">

### Author: ![Don](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/don/32/228726_2.png) [@Don](https://meta.discourse.org/u/Don)
#### Post date: [15.Май.2025 20:41:48 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/12 "2025-05-15T20:41:48Z")

</div>

Вы также можете использовать `iconHTML` вместо ручного добавления иконки.  
Что-то вроде этого должно сработать:

```js
import { apiInitializer } from "discourse/lib/api";
import { iconHTML } from "discourse/lib/icon-library";

export default apiInitializer((api) => {
  api.onPageChange(() => {
    const btnIcon = document.querySelector("#create-topic .d-icon");
    if (btnIcon) {
      btnIcon.outerHTML = iconHTML("plus");
    }
  });
});

```

---

<div class="post-metadata">

### Author: ![merefield](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/merefield/32/176214_2.png) [@merefield](https://meta.discourse.org/u/merefield)
#### Post date: [15.Май.2025 20:50:21 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/13 "2025-05-15T20:50:21Z")

</div>

Это может оказаться ненадёжным.

Полагаю, api.onPageChange срабатывает при смене маршрута.

Это происходит задолго до рендеринга.

Следовательно, существует риск, что элементы не успеют отрендериться, прежде чем вы попытаетесь найти и переопределить их.

Вам может повезти, но это не гарантировано. Именно поэтому awesomerobot предложил, что было бы неплохо иметь отдельный API…

(@Don, ваше решение, как я полагаю, имеет схожую проблему)

---

<div class="post-metadata">

### Author: ![Don](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/don/32/228726_2.png) [@Don](https://meta.discourse.org/u/Don)
#### Post date: [16.Май.2025 06:18:24 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/14 "2025-05-16T06:18:24Z")

</div>

Да, ты прав! Это не слишком стабильно. Может быть, с помощью `requestAnimationFrame` или `MutationObserver` это станет стабильнее? 🤔 Но да, лучшим решением было бы наличие отдельного API, как ты и упоминал.

---

<div class="post-metadata">

### Author: ![digitaldominica](https://avatars.discourse-cdn.com/v4/letter/d/97f17d/32.png) [@digitaldominica](https://meta.discourse.org/u/digitaldominica)
#### Post date: [16.Май.2025 22:18:37 UTC](https://meta.discourse.org/t/change-single-instance-of-icon/336356/15 "2025-05-16T22:18:37Z")

</div>

Спасибо, что обратили на это внимание — вы абсолютно правы. Использование только api.onPageChange может быть ненадёжным, так как оно срабатывает при смене маршрута, а не после рендеринга DOM. Это создаёт риск состояния гонки, когда кнопка #create-topic может ещё не существовать в момент, когда скрипт пытается получить к ней доступ.

```plaintext
api.onPageChange(() => {
        const targetSelector = "#create-topic";

        const tryEnhanceButton = () => {
            const button = document.querySelector(targetSelector);

            if (button) {
                // Отключаем наблюдатель после нахождения элемента
                observer.disconnect();

                // Временно скрываем кнопку, пока мы заменяем иконку
                button.style.display = "none";

                // Удаляем старую иконку, если она есть
                const oldIcon = button.querySelector("svg");
                if (oldIcon) oldIcon.remove();

                // Создаём и вставляем новую SVG-иконку Font Awesome
                const newIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
                newIcon.setAttribute("class", "fa d-icon d-icon-feather-pointed svg-icon svg-string");
                newIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg");
                newIcon.innerHTML = '<use href="#feather-pointed"></use>';

                button.insertBefore(newIcon, button.firstChild);

                // Снова показываем кнопку
                button.style.display = "block";
            }
        };

        // Наблюдаем за изменениями в DOM и пытаемся улучшить кнопку, когда она появится
        const observer = new MutationObserver(() => tryEnhanceButton());
        observer.observe(document.body, { childList: true, subtree: true });

        // Также пробуем сразу, на случай если элемент уже существует
        tryEnhanceButton();
    });

```

@Don Я добавил MutationObserver, чтобы гарантировать, что скрипт выполнится только после того, как целевой элемент действительно появится в DOM. После выполнения своей задачи наблюдатель отключается, чтобы избежать лишних накладных расходов. Также сохраняется попытка немедленной проверки на случай, если элемент уже существует.

Мне не удалось получить нужную SVG-иконку из системы иконок Discourse, хотя я зарегистрировал её в подмножестве административных SVG-иконок, поскольку, похоже, она есть в Font Awesome, но отсутствует в Discourse.
