편집기
서식 · 멘션이 조금 필요한 짧은 글 칸입니다. 입력칸과 같은 파인 칸이고, 마크다운으로 받고 마크다운으로 냅니다. Tiptap JSON 은 밖으로 나가지 않습니다(ADR 0007). 서식은 글을 고르면 뜨는 말풍선 메뉴 · 단축키 · 마크다운 입력으로 겁니다.
- import
- import { RichTextEditor } from "@uyoolabs/ui/editor"; // 지연 로드
- 쓰는 경우
- 댓글 · 리뷰 의견 · 결정 메모(comment), 채널 · 스레드 · DM 의 작성창(message — ./chat 의 Composer 가 감쌉니다). 글자 수 상한이 있으면 maxLength, 보내지 못하면 내용을 지우지 않고 칸 아래에 이유와 [다시 보내기]를 둡니다.
- 쓰지 않는 경우
- 서식이 필요 없는 한 줄 · 여러 줄은 Input · Textarea 입니다. 문서 본문 · 제목 · 표를 편집하려면 문서 편집기(./editor/document)입니다. 첫 화면 번들에 넣지 않습니다(지연 로드). 원문 블록을 HTML 로 그리지 않습니다. 글자 수를 처음부터 빨갛게 보이지 않습니다(넘쳤을 때만 오류 색).
- peer
- @tiptap/core · pm · react · starter-kit · markdown · extension-mention · extension-list · extension-paragraph · extension-hard-break · suggestion(모두 같은 정확한 버전) · marked · radix-ui
- CSS
- @uyoolabs/ui/css 에 들어 있습니다(css/carton/editor.css)
지연 로드
편집기 진입점은 gzip 약 190 KB 입니다(./ui 의 7배). React.lazy · next/dynamic 으로 따로 받고, 그동안 ./ui 의 EditorFallback 이 같은 높이의 자리를 그립니다.
comment 프리셋
⌘/Ctrl+Enter 로 남기고 Esc 로 취소합니다. @ · @ 로 사람을 부르고, 글을 고르면 서식 막대가 뜹니다. onSubmit 이 Promise 면 보내는 중 · 실패를 스스로 보이고, 끝나면 비제어 값을 비웁니다.
message 프리셋
Enter 가 전송, Shift+Enter 가 줄바꿈입니다(목록 안에서는 다음 항목). 답장 머리 · 수정 모드(editing) · 쓰는 중 신호(onTyping) · 채널 참조를 더합니다. 첨부 · 이모지 · 보내기 막대는 ./chat 의 Composer 가 감쌉니다.
마크다운 계약
docs/adr/0007 · docs/spikes/0001
| 무엇 | 마크다운 | 메모 |
|---|---|---|
| 줄바꿈 | 홑 줄바꿈 | breaks: true. 줄 끝 공백 두 칸을 쓰지 않습니다 |
| 멘션 | [@이름](mention:id) | 멘션을 모르는 렌더러에서도 ‘@이름’ 링크로 보입니다 |
| 채널 참조(message) | [#이름](channel:id) | 멘션과 같은 꼴입니다(결정 #33) |
| 취소선 | ~~글~~ | 물결표 하나는 글자입니다(3~5일 → 3\~5일) |
| 편집하지 않는 블록 | 제목 · 표 · 할 일 · 이미지 · 구분선 · 원시 HTML · 머리말 | 읽기 전용 원문 블록으로 한 글자도 바꾸지 않고 지킵니다. 글자로만 보이고(innerHTML 안 씀) 통째로 지울 수 있습니다 |
키보드 — comment
| 키 | 동작 |
|---|---|
| ⌘/CtrlEnter | 남깁니다. 빈 내용 · 넘침 · 조합 중 · 보내는 중이면 하지 않습니다 |
| Esc | 멘션 후보 → 말풍선 메뉴 → 취소(onCancel) 순으로 하나만 닫습니다 |
| Enter | 새 문단. 멘션 후보가 열려 있으면 고릅니다 |
| ShiftEnter | 같은 문단 안 줄바꿈 |
| @@ | 멘션 후보를 엽니다. ↑ ↓ 로 옮기고 Enter · Tab 으로 고릅니다 |
| ⌘B⌘I⌘⇧S⌘E | 굵게 · 기울임 · 취소선(⌘⇧X 도) · 인라인 코드 |
| ⌘K | 링크 팝오버(주소 · 적용 · 링크 삭제). 닫히면 글로 돌아옵니다 |
| ⌘⇧8⌘⇧7⌘⇧B⌘⌥C | 글머리 · 번호 목록 · 인용 · 코드 블록 |
| AltF10 | 서식 막대로 갑니다. ← → 로 옮기고 Esc 로 글에 돌아옵니다 |
| Tab | 편집기 밖으로 나갑니다(가두지 않습니다). 목록 안에서만 들여쓰기 |
키보드 — message
| 키 | 동작 |
|---|---|
| Enter | 보냅니다. 빈 내용이면 하지 않고, 조합 중이면 확정만 합니다 |
| ShiftEnter | 줄바꿈. 목록 안에서는 다음 항목 |
| ⌘/CtrlEnter | 보냅니다(enterToSend={false} 인 사람의 전송 키) |
| Esc | 후보 → 말풍선 메뉴 → 답장 · 수정 순으로 하나씩 닫습니다. 쓰던 글은 지우지 않습니다 |
| @@# | 멘션 · 채널 후보. Esc 로 닫은 @ 는 그 @ 가 사라질 때까지 다시 열지 않습니다 |
IME
Enter · Esc · 후보 고르기 모두 공용 isComposing 가드를 먼저 거칩니다. 한국어 · 일본어는 조합을 확정하는 데 Enter 를 쓰고, Safari 는 확정 뒤 keyCode 229 keydown 을 한 번 더 보냅니다.
- 조합 중 Enter
- 제출 · 전송 · 후보 고르기를 하지 않습니다(확정만). 목록은 조합 중에도 열립니다
- 제어 값
- 조합 중에 바뀐 value 는 버리지 않고 compositionend 뒤에 적용합니다. 조합 중에 갈아 끼우면 글자가 두 번 들어갑니다
- 실기 확인
- 헤드리스 Chromium 은 CDP 로 흉내만 냅니다. macOS 2벌식 · Kotoeri · MS-IME · Gboard 확인 목록은 docs/checks/editor.md 에 있습니다
내장 문구
lang(ko · en · ja)으로 고르고 labels 로 일부를 바꿉니다(ADR 0011).
| 키 | ko | en | ja |
|---|---|---|---|
| label · placeholder | 댓글 · 댓글 입력 | Comment · Write a comment | コメント · コメントを入力 |
| submit · cancel | 댓글 남기기 · 취소 | Comment · Cancel | コメントする · キャンセル |
| remaining · over | 60자 남음 · 12자 넘음 | 60 characters left · 12 over the limit | 残り60文字 · 12文字超過 |
| rawSource · rawKind | 편집할 수 없는 원문 · 표 | Read-only source · Table | 編集できない原文 · 表 |
| message: label · submit | 메시지 · 보내기 | Message · Send | メッセージ · 送信 |
RichTextEditor props
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| preset | "comment" | "message" | "comment" | comment(W3, 기본)는 ⌘↵ 로 남기고 Enter 가 새 문단이다. message(W4)는 Enter 가 전송이고 Shift+Enter 가 줄바꿈이다 |
| value | string | — | 제어 값(마크다운). 주면 onChange 로 받은 값을 다시 넘겨야 한다 |
| defaultValue | string | — | 비제어 첫 값(마크다운) |
| onChange | (markdown: string) => void | — | 내용이 바뀔 때마다 마크다운으로 받는다. 편집기 내부 형식(Tiptap JSON)은 밖으로 내지 않는다 |
| onSubmit | (markdown: string) => void | Promise<void> | — | ⌘/Ctrl+Enter(message 는 Enter 도) 또는 남기기 버튼. 빈 내용 · 넘침 · 조합 중 · 보내는 중에는 부르지 않는다. Promise 를 돌려주면 status 를 스스로 관리하고, 끝나면 비제어 값을 비운다 |
| onCancel | () => void | — | Esc 또는 취소 버튼. 멘션 후보 → 말풍선 메뉴 → (답장) → 취소 순으로 하나만 닫는다 |
| maxLength | number | — | 보이는 글자 수 상한(멘션은 이름 길이로 센다). 90% 부터 남은 글자, 넘으면 오류 + 남기기 막음 |
| mentions | (query: string) => MentionItem[] | Promise<MentionItem[]> | — | @ · @ 뒤 글자로 후보를 찾는다. 주지 않으면 후보 목록을 열지 않는다(멘션 읽기 · 저장은 된다). 비동기 허용 |
| channels | (query: string) => MentionItem[] | Promise<MentionItem[]> | — | message: # 뒤 글자로 채널 후보를 찾는다. [#이름](channel:id) 로 저장한다 |
| placeholder | string | — | 빈 칸 안내. 없으면 labels.placeholder |
| disabled | boolean | false | |
| readOnly | boolean | false | |
| status | "idle" | "sending" | "error" | — | onSubmit 이 Promise 가 아닐 때 앱이 넘긴다. sending: 편집을 멈추고 버튼 스피너. error: 내용은 남기고 칸 아래에 이유 + 다시 보내기 |
| error | ReactNode | — | error 일 때 보일 문장. 기본 labels.sendFailed |
| onRetry | () => void | — | 다시 보내기. 없으면 onSubmit 을 다시 부른다 |
| actions | ReactNode | false | — | 아래 줄 동작. 기본: onSubmit 이 있으면 [취소][남기기 ⌘↵](comment). false 면 그리지 않고, ReactNode 면 바꾼다 |
| toolbar | "bubble" | "fixed" | false | "bubble" | 서식 막대. 기본 "bubble"(고른 글 위). "fixed" 는 칸 위 고정 막대 |
| enterToSend | boolean | true | message: false 면 Enter 가 새 문단이고 ⌘/Ctrl+Enter 로 보낸다(사용자 설정) |
| replyTo | { author: string; excerpt: string } | — | message: 답장 머리(누구의 무엇에 답하는지) |
| onCancelReply | () => void | — | message: 답장 머리의 ×, 또는 답장 중 Esc |
| editing | boolean | false | message: 수정 모드. 아래에 키 안내 + [취소][저장], Enter 가 저장(onSubmit)이다 |
| onRequestDelete | () => void | — | message 수정 모드에서 내용을 다 지우고 저장했을 때 |
| onTyping | (typing: boolean) => void | — | message: 쓰는 중 신호. true 는 3초에 한 번까지만, 보내거나 비우면 false |
| aria-labelledby | string | — | 보이는 label 이 있으면 그 id. 없으면 labels.label 이 접근성 이름 |
| aria-describedby | string | — | |
| id | string | — | 편집 영역(textbox)의 id. Field 의 label for 가 가리킨다 |
| autoFocus | boolean | false | |
| lang | "ko" | "en" | "ja" | "ko" | 내장 문구와 편집 영역의 lang. IME · 맞춤법 검사 · 화면 낭독기가 이 언어로 다룬다 |
| labels | Partial<RichTextEditorLabels> | — | |
| className | string | — | |
| ref | Ref<RichTextEditorHandle> | — |
EditorFallback props
./ui. Tiptap 을 가져오지 않는 지연 로드 자리입니다.
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| preset | "comment" | "message" | "document" | "comment" | 불러올 편집기의 모양. comment 64 · message 40 · document 는 툴바 + 본문 |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<EditorFallbackLabels> | — | |
| className | string | — |