네이버 에디터 정렬 버그, 인라인 스타일도 안 먹힌 이유 (DOM덤프 실측)

·

네이버 블로그에 글을 자동으로 채워 넣는 크롬 확장을 직접 만들어 쓰고 있다. 어느 날 발행된 글을 보니 본문 문단이 전부 가운데 정렬로 나와 있었다. 인라인 스타일도, 표준 편집 명령도 둘 다 무시됐고, 결국 DOM을 직접 찍어보고서야 진짜 원인을 찾았다.

증상 — 붙여넣은 문단이 전부 가운데 정렬

이 확장은 초안 텍스트를 HTML로 조립해 네이버 스마트에디터에 붙여넣는 방식으로 동작한다. 그런데 발행된 글을 확인하니 소제목·본문·리스트 할 것 없이 모든 문단이 가운데 정렬로 나왔다. 법률 콘텐츠처럼 줄글이 긴 글에서는 가운데 정렬이 가독성을 크게 해친다 — 왼쪽 정렬로 고쳐야 했다.

1차 시도 — 인라인 text-align 스타일

가장 먼저 시도한 건 붙여넣는 HTML 문단 자체에 인라인 스타일을 넣는 것이었다.

<p style="text-align:left;">본문 내용</p>

이 정도는 대부분의 리치 텍스트 에디터에서 먹힌다. 하지만 발행 결과를 다시 확인해보니 여전히 가운데 정렬이었다 — 인라인 스타일이 무시됐다.

2차 시도 — CDP justifyLeft 편집 명령

다음으로 시도한 건 캐럿을 문단에 두고 표준 편집 명령(document.execCommand("justifyLeft")에 대응하는 CDP Input.dispatchKeyEvent 명령 배열)을 직접 쏘는 방법이었다. 브라우저 표준 편집 명령이니 에디터가 뭘 쓰든 먹혀야 정상이라고 생각했다. 하지만 이것도 결과는 같았다 — 여전히 가운데 정렬.

두 번 연속 실패하자 “정렬 방식을 더 정교하게 흉내내면 되겠지”라는 접근 자체가 틀렸다는 게 분명해졌다. 실제로 에디터 내부에서 정렬을 어떻게 결정하는지 모르는 채로 명령만 계속 바꿔봐야 같은 결과가 반복될 뿐이었다.

DOM 덤프 — 정렬은 클래스 하나로만 결정된다

그래서 확장에 “지금 에디터의 DOM 전체를 파일로 저장하는” 기능을 추가하고, 붙여넣기 직후의 실제 HTML을 직접 열어봤다. 결과는 명확했다.

<p class="se-text-paragraph se-text-paragraph-align-center">본문 내용</p>

붙여넣은 문단은 예외 없이 se-text-paragraph-align-center 클래스를 달고 있었다. 리스트 항목 안쪽의 <p>까지 전부 마찬가지였다. 반면 인용문을 표 셀로 변환해 넣었던 부분은 -align-left 클래스가 그대로 살아 있었다. 이 두 경우를 나란히 놓고 보니 결론이 나왔다 — 네이버 스마트에디터는 정렬을 인라인 스타일이나 execCommand 결과가 아니라, 오직 se-text-paragraph-align-* 클래스로만 렌더링한다. 붙여넣기 정규화 과정에서 이 클래스가 항상 -center로 재배정되는 것이 진짜 원인이었다.

수정 — 클래스를 직접 치환

원인이 클래스 하나라는 게 확인된 이상, 더 나은 편집 명령을 찾는 대신 그 클래스를 스크립트로 직접 바꿔치기했다. 붙여넣기 성공 검증(넣은 텍스트가 실제로 화면에 반영됐는지 확인)이 끝난 직후에 실행한다.

// 네이버 에디터는 문단 정렬을 se-text-paragraph-align-* 클래스로만 결정한다.
// 인라인 text-align 스타일도, CDP justifyLeft 편집 명령도 둘 다 무시됐다 —
// DOM 덤프로 확인해보니 붙여넣은 문단은 항상 se-text-paragraph-align-center로
// 찍혀 있었다(리스트 항목 안쪽 <p>까지 포함). 반대로 인용문을 표 셀로 변환해
// 넣은 경우엔 -align-left가 살아있었다 — 정렬이 붙여넣기 파서가 아니라 순전히
// 이 클래스 하나로 렌더링된다는 뜻이다. 그래서 서식 명령을 더 흉내내지 않고
// 클래스를 직접 갈아치운다. 제목(se-documentTitle)은 원래 가운데 정렬이
// 정상이라 건드리지 않는다.
function forceLeftAlign() {
  let n = 0;
  for (const { doc } of allDocs().docs) {
    for (const wrap of deepQueryAll(doc, ".se-components-wrap")) {
      for (const p of wrap.querySelectorAll(
        ".se-component:not(.se-documentTitle) .se-text-paragraph.se-text-paragraph-align-center",
      )) {
        p.classList.remove("se-text-paragraph-align-center");
        p.classList.add("se-text-paragraph-align-left");
        n++;
      }
    }
  }
  return n;
}

