@bavuchoko/js-editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/dist/Editor.d.ts +39 -0
- package/dist/Editor.d.ts.map +1 -0
- package/dist/TableSelection.d.ts +8 -0
- package/dist/TableSelection.d.ts.map +1 -0
- package/dist/Toolbar.d.ts +32 -0
- package/dist/Toolbar.d.ts.map +1 -0
- package/dist/commands.d.ts +22 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/document.d.ts +110 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/history.d.ts +26 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2343 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +2 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/sanitize.d.ts +3 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/selection.d.ts +32 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/style.css +2 -0
- package/dist/theme.d.ts +9 -0
- package/dist/theme.d.ts.map +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# @bavuchoko/js-editor
|
|
2
|
+
|
|
3
|
+
React용 경량 WYSIWYG 에디터입니다.
|
|
4
|
+
`contentEditable` + Selection API로 구현되어 있으며 TipTap / Lexical / ProseMirror 같은 외부 에디터 엔진에 의존하지 않습니다.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install @bavuchoko/js-editor
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
peer dependency:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install react react-dom
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
React 18 이상을 지원합니다.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { useRef } from 'react'
|
|
24
|
+
import { Editor, type EditorHandle } from '@bavuchoko/js-editor'
|
|
25
|
+
import '@bavuchoko/js-editor/style.css'
|
|
26
|
+
|
|
27
|
+
function App() {
|
|
28
|
+
const editorRef = useRef<EditorHandle>(null)
|
|
29
|
+
|
|
30
|
+
const save = () => {
|
|
31
|
+
// 권장: JSON으로 저장
|
|
32
|
+
const json = editorRef.current?.getJson()
|
|
33
|
+
// await api.save(json)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const load = (doc: unknown) => {
|
|
37
|
+
editorRef.current?.setJson(doc)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Editor
|
|
42
|
+
ref={editorRef}
|
|
43
|
+
theme={{
|
|
44
|
+
toolbar: { background: '#fff' },
|
|
45
|
+
content: { padding: 24, minHeight: 320 },
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
스타일(`style.css`)은 반드시 함께 import 해야 합니다.
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
| 영역 | 지원 |
|
|
57
|
+
|------|------|
|
|
58
|
+
| 인라인 | 굵게, 기울임, 밑줄, 글자색, 배경색 |
|
|
59
|
+
| 블록 | 본문, Heading 1–3, 인용, 코드 블록 |
|
|
60
|
+
| 목록 | 글머리, 번호, 체크리스트 |
|
|
61
|
+
| 정렬 | 왼쪽 / 가운데 / 오른쪽 |
|
|
62
|
+
| 테이블 | 삽입, 셀 드래그 선택, 셀 병합, Tab/Enter 이동 |
|
|
63
|
+
| 단축키 | Undo/Redo, 마크다운 입력 |
|
|
64
|
+
| 보안 | 붙여넣기·`initialHTML`·`setHtml` sanitize |
|
|
65
|
+
|
|
66
|
+
플레이스홀더 문구: `내용을 입력하세요`
|
|
67
|
+
|
|
68
|
+
## Keyboard shortcuts
|
|
69
|
+
|
|
70
|
+
| 단축키 | 동작 |
|
|
71
|
+
|--------|------|
|
|
72
|
+
| `Ctrl+Z` / `Cmd+Z` | Undo |
|
|
73
|
+
| `Ctrl+Shift+Z` / `Cmd+Shift+Z` | Redo |
|
|
74
|
+
| `Ctrl+Y` / `Cmd+Y` | Redo |
|
|
75
|
+
| `Tab` (테이블 셀) | 다음 셀로 이동 |
|
|
76
|
+
| `Enter` (테이블 셀) | 아래 셀로 이동 |
|
|
77
|
+
|
|
78
|
+
툴바에 Undo/Redo 버튼은 없고 단축키만 지원합니다.
|
|
79
|
+
|
|
80
|
+
## Markdown shortcuts
|
|
81
|
+
|
|
82
|
+
줄 시작에서 입력 후 스페이스:
|
|
83
|
+
|
|
84
|
+
| 입력 | 결과 |
|
|
85
|
+
|------|------|
|
|
86
|
+
| `# ` | Heading 1 |
|
|
87
|
+
| `## ` | Heading 2 |
|
|
88
|
+
| `### ` | Heading 3 |
|
|
89
|
+
| `> ` | 인용 |
|
|
90
|
+
| `- ` 또는 `* ` | 글머리 목록 |
|
|
91
|
+
| `1. ` | 번호 목록 |
|
|
92
|
+
| `` ``` `` + 스페이스 | 코드 블록 |
|
|
93
|
+
|
|
94
|
+
인라인:
|
|
95
|
+
|
|
96
|
+
| 입력 | 결과 |
|
|
97
|
+
|------|------|
|
|
98
|
+
| `**텍스트** ` | 굵게 |
|
|
99
|
+
| `*텍스트* ` | 기울임 |
|
|
100
|
+
|
|
101
|
+
## API
|
|
102
|
+
|
|
103
|
+
### `Editor` props
|
|
104
|
+
|
|
105
|
+
| prop | 타입 | 설명 |
|
|
106
|
+
|------|------|------|
|
|
107
|
+
| `initialHTML` | `string` | 초기 HTML (sanitize 후 적용) |
|
|
108
|
+
| `theme` | `EditorTheme` | `toolbar` / `content`에 `CSSProperties` |
|
|
109
|
+
| `className` / `style` | | 루트 요소 |
|
|
110
|
+
| `toolbarClassName` / `toolbarStyle` | | 툴바 (theme.toolbar와 병합) |
|
|
111
|
+
| `contentClassName` / `contentStyle` | | 본문 (theme.content와 병합) |
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
type EditorTheme = {
|
|
115
|
+
toolbar?: CSSProperties
|
|
116
|
+
content?: CSSProperties
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `ref` methods (`EditorHandle`)
|
|
121
|
+
|
|
122
|
+
| method | 설명 |
|
|
123
|
+
|--------|------|
|
|
124
|
+
| `getHtml()` | 현재 본문 HTML |
|
|
125
|
+
| `setHtml(html)` | HTML 로드 (sanitize + 히스토리 리셋) |
|
|
126
|
+
| `getJson()` | 현재 본문 JSON (`EditorDoc`) |
|
|
127
|
+
| `setJson(doc)` | JSON 로드 (HTML 변환 + sanitize + 히스토리 리셋) |
|
|
128
|
+
| `getText()` | plain text |
|
|
129
|
+
| `focus()` | 편집 영역 포커스 |
|
|
130
|
+
|
|
131
|
+
### 유틸 export
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import {
|
|
135
|
+
htmlToJson,
|
|
136
|
+
jsonToHtml,
|
|
137
|
+
domToJson,
|
|
138
|
+
normalizeDoc,
|
|
139
|
+
sanitizeHTML,
|
|
140
|
+
sanitizePlainText,
|
|
141
|
+
type EditorDoc,
|
|
142
|
+
type BlockNode,
|
|
143
|
+
type InlineNode,
|
|
144
|
+
type Mark,
|
|
145
|
+
} from '@bavuchoko/js-editor'
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| 함수 | 설명 |
|
|
149
|
+
|------|------|
|
|
150
|
+
| `htmlToJson(html)` | HTML → `EditorDoc` |
|
|
151
|
+
| `jsonToHtml(doc)` | `EditorDoc` → HTML |
|
|
152
|
+
| `domToJson(element)` | DOM 노드 → `EditorDoc` |
|
|
153
|
+
| `normalizeDoc(doc)` | 알 수 없는 입력을 안전한 `EditorDoc`으로 정규화 |
|
|
154
|
+
| `sanitizeHTML(html)` | 허용 태그/속성만 남김 |
|
|
155
|
+
| `sanitizePlainText(text)` | 텍스트만 남김 |
|
|
156
|
+
|
|
157
|
+
## Document JSON
|
|
158
|
+
|
|
159
|
+
저장·전송은 HTML보다 JSON을 권장합니다.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
type EditorDoc = {
|
|
163
|
+
type: 'doc'
|
|
164
|
+
content: BlockNode[]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
예시:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
{
|
|
172
|
+
"type": "doc",
|
|
173
|
+
"content": [
|
|
174
|
+
{
|
|
175
|
+
"type": "heading",
|
|
176
|
+
"attrs": { "level": 1 },
|
|
177
|
+
"content": [{ "type": "text", "text": "제목" }]
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"type": "paragraph",
|
|
181
|
+
"attrs": { "textAlign": "left" },
|
|
182
|
+
"content": [
|
|
183
|
+
{ "type": "text", "text": "안녕하세요, " },
|
|
184
|
+
{
|
|
185
|
+
"type": "text",
|
|
186
|
+
"text": "굵은 글씨",
|
|
187
|
+
"marks": [{ "type": "bold" }]
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"type": "taskList",
|
|
193
|
+
"content": [
|
|
194
|
+
{
|
|
195
|
+
"type": "taskItem",
|
|
196
|
+
"attrs": { "checked": false },
|
|
197
|
+
"content": [{ "type": "text", "text": "할 일" }]
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
지원 블록: `paragraph`, `heading`, `blockquote`, `codeBlock`, `bulletList`, `orderedList`, `taskList`, `table`
|
|
206
|
+
지원 마크: `bold`, `italic`, `underline`, `code`, `textStyle`(색), `highlight`(배경색)
|
|
207
|
+
|
|
208
|
+
## Security
|
|
209
|
+
|
|
210
|
+
- 붙여넣기, `initialHTML`, `setHtml` / `setJson` 경로에서 HTML을 sanitize 합니다.
|
|
211
|
+
- 허용되지 않은 태그·속성·이벤트 핸들러·위험한 URL은 제거됩니다.
|
|
212
|
+
- 서버에서도 저장 전에 검증하는 것을 권장합니다. JSON으로 저장하면 스키마 검증이 쉽습니다.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// 서버에서 받은 HTML을 넣을 때
|
|
216
|
+
editorRef.current?.setHtml(untrustedHtml) // 내부에서 sanitize
|
|
217
|
+
|
|
218
|
+
// 더 안전하게 JSON만 받기
|
|
219
|
+
editorRef.current?.setJson(docFromDb)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Develop
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm install
|
|
226
|
+
npm run dev # 데모 앱
|
|
227
|
+
npm run build # 라이브러리 빌드 (publish용)
|
|
228
|
+
npm run build:demo # 데모 앱 빌드
|
|
229
|
+
npm run pack:check # 빌드 + npm pack dry-run
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
package/dist/Editor.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
import { type EditorDoc } from './document';
|
|
3
|
+
import { type EditorTheme } from './theme';
|
|
4
|
+
export type EditorHandle = {
|
|
5
|
+
/** 현재 본문 HTML */
|
|
6
|
+
getHtml: () => string;
|
|
7
|
+
/** DB 등에서 불러온 HTML 넣기 (sanitize + 히스토리 리셋) */
|
|
8
|
+
setHtml: (html: string) => void;
|
|
9
|
+
/** 현재 본문 JSON */
|
|
10
|
+
getJson: () => EditorDoc;
|
|
11
|
+
/** DB 등에서 불러온 JSON 넣기 (HTML 변환 + sanitize + 히스토리 리셋) */
|
|
12
|
+
setJson: (doc: EditorDoc | unknown) => void;
|
|
13
|
+
/** 본문 텍스트만 */
|
|
14
|
+
getText: () => string;
|
|
15
|
+
/** 편집 영역에 포커스 */
|
|
16
|
+
focus: () => void;
|
|
17
|
+
};
|
|
18
|
+
export type EditorProps = {
|
|
19
|
+
initialHTML?: string;
|
|
20
|
+
/** 에디터 루트 클래스 */
|
|
21
|
+
className?: string;
|
|
22
|
+
/** 에디터 루트 인라인 스타일 */
|
|
23
|
+
style?: CSSProperties;
|
|
24
|
+
/**
|
|
25
|
+
* 툴바/본문 스타일
|
|
26
|
+
* @example theme={{ toolbar: { background: '#fff' }, content: { padding: 24 } }}
|
|
27
|
+
*/
|
|
28
|
+
theme?: EditorTheme;
|
|
29
|
+
/** 툴바 영역 클래스 */
|
|
30
|
+
toolbarClassName?: string;
|
|
31
|
+
/** 툴바 영역 인라인 스타일 (theme.toolbar와 병합) */
|
|
32
|
+
toolbarStyle?: CSSProperties;
|
|
33
|
+
/** 본문(페이퍼) 영역 클래스 */
|
|
34
|
+
contentClassName?: string;
|
|
35
|
+
/** 본문 편집 영역 인라인 스타일 (theme.content와 병합) */
|
|
36
|
+
contentStyle?: CSSProperties;
|
|
37
|
+
};
|
|
38
|
+
export declare const Editor: import("react").ForwardRefExoticComponent<EditorProps & import("react").RefAttributes<EditorHandle>>;
|
|
39
|
+
//# sourceMappingURL=Editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Editor.d.ts","sourceRoot":"","sources":["../src/editor/Editor.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,aAAa,EAGnB,MAAM,OAAO,CAAA;AAiBd,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,YAAY,CAAA;AAQnB,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAA;AAuB1C,MAAM,MAAM,YAAY,GAAG;IACzB,iBAAiB;IACjB,OAAO,EAAE,MAAM,MAAM,CAAA;IACrB,8CAA8C;IAC9C,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,iBAAiB;IACjB,OAAO,EAAE,MAAM,SAAS,CAAA;IACxB,wDAAwD;IACxD,OAAO,EAAE,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,KAAK,IAAI,CAAA;IAC3C,cAAc;IACd,OAAO,EAAE,MAAM,MAAM,CAAA;IACrB,iBAAiB;IACjB,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qBAAqB;IACrB,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB;;;OAGG;IACH,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,gBAAgB;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,wCAAwC;IACxC,YAAY,CAAC,EAAE,aAAa,CAAA;IAC5B,qBAAqB;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,aAAa,CAAA;CAC7B,CAAA;AAED,eAAO,MAAM,MAAM,sGAgQjB,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
type TableSelectionProps = {
|
|
3
|
+
editorRef: RefObject<HTMLDivElement | null>;
|
|
4
|
+
onMerge: (cells: HTMLTableCellElement[]) => void;
|
|
5
|
+
};
|
|
6
|
+
export declare function TableSelection({ editorRef, onMerge, }: TableSelectionProps): import("react").JSX.Element | null;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=TableSelection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableSelection.d.ts","sourceRoot":"","sources":["../src/editor/TableSelection.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEnE,KAAK,mBAAmB,GAAG;IACzB,SAAS,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAA;IAC3C,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,EAAE,KAAK,IAAI,CAAA;CACjD,CAAA;AAgJD,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,OAAO,GACR,EAAE,mBAAmB,sCA+NrB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ActiveState, AlignValue } from './selection';
|
|
2
|
+
export type ToolbarCommand = {
|
|
3
|
+
type: 'inline';
|
|
4
|
+
mark: 'bold' | 'italic' | 'underline';
|
|
5
|
+
} | {
|
|
6
|
+
type: 'block';
|
|
7
|
+
format: 'p' | 'h1' | 'h2' | 'h3' | 'blockquote' | 'pre' | 'ul' | 'ol' | 'checklist';
|
|
8
|
+
} | {
|
|
9
|
+
type: 'align';
|
|
10
|
+
align: AlignValue;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'textColor';
|
|
13
|
+
color: string | null;
|
|
14
|
+
} | {
|
|
15
|
+
type: 'highlightColor';
|
|
16
|
+
color: string | null;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'insertTable';
|
|
19
|
+
rows?: number;
|
|
20
|
+
cols?: number;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'undo';
|
|
23
|
+
} | {
|
|
24
|
+
type: 'redo';
|
|
25
|
+
};
|
|
26
|
+
type ToolbarProps = {
|
|
27
|
+
active: ActiveState;
|
|
28
|
+
onCommand: (command: ToolbarCommand) => void;
|
|
29
|
+
};
|
|
30
|
+
export declare function Toolbar({ active, onCommand }: ToolbarProps): import("react").JSX.Element;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=Toolbar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toolbar.d.ts","sourceRoot":"","sources":["../src/editor/Toolbar.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAsJ1D,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAA;CAAE,GACzD;IACE,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,YAAY,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,WAAW,CAAA;CACpF,GACD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AA2CpB,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,WAAW,CAAA;IACnB,SAAS,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAA;CAC7C,CAAA;AAmpBD,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,YAAY,+BAoF1D"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AlignValue } from './selection';
|
|
2
|
+
declare const INLINE_TAG_MAP: Record<string, string>;
|
|
3
|
+
export declare function toggleInline(editor: HTMLElement, mark: keyof typeof INLINE_TAG_MAP): boolean;
|
|
4
|
+
/** 체크리스트 체크박스 클릭 토글 */
|
|
5
|
+
export declare function handleChecklistClick(editor: HTMLElement, event: MouseEvent): boolean;
|
|
6
|
+
export declare function setBlockFormat(editor: HTMLElement, format: 'p' | 'h1' | 'h2' | 'h3' | 'blockquote' | 'pre' | 'ul' | 'ol' | 'checklist'): boolean;
|
|
7
|
+
export declare function setAlign(editor: HTMLElement, align: AlignValue): boolean;
|
|
8
|
+
export declare function setTextColor(editor: HTMLElement, color: string | null): boolean;
|
|
9
|
+
export declare function setHighlightColor(editor: HTMLElement, color: string | null): boolean;
|
|
10
|
+
export declare function insertHTML(editor: HTMLElement, html: string): boolean;
|
|
11
|
+
export declare function insertTable(editor: HTMLElement, rows?: number, cols?: number): boolean;
|
|
12
|
+
/** 사각 선택 영역의 셀들을 병합 (가로 colspan + 세로 rowspan) */
|
|
13
|
+
export declare function mergeCells(cells: HTMLTableCellElement[]): boolean;
|
|
14
|
+
/** @deprecated use mergeCells */
|
|
15
|
+
export declare function mergeColumnCells(cells: HTMLTableCellElement[]): boolean;
|
|
16
|
+
/** 현재 셀을 오른쪽 옆 셀과 열 병합 (colspan) */
|
|
17
|
+
export declare function mergeColumns(editor: HTMLElement): boolean;
|
|
18
|
+
export declare function handleTableTab(editor: HTMLElement, event: KeyboardEvent): boolean;
|
|
19
|
+
export declare function handleEnterKey(editor: HTMLElement, event: KeyboardEvent): boolean;
|
|
20
|
+
export declare function focusEditor(editor: HTMLElement): void;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/editor/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAWL,KAAK,UAAU,EAChB,MAAM,aAAa,CAAA;AAEpB,QAAA,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAI1C,CAAA;AAuDD,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,OAAO,cAAc,WAclF;AA+UD,uBAAuB;AACvB,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU,GAChB,OAAO,CAaT;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,YAAY,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,WAAW,WA8CpF;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,WAS9D;AA2GD,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,WAErE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,WAE1E;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,WAW3D;AAQD,wBAAgB,WAAW,CACzB,MAAM,EAAE,WAAW,EACnB,IAAI,SAAI,EACR,IAAI,SAAI,GACP,OAAO,CAyCT;AA+DD,iDAAiD;AACjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAwDjE;AAED,iCAAiC;AACjC,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,OAAO,CAEvE;AAED,oCAAoC;AACpC,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CASzD;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,aAAa,GACnB,OAAO,CAgCT;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CA6EjF;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,QAmB9C"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** 에디터 문서 JSON 스키마 */
|
|
2
|
+
export type TextAlign = 'left' | 'center' | 'right';
|
|
3
|
+
export type Mark = {
|
|
4
|
+
type: 'bold';
|
|
5
|
+
} | {
|
|
6
|
+
type: 'italic';
|
|
7
|
+
} | {
|
|
8
|
+
type: 'underline';
|
|
9
|
+
} | {
|
|
10
|
+
type: 'code';
|
|
11
|
+
} | {
|
|
12
|
+
type: 'textStyle';
|
|
13
|
+
attrs?: {
|
|
14
|
+
color?: string;
|
|
15
|
+
};
|
|
16
|
+
} | {
|
|
17
|
+
type: 'highlight';
|
|
18
|
+
attrs?: {
|
|
19
|
+
color?: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type TextNode = {
|
|
23
|
+
type: 'text';
|
|
24
|
+
text: string;
|
|
25
|
+
marks?: Mark[];
|
|
26
|
+
};
|
|
27
|
+
export type HardBreakNode = {
|
|
28
|
+
type: 'hardBreak';
|
|
29
|
+
};
|
|
30
|
+
export type InlineNode = TextNode | HardBreakNode;
|
|
31
|
+
export type ParagraphNode = {
|
|
32
|
+
type: 'paragraph';
|
|
33
|
+
attrs?: {
|
|
34
|
+
textAlign?: TextAlign;
|
|
35
|
+
};
|
|
36
|
+
content?: InlineNode[];
|
|
37
|
+
};
|
|
38
|
+
export type HeadingNode = {
|
|
39
|
+
type: 'heading';
|
|
40
|
+
attrs: {
|
|
41
|
+
level: 1 | 2 | 3;
|
|
42
|
+
textAlign?: TextAlign;
|
|
43
|
+
};
|
|
44
|
+
content?: InlineNode[];
|
|
45
|
+
};
|
|
46
|
+
export type BlockquoteNode = {
|
|
47
|
+
type: 'blockquote';
|
|
48
|
+
attrs?: {
|
|
49
|
+
textAlign?: TextAlign;
|
|
50
|
+
};
|
|
51
|
+
content?: InlineNode[];
|
|
52
|
+
};
|
|
53
|
+
export type CodeBlockNode = {
|
|
54
|
+
type: 'codeBlock';
|
|
55
|
+
content?: TextNode[];
|
|
56
|
+
};
|
|
57
|
+
export type ListItemNode = {
|
|
58
|
+
type: 'listItem';
|
|
59
|
+
content?: Array<ParagraphNode | BulletListNode | OrderedListNode>;
|
|
60
|
+
};
|
|
61
|
+
export type BulletListNode = {
|
|
62
|
+
type: 'bulletList';
|
|
63
|
+
content?: ListItemNode[];
|
|
64
|
+
};
|
|
65
|
+
export type OrderedListNode = {
|
|
66
|
+
type: 'orderedList';
|
|
67
|
+
content?: ListItemNode[];
|
|
68
|
+
};
|
|
69
|
+
export type TaskItemNode = {
|
|
70
|
+
type: 'taskItem';
|
|
71
|
+
attrs?: {
|
|
72
|
+
checked?: boolean;
|
|
73
|
+
};
|
|
74
|
+
content?: InlineNode[];
|
|
75
|
+
};
|
|
76
|
+
export type TaskListNode = {
|
|
77
|
+
type: 'taskList';
|
|
78
|
+
content?: TaskItemNode[];
|
|
79
|
+
};
|
|
80
|
+
export type TableCellNode = {
|
|
81
|
+
type: 'tableCell';
|
|
82
|
+
attrs?: {
|
|
83
|
+
colspan?: number;
|
|
84
|
+
rowspan?: number;
|
|
85
|
+
color?: string;
|
|
86
|
+
backgroundColor?: string;
|
|
87
|
+
};
|
|
88
|
+
content?: InlineNode[];
|
|
89
|
+
};
|
|
90
|
+
export type TableRowNode = {
|
|
91
|
+
type: 'tableRow';
|
|
92
|
+
content?: TableCellNode[];
|
|
93
|
+
};
|
|
94
|
+
export type TableNode = {
|
|
95
|
+
type: 'table';
|
|
96
|
+
content?: TableRowNode[];
|
|
97
|
+
};
|
|
98
|
+
export type BlockNode = ParagraphNode | HeadingNode | BlockquoteNode | CodeBlockNode | BulletListNode | OrderedListNode | TaskListNode | TableNode;
|
|
99
|
+
export type EditorDoc = {
|
|
100
|
+
type: 'doc';
|
|
101
|
+
content: BlockNode[];
|
|
102
|
+
};
|
|
103
|
+
/** contentEditable 루트 → JSON */
|
|
104
|
+
export declare function domToJson(root: HTMLElement): EditorDoc;
|
|
105
|
+
export declare function htmlToJson(html: string): EditorDoc;
|
|
106
|
+
/** JSON → HTML 문자열 */
|
|
107
|
+
export declare function jsonToHtml(doc: EditorDoc): string;
|
|
108
|
+
/** 느슨한 입력도 EditorDoc으로 정규화 */
|
|
109
|
+
export declare function normalizeDoc(input: unknown): EditorDoc;
|
|
110
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/editor/document.ts"],"names":[],"mappings":"AAAA,sBAAsB;AAEtB,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEnD,MAAM,MAAM,IAAI,GACZ;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAA;AAErD,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;CACf,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAA;AAEjD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,aAAa,CAAA;AAEjD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,KAAK,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,SAAS,CAAA;KAAE,CAAA;IACjC,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,CAAA;IACf,KAAK,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAAC,SAAS,CAAC,EAAE,SAAS,CAAA;KAAE,CAAA;IAClD,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,SAAS,CAAA;KAAE,CAAA;IACjC,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,CAAC,EAAE,KAAK,CAAC,aAAa,GAAG,cAAc,GAAG,eAAe,CAAC,CAAA;CAClE,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,YAAY,CAAA;IAClB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,UAAU,CAAA;IAChB,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;IAC7B,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,WAAW,CAAA;IACjB,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,eAAe,CAAC,EAAE,MAAM,CAAA;KACzB,CAAA;IACD,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,SAAS,GACjB,aAAa,GACb,WAAW,GACX,cAAc,GACd,aAAa,GACb,cAAc,GACd,eAAe,GACf,YAAY,GACZ,SAAS,CAAA;AAEb,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,KAAK,CAAA;IACX,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB,CAAA;AA2ND,gCAAgC;AAChC,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,CAYtD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAMlD;AAgID,sBAAsB;AACtB,wBAAgB,UAAU,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAMjD;AAED,8BAA8B;AAC9B,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAUtD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type HistorySnapshot = {
|
|
2
|
+
html: string;
|
|
3
|
+
selection?: {
|
|
4
|
+
startPath: number[];
|
|
5
|
+
startOffset: number;
|
|
6
|
+
endPath: number[];
|
|
7
|
+
endOffset: number;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export declare function captureSelection(root: HTMLElement): HistorySnapshot['selection'];
|
|
11
|
+
export declare function restoreSelection(root: HTMLElement, snapshot?: HistorySnapshot['selection']): void;
|
|
12
|
+
export declare class EditorHistory {
|
|
13
|
+
private undoStack;
|
|
14
|
+
private redoStack;
|
|
15
|
+
private lastPush;
|
|
16
|
+
private debounceMs;
|
|
17
|
+
private maxSize;
|
|
18
|
+
constructor(debounceMs?: number, maxSize?: number);
|
|
19
|
+
reset(html: string): void;
|
|
20
|
+
push(editor: HTMLElement, force?: boolean): void;
|
|
21
|
+
undo(editor: HTMLElement): boolean;
|
|
22
|
+
redo(editor: HTMLElement): boolean;
|
|
23
|
+
canUndo(): boolean;
|
|
24
|
+
canRedo(): boolean;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../src/editor/history.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE;QACV,SAAS,EAAE,MAAM,EAAE,CAAA;QACnB,WAAW,EAAE,MAAM,CAAA;QACnB,OAAO,EAAE,MAAM,EAAE,CAAA;QACjB,SAAS,EAAE,MAAM,CAAA;KAClB,CAAA;CACF,CAAA;AAuBD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,CAAC,WAAW,CAAC,CAehF;AAQD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,WAAW,EACjB,QAAQ,CAAC,EAAE,eAAe,CAAC,WAAW,CAAC,QAgBxC;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,SAAS,CAAwB;IACzC,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,OAAO,CAAQ;gBAEX,UAAU,SAAM,EAAE,OAAO,SAAM;IAK3C,KAAK,CAAC,IAAI,EAAE,MAAM;IAMlB,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,UAAQ;IAyBvC,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO;IAUlC,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO;IASlC,OAAO;IAIP,OAAO;CAGR"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Editor, type EditorHandle, type EditorProps } from './Editor';
|
|
2
|
+
export { domToJson, htmlToJson, jsonToHtml, normalizeDoc, type BlockNode, type EditorDoc, type InlineNode, type Mark, } from './document';
|
|
3
|
+
export { sanitizeHTML, sanitizePlainText } from './sanitize';
|
|
4
|
+
export { type EditorTheme, type EditorThemeVars } from './theme';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAA;AAErB,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAA;AACtE,OAAO,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,YAAY,EACZ,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,IAAI,GACV,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC5D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,SAAS,CAAA"}
|