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

Верните к жизни этот старый пост: Changing a single instance of an icon

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

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

Спасибо!

1 лайк

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

3 лайка

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

2 лайка

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

2 лайка

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

2 лайка

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

2 лайка

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

1 лайк

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

image

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

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

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

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

2 лайка

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

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

<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>

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

<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>

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

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");
    }
  });
});
1 лайк

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

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

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

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

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

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

2 лайка

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

2 лайка

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

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.

1 лайк