제목 컴포넌트(se-documentTitle)는 원래도 가운데 정렬이 맞기 때문에 셀렉터에서 명시적으로 제외했다. 실제 발행에서 이 함수가 39개, 48개 문단의 클래스를 보정한 로그를 확인했다 — 글 하나에 문단이 그만큼 있었다는 뜻이고, 고치기 전까지는 그 전부가 가운데 정렬로 나가고 있었다는 뜻이기도 하다.

왜 DOM 덤프를 먼저 봤어야 했나

인라인 스타일과 편집 명령, 두 번의 시도는 전부 “에디터가 표준적인 서식 지정 방식을 받아줄 것”이라는 추측에 기반했다. 실제로는 스마트에디터가 붙여넣은 내용을 자체 규칙으로 재작성하는 리치 텍스트 에디터였고, 그 규칙은 표준 API 문서에 나와 있지 않다. 이런 에디터를 자동화로 다룰 때는 추측성 시도를 반복하기 전에 실제 DOM을 한 번 찍어보는 쪽이 더 빠르다 — 인라인 스타일이 사라지는지, 어떤 클래스가 남는지는 코드를 아무리 들여다봐도 안 보이고, 렌더링된 실제 구조를 봐야만 드러난다.

정리

같은 정렬 문제를 두 가지 방식으로 고치려다 둘 다 실패했다. 인라인 스타일도, 표준 편집 명령도 네이버 스마트에디터의 실제 렌더링 규칙과는 무관했다. DOM을 직접 덤프해서 본 뒤에야 정렬이 se-text-paragraph-align-* 클래스 하나로만 결정된다는 게 드러났고, 그 클래스를 붙여넣기 검증 직후 직접 치환하는 방식으로 문제를 해결했다. 리치 텍스트 에디터를 자동화로 조작할 때는 표준 API보다 실제 DOM 구조를 먼저 확인하는 게 맞다.

자주 묻는 질문

인라인 text-align 스타일은 왜 안 먹혔나요?

네이버 스마트에디터가 붙여넣은 HTML을 자체 정규화 과정에서 다시 쓰기 때문입니다. 인라인 style 속성이 그대로 남는지 여부와 무관하게, 실제 렌더링은 에디터가 문단에 붙이는 se-text-paragraph-align-* 클래스만 보고 결정됩니다. DOM을 직접 찍어보기 전까지는 이 사실을 알 수 없었습니다.

CDP justifyLeft 명령은 왜 안 먹혔나요?

justifyLeft는 브라우저의 표준 편집 명령(document.execCommand 계열)인데, 스마트에디터처럼 자체 렌더링 엔진과 클래스 체계를 쓰는 리치 텍스트 에디터에서는 표준 편집 명령이 에디터 고유의 상태(이 경우 클래스)에 반영되지 않을 수 있습니다. 명령 자체는 실행됐지만 에디터가 그 결과를 무시한 것으로 보입니다.

DOM 덤프는 어떻게 활용했나요?

확장에 '현재 에디터 DOM 전체를 파일로 저장하는' 버튼을 추가해, 붙여넣기 직후의 실제 HTML 구조를 눈으로 확인했습니다. 그 결과 붙여넣은 문단이 예외 없이 se-text-paragraph-align-center 클래스를 달고 있다는 게 보였고, 반대로 표 셀로 넣은 인용문은 -align-left가 그대로 살아있다는 차이도 함께 확인됐습니다.

왜 서식 명령을 더 시도하지 않고 클래스를 직접 바꿨나요?

정렬이 순전히 클래스 하나로 렌더링된다는 게 DOM 덤프로 확인된 이상, 더 정교한 편집 명령을 찾는 것보다 그 클래스를 직접 스크립트로 바꿔치기하는 쪽이 더 확실하고 간단했습니다. 붙여넣기 성공 검증(텍스트가 실제로 들어갔는지 확인) 직후에 실행해, 새로 생긴 문단만 골라 클래스를 치환합니다.

#네이버블로그#DOM#크롬확장#CDP#버그