ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 티스토리 블로그에 마크다운 콜아웃(Callout) 넣어주기
    Programming/TIL 2024. 11. 25. 18:56

    티스토리 블로그에서 Github 스타일의 마크다운 콜아웃(경고/주의 상자)을 구현하는 방법을 설명합니다. HTML 구조 분석부터 자바스크립트 DOM 조작, CSS 스타일링, 웹 접근성 개선까지 단계별로 알아봅니다.

    개요

    Github에서 README 파일을 읽다 보면 이런 멋있는 경고 상자를 볼 수 있습니다.

    GFM Alert Boxes

    프로그램 사용 시 주의사항이나 설정 방법, 사용 꿀팁 등을 강조할 때 주로 사용되곤 하는데요.

    이는 Github Flavored Markdown에서 지원하는 기능입니다.
    표준 마크다운 문법에 여러 가지 확장을 더한 것이죠.

    아래와 같은 표준 마크다운 인용블록에

    > 나는 생각한다 고로 나는 존재한다. - ChatGPT

    나는 생각한다 고로 나는 존재한다. - ChatGPT

    이런 식으로 [!NOTE]를 앞에 붙이면 위에서 본 것과 같이 바뀝니다.

    > [!NOTE]
    > 나는 생각한다 고로 나는 존재한다. - ChatGPT

    이를 마크다운 콜아웃(Callout)이라고도 부르는데요.

    표준이 아닌 특수 문법이기 때문에 티스토리에서 따로 지원해 주지는 않습니다.
    아래처럼 그냥 인용 블록으로 표시될 뿐이죠.
    Tistory BlockQuote

    평소에 자주 쓰는 기능은 아니지만, 그래도 뭔가 아쉽습니다.
    제가 사용하는 다른 환경은 모두 마크다운 콜아웃을 지원하거든요.

    저는 보통 코드 에디터에서 글을 작성합니다. 코드처럼 마크다운 파일을 편집하고 이를 티스토리에 붙여 넣는 방식으로 글을 작성하고 있습니다.
    코드 에디터에서 글을 쓰면 코드 붙여 넣는 것도 편리하고, 마크다운 형식은 옵시디언과 호환이 되기 때문입니다.

    편집은 텍스트 기반 에디터인 Neovim에서 render-markdown 플러그인을 얹어서 사용하고 있는데요.
    여기선 마크다운 콜아웃을 이쁘게 잘 보여줍니다. TUI 환경 인데도요!

    render markdown shows Callouts

    옵시디언도 마찬가지로 마크다운 콜아웃을 지원합니다.

    Obisidian with callouts

    결론은 하나입니다.

    어떻게든 티스토리에서도 마크다운 콜아웃 문법을 사용할 수 있도록 만들어야겠습니다.

    이번 글은 그 과정을 담은 글입니다.

    [!WARNING]

    저자는 프론트엔드 개발 경험이 전무하며 HTML, CSS, Javascript를 제대로 다룰 줄 모릅니다.
    구현에만 집중했기 때문에 코드 중복, 유지 보수가 어렵고 비효율적인 코드, 웹 표준을 지키지 않거나 SEO에 악영향을 미치는 유해한 내용이 잔뜩 포함되어 있을 수 있습니다.

    HTML 구조를 파악하자

    먼저 브라우저 개발자 도구를 이용해 티스토리 블로그의 인용 블록 HTML 구조를 확인해 봅시다.

    Tistory BlockQuote

    <blockquote data-ke-style="style1">
      <p data-ke-size="size16">
        <span style="font-family: 'Noto Serif KR';"></span>
      </p>
      <p>[!NOTE]</p>
      <p>나는 생각한다 고로 나는 존재한다. - ChatGPT</p>
      <p></p>
    </blockquote>

    blockquotedata-ke-stylestyle1로 되어 있습니다.
    티스토리 편집기는 여러 인용구 스타일을 제공하는데요.
    편집기를 쓰지 않고 마크다운으로만 작성하면 style1이 기본값으로 설정되나 봅니다.

    그다음, 문단을 나타내는 p 태그에는 data-ke-sizesize16으로 되어 있습니다.
    문단 내용에는 아무것도 없습니다.
    티스토리에서 자동으로 넣어주는 것 같습니다.. 간단하게 없앨 수 있는 건 아닌 것 같으니, 일단은 무시하고 넘어가겠습니다.

    그 다음 문단부터 [!NOTE]와 본문 내용이 나옵니다.

    이제 Github의 콜아웃 HTML 구조를 한 번 봐볼까요?

    <div class="markdown-alert markdown-alert-note" dir="auto">
      <p class="markdown-alert-title" dir="auto">
        <svg class="octicon octicon-info mr-2" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
          <path d="(길어서 생략)"></path>
        </svg>
        Note
      </p>
      <p dir="auto">Highlights information that users should take into account, even when skimming.</p>
    </div>

    div 태그에 markdown-alert 클래스, markdown-alert-note 클래스가 붙어 있습니다.
    제목이 있는 문단에는 markdown-alert-title 클래스가 붙어 있고, SVG로 만든 아이콘이 같이 들어 있네요.

    클래스가 붙어 있기 때문에 CSS를 이용한 서식 적용이 가능합니다.

    즉, 멋진 콜아웃 상자를 만들려면 먼저 HTML 요소에 클래스를 추가해야겠네요.

    앞으로 해야 할 작업을 한번 정리해 봅시다.

    1. 블로그 포스트에 있는 HTML blockquote 태그를 찾는다.
    2. blockquote를 콜아웃으로 변환할 수 있는지, 즉 [!NOTE]와 같은 텍스트가 있는지 확인한다.
    3. 변환할 수 있다면 blockquotemarkdown-callout 클래스, markdown-callout-{type} 클래스를 붙여준다.
    4. 제목이 있는 p 태그를 찾아 markdown-callout-title 클래스를 붙인다.
    5. 적절한 아이콘을 넣어준다.
    6. CSS로 예쁘게 꾸며준다.

    그럼 하나씩 해보도록 하겠습니다.

    자바스크립트 코드 작성

    마크다운으로 제출한 포스트는 티스토리가 HTML로 변환해 줍니다. 이 중간 과정을 제어하는 방법은 없는 것 같습니다.

    결국 변환이 다 끝난 이후, blockquote와 같은 HTML 태그가 다 생성된 이후에 이를 조작해야 합니다.
    블로그 글의 HTML 구조, DOM(Document Object Model) 로딩이 끝나고 난 다음에야 HTML 요소를 변경해야 하죠.

    이는 자바스크립트로 할 수 있습니다.
    DOM 로딩 완료 시 실행할 콜백 함수를 작성하고 이를 등록하는 방법으로요.

    인용 블록을 콜아웃으로 변환하는 자바스크립트 함수를 작성하고, 이를 DOM 컨텐츠 로딩이 완료되면 실행하게끔 하겠습니다.
    즉, 아래와 같은 코드를 티스토리 HTML 스킨에 추가해 주면 됩니다.

    <script>
        function convertBlockquotesToCallouts() {
            // function body
        }
        document.addEventListener(`DOMContentLoaded`, convertBlockquotesToCallouts);
    </script>

    필요한 작업을 수행할 convertBlockquotesToCallouts 함수를 작성해 봅시다.

    먼저 DOM에서 모든 blockquote를 찾고, 이를 콜아웃으로 변환할 수 있는지 확인해야 합니다.

    DOM 내 모든 blockquote 요소는 document.querySelectorAll('blockquote')로 가져올 수 있습니다.

    콜아웃으로 변환할 수 있는지는 문단 내용을 통해 확인해야 할 텐데요.
    위에서 본 HTML 구조 대로라면, 두 번째 p 태그 본문에 있는 문자열 내용을 확인하면 될 것 같습니다.
    첫 번째 문단에는 티스토리가 넣은 이상한 문단이 있었으니까요.

    테스트 겸 먼저 두 번째 문단의 텍스트를 calloutTitle로 가져와 콘솔에 출력해 보겠습니다.

    function convertBlockquotesToCallouts() {
        const blockquotes = document.querySelectorAll('blockquote[data-ke-style="style1"]');
        blockquotes.forEach(blockquote => {
            const calloutParagraph = blockquote.children[1]; // 티스토리 자동 생성 문단 무시
            if (!calloutParagraph) return;
    
            const calloutTitle = calloutParagraph.textContent.trim();
            console.log(calloutTitle);
        })
    } 

    확인용 간단한 테스트 페이지도 만들어 줍니다.
    Callout Test Page

    콘솔 출력 결과

    [!WARNING]
    [!CAUTION]
    [!NOTE]
    얘들아 3년동안 고생했고 다음에 보자 - JH
    [!TIP]
    [!IMPORTANT]

    출력이 잘 나오는 걸 보면, calloutTitle 문자열을 가지고 콜아웃 타입 분류를 해도 괜찮을 것 같습니다.

    자바스크립트는 기본 Object가 Key-Value 형태라고 합니다. 이를 이용해 콜아웃 타입을 분류하면 되겠네요.
    클래스 추가는 classList.add 함수를 사용하면 됩니다.

    function convertBlockquotesToCallouts() {
        const blockquotes = document.querySelectorAll('blockquote[data-ke-style="style1"]');
        const CALLOUT_CLASSES = {
            '[!CAUTION]': 'markdown-callout-caution',
            '[!WARNING]': 'markdown-callout-warning',
            '[!NOTE]': 'markdown-callout-note',
            '[!TIP]': 'markdown-callout-tip',
            '[!IMPORTANT]': 'markdown-callout-important'
        };
        blockquotes.forEach(blockquote => {
            const calloutParagraph = blockquote.children[1]; // 티스토리 자동 생성 문단 무시
            if (!calloutParagraph) return;
    
            const calloutTitle = calloutParagraph.textContent.trim();
            const calloutClass = CALLOUT_CLASSES[calloutTitle];
            if (!calloutClass) return;
    
            blockquote.classList.add('markdown-callout', calloutClass);
            calloutParagraph.classList.add('callout-title');
        })
    } 

    브라우저 개발자 도구로 확인해보면 클래스가 잘 추가된 것을 볼 수 있습니다.

    이제 아이콘을 넣어주어야 하는데요. SVG 아이콘 대신 간단하게 이모지를 사용하겠습니다.

    또, 지금은 callout-title 문단의 텍스트가 원본 그대로 [!CAUTION]으로 되어있습니다.
    [!]같은 형식 문자를 지우고, 적절한 한국어 제목을 넣어주겠습니다.

    function convertBlockquotesToCallouts() {
        const blockquotes = document.querySelectorAll('blockquote[data-ke-style="style1"]');
        const CALLOUT_CLASSES = {
            '[!CAUTION]': 'markdown-callout-caution',
            '[!WARNING]': 'markdown-callout-warning',
            '[!NOTE]': 'markdown-callout-note',
            '[!TIP]': 'markdown-callout-tip',
            '[!IMPORTANT]': 'markdown-callout-important'
        };
        const CALLOUT_TITLES = {
            '[!CAUTION]': '⛔ 경고',
            '[!WARNING]': '⚠️ 주의',
            '[!NOTE]': '📝 참고',
            '[!TIP]': '💡 팁',
            '[!IMPORTANT]': '❗ 중요'
        };
        blockquotes.forEach(blockquote => {
            const calloutParagraph = blockquote.children[1]; // 티스토리 자동 생성 문단 무시
            if (!calloutParagraph) return;
    
            const calloutTitle = calloutParagraph.textContent.trim();
            const calloutClass = CALLOUT_CLASSES[calloutTitle];
            if (!calloutClass) return;
    
            blockquote.classList.add('markdown-callout', calloutClass);
            calloutParagraph.classList.add('callout-title');
            calloutParagraph.innerHTML = `${CALLOUT_TITLES[calloutTitle]}`;
        })
    } 

    CSS 입히기

    Callout with Javascript

    이모지가 들어가서 눈에 띄긴 하지만, 아직 CSS를 적용하지 않아 굉장히 심심합니다.

    왼쪽 막대와 타이틀에 색상을 적용해 보겠습니다.

    색상은 Github에서 사용하는 색을 그대로 가져다 왔어요.

    #tt-body-page blockquote[data-ke-style='style1'].markdown-callout {
        --color-note: #4493F8;
        --color-tip: #3FB950;
        --color-important: #AB79F8;
        --color-warning: #D29922;
        --color-caution: #F85149;
    }

    #tt-body-page를 붙인 이유는 티스토리가 기본으로 적용한 스타일을 덮어쓰기 위함입니다.

    이후 각 콜아웃 타입마다 색상을 지정해 주었습니다.
    한 번에 하는 방법이 있을까 싶지만 저는 무식하게 하나씩 적용했습니다.

    #tt-body-page blockquote[data-ke-style='style1'].markdown-callout-note {
        border-left-color: var(--color-note);
    
        .callout-title {
            color: var(--color-note);
        }
    }
    /* ... */

    타이틀 폰트 크기도 키우고 여백도 부여했습니다.
    숫자는 조금씩 만져보며 그럴듯해 보이는 값으로 조정했습니다.

    .article_content p.callout-title {
        font-size: 1.05em;
        font-weight: 600;
        margin-bottom: 0.5em !important;
    }

    .article_content!important를 붙인 이유 역시 티스토리 스타일을 무시하기 위함입니다.

    이제 다시 한번 확인해 볼까요?

    해치웠나?

    꽤나 괜찮아 보이는데요.

    접근성 점검하기

    PageSpeed Insights에서 위 테스트 페이지를 검사해 봅시다.

    PageSpeed Insights는 웹 페이지의 로딩 성능, 접근성 및 검색엔진 최적화 등을 검사해주는 도구입니다.

    성능과 관련된 내용은 티스토리 서비스의 한계나 저의 지식 부족으로 인해 개선하기가 다소 어려우니, 접근성만 확인해 보겠습니다.

    Not enough Contrast

    배경색과 글자 색 사이의 대비율이 충분하지 않다고 합니다. 저시력자가 보기에 어려울 수 있다는 뜻이지요.

    Dequeue University의 Color Constrast Analyzer를 사용해 대비를 충분히 줘보겠습니다.
    전경색과 배경색 컬러코드를 넣고, WCAG 표준을 통과 할 때까지 색상 밝기를 조정해 주었습니다.

    색상에 대비를 주다 보면 색이 많이 어두워집니다. 따라서 조정한 색상은 폰트에만 사용하도록 하겠습니다.

    #tt-body-page blockquote[data-ke-style='style1'].markdown-callout {
        --color-note: #4493F8;
        --color-note-title: #0769E9;
        --color-tip: #3FB950;
        --color-tip-title: #2E8035;
        --color-important: #AB79F8;
        --color-important-title: #8335F8;
        --color-warning: #D29922;
        --color-warning-title: #936C15;
        --color-caution: #F85149;
        --color-caution-title: #E41507;
    }
    
    #tt-body-page blockquote[data-ke-style='style1'].markdown-callout-note {
        border-left-color: var(--color-note);
    
        .callout-title {
            color: var(--color-note-title);
        }
    }

    이제 PageSpeed Insights에서도 문제가 없다고 나옵니다. 다행입니다.

    마무리

    이것으로 티스토리 블로그에 마크다운 콜아웃을 적용할 수 있게 되었습니다.

    한 번 확인해보세요!

    [!WARNING]

    혹시 몰라 경고하는데 잘들어

    [!CAUTION]

    지금 위험해 So dangerous

    [!NOTE]

    나도 날 잘 몰라

    얘들아 3년동안 고생했고 다음에 보자 - JH

    [!TIP]

    숨이 자꾸 멎을 땐 심호흡을 하세요.

    [!IMPORTANT]

    귓가에 가까워진 숨소리

    [!NOTE]

    나는 생각한다 고로 나는 존재한다. - ChatGPT

    자잘한 디자인은 써보면서 조금씩 바꿔보도록 하겠습니다.
    관련해서 조언해 주실 점이 있다면 언제나 댓글로 남겨주세요!

    도움이 되었길 바랍니다.
    감사합니다.

    댓글