# Case 본문 문법 `본문 Markdown` 칸에만 해당한다. 다른 종류의 칸은 평문이다. 본문은 자유 Markdown이 아니라 **화이트리스트로 좁힌 Markdown**이다. 공개 사이트가 raw HTML이 아니라 타입 블록을 렌더링하기 때문에, 유니온에 없는 문법은 렌더링할 대상이 없어 거절된다. 벗어나면 `CONTENT_FORMAT_INVALID`로 게시가 막힌다. ## 쓸 수 있는 것 | 문법 | 예 | |---|---| | 제목 | `#` ~ `######` (1~6단계) | | 문단 | 그냥 쓴다 | | 강조 | `**굵게**` `*기울임*` `` `코드` `` | | 링크 | `[문구](/경로)` · `[문구](https://…)` | | 목록 | `- 항목` · `1. 항목` | | 인용 | `> 한 문단` | | 수평선 | `---` | | 코드블록 | ```` ```java ```` | | 표 | 파이프 표. **감싸지 않는다** | | 그림 | `![대체 텍스트](/api/v1/public/media/…)` | | 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 ``` **이것이 조용한 결함이 되는 경로가 있다.** 막히면 시각을 빼고 넘어가게 되고, 그러면 검사기는 통과하는데 **기록에서 수치가 사라진다.** 실제로 한 회차에서 두 편이 각각 시각 둘과 타이머 설정값을 빼고 통과시켰다. 수치·날짜·시각은 보호 구간이다 — **빼지 말고 감싼다.** 표와 코드블록 안은 걸리지 않는다. 그래서 같은 프로젝트의 다른 기록이 통과하는 것이 「이 문법이 괜찮다」는 뜻이 아니다 — 그쪽은 시각이 전부 표 안에 있었을 뿐이다.