prose mirror custom markdown parser
오늘은 prose mirror custom markdown parser 작업을 했다.
todo list 기능을 다음을 참고해서 만들었다.
https://glitch.com/edit/#!/grave-governor
todo 리스트 만들기
문제는 markdown serializer 와 markdown parser를 만드는 것이었다.
serializer는 bullet list 규칙을 보면서 간신히 만들었다. 유사한 것을 보면서 카테고리화 하면 어느 정도 따라할 수 있다.
todo_list(state, node) {
state.renderList(node, " ", () => "- ");
},
todo_item(state, node) {
const head = node.attrs.done === true ? "[x] " : "[ ] ";
state.write(head);
state.renderContent(node);
},
진짜 문제는 마크다운 파서였다. prose mirror의 마크다운 파서는 markdown-it 을 사용하고 있는데 default markdown parser 는 확장이 안되기 때문에 복사해서 새로운 것을 만들었다.
markdown-it-task-lists 이라는 패키지의 소스 코드를 보면서 새로운 룰을 만들어 줬다. 그대로는 적용이 안되서 원리를 보고 일부 코드만 참고해서 만들었다.
md.core.ruler.before("inline", "todo_list", function (state) {
let tokens = state.tokens;
for (var i = 2; i < tokens.length; i++) {
md 의 core 에 룰을 적용하면 마크다운을 토큰 단위로 파싱을 해준다. 이 토큰을 for 문을 돌면서 내가 원하는 대로 바꿔줄 수 있다. 무수히 console.log 를 찍으면서 알아냈다.
그런데 화면이 변할 때 마다 새로운 상태를 읽어 파싱을 계속하는데 최적화가 제대로 될지 모르겠다. 프로스 미러도 마찬가지로 쓰고 있어서 그냥 적용했다. 아마 내부에서 어느 정도 최적화를 해주고 있을것 같다.
이렇게 나온 것을 마크다운 파서에서 매칭을 해주면 된다.
export const mdParser = new MarkdownParser(mySchema, md, {
todo_list: {
block: "todo_list",
getAttrs: (_, tokens, i) => ({ tight: listIsTight(tokens, i) }),
},
todo_item: {
block: "todo_item",
getAttrs: (tok) => {
return { done: tok.attrs[0][1] };
},
},
MarkdownParser 에 들어가는 인자는 순서대로 schema, tokenizer, tokens 인데 tokenizer 에는 커스텀 룰을 적용한 markdown-it 이 들어가고 3번째 인자에는 markdown-it의 토큰 명과 prose mirror 의 노드 명이 매칭되어 들어간다.
노드 이름은 schema 의 toDOM, parseDOM과 연결되어 html 을 렌더링하거나 dom 형식으로 만들어 준다.
역시 프로그래밍은 연결이다. 얘네들간의 관계와 규칙을 이해하는게 너무나 어려웠다.
그래도 되는게 신기하다. 잘 작동할 때 기뻐서 소리 지를뻔…
내 맘대로 막 바꿨는데 분명 문제가 있을 것이다. 그때 가서 수정하면 되겠지… 내일의 나에게 맡긴다.