에디터
에디터 선택 기준
무료
모던함 - 포맷에 대한 단축키 지원, (내 기준) 사용자의 의도를 동작에 반영
quilljs
개인적으로 에디터를 사용할 때 리스트 항목 내에서 Shift+Enter를 자주 사용하는 편이다. 새로운 리스트 항목을 만들지 않고 단순 줄바꿈만 되길 원하는데, Quill.js는 이 동작을 기본적으로 지원하지 않았다. 오픈소스이고 커스터마이징이 가능하다는 말에 코드를 들여다보며 여러 가지 테스트를 해봤지만, 내가 원하는 수준까지는 조정이 어렵다는 걸 알게 되었다.
이 과정에서 에디터가 어떻게 동작하는지에 대한 큰 틀을 이해할 수 있었고, 관련된 이론과 알고리즘도 새롭게 알게 되었다. 덕분에 '한번 직접 만들어보자'는 마음이 생겼고, 그렇게 에디터 개발이라는 새로운 도전을 시작하게 되었다.
iterating-editor(직접구현)
내가 넣고 싶은 기능을 마음대로 넣기 위해 시작한 프로젝트.
TODO
줄바꿈 ✅ 완료
블록요소, 텍스트 요소 등에 대한 것들을 이용해보자 ⏳ 확인 필요
사용자가 선택한 dom 노드에 대응 하는 vdom 노드를 찾는 기능 ✅ 완료
사용자가 블록 지정한 시작 dom - 끝 dom에 대응 하는 vdom 노드 리스트 찾는 기능 ✅ 완료
vdom 요소에 diff를 정의하여 사용자에 의해 어떤 변경이 있었는지 정의
✅ 완료기능 구현상 vdom 트리의 변경을 수행 후 dom에 모든 변경을 적용하는 방식보다는 vdom이 변경하면 대응되는 dom도 같이 변경해주는 동작으로 구현 중 어떤 방식으로 만들지는 앞으로의 개발과정에서 지켜보기⏳ 확인 필요
사실 diff 필드 사용으로는 자식 노드가 변경되면 부모 노드도 변경되고 그 부모까지도 변경되는 개념을 표현하기가 어려웠다...
에디터에 변경이 발생하는 사용자의 모든 행위 정의하기 ⏳ 진행 중
빈 줄에서 Backspace
텍스트에서 Backspace
블록 지정 후 Delete
빈 줄에서 Enter
텍스트에서 Enter
빈 줄에서 사용자의 Input
텍스트에서 사용자의 Input
명령어 Ctrl + b
rxjs 도입 고려 -> 도입하지 않기로 결정 ✅ 완료
백스페이스로 인한 줄제거 ✅ 완료
브라우저 동작이 어느 시점에 어떻게 dom을 변경하는지 이해 필요 ⏳ 확인 필요
bold의 경우 폰트가 없으면 볼드체가 적용되지 않는다. 적어도 알림이라도 띄우는 것이 좋겠다. ⏳ 대기 중
vdom, dom에 대한 인터페이스 정의 ✅ 완료
dom 변경에 web api를 직접 사용했었기 때문에 코드 레벨의 인터페이스를 적용하기 어려워 wrapper class를 하나 정의했다.
undo를 위한 vdom 히스토리 관리 ⏳ 대기 중
우선은 브라우저에서 제공하는 undo 기능이 있는지 이것으로 해결이 될 수 있을지, 그리고 된다면 dom을 이용한 vdom 재구성 등 생각할게 이것저것 있다.
ms워드처럼 header에 자동 넘버링 ⏳ 대기 중
vdom을 이용한 undo를 구현 - ⏳ 진행 중
사용자로부터 입력이 발생할 때 어떤 이벤트가 발생하는지 분석 - ⏳ 진행 중
규칙
일반텍스트는 span 태그에 작성된다.
span 태그의 부모는 항상 p 태그다.
p 태그에 내용이 비어있을 때는 br 태그가 있어야 한다.
에디터의 모든 변경은 vdom에서 시작해 실제 dom에 반영되어야 한다.
사용자의 텍스트 변경이 발생하는 경우는 input을 사용한다. keydown으로는 한글에 대한 입력 값을 온전히 얻을 수 없기 때문, input은 텍스트가 변경된 후에 이벤트가 발생하기 때문에 이 경우에 한해서 dom -> vdom 방향으로 상태 흐름이 반영된다.
개발로그
2/3
줄바꿈 처리와 커서 이동 기능을 구현하면서 다음과 같은 문제를 겪었다.
엔터 입력 시, 새로운 p 태그와 그 하위에 span 태그를 생성한 후 커서를 span에 위치시키려 했다. 그러나 실제로는 커서가 p 태그에 위치했다. 원인을 살펴보니, span은 인라인 요소로 자체적인 영역이 없어 내용이 없으면 커서를 위치시킬 수 없기 때문이었다. 이 때문에 브라우저는 자동으로 상위 블록 요소인 p로 커서를 이동시킨다. b 태그나 \u200B 를 추가해도 브라우저는 내용이 없다고 인식한다.
개발자 코드에 의해 태그가 변경되면 이런 현상이 발생한다.
이에 줄바꿈 동작을 다음 두 단계로 진행한다.
엔터 입력 시 - 새로운 줄을 표현하는
p태그를 생성하고, 커서를 해당p태그로 이동시킴텍스트 입력 시 - 커서 위치의
p태그에span이 없다면 새로 생성하고, 커서를 그span으로 이동시킴
2/9
기능을 추가할 때마다 기존 로직에 영향을 주는 상황이 반복되고 있다. 개발 중 로직이 변경되는 것은 자연스러운 일이지만, 변경의 영향 범위를 정확히 파악할 수 있어야 유지보수가 쉬워진다. 현재는 각 영역 간의 독립성이나 경계를 명확히 파악하기 어려운 상태다.
vdom에 대한 테스트 코드는 존재하지만, 전체 프로젝트에 대한 테스트 코드는 아직 없다. 이제는 전체 테스트 구조를 잡는 데 도전해볼 시점이다.
기능을 추가할 때마다 기존에 만들어뒀었던 로직에 영향이 생기게 된다. 개발하면서 로직이 변경하는 것은 매우 자연스러운 일이지만 그 영향이 어디까지 미치는지는 알아야 수정이 간편해진다. 어느 영역까지 독립적인지 아직은 구분이 어려우니까. VDOM에 대한 테스트코드는 작성되어있지만 전체 프로젝트에 대한 테스트코드가 존재하지 않는다. 한 번 도전해보자.
2/10
생각해보니 단순한 기능 하나를 구현하는 데도 여러 번 수정을 거쳤다. 이 과정을 겪으며 든 생각은, 초기부터 테스트 코드를 작성하면서 개발하는 것이 오히려 비효율적일 수 있다는 점이다. 기능을 어느 정도 안정화하고 일관성을 확보한 뒤에 테스트 코드를 작성하는 방식이 지금의 개발 흐름에 더 잘 맞는다는 판단을 하게 되었다.
2/12
사용자의 행동은 대부분 이벤트로 전달된다. 특히 keydown 이벤트의 경우 다양한 동작들을 처리하기 때문에 if 문이 많은 상태인데, 이런 구조는 유지보수나 확장에 한계가 있다. 각 동작을 독립적으로 선언하고 필요할 때 명확하게 연결할 수 있는 구조가 필요하다. 이에 rxjs 도입을 고민하게 되었다.
2/15
rxjs를 고려했던 이유는 다양한 이벤트를 세분화한 다음 해당 이벤트를 처리하려고 했던 것이었다. 그러나 세분화된 인스턴스도 결국 keydown 이벤트를 기반으로 만들어지기 때문에, 그 개수만큼 여러 이벤트 동작이 발생하게 된다. 또한 rxjs는 이벤트 정리를 위한 도구가 아니기 때문에 사용하지 않기로 결정했다.
2/19
현재 에디터의 데이터 흐름은 vdom -> dom으로 흐른다. vdom의 변경이 dom에 반영된다는 의미인데, 내가 정해놓은 규칙에는 다음과 같은 것이 있다.
내용이 없는 p 태그는 br 태그를 갖는다.
이런 규칙은 vdom에 정의되어 있지 않다. 왜냐면 vdom은 의미만을 가지고 있어도 충분하기 때문이다. 그렇다면 br 태그는 누가 추가를 해주어야 하는 것인가? 물론 시점은 dom에 반영될 때이다.
dom을 관리하는 레이어에서? - html element를 관리하는 목적을 가진 곳에서 내가 임의로 추가한 규칙을 넣을 수는 없다.
editor의 이벤트 리스터에서? - 해당 리스너는 어느 위치에서 어떤 동작이 있었는지 분석하는 역할을 갖는다 dom을 직접적으로 수정하는 목적이 아니다.
vdom의 변경을 dom에 전달하는 sync 레이어에서? - 가장 합당한 위치이지만 모든 로직을 작성하면서 위의 규칙을 머릿속에 항상 가지고 있어야한다. 그만큼 로직이 복잡해지고 유연성이 떨어지게 된다.
이런 고민을 하다가 앞으로 할 일이 너무 많은데 결정이 나지 않아서 sync 레이어로 결정했다.
앞으로 새롭게 발견되는 문제점들이 이 고민의 해답이 되었으면 한다.
2/20
또 다시 브라우저 동작 발견...
빈 줄을 표현하기 위해서 p 태그 하위에 br 태그를 생성하는 규칙이 반영되어 있다.
해당 상태의 초기화에서는 별다른 문제가 없었지만 텍스트가 채워지고 지워지는 동작으로 글 내용이 제거되는 경우에 p태그가 모조리 지워지는 동작이 발생했다.
코드를 아무리 찾아봐도 태그를 지우는 부분은 실행되지 않았다. 테스트 html을 만들어서 반복적인 실험 결과, 코드에 의해서 p + br 태그가 생성되면 브라우저가 이를 불필요한 요소로 인식하지 않는데, 이벤트에 의해서 p 태그가 내용없는 태그가 되어버린다면 브라우저에서 이를 제거하는 것.
내가 정의한 모든 로직이 끝나면 더이상 브라우저 동작이 발생하지 않도록 event.preventDefault()를 실행시킴으로써 해결했다.
어디까지가 브라우저 동작인지에 대한 이해가 필요. 브라우저마다 다를것이고.. 아마 불가능하지 않을까
2/21
element를 직접 console.log로 출력하게되면 렌더링 시간차이로 다름이 발생. console.log로 element를 출력할 것이 아닌, element.outerHTML을 출력해서 그 순간의 html 상태를 출력하는게 맞다.
2/25
모든 사용자 입력을 프로그램에서 먼저 처리하기 위해 keydown 이벤트를 사용하고 있었다. 하지만 파이어폭스에서 확인해보니, 한글 입력 시 keydown의 key 값이 실제 입력값이 아니라 "Process"로 표시되었다. 이는 입력 중 문자가 완성되지 않은 상태에서 IME(Input Method Editor)가 개입하기 때문이며, 이런 상황에서는 keydown 이벤트만으로 입력을 제어할 수 없게 된다.
브라우저별로 로직을 따로 구성하고 싶지 않기 때문에, 입력에 대해서는 input 이벤트를 감지하고 vdom 보다 dom을 먼저 변경하게 되는 경우가 추가되었다.
2/27
input 이벤트를 중심으로 화면을 관리하게 되면 DOM이 먼저 변경되고, 그 변경 사항을 VDOM에 반영해야 하는 구조가 된다. 기존에는 이벤트가 VDOM에 적용된 후 내부 레이어를 통해 DOM에 반영되는 흐름이었기 때문에, 이 순서의 역전은 구조적인 의미를 다시 고민하게 만들었다. 결국 입력의 시작점은 DOM이며, selection 또한 DOM 기준으로 처리되기 때문에 VDOM의 필요성에 대해 의문이 생겼다. 현재 기능 규모에서는 DOM만으로도 충분히 제어가 가능하고, 오히려 VDOM이 복잡도를 추가하는 느낌도 있다.
한편, 현재 구조에서는 한 줄을 표현할 때 p 태그를 사용하고, 내용이 없으면 br 태그를 삽입하지만, 이 구조는 VDOM에는 반영하지 않는다. 그 의미가 중요하지, 표현 방식 자체는 중요하지 않기 때문이다. 마찬가지로 텍스트를 표현하는 textNode 역시 VDOM에서는 하나의 단위로 취급하지 않고, span 내 텍스트 값으로만 간주한다. 결국 지금의 VDOM은 표현보다는 의미 중심의 상태만 담고 있으며, 시각적인 요소들은 단순화해서 제외하고 있다.
3/1
단축키를 이용해서 지정된 범위에 bold 효과를 적용하는 것까지 추가했다.
style: font-weight를 이용하는데, 그런데 왠걸 한글만 볼드체 적용이 안된다. 알아보니 사용하고 있는 폰트에 bold가 없어서 그렇다는 것. 그런데 또 <b> 태그에서는 볼드체가 적용이된다.
b 태그를 이용하자니 앞으로 i u 같은 다양한 포맷들이 추가될 텐데 이 태그들을 관리할 자신이 없다.
그래서 css를 이용하려고 했는데 이런 문제가 발생하다니. 일단 이 문제는 내가 리눅스를 사용하고 있고 해당 컴퓨터에서 별다른 폰트를 제공해주지 않아서 발생하는 문제이니 넘어가도록 한다.
VDOM
3/3 비상...
볼드체 기능을 적용하며 테스트를 진행하던 중, Ctrl+Z(Undo)가 동작하지 않는 문제를 발견했다. 현재는 이벤트를 통해 VDOM을 변경하지만, VDOM 자체의 히스토리 관리 기능이 없기 때문이다.
이 시점부터는 VDOM을 체계적으로 관리할 필요가 생겼고, 이 즈음에서 전체 코드 정리와 함께 테스트 코드 도입을 결심했다.
당장 생각되는 내용으로는 dom에서 시작되는 변경, vdom에서 시작되는 변경이 있는데 이것들을 어떻게 관리할지가 중요한 것 같다. 브라우저의 undo의 범위가 어디까지일까.. 이것을 믿어보고 싶다.
3/11
dom과 vdom은 거울처럼 움직이기 때문에 동일한 인터페이스를 가지고 있는게 맞지만 dom에 대해서는 document api를 사용하고 있었기 때문에 공통의 인터페이스를 정의하지 못했다. 이런 이유로 element에 대한 클래스를 하나 더 추가해서 공통 인터페이스를 적용하겠다.
3/17
대부분의 테스트코드를 작성하였다.
특정 기능 테스트를 위해서 필요한 데이터를 준비하였고, 메서드를 독립적으로 테스트할 수 있게 인스턴스화 하는 방법을 재정의 하였다. 이 과정에서 전체적인 코드 정리를 완료하였다. 이제 다시 블로그 개발로 돌아가자.
다음에 할 일은 vdom 히스토리 관리.
4/1
우선은 내가 지금까지 만든 에디터에서 dom과 vdom이 서로 일치하는지 확인하는 작업을 해보았다.
일치하는지 확인하는 함수는 만들어 두었지만 매번 호출하기에는 그 시점을 정하는게 애매하였고 코드를 계속 변경해주어야 한다. 디버깅을 위해서 console.log를 계속 찍는 느낌,
기존 만든 코드는 건드리지 않기 위해서 debug를 위한 함수를 새로 만들었으며, 특정 단축키에 등록하여 vdom을 간단한 트리형태로 출력해주는 것을 만들었다.
4/1
vdom에 의해서 화면에 변경이 생긴 것은 브라우저에서 undo를 위한 관리를 해주지 않는다. 결국 내가 undo를 위한 프로세스를 만들어야하는 상황.
초기의 규칙인 모든 데이터의 흐름은 vdom에서 dom으로... 를 지키기 위해서 vdom에 일어나는 모든 변화를 관리하고자 한다.
일단 첫 번째, undo라는 것은 사용자의 행위로 발생한 결과를 이전으로 되돌리는 행위이다. 그러기 위해서는 어느 지점에서 변경이 있었는지 알아야한다. 전체를 되돌릴수는 없으니까
초기 에디터 설계에서 각 노드에 변경이 발생할 때마다 내부의 diff 필드를 INSERT, DELETE, UPDATE로 변경하여 관리했었는데, 자식이 추가 된다면 부모에도 diff 필드를 UPDATE로 변경해주어야 하는 복잡도가 상당하여 포기했었다.
그런데 지금은 블록체인 일을 하면서 알게 된 머클 트리 개념이 떠올랐다. 간단하게 설명하자면 자식 노드가 변경되지 않는다면 그 부모 노드의 상태값을 동일하다는 것이다.
반대로 변경이 발생하는 부분을 위로 전파하여 어떤 노드가 변경이 되었는지를 업데이트해주는 것이다. 최하위의 노드가 변경되면 그 부모가 변경이 될 것이고 이 동작은 루트까지 전파될 것이다.
이후에 루트에서 아래 방향으로 탐색하면서 어떤 노드의 상태가 변경되었는지 확인 후 그 부분을 교체해주는 것을 구현해보려고 한다.
4/3
상태 변경을 체크하는 방법이 하나 떠올랐다. 두 개의 상태 트리가 있고, 각 루트에서 시작해 다음을 수행한다.
두 노드의 hash값이 일치한지 확인한다.
일치한다면 다음 노드로 넘어간다.
일치하지 않는다면
노드의 내부 필드를 확인 후 업데이트 한다.
노드의 자식들을 비교 후 업데이트 한다.
여기에서 어려운 부분은 두 노드의 자식들을 어떻게 비교할 것인지였다.
배열로 되어있는 자식들 구조에서는 언제든지 삽입, 삭제가 발생할 수 있다.
삽입되었다면 어느 위치에 새로운 노드를 넣어야 할지 결정해야한다.
이 위치를 결정하는 것이 어려운데, 다음의 예시로 고민을 설명한다.
기존 자식배열: A-B-C-D-E
신규 자식배열: A-X-Y-C-D-E
A는 고민의 대상이 아니다,
기존 B와 다른 X가 발견되었으니 새롭게 추가되어야 한다. Y도 마찬가지다.
이제부터 문제가 시작된다. 현재까지의 인덱스에서는 C의 존재를 알지 못하기 때문에, B와 다르니 C를 새롭게 추가할 것이다.
D도 마찬가지다.
추가만 하다가 동작이 종료된다.
이 동작은 신규 자식배열에서 B를 발견하지 않는 한 A 뒤의 모든 신규 자식배열은 새로운 요소가 된다.
기존 자식 배열에 C가 있다는 정보가 없으니 단순 인덱스 비교로는 문제를 해결할 수 없다.
이 문제를 해결하기 위해서는 순서를 지키면서 최대한 공통되는 부분이 필요하다.
어떤 것을 만들어야하는지는 정해졌다. 이제 두 번째 고민.
내가 정의한 방안은 알고리즘 문제를 풀다보면 한번쯤 봤을 법한 내용이다. 분명 있는 알고리즘이다.
생각한 것을 직접 구현할 것인지 vs 알고리즘을 찾아서 할지.
사고 능력이나, 구현 능력을 기르기 위해서는 직접하는게 낫지 않을까하는 생각이 먼저 떠오른다.
그런데 한편으로는 내가 아무리 오랜시간 시간을 투자하고 공을 들여도 정립된 알고리즘을 이기지 못한다는 생각이 든다.
결국 알고리즘 검색에 들어가고 있는 나...
각 오브젝트를 하나의 문자로 생각한다면 문자열 알고리즘과 동일하다.
사실상 diff와 관련이 없지는 않을 것이다.
LCS 알고리즘이 바로 검색된다. 보통 이런 알고리즘 문제를 풀게되면 몇 개의 공통 문자가 있는지 출력하는 문제가 대부분이다. 실제 검색된 자료도 그런 것들이 많았다.
내가 필요로 하는 것은 그 공통 문자가 무엇인지이다. 관련 내용을 찾아보니 왠걸, 어떤 요소가 삽입되고 삭제되는지까지 연결되는 알고리즘이다.
A 문자열에서 B 문자열로 변경되는 과정을 표현할 수 있는 것을 Edit Script라고 하네.
4/3 - LCS 알고리즘을 알아보며
이 알고리즘은 직접 구현해보면 어렵지 않다. 두 문자열에 대한 표를 그리고, 숫자를 하나씩 적어가며 알고리즘의 동작을 확인하면 된다.
내 방식대로 이 알고리즘을 정의하자면, 문자 간 일치 여부에 대한 정보를 숫자로 남기고, 그 숫자를 바탕으로 일치 개수를 파악하는 것이다.
그런데 어떻게 이런 알고리즘을 생각해냈을까?
나는 과학이라는 것이 결국 경험에 기반한다고 생각한다. 수많은 시도와 최적화 과정을 거쳐 만들어졌겠지.
궁금해서 GPT에게 이 알고리즘의 배경을 물어보았다. 경험인가? 충분한 이론과 근거가 있는 것인가?
이런 질문들에까지 답해주는 GPT가 참 좋다. 형식 이론과 동적 프로그래밍의 고전 예제라고 한다.
이런 공부해보고 싶다. GPT와 함께 공부를 시작해본다.
그 전에, 사람이 이 문제를 직접 푼다고 하면 눈으로 하나하나 위치를 기억해가며 “이건가? 저건가? 아니네?” 같은 과정을 반복할 것이다. 물론 문자열이 충분히 짧다는 전제하에.
하지만 문자열이 길어지면, 사람 머리로는 도저히 감당할 수 없는 문제가 되어버린다. 머릿속에 어떤 과정이 일어나는지 기억할 수가 없다.
그럼에도 이 문제를 풀어야 한다면, 결국 표를 그리고 숫자를 적어나갈 수밖에 없다.
이런 문제도 연습을 많이하면 표 없이 눈으로 풀 수 있을까?
그렇게 되면 숫자를 적으며 생각하는 그 과정에, 표를 이용하는 사고에 익숙해질 수 있을까...? 그런 생각이 든다.
궁금하기는 하지만, 막상 해보고 싶지는 않고...
4/11
비상.....
원래 텍스트 변경이 발생하는 모든 행위를 keydown이벤트로 처리하려고 했었다. 그런데 keydown으로는 한글 입력에 대한 키값 처리가 어려웠고, 텍스트 변경이 발생한 후 발생하는 input 이벤트를 이용해 변경을 감지하고 있다.
이 때 전체 데이터 흐름에 대해서만 신경썼고 잘 동작하는지에 대해서만 확인을 했었다. 이벤트 로그에 대해서는 자세히 찾아보지 않았었는데, 지금와서야 문제가 발생했다.
예를 들어 'ㅁㄴㅇ'를 입력한다면 'ㅁ', 'ㄴ', 'ㅇ'에 대해서만 input 이벤트가 발생하는 것이 아닌 'ㅁ', 'ㅁ', 'ㄴ', 'ㄴ', 'ㅇ' 이벤트가 발생하는 것.
keydown 이벤트에서 발생했던 것과 같은 문제인데, 여기에서 다시 정리한다.
알파벳으로만 문자열을 구성하는 영어와 달리 한글은 문자를 조립하는 과정이 있다. 'ㅁ'를 입력한다고 하면, 'ㅁ'의 입력이 들어왔고 이제 문자 조립을 시작한다는 이벤트가 발생한다. 이어서 'ㄴ'를 입력하면 'ㅁ' 문자 조립이 종료되었다는 이벤트가 추가로 발생하여 시각적으로 보이는 하나의 문자에 대해서 두 번의 이벤트가 발생한다.
지금 vdom 히스토리를 위해서 사용자에 의해 무언가 변경이 될 때마다 vdom history를 쌓고 있는데, 이벤트가 발생하니 두 개의 동일한 상태가 저장되고 undo를 두 번 눌러야 이전 상태로 돌아가는 현상이 발생한다.
글자 입력에 대한 세부 이벤트 플로우를 찾아보고 정의해야한다. 그런데 이렇게 알지 못했던 이벤트가 너무나 많은데, 식별하자니 이벤트를 하나하나 살펴봐야하니 걱정이 앞선다.
현재는 이벤트 하나를 사용자의 동작이라고 판단하고 처리하도록 되어있는데, 이제는 이벤트 순서의 집합이 사용자의 동작이 되어야 한다. 이를 정의해야 한다.
4/12
아무리 생각해도 상태머신밖에 떠오르지 않는다. 잘 알지도 못하면서... 한 번 떠오른 생각에서 벗어나기 어렵고 어설프게 알지도 못하니 답답할 뿐이다. 일단 내 생각을 찾았으니 다른 오픈소스는 어떻게 하고 있는지 확인해보자.
4/22
한글 입력의 최소 단위를 다음의 이벤트 순서로 정의한다.
keydown -> compositionstart -> compositionupdate -> compositionend -> input
5/8
한글의 경우 다음의 상태 관리를 이용해서 입력이 완료되는 시점을 구분하였다. 다시 idle로 돌아가는 경우에 editorinput이라는 커스텀 이벤트를 발생시킨다.
idle -> compositionstart -> compositionupdate -> compositionend -> idle
영어의 경우에는 input 이벤트로 처리가 가능하기 때문에 기존 방식에 한글이 입력중인 상태인지 체크하는 로직만 추가하였다.
6/20
이런 저런 입력을 해보면서 각종 오동작을 수정하였다.
7/1
TipTap으로 마이그레이션 결정