# Case 본문 문법
`본문 Markdown` 칸에만 해당한다. 다른 종류의 칸은 평문이다.
본문은 자유 Markdown이 아니라 **화이트리스트로 좁힌 Markdown**이다. 공개 사이트가 raw HTML이
아니라 타입 블록을 렌더링하기 때문에, 유니온에 없는 문법은 렌더링할 대상이 없어 거절된다.
벗어나면 `CONTENT_FORMAT_INVALID`로 게시가 막힌다.
## 쓸 수 있는 것
| 문법 | 예 |
|---|---|
| 제목 | `#` ~ `######` (1~6단계) |
| 문단 | 그냥 쓴다 |
| 강조 | `**굵게**` `*기울임*` `` `코드` `` |
| 링크 | `[문구](/경로)` · `[문구](https://…)` |
| 목록 | `- 항목` · `1. 항목` |
| 인용 | `> 한 문단` |
| 수평선 | `---` |
| 코드블록 | ```` ```java ```` |
| 표 | 파이프 표. **감싸지 않는다** |
| 그림 | `` |
| callout | `:::note` `:::tip` `:::warning` `:::danger` |
| 증거 이미지 | `:::evidence key="…" alt="…" caption="…" zoom="true"` |
제목에 고정 id를 주려면 `## 측정 결과 {#measurement}`.
## 쓸 수 없는 것
- **raw HTML** — `
`, `
`, `
![]()
` 모두 거절
- **각주** — `[^1]`
- **체크박스 목록** — `- [ ] 할 일`
- **중첩 목록** — 목록 항목 안에 목록
- **중첩 인용** — `> >`
- **취소선** — `~~지움~~`
- **링크 title** — `[문구](/경로 "설명")`
- **외부 스킴** — `javascript:`, `data:`, `//다른호스트`
인용과 callout은 **문단을 정확히 하나만** 담는다. 목록 항목도 문단 하나만 담는다. 여러 문단이
필요하면 블록을 나눈다.
## 링크와 이미지 주소
허용되는 주소는 넷뿐이다.
```text
#앵커
/상대경로
https://… 또는 http://…
mailto:…
```
`//호스트`로 시작하는 주소는 거절된다. 프로토콜 상대 주소는 어느 사이트를 가리키는지 원문만
보고 알 수 없기 때문이다.
**object storage 주소를 본문에 직접 쓰지 않는다.** 만료되는 presigned URL이 원문에 박히면 나중에
깨진다. 이미지는 Asset으로 올리고 `/api/v1/public/media/{assetId}` 또는 `:::evidence`로 가리킨다.
## directive 쓰는 법
세 개뿐이다: `table`, `callout`, `evidence`. 그리고 서버가 아는 이름 넷: `note`, `tip`,
`warning`, `danger`.
속성은 **정확히 맞아야 한다** — 하나라도 빠지거나 남으면 거절된다.
```text
:::table id="…" caption="…" rowHeaderColumn="1"|"none"
:::callout tone="warning"|"info" label="…"
:::evidence key="…" alt="…" caption="…" zoom="true"|"false"
```
`note`·`tip`·`warning`·`danger`는 이름이 곧 성격이라 속성을 받지 않는다.
```text
:::note
참고할 내용 한 문단.
:::
```
여는 줄과 닫는 `:::` 사이에 **빈 줄**을 둔다. 붙여 쓰면 문단으로 인식되지 않는다.
## 자주 나오는 거절과 원인
| 메시지 | 원인 |
|---|---|
| `unsupported block syntax: html` | raw HTML을 썼다 |
| `unsupported inline syntax: image` | 문단 안에 글과 그림을 섞었다 |
| `unknown block directive: …` | 위 일곱 이름이 아니다 |
| `table directive must contain exactly one GFM table` | `:::table` 안에 표가 없거나 둘이다 |
| `callout directive must contain exactly one paragraph` | callout에 문단이 없거나 둘 이상이다 |
| `list items must contain exactly one paragraph` | 목록을 중첩했다 |
| `unsafe link URL: …` | 허용되지 않는 스킴이나 `//` 주소 |
| `duplicate explicit ID: …` | 같은 id를 두 번 썼다 |
| `unknown inline directive: 27` | **문단에 시각을 그냥 썼다** — 아래를 본다 |
거절 메시지에는 줄·칸 번호가 붙는다. 그 줄을 먼저 본다.
## 문단 안의 시각은 백틱으로 감싼다
`:` 뒤에 글자가 붙으면 파서가 인라인 directive 로 읽는다. 그래서 문단에 `08:20:27` 을
그냥 쓰면 `:27` 에서 막힌다. `00/12:00:00` 같은 설정값도 같다.
```text
새 인증서가 08:20:27 에 기록됐다. ← FAIL unknown inline directive: 27
새 인증서가 `08:20:27` 에 기록됐다. ← PASS
```
**이것이 조용한 결함이 되는 경로가 있다.** 막히면 시각을 빼고 넘어가게 되고, 그러면
검사기는 통과하는데 **기록에서 수치가 사라진다.** 실제로 한 회차에서 두 편이 각각
시각 둘과 타이머 설정값을 빼고 통과시켰다. 수치·날짜·시각은 보호 구간이다 —
**빼지 말고 감싼다.**
표와 코드블록 안은 걸리지 않는다. 그래서 같은 프로젝트의 다른 기록이 통과하는 것이
「이 문법이 괜찮다」는 뜻이 아니다 — 그쪽은 시각이 전부 표 안에 있었을 뿐이다.