Discourse 기본 SVG 아이콘을 테마에서 사용자 지정 아이콘으로 교체

Discourse의 기본 SVG 아이콘을 개별적으로 또는 전체를 하나의 세트로 자체 제작한 SVG로 교체하고, 테마 또는 테마 컴포넌트 내에서 오버라이드할 수 있습니다.

1단계 - SVG 스프라이트시트 생성

시작하려면 SVG 스프라이트시트를 생성해야 합니다. 이 시트에는 추가적인 커스텀 SVG 아이콘 1개부터 수백 개에 이르는 전체 교체 세트까지 무엇이든 포함될 수 있습니다.

스프라이트시트는 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를 추가하는 것을 잊지 마세요. 정신 건강을 위해 ID 앞에 테마 이름(예: my-theme-icon)을 접두사로 붙이는 것이 도움이 될 수 있습니다.

  • 기존 아이콘처럼 아이콘 색상이 동적으로 변경되도록 하려면, 하드코딩된 색상(예: #333) 대신 fill을 currentColor로 설정하세요.

  • 아이콘을 올바르게 확장하거나 중앙 정렬하려면 <symbol> 태그에 viewBox 속성을 사용하세요. 자세한 내용은 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 파일을 컴포넌트/테마에 추가해야 합니다. UI를 통해 쉽게 수행할 수 있으며, 컴포넌트/테마에 하드코딩하는 것도 가능합니다.

:information_source: 설치된 컴포넌트/테마 중 어디에든 업로드되면, <symbol> 태그의 ID를 사용하여 인스턴스 전체에서 사용할 수 있게 됩니다.

UI를 통한 방법

테마/컴포넌트 설정의 업로드(Uploads) 섹션으로 이동하여 icons-sprite라는 SCSS 변수 이름을 가진 스프라이트 파일을 추가하세요:

테마/컴포넌트에 하드코딩

스프라이트시트 파일을 테마의 /assets 폴더에 추가합니다. 그런 다음 루트 폴더의 assets.json 파일을 업데이트하세요.
my-icons.svg라는 이름의 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");
  // etc.
});

첫 번째 ID인 bars는 Discourse의 기본 아이콘 ID이고, 두 번째는 대체 아이콘의 ID입니다. 아이콘 ID를 찾는 가장 쉬운 방법은 브라우저에서 아이콘을 검사하는 것입니다.

여기서 아이콘 이름은 d-icon- 접두사를 따릅니다. 따라서 이 예시에서는 d-unliked가 됩니다.

대부분의 아이콘은 https://fontawesome.com/ 의 아이콘 이름을 따르지만, 예외도 있습니다(이것이 인스펙터에서 ID를 확인하는 것이 가장 신뢰할 수 있는 방법인 이유입니다). 모든 예외는 github에서의 const REPLACEMENTS 블록에서 확인할 수 있습니다.

이것으로 끝입니다. 이제 자체 제작한 커스텀 아이콘으로 Discourse를 스타일링할 수 있습니다!


이 문서는 버전 관리됩니다 - github에서 변경 사항을 제안하세요.

58개의 좋아요

특정 요소 안의 특정 아이콘을 어떻게 표적화하나요? 제 경우, 사이드바 메뉴의 Docs 아이콘을 다른 FA 아이콘으로 교체하고 싶습니다.

1개의 좋아요

CSS로 이를 숨기고 새로운 버튼을 추가하겠습니다.

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

여기에서 가져온 내용인데, 여전히 작동하지 않는 것 같습니다. 이것도 미리보기 링크에서 작동하지 않기 때문에, 스크립트 태그 방식이 깨진 것 같습니다. 솔직히 확신이 서지 않습니다.

1개의 좋아요

제겐 잘 작동해요 :woman_shrugging:t2:

head 탭에 넣으시는 건가요? 저는 헤더에서 robot을 교체하기도 해요:

관리자 SVG icon subset 설정에 아이콘을 추가해야 할 수도 있습니다.

2개의 좋아요

네, head 탭이요. 그리고 가이드에 그렇게 되어 있어서 header 탭에도 넣었습니다.

완료했습니다. 이제 작동합니다. 감사합니다!

1개의 좋아요

@NateDhaliwal 개인 메시지를 보내주실 수 있을까요? 도움이 필요한 게 있는데, 프로필에서 채팅 옵션을 찾을 수가 없어요. 감사합니다!

