@lumir-company/editor 0.2.0 → 0.2.1
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/README.md +379 -925
- package/dist/index.d.mts +8 -20
- package/dist/index.d.ts +8 -20
- package/dist/index.js +104 -134
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +105 -135
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +37 -13
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,925 +1,379 @@
|
|
|
1
|
-
# LumirEditor
|
|
2
|
-
|
|
3
|
-
BlockNote
|
|
4
|
-
|
|
5
|
-
## ✨ 주요 특징
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
- 🎨
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- ⚡
|
|
13
|
-
|
|
14
|
-
## 📦 설치
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
|
|
32
|
-
import
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### 3.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
<LumirEditor
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
| `tabBehavior` | `"prefer-navigate-ui" \| "prefer-indent"` | `"prefer-navigate-ui"` | Tab 키 동작 |
|
|
381
|
-
| `trailingBlock` | `boolean` | `true` | 문서 끝 빈 블록 유지 |
|
|
382
|
-
|
|
383
|
-
### 🎨 UI 및 테마
|
|
384
|
-
|
|
385
|
-
| Prop | 타입 | 기본값 | 설명 |
|
|
386
|
-
| ---------------------- | ----------------------------- | --------- | --------------------- |
|
|
387
|
-
| `theme` | `"light" \| "dark" \| object` | `"light"` | 에디터 테마 |
|
|
388
|
-
| `editable` | `boolean` | `true` | 편집 가능 여부 |
|
|
389
|
-
| `className` | `string` | `""` | 커스텀 CSS 클래스 |
|
|
390
|
-
| `includeDefaultStyles` | `boolean` | `true` | 기본 스타일 포함 여부 |
|
|
391
|
-
|
|
392
|
-
### 🛠️ 툴바 및 메뉴
|
|
393
|
-
|
|
394
|
-
| Prop | 타입 | 기본값 | 설명 |
|
|
395
|
-
| ------------------- | --------- | ------ | ------------------------- |
|
|
396
|
-
| `formattingToolbar` | `boolean` | `true` | 서식 툴바 표시 |
|
|
397
|
-
| `linkToolbar` | `boolean` | `true` | 링크 툴바 표시 |
|
|
398
|
-
| `sideMenu` | `boolean` | `true` | 사이드 메뉴 표시 |
|
|
399
|
-
| `sideMenuAddButton` | `boolean` | `true` | 사이드 메뉴 Add 버튼 표시 |
|
|
400
|
-
| `slashMenu` | `boolean` | `true` | 슬래시 메뉴 표시 |
|
|
401
|
-
| `emojiPicker` | `boolean` | `true` | 이모지 피커 표시 |
|
|
402
|
-
| `filePanel` | `boolean` | `true` | 파일 패널 표시 |
|
|
403
|
-
| `tableHandles` | `boolean` | `true` | 표 핸들 표시 |
|
|
404
|
-
| `comments` | `boolean` | `true` | 댓글 기능 표시 |
|
|
405
|
-
|
|
406
|
-
### 🔗 고급 설정
|
|
407
|
-
|
|
408
|
-
| Prop | 타입 | 기본값 | 설명 |
|
|
409
|
-
| ------------------- | -------------------------------------------- | ----------- | -------------------- |
|
|
410
|
-
| `editorRef` | `React.MutableRefObject<EditorType \| null>` | `undefined` | 에디터 인스턴스 참조 |
|
|
411
|
-
| `domAttributes` | `Record<string, string>` | `{}` | DOM 속성 추가 |
|
|
412
|
-
| `resolveFileUrl` | `(url: string) => Promise<string>` | `undefined` | 파일 URL 변환 함수 |
|
|
413
|
-
| `onSelectionChange` | `() => void` | `undefined` | 선택 영역 변경 콜백 |
|
|
414
|
-
|
|
415
|
-
## 📖 타입 정의
|
|
416
|
-
|
|
417
|
-
### 주요 타입 가져오기
|
|
418
|
-
|
|
419
|
-
```tsx
|
|
420
|
-
import type {
|
|
421
|
-
LumirEditorProps,
|
|
422
|
-
EditorType,
|
|
423
|
-
DefaultPartialBlock,
|
|
424
|
-
DefaultBlockSchema,
|
|
425
|
-
DefaultInlineContentSchema,
|
|
426
|
-
DefaultStyleSchema,
|
|
427
|
-
PartialBlock,
|
|
428
|
-
BlockNoteEditor,
|
|
429
|
-
} from "@lumir-company/editor";
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
### 타입 사용 예시
|
|
433
|
-
|
|
434
|
-
```tsx
|
|
435
|
-
import { useRef } from "react";
|
|
436
|
-
import {
|
|
437
|
-
LumirEditor,
|
|
438
|
-
type EditorType,
|
|
439
|
-
type DefaultPartialBlock,
|
|
440
|
-
} from "@lumir-company/editor";
|
|
441
|
-
|
|
442
|
-
function MyEditor() {
|
|
443
|
-
const editorRef = useRef<EditorType>(null);
|
|
444
|
-
|
|
445
|
-
const handleContentChange = (content: DefaultPartialBlock[]) => {
|
|
446
|
-
console.log("변경된 블록:", content);
|
|
447
|
-
saveToDatabase(JSON.stringify(content));
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
const insertImage = () => {
|
|
451
|
-
editorRef.current?.pasteHTML('<img src="/example.jpg" alt="Example" />');
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
return (
|
|
455
|
-
<div>
|
|
456
|
-
<button onClick={insertImage}>이미지 삽입</button>
|
|
457
|
-
<LumirEditor
|
|
458
|
-
editorRef={editorRef}
|
|
459
|
-
onContentChange={handleContentChange}
|
|
460
|
-
/>
|
|
461
|
-
</div>
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
## 🎨 스타일 커스터마이징 완벽 가이드
|
|
467
|
-
|
|
468
|
-
### 1. 기본 스타일 시스템
|
|
469
|
-
|
|
470
|
-
LumirEditor는 3가지 스타일링 방법을 제공합니다:
|
|
471
|
-
|
|
472
|
-
1. **기본 스타일**: `includeDefaultStyles={true}` (권장)
|
|
473
|
-
2. **Tailwind CSS**: `className` prop으로 유틸리티 클래스 적용
|
|
474
|
-
3. **커스텀 CSS**: 전통적인 CSS 클래스와 선택자 사용
|
|
475
|
-
|
|
476
|
-
### 2. 기본 설정 및 제어
|
|
477
|
-
|
|
478
|
-
```tsx
|
|
479
|
-
// 기본 스타일 포함 (권장)
|
|
480
|
-
<LumirEditor
|
|
481
|
-
includeDefaultStyles={true} // 기본값
|
|
482
|
-
className="추가-커스텀-클래스"
|
|
483
|
-
/>
|
|
484
|
-
|
|
485
|
-
// 기본 스타일 완전 제거 (고급 사용자)
|
|
486
|
-
<LumirEditor
|
|
487
|
-
includeDefaultStyles={false}
|
|
488
|
-
className="완전-커스텀-에디터-스타일"
|
|
489
|
-
/>
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
### 3. Tailwind CSS 스타일링
|
|
493
|
-
|
|
494
|
-
#### 기본 레이아웃 스타일링
|
|
495
|
-
|
|
496
|
-
```tsx
|
|
497
|
-
<LumirEditor
|
|
498
|
-
className="
|
|
499
|
-
min-h-[500px] max-w-4xl mx-auto
|
|
500
|
-
rounded-xl border border-gray-200 shadow-lg
|
|
501
|
-
bg-white dark:bg-gray-900
|
|
502
|
-
"
|
|
503
|
-
/>
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
#### 반응형 스타일링
|
|
507
|
-
|
|
508
|
-
```tsx
|
|
509
|
-
<LumirEditor
|
|
510
|
-
className="
|
|
511
|
-
h-64 md:h-96 lg:h-[500px]
|
|
512
|
-
text-sm md:text-base
|
|
513
|
-
p-2 md:p-4 lg:p-6
|
|
514
|
-
rounded-md md:rounded-lg lg:rounded-xl
|
|
515
|
-
shadow-sm md:shadow-md lg:shadow-lg
|
|
516
|
-
"
|
|
517
|
-
/>
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
#### 고급 내부 요소 스타일링
|
|
521
|
-
|
|
522
|
-
```tsx
|
|
523
|
-
<LumirEditor
|
|
524
|
-
className="
|
|
525
|
-
/* 에디터 영역 패딩 조정 */
|
|
526
|
-
[&_.bn-editor]:px-8 [&_.bn-editor]:py-4
|
|
527
|
-
|
|
528
|
-
/* 특정 블록 타입 스타일링 */
|
|
529
|
-
[&_[data-content-type='paragraph']]:text-base [&_[data-content-type='paragraph']]:leading-relaxed
|
|
530
|
-
[&_[data-content-type='heading']]:font-bold [&_[data-content-type='heading']]:text-gray-900
|
|
531
|
-
[&_[data-content-type='list']]:ml-4
|
|
532
|
-
|
|
533
|
-
/* 포커스 상태 스타일링 */
|
|
534
|
-
focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500
|
|
535
|
-
|
|
536
|
-
/* 테마별 스타일링 */
|
|
537
|
-
dark:[&_.bn-editor]:bg-gray-800 dark:[&_.bn-editor]:text-white
|
|
538
|
-
|
|
539
|
-
/* 호버 효과 */
|
|
540
|
-
hover:shadow-md transition-shadow duration-200
|
|
541
|
-
"
|
|
542
|
-
/>
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
#### 테마별 스타일링
|
|
546
|
-
|
|
547
|
-
```tsx
|
|
548
|
-
// 라이트 모드
|
|
549
|
-
<LumirEditor
|
|
550
|
-
theme="light"
|
|
551
|
-
className="
|
|
552
|
-
bg-white text-gray-900 border-gray-200
|
|
553
|
-
[&_.bn-editor]:bg-white
|
|
554
|
-
[&_[data-content-type='paragraph']]:text-gray-800
|
|
555
|
-
"
|
|
556
|
-
/>
|
|
557
|
-
|
|
558
|
-
// 다크 모드
|
|
559
|
-
<LumirEditor
|
|
560
|
-
theme="dark"
|
|
561
|
-
className="
|
|
562
|
-
bg-gray-900 text-white border-gray-700
|
|
563
|
-
[&_.bn-editor]:bg-gray-900
|
|
564
|
-
[&_[data-content-type='paragraph']]:text-gray-100
|
|
565
|
-
"
|
|
566
|
-
/>
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
### 4. CSS 클래스 스타일링
|
|
570
|
-
|
|
571
|
-
#### 기본 CSS 구조
|
|
572
|
-
|
|
573
|
-
```css
|
|
574
|
-
/* 메인 에디터 컨테이너 */
|
|
575
|
-
.my-custom-editor {
|
|
576
|
-
border: 2px solid #e5e7eb;
|
|
577
|
-
border-radius: 12px;
|
|
578
|
-
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
579
|
-
background: white;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/* 에디터 내용 영역 */
|
|
583
|
-
.my-custom-editor .bn-editor {
|
|
584
|
-
font-family: "Pretendard", -apple-system, BlinkMacSystemFont, sans-serif;
|
|
585
|
-
font-size: 14px;
|
|
586
|
-
line-height: 1.6;
|
|
587
|
-
padding: 24px;
|
|
588
|
-
min-height: 200px;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/* 포커스 상태 */
|
|
592
|
-
.my-custom-editor:focus-within {
|
|
593
|
-
border-color: #3b82f6;
|
|
594
|
-
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
595
|
-
}
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
#### 블록별 세부 스타일링
|
|
599
|
-
|
|
600
|
-
```css
|
|
601
|
-
/* 문단 블록 */
|
|
602
|
-
.my-custom-editor .bn-block[data-content-type="paragraph"] {
|
|
603
|
-
margin-bottom: 12px;
|
|
604
|
-
font-size: 14px;
|
|
605
|
-
color: #374151;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
/* 헤딩 블록 */
|
|
609
|
-
.my-custom-editor .bn-block[data-content-type="heading"] {
|
|
610
|
-
font-weight: 700;
|
|
611
|
-
margin: 24px 0 12px 0;
|
|
612
|
-
color: #111827;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
.my-custom-editor .bn-block[data-content-type="heading"][data-level="1"] {
|
|
616
|
-
font-size: 28px;
|
|
617
|
-
border-bottom: 2px solid #e5e7eb;
|
|
618
|
-
padding-bottom: 8px;
|
|
619
|
-
}
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
## 🔧 고급 사용법
|
|
623
|
-
|
|
624
|
-
### 명령형 API 사용
|
|
625
|
-
|
|
626
|
-
```tsx
|
|
627
|
-
function AdvancedEditor() {
|
|
628
|
-
const editorRef = useRef<EditorType>(null);
|
|
629
|
-
|
|
630
|
-
const insertTable = () => {
|
|
631
|
-
editorRef.current?.insertBlocks(
|
|
632
|
-
[
|
|
633
|
-
{
|
|
634
|
-
type: "table",
|
|
635
|
-
content: {
|
|
636
|
-
type: "tableContent",
|
|
637
|
-
rows: [{ cells: ["셀 1", "셀 2"] }, { cells: ["셀 3", "셀 4"] }],
|
|
638
|
-
},
|
|
639
|
-
},
|
|
640
|
-
],
|
|
641
|
-
editorRef.current.getTextCursorPosition().block
|
|
642
|
-
);
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
return (
|
|
646
|
-
<div>
|
|
647
|
-
<button onClick={insertTable}>표 삽입</button>
|
|
648
|
-
<button onClick={() => editorRef.current?.focus()}>포커스</button>
|
|
649
|
-
<LumirEditor editorRef={editorRef} />
|
|
650
|
-
</div>
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
### 커스텀 붙여넣기 핸들러
|
|
656
|
-
|
|
657
|
-
```tsx
|
|
658
|
-
<LumirEditor
|
|
659
|
-
pasteHandler={({ event, defaultPasteHandler }) => {
|
|
660
|
-
const text = event.clipboardData?.getData("text/plain");
|
|
661
|
-
|
|
662
|
-
// URL 감지 시 자동 링크 생성
|
|
663
|
-
if (text?.startsWith("http")) {
|
|
664
|
-
return defaultPasteHandler({ pasteBehavior: "prefer-html" }) ?? false;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// 기본 처리
|
|
668
|
-
return defaultPasteHandler() ?? false;
|
|
669
|
-
}}
|
|
670
|
-
/>
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
### 실시간 자동 저장
|
|
674
|
-
|
|
675
|
-
```tsx
|
|
676
|
-
function AutoSaveEditor() {
|
|
677
|
-
const [saveStatus, setSaveStatus] = useState<"saved" | "saving" | "error">(
|
|
678
|
-
"saved"
|
|
679
|
-
);
|
|
680
|
-
|
|
681
|
-
const handleContentChange = useCallback(
|
|
682
|
-
debounce(async (content: DefaultPartialBlock[]) => {
|
|
683
|
-
setSaveStatus("saving");
|
|
684
|
-
try {
|
|
685
|
-
await saveToServer(JSON.stringify(content));
|
|
686
|
-
setSaveStatus("saved");
|
|
687
|
-
} catch (error) {
|
|
688
|
-
setSaveStatus("error");
|
|
689
|
-
}
|
|
690
|
-
}, 1000),
|
|
691
|
-
[]
|
|
692
|
-
);
|
|
693
|
-
|
|
694
|
-
return (
|
|
695
|
-
<div>
|
|
696
|
-
<div className="mb-2">
|
|
697
|
-
상태: <span className={`badge badge-${saveStatus}`}>{saveStatus}</span>
|
|
698
|
-
</div>
|
|
699
|
-
<LumirEditor onContentChange={handleContentChange} />
|
|
700
|
-
</div>
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
## 📱 반응형 디자인
|
|
706
|
-
|
|
707
|
-
```tsx
|
|
708
|
-
<LumirEditor
|
|
709
|
-
className="
|
|
710
|
-
w-full h-96
|
|
711
|
-
md:h-[500px]
|
|
712
|
-
lg:h-[600px]
|
|
713
|
-
rounded-lg
|
|
714
|
-
border border-gray-300
|
|
715
|
-
md:rounded-xl
|
|
716
|
-
lg:shadow-xl
|
|
717
|
-
"
|
|
718
|
-
// 모바일에서는 일부 툴바 숨김
|
|
719
|
-
formattingToolbar={true}
|
|
720
|
-
filePanel={window.innerWidth > 768}
|
|
721
|
-
tableHandles={window.innerWidth > 1024}
|
|
722
|
-
/>
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
## ⚠️ 주의사항 및 문제 해결
|
|
726
|
-
|
|
727
|
-
### 1. SSR 환경 (필수)
|
|
728
|
-
|
|
729
|
-
Next.js 등 SSR 환경에서는 반드시 클라이언트 사이드에서만 렌더링해야 합니다:
|
|
730
|
-
|
|
731
|
-
```tsx
|
|
732
|
-
// ✅ 올바른 방법
|
|
733
|
-
const LumirEditor = dynamic(
|
|
734
|
-
() => import("@lumir-company/editor").then((m) => m.LumirEditor),
|
|
735
|
-
{ ssr: false }
|
|
736
|
-
);
|
|
737
|
-
|
|
738
|
-
// ❌ 잘못된 방법 - SSR 오류 발생
|
|
739
|
-
import { LumirEditor } from "@lumir-company/editor";
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
### 2. React StrictMode
|
|
743
|
-
|
|
744
|
-
React 19/Next.js 15 일부 환경에서 StrictMode 이슈가 보고되었습니다. 문제 발생 시 임시로 StrictMode를 비활성화하는 것을 고려해보세요.
|
|
745
|
-
|
|
746
|
-
### 3. 일반적인 설치 문제
|
|
747
|
-
|
|
748
|
-
#### TypeScript 타입 오류
|
|
749
|
-
|
|
750
|
-
```bash
|
|
751
|
-
# TypeScript 타입 문제 해결
|
|
752
|
-
npm install --save-dev @types/react @types/react-dom
|
|
753
|
-
|
|
754
|
-
# 또는 tsconfig.json에서
|
|
755
|
-
{
|
|
756
|
-
"compilerOptions": {
|
|
757
|
-
"skipLibCheck": true
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
```
|
|
761
|
-
|
|
762
|
-
#### CSS 스타일이 적용되지 않는 경우
|
|
763
|
-
|
|
764
|
-
```tsx
|
|
765
|
-
// 1. CSS 파일이 올바르게 임포트되었는지 확인
|
|
766
|
-
import "@lumir-company/editor/style.css";
|
|
767
|
-
|
|
768
|
-
// 2. Tailwind CSS 설정 확인
|
|
769
|
-
// tailwind.config.js에 패키지 경로 추가 필요
|
|
770
|
-
|
|
771
|
-
// 3. CSS 우선순위 문제인 경우
|
|
772
|
-
.my-editor {
|
|
773
|
-
/* !important 사용 또는 더 구체적인 선택자 */
|
|
774
|
-
}
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
#### 번들러 호환성 문제
|
|
778
|
-
|
|
779
|
-
```js
|
|
780
|
-
// Webpack 설정
|
|
781
|
-
module.exports = {
|
|
782
|
-
resolve: {
|
|
783
|
-
fallback: {
|
|
784
|
-
crypto: require.resolve("crypto-browserify"),
|
|
785
|
-
stream: require.resolve("stream-browserify"),
|
|
786
|
-
},
|
|
787
|
-
},
|
|
788
|
-
};
|
|
789
|
-
|
|
790
|
-
// Vite 설정
|
|
791
|
-
export default defineConfig({
|
|
792
|
-
optimizeDeps: {
|
|
793
|
-
include: ["@lumir-company/editor"],
|
|
794
|
-
},
|
|
795
|
-
});
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
#### 이미지 업로드 문제
|
|
799
|
-
|
|
800
|
-
```tsx
|
|
801
|
-
// CORS 문제 해결
|
|
802
|
-
const uploadFile = async (file: File) => {
|
|
803
|
-
const response = await fetch("/api/upload", {
|
|
804
|
-
method: "POST",
|
|
805
|
-
headers: {
|
|
806
|
-
// CORS 헤더 확인
|
|
807
|
-
},
|
|
808
|
-
body: formData,
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
if (!response.ok) {
|
|
812
|
-
throw new Error(`업로드 실패: ${response.status}`);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
return url; // 반드시 접근 가능한 public URL
|
|
816
|
-
};
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
### 4. 성능 최적화
|
|
820
|
-
|
|
821
|
-
#### 큰 문서 처리
|
|
822
|
-
|
|
823
|
-
```tsx
|
|
824
|
-
// 대용량 문서의 경우 초기 렌더링 최적화
|
|
825
|
-
<LumirEditor
|
|
826
|
-
initialContent={largeContent}
|
|
827
|
-
// 불필요한 기능 비활성화
|
|
828
|
-
animations={false}
|
|
829
|
-
formattingToolbar={false}
|
|
830
|
-
// 메모리 사용량 줄이기
|
|
831
|
-
storeImagesAsBase64={false}
|
|
832
|
-
/>
|
|
833
|
-
```
|
|
834
|
-
|
|
835
|
-
#### 메모리 누수 방지
|
|
836
|
-
|
|
837
|
-
```tsx
|
|
838
|
-
// 컴포넌트 언마운트 시 정리
|
|
839
|
-
useEffect(() => {
|
|
840
|
-
return () => {
|
|
841
|
-
// 에디터 정리 로직
|
|
842
|
-
if (editorRef.current) {
|
|
843
|
-
editorRef.current = null;
|
|
844
|
-
}
|
|
845
|
-
};
|
|
846
|
-
}, []);
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
## 🚀 시작하기 체크리스트
|
|
850
|
-
|
|
851
|
-
프로젝트에 LumirEditor를 성공적으로 통합하기 위한 체크리스트:
|
|
852
|
-
|
|
853
|
-
### 📋 필수 설치 단계
|
|
854
|
-
|
|
855
|
-
- [ ] 패키지 설치: `npm install @lumir-company/editor`
|
|
856
|
-
- [ ] CSS 임포트: `import "@lumir-company/editor/style.css"`
|
|
857
|
-
- [ ] TypeScript 타입 설치: `npm install --save-dev @types/react @types/react-dom`
|
|
858
|
-
- [ ] SSR 환경이라면 dynamic import 설정
|
|
859
|
-
|
|
860
|
-
### 🎨 스타일링 설정
|
|
861
|
-
|
|
862
|
-
- [ ] Tailwind CSS 사용 시 `tailwind.config.js`에 패키지 경로 추가
|
|
863
|
-
- [ ] 기본 스타일 적용 확인: `includeDefaultStyles={true}`
|
|
864
|
-
- [ ] 커스텀 스타일이 필요하면 `className` prop 활용
|
|
865
|
-
|
|
866
|
-
### 🔧 기능 설정
|
|
867
|
-
|
|
868
|
-
- [ ] 파일 업로드가 필요하면 `uploadFile` 함수 구현
|
|
869
|
-
- [ ] 콘텐츠 변경 감지가 필요하면 `onContentChange` 콜백 설정
|
|
870
|
-
- [ ] 필요에 따라 툴바와 메뉴 표시/숨김 설정
|
|
871
|
-
|
|
872
|
-
### ✅ 테스트 확인
|
|
873
|
-
|
|
874
|
-
- [ ] 기본 텍스트 입력 동작 확인
|
|
875
|
-
- [ ] 이미지 업로드/붙여넣기 동작 확인
|
|
876
|
-
- [ ] 스타일이 올바르게 적용되는지 확인
|
|
877
|
-
- [ ] 다양한 브라우저에서 테스트
|
|
878
|
-
|
|
879
|
-
## 📋 변경 기록
|
|
880
|
-
|
|
881
|
-
### v0.2.0 (최신)
|
|
882
|
-
|
|
883
|
-
- ✨ **하이브리드 콘텐츠 지원**: `initialContent`에서 JSON 객체 배열과 JSON 문자열 모두 지원
|
|
884
|
-
- ✨ **Placeholder 기능**: 첫 번째 블록에 placeholder 텍스트 설정 가능
|
|
885
|
-
- ✨ **초기 블록 개수 설정**: `initialEmptyBlocks` prop으로 빈 블록 개수 조정
|
|
886
|
-
- 🔧 **유틸리티 클래스 추가**: `ContentUtils`, `EditorConfig` 클래스로 코드 정리
|
|
887
|
-
- 📁 **타입 분리**: 모든 타입 정의를 별도 파일로 분리하여 관리 개선
|
|
888
|
-
- 🎨 **기본 스타일 최적화**: 더 나은 기본 패딩과 스타일 적용
|
|
889
|
-
|
|
890
|
-
### v0.1.15
|
|
891
|
-
|
|
892
|
-
- 🐛 파일 검증 로직 보완
|
|
893
|
-
|
|
894
|
-
### v0.1.14
|
|
895
|
-
|
|
896
|
-
- 🔧 슬래시 추천 메뉴 항목 변경
|
|
897
|
-
|
|
898
|
-
### v0.1.13
|
|
899
|
-
|
|
900
|
-
- ⚙️ Audio, Video, Movie 업로드 기본값을 false로 변경
|
|
901
|
-
|
|
902
|
-
### v0.1.12
|
|
903
|
-
|
|
904
|
-
- 🐛 조건부 Helper 항목 렌더링 수정
|
|
905
|
-
|
|
906
|
-
### v0.1.11
|
|
907
|
-
|
|
908
|
-
- 🐛 이미지 중복 드롭 이슈 수정
|
|
909
|
-
|
|
910
|
-
### v0.1.10
|
|
911
|
-
|
|
912
|
-
- 🎨 기본 이미지 저장 방식을 Base64로 설정
|
|
913
|
-
- ✨ `storeImagesAsBase64` prop 추가
|
|
914
|
-
- 🐛 드래그앤드롭 중복 삽입 방지
|
|
915
|
-
|
|
916
|
-
### v0.1.0
|
|
917
|
-
|
|
918
|
-
- 🎉 초기 릴리스
|
|
919
|
-
|
|
920
|
-
## 📄 라이선스
|
|
921
|
-
|
|
922
|
-
이 패키지는 BlockNote의 무료 기능만을 사용합니다.
|
|
923
|
-
|
|
924
|
-
- 의존성: `@blocknote/core`, `@blocknote/react`, `@blocknote/mantine`
|
|
925
|
-
- BlockNote 라이선스를 따릅니다.
|
|
1
|
+
# LumirEditor
|
|
2
|
+
|
|
3
|
+
이미지 전용 BlockNote 기반 Rich Text 에디터 React 컴포넌트
|
|
4
|
+
|
|
5
|
+
## ✨ 주요 특징
|
|
6
|
+
|
|
7
|
+
- 🖼️ **이미지 전용 에디터**: 이미지 업로드/드래그앤드롭만 지원 (Base64 변환)
|
|
8
|
+
- 🚀 **간소화된 API**: 핵심 기능만 포함한 미니멀한 인터페이스
|
|
9
|
+
- 🎨 **BlockNote Theme 지원**: 공식 theme prop으로 에디터 스타일링
|
|
10
|
+
- 🔧 **TypeScript 지원**: 완전한 타입 안전성
|
|
11
|
+
- 📝 **Pretendard 폰트**: 기본 폰트로 Pretendard 최우선 적용 (14px)
|
|
12
|
+
- ⚡ **경량화**: 비디오/오디오/파일 업로드 기능 제거로 빠른 로딩
|
|
13
|
+
|
|
14
|
+
## 📦 설치
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @lumir-company/editor
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 🚀 기본 사용법
|
|
21
|
+
|
|
22
|
+
### 1. CSS 임포트 (필수)
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import '@lumir-company/editor/style.css';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. 기본 사용
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { LumirEditor } from '@lumir-company/editor';
|
|
32
|
+
import '@lumir-company/editor/style.css';
|
|
33
|
+
|
|
34
|
+
export default function App() {
|
|
35
|
+
return (
|
|
36
|
+
<div className='w-full h-[400px]'>
|
|
37
|
+
<LumirEditor onContentChange={(blocks) => console.log(blocks)} />
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3. Next.js에서 사용 (SSR 비활성화)
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
'use client';
|
|
47
|
+
import dynamic from 'next/dynamic';
|
|
48
|
+
|
|
49
|
+
const LumirEditor = dynamic(
|
|
50
|
+
() => import('@lumir-company/editor').then((m) => m.LumirEditor),
|
|
51
|
+
{ ssr: false },
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
export default function Editor() {
|
|
55
|
+
return (
|
|
56
|
+
<div className='w-full h-[500px]'>
|
|
57
|
+
<LumirEditor />
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 📚 핵심 Props
|
|
64
|
+
|
|
65
|
+
| Prop | 타입 | 기본값 | 설명 |
|
|
66
|
+
| ------------------- | ---------------------------------- | --------- | ---------------- |
|
|
67
|
+
| `initialContent` | `DefaultPartialBlock[] \| string` | - | 초기 콘텐츠 |
|
|
68
|
+
| `className` | `string` | `""` | CSS 클래스 |
|
|
69
|
+
| `theme` | `"light" \| "dark" \| ThemeObject` | `"light"` | 에디터 테마 |
|
|
70
|
+
| `onContentChange` | `(blocks) => void` | - | 콘텐츠 변경 콜백 |
|
|
71
|
+
| `editable` | `boolean` | `true` | 편집 가능 여부 |
|
|
72
|
+
| `sideMenuAddButton` | `boolean` | `false` | Add 버튼 표시 |
|
|
73
|
+
|
|
74
|
+
### 고급 Props
|
|
75
|
+
|
|
76
|
+
| Prop | 타입 | 기본값 | 설명 |
|
|
77
|
+
| --------------------- | --------------------------------- | ------ | -------------------- |
|
|
78
|
+
| `uploadFile` | `(file: File) => Promise<string>` | - | 커스텀 이미지 업로더 |
|
|
79
|
+
| `storeImagesAsBase64` | `boolean` | `true` | Base64 저장 여부 |
|
|
80
|
+
| `formattingToolbar` | `boolean` | `true` | 서식 툴바 표시 |
|
|
81
|
+
| `linkToolbar` | `boolean` | `true` | 링크 툴바 표시 |
|
|
82
|
+
| `sideMenu` | `boolean` | `true` | 사이드 메뉴 표시 |
|
|
83
|
+
| `slashMenu` | `boolean` | `true` | 슬래시 메뉴 표시 |
|
|
84
|
+
| `emojiPicker` | `boolean` | `true` | 이모지 피커 표시 |
|
|
85
|
+
| `filePanel` | `boolean` | `true` | 파일 패널 표시 |
|
|
86
|
+
| `tableHandles` | `boolean` | `true` | 표 핸들 표시 |
|
|
87
|
+
| `tables` | `TableConfig` | - | 테이블 설정 |
|
|
88
|
+
| `heading` | `HeadingConfig` | - | 헤딩 설정 |
|
|
89
|
+
| `animations` | `boolean` | `true` | 애니메이션 활성화 |
|
|
90
|
+
| `defaultStyles` | `boolean` | `true` | 기본 스타일 적용 |
|
|
91
|
+
|
|
92
|
+
### Props 사용 예시
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { LumirEditor } from '@lumir-company/editor';
|
|
96
|
+
|
|
97
|
+
// 1. 초기 콘텐츠 설정
|
|
98
|
+
<LumirEditor
|
|
99
|
+
initialContent="에디터 시작 텍스트"
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
// 2. 블록 배열로 초기 콘텐츠 설정
|
|
103
|
+
<LumirEditor
|
|
104
|
+
initialContent={[
|
|
105
|
+
{
|
|
106
|
+
type: 'paragraph',
|
|
107
|
+
content: [{ type: 'text', text: '안녕하세요!' }]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: 'heading',
|
|
111
|
+
props: { level: 2 },
|
|
112
|
+
content: [{ type: 'text', text: '제목입니다' }]
|
|
113
|
+
}
|
|
114
|
+
]}
|
|
115
|
+
/>
|
|
116
|
+
|
|
117
|
+
// 3. 이벤트 핸들러 사용
|
|
118
|
+
<LumirEditor
|
|
119
|
+
onContentChange={(blocks) => {
|
|
120
|
+
console.log('변경된 콘텐츠:', blocks);
|
|
121
|
+
saveToDatabase(blocks);
|
|
122
|
+
}}
|
|
123
|
+
onSelectionChange={() => {
|
|
124
|
+
console.log('선택 영역이 변경되었습니다');
|
|
125
|
+
}}
|
|
126
|
+
/>
|
|
127
|
+
|
|
128
|
+
// 4. UI 컴포넌트 제어
|
|
129
|
+
<LumirEditor
|
|
130
|
+
sideMenuAddButton={true} // Add 버튼 표시
|
|
131
|
+
formattingToolbar={false} // 서식 툴바 숨김
|
|
132
|
+
linkToolbar={false} // 링크 툴바 숨김
|
|
133
|
+
slashMenu={false} // 슬래시 메뉴 숨김
|
|
134
|
+
emojiPicker={false} // 이모지 피커 숨김
|
|
135
|
+
/>
|
|
136
|
+
|
|
137
|
+
// 5. 읽기 전용 모드
|
|
138
|
+
<LumirEditor
|
|
139
|
+
editable={false}
|
|
140
|
+
initialContent={savedContent}
|
|
141
|
+
formattingToolbar={false}
|
|
142
|
+
sideMenu={false}
|
|
143
|
+
/>
|
|
144
|
+
|
|
145
|
+
// 6. 커스텀 이미지 업로더
|
|
146
|
+
<LumirEditor
|
|
147
|
+
uploadFile={async (file) => {
|
|
148
|
+
const formData = new FormData();
|
|
149
|
+
formData.append('image', file);
|
|
150
|
+
const response = await fetch('/api/upload', {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
body: formData
|
|
153
|
+
});
|
|
154
|
+
return (await response.json()).url;
|
|
155
|
+
}}
|
|
156
|
+
storeImagesAsBase64={false}
|
|
157
|
+
/>
|
|
158
|
+
|
|
159
|
+
// 7. 테이블과 헤딩 설정
|
|
160
|
+
<LumirEditor
|
|
161
|
+
tables={{
|
|
162
|
+
splitCells: true,
|
|
163
|
+
cellBackgroundColor: true,
|
|
164
|
+
cellTextColor: true,
|
|
165
|
+
headers: true
|
|
166
|
+
}}
|
|
167
|
+
heading={{
|
|
168
|
+
levels: [1, 2, 3, 4] // H1~H4만 허용
|
|
169
|
+
}}
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
// 8. 애니메이션과 스타일 제어
|
|
173
|
+
<LumirEditor
|
|
174
|
+
animations={false} // 애니메이션 비활성화
|
|
175
|
+
defaultStyles={true} // 기본 스타일 사용
|
|
176
|
+
/>
|
|
177
|
+
|
|
178
|
+
// 9. 완전한 설정 예시
|
|
179
|
+
<LumirEditor
|
|
180
|
+
initialContent="시작 텍스트"
|
|
181
|
+
className="min-h-[400px] border rounded-lg"
|
|
182
|
+
theme="light"
|
|
183
|
+
editable={true}
|
|
184
|
+
sideMenuAddButton={true}
|
|
185
|
+
formattingToolbar={true}
|
|
186
|
+
linkToolbar={true}
|
|
187
|
+
slashMenu={true}
|
|
188
|
+
emojiPicker={true}
|
|
189
|
+
onContentChange={(blocks) => console.log(blocks)}
|
|
190
|
+
onSelectionChange={() => console.log('선택 변경')}
|
|
191
|
+
uploadFile={customUploader}
|
|
192
|
+
storeImagesAsBase64={false}
|
|
193
|
+
/>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## 🎨 Theme 스타일링
|
|
197
|
+
|
|
198
|
+
### 기본 테마
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
// 라이트 모드
|
|
202
|
+
<LumirEditor theme="light" />
|
|
203
|
+
|
|
204
|
+
// 다크 모드
|
|
205
|
+
<LumirEditor theme="dark" />
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 커스텀 테마 (권장)
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
const customTheme = {
|
|
212
|
+
colors: {
|
|
213
|
+
editor: {
|
|
214
|
+
text: '#1f2937',
|
|
215
|
+
background: '#ffffff',
|
|
216
|
+
},
|
|
217
|
+
menu: {
|
|
218
|
+
text: '#374151',
|
|
219
|
+
background: '#f9fafb',
|
|
220
|
+
},
|
|
221
|
+
tooltip: {
|
|
222
|
+
text: '#6b7280',
|
|
223
|
+
background: '#f3f4f6',
|
|
224
|
+
},
|
|
225
|
+
hovered: {
|
|
226
|
+
text: '#111827',
|
|
227
|
+
background: '#e5e7eb',
|
|
228
|
+
},
|
|
229
|
+
selected: {
|
|
230
|
+
text: '#ffffff',
|
|
231
|
+
background: '#3b82f6',
|
|
232
|
+
},
|
|
233
|
+
disabled: {
|
|
234
|
+
text: '#9ca3af',
|
|
235
|
+
background: '#f3f4f6',
|
|
236
|
+
},
|
|
237
|
+
shadow: '#000000',
|
|
238
|
+
border: '#d1d5db',
|
|
239
|
+
sideMenu: '#6b7280',
|
|
240
|
+
},
|
|
241
|
+
borderRadius: 8,
|
|
242
|
+
fontFamily: 'Pretendard, system-ui, sans-serif',
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
<LumirEditor theme={customTheme} />;
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 라이트/다크 모드 조장장
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
const dualTheme = {
|
|
252
|
+
light: {
|
|
253
|
+
colors: {
|
|
254
|
+
editor: { text: '#374151', background: '#ffffff' },
|
|
255
|
+
menu: { text: '#111827', background: '#f9fafb' },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
dark: {
|
|
259
|
+
colors: {
|
|
260
|
+
editor: { text: '#f9fafb', background: '#111827' },
|
|
261
|
+
menu: { text: '#e5e7eb', background: '#1f2937' },
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
<LumirEditor theme={dualTheme} />;
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## 🖼️ 이미지 업로드
|
|
270
|
+
|
|
271
|
+
### 자동 Base64 변환
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
// 기본적으로 이미지가 Base64로 자동 변환됩니다
|
|
275
|
+
<LumirEditor
|
|
276
|
+
onContentChange={(blocks) => {
|
|
277
|
+
// 이미지가 포함된 블록들을 확인
|
|
278
|
+
const hasImages = blocks.some((block) =>
|
|
279
|
+
block.content?.some((content) => content.type === 'image'),
|
|
280
|
+
);
|
|
281
|
+
if (hasImages) {
|
|
282
|
+
console.log('이미지가 포함된 콘텐츠:', blocks);
|
|
283
|
+
}
|
|
284
|
+
}}
|
|
285
|
+
/>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## 📖 타입 정의
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
import type {
|
|
292
|
+
LumirEditorProps,
|
|
293
|
+
DefaultPartialBlock,
|
|
294
|
+
ContentUtils,
|
|
295
|
+
EditorConfig,
|
|
296
|
+
} from '@lumir-company/editor';
|
|
297
|
+
|
|
298
|
+
// 콘텐츠 검증
|
|
299
|
+
const isValidContent = ContentUtils.isValidJSONString(jsonString);
|
|
300
|
+
const blocks = ContentUtils.parseJSONContent(jsonString);
|
|
301
|
+
|
|
302
|
+
// 에디터 설정
|
|
303
|
+
const tableConfig = EditorConfig.getDefaultTableConfig();
|
|
304
|
+
const headingConfig = EditorConfig.getDefaultHeadingConfig();
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## 💡 사용 팁
|
|
308
|
+
|
|
309
|
+
### 1. 컨테이너 크기 설정
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
// 고정 높이
|
|
313
|
+
<div className='h-[400px]'>
|
|
314
|
+
<LumirEditor />
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
// 최소 높이
|
|
318
|
+
<div className='min-h-[300px]'>
|
|
319
|
+
<LumirEditor />
|
|
320
|
+
</div>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 2. 반응형 디자인
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
<div className='w-full h-64 md:h-96 lg:h-[500px]'>
|
|
327
|
+
<LumirEditor className='h-full' theme='light' />
|
|
328
|
+
</div>
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 3. 읽기 전용 모드
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
<LumirEditor
|
|
335
|
+
editable={false}
|
|
336
|
+
initialContent={savedContent}
|
|
337
|
+
formattingToolbar={false}
|
|
338
|
+
sideMenu={false}
|
|
339
|
+
/>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## ⚠️ 중요 사항
|
|
343
|
+
|
|
344
|
+
### 1. CSS 임포트 필수
|
|
345
|
+
|
|
346
|
+
```tsx
|
|
347
|
+
// 반드시 CSS를 임포트해야 합니다
|
|
348
|
+
import '@lumir-company/editor/style.css';
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 2. Next.js SSR 비활성화
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
// 서버 사이드 렌더링을 비활성화해야 합니다
|
|
355
|
+
const LumirEditor = dynamic(
|
|
356
|
+
() => import('@lumir-company/editor').then((m) => m.LumirEditor),
|
|
357
|
+
{ ssr: false },
|
|
358
|
+
);
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### 3. 이미지만 지원
|
|
362
|
+
|
|
363
|
+
- ✅ 이미지 파일: PNG, JPG, GIF, WebP, BMP, SVG
|
|
364
|
+
- ❌ 비디오, 오디오, 일반 파일 업로드 불가
|
|
365
|
+
- 🔄 자동 Base64 변환 또는 커스텀 업로더 사용
|
|
366
|
+
|
|
367
|
+
## 📋 변경 기록
|
|
368
|
+
|
|
369
|
+
### v0.0.1
|
|
370
|
+
|
|
371
|
+
- 🎉 **초기 릴리스**: 이미지 전용 BlockNote 에디터
|
|
372
|
+
- 🖼️ **이미지 업로드**: Base64 변환 및 드래그앤드롭 지원
|
|
373
|
+
- 🎨 **Theme 지원**: BlockNote 공식 theme prop 지원
|
|
374
|
+
- 📝 **Pretendard 폰트**: 기본 폰트 설정 (14px)
|
|
375
|
+
- 🚫 **미디어 제한**: 비디오/오디오/파일 업로드 비활성화
|
|
376
|
+
|
|
377
|
+
## 📄 라이선스
|
|
378
|
+
|
|
379
|
+
MIT License
|