Skip to main content
← 블로그

표 하나를 고치려면 파서 세 벌을 고쳐야 한다 — 설명 표의 셀 병합과 셀 배경

VauDium·

할 일 설명 안의 표에 셀 병합과 셀 배경색을 넣었습니다. 데스크톱만 만지면 될 줄 알았는데, 같은 문서를 iOS·Android 네이티브 에디터가 각자 파싱합니다. 포맷 하나를 정하고, 파서 세 벌을 맞추고, 흩어져 있던 표 메뉴를 톱니 하나로 모은 이야기입니다.

표 하나를 고치려면 파서 세 벌을 고쳐야 한다 — 설명 표의 셀 병합과 셀 배경

페칫의 할 일 설명은 리치 에디터입니다. 그 안에 표를 넣을 수 있고, 열 폭을 끌어 바꾸고, 열과 행을 더하고 뺄 수 있습니다. 없던 것은 둘이었습니다. 셀을 합치는 것셀에 색을 칠하는 것. 회의 안건표에서 제목 행을 가로로 합치거나, 완료된 칸을 연하게 칠하는, 표를 표답게 쓰는 데 필요한 기본기입니다. 오늘 이 둘을 넣었습니다.

1. 데스크톱만 고칠 수는 없었다

처음 생각은 단순했습니다. 데스크톱 에디터가 ProseMirror 기반이니 표 확장에 병합과 배경 속성을 얹으면 끝이라고요. 그런데 페칫의 설명 문서는 세 곳에서 읽힙니다. 데스크톱의 JS 파서, iOS의 Swift 파서, Android의 Kotlin 파서. 모바일 에디터는 네이티브로 짜여 있고, 같은 문서 포맷을 각자 파싱해 각자 그립니다.

데스크톱에서만 병합을 저장하면 어떻게 될까요. 모바일이 그 문서를 열고 속성을 모른 채 다시 저장하는 순간, 병합이 풀립니다. 그래서 순서가 정해졌습니다. 포맷을 먼저 정하고, 세 벌을 함께 고치고, 모바일을 스토어에 먼저 내보낸다. 코드블록의 언어 표시를 넣을 때 겪었던 것과 같은 제약입니다.

2. 포맷 — 자유 색은 없다

셀 하나는 이렇게 생겼습니다. 열 폭, 가로 병합 수, 세로 병합 수, 배경. 속성 순서는 고정이고, 병합 수는 1보다 클 때만 씁니다. 세 파서가 같은 문자열을 내놓아야 왕복 저장이 안전하니까요.

배경색은 자유 hex를 허용하지 않기로 했습니다. 페칫에는 이미 포인트 컬러 팔레트가 있습니다. 할 일의 별 색, 꼬리표 색, 프로젝트 색이 전부 그 열몇 개의 이름에서 나옵니다. 셀 배경도 그 이름 중 하나만 받습니다. 저장되는 건 “lemon” 같은 키이고, 실제 색은 각 플랫폼이 라이트·다크 테마에 맞춰 풀어 씁니다. 다크 모드에서 문서를 열었을 때 라이트용 노랑이 그대로 박혀 있는 일이 없습니다. 알파 0.3으로 옅게 칠하는 것도 세 플랫폼이 같습니다.

행 배열에는 소유 셀만 둡니다. 병합에 덮인 칸은 문서에 없습니다. HTML의 표가 원래 그렇게 생겼고, 네이티브 쪽에서 점유 격자를 따로 계산해 그립니다.

3. 메뉴를 하나로 모으다

기능보다 오래 걸린 건 조작 UI였습니다. 데스크톱 표에는 이미 열 핸들과 행 핸들이 있었고, 각 핸들에서 열 메뉴·행 메뉴가 나왔습니다. 여기에 병합·분할·셀 색을 더하려니, 셀을 고르면 셀 액션 필이 하나 더 뜨고, 색은 인라인 피커가 또 뜨는 식으로 층이 쌓였습니다. 만들어 보고 걷어냈습니다.

최종 모양은 이렇습니다. 셀을 선택하면 열·행 핸들은 숨고, 선택 박스 우상단에 톱니 하나만 남습니다. 그 톱니에서 나오는 메뉴 하나가 셀(병합·분할·셀 색), 열(헤더 열·왼쪽/오른쪽에 열 추가·열 삭제), 행(헤더 행·위/아래에 행 추가·행 삭제), 그리고 전체가 선택됐을 때는 테이블 삭제까지 전부 담습니다. 전 항목이 아이콘과 이름으로 정렬돼 있고, 셀 색은 항목을 누르면 메뉴 옆으로 색 패널이 한 단계 펼쳐집니다. 오른쪽에 자리가 없으면 왼쪽으로 갑니다. 핸들을 누르면 선택과 메뉴가 한 번에 열리는 기존 동작은 그대로 두었습니다.

작은 것도 하나 고쳤습니다. 셀 라이브러리의 기본 동작은 같은 칸을 Shift+클릭했을 때 아무 일도 하지 않습니다. 다른 칸으로만 확장하거든요. 한 칸만 골라 색을 칠하려면 그 동작이 필요해서, 같은 칸 Shift+클릭을 한 칸 선택으로 정의했습니다. 선택 범위를 늘리는 그립은 표 경계 안에서 늘릴 수 있는 방향, 두 칸 이상이라 줄일 수 있는 축의 변 중앙에만 나타나고, 드래그는 한 축만 바꿉니다. 열 경계는 첫 행의 DOM을 세지 않고 표 맵으로 계산합니다. 첫 행에 병합 셀이 있으면 DOM 세기가 어긋나거든요.

4. 모바일은 보여 주고 지킨다

모바일에서는 병합과 색을 조작할 수 없습니다. 표시하고, 보존합니다. 세 벌 파서를 맞춘 이유가 이겁니다. 데스크톱에서 합친 셀은 iOS와 Android에서 한 상자로 그려지고, 세로 병합이 표 끝을 넘으면 마지막 행에서 멈춥니다. 모바일에서 행이나 열을 더하고 빼면 병합 수가 따라 늘고 줄어, 다시 저장해도 데스크톱에서 열었을 때 그대로입니다. 색은 테마별 팔레트를 네이티브에 넘겨 같은 알파로 칠합니다.

병합 메뉴를 모바일에 넣지 않은 건 화면 크기의 문제라기보다 우선순위의 문제입니다. 표 구조를 설계하는 일은 큰 화면에서 하고, 작은 화면은 읽고 체크하는 데 씁니다. 그 경계를 오늘은 넘지 않았습니다.

남긴 것

이 커밋은 런타임을 아직 충분히 돌리지 못했습니다. 데스크톱과 모바일 JS의 타입 체크, iOS의 Swift 파싱은 통과했고, Android는 컴파일 전입니다. 배포 순서도 지켜야 합니다. 모바일 스토어가 먼저, 데스크톱이 그 다음. 표 포맷을 또 넓힐 일이 생기면 오늘과 같은 순서로 갑니다. 포맷, 파서 세 벌, 네이티브 레이아웃, 그리고 스토어.