1개의 좋아요
왼쪽 메뉴에서 Audi 카테고리 안의 prefix-span 클래스가 있는 요소의 배경을 다시 정의합니다 */
.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 변수 이름, 그리고 심볼 ID 사이의 관계를 설명해 주실 수 있을까요?

모더레이터의 shield-halved 아이콘을 자체 아이콘으로 교체하려고 하는데, 설명이 다소 불분명합니다.

2단계에서:

  • “UI를 통해” 스크린샷에는 파일명이 baticonsprite.svg이고 SCSS 변수 이름이 icons-sprite로 표시되어 있습니다.
  • 하지만 “테마에 하드코딩” 부분에서는 테마/컴포넌트에 하드코딩하라고 안내합니다.
    • 그런데 어떻게 해야 하는 건가요? 편집기에서 assets.json 파일을 찾을 수 없습니다. 컴포넌트를 내보내면 about.json 파일이 보이며, 여기에는 UI를 통해 업로드한 스프라이트가 표시되어 있습니다.
    • 하지만 이 예시에는 /assets/my-icons.svg라는 다른 파일명이 나와 있습니다. 이는 baticonsprite.svg와 동일한 파일을 의미하는 건가요?
    • 이 두 가지 방법은 동일한 작업을 수행하는 대안이고, 둘 중 하나만 수행하면 되는 건가요?

3단계에서:

  • 그런데 이제 api.replaceIcon()에서 두 번째 매개변수는 이전의 어떤 ID도 사용하지 않습니다. icons-sprite, bat-icon, baticonsprite.svg, my-icons.svg도 아닙니다. 대신 완전히 새로운 my-theme-icon-bars가 나옵니다… 혼란스럽습니다.
    • my-theme 접두사는 필수적인가요? 그렇다면 그 “테마 이름” 문자열은 어디에서 오는 건가요. my-theme-bat-icon이어야 하나요? 그리고 만약 컴포넌트인 경우 테마가 아닌 경우엔 어떻게 되나요?
    • 그리고 icon-bars 부분은 다음 중 무엇이어야 하나요?
      • SVG 스프라이트 시트 XML의 심볼 ID
      • SVG 파일의 파일명
      • 부여하는 SCSS 변수 이름
      • 위의 조합 (예: icons-sprite-bat-icon?)

그리고 실제로 api.replaceIcon() 호출은 어디에 넣어야 하나요? 이미 다음 보일러플레이트가 있는 커스텀 컴포넌트의 “JS” 탭에 넣는 것이 괜찮나요?

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

export default apiInitializer((api) => {
   // your code here
});

아니면 커스텀 <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")
});

UI를 통해 파일을 업로드할 경우, 두 번째 ID(my-logo)가 어디에서 오는지 잘 모르겠습니다:

$my-logo, $test, my-logo.svg, 디스크에 있는 SVG의 절대 URL 등 아무것도 동작하지 않습니다. 방패 아이콘은 대체되긴 하지만, 아무것도 표시되지 않습니다. SVG가 내용 없이 빈 <use href="#my-logo"> 태그만 남게 됩니다.

1개의 좋아요

드디어 해결했습니다 (클로드에게 감사드립니다)!

즉:

  1. SVG 파일명은 중요하지 않습니다.

  2. SVG 스프라이트 시트 내부에서는 심볼 id가 최종 아이콘 이름을 결정합니다. 예를 들어 <symbol id=”my-logo” …>

  3. 하지만 SCSS 변수 이름은 반드시 icons-sprite여야 합니다. 최종 스프라이트 시트 ID와 관련이 있는 이름으로는 절대 안 됩니다:

  4. 업로드 후 이 모습이 됩니다:


    (스프라이트 시트/변수 이름은 어디에도 보이지 않는다는 점을 주목하십시오. 이전 단계에서 입력한 내용에 $가 자동으로 붙어 $icons-sprite가 되어야 합니다.)

  5. 마지막으로, 테마의 “JS” 탭(<head>가 아님)에서 1단계의 스프라이트 시트 ID를 사용합니다:

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

즉, SCSS 변수 이름은 반드시 icons-sprite여야 하고, 파일명은 중요하지 않으며, API 아이콘 이름은 스프라이트 시트 내부의 심볼 ID에 의해 결정됩니다.

3개의 좋아요