Lilyrs

네이버 스마트에디터2 작동 원리 정리

네이버 스마트에디터 2.0

웹 서비스를 제공하다보면, 사용자가 게시물을 직접 작성할 수 있도록 하는 경우가 많다. 게시판 글을 작성할 때, 단순하게 텍스트와 줄넘김만을 사용하여 작성하던 시절도 있었지만, 점차 서식의 필요성이 생기고 입력된 텍스트에 글꼴 속성, 문단 배치, 표, 이미지 및 첨부파일 등록 등 다양한 기능의 지원이 필요해졌다.

이런 문서를 작성하기 위해 과거에 BBCode 같은 물건도 이용했고, 최근에는 Markdown을 위시한 여러 마크업 언어를 사용하여 작성한 뒤 변환기를 통하여 HTML을 얻어낸 후 사용자에게 보여주고 있다. Typora 고수준의 에디터들은 이런 마크업 언어를 실시간으로 처리해주며 사용자에게 보여줄 아웃풋을 예측할 수 있게 해주는데, 웹에서도 자신이 작성한 게시물이 어떤 형태로 보여줄지를 미리 확인할 수 있게 하면서 작성 편의를 도모하는 에디터들이 존재한다. 속칭 위지윅 에디터(WYSIWYG Editor)이다.

WYSIWYGWhat You See Is What You Get의 이니셜만 따서 모아진 단어로, 작성 화면에서 보는 그대로 출력 화면에서도 동일한 결과를 얻을 수 있다는 뜻이다. 웹상에서 이를 구현해주는 에디터 구현은 여러 구현체들이 있으나, 오늘은 꽤 오래 전 네이버 블로그 팀에서 이용한 걸로 알려져있고, 지금도 많은 곳에서 애용하고 있는 스마트에디터2.0을 살펴보려고 한다.

WYSIWYG 에디터의 구현 방향성

네이버 스마트에디터 2.0은 프로젝트 설명에서 확인할 수 있다시피 Javascript를 기반으로 한 웹용 WYSIWYG 에디터이다.

아무 생각 없이 사용하던 도중 문득 내가 이걸 구현하면 어떻게 될까를 고민해 보면서 HTML5와 에디터의 조합을 생각했을 때, WYSIWYG을 구현하려면 어떻게 구현할까에 대해 몇가지 방향성을 고민해 보았는데 크게 두가지 분류로 나눠지는 것 같다.

  1. Canvas 기반 에디터 (각 화면 요소를 렌더러 통해서 캔버스에 그림, 입력/포커스/마우스 이벤트 직접 처리)
  2. non-Canvas 기반 에디터 (HTML 스펙 사용)

Canvas 기반 에디터는 많은 구현을 찾긴 힘드나, PrimroseCarota라는 에디터를 대표적으로 골라보았다. 위지윅은 아니지만 VSCode 실제로 이 구현체는 F12 등을 통하여 렌더링 영역을 확인해보면 달랑 canvas 엘리먼트 하나만 존재함을 확인할 수 있다.

그외의 나머지 에디터들은 HTML과 웹 브라우저에서 제공해주는 스펙을 활용하여 위지윅 에디터를 만들고 있다. 정확히는 contentEditable 속성을 활용한 것이다. 네이버 스마트에디터 2.0은 이것을 통하여 구현되어있다. 좀 더 상세한 구조는 이후에 설명하겠다.

Canvas로 만든 에디터 수가 적은 이유

생각보다 구현 예시를 들기가 어려울 정도로, 잘 만들어졌으며 대중적으로 사용되는 에디터를 찾기가 힘든게 이 canvas 기반 에디터이다. 왠지 2D 캔버스를 그래픽 카드의 가속을 받으며 쓰면 매우 좋을 것 같고, 웹으로 구현하기 힘든 다른 요소들도 추가할 수 있을 것 같은데 왜 많이 만들지 않았을까?

실제 canvas 기반 에디터를 만들 경우, 브라우저에서 제공해주는 기본적인 이벤트 핸들링 인프라나 직렬화/역직렬화 과정에서 해줄 수 있는 부분들을 많은 부분 포기해야한다. 더불어 접근성(Accessibility) 측면에서 볼 때, 캔버스를 통해 보여지는 정보는 비트맵 이미지 정보 뿐이기 때문에 시각 장애인들이 브라우저 내장 스크린리더를 통해 단어 단위로 읽어나갈 때 이 부분을 읽을 수 없다.

저장과 불러오기 단계만 볼 때, 화면에 입력된 내용을 어떤 형태로든 저장해야하는데 HTML contentEditable 기반으로 만들어진 에디터들은 브라우저 JS API를 통해 간단히 HTML을 아웃풋으로 얻어서 활용할 수 있는 반면에 직접 구현해둔 애들은 이 구현도 직접 해야한다는 점이 있다.

