I need to change single instances of icons throughout the site. I do not want to use replaceIcon() and override all instances of that icon. I see in the past they made this possible for specific areas of the site, or that is how it reads, but not throughout the site.
For instance, I want to change ONE of the cog icons in search to better reflect what happens when the user clicks it. I do not want to change all cog icons.
Which icon is this specifically (where does it appear)? if it seems like there’s a reasonable case others could find useful, we can create an alias for it to allow it to be replaced separately.
We use a “sliders” icon here by default (and it’s the only occurrence of this icon in Discourse by default) — so it would be safe to use replaceIcon() in this case
Because we don’t have an API to replace individual icons, the best way we can support this today is by evaluating requests to add new aliases that group similar use cases.
So for example, we use d-liked as an alias for heart so replaceIcon() can be used against d-liked to change it in the like context, rather than replacing every occurrence of the heart icon throughout the app.
It would be nice to replace any single occurrence though, that’s not an unusual situation in themes — hopefully someday we’ll have an API for that.
Okay I seem to understand now what’s the verdict on this. Sad to know it’s not available until an API update so I’ll have to use the method I’ve done for the time being.
@awesomerobot Here’s an updated code of what I’ve done to change the create topic icon which is a + without global change to all +
<script type="text/discourse-plugin" version="0.8">
api.onPageChange(() => {
const button = document.querySelector("#create-topic");
if (button) {
// Temporarily hide the button while we replace the icon
button.style.display = "none";
// Look for the SVG inside the button
const oldIcon = button.querySelector("svg");
if (oldIcon) {
// Remove the existing icon (SVG)
oldIcon.remove();
}
// Create a new SVG for the Font Awesome icon (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>';
// Insert the new icon inside the button
button.insertBefore(newIcon, button.firstChild);
// Show the button again after icon replacement
button.style.display = "block";
}
});
</script>
Have to refine it a bit more for multiple icon changes
<script type="text/discourse-plugin" version="0.8">
api.onPageChange(() => {
// List of button selectors and corresponding custom SVG content
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) {
// Temporarily hide the button while we replace the icon
button.style.display = "none";
// Look for the existing SVG inside the button
const oldIcon = button.querySelector("svg");
if (oldIcon) {
// Remove the existing icon (SVG)
oldIcon.remove();
}
// Create a new SVG element and set its content
const newIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
newIcon.innerHTML = icon.newSVG; // Set the custom SVG content
// Insert the new SVG icon inside the button
button.insertBefore(newIcon, button.firstChild);
// Show the button again after icon replacement
button.style.display = "block";
}
});
});
</script>
Yeah, you’re right! This is not too stable. Maybe it can be more stable with requestAnimationFrame or MutationObserver? but yeah the best would be a dedicated api like you mentioned.
Thanks for pointing that out — you’re absolutely right. Using api.onPageChange alone can be unreliable since it fires on route change, not after DOM rendering. That means there’s a race condition risk where the #create-topic button might not exist yet when the script tries to access it.
api.onPageChange(() => {
const targetSelector = "#create-topic";
const tryEnhanceButton = () => {
const button = document.querySelector(targetSelector);
if (button) {
// Disconnect observer after finding the element
observer.disconnect();
// Temporarily hide the button while we replace the icon
button.style.display = "none";
// Remove old icon if exists
const oldIcon = button.querySelector("svg");
if (oldIcon) oldIcon.remove();
// Create and insert new Font Awesome SVG icon
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);
// Show the button again
button.style.display = "block";
}
};
// Observe changes to the DOM and try to enhance the button when it's added
const observer = new MutationObserver(() => tryEnhanceButton());
observer.observe(document.body, { childList: true, subtree: true });
// Try immediately as well in case it's already there
tryEnhanceButton();
});
@Don I’ve included MutationObserver to ensure the script only runs once the target element actually exists, it disconnects itself after doing its job to avoid unnecessary overhead. It still attempts an immediate check in case the element already exists.
I’m unable to fetch the svg I want from the Discourse icon system, although i registered it admin SVG icon subset as it seems it’s in fontawesome but not in discourse.