주제: patchworks: 코어 수정에 대한 설명
안녕하세요. lifthrasiir입니다. 다들 토끼군이라고 부르지만 그건 넘어 가고;
patchworks 프로젝트는 태터툴즈의 (현재는 위지윅) 편집기와 (현재는 TTML) 포매터를 완전히 모듈화시키고 그 바탕에 새로운 편집기·포매터 플러그인을 만들려는 프로젝트입니다. 서브버전 커밋이 계속 안 되는 고난을 겪으면서 오늘 r3035로 커밋을 했는데, 여기에 있는 내용이 상당히 많기 때문에 구체적으로 어떻게 바뀌었는지 설명하려고 글을 씁니다.
...심심하신 분께서는 1.2 브랜치 체크아웃해서 테스트해 보시고 피드백해 주셔도 됩니다.
편집기와 포매터
약간 혼란스러울 수 있는 개념인데, 편집기와 포매터는 연관은 있지만 엄연히 다른 개념입니다. 편집기는 날 텍스트로 된 걸 보기 좋게 꾸며서 편집하기 좋게 인터페이스를 제공하는 것이고, 포매터는 이런 날 텍스트를 실제로 HTML로 렌더링하는 역할을 하게 됩니다. 따라서 편집기는 사실상 자바스크립트로 구현되고 포매터는 PHP로 구현된다고 보면 됩니다. 날 텍스트의 형태는 포매터에 따라 달라질 수 있기 때문에 보통 편집기와 포매터가 함께 제공된다고 생각할 수 있겠습니다.
편집기 인터페이스
각각의 편집기는 하나의 자바스크립트 오브젝트로 구현됩니다. 이 오브젝트는 편집기에 관련된 모든 일(이벤트 처리나 포맷 변환 등등...)을 하게 되며, 여러 개의 편집기 오브젝트가 공존할 경우에도 동작하도록 구현하는 게 좋습니다. 다만 현재 구현은 속성 창 때문에 하나의 오브젝트만 활성화되도록 되어 있습니다. 이 오브젝트는 외부에 네 개의 메소드를 노출시키는데,
- editor.initialize(textarea): 주어진 textarea 엘리먼트 주변에 필요한 요소들(속성 창 등)을 추가하고 이벤트 핸들러를 설치합니다.
- editor.finalize(): editor.initialize의 반대 역할로, 추가했던 요소들과 이벤트 핸들러 등을 모두 지워서 textarea를 다시 원래대로 되돌려 놓습니다. 이 메소드는 다른 편집기를 선택할 때 현재 편집기를 비활성화시키기 위해 꼭 필요합니다.
- editor.syncTextarea(): 원래 textarea가 아닌 다른 곳(위지윅 에디터 같은 경우)에서 편집이 일어 나고 있을 때 그 변경 사항을 원래 textarea에 동기화하는 역할을 합니다. textarea를 그대로 쓴다면 아무 일도 하지 않습니다.
- edtior.addObject(data): 편집기에 data로 명시된 그림·동영상·주크박스 등을 삽입합니다. data는 {mode: 'Image1L', objects: [...]} 형태의 오브젝트로 objects 속성에는 추가되어야 할 것들의 경로와 옵션, 설명 등이 배열로 들어 있습니다. 성공하면 true를, 아니면 false를 반환합니다.
거꾸로 편집기에서는 (아마 편집기에서나 쓰일 법한) 다음과 같음 전역 함수를 사용할 수 있습니다.
- savePosition(textarea): 주어진 textarea의 현재 선택 영역을 보존합니다. insertTag와 함께 쓰입니다.
- insertTag(textarea, prefix, postfix): 주어진 textarea의 현재 선택 영역의 앞뒤에 prefix와 postfix라는 텍스트를 추가합니다.
- editorChanged(): entryManager에게 현재 편집기에 있는 내용이 바뀌었음을 알립니다. 자동 저장에 쓰입니다.
포매터 인터페이스
포매터 인터페이스는 두 개의 PHP 함수로 구현됩니다. 하나는 텍스트를 받아서 HTML로 변환하는 함수(formatfunc)이고, 또 하나는 텍스트를 받아서 RSS 등에 쓸 수 있는 짧은 요약문을 반환하는 함수(summaryfunc)입니다. 이 때 주의할 점은 summaryfunc은 전문·부분 공개 설정을 읽어서 적절히 잘라 주고 하는 역할도 갖고 있다는 것입니다. (나중에 코어가 바뀌는 것을 염두에 두고 이렇게 했습니다.) formatfunc과 summaryfunc은 같은 형태의 인자를 받는데,
function formatfunc($owner, $id, $content, $keywords = array(), $useAbsolutePath = false);
// $owner, $id: 해당 글이 어디에 속해 있는지를 보임.
// $content: 변환될 글의 내용.
// $keywords: 키워드의 목록. 배열이 아니면 키워드 기능을 무시함. (키워드가 실제로 어떻게 해석되는지는 포매터마다 다름)
// $useAbsolutePath: 참이면 URL에 절대 경로를 사용하도록 함.
여기서 텍스트를 어떻게 지지고 볶는 지는 포매터 나름입니다.
편집기·포매터 플러그인
이번 작업으로 편집기와 포매터를 위한 플러그인 XML 포맷이 새로 생겼으며, 다음과 같습니다.
<binding>
<formatter xml:lang="ko" id="markdown" name="마크다운 포매터">
<format>FM_Markdown_format</format>
<summary>FM_Markdown_summary</summary>
<usedFor editor="markdown">마크다운 마크업</usedFor>
<usedFor editor="plain" />
</formatter>
<editor xml:lang="ko" id="markdown" name="마크다운 편집기">
<initialize>FM_Markdown_editorinit</initialize>
<usedFor formatter="markdown">마크다운 마크업</usedFor>
</editor>
</binding>
<formatter> 및 <editor> 엘리먼트는 각 편집기·포매터를 식별하기 위한 식별자(id)를 갖습니다. 이 식별자는 꼭 필요하며, 다른 플러그인과 겹치지 않도록 적절히 잡아 주시는 게 좋습니다. (이 속성은 한 플러그인이 서로 비슷한 여러 편집기·포매터를 가질 경우를 감안하여 추가되었습니다.)
<format>, <summary>, <initialize>는 각각 HTML 변환 및 요약(포매터)과 초기화 코드(편집기)를 명시합니다. format과 summary는 앞에서 설명한 formatfunc과 summaryfunc과 같고, initialize는 해당 편집기 정보를 담은 하나의 인자를 받아서 편집기 초기화에 사용될 자바스크립트 코드를 반환합니다. initialize의 반환값은 switch문-_-에 들어 가기 때문에, 성공하면 return 문으로 해당 오브젝트를 반환하도록 하고 실패하면 break;로 빠져 나가 기본 편집기를 쓰도록 해야 합니다.
<usedFor>는 편집기와 포매터 사이의 연관 관계를 표시합니다. 예를 들어서 위의 설정에서는 "마크다운 포매터는 마크다운 편집기(markdown)와 기본 편집기(plain)에 쓰일 수 있다"는 것을 나타냅니다. 안에 텍스트 데이터가 있으면 해당 편집기-포매터 짝에 별도의 이름을 붙여 줍니다. (물론 아직 쓰이지는 않습니다만...) 편집기와 포매터가 서로를 가리키는 선언을 가지고 있어도 됩니다만, 이름이 따로 붙여져 있을 경우 둘 중 어느 게 사용될 지는 알 수 없습니다. (따라서 같은 이름을 쓸 것을 권장합니다)
아무 편집기·포매터 플러그인도 없을 때는 기본적으로 HTML 포매터(html)와 날 텍스트 편집기(plain)만이 사용됩니다. 이 포매터와 편집기는 기본적인 작업만을 수행하며, 새로운 플러그인을 만들고자 할 때 참고할 수 있고 편집기가 따로 없는 포매터를 위해서도 사용할 수 있습니다.
기타 코어 수정
- <prefix>Entries 테이블에 편집기와 포매터를 나타내는 두 개의 필드가 추가되었습니다.
- XML 백업 포맷에 편집기와 포매터 속성이 추가되었습니다. 속성이 없으면 현재 기본 설정을 사용하기 때문에 호환성은 유지됩니다.
- language/messages.php가 추가되었습니다. 이 파일은 관리자 언어 설정에 따라 자바스크립트 코드를 생성하며, PHP랑 비슷하게 _t() 함수를 구현합니다. 자바스크립트 등에서 지역화된 문자열이 필요할 때 쓸 수 있습니다. (N.B. 이 코드는 블로그 언어 설정에서도 쓸 수 있게 하는 게 좋겠지만 약간 모호한 경우가 있어서 뒤로 미룹니다.)
- 편집기에서 사용하는 자바스크립트 및 CSS 등을 추가할 수 있게 하기 위한 ShowAdminHeader 이벤트가 추가되었습니다. 각 함수는 알아서 $suri를 파싱해서 여기에 출력을 해야 하나 말아야 하나를 결정해야 합니다;
- EAF의 STD.addEventListener가 addEventListener 외에도 removeEventListener를 추가하도록 했습니다. 용도는 물론 편집기 finalize 메소드에서 쓰려고...
이상이 r3035(와 글 쓰면서 수정한-_- r3036)의 수정 목록입니다. 질문이 있거나 버그가 있거나(...없기를-_-) 심심하거나 <del>밥 뜯어내고 싶다거나</del> 하면 이 글타래에 달아 주세요.