HTML Living Standard 4.12.5 Canvas Element의 ‘Best pratice’ 섹션을 보면 위에서 언급한 단점들에 대해 간접적으로 언급하고 있다. 더불어 가능하면 canvas로 텍스트 에디터를 만들지 마라고 쓰여 있다. 만약 캔버스로 에디터와 유사한 무언가를 만들고자 한다면 재구현 해야할 목록을 정리해두었는데 이걸 모두 맞춰서 구현할 수 있다면 만들어도 좋을 것 같다.

장점을 굳이 찾으면, 캔버스 자체는 일관되게 돌 것이기 때문에 브라우저 간 레이아웃 호환성을 덜 받을 것 같기도 하다. 물론 non-Canvas 방식도 완전하지는 않다. Internet Explorer 때문이다. 다음에서 스마트에디터 구조를 설명할 때 IE에서 발생되는 특이사항에 대해 덧붙이고자 한다.

네이버 스마트에디터 2.0의 구조

네이버 스마트에디터 2.0은 canvas 기반의 에디터를 만들었을 때 발생되는 막대한 작업 기간과 브라우저별 호환성 문제 등을 고려했을거고 이에 따라 비교적 여러 브라우저를 지원하면서 (당시는 IE 지원도 필수) 만들어질 수 있는 non-Canvas 방식을 선택했을 것이다.

이 에디터는 Jindo 프레임워크를 활용하여 작성되었다. 진도 프레임워크는 이벤트 바인딩, 구 Javascript에는 없던 OOP의 Class 구현을 위한 기능[1], Form, AJAX, JSON 처리 등을 네이버 자사 제품군 내에서 편하게 하기 위해 만들어진 프레임워크로 현재는 쓰이지 않는다. 네이버 스마트에디터도 3.0으로 바뀌고 이후로는 공개되지 않았으므로 3.0의 구조는 해당 회사에 입사하면 구경할 수 있지 않을까 싶다.

아무튼, Jindo는 네이버가 만든 jQuery라고 생각하고 블랙박스로 간주한 다음에 스마트에디터를 살펴보자.

스마트에디터 2.0은 크게 3개 구성요소를 동작시켜야 정확히 이용할 수 있다.

  1. 스마트에디터의 직렬화된 아웃풋을 저장할 <textarea> (Form 요소)
  2. 스마트에디터 스킨을 불러올 <iframe> 요소 (각종 버튼 영역)
  3. 스마트에디터 스킨에서 WYSIWYG 영역으로 사용할 <iframe> 요소 (문서 수정 영역)

문서를 에디터에 불러오는 데이터 흐름은 1 -> 2 -> 3 순서로 이루어지고, 문서의 저장은 3 -> 2 -> 1 순서로 데이터가 이동된다. Form으로 서버에 전송할 데이터를 담을 위치에 직렬화된 문서를 저장하고, 에디터에 불러올 때는 <textarea>에 직렬화된 문서를 2의 스킨 속 JS가 불러와서 3의 영역에 삽입해 표출시킨다. 직렬화 형식은 당연히 브라우저 도큐먼트 JS API를 활용했으므로 HTML이다. 작성된 문서를 에디터 외부에서 출력할 땐 그냥 이 HTML을 뿌리기만 하면된다.

각 요소간 통신은 JS를 통해 이루어지며, 이벤트를 주고 받거나 싱글턴 객체에 특정 커맨드(command)를 보내고 커맨드를 받아 에디터가 화면 상의 처리를 해주는 구조로 동작한다. 이 구조를 통하여 플러그인에서 에디터 영역에 컨텐츠를 추가하거나 삭제할 수 있다. (스마트에디터2.0 플러그인 작성 예시 참고)

스마트에디터2.0 WYSIWYG 영역

이 영역은 이전 섹션에서 언급한 영역 중 3번에 속하는 영역으로, 이 영역에서 앞에 언급한 HTML contentEditable 속성을 활용한다.

이 속성은 MDN 문서에서와 같이 아래와 같은 코드로 활성화할 수 있다.

1
2
editable = element.contentEditable
element.contentEditable = 'true'

어느 사이트들 개발자 도구를 열어 한번 활성화하면, 화면의 요소가 갑자기 마우스로 드래그 되기 시작하고 텍스트 영역에 글자가 갑자기 입력되기 시작한다. 이 표준은 IE 5.5(!) 부터 지원되는 표준으로, 대부분 상용되는 브라우저에서 모두 사용할 수 있다.

디자인 모드(Design Mode, document.designMode=’on’)로도 불리는데, 이 속성의 경우 적용 범위의 차이가 있을 뿐 동일하게 동작한다고 볼 수 있다. contentEditable이 좀 더 좁은 범위에 적용이 가능하다.

