본문으로 건너뛰기

편집기

서식 · 멘션이 조금 필요한 짧은 글 칸입니다. 입력칸과 같은 파인 칸이고, 마크다운으로 받고 마크다운으로 냅니다. 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).

키koenja
label · placeholder댓글 · 댓글 입력Comment · Write a commentコメント · コメントを入力
submit · cancel댓글 남기기 · 취소Comment · Cancelコメントする · キャンセル
remaining · over60자 남음 · 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 가 줄바꿈이다
valuestring—제어 값(마크다운). 주면 onChange 로 받은 값을 다시 넘겨야 한다
defaultValuestring—비제어 첫 값(마크다운)
onChange(markdown: string) => void—내용이 바뀔 때마다 마크다운으로 받는다. 편집기 내부 형식(Tiptap JSON)은 밖으로 내지 않는다
onSubmit(markdown: string) => void | Promise<void>—⌘/Ctrl+Enter(message 는 Enter 도) 또는 남기기 버튼. 빈 내용 · 넘침 · 조합 중 · 보내는 중에는 부르지 않는다. Promise 를 돌려주면 status 를 스스로 관리하고, 끝나면 비제어 값을 비운다
onCancel() => void—Esc 또는 취소 버튼. 멘션 후보 → 말풍선 메뉴 → (답장) → 취소 순으로 하나만 닫는다
maxLengthnumber—보이는 글자 수 상한(멘션은 이름 길이로 센다). 90% 부터 남은 글자, 넘으면 오류 + 남기기 막음
mentions(query: string) => MentionItem[] | Promise<MentionItem[]>—@ · @ 뒤 글자로 후보를 찾는다. 주지 않으면 후보 목록을 열지 않는다(멘션 읽기 · 저장은 된다). 비동기 허용
channels(query: string) => MentionItem[] | Promise<MentionItem[]>—message: # 뒤 글자로 채널 후보를 찾는다. [#이름](channel:id) 로 저장한다
placeholderstring—빈 칸 안내. 없으면 labels.placeholder
disabledbooleanfalse
readOnlybooleanfalse
status"idle" | "sending" | "error"—onSubmit 이 Promise 가 아닐 때 앱이 넘긴다. sending: 편집을 멈추고 버튼 스피너. error: 내용은 남기고 칸 아래에 이유 + 다시 보내기
errorReactNode—error 일 때 보일 문장. 기본 labels.sendFailed
onRetry() => void—다시 보내기. 없으면 onSubmit 을 다시 부른다
actionsReactNode | false—아래 줄 동작. 기본: onSubmit 이 있으면 [취소][남기기 ⌘↵](comment). false 면 그리지 않고, ReactNode 면 바꾼다
toolbar"bubble" | "fixed" | false"bubble"서식 막대. 기본 "bubble"(고른 글 위). "fixed" 는 칸 위 고정 막대
enterToSendbooleantruemessage: false 면 Enter 가 새 문단이고 ⌘/Ctrl+Enter 로 보낸다(사용자 설정)
replyTo{ author: string; excerpt: string }—message: 답장 머리(누구의 무엇에 답하는지)
onCancelReply() => void—message: 답장 머리의 ×, 또는 답장 중 Esc
editingbooleanfalsemessage: 수정 모드. 아래에 키 안내 + [취소][저장], Enter 가 저장(onSubmit)이다
onRequestDelete() => void—message 수정 모드에서 내용을 다 지우고 저장했을 때
onTyping(typing: boolean) => void—message: 쓰는 중 신호. true 는 3초에 한 번까지만, 보내거나 비우면 false
aria-labelledbystring—보이는 label 이 있으면 그 id. 없으면 labels.label 이 접근성 이름
aria-describedbystring—
idstring—편집 영역(textbox)의 id. Field 의 label for 가 가리킨다
autoFocusbooleanfalse
lang"ko" | "en" | "ja""ko"내장 문구와 편집 영역의 lang. IME · 맞춤법 검사 · 화면 낭독기가 이 언어로 다룬다
labelsPartial<RichTextEditorLabels>—
classNamestring—
refRef<RichTextEditorHandle>—

EditorFallback props

./ui. Tiptap 을 가져오지 않는 지연 로드 자리입니다.

prop타입기본값설명
preset"comment" | "message" | "document""comment"불러올 편집기의 모양. comment 64 · message 40 · document 는 툴바 + 본문
lang"ko" | "en" | "ja"—
labelsPartial<EditorFallbackLabels>—
classNamestring—