현재 Toast UI의 Editor를 적용해서 게시글 작성 페이지를 구상하고 있다. 다만 아쉬운 점이 있다면 폰트사이즈, 폰트 색상 옵션에 대해 기본적으로 제공을 하지 않는다는 것이다. 물론 이 부분은 플러그인만 추가하면 쉽게 해결이 가능하나 본인에게는 살짝 못미더운 부분이 있었다.
필자는 일반적인 네이버의 에디터 화면처럼 현재 커서가 위치한 곳에서 폰트사이즈나 색상을 바꾸면 그 커서위치부터 입력되는 값은 적용한 스타일셋이 그대로 유지되기를 원했다. 그러나 ToastUI 의 경우 반드시 드래그 하여 선택범위를 지정해주어야만 여기에 태그가 덧씌워져서 스타일이 적용되는 듯했다.
그리고 이 문제는 현재진행형이다. 사실 구현하지 못한 것을 블로그에 기술하면 뭐하나 싶지만, 요 근래에 내가 썼던 글들을 다시 훑어보면서 복습의 시간을 가지고 있기에 이렇게 기록이라도 남겨두면 언젠가 발견하고, 다시 새로운 방법을 찾아 해결할 수 있지 않을까 싶다.
(아래는 몇가지 시도해 본 방법들이다.)
시도 1. customHTMLRenderer 속성 활용
보기와 같이 옵션 내부에 들어갈 객체를 정의해주는데 각 html 태그같은 부분은 설정 시 제공되는 토큰객체를 이용, 이 토큰객체를 이용해서 렌더링 시 각 값에 맞게 HTML 변환된다.
위 코드의 경우 입력되는 값에 자식 노드로 span 태그를 추가하고, 여기에 스타일값을 줘서 폰트사이즈를 변경하도록 짜여졌다. 이렇게 하면 이후 markdown에 작성되는 글들은 기본적으로 35px의 폰트사이즈를 가지고 입력되게 된다.
그리고 속성을 이렇게 집어넣어주기만 하면 바로 적용이 가능하다.
그러나 이 방식은 문제가 있는데 wysiwyg 모드에서 작성하는 것에는 적용이 되지 않는다는 점이었다. 대신 markdown 모드에서 옆에 wysiwyg 모드 버전으로 미리보는 화면상에는 잘 적용된채로 보여졌다.
왜 마크다운 모드에서는 잘 보이는 걸까 ? 문서를 꼼꼼히 다시 읽어보면 그 이유를 찾을 수 있었다.
Editor 는 자체 마크다운 파서를 이용합니다. 마크다운 텍스트를 AST 로 변환합니다.
그러니까 애초에 markdown 문법을 html 형식으로 변환시켜준다는 것이다. 아마 이 부분 때문에 처음부터 wysiwyg 모드에서 작성하면 해당 모드에서는 변경점이 적용되지 않는 듯 했다. (아직 확실치는 않음)
그나저나 저기서 AST라는 생소한 용어가 궁금해져서 조금 찾아봤다. 이름만 직역해보면 추상구문트리, 무언가 추상적으로 트리구조를 간략하게 잡아서 생성하는 그런 느낌이다.
AST (추상구문트리)
컴파일러가 사용하는 파일구조의 일종으로, 각 코드를 추상적인 집합으로 된 토큰 객체로 만들고, 이를 트리구조로 재구성한 결과물이다. 좀 더 쉽게 이해하자면 각 구문에 대해 해당 구문의 타입, 위치, 식별자 등을 토큰화 해서 트리로 나타내어 컴파일러 같은 프로그램이 각 코드의 역할을 보다 쉽게 이해할 수 있게 해준다고 볼 수 있다.
즉 컴파일러가 소스코드를 해석화는 일련의 과정들 중, 구문분석을 할 때 이 시점에 생성되는 트리다. 각 트리는 코드 상에 선언된 함수, 변수, 속성 등의 값에 대해 객체형식의 데이터구조로 짜여져있으며 이를 토대로 개별 소스들을 이해하고 확인하기 위해 효율적으로 활용가능하다고 하다.
(추상구문트리를 활용하는 예시 = Eslint)
AST 부분은 나중에 따로 더 깊이 파볼 필요가 있을듯하다. 아무튼 그래서 내가 사용하려는 이 customHTMLRender 또한 마크다운으로 입력된 값을 렌더링하기 전에 우선 AST 로 트리구조화 하는데, 개발자가 여기서 이 토큰객체를 입맛에 맞게 커스텀할 수 있도록 지원해주고 있는 것이었다.
+) 모 블로그에서 iframe 을 커스텀 블록으로 만들어서 집어넣는 코드를 보고 영감이 떠올랐다. 그 분의 경우 iframe 태그를 삽입하기 전 모드를 markdown으로 바꾼 후 데이터를 넣고, 다시 wysiwyg 모드로 변환하는 방식을 취하고 있었다. 다만 본인의 경우 실시간으로 입력되는 값에 대한 태그구조를 변경학 싶은거라 이 경우에는 evnetListener로 매 입력마다 모드를 바꾸고, 집어넣는 과정을 반복해야 되는데, 이게 과연 효율성이 있는 것인가에 의구심이 들어 아직 시도해보지는 않았다. (더 좋은 방법을 찾았으면 ..ㅜ)
시도2 - Observer 함수 활용
이번에 시도를 통해 처음으로 observer를 제대로 활용해보았다. 물론 이 방법도 결과적으로는 실패였으나 Observer에 대해 나름 공부하고 찾아보는 유의미한 시간이었다고 생각한다.
위 코드는 wysiwyg(Editor 의 wysiwyg 화면 element) 에 대해 구독(감시)를 하는 함수다. 내부 로직에 따라 Observer 는 wysiwyg element 의 각 하위 child 요소들과 그 하위 요소들(subtree) 까지 변경점을 감시하는데 이 중 새로 삽입된 요소들 중 p태그를 찾는다.
찾았을 경우 span 태그를 만들어서 스타일속성을 부여한 후, 노드의 자식으로 집어넣는다.
(콘솔에서 확인해보면 정상적으로 노드에 자식으로 들어가 있지 않음을 확인했다. 기존 라이브러리 코드가 선행되면서 이 코드는 무시되는건지 모르겠으나 이 부분 역시 속시원한 답안을 찾아내지는 못했다..)
Observer 함수
// 1. 감지 대상 요소
const target = document.getElementById('id');
// 2. 옵저버 콜백 함수 선언
const callback = (mutationList, observer) => {
console.log(mutationList);
};
// 3. 옵저버 인스턴스 생성
const observer = new MutationObserver(callback); // 타겟에 변화가 일어나면 콜백함수를 실행하게 된다.
// 4. 변경감지 트리거 여부
const config = {
attributes: true, // 속성 변화 할때 감지
childList: true, // 자식노드 추가/제거 감지
characterData: true // 데이터 변경전 내용 기록
};
// 5. 감시 시작
observer.observe(target, config);
// 6. 중단
observer.disconnect();
Observer 는 DOM의 속성, 텍스트, 자식 노드들에 대한 변경을 감지할 수 있는 내장 API이다. 특정 노드 객체를 관찰하고, 변경이 발생했을 경우 콜백함수가 실행된다.
target은 말 그대로 추적감시의 대상이다.
callback 은 변경사항이 감지될 때마다 반복되서 실행되는 함수다.
config 객체에는 어떤 변화를 감지할지를 정의할 수 있다. boolean 값에 따라 observer 실행의 트리거 기준이 바뀐다.
observe로 감시를 시작하며 disconnect 로 종료가능하다.
(일반적으로 감시가 끝나고 원하는 실행이 끝나면 observe의 감시를 종료하는 게 좋음)
보통 DOM 요소의 특정 값이 바뀌거나, 새로추가될 때 내부적으로 새 요소를 만들어서 추가하거나 하는 식으로 뭔가 동적인 변경이 필요할 때 사용하면 좋을 것 같다고 생각된다.