이제 감이 어느 정도 왔을 수 있는데, 이렇게 편집이 된 문서를 탑레벨부터 훑어 내려가며 HTML로 만들어내면 에디터가 만들어 낸 결과물을 얻을 수 있다. 하지만 이렇게만 만들면 글자 속성 변경이나, 표 그리기, 이미지 삽입 등이 안되니까 추가적인 기능을 더 구현해야한다. 이미 해당 속성이 적용된 부분에선 사용자 화면에 적용되어 있다고 피드백도 줘야 한다. 그리고 브라우저 마다 미묘하게 다른 동작들을 일관되게 동작하도록(커서 위치 문제, 줄 넘김 문제 등) 여러 처리를 더 거쳐 나온 것이 스마트에디터2.0이다.

execCommand()와 스마트에디터2.0의 커스텀 커맨드

스마트에디터와 위지윅 영역 간의 연결, 그리고 스마트에디터와 에디터를 불러온 본 페이지와의 커뮤니케이션은 커맨드와 이벤트를 통해 이루어진다. 이 중에 커맨드(command)는 글자 속성 변경, 들여쓰기/내어쓰기, 글자 삭제 등 블록된 영역 또는 커서 아래의 텍스트에 대해 여러 작업을 할 수 있게 해주는 명령 체계이다.

커맨드는 Jindo 프레임워크 내부의 채널을 통해 전달된다. 전달된 커맨드는 스마트에디터 내부에 작성된 위지윅 스타일 커맨드 핸들러에게 전달되고, 여기에서 브라우저가 기본적으로 제공해주는 document.execCommand()를 사용해 처리할지 아니면 별도로 처리해줄지 결정한다.

생각보다 execCommand가 제공해주는 기본 기능이 많다. 링크된 MDN 문서의 Commands 섹션을 확인하길 바란다. 이걸 확인하고 스마트에디터의 메뉴를 확인해보면 어떤 기능이 어디에 대응되는지 확인할 수 있을 것이다.

Internet Explorer에서의 execCommand(“italic”) 관련

Internet Explorer를 지금 와서도 계속 언급하는 건, 다 죽은 인터넷의 망령을 찾는 느낌이지만 아직도 이 브라우저는 (MS 공식 PDF) Internet Explorer 11 데스크톱 애플리케이션 2022년 6월 15일 부로 지원 종료 글에 따라 일반 사용자용 윈도우에서는 작성 시점 기준 내년 6월까지는 살아있을 것이며 Windows 10 LTSC(The Long-Term Servicing Channel)라고 기업용으로 제공되는 윈도우는 거의 2030년까지 살아 있다. 그렇기에 관련 사항을 정리해둠이 옳다고 생각되어 정리한다. (특히나 폐쇄망 환경을 운영하는 기업)

다른 브라우저들이 contentEditable 영역 내의 텍스트 선택 후 execCommand("italic") 을 할 경우 <i> </i> HTML 엘리먼트를 활용하는 동안, IE만은 <em> </em>을 사용한다.[3] 이건 이미 정해진 사양이라 브라우저에서는 변경할 방법이 없다.

이것은 두 가지 문제를 일으킨다. 스타일시트 등을 활용해 <em>의 스타일을 font-style: normal 로 덮어 쓴 경우,

  1. Internet Explorer에서 문서를 작성하면, IE와 기타 브라우저 이용자 모두 문서 열람시 이탤릭 문자가 정상적으로 표시되지 않는다. (em으로 작성됨)
  2. 기타 브라우저(MS Edge, 구글 크롬, 파이어폭스 등)에서 작성한 사람은 IE에서도 이탤릭 표기가 정상적으로 표시된다. (i로 작성됨)

위의 동작은 두가지 방향에서 수정이 가능하다.

하나는 스마트에디터 2.0 플러그인을 활용하고, 스마트에디터 2.0 원본 코드를 수정하여 italic 처리 이벤트를 별도로 구현해주면 브라우저 상관 없이 <i> 엘리먼트를 활용하여 이탤릭 처리를 할 수 있는 것이고, 다른 하나는 내부의 IR_TO_DB, DB_TO_IR[2] 변환 이벤트를 가로채서 <em></em><i></i>로 치환하는 것이다.

변환 이벤트는 위지윅 에디터 컨텐츠와 Form을 오가는 변환 과정을 수행도록 요청하는 이벤트이다. 여기서 직렬화된 HTML 문서를 직접 문자열 치환해버림으로써 em 엘리먼트가 전달되지 않도록 하는 것이 핵심.

근본적으로는 <em> 엘리먼트를 용도 외로 활용하지 않도록 하는 것이 중요하지만 말이다.

맺음말


  1. 1.물론 아예 없는 것은 아니지만, prototype과 function, closure 등의 특성을 활용하여 클래스 생성과 객체 상속 등을 구현할 순 있다. 그걸 좀 더 체계화시켜 하려는 것이 목적인 것이다.
  2. 2.DB와 IR이 무엇의 약자인지를 모르겠다.
  3. 3.Toggles italics on/off for the selection or at the insertion point. (Internet Explorer uses the <em> element instead of <i>.) MDN Document.execCommand