fix(cafe24): 소스 들여쓰기 정상화 + 줄 번호·가로 스크롤 편집기

1) 들여쓰기가 안 되던 원인

인라인 요소가 여러 줄에 걸쳐 있으면 그 구간을 하나의 텍스트 덩어리로 다뤄
첫 줄에만 들여쓰기를 붙이고 있었다. 그래서 <img> 가 한 줄에 하나씩 적힌
상세페이지에서 두 번째 <img> 부터 1열에 붙어 나왔다.

원문 줄바꿈을 살리면서 각 줄을 현재 깊이로 들여쓰도록 고쳤다. 줄 앞 공백은
렌더링에 영향이 없으므로 안전하다. 상세페이지는 <img> 를 한 줄에 하나씩 적어두는
경우가 많고 그 모양이 저자의 의도라, 한 줄로 합치는 것보다 살리는 쪽이 읽기 좋다.

주석도 줄을 강제로 나누지 않게 바꿨다. `<!-- 대파_타임랩스 --><img ...>` 처럼 바로
뒤 요소를 설명하는 주석이 많아서, 나누면 라벨과 대상이 떨어져 오히려 읽기 나빠진다.

빈 줄은 구획 표시로 한 줄까지 유지한다. 이 과정에서 멱등성이 다시 깨지는 것을
테스트가 잡았다 — 텍스트 끝에 남은 "\n  " 조각이 매번 빈 줄로 바뀌고 있었고,
양 끝 공백을 함께 제거하도록 고쳤다.

2) 줄 번호와 가로 스크롤

전문 편집기처럼 줄 번호 칸을 넣었다. 줄바꿈을 허용하면 한 논리 줄이 여러 행이 되어
번호가 어긋나므로, 줄바꿈을 끄고(white-space:pre + wrap=off) 가로로 스크롤한다.
번호 칸은 position:sticky 라 가로로 스크롤해도 왼쪽에 남는다.

크기 계산도 단순해졌다. <pre> 가 흐름에 남아 상자 크기를 정하고 textarea 가
inset:0 으로 그 위를 덮는다 — JS 로 높이를 맞추지 않으니 어긋날 여지가 없다.
색상 계열은 그대로 뒀다(태그 초록·속성 갈색·값 남색·주석 회색).

검증: 유닛테스트 45개 통과(신규 4개 — 모든 줄 들여쓰기, 주석과 요소 붙임,
빈 줄 1개 유지, 실제 상세페이지 모양 멱등). 브라우저 실측: 줄 번호 개수가 줄 수와
일치(18/18, 19/19), 번호와 코드의 세로 위치 일치, 긴 줄에서 편집기 안에서만 가로
스크롤(1920 > 716)되고 페이지는 가로 스크롤 없음, 400px 스크롤 후에도 번호 칸 고정,
textarea 내부 스크롤 0(두 층 정렬 유지).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 13:03:54 +09:00
parent 1d3dac3bec
commit 5284fb1c6e
8 changed files with 159 additions and 42 deletions
+48 -11
View File
@@ -264,44 +264,81 @@
position: relative;
flex: 1 1 auto;
min-height: 340px;
overflow: auto;
overflow: auto; /* 가로·세로 스크롤을 여기서 담당 */
border: 1px solid var(--color-subtle-ash, #e5e5e5);
border-radius: var(--r-lg, 10px);
background: var(--color-canvas-white, #fff);
}
.cf24-code-rows {
display: flex;
align-items: stretch;
min-width: max-content; /* 가장 긴 줄에 맞춰 넓어진다(줄바꿈 없음) */
min-height: 100%;
}
/* 줄 번호 — 가로로 스크롤해도 왼쪽에 붙어 있게 sticky */
.cf24-gutter {
position: sticky;
left: 0;
z-index: 2;
flex: 0 0 auto;
padding: var(--sp-12, 12px) var(--sp-8, 8px);
text-align: right;
background: var(--color-ghost-gray, #f6f8fa);
border-right: 1px solid var(--color-subtle-ash, #e5e5e5);
color: #8c959f;
user-select: none;
white-space: pre;
}
.cf24-code-body {
position: relative; /* textarea 의 기준 박스 — 크기는 <pre> 가 정한다 */
flex: 1 0 auto;
}
/* 두 층의 글자가 정확히 겹치려면 아래 속성이 전부 같아야 한다.
줄 번호(.cf24-gutter)도 같은 글꼴 지표를 써야 줄이 맞는다. */
.cf24-gutter,
.cf24-code-hl,
.cf24-code-input {
margin: 0;
padding: var(--sp-12, 12px);
border: 0;
width: 100%;
box-sizing: border-box;
font-family: var(--font-geist-mono, ui-monospace, "Consolas", monospace);
font-size: 12px;
line-height: 1.6;
white-space: pre-wrap;
overflow-wrap: break-word;
tab-size: 2;
}
.cf24-code-hl,
.cf24-code-input {
padding: var(--sp-12, 12px);
white-space: pre; /* 줄바꿈하지 않는다 — 줄 번호가 어긋나지 않게 */
overflow-wrap: normal;
}
/* <pre> 가 흐름에 남아 본문 칸의 크기를 결정한다. */
.cf24-code-hl {
position: absolute;
top: 0;
left: 0;
min-height: 100%;
width: max-content;
min-width: 100%;
pointer-events: none;
color: var(--color-rich-black, #0a0a0a);
}
/* textarea 는 그 위를 정확히 덮는다(크기 계산을 JS 로 하지 않아도 어긋나지 않음). */
.cf24-code-input {
position: relative;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
background: transparent;
color: transparent;
caret-color: var(--color-rich-black, #0a0a0a);
resize: none;
overflow: hidden; /* 높이를 내용에 맞추고 스크롤은 .cf24-code 가 담당 */
overflow: hidden;
}
.cf24-code-input:focus {