sqzass.git

sqzass

~ A static site generator written in Rust
git clone https://github.com/debuman/sqzass.git

docs/content/features/highlighting.ko.md (6541B)

      1 +++
      2 title = "구문 강조"
      3 description = "빌드 시점에, 클래스로, 두 테마를 함께"
      4 weight = 10
      5 toc = true
      6 +++
      7 
      8 코드 블록은 사이트를 빌드하는 동안 강조됩니다. 색을 입히려고 독자의 브라우저에서
      9 JavaScript가 도는 일이 없으므로, 페이지는 첫 페인트부터 색이 입혀져 있습니다.
     10 
     11 ```toml
     12 [highlight]
     13 enabled     = true
     14 theme_light = "InspiredGitHub"
     15 theme_dark  = "base16-ocean.dark"
     16 ```
     17 
     18 ## 인라인 스타일이 아니라 클래스
     19 
     20 강조된 블록은 이렇게 생겼습니다.
     21 
     22 ```html
     23 <pre class="highlight"><code class="language-rust" data-lang="rust"><span class="hl-source hl-rust">…</span></code></pre>
     24 ```
     25 
     26 `style="color:#268bd2"`가 아닙니다. 이 차이가 세 가지를 결정합니다.
     27 
     28 **다크 모드가 가능해집니다.** 인라인 색상은 지금까지 만든 모든 문서에 테마 하나를
     29 박아 넣습니다. 테마를 바꾸려면 사이트를 다시 빌드해야 하고, 독자의 설정을 따르려면
     30 두 테마를 마크업 안에 같이 실어 보내야 합니다. 클래스라면 두 테마는 CSS 두 덩어리일
     31 뿐이고, 전환은 CSS 전환입니다.
     32 
     33 **엄격한 `style-src`를 쓸 수 있습니다.** 인라인 스타일을 금지하는 CSP는 HTML이
     34 인라인 스타일로 가득 찬 순간 선택지에서 사라집니다.
     35 
     36 **스타일시트가 파일 하나입니다.** 색 하나를 바꾸면 HTML 페이지를 하나도 다시 만들지
     37 않고 모든 페이지가 바뀝니다.
     38 
     39 ## 두 테마, 하나의 스타일시트
     40 
     41 `theme_light`와 `theme_dark`가 둘 다 생성된 스타일시트에 들어갑니다. 다크 규칙은
     42 두 번 나갑니다. 토글을 쓰지 않는 독자를 위해 `prefers-color-scheme: dark` 아래
     43 한 번, 이 사이트처럼 전환 버튼을 제공하는 경우를 위해 `[data-theme="dark"]` 아래
     44 한 번입니다.
     45 
     46 syntect 기본 세트의 테마 이름이면 무엇이든 됩니다. 없는 이름을 쓰면 빌드 에러이고,
     47 메시지에 쓸 수 있는 이름들이 같이 나옵니다.
     48 
     49 ## 접두사
     50 
     51 클래스에는 `hl-` 접두사가 붙습니다. 접두사가 없으면 syntect는 `source`, `keyword`,
     52 `string` 같은 이름을 내보내는데, 프로그래밍을 다루는 사이트에서 사이트 스타일시트와
     53 부딪히기 딱 좋은 일반적인 단어들입니다.
     54 
     55 ## 줄 표시와 파일명
     56 
     57 옵션은 언어 뒤에, 공백으로 구분해, `key=value`로 적습니다.
     58 
     59 ````markdown
     60 ```rust hl_lines=2-3 name=src/main.rs
     61 fn main() {
     62     let marked = 2;
     63     let also_marked = 3;
     64 }
     65 ```
     66 ````
     67 
     68 이 사이트의 스타일시트로 렌더하면 이렇게 됩니다.
     69 
     70 ```rust hl_lines=2-3 name=src/main.rs
     71 fn main() {
     72     let marked = 2;
     73     let also_marked = 3;
     74 }
     75 ```
     76 
     77 `hl_lines`는 1부터 세는 줄 번호와 닫힌 구간을 쉼표로 나열합니다:
     78 `hl_lines=2-4,7`. 지목된 줄은 `<mark class="hl-line">`로 감싸집니다.
     79 `<mark>`는 브라우저가 기본으로 칠해 주므로 CSS가 하나도 없어도 표시가 보이고,
     80 사이트는 `.highlight mark`로 다시 꾸미면 됩니다. 구문 강조는 줄 경계를 건너
     81 이어집니다.
     82 여러 줄 주석의 가운데 줄만 표시해도 그 줄은 여전히 주석입니다.
     83 
     84 `name`은 블록에 파일명을 답니다. 마크업이 아니라 속성으로 나가므로 —
     85 `<code … data-name="src/main.rs">` — 보여줄지는 CSS가 정합니다.
     86 
     87 ```css
     88 .prose pre code { display: block; width: max-content; min-width: 100%; }
     89 .prose pre code[data-name]::before {
     90   content: attr(data-name);
     91   display: block;
     92 }
     93 ```
     94 
     95 첫 규칙은 `code`를 가장 긴 줄까지 넓힙니다. 블록이 옆으로 스크롤될 때 라벨과
     96 `hl_lines` 표시가 보이는 폭에서 끊기지 않고 줄 끝까지 닿는 것은 이 규칙
     97 덕입니다.
     98 
     99 Zola식 `rust,hl_lines=2-4`가 아닌 이유: 쉼표는 옵션을 언어 토큰에 붙여 버려서
    100 문법 조회가 실패하고 `class="language-…"`가 오염됩니다. 첫 공백 뒤에는 무엇이 와도 되고,
    101 comrak도 바로 거기서 정보 문자열을 가릅니다.
    102 
    103 오타는 조용한 무시가 아니라 빌드 에러입니다. `hl_line=3`도, `linenos=true`도,
    104 세 줄짜리 블록의 `hl_lines=9`도, 두 번 적은 키도 전부 파일명과 함께 빌드를
    105 멈춥니다 — 그 반대는 멀쩡해 보이는 채로 강조가 빠진 페이지가 배포되는
    106 것입니다. 언어 없이 옵션부터 시작한 펜스도 마찬가지입니다. 언어 이름에 `=`가
    107 들어가는 일은 없으므로, `` ```hl_lines=2 ``는 모르는 언어가 아니라 언어 자리에
    108 온 옵션입니다. 옵션은 하이라이터의 일부이므로 `enabled = false`면 적용도 검사도
    109 되지 않습니다.
    110 
    111 ## 줄 번호와 복사 버튼은 직접 만듭니다
    112 
    113 둘 다 설정 키가 아니고, `line_numbers`는 아무 일도 안 하는 채로 남기느니 **삭제**했습니다.
    114 아무것도 하지 않는 설정은 없는 것보다 나쁩니다. 누군가 그걸 켜 놓고 기다리게 되니까요.
    115 
    116 보통의 블록에는 CSS 카운터를 얹을 줄 단위 마크업이 없습니다 — 모든 블록의 모든
    117 줄에 래퍼를 감으면, 대부분의 블록이 안 쓰는 기능의 값을 모든 페이지가 치르게
    118 됩니다. 줄 번호 거터는 줄 수를 세는 JS 몇 줄입니다.
    119 
    120 ```js
    121 document.querySelectorAll(".prose pre > code").forEach(function (code) {
    122   var lines = code.textContent.split("\n").length - 1;
    123   var gutter = document.createElement("span");
    124   gutter.className = "linenos";
    125   for (var i = 1; i <= lines; i++) gutter.textContent += i + "\n";
    126   code.parentElement.prepend(gutter);
    127 });
    128 ```
    129 
    130 ```css
    131 .prose pre { display: flex; gap: 1em; }
    132 .prose pre .linenos { text-align: right; color: var(--ink-3); user-select: none; }
    133 ```
    134 
    135 복사 버튼은 열 줄 남짓이고, 언어는 이미 엘리먼트에 붙어 있습니다.
    136 
    137 ```js
    138 document.querySelectorAll(".prose pre").forEach(function (pre) {
    139   var b = document.createElement("button");
    140   b.textContent = "copy";
    141   b.addEventListener("click", function () {
    142     navigator.clipboard.writeText(pre.textContent);
    143   });
    144   pre.appendChild(b);
    145 });
    146 ```
    147 
    148 둘 다 `static/`에 있고, 얼마든지 다시 꾸밀 수 있으며, 영원히 같은
    149 뜻을 유지해야 하는 설정 파일에 키를 하나도 더하지 않습니다.
    150 
    151 ## 끄기
    152 
    153 `enabled = false`면 강조를 건너뛰고 스타일시트도 만들지 않습니다. 코드 블록에는
    154 여전히 `class="language-rust"`가 붙으므로 클라이언트 쪽 하이라이터가 받아 쓸 수
    155 있습니다.