soma0sd

코딩 & 과학 & 교육

오늘의 스킨제작: highlight.js, 구문강조 박스 꾸미기

반응형

목표

```python
print("python")

마크다운 문법으로 코드 블록을 사용한 예시입니다. 티스토리를 기준으로 마크다운 코드 블록은 다음과 같이 변환됩니다.

```html
<pre>
  <code class="language-python">
    print("python")
  </code>
</pre>

구문 강조 도구인 highlight.js를 사용하면 구문 강조를 위한 태그와 도구가 다녀갔다는 흔적인 클래스를 남기게 됩니다.

<pre>
  <code class="language-python hljs">
    print(<span class="hljs-string">"python"</span>)
  </code>
</pre>

오늘은 변환한 코드블럭이 어떤 언어를 사용하는지 보여주는 박스를 추가했습니다.

사용한 트릭 & 팁

티스토리 외부 자바스크립트에서 스킨 URL을 다루는 방법

images에 추가하는 스킨 파일은 skin.html에서는 상대경로 표기로 접근할 수 있지만 외부에서 불러오는 스크립트 파일은 변환 대상이 아니기 때문에 곤란합니다. 그래서 한 가지 트릭을 사용합니다.

<meta name="skin_var" id="skin_root_url" content="./images">

이렇게 <head>태그 안에 위의 메타태그를 사용하면 id 속성을 이용해 손쉽게 접근할 수 있습니다.

var root_url = document.querySelector('#skin_root_url').getAttribute('content');

ES6의 템플릿 문자열

ECMAScript 6(ES6), 혹은 ECMAScript 2015라고 불리는 자바스크립트 표준이 있습니다. jQuery만 주구장창 해왔던 제가 '어라 이거 더 이상 안 써도 괜찮겠는데?'라는 생각이 들게 한 여러 기능과 문법이 추가되었습니다. 그중 하나가 지금 다루는 템플릿 문자열입니다.

템플릿 문자열은 `(억음부호, 보통 탭 위에 있습니다.)로 묶습니다. 줄 바꿈을 위해서 \n을 사용하지 않아도 되는데다 ${변수}로 값을 포함할 수 있으니 결과물에 가까운 입력 문자열을 볼 수 있다는 편리함이 큰 장점입니다.

var foo = 600;
var bar = 'bar';
var temp_str = `<div><ul>
<li>${foo}</li>
<li>${bar}</li>
</ul></div>`;

// 출력
// <div><ul>
// <li>600</li>
// <li>bar</li>
// </ul></div>

투명한 배경의 png 이미지에 그림자 입히기

CSS에서 그림자가 필요하다고 하면 보통은 box-shadow속성을 사용하게 됩니다.하지만 배경이 투명한 PNG 등에서는 원하는 그림자가 아닌 사각형으로 붕 뜬 그림자만 보게 되는데요.

이때 사용할 수 있는 속성이 filter속성의 drop-shadow입니다. 모질라 재단의 데모 통해 효과를 직접 살펴보고 어떻게 사용하는지를 살펴볼 수 있습니다.

filter: drop-shadow(0px 0px 3px rgba(255,255,255,0.6));

결과물

이제 당분간은 코드 블록이랑 씨름하지 않을 생각입니다.

<pre>
  <code class="language-python hljs">
    print(<span class="hljs-string">"python"</span>)
  </code>
</pre>

위의 내용이 아래의 내용으로 바뀌게 됩니다.

<pre class="ss_codeblock_wrapper">
  <code class="language-python hljs">
    소스코드 내용
  </code>
  <div class="ss_codeblock_menubar">
  <img class="logo" src="https://tistory3.daumcdn.net/tistory/1798630/skin/images/language-python.png">
  <span class="lang">Python</span>
  </div>
</pre>

소스코드

<!-- 스크립트용 스킨 URL -->
<meta name="skin_var" id="skin_root_url" content="./images">
var root_url = document.querySelector('#skin_root_url').getAttribute('content');

document.addEventListener('DOMContentLoaded', (event) => {
    document.querySelectorAll('pre code').forEach((block) => {
        hljs.highlightBlock(block);
        plugin_codeblock(block);
    });
});

var code_langs_dict = {
    'language-apache': 'APACHE',
    'language-bash': 'BASH',
    'language-coffeescript': 'CoffeeScript',
    'language-cpp': 'C++',
    'language-cs': 'C#',
    'language-css': 'CSS',
    'language-go': 'Go',
    'language-ini': 'INI(TOML)',
    'language-java': 'Java',
    'language-javascript': 'JavaScript',
    'language-json': 'JSON',
    'language-kotlin': 'Kotlin',
    'language-less': 'Less',
    'language-lua': 'Lua',
    'language-xml': 'XML',
    'language-markdown': 'Markdown',
    'language-nginx': 'NGINX',
    'language-objectivec': 'Objective-C',
    'language-perl': 'Perl',
    'language-php': 'PHP',
    'language-plaintext': 'TEXT',
    'language-python': 'Python',
    'language-ruby': 'Ruby',
    'language-rust': 'Rust',
    'language-scss': 'SASS(SCSS)',
    'language-shell': 'Shell',
    'language-sql': 'SQL',
    'language-swift': 'Swift',
    'language-typescript': 'TypeScript',
    'language-yaml': 'YAML'
}

function plugin_codeblock(block) {
    let wrapper = block.parentElement;
    let menubar = document.createElement("DIV");
    for (let i = 0, cls; cls = block.classList[i]; i++) {
        if (/language-.*/.test(cls)) {
            let logo_src = `${root_url}/${cls}.png`;
            let name = code_langs_dict[cls];
            menubar.innerHTML = `<img class="logo" src="${logo_src}"><span class="lang">${name}</span>`
        } else if (cls != "hljs") {
            let logo_src = `${root_url}/language-${cls}.png`;
            let name = code_langs_dict[`language-${cls}`];
            menubar.innerHTML = `<img class="logo" src="${logo_src}"><span class="lang">${name}</span>`
        }
    }
    wrapper.classList.add('ss_codeblock_wrapper');
    menubar.classList.add('ss_codeblock_menubar');
    wrapper.appendChild(menubar);
}
.ss_codeblock_wrapper {
  position: relative;
}
.ss_codeblock_wrapper .ss_codeblock_menubar {
    position: absolute;
    left: 0; right: 0; top: 0;
    min-height: 1em;
    background: rgba(0,0,0,0.38);
    box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
    color: rgba(255,255,255,0.86);
    padding: 0.3rem 0.5rem;
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 0.5rem;
}
.ss_codeblock_wrapper .lang {
    align-self: center;
    justify-self: start;
    font-family: 'Noto Sans KR', sans-serif;
    font-weight: bold;
}
.ss_codeblock_wrapper .logo {
    justify-self: center;
    align-self: center;
    height: 1.2em;
    width: auto;
    filter: drop-shadow(0px 0px 3px rgba(255,255,255,0.6));
}
.ss_codeblock_wrapper code {
    padding-top: 2.3em;
    overflow: auto;
    max-height: 300px;
}
.ss_codeblock_wrapper code.open {
    max-height: none;
}
반응형
태그:

댓글

End of content

No more pages to load