@lumir-company/editor 0.4.0 โ 0.4.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 +115 -57
- package/dist/index.d.mts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +20 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# LumirEditor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**์ด๋ฏธ์ง ์ ์ฉ** BlockNote ๊ธฐ๋ฐ Rich Text ์๋ํฐ
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@lumir-company/editor)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -9,32 +9,32 @@
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## ๋ชฉ์ฐจ
|
|
13
13
|
|
|
14
|
-
- [ํน์ง](
|
|
15
|
-
- [๋น ๋ฅธ ์์](
|
|
16
|
-
- [์ด๋ฏธ์ง ์
๋ก๋](
|
|
14
|
+
- [ํน์ง](#ํน์ง)
|
|
15
|
+
- [๋น ๋ฅธ ์์](#๋น ๋ฅธ-์์)
|
|
16
|
+
- [์ด๋ฏธ์ง ์
๋ก๋](#์ด๋ฏธ์ง-์
๋ก๋)
|
|
17
17
|
- [S3 ์
๋ก๋ ์ค์ ](#1-s3-์
๋ก๋-๊ถ์ฅ)
|
|
18
|
-
- [ํ์ผ๋ช
์ปค์คํฐ๋ง์ด์ง](
|
|
18
|
+
- [ํ์ผ๋ช
์ปค์คํฐ๋ง์ด์ง](#ํ์ผ๋ช
-์ปค์คํฐ๋ง์ด์ง)
|
|
19
19
|
- [์ปค์คํ
์
๋ก๋](#2-์ปค์คํ
-์
๋ก๋)
|
|
20
|
-
- [Props API](
|
|
21
|
-
- [์ฌ์ฉ ์์ ](
|
|
22
|
-
- [์คํ์ผ๋ง](
|
|
23
|
-
- [ํธ๋ฌ๋ธ์ํ
](
|
|
20
|
+
- [Props API](#props-api)
|
|
21
|
+
- [์ฌ์ฉ ์์ ](#์ฌ์ฉ-์์ )
|
|
22
|
+
- [์คํ์ผ๋ง](#์คํ์ผ๋ง)
|
|
23
|
+
- [ํธ๋ฌ๋ธ์ํ
](#ํธ๋ฌ๋ธ์ํ
)
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## ํน์ง
|
|
28
28
|
|
|
29
|
-
| ํน์ง
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
29
|
+
| ํน์ง | ์ค๋ช
|
|
|
30
|
+
| ----------------------- | ------------------------------------------------------ |
|
|
31
|
+
| **์ด๋ฏธ์ง ์ ์ฉ** | ์ด๋ฏธ์ง ์
๋ก๋/๋๋๊ทธ์ค๋๋กญ๋ง ์ง์ (๋น๋์ค/์ค๋์ค ์ ๊ฑฐ) |
|
|
32
|
+
| **S3 ์ฐ๋** | Presigned URL ๊ธฐ๋ฐ S3 ์
๋ก๋ ๋ด์ฅ |
|
|
33
|
+
| **ํ์ผ๋ช
์ปค์คํฐ๋ง์ด์ง** | ์
๋ก๋ ํ์ผ๋ช
๋ณ๊ฒฝ ์ฝ๋ฐฑ + UUID ์๋ ์ถ๊ฐ ์ง์ |
|
|
34
|
+
| **๋ก๋ฉ ์คํผ๋** | ์ด๋ฏธ์ง ์
๋ก๋ ์ค ์๋ ์คํผ๋ ํ์ |
|
|
35
|
+
| **์ฑ๋ฅ ์ต์ ํ** | ์ ๋๋ฉ์ด์
๋นํ์ฑํ๋ก ๋น ๋ฅธ ๋ ๋๋ง |
|
|
36
|
+
| **TypeScript** | ์์ ํ ํ์
์์ ์ฑ |
|
|
37
|
+
| **ํ
๋ง ์ง์** | ๋ผ์ดํธ/๋คํฌ ํ
๋ง ๋ฐ ์ปค์คํ
ํ
๋ง |
|
|
38
38
|
|
|
39
39
|
### ์ง์ ์ด๋ฏธ์ง ํ์
|
|
40
40
|
|
|
@@ -44,7 +44,7 @@ PNG, JPEG/JPG, GIF, WebP, BMP, SVG
|
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
|
47
|
-
##
|
|
47
|
+
## ๋น ๋ฅธ ์์
|
|
48
48
|
|
|
49
49
|
### 1. ์ค์น
|
|
50
50
|
|
|
@@ -74,7 +74,7 @@ export default function App() {
|
|
|
74
74
|
}
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
>
|
|
77
|
+
> **์ค์**: `style.css`๋ฅผ ์ํฌํธํ์ง ์์ผ๋ฉด ์๋ํฐ๊ฐ ์ ์ ์๋ํ์ง ์์ต๋๋ค.
|
|
78
78
|
|
|
79
79
|
### 3. Next.js์์ ์ฌ์ฉ
|
|
80
80
|
|
|
@@ -102,7 +102,7 @@ export default function EditorPage() {
|
|
|
102
102
|
|
|
103
103
|
---
|
|
104
104
|
|
|
105
|
-
##
|
|
105
|
+
## ์ด๋ฏธ์ง ์
๋ก๋
|
|
106
106
|
|
|
107
107
|
### 1. S3 ์
๋ก๋ (๊ถ์ฅ)
|
|
108
108
|
|
|
@@ -140,10 +140,12 @@ production/blog/images/my-photo.png
|
|
|
140
140
|
|
|
141
141
|
---
|
|
142
142
|
|
|
143
|
-
###
|
|
143
|
+
### ํ์ผ๋ช
์ปค์คํฐ๋ง์ด์ง
|
|
144
144
|
|
|
145
145
|
์ฌ๋ฌ ์ด๋ฏธ์ง๋ฅผ ๋์์ ์
๋ก๋ํ ๋ ํ์ผ๋ช
์ค๋ณต์ ๋ฐฉ์งํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ฝ๊ฒ ๋ง๋๋ ๊ธฐ๋ฅ์
๋๋ค.
|
|
146
146
|
|
|
147
|
+
> **์ฐธ๊ณ **: ๊ธฐ๋ณธ์ ์ผ๋ก ํ์ฅ์๋ ์๋์ผ๋ก ๋ถ์ต๋๋ค. `preserveExtension: false`๋ก ์ค์ ํ๋ฉด ํ์ฅ์๋ฅผ ๋ถ์ด์ง ์์ต๋๋ค.
|
|
148
|
+
|
|
147
149
|
#### ์ต์
1: UUID ์๋ ์ถ๊ฐ
|
|
148
150
|
|
|
149
151
|
```tsx
|
|
@@ -172,10 +174,11 @@ production/blog/images/my-photo.png
|
|
|
172
174
|
apiEndpoint: "/api/s3/presigned",
|
|
173
175
|
env: "production",
|
|
174
176
|
path: "uploads",
|
|
175
|
-
fileNameTransform: (
|
|
176
|
-
//
|
|
177
|
+
fileNameTransform: (nameWithoutExt, file) => {
|
|
178
|
+
// nameWithoutExt๋ ํ์ฅ์๊ฐ ์ ๊ฑฐ๋ ํ์ผ๋ช
(์: "photo")
|
|
179
|
+
// ํ์ฅ์๋ ์๋์ผ๋ก ๋ถ์ต๋๋ค
|
|
177
180
|
const userId = getCurrentUserId();
|
|
178
|
-
return `${userId}_${
|
|
181
|
+
return `${userId}_${nameWithoutExt}`;
|
|
179
182
|
},
|
|
180
183
|
}}
|
|
181
184
|
/>
|
|
@@ -185,7 +188,9 @@ production/blog/images/my-photo.png
|
|
|
185
188
|
|
|
186
189
|
```
|
|
187
190
|
์๋ณธ: photo.png
|
|
188
|
-
|
|
191
|
+
โ nameWithoutExt: "photo"
|
|
192
|
+
โ ๋ณํ ํ: "user123_photo"
|
|
193
|
+
โ ์ต์ข
: user123_photo.png
|
|
189
194
|
```
|
|
190
195
|
|
|
191
196
|
#### ์ต์
3: ์กฐํฉ ์ฌ์ฉ (๊ถ์ฅ)
|
|
@@ -196,7 +201,7 @@ production/blog/images/my-photo.png
|
|
|
196
201
|
apiEndpoint: "/api/s3/presigned",
|
|
197
202
|
env: "production",
|
|
198
203
|
path: "uploads",
|
|
199
|
-
fileNameTransform: (
|
|
204
|
+
fileNameTransform: (nameWithoutExt) => `user123_${nameWithoutExt}`,
|
|
200
205
|
appendUUID: true, // ๋ณํ ํ UUID ์ถ๊ฐ
|
|
201
206
|
}}
|
|
202
207
|
/>
|
|
@@ -206,8 +211,10 @@ production/blog/images/my-photo.png
|
|
|
206
211
|
|
|
207
212
|
```
|
|
208
213
|
์๋ณธ: photo.png
|
|
209
|
-
|
|
210
|
-
|
|
214
|
+
โ nameWithoutExt: "photo"
|
|
215
|
+
1. fileNameTransform ์ ์ฉ: "user123_photo"
|
|
216
|
+
2. appendUUID ์ ์ฉ: "user123_photo_550e8400-e29b-41d4"
|
|
217
|
+
3. ํ์ฅ์ ๋ถ์ด๊ธฐ: user123_photo_550e8400-e29b-41d4.png
|
|
211
218
|
```
|
|
212
219
|
|
|
213
220
|
#### ์ค์ ์์ : ํ์์คํฌํ + UUID
|
|
@@ -220,11 +227,10 @@ function MyEditor() {
|
|
|
220
227
|
apiEndpoint: "/api/s3/presigned",
|
|
221
228
|
env: "production",
|
|
222
229
|
path: "uploads",
|
|
223
|
-
fileNameTransform: (
|
|
230
|
+
fileNameTransform: (nameWithoutExt, file) => {
|
|
231
|
+
// nameWithoutExt๋ ์ด๋ฏธ ํ์ฅ์๊ฐ ์ ๊ฑฐ๋จ
|
|
224
232
|
const timestamp = new Date().toISOString().split("T")[0]; // 2024-01-15
|
|
225
|
-
|
|
226
|
-
const nameWithoutExt = originalName.replace(`.${ext}`, "");
|
|
227
|
-
return `${timestamp}_${nameWithoutExt}.${ext}`;
|
|
233
|
+
return `${timestamp}_${nameWithoutExt}`;
|
|
228
234
|
},
|
|
229
235
|
appendUUID: true,
|
|
230
236
|
}}
|
|
@@ -236,7 +242,41 @@ function MyEditor() {
|
|
|
236
242
|
**๊ฒฐ๊ณผ:**
|
|
237
243
|
|
|
238
244
|
```
|
|
239
|
-
|
|
245
|
+
์๋ณธ: photo.png
|
|
246
|
+
โ nameWithoutExt: "photo"
|
|
247
|
+
1. fileNameTransform: "2024-01-15_photo"
|
|
248
|
+
2. appendUUID: "2024-01-15_photo_550e8400-e29b-41d4"
|
|
249
|
+
3. ํ์ฅ์ ๋ถ์ด๊ธฐ: 2024-01-15_photo_550e8400-e29b-41d4.png
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
#### ์ต์
4: ํ์ฅ์ ์ ๊ฑฐ (preserveExtension: false)
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
<LumirEditor
|
|
256
|
+
s3Upload={{
|
|
257
|
+
apiEndpoint: "/api/s3/presigned",
|
|
258
|
+
env: "production",
|
|
259
|
+
path: "uploads",
|
|
260
|
+
fileNameTransform: (nameWithoutExt) => `${nameWithoutExt}_custom`,
|
|
261
|
+
preserveExtension: false, // ํ์ฅ์ ์ ๋ถ์
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**๊ฒฐ๊ณผ:**
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
์๋ณธ: photo.png
|
|
270
|
+
โ nameWithoutExt: "photo"
|
|
271
|
+
โ ๋ณํ ํ: "photo_custom"
|
|
272
|
+
โ ์ต์ข
: photo_custom (ํ์ฅ์ ์์)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**์ฌ์ฉ ์ฌ๋ก**: WebP ๋ณํ ๋ฑ ์๋ฒ์์ ํ์ฅ์๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒฝ์ฐ
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
fileNameTransform: (nameWithoutExt) => `${nameWithoutExt}.webp`,
|
|
279
|
+
preserveExtension: false,
|
|
240
280
|
```
|
|
241
281
|
|
|
242
282
|
---
|
|
@@ -289,7 +329,7 @@ const imageUrl = await s3Uploader(imageFile);
|
|
|
289
329
|
|
|
290
330
|
---
|
|
291
331
|
|
|
292
|
-
##
|
|
332
|
+
## Props API
|
|
293
333
|
|
|
294
334
|
### ํต์ฌ Props
|
|
295
335
|
|
|
@@ -313,8 +353,9 @@ interface S3UploaderConfig {
|
|
|
313
353
|
path: string; // S3 ์ ์ฅ ๊ฒฝ๋ก
|
|
314
354
|
|
|
315
355
|
// ์ ํ (ํ์ผ๋ช
์ปค์คํฐ๋ง์ด์ง)
|
|
316
|
-
fileNameTransform?: (
|
|
317
|
-
appendUUID?: boolean; // true: ํ์ผ๋ช
๋ค์ UUID ์ถ๊ฐ
|
|
356
|
+
fileNameTransform?: (nameWithoutExt: string, file: File) => string; // ํ์ฅ์ ์ ์ธํ ์ด๋ฆ ๋ณํ
|
|
357
|
+
appendUUID?: boolean; // true: ํ์ผ๋ช
๋ค์ UUID ์ถ๊ฐ (ํ์ฅ์ ์์ ์ฝ์
)
|
|
358
|
+
preserveExtension?: boolean; // false: ํ์ฅ์๋ฅผ ๋ถ์ด์ง ์์ (๊ธฐ๋ณธ: true)
|
|
318
359
|
}
|
|
319
360
|
```
|
|
320
361
|
|
|
@@ -329,7 +370,14 @@ interface LumirEditorProps {
|
|
|
329
370
|
initialContent?: DefaultPartialBlock[] | string; // ์ด๊ธฐ ์ฝํ
์ธ (๋ธ๋ก ๋ฐฐ์ด ๋๋ JSON ๋ฌธ์์ด)
|
|
330
371
|
initialEmptyBlocks?: number; // ์ด๊ธฐ ๋น ๋ธ๋ก ๊ฐ์ (๊ธฐ๋ณธ: 3)
|
|
331
372
|
uploadFile?: (file: File) => Promise<string>; // ์ปค์คํ
ํ์ผ ์
๋ก๋ ํจ์
|
|
332
|
-
s3Upload?:
|
|
373
|
+
s3Upload?: {
|
|
374
|
+
apiEndpoint: string;
|
|
375
|
+
env: "development" | "production";
|
|
376
|
+
path: string;
|
|
377
|
+
fileNameTransform?: (nameWithoutExt: string, file: File) => string; // ํ์ฅ์ ์ ์ธํ ์ด๋ฆ ๋ณํ
|
|
378
|
+
appendUUID?: boolean; // UUID ์๋ ์ถ๊ฐ (ํ์ฅ์ ์)
|
|
379
|
+
preserveExtension?: boolean; // ํ์ฅ์ ์๋ ๋ถ์ด๊ธฐ (๊ธฐ๋ณธ: true)
|
|
380
|
+
};
|
|
333
381
|
|
|
334
382
|
// === ์ฝ๋ฐฑ ===
|
|
335
383
|
onContentChange?: (blocks: DefaultPartialBlock[]) => void; // ์ฝํ
์ธ ๋ณ๊ฒฝ ์ ํธ์ถ
|
|
@@ -366,7 +414,7 @@ interface LumirEditorProps {
|
|
|
366
414
|
|
|
367
415
|
---
|
|
368
416
|
|
|
369
|
-
##
|
|
417
|
+
## ์ฌ์ฉ ์์
|
|
370
418
|
|
|
371
419
|
### ์ฝ๊ธฐ ์ ์ฉ ๋ชจ๋
|
|
372
420
|
|
|
@@ -416,7 +464,7 @@ function EditorWithSave() {
|
|
|
416
464
|
|
|
417
465
|
---
|
|
418
466
|
|
|
419
|
-
##
|
|
467
|
+
## ์คํ์ผ๋ง
|
|
420
468
|
|
|
421
469
|
### Tailwind CSS์ ํจ๊ป ์ฌ์ฉ
|
|
422
470
|
|
|
@@ -454,7 +502,7 @@ import { LumirEditor, cn } from "@lumir-company/editor";
|
|
|
454
502
|
|
|
455
503
|
---
|
|
456
504
|
|
|
457
|
-
##
|
|
505
|
+
## ํธ๋ฌ๋ธ์ํ
|
|
458
506
|
|
|
459
507
|
### ํ์ ์ฒดํฌ๋ฆฌ์คํธ
|
|
460
508
|
|
|
@@ -468,10 +516,10 @@ import { LumirEditor, cn } from "@lumir-company/editor";
|
|
|
468
516
|
#### 1. ์๋ํฐ๊ฐ ๋ณด์ด์ง ์์
|
|
469
517
|
|
|
470
518
|
```tsx
|
|
471
|
-
//
|
|
519
|
+
// ์๋ชป๋จ
|
|
472
520
|
<LumirEditor />;
|
|
473
521
|
|
|
474
|
-
//
|
|
522
|
+
// ์ฌ๋ฐ๋ฆ
|
|
475
523
|
import "@lumir-company/editor/style.css";
|
|
476
524
|
<div className="h-[400px]">
|
|
477
525
|
<LumirEditor />
|
|
@@ -481,10 +529,10 @@ import "@lumir-company/editor/style.css";
|
|
|
481
529
|
#### 2. Next.js Hydration ์ค๋ฅ
|
|
482
530
|
|
|
483
531
|
```tsx
|
|
484
|
-
//
|
|
532
|
+
// ์๋ชป๋จ
|
|
485
533
|
import { LumirEditor } from "@lumir-company/editor";
|
|
486
534
|
|
|
487
|
-
//
|
|
535
|
+
// ์ฌ๋ฐ๋ฆ
|
|
488
536
|
const LumirEditor = dynamic(
|
|
489
537
|
() =>
|
|
490
538
|
import("@lumir-company/editor").then((m) => ({ default: m.LumirEditor })),
|
|
@@ -508,7 +556,7 @@ const LumirEditor = dynamic(
|
|
|
508
556
|
#### 4. ์ฌ๋ฌ ์ด๋ฏธ์ง ์
๋ก๋ ์ ์ค๋ณต ๋ฌธ์
|
|
509
557
|
|
|
510
558
|
```tsx
|
|
511
|
-
//
|
|
559
|
+
// ํด๊ฒฐ: appendUUID ์ฌ์ฉ
|
|
512
560
|
<LumirEditor
|
|
513
561
|
s3Upload={{
|
|
514
562
|
apiEndpoint: "/api/s3/presigned",
|
|
@@ -521,7 +569,7 @@ const LumirEditor = dynamic(
|
|
|
521
569
|
|
|
522
570
|
---
|
|
523
571
|
|
|
524
|
-
##
|
|
572
|
+
## ์ ํธ๋ฆฌํฐ API
|
|
525
573
|
|
|
526
574
|
### ContentUtils
|
|
527
575
|
|
|
@@ -554,23 +602,33 @@ const uploader = createS3Uploader({
|
|
|
554
602
|
const url = await uploader(imageFile);
|
|
555
603
|
```
|
|
556
604
|
|
|
557
|
-
##
|
|
605
|
+
## ๊ด๋ จ ๋งํฌ
|
|
558
606
|
|
|
559
607
|
- [npm Package](https://www.npmjs.com/package/@lumir-company/editor)
|
|
560
608
|
- [BlockNote Documentation](https://www.blocknotejs.org/)
|
|
561
609
|
|
|
562
610
|
---
|
|
563
611
|
|
|
564
|
-
##
|
|
612
|
+
## ๋ณ๊ฒฝ ๋ก๊ทธ
|
|
613
|
+
|
|
614
|
+
### v0.4.1
|
|
615
|
+
|
|
616
|
+
- `preserveExtension` prop ์ถ๊ฐ - ํ์ฅ์ ์๋ ๋ถ์ด๊ธฐ ์ ์ด (๊ธฐ๋ณธ: true)
|
|
617
|
+
- **์ค์**: ํ์ผ๋ช
๋ณํ ์ ํ์ฅ์ ์์น ์์ (ํ์ฅ์๊ฐ ํญ์ ๋งจ ๋ค์ ์ค๋๋ก)
|
|
618
|
+
- **Breaking Change**: `fileNameTransform` ํ๋ผ๋ฏธํฐ ๋ณ๊ฒฝ - ์ด์ ํ์ฅ์ ์ ์ธํ ํ์ผ๋ช
๋ง ์ ๋ฌ๋จ
|
|
619
|
+
- ์ด์ : `fileNameTransform: (originalName, file) => ...` โ originalName์ ํ์ฅ์ ํฌํจ
|
|
620
|
+
- ๋ณ๊ฒฝ: `fileNameTransform: (nameWithoutExt, file) => ...` โ nameWithoutExt์ ํ์ฅ์ ์ ์ธ
|
|
621
|
+
- ํ์ฅ์ ์ ๊ฑฐ ์ฌ์ฉ ์ฌ๋ก ๋ฌธ์ํ
|
|
622
|
+
- README ์์ ๋ฐ ์ค๋ช
๊ฐ์
|
|
565
623
|
|
|
566
624
|
### v0.4.0
|
|
567
625
|
|
|
568
|
-
-
|
|
569
|
-
-
|
|
570
|
-
-
|
|
571
|
-
-
|
|
626
|
+
- ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ (`fileNameTransform`) ์ถ๊ฐ
|
|
627
|
+
- UUID ์๋ ์ถ๊ฐ ์ต์
(`appendUUID`) ์ถ๊ฐ
|
|
628
|
+
- ์ฌ๋ฌ ์ด๋ฏธ์ง ๋์ ์
๋ก๋ ์ ์ค๋ณต ๋ฌธ์ ํด๊ฒฐ
|
|
629
|
+
- ๋ฌธ์ ๋ํญ ๊ฐ์
|
|
572
630
|
|
|
573
631
|
### v0.3.3
|
|
574
632
|
|
|
575
|
-
-
|
|
576
|
-
-
|
|
633
|
+
- ์๋ํฐ ์ฌ์์ฑ ๋ฐฉ์ง ์ต์ ํ
|
|
634
|
+
- ํ์
์ ์ ๊ฐ์
|
package/dist/index.d.mts
CHANGED
|
@@ -22,10 +22,12 @@ interface LumirEditorProps {
|
|
|
22
22
|
apiEndpoint: string;
|
|
23
23
|
env: "development" | "production";
|
|
24
24
|
path: string;
|
|
25
|
-
/** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ -
|
|
26
|
-
fileNameTransform?: (
|
|
25
|
+
/** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ - ํ์ฅ์๋ฅผ ์ ์ธํ ํ์ผ๋ช
์ ๋ฐ์ ๋ณํํฉ๋๋ค */
|
|
26
|
+
fileNameTransform?: (nameWithoutExt: string, file: File) => string;
|
|
27
27
|
/** true์ผ ๊ฒฝ์ฐ ํ์ผ๋ช
๋ค์ UUID๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํฉ๋๋ค (์: image_abc123.png) */
|
|
28
28
|
appendUUID?: boolean;
|
|
29
|
+
/** false๋ก ์ค์ ํ๋ฉด ํ์ฅ์๋ฅผ ์๋์ผ๋ก ๋ถ์ด์ง ์์ (๊ธฐ๋ณธ: true) */
|
|
30
|
+
preserveExtension?: boolean;
|
|
29
31
|
};
|
|
30
32
|
allowVideoUpload?: boolean;
|
|
31
33
|
allowAudioUpload?: boolean;
|
|
@@ -138,10 +140,12 @@ interface S3UploaderConfig {
|
|
|
138
140
|
apiEndpoint: string;
|
|
139
141
|
env: "production" | "development";
|
|
140
142
|
path: string;
|
|
141
|
-
/** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ -
|
|
142
|
-
fileNameTransform?: (
|
|
143
|
+
/** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ - ํ์ฅ์๋ฅผ ์ ์ธํ ํ์ผ๋ช
์ ๋ฐ์ ๋ณํํฉ๋๋ค */
|
|
144
|
+
fileNameTransform?: (nameWithoutExt: string, file: File) => string;
|
|
143
145
|
/** true์ผ ๊ฒฝ์ฐ ํ์ผ๋ช
๋ค์ UUID๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํฉ๋๋ค (์: image_abc123.png) */
|
|
144
146
|
appendUUID?: boolean;
|
|
147
|
+
/** false๋ก ์ค์ ํ๋ฉด ํ์ฅ์๋ฅผ ์๋์ผ๋ก ๋ถ์ด์ง ์์ (๊ธฐ๋ณธ: true) */
|
|
148
|
+
preserveExtension?: boolean;
|
|
145
149
|
}
|
|
146
150
|
declare const createS3Uploader: (config: S3UploaderConfig) => (file: File) => Promise<string>;
|
|
147
151
|
|
package/dist/index.d.ts
CHANGED
|
@@ -22,10 +22,12 @@ interface LumirEditorProps {
|
|
|
22
22
|
apiEndpoint: string;
|
|
23
23
|
env: "development" | "production";
|
|
24
24
|
path: string;
|
|
25
|
-
/** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ -
|
|
26
|
-
fileNameTransform?: (
|
|
25
|
+
/** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ - ํ์ฅ์๋ฅผ ์ ์ธํ ํ์ผ๋ช
์ ๋ฐ์ ๋ณํํฉ๋๋ค */
|
|
26
|
+
fileNameTransform?: (nameWithoutExt: string, file: File) => string;
|
|
27
27
|
/** true์ผ ๊ฒฝ์ฐ ํ์ผ๋ช
๋ค์ UUID๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํฉ๋๋ค (์: image_abc123.png) */
|
|
28
28
|
appendUUID?: boolean;
|
|
29
|
+
/** false๋ก ์ค์ ํ๋ฉด ํ์ฅ์๋ฅผ ์๋์ผ๋ก ๋ถ์ด์ง ์์ (๊ธฐ๋ณธ: true) */
|
|
30
|
+
preserveExtension?: boolean;
|
|
29
31
|
};
|
|
30
32
|
allowVideoUpload?: boolean;
|
|
31
33
|
allowAudioUpload?: boolean;
|
|
@@ -138,10 +140,12 @@ interface S3UploaderConfig {
|
|
|
138
140
|
apiEndpoint: string;
|
|
139
141
|
env: "production" | "development";
|
|
140
142
|
path: string;
|
|
141
|
-
/** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ -
|
|
142
|
-
fileNameTransform?: (
|
|
143
|
+
/** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ - ํ์ฅ์๋ฅผ ์ ์ธํ ํ์ผ๋ช
์ ๋ฐ์ ๋ณํํฉ๋๋ค */
|
|
144
|
+
fileNameTransform?: (nameWithoutExt: string, file: File) => string;
|
|
143
145
|
/** true์ผ ๊ฒฝ์ฐ ํ์ผ๋ช
๋ค์ UUID๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํฉ๋๋ค (์: image_abc123.png) */
|
|
144
146
|
appendUUID?: boolean;
|
|
147
|
+
/** false๋ก ์ค์ ํ๋ฉด ํ์ฅ์๋ฅผ ์๋์ผ๋ก ๋ถ์ด์ง ์์ (๊ธฐ๋ณธ: true) */
|
|
148
|
+
preserveExtension?: boolean;
|
|
145
149
|
}
|
|
146
150
|
declare const createS3Uploader: (config: S3UploaderConfig) => (file: File) => Promise<string>;
|
|
147
151
|
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,14 @@ var generateUUID = () => {
|
|
|
51
51
|
});
|
|
52
52
|
};
|
|
53
53
|
var createS3Uploader = (config) => {
|
|
54
|
-
const {
|
|
54
|
+
const {
|
|
55
|
+
apiEndpoint,
|
|
56
|
+
env,
|
|
57
|
+
path,
|
|
58
|
+
fileNameTransform,
|
|
59
|
+
appendUUID,
|
|
60
|
+
preserveExtension = true
|
|
61
|
+
} = config;
|
|
55
62
|
if (!apiEndpoint || apiEndpoint.trim() === "") {
|
|
56
63
|
throw new Error(
|
|
57
64
|
"apiEndpoint is required for S3 upload. Please provide a valid API endpoint."
|
|
@@ -73,12 +80,19 @@ var createS3Uploader = (config) => {
|
|
|
73
80
|
return `${name}_${generateUUID()}${ext}`;
|
|
74
81
|
};
|
|
75
82
|
const generateHierarchicalFileName = (file) => {
|
|
76
|
-
|
|
83
|
+
const originalName = file.name;
|
|
84
|
+
const lastDotIndex = originalName.lastIndexOf(".");
|
|
85
|
+
const nameWithoutExt = lastDotIndex === -1 ? originalName : originalName.substring(0, lastDotIndex);
|
|
86
|
+
const extension = lastDotIndex === -1 ? "" : originalName.substring(lastDotIndex);
|
|
87
|
+
let filename = nameWithoutExt;
|
|
77
88
|
if (fileNameTransform) {
|
|
78
89
|
filename = fileNameTransform(filename, file);
|
|
79
90
|
}
|
|
80
91
|
if (appendUUID) {
|
|
81
|
-
filename =
|
|
92
|
+
filename = `${filename}_${generateUUID()}`;
|
|
93
|
+
}
|
|
94
|
+
if (preserveExtension) {
|
|
95
|
+
filename = `${filename}${extension}`;
|
|
82
96
|
}
|
|
83
97
|
return `${env}/${path}/${filename}`;
|
|
84
98
|
};
|
|
@@ -306,6 +320,7 @@ function LumirEditor({
|
|
|
306
320
|
env: s3Upload.env,
|
|
307
321
|
path: s3Upload.path,
|
|
308
322
|
appendUUID: s3Upload.appendUUID,
|
|
323
|
+
preserveExtension: s3Upload.preserveExtension,
|
|
309
324
|
// ์ต์ ์ฝ๋ฐฑ์ ํญ์ ์ฌ์ฉํ๋๋ก ref๋ฅผ ํตํด ์ ๊ทผ
|
|
310
325
|
fileNameTransform: (originalName, file) => {
|
|
311
326
|
return fileNameTransformRef.current ? fileNameTransformRef.current(originalName, file) : originalName;
|
|
@@ -315,7 +330,8 @@ function LumirEditor({
|
|
|
315
330
|
s3Upload?.apiEndpoint,
|
|
316
331
|
s3Upload?.env,
|
|
317
332
|
s3Upload?.path,
|
|
318
|
-
s3Upload?.appendUUID
|
|
333
|
+
s3Upload?.appendUUID,
|
|
334
|
+
s3Upload?.preserveExtension
|
|
319
335
|
]);
|
|
320
336
|
const editor = (0, import_react2.useCreateBlockNote)(
|
|
321
337
|
{
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/LumirEditor.tsx","../src/utils/cn.ts","../src/utils/s3-uploader.ts"],"sourcesContent":["\"use client\";\r\n\r\n// ์ปดํฌ๋ํธ ๋ฐ ์ ํธ๋ฆฌํฐ export\r\nexport {\r\n default as LumirEditor,\r\n ContentUtils,\r\n EditorConfig,\r\n} from \"./components/LumirEditor\";\r\nexport { cn } from \"./utils/cn\";\r\nexport { createS3Uploader } from \"./utils/s3-uploader\";\r\n\r\n// ํ์
export (๋ณ๋ ํ์ผ์์ ๊ด๋ฆฌ)\r\nexport type {\r\n LumirEditorProps,\r\n EditorType,\r\n DefaultPartialBlock,\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema,\r\n PartialBlock,\r\n BlockNoteEditor,\r\n} from \"./types\";\r\nexport type { S3UploaderConfig } from \"./utils/s3-uploader\";\r\n","\"use client\";\r\n\r\nimport { useEffect, useMemo, useCallback, useState, useRef } from \"react\";\r\nimport {\r\n useCreateBlockNote,\r\n SideMenu as BlockSideMenu,\r\n SideMenuController,\r\n DragHandleButton,\r\n SuggestionMenuController,\r\n getDefaultReactSlashMenuItems,\r\n} from \"@blocknote/react\";\r\nimport { BlockNoteView } from \"@blocknote/mantine\";\r\nimport { cn } from \"../utils/cn\";\r\n\r\nimport type {\r\n DefaultPartialBlock,\r\n LumirEditorProps,\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema,\r\n} from \"../types\";\r\n\r\nimport { createS3Uploader } from \"../utils/s3-uploader\";\r\n\r\n// ==========================================\r\n// ์ ํธ๋ฆฌํฐ ํด๋์ค๋ค\r\n// ==========================================\r\n\r\n/**\r\n * ์ฝํ
์ธ ๊ด๋ฆฌ ์ ํธ๋ฆฌํฐ\r\n * ๊ธฐ๋ณธ ๋ธ๋ก ์์ฑ ๋ฐ ์ฝํ
์ธ ๊ฒ์ฆ ๋ก์ง์ ๋ด๋น\r\n */\r\nexport class ContentUtils {\r\n /**\r\n * JSON ๋ฌธ์์ด์ ์ ํจ์ฑ์ ๊ฒ์ฆํฉ๋๋ค\r\n * @param jsonString ๊ฒ์ฆํ JSON ๋ฌธ์์ด\r\n * @returns ์ ํจํ JSON ๋ฌธ์์ด์ธ์ง ์ฌ๋ถ\r\n */\r\n static isValidJSONString(jsonString: string): boolean {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n return Array.isArray(parsed);\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * JSON ๋ฌธ์์ด์ DefaultPartialBlock ๋ฐฐ์ด๋ก ํ์ฑํฉ๋๋ค\r\n * @param jsonString JSON ๋ฌธ์์ด\r\n * @returns ํ์ฑ๋ ๋ธ๋ก ๋ฐฐ์ด ๋๋ null (ํ์ฑ ์คํจ ์)\r\n */\r\n static parseJSONContent(jsonString: string): DefaultPartialBlock[] | null {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n if (Array.isArray(parsed)) {\r\n return parsed as DefaultPartialBlock[];\r\n }\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * ๊ธฐ๋ณธ paragraph ๋ธ๋ก ์์ฑ\r\n * @returns ๊ธฐ๋ณธ ์ค์ ์ด ์ ์ฉ๋ DefaultPartialBlock\r\n */\r\n static createDefaultBlock(): DefaultPartialBlock {\r\n return {\r\n type: \"paragraph\",\r\n props: {\r\n textColor: \"default\",\r\n backgroundColor: \"default\",\r\n textAlignment: \"left\",\r\n },\r\n content: [{ type: \"text\", text: \"\", styles: {} }],\r\n children: [],\r\n };\r\n }\r\n\r\n /**\r\n * ์ฝํ
์ธ ์ ํจ์ฑ ๊ฒ์ฆ ๋ฐ ๊ธฐ๋ณธ๊ฐ ์ค์ \r\n * @param content ์ฌ์ฉ์ ์ ๊ณต ์ฝํ
์ธ (๊ฐ์ฒด ๋ฐฐ์ด ๋๋ JSON ๋ฌธ์์ด)\r\n * @param emptyBlockCount ๋น ๋ธ๋ก ๊ฐ์ (๊ธฐ๋ณธ๊ฐ: 3)\r\n * @returns ๊ฒ์ฆ๋ ์ฝํ
์ธ ๋ฐฐ์ด\r\n */\r\n static validateContent(\r\n content?: DefaultPartialBlock[] | string,\r\n emptyBlockCount: number = 3\r\n ): DefaultPartialBlock[] {\r\n // 1. ๋ฌธ์์ด์ธ ๊ฒฝ์ฐ JSON ํ์ฑ ์๋\r\n if (typeof content === \"string\") {\r\n if (content.trim() === \"\") {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n const parsedContent = this.parseJSONContent(content);\r\n if (parsedContent && parsedContent.length > 0) {\r\n return parsedContent;\r\n }\r\n\r\n // ํ์ฑ ์คํจ ์ ๋น ๋ธ๋ก ์์ฑ\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n // 2. ๋ฐฐ์ด์ธ ๊ฒฝ์ฐ ๊ธฐ์กด ๋ก์ง\r\n if (!content || content.length === 0) {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n return content;\r\n }\r\n\r\n /**\r\n * ๋น ๋ธ๋ก๋ค์ ์์ฑํฉ๋๋ค\r\n * @param emptyBlockCount ์์ฑํ ๋ธ๋ก ๊ฐ์\r\n * @returns ์์ฑ๋ ๋น ๋ธ๋ก ๋ฐฐ์ด\r\n */\r\n private static createEmptyBlocks(\r\n emptyBlockCount: number\r\n ): DefaultPartialBlock[] {\r\n return Array.from({ length: emptyBlockCount }, () =>\r\n this.createDefaultBlock()\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * ์๋ํฐ ์ค์ ๊ด๋ฆฌ ์ ํธ๋ฆฌํฐ\r\n * ๊ฐ์ข
์ค์ ์ ๊ธฐ๋ณธ๊ฐ๊ณผ ๊ฒ์ฆ ๋ก์ง์ ๋ด๋น\r\n */\r\nexport class EditorConfig {\r\n /**\r\n * ํ
์ด๋ธ ์ค์ ๊ธฐ๋ณธ๊ฐ ์ ์ฉ\r\n * @param userTables ์ฌ์ฉ์ ํ
์ด๋ธ ์ค์ \r\n * @returns ๊ธฐ๋ณธ๊ฐ์ด ์ ์ฉ๋ ํ
์ด๋ธ ์ค์ \r\n */\r\n static getDefaultTableConfig(userTables?: LumirEditorProps[\"tables\"]) {\r\n return {\r\n splitCells: userTables?.splitCells ?? true,\r\n cellBackgroundColor: userTables?.cellBackgroundColor ?? true,\r\n cellTextColor: userTables?.cellTextColor ?? true,\r\n headers: userTables?.headers ?? true,\r\n };\r\n }\r\n\r\n /**\r\n * ํค๋ฉ ์ค์ ๊ธฐ๋ณธ๊ฐ ์ ์ฉ\r\n * @param userHeading ์ฌ์ฉ์ ํค๋ฉ ์ค์ \r\n * @returns ๊ธฐ๋ณธ๊ฐ์ด ์ ์ฉ๋ ํค๋ฉ ์ค์ \r\n */\r\n static getDefaultHeadingConfig(userHeading?: LumirEditorProps[\"heading\"]) {\r\n return userHeading?.levels && userHeading.levels.length > 0\r\n ? userHeading\r\n : { levels: [1, 2, 3, 4, 5, 6] as (1 | 2 | 3 | 4 | 5 | 6)[] };\r\n }\r\n\r\n /**\r\n * ๋นํ์ฑํํ ํ์ฅ ๊ธฐ๋ฅ ๋ชฉ๋ก ์์ฑ\r\n * @param userExtensions ์ฌ์ฉ์ ์ ์ ๋นํ์ฑ ํ์ฅ\r\n * @param allowVideo ๋น๋์ค ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @param allowAudio ์ค๋์ค ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @param allowFile ์ผ๋ฐ ํ์ผ ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @returns ๋นํ์ฑํํ ํ์ฅ ๊ธฐ๋ฅ ๋ชฉ๋ก\r\n */\r\n static getDisabledExtensions(\r\n userExtensions?: string[],\r\n allowVideo = false,\r\n allowAudio = false,\r\n allowFile = false\r\n ): string[] {\r\n const set = new Set<string>(userExtensions ?? []);\r\n if (!allowVideo) set.add(\"video\");\r\n if (!allowAudio) set.add(\"audio\");\r\n if (!allowFile) set.add(\"file\");\r\n return Array.from(set);\r\n }\r\n}\r\n\r\n// ํ์ผ ํ์
๊ฒ์ฆ ํจ์\r\nconst isImageFile = (file: File): boolean => {\r\n return (\r\n file.size > 0 &&\r\n (file.type?.startsWith(\"image/\") ||\r\n (!file.type && /\\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(file.name || \"\")))\r\n );\r\n};\r\n\r\nexport default function LumirEditor({\r\n // editor options\r\n initialContent,\r\n initialEmptyBlocks = 3,\r\n uploadFile,\r\n s3Upload,\r\n tables,\r\n heading,\r\n defaultStyles = true,\r\n disableExtensions,\r\n tabBehavior = \"prefer-navigate-ui\",\r\n trailingBlock = true,\r\n allowVideoUpload = false,\r\n allowAudioUpload = false,\r\n allowFileUpload = false,\r\n // view options\r\n editable = true,\r\n theme = \"light\",\r\n formattingToolbar = true,\r\n linkToolbar = true,\r\n sideMenu = true,\r\n emojiPicker = true,\r\n filePanel = true,\r\n tableHandles = true,\r\n onSelectionChange,\r\n className = \"\",\r\n sideMenuAddButton = false,\r\n // callbacks / refs\r\n onContentChange,\r\n}: LumirEditorProps) {\r\n // ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ์ํ\r\n const [isUploading, setIsUploading] = useState(false);\r\n const validatedContent = useMemo<DefaultPartialBlock[]>(() => {\r\n return ContentUtils.validateContent(initialContent, initialEmptyBlocks);\r\n }, [initialContent, initialEmptyBlocks]);\r\n\r\n // ํ
์ด๋ธ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const tableConfig = useMemo(() => {\r\n return EditorConfig.getDefaultTableConfig(tables);\r\n }, [\r\n tables?.splitCells,\r\n tables?.cellBackgroundColor,\r\n tables?.cellTextColor,\r\n tables?.headers,\r\n ]);\r\n\r\n // ํค๋ฉ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const headingConfig = useMemo(() => {\r\n return EditorConfig.getDefaultHeadingConfig(heading);\r\n }, [heading?.levels?.join(\",\") ?? \"\"]);\r\n\r\n // ๋นํ์ฑํ ํ์ฅ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const disabledExtensions = useMemo(() => {\r\n return EditorConfig.getDisabledExtensions(\r\n disableExtensions,\r\n allowVideoUpload,\r\n allowAudioUpload,\r\n allowFileUpload\r\n );\r\n }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);\r\n\r\n // fileNameTransform ์ฝ๋ฐฑ์ ref๋ก ๊ด๋ฆฌ (์๋ํฐ ์ฌ์์ฑ ๋ฐฉ์ง)\r\n const fileNameTransformRef = useRef(s3Upload?.fileNameTransform);\r\n useEffect(() => {\r\n fileNameTransformRef.current = s3Upload?.fileNameTransform;\r\n }, [s3Upload?.fileNameTransform]);\r\n\r\n // S3 ์
๋ก๋ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
(๊ฐ์ฒด ์ฐธ์กฐ ์์ ํ)\r\n // ์ฃผ์: fileNameTransform์ ref๋ก ๊ด๋ฆฌํ๋ฏ๋ก ์์กด์ฑ์์ ์ ์ธ\r\n const memoizedS3Upload = useMemo(() => {\r\n if (!s3Upload) return undefined;\r\n return {\r\n apiEndpoint: s3Upload.apiEndpoint,\r\n env: s3Upload.env,\r\n path: s3Upload.path,\r\n appendUUID: s3Upload.appendUUID,\r\n // ์ต์ ์ฝ๋ฐฑ์ ํญ์ ์ฌ์ฉํ๋๋ก ref๋ฅผ ํตํด ์ ๊ทผ\r\n fileNameTransform: ((originalName: string, file: File) => {\r\n return fileNameTransformRef.current\r\n ? fileNameTransformRef.current(originalName, file)\r\n : originalName;\r\n }) as ((originalName: string, file: File) => string) | undefined,\r\n };\r\n }, [\r\n s3Upload?.apiEndpoint,\r\n s3Upload?.env,\r\n s3Upload?.path,\r\n s3Upload?.appendUUID,\r\n ]);\r\n\r\n const editor = useCreateBlockNote<\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema\r\n >(\r\n {\r\n initialContent: validatedContent as DefaultPartialBlock[],\r\n tables: tableConfig,\r\n heading: headingConfig,\r\n animations: false, // ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๋๋ฉ์ด์
๋นํ์ฑํ\r\n defaultStyles,\r\n // ํ์ฅ ๋นํ์ฑ: ๋น๋์ค/์ค๋์ค/ํ์ผ ์ ์ด\r\n disableExtensions: disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile: async (file) => {\r\n // ์ด๋ฏธ์ง ํ์ผ๋ง ํ์ฉ (์ด๋ฏธ์ง ์ ์ฉ ์๋ํฐ)\r\n if (!isImageFile(file)) {\r\n throw new Error(\"Only image files are allowed\");\r\n }\r\n\r\n try {\r\n let imageUrl: string;\r\n\r\n // 1. ์ฌ์ฉ์ ์ ์ uploadFile ์ฐ์ \r\n if (uploadFile) {\r\n imageUrl = await uploadFile(file);\r\n }\r\n // 2. S3 ์
๋ก๋ (uploadFile ์์ ๋)\r\n else if (memoizedS3Upload?.apiEndpoint) {\r\n const s3Uploader = createS3Uploader(memoizedS3Upload);\r\n imageUrl = await s3Uploader(file);\r\n }\r\n // 3. ์
๋ก๋ ๋ฐฉ๋ฒ์ด ์์ผ๋ฉด ์๋ฌ\r\n else {\r\n throw new Error(\"No upload method available\");\r\n }\r\n\r\n // BlockNote๊ฐ ์๋์ผ๋ก ์ด๋ฏธ์ง ๋ธ๋ก์ ์์ฑํ๋๋ก URL๋ง ๋ฐํ\r\n return imageUrl;\r\n } catch (error) {\r\n console.error(\"Image upload failed:\", error);\r\n throw new Error(\r\n \"Upload failed: \" +\r\n (error instanceof Error ? error.message : String(error))\r\n );\r\n }\r\n },\r\n pasteHandler: (ctx) => {\r\n const { event, editor, defaultPasteHandler } = ctx as any;\r\n const fileList =\r\n (event?.clipboardData?.files as FileList | null) ?? null;\r\n const files: File[] = fileList ? Array.from(fileList) : [];\r\n const acceptedFiles: File[] = files.filter(isImageFile);\r\n\r\n // ํ์ผ์ด ์์ง๋ง ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ ์ฒ๋ฆฌ ๋ง๊ณ ๋ฌด์\r\n if (files.length > 0 && acceptedFiles.length === 0) {\r\n event.preventDefault();\r\n return true;\r\n }\r\n\r\n // ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ ์ฒ๋ฆฌ\r\n if (acceptedFiles.length === 0) {\r\n return defaultPasteHandler() ?? false;\r\n }\r\n\r\n event.preventDefault();\r\n (async () => {\r\n // ๋ถ์ฌ๋ฃ๊ธฐ๋ก ์ฌ๋ฌ ์ด๋ฏธ์ง ์
๋ก๋ ์ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // ์๋ํฐ์ uploadFile ํจ์ ์ฌ์ฉ (ํต์ผ๋ ๋ก์ง)\r\n const url = await editor.uploadFile(file);\r\n editor.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n return true;\r\n },\r\n },\r\n [\r\n validatedContent,\r\n tableConfig,\r\n headingConfig,\r\n defaultStyles,\r\n disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile,\r\n memoizedS3Upload,\r\n ]\r\n );\r\n\r\n // ํธ์ง ๊ฐ๋ฅ ์ฌ๋ถ ์ค์ \r\n useEffect(() => {\r\n if (editor) {\r\n editor.isEditable = editable;\r\n }\r\n }, [editor, editable]);\r\n\r\n // ์ฝํ
์ธ ๋ณ๊ฒฝ ๊ฐ์ง\r\n useEffect(() => {\r\n if (!editor || !onContentChange) return;\r\n\r\n const handleContentChange = () => {\r\n // BlockNote์ ์ฌ๋ฐ๋ฅธ API ์ฌ์ฉ\r\n const blocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n onContentChange(blocks);\r\n };\r\n\r\n return editor.onEditorContentChange(handleContentChange);\r\n }, [editor, onContentChange]);\r\n\r\n // ๋๋๊ทธ์ค๋๋กญ ์ด๋ฏธ์ง ์ฒ๋ฆฌ\r\n useEffect(() => {\r\n const el = editor?.domElement as HTMLElement | undefined;\r\n if (!el) return;\r\n\r\n const handleDragOver = (e: DragEvent) => {\r\n if (e.defaultPrevented) return;\r\n const hasFiles = (\r\n e.dataTransfer?.types as unknown as string[] | undefined\r\n )?.includes?.(\"Files\");\r\n if (hasFiles) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n }\r\n };\r\n\r\n const handleDrop = (e: DragEvent) => {\r\n if (!e.dataTransfer) return;\r\n const hasFiles = (\r\n (e.dataTransfer.types as unknown as string[] | undefined) ?? []\r\n ).includes(\"Files\");\r\n if (!hasFiles) return;\r\n\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const items = Array.from(e.dataTransfer.items ?? []);\r\n const files = items\r\n .filter((it) => it.kind === \"file\")\r\n .map((it) => it.getAsFile())\r\n .filter((f): f is File => !!f);\r\n\r\n // ์ด๋ฏธ์ง ํ์ผ๋ง ํ์ฉ\r\n const acceptedFiles = files.filter(isImageFile);\r\n\r\n if (acceptedFiles.length === 0) return;\r\n\r\n (async () => {\r\n // ๋๋๊ทธ์ค๋๋กญ์ผ๋ก ์ฌ๋ฌ ์ด๋ฏธ์ง ์
๋ก๋ ์ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // ์๋ํฐ์ uploadFile ํจ์ ์ฌ์ฉ (์ผ๊ด๋ ๋ก์ง)\r\n if (editor?.uploadFile) {\r\n const url = await editor.uploadFile(file);\r\n if (url) {\r\n editor.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\r\n }\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n };\r\n\r\n el.addEventListener(\"dragover\", handleDragOver, { capture: true });\r\n el.addEventListener(\"drop\", handleDrop, { capture: true });\r\n\r\n return () => {\r\n el.removeEventListener(\"dragover\", handleDragOver, {\r\n capture: true,\r\n } as any);\r\n el.removeEventListener(\"drop\", handleDrop, { capture: true } as any);\r\n };\r\n }, [editor]);\r\n\r\n // SideMenu ์ค์ (Add ๋ฒํผ ์ ์ด)\r\n const computedSideMenu = useMemo(() => {\r\n return sideMenuAddButton ? sideMenu : false;\r\n }, [sideMenuAddButton, sideMenu]);\r\n\r\n // Add ๋ฒํผ ์๋ ์ฌ์ด๋ ๋ฉ๋ด (๋๋๊ทธ ํธ๋ค๋ง) - ๋ฉ๋ชจ์ด์ ์ด์
\r\n const DragHandleOnlySideMenu = useMemo(() => {\r\n return (props: any) => (\r\n <BlockSideMenu {...props}>\r\n <DragHandleButton {...props} />\r\n </BlockSideMenu>\r\n );\r\n }, []);\r\n\r\n return (\r\n <div\r\n className={cn(\"lumirEditor\", className)}\r\n style={{ position: \"relative\" }}\r\n >\r\n <BlockNoteView\r\n editor={editor}\r\n editable={editable}\r\n theme={theme}\r\n formattingToolbar={formattingToolbar}\r\n linkToolbar={linkToolbar}\r\n sideMenu={computedSideMenu}\r\n slashMenu={false}\r\n emojiPicker={emojiPicker}\r\n filePanel={filePanel}\r\n tableHandles={tableHandles}\r\n onSelectionChange={onSelectionChange}\r\n >\r\n {\r\n <SuggestionMenuController\r\n triggerCharacter=\"/\"\r\n getItems={useCallback(\r\n async (query: string) => {\r\n const items = getDefaultReactSlashMenuItems(editor);\r\n // ๋น๋์ค, ์ค๋์ค, ํ์ผ ๊ด๋ จ ํญ๋ชฉ ์ ๊ฑฐ\r\n const filtered = items.filter((item: any) => {\r\n const key = (item?.key || \"\").toString().toLowerCase();\r\n const title = (item?.title || \"\").toString().toLowerCase();\r\n // ๋น๋์ค, ์ค๋์ค, ํ์ผ ๊ด๋ จ ํญ๋ชฉ ์ ๊ฑฐ\r\n if ([\"video\", \"audio\", \"file\"].includes(key)) return false;\r\n if (\r\n title.includes(\"video\") ||\r\n title.includes(\"audio\") ||\r\n title.includes(\"file\")\r\n )\r\n return false;\r\n return true;\r\n });\r\n\r\n if (!query) return filtered;\r\n const q = query.toLowerCase();\r\n return filtered.filter(\r\n (item: any) =>\r\n item.title?.toLowerCase().includes(q) ||\r\n (item.aliases || []).some((a: string) =>\r\n a.toLowerCase().includes(q)\r\n )\r\n );\r\n },\r\n [editor]\r\n )}\r\n />\r\n }\r\n {!sideMenuAddButton && (\r\n <SideMenuController sideMenu={DragHandleOnlySideMenu} />\r\n )}\r\n </BlockNoteView>\r\n\r\n {/* ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ์คํผ๋ */}\r\n {isUploading && (\r\n <div className=\"lumirEditor-upload-overlay\">\r\n <div className=\"lumirEditor-spinner\" />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","// clsx์ tailwind-merge๋ฅผ ์ฌ์ฉํ className ์ ํธ๋ฆฌํฐ\r\n// ์ฌ์ฉ์๊ฐ ์ง์ ์ค์นํ๋๋ก ๊ถ์ฅํ๊ฑฐ๋, ๊ฐ๋จํ ๋ฒ์ ์ ๊ณต\r\n\r\nexport function cn(...inputs: (string | undefined | null | false)[]) {\r\n return inputs.filter(Boolean).join(' ');\r\n}\r\n","export interface S3UploaderConfig {\r\n apiEndpoint: string; // '/api/s3/presigned'(ํ์)\r\n env: \"production\" | \"development\"; // ํ๊ฒฝ (ํ์)\r\n path: string; // ํ์ผ ๊ฒฝ๋ก (ํ์)\r\n /** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ - ์
๋ก๋ ์ ํ์ผ๋ช
์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค */\r\n fileNameTransform?: (originalName: string, file: File) => string;\r\n /** true์ผ ๊ฒฝ์ฐ ํ์ผ๋ช
๋ค์ UUID๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํฉ๋๋ค (์: image_abc123.png) */\r\n appendUUID?: boolean;\r\n}\r\n\r\n// UUID ์์ฑ ํจ์ (crypto.randomUUID ๋๋ ํด๋ฐฑ)\r\nconst generateUUID = (): string => {\r\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n // ํด๋ฐฑ: ๊ฐ๋จํ UUID v4 ํ์ ์์ฑ\r\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0;\r\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n};\r\n\r\nexport const createS3Uploader = (config: S3UploaderConfig) => {\r\n const { apiEndpoint, env, path, fileNameTransform, appendUUID } = config;\r\n\r\n // ํ์ ํ๋ผ๋ฏธํฐ ๊ฒ์ฆ\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"apiEndpoint is required for S3 upload. Please provide a valid API endpoint.\"\r\n );\r\n }\r\n\r\n if (!env) {\r\n throw new Error(\"env is required. Must be 'development' or 'production'.\");\r\n }\r\n\r\n if (!path || path.trim() === \"\") {\r\n throw new Error(\"path is required and cannot be empty.\");\r\n }\r\n\r\n // ํ์ผ๋ช
์ UUID ์ถ๊ฐํ๋ ํจ์\r\n const appendUUIDToFileName = (filename: string): string => {\r\n const lastDotIndex = filename.lastIndexOf(\".\");\r\n if (lastDotIndex === -1) {\r\n // ํ์ฅ์๊ฐ ์๋ ๊ฒฝ์ฐ\r\n return `${filename}_${generateUUID()}`;\r\n }\r\n const name = filename.substring(0, lastDotIndex);\r\n const ext = filename.substring(lastDotIndex);\r\n return `${name}_${generateUUID()}${ext}`;\r\n };\r\n\r\n // ๊ณ์ธต ๊ตฌ์กฐ ํ์ผ๋ช
์์ฑ ํจ์\r\n const generateHierarchicalFileName = (file: File): string => {\r\n let filename = file.name;\r\n\r\n // 1. ์ฌ์ฉ์ ์ ์ ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ ์ ์ฉ\r\n if (fileNameTransform) {\r\n filename = fileNameTransform(filename, file);\r\n }\r\n\r\n // 2. UUID ์๋ ์ถ๊ฐ (appendUUID๊ฐ true์ธ ๊ฒฝ์ฐ)\r\n if (appendUUID) {\r\n filename = appendUUIDToFileName(filename);\r\n }\r\n\r\n // {env}/{path}/{filename}\r\n return `${env}/${path}/${filename}`;\r\n };\r\n\r\n return async (file: File): Promise<string> => {\r\n try {\r\n // ํ์ผ ์
๋ก๋ ์์๋ apiEndpoint ์ฌ๊ฒ์ฆ\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"Invalid apiEndpoint: Cannot upload file without a valid API ENDPOINT\"\r\n );\r\n }\r\n\r\n // 1. ๊ณ์ธต ๊ตฌ์กฐ ํ์ผ๋ช
์์ฑ\r\n const fileName = generateHierarchicalFileName(file);\r\n\r\n // 2. presigned URL ์์ฒญ\r\n const response = await fetch(\r\n `${apiEndpoint}?key=${encodeURIComponent(fileName)}`\r\n );\r\n\r\n if (!response.ok) {\r\n const errorText = (await response.text()) || \"\";\r\n throw new Error(\r\n `Failed to get presigned URL: ${response.statusText}, ${errorText}`\r\n );\r\n }\r\n\r\n const responseData = await response.json();\r\n const { presignedUrl, publicUrl } = responseData;\r\n\r\n // 3. S3์ ์
๋ก๋\r\n const uploadResponse = await fetch(presignedUrl, {\r\n method: \"PUT\",\r\n headers: {\r\n \"Content-Type\": file.type || \"application/octet-stream\",\r\n },\r\n body: file,\r\n });\r\n\r\n if (!uploadResponse.ok) {\r\n throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);\r\n }\r\n\r\n // 4. ๊ณต๊ฐ URL ๋ฐํ\r\n return publicUrl;\r\n } catch (error) {\r\n console.error(\"S3 upload failed:\", error);\r\n throw error;\r\n }\r\n };\r\n};\r\n"],"mappings":"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","names":["import_react","editor","BlockSideMenu"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/LumirEditor.tsx","../src/utils/cn.ts","../src/utils/s3-uploader.ts"],"sourcesContent":["\"use client\";\r\n\r\n// ์ปดํฌ๋ํธ ๋ฐ ์ ํธ๋ฆฌํฐ export\r\nexport {\r\n default as LumirEditor,\r\n ContentUtils,\r\n EditorConfig,\r\n} from \"./components/LumirEditor\";\r\nexport { cn } from \"./utils/cn\";\r\nexport { createS3Uploader } from \"./utils/s3-uploader\";\r\n\r\n// ํ์
export (๋ณ๋ ํ์ผ์์ ๊ด๋ฆฌ)\r\nexport type {\r\n LumirEditorProps,\r\n EditorType,\r\n DefaultPartialBlock,\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema,\r\n PartialBlock,\r\n BlockNoteEditor,\r\n} from \"./types\";\r\nexport type { S3UploaderConfig } from \"./utils/s3-uploader\";\r\n","\"use client\";\r\n\r\nimport { useEffect, useMemo, useCallback, useState, useRef } from \"react\";\r\nimport {\r\n useCreateBlockNote,\r\n SideMenu as BlockSideMenu,\r\n SideMenuController,\r\n DragHandleButton,\r\n SuggestionMenuController,\r\n getDefaultReactSlashMenuItems,\r\n} from \"@blocknote/react\";\r\nimport { BlockNoteView } from \"@blocknote/mantine\";\r\nimport { cn } from \"../utils/cn\";\r\n\r\nimport type {\r\n DefaultPartialBlock,\r\n LumirEditorProps,\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema,\r\n} from \"../types\";\r\n\r\nimport { createS3Uploader } from \"../utils/s3-uploader\";\r\n\r\n// ==========================================\r\n// ์ ํธ๋ฆฌํฐ ํด๋์ค๋ค\r\n// ==========================================\r\n\r\n/**\r\n * ์ฝํ
์ธ ๊ด๋ฆฌ ์ ํธ๋ฆฌํฐ\r\n * ๊ธฐ๋ณธ ๋ธ๋ก ์์ฑ ๋ฐ ์ฝํ
์ธ ๊ฒ์ฆ ๋ก์ง์ ๋ด๋น\r\n */\r\nexport class ContentUtils {\r\n /**\r\n * JSON ๋ฌธ์์ด์ ์ ํจ์ฑ์ ๊ฒ์ฆํฉ๋๋ค\r\n * @param jsonString ๊ฒ์ฆํ JSON ๋ฌธ์์ด\r\n * @returns ์ ํจํ JSON ๋ฌธ์์ด์ธ์ง ์ฌ๋ถ\r\n */\r\n static isValidJSONString(jsonString: string): boolean {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n return Array.isArray(parsed);\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * JSON ๋ฌธ์์ด์ DefaultPartialBlock ๋ฐฐ์ด๋ก ํ์ฑํฉ๋๋ค\r\n * @param jsonString JSON ๋ฌธ์์ด\r\n * @returns ํ์ฑ๋ ๋ธ๋ก ๋ฐฐ์ด ๋๋ null (ํ์ฑ ์คํจ ์)\r\n */\r\n static parseJSONContent(jsonString: string): DefaultPartialBlock[] | null {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n if (Array.isArray(parsed)) {\r\n return parsed as DefaultPartialBlock[];\r\n }\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * ๊ธฐ๋ณธ paragraph ๋ธ๋ก ์์ฑ\r\n * @returns ๊ธฐ๋ณธ ์ค์ ์ด ์ ์ฉ๋ DefaultPartialBlock\r\n */\r\n static createDefaultBlock(): DefaultPartialBlock {\r\n return {\r\n type: \"paragraph\",\r\n props: {\r\n textColor: \"default\",\r\n backgroundColor: \"default\",\r\n textAlignment: \"left\",\r\n },\r\n content: [{ type: \"text\", text: \"\", styles: {} }],\r\n children: [],\r\n };\r\n }\r\n\r\n /**\r\n * ์ฝํ
์ธ ์ ํจ์ฑ ๊ฒ์ฆ ๋ฐ ๊ธฐ๋ณธ๊ฐ ์ค์ \r\n * @param content ์ฌ์ฉ์ ์ ๊ณต ์ฝํ
์ธ (๊ฐ์ฒด ๋ฐฐ์ด ๋๋ JSON ๋ฌธ์์ด)\r\n * @param emptyBlockCount ๋น ๋ธ๋ก ๊ฐ์ (๊ธฐ๋ณธ๊ฐ: 3)\r\n * @returns ๊ฒ์ฆ๋ ์ฝํ
์ธ ๋ฐฐ์ด\r\n */\r\n static validateContent(\r\n content?: DefaultPartialBlock[] | string,\r\n emptyBlockCount: number = 3\r\n ): DefaultPartialBlock[] {\r\n // 1. ๋ฌธ์์ด์ธ ๊ฒฝ์ฐ JSON ํ์ฑ ์๋\r\n if (typeof content === \"string\") {\r\n if (content.trim() === \"\") {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n const parsedContent = this.parseJSONContent(content);\r\n if (parsedContent && parsedContent.length > 0) {\r\n return parsedContent;\r\n }\r\n\r\n // ํ์ฑ ์คํจ ์ ๋น ๋ธ๋ก ์์ฑ\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n // 2. ๋ฐฐ์ด์ธ ๊ฒฝ์ฐ ๊ธฐ์กด ๋ก์ง\r\n if (!content || content.length === 0) {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n return content;\r\n }\r\n\r\n /**\r\n * ๋น ๋ธ๋ก๋ค์ ์์ฑํฉ๋๋ค\r\n * @param emptyBlockCount ์์ฑํ ๋ธ๋ก ๊ฐ์\r\n * @returns ์์ฑ๋ ๋น ๋ธ๋ก ๋ฐฐ์ด\r\n */\r\n private static createEmptyBlocks(\r\n emptyBlockCount: number\r\n ): DefaultPartialBlock[] {\r\n return Array.from({ length: emptyBlockCount }, () =>\r\n this.createDefaultBlock()\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * ์๋ํฐ ์ค์ ๊ด๋ฆฌ ์ ํธ๋ฆฌํฐ\r\n * ๊ฐ์ข
์ค์ ์ ๊ธฐ๋ณธ๊ฐ๊ณผ ๊ฒ์ฆ ๋ก์ง์ ๋ด๋น\r\n */\r\nexport class EditorConfig {\r\n /**\r\n * ํ
์ด๋ธ ์ค์ ๊ธฐ๋ณธ๊ฐ ์ ์ฉ\r\n * @param userTables ์ฌ์ฉ์ ํ
์ด๋ธ ์ค์ \r\n * @returns ๊ธฐ๋ณธ๊ฐ์ด ์ ์ฉ๋ ํ
์ด๋ธ ์ค์ \r\n */\r\n static getDefaultTableConfig(userTables?: LumirEditorProps[\"tables\"]) {\r\n return {\r\n splitCells: userTables?.splitCells ?? true,\r\n cellBackgroundColor: userTables?.cellBackgroundColor ?? true,\r\n cellTextColor: userTables?.cellTextColor ?? true,\r\n headers: userTables?.headers ?? true,\r\n };\r\n }\r\n\r\n /**\r\n * ํค๋ฉ ์ค์ ๊ธฐ๋ณธ๊ฐ ์ ์ฉ\r\n * @param userHeading ์ฌ์ฉ์ ํค๋ฉ ์ค์ \r\n * @returns ๊ธฐ๋ณธ๊ฐ์ด ์ ์ฉ๋ ํค๋ฉ ์ค์ \r\n */\r\n static getDefaultHeadingConfig(userHeading?: LumirEditorProps[\"heading\"]) {\r\n return userHeading?.levels && userHeading.levels.length > 0\r\n ? userHeading\r\n : { levels: [1, 2, 3, 4, 5, 6] as (1 | 2 | 3 | 4 | 5 | 6)[] };\r\n }\r\n\r\n /**\r\n * ๋นํ์ฑํํ ํ์ฅ ๊ธฐ๋ฅ ๋ชฉ๋ก ์์ฑ\r\n * @param userExtensions ์ฌ์ฉ์ ์ ์ ๋นํ์ฑ ํ์ฅ\r\n * @param allowVideo ๋น๋์ค ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @param allowAudio ์ค๋์ค ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @param allowFile ์ผ๋ฐ ํ์ผ ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @returns ๋นํ์ฑํํ ํ์ฅ ๊ธฐ๋ฅ ๋ชฉ๋ก\r\n */\r\n static getDisabledExtensions(\r\n userExtensions?: string[],\r\n allowVideo = false,\r\n allowAudio = false,\r\n allowFile = false\r\n ): string[] {\r\n const set = new Set<string>(userExtensions ?? []);\r\n if (!allowVideo) set.add(\"video\");\r\n if (!allowAudio) set.add(\"audio\");\r\n if (!allowFile) set.add(\"file\");\r\n return Array.from(set);\r\n }\r\n}\r\n\r\n// ํ์ผ ํ์
๊ฒ์ฆ ํจ์\r\nconst isImageFile = (file: File): boolean => {\r\n return (\r\n file.size > 0 &&\r\n (file.type?.startsWith(\"image/\") ||\r\n (!file.type && /\\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(file.name || \"\")))\r\n );\r\n};\r\n\r\nexport default function LumirEditor({\r\n // editor options\r\n initialContent,\r\n initialEmptyBlocks = 3,\r\n uploadFile,\r\n s3Upload,\r\n tables,\r\n heading,\r\n defaultStyles = true,\r\n disableExtensions,\r\n tabBehavior = \"prefer-navigate-ui\",\r\n trailingBlock = true,\r\n allowVideoUpload = false,\r\n allowAudioUpload = false,\r\n allowFileUpload = false,\r\n // view options\r\n editable = true,\r\n theme = \"light\",\r\n formattingToolbar = true,\r\n linkToolbar = true,\r\n sideMenu = true,\r\n emojiPicker = true,\r\n filePanel = true,\r\n tableHandles = true,\r\n onSelectionChange,\r\n className = \"\",\r\n sideMenuAddButton = false,\r\n // callbacks / refs\r\n onContentChange,\r\n}: LumirEditorProps) {\r\n // ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ์ํ\r\n const [isUploading, setIsUploading] = useState(false);\r\n const validatedContent = useMemo<DefaultPartialBlock[]>(() => {\r\n return ContentUtils.validateContent(initialContent, initialEmptyBlocks);\r\n }, [initialContent, initialEmptyBlocks]);\r\n\r\n // ํ
์ด๋ธ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const tableConfig = useMemo(() => {\r\n return EditorConfig.getDefaultTableConfig(tables);\r\n }, [\r\n tables?.splitCells,\r\n tables?.cellBackgroundColor,\r\n tables?.cellTextColor,\r\n tables?.headers,\r\n ]);\r\n\r\n // ํค๋ฉ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const headingConfig = useMemo(() => {\r\n return EditorConfig.getDefaultHeadingConfig(heading);\r\n }, [heading?.levels?.join(\",\") ?? \"\"]);\r\n\r\n // ๋นํ์ฑํ ํ์ฅ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const disabledExtensions = useMemo(() => {\r\n return EditorConfig.getDisabledExtensions(\r\n disableExtensions,\r\n allowVideoUpload,\r\n allowAudioUpload,\r\n allowFileUpload\r\n );\r\n }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);\r\n\r\n // fileNameTransform ์ฝ๋ฐฑ์ ref๋ก ๊ด๋ฆฌ (์๋ํฐ ์ฌ์์ฑ ๋ฐฉ์ง)\r\n const fileNameTransformRef = useRef(s3Upload?.fileNameTransform);\r\n useEffect(() => {\r\n fileNameTransformRef.current = s3Upload?.fileNameTransform;\r\n }, [s3Upload?.fileNameTransform]);\r\n\r\n // S3 ์
๋ก๋ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
(๊ฐ์ฒด ์ฐธ์กฐ ์์ ํ)\r\n // ์ฃผ์: fileNameTransform์ ref๋ก ๊ด๋ฆฌํ๋ฏ๋ก ์์กด์ฑ์์ ์ ์ธ\r\n const memoizedS3Upload = useMemo(() => {\r\n if (!s3Upload) return undefined;\r\n return {\r\n apiEndpoint: s3Upload.apiEndpoint,\r\n env: s3Upload.env,\r\n path: s3Upload.path,\r\n appendUUID: s3Upload.appendUUID,\r\n preserveExtension: s3Upload.preserveExtension,\r\n // ์ต์ ์ฝ๋ฐฑ์ ํญ์ ์ฌ์ฉํ๋๋ก ref๋ฅผ ํตํด ์ ๊ทผ\r\n fileNameTransform: ((originalName: string, file: File) => {\r\n return fileNameTransformRef.current\r\n ? fileNameTransformRef.current(originalName, file)\r\n : originalName;\r\n }) as ((originalName: string, file: File) => string) | undefined,\r\n };\r\n }, [\r\n s3Upload?.apiEndpoint,\r\n s3Upload?.env,\r\n s3Upload?.path,\r\n s3Upload?.appendUUID,\r\n s3Upload?.preserveExtension,\r\n ]);\r\n\r\n const editor = useCreateBlockNote<\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema\r\n >(\r\n {\r\n initialContent: validatedContent as DefaultPartialBlock[],\r\n tables: tableConfig,\r\n heading: headingConfig,\r\n animations: false, // ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๋๋ฉ์ด์
๋นํ์ฑํ\r\n defaultStyles,\r\n // ํ์ฅ ๋นํ์ฑ: ๋น๋์ค/์ค๋์ค/ํ์ผ ์ ์ด\r\n disableExtensions: disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile: async (file) => {\r\n // ์ด๋ฏธ์ง ํ์ผ๋ง ํ์ฉ (์ด๋ฏธ์ง ์ ์ฉ ์๋ํฐ)\r\n if (!isImageFile(file)) {\r\n throw new Error(\"Only image files are allowed\");\r\n }\r\n\r\n try {\r\n let imageUrl: string;\r\n\r\n // 1. ์ฌ์ฉ์ ์ ์ uploadFile ์ฐ์ \r\n if (uploadFile) {\r\n imageUrl = await uploadFile(file);\r\n }\r\n // 2. S3 ์
๋ก๋ (uploadFile ์์ ๋)\r\n else if (memoizedS3Upload?.apiEndpoint) {\r\n const s3Uploader = createS3Uploader(memoizedS3Upload);\r\n imageUrl = await s3Uploader(file);\r\n }\r\n // 3. ์
๋ก๋ ๋ฐฉ๋ฒ์ด ์์ผ๋ฉด ์๋ฌ\r\n else {\r\n throw new Error(\"No upload method available\");\r\n }\r\n\r\n // BlockNote๊ฐ ์๋์ผ๋ก ์ด๋ฏธ์ง ๋ธ๋ก์ ์์ฑํ๋๋ก URL๋ง ๋ฐํ\r\n return imageUrl;\r\n } catch (error) {\r\n console.error(\"Image upload failed:\", error);\r\n throw new Error(\r\n \"Upload failed: \" +\r\n (error instanceof Error ? error.message : String(error))\r\n );\r\n }\r\n },\r\n pasteHandler: (ctx) => {\r\n const { event, editor, defaultPasteHandler } = ctx as any;\r\n const fileList =\r\n (event?.clipboardData?.files as FileList | null) ?? null;\r\n const files: File[] = fileList ? Array.from(fileList) : [];\r\n const acceptedFiles: File[] = files.filter(isImageFile);\r\n\r\n // ํ์ผ์ด ์์ง๋ง ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ ์ฒ๋ฆฌ ๋ง๊ณ ๋ฌด์\r\n if (files.length > 0 && acceptedFiles.length === 0) {\r\n event.preventDefault();\r\n return true;\r\n }\r\n\r\n // ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ ์ฒ๋ฆฌ\r\n if (acceptedFiles.length === 0) {\r\n return defaultPasteHandler() ?? false;\r\n }\r\n\r\n event.preventDefault();\r\n (async () => {\r\n // ๋ถ์ฌ๋ฃ๊ธฐ๋ก ์ฌ๋ฌ ์ด๋ฏธ์ง ์
๋ก๋ ์ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // ์๋ํฐ์ uploadFile ํจ์ ์ฌ์ฉ (ํต์ผ๋ ๋ก์ง)\r\n const url = await editor.uploadFile(file);\r\n editor.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n return true;\r\n },\r\n },\r\n [\r\n validatedContent,\r\n tableConfig,\r\n headingConfig,\r\n defaultStyles,\r\n disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile,\r\n memoizedS3Upload,\r\n ]\r\n );\r\n\r\n // ํธ์ง ๊ฐ๋ฅ ์ฌ๋ถ ์ค์ \r\n useEffect(() => {\r\n if (editor) {\r\n editor.isEditable = editable;\r\n }\r\n }, [editor, editable]);\r\n\r\n // ์ฝํ
์ธ ๋ณ๊ฒฝ ๊ฐ์ง\r\n useEffect(() => {\r\n if (!editor || !onContentChange) return;\r\n\r\n const handleContentChange = () => {\r\n // BlockNote์ ์ฌ๋ฐ๋ฅธ API ์ฌ์ฉ\r\n const blocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n onContentChange(blocks);\r\n };\r\n\r\n return editor.onEditorContentChange(handleContentChange);\r\n }, [editor, onContentChange]);\r\n\r\n // ๋๋๊ทธ์ค๋๋กญ ์ด๋ฏธ์ง ์ฒ๋ฆฌ\r\n useEffect(() => {\r\n const el = editor?.domElement as HTMLElement | undefined;\r\n if (!el) return;\r\n\r\n const handleDragOver = (e: DragEvent) => {\r\n if (e.defaultPrevented) return;\r\n const hasFiles = (\r\n e.dataTransfer?.types as unknown as string[] | undefined\r\n )?.includes?.(\"Files\");\r\n if (hasFiles) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n }\r\n };\r\n\r\n const handleDrop = (e: DragEvent) => {\r\n if (!e.dataTransfer) return;\r\n const hasFiles = (\r\n (e.dataTransfer.types as unknown as string[] | undefined) ?? []\r\n ).includes(\"Files\");\r\n if (!hasFiles) return;\r\n\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const items = Array.from(e.dataTransfer.items ?? []);\r\n const files = items\r\n .filter((it) => it.kind === \"file\")\r\n .map((it) => it.getAsFile())\r\n .filter((f): f is File => !!f);\r\n\r\n // ์ด๋ฏธ์ง ํ์ผ๋ง ํ์ฉ\r\n const acceptedFiles = files.filter(isImageFile);\r\n\r\n if (acceptedFiles.length === 0) return;\r\n\r\n (async () => {\r\n // ๋๋๊ทธ์ค๋๋กญ์ผ๋ก ์ฌ๋ฌ ์ด๋ฏธ์ง ์
๋ก๋ ์ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // ์๋ํฐ์ uploadFile ํจ์ ์ฌ์ฉ (์ผ๊ด๋ ๋ก์ง)\r\n if (editor?.uploadFile) {\r\n const url = await editor.uploadFile(file);\r\n if (url) {\r\n editor.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\r\n }\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n };\r\n\r\n el.addEventListener(\"dragover\", handleDragOver, { capture: true });\r\n el.addEventListener(\"drop\", handleDrop, { capture: true });\r\n\r\n return () => {\r\n el.removeEventListener(\"dragover\", handleDragOver, {\r\n capture: true,\r\n } as any);\r\n el.removeEventListener(\"drop\", handleDrop, { capture: true } as any);\r\n };\r\n }, [editor]);\r\n\r\n // SideMenu ์ค์ (Add ๋ฒํผ ์ ์ด)\r\n const computedSideMenu = useMemo(() => {\r\n return sideMenuAddButton ? sideMenu : false;\r\n }, [sideMenuAddButton, sideMenu]);\r\n\r\n // Add ๋ฒํผ ์๋ ์ฌ์ด๋ ๋ฉ๋ด (๋๋๊ทธ ํธ๋ค๋ง) - ๋ฉ๋ชจ์ด์ ์ด์
\r\n const DragHandleOnlySideMenu = useMemo(() => {\r\n return (props: any) => (\r\n <BlockSideMenu {...props}>\r\n <DragHandleButton {...props} />\r\n </BlockSideMenu>\r\n );\r\n }, []);\r\n\r\n return (\r\n <div\r\n className={cn(\"lumirEditor\", className)}\r\n style={{ position: \"relative\" }}\r\n >\r\n <BlockNoteView\r\n editor={editor}\r\n editable={editable}\r\n theme={theme}\r\n formattingToolbar={formattingToolbar}\r\n linkToolbar={linkToolbar}\r\n sideMenu={computedSideMenu}\r\n slashMenu={false}\r\n emojiPicker={emojiPicker}\r\n filePanel={filePanel}\r\n tableHandles={tableHandles}\r\n onSelectionChange={onSelectionChange}\r\n >\r\n {\r\n <SuggestionMenuController\r\n triggerCharacter=\"/\"\r\n getItems={useCallback(\r\n async (query: string) => {\r\n const items = getDefaultReactSlashMenuItems(editor);\r\n // ๋น๋์ค, ์ค๋์ค, ํ์ผ ๊ด๋ จ ํญ๋ชฉ ์ ๊ฑฐ\r\n const filtered = items.filter((item: any) => {\r\n const key = (item?.key || \"\").toString().toLowerCase();\r\n const title = (item?.title || \"\").toString().toLowerCase();\r\n // ๋น๋์ค, ์ค๋์ค, ํ์ผ ๊ด๋ จ ํญ๋ชฉ ์ ๊ฑฐ\r\n if ([\"video\", \"audio\", \"file\"].includes(key)) return false;\r\n if (\r\n title.includes(\"video\") ||\r\n title.includes(\"audio\") ||\r\n title.includes(\"file\")\r\n )\r\n return false;\r\n return true;\r\n });\r\n\r\n if (!query) return filtered;\r\n const q = query.toLowerCase();\r\n return filtered.filter(\r\n (item: any) =>\r\n item.title?.toLowerCase().includes(q) ||\r\n (item.aliases || []).some((a: string) =>\r\n a.toLowerCase().includes(q)\r\n )\r\n );\r\n },\r\n [editor]\r\n )}\r\n />\r\n }\r\n {!sideMenuAddButton && (\r\n <SideMenuController sideMenu={DragHandleOnlySideMenu} />\r\n )}\r\n </BlockNoteView>\r\n\r\n {/* ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ์คํผ๋ */}\r\n {isUploading && (\r\n <div className=\"lumirEditor-upload-overlay\">\r\n <div className=\"lumirEditor-spinner\" />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","// clsx์ tailwind-merge๋ฅผ ์ฌ์ฉํ className ์ ํธ๋ฆฌํฐ\r\n// ์ฌ์ฉ์๊ฐ ์ง์ ์ค์นํ๋๋ก ๊ถ์ฅํ๊ฑฐ๋, ๊ฐ๋จํ ๋ฒ์ ์ ๊ณต\r\n\r\nexport function cn(...inputs: (string | undefined | null | false)[]) {\r\n return inputs.filter(Boolean).join(' ');\r\n}\r\n","export interface S3UploaderConfig {\r\n apiEndpoint: string; // '/api/s3/presigned'(ํ์)\r\n env: \"production\" | \"development\"; // ํ๊ฒฝ (ํ์)\r\n path: string; // ํ์ผ ๊ฒฝ๋ก (ํ์)\r\n /** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ - ํ์ฅ์๋ฅผ ์ ์ธํ ํ์ผ๋ช
์ ๋ฐ์ ๋ณํํฉ๋๋ค */\r\n fileNameTransform?: (nameWithoutExt: string, file: File) => string;\r\n /** true์ผ ๊ฒฝ์ฐ ํ์ผ๋ช
๋ค์ UUID๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํฉ๋๋ค (์: image_abc123.png) */\r\n appendUUID?: boolean;\r\n /** false๋ก ์ค์ ํ๋ฉด ํ์ฅ์๋ฅผ ์๋์ผ๋ก ๋ถ์ด์ง ์์ (๊ธฐ๋ณธ: true) */\r\n preserveExtension?: boolean;\r\n}\r\n\r\n// UUID ์์ฑ ํจ์ (crypto.randomUUID ๋๋ ํด๋ฐฑ)\r\nconst generateUUID = (): string => {\r\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n // ํด๋ฐฑ: ๊ฐ๋จํ UUID v4 ํ์ ์์ฑ\r\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0;\r\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n};\r\n\r\nexport const createS3Uploader = (config: S3UploaderConfig) => {\r\n const {\r\n apiEndpoint,\r\n env,\r\n path,\r\n fileNameTransform,\r\n appendUUID,\r\n preserveExtension = true,\r\n } = config;\r\n\r\n // ํ์ ํ๋ผ๋ฏธํฐ ๊ฒ์ฆ\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"apiEndpoint is required for S3 upload. Please provide a valid API endpoint.\"\r\n );\r\n }\r\n\r\n if (!env) {\r\n throw new Error(\"env is required. Must be 'development' or 'production'.\");\r\n }\r\n\r\n if (!path || path.trim() === \"\") {\r\n throw new Error(\"path is required and cannot be empty.\");\r\n }\r\n\r\n // ํ์ผ๋ช
์ UUID ์ถ๊ฐํ๋ ํจ์\r\n const appendUUIDToFileName = (filename: string): string => {\r\n const lastDotIndex = filename.lastIndexOf(\".\");\r\n if (lastDotIndex === -1) {\r\n // ํ์ฅ์๊ฐ ์๋ ๊ฒฝ์ฐ\r\n return `${filename}_${generateUUID()}`;\r\n }\r\n const name = filename.substring(0, lastDotIndex);\r\n const ext = filename.substring(lastDotIndex);\r\n return `${name}_${generateUUID()}${ext}`;\r\n };\r\n\r\n // ๊ณ์ธต ๊ตฌ์กฐ ํ์ผ๋ช
์์ฑ ํจ์\r\n const generateHierarchicalFileName = (file: File): string => {\r\n // 0. ํ์ฅ์ ๋ถ๋ฆฌ\r\n const originalName = file.name;\r\n const lastDotIndex = originalName.lastIndexOf(\".\");\r\n const nameWithoutExt =\r\n lastDotIndex === -1\r\n ? originalName\r\n : originalName.substring(0, lastDotIndex);\r\n const extension =\r\n lastDotIndex === -1 ? \"\" : originalName.substring(lastDotIndex);\r\n\r\n let filename = nameWithoutExt;\r\n\r\n // 1. ์ฌ์ฉ์ ์ ์ ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ ์ ์ฉ (ํ์ฅ์ ์ ์ธํ ์ด๋ฆ๋ง)\r\n if (fileNameTransform) {\r\n filename = fileNameTransform(filename, file);\r\n }\r\n\r\n // 2. UUID ์๋ ์ถ๊ฐ (appendUUID๊ฐ true์ธ ๊ฒฝ์ฐ)\r\n if (appendUUID) {\r\n filename = `${filename}_${generateUUID()}`;\r\n }\r\n\r\n // 3. ํ์ฅ์ ๋ค์ ๋ถ์ด๊ธฐ (preserveExtension์ด true์ธ ๊ฒฝ์ฐ๋ง)\r\n if (preserveExtension) {\r\n filename = `${filename}${extension}`;\r\n }\r\n\r\n // {env}/{path}/{filename}\r\n return `${env}/${path}/${filename}`;\r\n };\r\n\r\n return async (file: File): Promise<string> => {\r\n try {\r\n // ํ์ผ ์
๋ก๋ ์์๋ apiEndpoint ์ฌ๊ฒ์ฆ\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"Invalid apiEndpoint: Cannot upload file without a valid API ENDPOINT\"\r\n );\r\n }\r\n\r\n // 1. ๊ณ์ธต ๊ตฌ์กฐ ํ์ผ๋ช
์์ฑ\r\n const fileName = generateHierarchicalFileName(file);\r\n\r\n // 2. presigned URL ์์ฒญ\r\n const response = await fetch(\r\n `${apiEndpoint}?key=${encodeURIComponent(fileName)}`\r\n );\r\n\r\n if (!response.ok) {\r\n const errorText = (await response.text()) || \"\";\r\n throw new Error(\r\n `Failed to get presigned URL: ${response.statusText}, ${errorText}`\r\n );\r\n }\r\n\r\n const responseData = await response.json();\r\n const { presignedUrl, publicUrl } = responseData;\r\n\r\n // 3. S3์ ์
๋ก๋\r\n const uploadResponse = await fetch(presignedUrl, {\r\n method: \"PUT\",\r\n headers: {\r\n \"Content-Type\": file.type || \"application/octet-stream\",\r\n },\r\n body: file,\r\n });\r\n\r\n if (!uploadResponse.ok) {\r\n throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);\r\n }\r\n\r\n // 4. ๊ณต๊ฐ URL ๋ฐํ\r\n return publicUrl;\r\n } catch (error) {\r\n console.error(\"S3 upload failed:\", error);\r\n throw error;\r\n }\r\n };\r\n};\r\n"],"mappings":"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","names":["import_react","editor","BlockSideMenu"]}
|
package/dist/index.mjs
CHANGED
|
@@ -29,7 +29,14 @@ var generateUUID = () => {
|
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
31
|
var createS3Uploader = (config) => {
|
|
32
|
-
const {
|
|
32
|
+
const {
|
|
33
|
+
apiEndpoint,
|
|
34
|
+
env,
|
|
35
|
+
path,
|
|
36
|
+
fileNameTransform,
|
|
37
|
+
appendUUID,
|
|
38
|
+
preserveExtension = true
|
|
39
|
+
} = config;
|
|
33
40
|
if (!apiEndpoint || apiEndpoint.trim() === "") {
|
|
34
41
|
throw new Error(
|
|
35
42
|
"apiEndpoint is required for S3 upload. Please provide a valid API endpoint."
|
|
@@ -51,12 +58,19 @@ var createS3Uploader = (config) => {
|
|
|
51
58
|
return `${name}_${generateUUID()}${ext}`;
|
|
52
59
|
};
|
|
53
60
|
const generateHierarchicalFileName = (file) => {
|
|
54
|
-
|
|
61
|
+
const originalName = file.name;
|
|
62
|
+
const lastDotIndex = originalName.lastIndexOf(".");
|
|
63
|
+
const nameWithoutExt = lastDotIndex === -1 ? originalName : originalName.substring(0, lastDotIndex);
|
|
64
|
+
const extension = lastDotIndex === -1 ? "" : originalName.substring(lastDotIndex);
|
|
65
|
+
let filename = nameWithoutExt;
|
|
55
66
|
if (fileNameTransform) {
|
|
56
67
|
filename = fileNameTransform(filename, file);
|
|
57
68
|
}
|
|
58
69
|
if (appendUUID) {
|
|
59
|
-
filename =
|
|
70
|
+
filename = `${filename}_${generateUUID()}`;
|
|
71
|
+
}
|
|
72
|
+
if (preserveExtension) {
|
|
73
|
+
filename = `${filename}${extension}`;
|
|
60
74
|
}
|
|
61
75
|
return `${env}/${path}/${filename}`;
|
|
62
76
|
};
|
|
@@ -284,6 +298,7 @@ function LumirEditor({
|
|
|
284
298
|
env: s3Upload.env,
|
|
285
299
|
path: s3Upload.path,
|
|
286
300
|
appendUUID: s3Upload.appendUUID,
|
|
301
|
+
preserveExtension: s3Upload.preserveExtension,
|
|
287
302
|
// ์ต์ ์ฝ๋ฐฑ์ ํญ์ ์ฌ์ฉํ๋๋ก ref๋ฅผ ํตํด ์ ๊ทผ
|
|
288
303
|
fileNameTransform: (originalName, file) => {
|
|
289
304
|
return fileNameTransformRef.current ? fileNameTransformRef.current(originalName, file) : originalName;
|
|
@@ -293,7 +308,8 @@ function LumirEditor({
|
|
|
293
308
|
s3Upload?.apiEndpoint,
|
|
294
309
|
s3Upload?.env,
|
|
295
310
|
s3Upload?.path,
|
|
296
|
-
s3Upload?.appendUUID
|
|
311
|
+
s3Upload?.appendUUID,
|
|
312
|
+
s3Upload?.preserveExtension
|
|
297
313
|
]);
|
|
298
314
|
const editor = useCreateBlockNote(
|
|
299
315
|
{
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/LumirEditor.tsx","../src/utils/cn.ts","../src/utils/s3-uploader.ts"],"sourcesContent":["\"use client\";\r\n\r\nimport { useEffect, useMemo, useCallback, useState, useRef } from \"react\";\r\nimport {\r\n useCreateBlockNote,\r\n SideMenu as BlockSideMenu,\r\n SideMenuController,\r\n DragHandleButton,\r\n SuggestionMenuController,\r\n getDefaultReactSlashMenuItems,\r\n} from \"@blocknote/react\";\r\nimport { BlockNoteView } from \"@blocknote/mantine\";\r\nimport { cn } from \"../utils/cn\";\r\n\r\nimport type {\r\n DefaultPartialBlock,\r\n LumirEditorProps,\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema,\r\n} from \"../types\";\r\n\r\nimport { createS3Uploader } from \"../utils/s3-uploader\";\r\n\r\n// ==========================================\r\n// ์ ํธ๋ฆฌํฐ ํด๋์ค๋ค\r\n// ==========================================\r\n\r\n/**\r\n * ์ฝํ
์ธ ๊ด๋ฆฌ ์ ํธ๋ฆฌํฐ\r\n * ๊ธฐ๋ณธ ๋ธ๋ก ์์ฑ ๋ฐ ์ฝํ
์ธ ๊ฒ์ฆ ๋ก์ง์ ๋ด๋น\r\n */\r\nexport class ContentUtils {\r\n /**\r\n * JSON ๋ฌธ์์ด์ ์ ํจ์ฑ์ ๊ฒ์ฆํฉ๋๋ค\r\n * @param jsonString ๊ฒ์ฆํ JSON ๋ฌธ์์ด\r\n * @returns ์ ํจํ JSON ๋ฌธ์์ด์ธ์ง ์ฌ๋ถ\r\n */\r\n static isValidJSONString(jsonString: string): boolean {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n return Array.isArray(parsed);\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * JSON ๋ฌธ์์ด์ DefaultPartialBlock ๋ฐฐ์ด๋ก ํ์ฑํฉ๋๋ค\r\n * @param jsonString JSON ๋ฌธ์์ด\r\n * @returns ํ์ฑ๋ ๋ธ๋ก ๋ฐฐ์ด ๋๋ null (ํ์ฑ ์คํจ ์)\r\n */\r\n static parseJSONContent(jsonString: string): DefaultPartialBlock[] | null {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n if (Array.isArray(parsed)) {\r\n return parsed as DefaultPartialBlock[];\r\n }\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * ๊ธฐ๋ณธ paragraph ๋ธ๋ก ์์ฑ\r\n * @returns ๊ธฐ๋ณธ ์ค์ ์ด ์ ์ฉ๋ DefaultPartialBlock\r\n */\r\n static createDefaultBlock(): DefaultPartialBlock {\r\n return {\r\n type: \"paragraph\",\r\n props: {\r\n textColor: \"default\",\r\n backgroundColor: \"default\",\r\n textAlignment: \"left\",\r\n },\r\n content: [{ type: \"text\", text: \"\", styles: {} }],\r\n children: [],\r\n };\r\n }\r\n\r\n /**\r\n * ์ฝํ
์ธ ์ ํจ์ฑ ๊ฒ์ฆ ๋ฐ ๊ธฐ๋ณธ๊ฐ ์ค์ \r\n * @param content ์ฌ์ฉ์ ์ ๊ณต ์ฝํ
์ธ (๊ฐ์ฒด ๋ฐฐ์ด ๋๋ JSON ๋ฌธ์์ด)\r\n * @param emptyBlockCount ๋น ๋ธ๋ก ๊ฐ์ (๊ธฐ๋ณธ๊ฐ: 3)\r\n * @returns ๊ฒ์ฆ๋ ์ฝํ
์ธ ๋ฐฐ์ด\r\n */\r\n static validateContent(\r\n content?: DefaultPartialBlock[] | string,\r\n emptyBlockCount: number = 3\r\n ): DefaultPartialBlock[] {\r\n // 1. ๋ฌธ์์ด์ธ ๊ฒฝ์ฐ JSON ํ์ฑ ์๋\r\n if (typeof content === \"string\") {\r\n if (content.trim() === \"\") {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n const parsedContent = this.parseJSONContent(content);\r\n if (parsedContent && parsedContent.length > 0) {\r\n return parsedContent;\r\n }\r\n\r\n // ํ์ฑ ์คํจ ์ ๋น ๋ธ๋ก ์์ฑ\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n // 2. ๋ฐฐ์ด์ธ ๊ฒฝ์ฐ ๊ธฐ์กด ๋ก์ง\r\n if (!content || content.length === 0) {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n return content;\r\n }\r\n\r\n /**\r\n * ๋น ๋ธ๋ก๋ค์ ์์ฑํฉ๋๋ค\r\n * @param emptyBlockCount ์์ฑํ ๋ธ๋ก ๊ฐ์\r\n * @returns ์์ฑ๋ ๋น ๋ธ๋ก ๋ฐฐ์ด\r\n */\r\n private static createEmptyBlocks(\r\n emptyBlockCount: number\r\n ): DefaultPartialBlock[] {\r\n return Array.from({ length: emptyBlockCount }, () =>\r\n this.createDefaultBlock()\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * ์๋ํฐ ์ค์ ๊ด๋ฆฌ ์ ํธ๋ฆฌํฐ\r\n * ๊ฐ์ข
์ค์ ์ ๊ธฐ๋ณธ๊ฐ๊ณผ ๊ฒ์ฆ ๋ก์ง์ ๋ด๋น\r\n */\r\nexport class EditorConfig {\r\n /**\r\n * ํ
์ด๋ธ ์ค์ ๊ธฐ๋ณธ๊ฐ ์ ์ฉ\r\n * @param userTables ์ฌ์ฉ์ ํ
์ด๋ธ ์ค์ \r\n * @returns ๊ธฐ๋ณธ๊ฐ์ด ์ ์ฉ๋ ํ
์ด๋ธ ์ค์ \r\n */\r\n static getDefaultTableConfig(userTables?: LumirEditorProps[\"tables\"]) {\r\n return {\r\n splitCells: userTables?.splitCells ?? true,\r\n cellBackgroundColor: userTables?.cellBackgroundColor ?? true,\r\n cellTextColor: userTables?.cellTextColor ?? true,\r\n headers: userTables?.headers ?? true,\r\n };\r\n }\r\n\r\n /**\r\n * ํค๋ฉ ์ค์ ๊ธฐ๋ณธ๊ฐ ์ ์ฉ\r\n * @param userHeading ์ฌ์ฉ์ ํค๋ฉ ์ค์ \r\n * @returns ๊ธฐ๋ณธ๊ฐ์ด ์ ์ฉ๋ ํค๋ฉ ์ค์ \r\n */\r\n static getDefaultHeadingConfig(userHeading?: LumirEditorProps[\"heading\"]) {\r\n return userHeading?.levels && userHeading.levels.length > 0\r\n ? userHeading\r\n : { levels: [1, 2, 3, 4, 5, 6] as (1 | 2 | 3 | 4 | 5 | 6)[] };\r\n }\r\n\r\n /**\r\n * ๋นํ์ฑํํ ํ์ฅ ๊ธฐ๋ฅ ๋ชฉ๋ก ์์ฑ\r\n * @param userExtensions ์ฌ์ฉ์ ์ ์ ๋นํ์ฑ ํ์ฅ\r\n * @param allowVideo ๋น๋์ค ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @param allowAudio ์ค๋์ค ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @param allowFile ์ผ๋ฐ ํ์ผ ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @returns ๋นํ์ฑํํ ํ์ฅ ๊ธฐ๋ฅ ๋ชฉ๋ก\r\n */\r\n static getDisabledExtensions(\r\n userExtensions?: string[],\r\n allowVideo = false,\r\n allowAudio = false,\r\n allowFile = false\r\n ): string[] {\r\n const set = new Set<string>(userExtensions ?? []);\r\n if (!allowVideo) set.add(\"video\");\r\n if (!allowAudio) set.add(\"audio\");\r\n if (!allowFile) set.add(\"file\");\r\n return Array.from(set);\r\n }\r\n}\r\n\r\n// ํ์ผ ํ์
๊ฒ์ฆ ํจ์\r\nconst isImageFile = (file: File): boolean => {\r\n return (\r\n file.size > 0 &&\r\n (file.type?.startsWith(\"image/\") ||\r\n (!file.type && /\\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(file.name || \"\")))\r\n );\r\n};\r\n\r\nexport default function LumirEditor({\r\n // editor options\r\n initialContent,\r\n initialEmptyBlocks = 3,\r\n uploadFile,\r\n s3Upload,\r\n tables,\r\n heading,\r\n defaultStyles = true,\r\n disableExtensions,\r\n tabBehavior = \"prefer-navigate-ui\",\r\n trailingBlock = true,\r\n allowVideoUpload = false,\r\n allowAudioUpload = false,\r\n allowFileUpload = false,\r\n // view options\r\n editable = true,\r\n theme = \"light\",\r\n formattingToolbar = true,\r\n linkToolbar = true,\r\n sideMenu = true,\r\n emojiPicker = true,\r\n filePanel = true,\r\n tableHandles = true,\r\n onSelectionChange,\r\n className = \"\",\r\n sideMenuAddButton = false,\r\n // callbacks / refs\r\n onContentChange,\r\n}: LumirEditorProps) {\r\n // ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ์ํ\r\n const [isUploading, setIsUploading] = useState(false);\r\n const validatedContent = useMemo<DefaultPartialBlock[]>(() => {\r\n return ContentUtils.validateContent(initialContent, initialEmptyBlocks);\r\n }, [initialContent, initialEmptyBlocks]);\r\n\r\n // ํ
์ด๋ธ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const tableConfig = useMemo(() => {\r\n return EditorConfig.getDefaultTableConfig(tables);\r\n }, [\r\n tables?.splitCells,\r\n tables?.cellBackgroundColor,\r\n tables?.cellTextColor,\r\n tables?.headers,\r\n ]);\r\n\r\n // ํค๋ฉ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const headingConfig = useMemo(() => {\r\n return EditorConfig.getDefaultHeadingConfig(heading);\r\n }, [heading?.levels?.join(\",\") ?? \"\"]);\r\n\r\n // ๋นํ์ฑํ ํ์ฅ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const disabledExtensions = useMemo(() => {\r\n return EditorConfig.getDisabledExtensions(\r\n disableExtensions,\r\n allowVideoUpload,\r\n allowAudioUpload,\r\n allowFileUpload\r\n );\r\n }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);\r\n\r\n // fileNameTransform ์ฝ๋ฐฑ์ ref๋ก ๊ด๋ฆฌ (์๋ํฐ ์ฌ์์ฑ ๋ฐฉ์ง)\r\n const fileNameTransformRef = useRef(s3Upload?.fileNameTransform);\r\n useEffect(() => {\r\n fileNameTransformRef.current = s3Upload?.fileNameTransform;\r\n }, [s3Upload?.fileNameTransform]);\r\n\r\n // S3 ์
๋ก๋ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
(๊ฐ์ฒด ์ฐธ์กฐ ์์ ํ)\r\n // ์ฃผ์: fileNameTransform์ ref๋ก ๊ด๋ฆฌํ๋ฏ๋ก ์์กด์ฑ์์ ์ ์ธ\r\n const memoizedS3Upload = useMemo(() => {\r\n if (!s3Upload) return undefined;\r\n return {\r\n apiEndpoint: s3Upload.apiEndpoint,\r\n env: s3Upload.env,\r\n path: s3Upload.path,\r\n appendUUID: s3Upload.appendUUID,\r\n // ์ต์ ์ฝ๋ฐฑ์ ํญ์ ์ฌ์ฉํ๋๋ก ref๋ฅผ ํตํด ์ ๊ทผ\r\n fileNameTransform: ((originalName: string, file: File) => {\r\n return fileNameTransformRef.current\r\n ? fileNameTransformRef.current(originalName, file)\r\n : originalName;\r\n }) as ((originalName: string, file: File) => string) | undefined,\r\n };\r\n }, [\r\n s3Upload?.apiEndpoint,\r\n s3Upload?.env,\r\n s3Upload?.path,\r\n s3Upload?.appendUUID,\r\n ]);\r\n\r\n const editor = useCreateBlockNote<\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema\r\n >(\r\n {\r\n initialContent: validatedContent as DefaultPartialBlock[],\r\n tables: tableConfig,\r\n heading: headingConfig,\r\n animations: false, // ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๋๋ฉ์ด์
๋นํ์ฑํ\r\n defaultStyles,\r\n // ํ์ฅ ๋นํ์ฑ: ๋น๋์ค/์ค๋์ค/ํ์ผ ์ ์ด\r\n disableExtensions: disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile: async (file) => {\r\n // ์ด๋ฏธ์ง ํ์ผ๋ง ํ์ฉ (์ด๋ฏธ์ง ์ ์ฉ ์๋ํฐ)\r\n if (!isImageFile(file)) {\r\n throw new Error(\"Only image files are allowed\");\r\n }\r\n\r\n try {\r\n let imageUrl: string;\r\n\r\n // 1. ์ฌ์ฉ์ ์ ์ uploadFile ์ฐ์ \r\n if (uploadFile) {\r\n imageUrl = await uploadFile(file);\r\n }\r\n // 2. S3 ์
๋ก๋ (uploadFile ์์ ๋)\r\n else if (memoizedS3Upload?.apiEndpoint) {\r\n const s3Uploader = createS3Uploader(memoizedS3Upload);\r\n imageUrl = await s3Uploader(file);\r\n }\r\n // 3. ์
๋ก๋ ๋ฐฉ๋ฒ์ด ์์ผ๋ฉด ์๋ฌ\r\n else {\r\n throw new Error(\"No upload method available\");\r\n }\r\n\r\n // BlockNote๊ฐ ์๋์ผ๋ก ์ด๋ฏธ์ง ๋ธ๋ก์ ์์ฑํ๋๋ก URL๋ง ๋ฐํ\r\n return imageUrl;\r\n } catch (error) {\r\n console.error(\"Image upload failed:\", error);\r\n throw new Error(\r\n \"Upload failed: \" +\r\n (error instanceof Error ? error.message : String(error))\r\n );\r\n }\r\n },\r\n pasteHandler: (ctx) => {\r\n const { event, editor, defaultPasteHandler } = ctx as any;\r\n const fileList =\r\n (event?.clipboardData?.files as FileList | null) ?? null;\r\n const files: File[] = fileList ? Array.from(fileList) : [];\r\n const acceptedFiles: File[] = files.filter(isImageFile);\r\n\r\n // ํ์ผ์ด ์์ง๋ง ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ ์ฒ๋ฆฌ ๋ง๊ณ ๋ฌด์\r\n if (files.length > 0 && acceptedFiles.length === 0) {\r\n event.preventDefault();\r\n return true;\r\n }\r\n\r\n // ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ ์ฒ๋ฆฌ\r\n if (acceptedFiles.length === 0) {\r\n return defaultPasteHandler() ?? false;\r\n }\r\n\r\n event.preventDefault();\r\n (async () => {\r\n // ๋ถ์ฌ๋ฃ๊ธฐ๋ก ์ฌ๋ฌ ์ด๋ฏธ์ง ์
๋ก๋ ์ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // ์๋ํฐ์ uploadFile ํจ์ ์ฌ์ฉ (ํต์ผ๋ ๋ก์ง)\r\n const url = await editor.uploadFile(file);\r\n editor.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n return true;\r\n },\r\n },\r\n [\r\n validatedContent,\r\n tableConfig,\r\n headingConfig,\r\n defaultStyles,\r\n disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile,\r\n memoizedS3Upload,\r\n ]\r\n );\r\n\r\n // ํธ์ง ๊ฐ๋ฅ ์ฌ๋ถ ์ค์ \r\n useEffect(() => {\r\n if (editor) {\r\n editor.isEditable = editable;\r\n }\r\n }, [editor, editable]);\r\n\r\n // ์ฝํ
์ธ ๋ณ๊ฒฝ ๊ฐ์ง\r\n useEffect(() => {\r\n if (!editor || !onContentChange) return;\r\n\r\n const handleContentChange = () => {\r\n // BlockNote์ ์ฌ๋ฐ๋ฅธ API ์ฌ์ฉ\r\n const blocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n onContentChange(blocks);\r\n };\r\n\r\n return editor.onEditorContentChange(handleContentChange);\r\n }, [editor, onContentChange]);\r\n\r\n // ๋๋๊ทธ์ค๋๋กญ ์ด๋ฏธ์ง ์ฒ๋ฆฌ\r\n useEffect(() => {\r\n const el = editor?.domElement as HTMLElement | undefined;\r\n if (!el) return;\r\n\r\n const handleDragOver = (e: DragEvent) => {\r\n if (e.defaultPrevented) return;\r\n const hasFiles = (\r\n e.dataTransfer?.types as unknown as string[] | undefined\r\n )?.includes?.(\"Files\");\r\n if (hasFiles) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n }\r\n };\r\n\r\n const handleDrop = (e: DragEvent) => {\r\n if (!e.dataTransfer) return;\r\n const hasFiles = (\r\n (e.dataTransfer.types as unknown as string[] | undefined) ?? []\r\n ).includes(\"Files\");\r\n if (!hasFiles) return;\r\n\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const items = Array.from(e.dataTransfer.items ?? []);\r\n const files = items\r\n .filter((it) => it.kind === \"file\")\r\n .map((it) => it.getAsFile())\r\n .filter((f): f is File => !!f);\r\n\r\n // ์ด๋ฏธ์ง ํ์ผ๋ง ํ์ฉ\r\n const acceptedFiles = files.filter(isImageFile);\r\n\r\n if (acceptedFiles.length === 0) return;\r\n\r\n (async () => {\r\n // ๋๋๊ทธ์ค๋๋กญ์ผ๋ก ์ฌ๋ฌ ์ด๋ฏธ์ง ์
๋ก๋ ์ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // ์๋ํฐ์ uploadFile ํจ์ ์ฌ์ฉ (์ผ๊ด๋ ๋ก์ง)\r\n if (editor?.uploadFile) {\r\n const url = await editor.uploadFile(file);\r\n if (url) {\r\n editor.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\r\n }\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n };\r\n\r\n el.addEventListener(\"dragover\", handleDragOver, { capture: true });\r\n el.addEventListener(\"drop\", handleDrop, { capture: true });\r\n\r\n return () => {\r\n el.removeEventListener(\"dragover\", handleDragOver, {\r\n capture: true,\r\n } as any);\r\n el.removeEventListener(\"drop\", handleDrop, { capture: true } as any);\r\n };\r\n }, [editor]);\r\n\r\n // SideMenu ์ค์ (Add ๋ฒํผ ์ ์ด)\r\n const computedSideMenu = useMemo(() => {\r\n return sideMenuAddButton ? sideMenu : false;\r\n }, [sideMenuAddButton, sideMenu]);\r\n\r\n // Add ๋ฒํผ ์๋ ์ฌ์ด๋ ๋ฉ๋ด (๋๋๊ทธ ํธ๋ค๋ง) - ๋ฉ๋ชจ์ด์ ์ด์
\r\n const DragHandleOnlySideMenu = useMemo(() => {\r\n return (props: any) => (\r\n <BlockSideMenu {...props}>\r\n <DragHandleButton {...props} />\r\n </BlockSideMenu>\r\n );\r\n }, []);\r\n\r\n return (\r\n <div\r\n className={cn(\"lumirEditor\", className)}\r\n style={{ position: \"relative\" }}\r\n >\r\n <BlockNoteView\r\n editor={editor}\r\n editable={editable}\r\n theme={theme}\r\n formattingToolbar={formattingToolbar}\r\n linkToolbar={linkToolbar}\r\n sideMenu={computedSideMenu}\r\n slashMenu={false}\r\n emojiPicker={emojiPicker}\r\n filePanel={filePanel}\r\n tableHandles={tableHandles}\r\n onSelectionChange={onSelectionChange}\r\n >\r\n {\r\n <SuggestionMenuController\r\n triggerCharacter=\"/\"\r\n getItems={useCallback(\r\n async (query: string) => {\r\n const items = getDefaultReactSlashMenuItems(editor);\r\n // ๋น๋์ค, ์ค๋์ค, ํ์ผ ๊ด๋ จ ํญ๋ชฉ ์ ๊ฑฐ\r\n const filtered = items.filter((item: any) => {\r\n const key = (item?.key || \"\").toString().toLowerCase();\r\n const title = (item?.title || \"\").toString().toLowerCase();\r\n // ๋น๋์ค, ์ค๋์ค, ํ์ผ ๊ด๋ จ ํญ๋ชฉ ์ ๊ฑฐ\r\n if ([\"video\", \"audio\", \"file\"].includes(key)) return false;\r\n if (\r\n title.includes(\"video\") ||\r\n title.includes(\"audio\") ||\r\n title.includes(\"file\")\r\n )\r\n return false;\r\n return true;\r\n });\r\n\r\n if (!query) return filtered;\r\n const q = query.toLowerCase();\r\n return filtered.filter(\r\n (item: any) =>\r\n item.title?.toLowerCase().includes(q) ||\r\n (item.aliases || []).some((a: string) =>\r\n a.toLowerCase().includes(q)\r\n )\r\n );\r\n },\r\n [editor]\r\n )}\r\n />\r\n }\r\n {!sideMenuAddButton && (\r\n <SideMenuController sideMenu={DragHandleOnlySideMenu} />\r\n )}\r\n </BlockNoteView>\r\n\r\n {/* ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ์คํผ๋ */}\r\n {isUploading && (\r\n <div className=\"lumirEditor-upload-overlay\">\r\n <div className=\"lumirEditor-spinner\" />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","// clsx์ tailwind-merge๋ฅผ ์ฌ์ฉํ className ์ ํธ๋ฆฌํฐ\r\n// ์ฌ์ฉ์๊ฐ ์ง์ ์ค์นํ๋๋ก ๊ถ์ฅํ๊ฑฐ๋, ๊ฐ๋จํ ๋ฒ์ ์ ๊ณต\r\n\r\nexport function cn(...inputs: (string | undefined | null | false)[]) {\r\n return inputs.filter(Boolean).join(' ');\r\n}\r\n","export interface S3UploaderConfig {\r\n apiEndpoint: string; // '/api/s3/presigned'(ํ์)\r\n env: \"production\" | \"development\"; // ํ๊ฒฝ (ํ์)\r\n path: string; // ํ์ผ ๊ฒฝ๋ก (ํ์)\r\n /** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ - ์
๋ก๋ ์ ํ์ผ๋ช
์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค */\r\n fileNameTransform?: (originalName: string, file: File) => string;\r\n /** true์ผ ๊ฒฝ์ฐ ํ์ผ๋ช
๋ค์ UUID๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํฉ๋๋ค (์: image_abc123.png) */\r\n appendUUID?: boolean;\r\n}\r\n\r\n// UUID ์์ฑ ํจ์ (crypto.randomUUID ๋๋ ํด๋ฐฑ)\r\nconst generateUUID = (): string => {\r\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n // ํด๋ฐฑ: ๊ฐ๋จํ UUID v4 ํ์ ์์ฑ\r\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0;\r\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n};\r\n\r\nexport const createS3Uploader = (config: S3UploaderConfig) => {\r\n const { apiEndpoint, env, path, fileNameTransform, appendUUID } = config;\r\n\r\n // ํ์ ํ๋ผ๋ฏธํฐ ๊ฒ์ฆ\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"apiEndpoint is required for S3 upload. Please provide a valid API endpoint.\"\r\n );\r\n }\r\n\r\n if (!env) {\r\n throw new Error(\"env is required. Must be 'development' or 'production'.\");\r\n }\r\n\r\n if (!path || path.trim() === \"\") {\r\n throw new Error(\"path is required and cannot be empty.\");\r\n }\r\n\r\n // ํ์ผ๋ช
์ UUID ์ถ๊ฐํ๋ ํจ์\r\n const appendUUIDToFileName = (filename: string): string => {\r\n const lastDotIndex = filename.lastIndexOf(\".\");\r\n if (lastDotIndex === -1) {\r\n // ํ์ฅ์๊ฐ ์๋ ๊ฒฝ์ฐ\r\n return `${filename}_${generateUUID()}`;\r\n }\r\n const name = filename.substring(0, lastDotIndex);\r\n const ext = filename.substring(lastDotIndex);\r\n return `${name}_${generateUUID()}${ext}`;\r\n };\r\n\r\n // ๊ณ์ธต ๊ตฌ์กฐ ํ์ผ๋ช
์์ฑ ํจ์\r\n const generateHierarchicalFileName = (file: File): string => {\r\n let filename = file.name;\r\n\r\n // 1. ์ฌ์ฉ์ ์ ์ ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ ์ ์ฉ\r\n if (fileNameTransform) {\r\n filename = fileNameTransform(filename, file);\r\n }\r\n\r\n // 2. UUID ์๋ ์ถ๊ฐ (appendUUID๊ฐ true์ธ ๊ฒฝ์ฐ)\r\n if (appendUUID) {\r\n filename = appendUUIDToFileName(filename);\r\n }\r\n\r\n // {env}/{path}/{filename}\r\n return `${env}/${path}/${filename}`;\r\n };\r\n\r\n return async (file: File): Promise<string> => {\r\n try {\r\n // ํ์ผ ์
๋ก๋ ์์๋ apiEndpoint ์ฌ๊ฒ์ฆ\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"Invalid apiEndpoint: Cannot upload file without a valid API ENDPOINT\"\r\n );\r\n }\r\n\r\n // 1. ๊ณ์ธต ๊ตฌ์กฐ ํ์ผ๋ช
์์ฑ\r\n const fileName = generateHierarchicalFileName(file);\r\n\r\n // 2. presigned URL ์์ฒญ\r\n const response = await fetch(\r\n `${apiEndpoint}?key=${encodeURIComponent(fileName)}`\r\n );\r\n\r\n if (!response.ok) {\r\n const errorText = (await response.text()) || \"\";\r\n throw new Error(\r\n `Failed to get presigned URL: ${response.statusText}, ${errorText}`\r\n );\r\n }\r\n\r\n const responseData = await response.json();\r\n const { presignedUrl, publicUrl } = responseData;\r\n\r\n // 3. S3์ ์
๋ก๋\r\n const uploadResponse = await fetch(presignedUrl, {\r\n method: \"PUT\",\r\n headers: {\r\n \"Content-Type\": file.type || \"application/octet-stream\",\r\n },\r\n body: file,\r\n });\r\n\r\n if (!uploadResponse.ok) {\r\n throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);\r\n }\r\n\r\n // 4. ๊ณต๊ฐ URL ๋ฐํ\r\n return publicUrl;\r\n } catch (error) {\r\n console.error(\"S3 upload failed:\", error);\r\n throw error;\r\n }\r\n };\r\n};\r\n"],"mappings":"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","names":["editor"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/LumirEditor.tsx","../src/utils/cn.ts","../src/utils/s3-uploader.ts"],"sourcesContent":["\"use client\";\r\n\r\nimport { useEffect, useMemo, useCallback, useState, useRef } from \"react\";\r\nimport {\r\n useCreateBlockNote,\r\n SideMenu as BlockSideMenu,\r\n SideMenuController,\r\n DragHandleButton,\r\n SuggestionMenuController,\r\n getDefaultReactSlashMenuItems,\r\n} from \"@blocknote/react\";\r\nimport { BlockNoteView } from \"@blocknote/mantine\";\r\nimport { cn } from \"../utils/cn\";\r\n\r\nimport type {\r\n DefaultPartialBlock,\r\n LumirEditorProps,\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema,\r\n} from \"../types\";\r\n\r\nimport { createS3Uploader } from \"../utils/s3-uploader\";\r\n\r\n// ==========================================\r\n// ์ ํธ๋ฆฌํฐ ํด๋์ค๋ค\r\n// ==========================================\r\n\r\n/**\r\n * ์ฝํ
์ธ ๊ด๋ฆฌ ์ ํธ๋ฆฌํฐ\r\n * ๊ธฐ๋ณธ ๋ธ๋ก ์์ฑ ๋ฐ ์ฝํ
์ธ ๊ฒ์ฆ ๋ก์ง์ ๋ด๋น\r\n */\r\nexport class ContentUtils {\r\n /**\r\n * JSON ๋ฌธ์์ด์ ์ ํจ์ฑ์ ๊ฒ์ฆํฉ๋๋ค\r\n * @param jsonString ๊ฒ์ฆํ JSON ๋ฌธ์์ด\r\n * @returns ์ ํจํ JSON ๋ฌธ์์ด์ธ์ง ์ฌ๋ถ\r\n */\r\n static isValidJSONString(jsonString: string): boolean {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n return Array.isArray(parsed);\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * JSON ๋ฌธ์์ด์ DefaultPartialBlock ๋ฐฐ์ด๋ก ํ์ฑํฉ๋๋ค\r\n * @param jsonString JSON ๋ฌธ์์ด\r\n * @returns ํ์ฑ๋ ๋ธ๋ก ๋ฐฐ์ด ๋๋ null (ํ์ฑ ์คํจ ์)\r\n */\r\n static parseJSONContent(jsonString: string): DefaultPartialBlock[] | null {\r\n try {\r\n const parsed = JSON.parse(jsonString);\r\n if (Array.isArray(parsed)) {\r\n return parsed as DefaultPartialBlock[];\r\n }\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * ๊ธฐ๋ณธ paragraph ๋ธ๋ก ์์ฑ\r\n * @returns ๊ธฐ๋ณธ ์ค์ ์ด ์ ์ฉ๋ DefaultPartialBlock\r\n */\r\n static createDefaultBlock(): DefaultPartialBlock {\r\n return {\r\n type: \"paragraph\",\r\n props: {\r\n textColor: \"default\",\r\n backgroundColor: \"default\",\r\n textAlignment: \"left\",\r\n },\r\n content: [{ type: \"text\", text: \"\", styles: {} }],\r\n children: [],\r\n };\r\n }\r\n\r\n /**\r\n * ์ฝํ
์ธ ์ ํจ์ฑ ๊ฒ์ฆ ๋ฐ ๊ธฐ๋ณธ๊ฐ ์ค์ \r\n * @param content ์ฌ์ฉ์ ์ ๊ณต ์ฝํ
์ธ (๊ฐ์ฒด ๋ฐฐ์ด ๋๋ JSON ๋ฌธ์์ด)\r\n * @param emptyBlockCount ๋น ๋ธ๋ก ๊ฐ์ (๊ธฐ๋ณธ๊ฐ: 3)\r\n * @returns ๊ฒ์ฆ๋ ์ฝํ
์ธ ๋ฐฐ์ด\r\n */\r\n static validateContent(\r\n content?: DefaultPartialBlock[] | string,\r\n emptyBlockCount: number = 3\r\n ): DefaultPartialBlock[] {\r\n // 1. ๋ฌธ์์ด์ธ ๊ฒฝ์ฐ JSON ํ์ฑ ์๋\r\n if (typeof content === \"string\") {\r\n if (content.trim() === \"\") {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n const parsedContent = this.parseJSONContent(content);\r\n if (parsedContent && parsedContent.length > 0) {\r\n return parsedContent;\r\n }\r\n\r\n // ํ์ฑ ์คํจ ์ ๋น ๋ธ๋ก ์์ฑ\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n // 2. ๋ฐฐ์ด์ธ ๊ฒฝ์ฐ ๊ธฐ์กด ๋ก์ง\r\n if (!content || content.length === 0) {\r\n return this.createEmptyBlocks(emptyBlockCount);\r\n }\r\n\r\n return content;\r\n }\r\n\r\n /**\r\n * ๋น ๋ธ๋ก๋ค์ ์์ฑํฉ๋๋ค\r\n * @param emptyBlockCount ์์ฑํ ๋ธ๋ก ๊ฐ์\r\n * @returns ์์ฑ๋ ๋น ๋ธ๋ก ๋ฐฐ์ด\r\n */\r\n private static createEmptyBlocks(\r\n emptyBlockCount: number\r\n ): DefaultPartialBlock[] {\r\n return Array.from({ length: emptyBlockCount }, () =>\r\n this.createDefaultBlock()\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * ์๋ํฐ ์ค์ ๊ด๋ฆฌ ์ ํธ๋ฆฌํฐ\r\n * ๊ฐ์ข
์ค์ ์ ๊ธฐ๋ณธ๊ฐ๊ณผ ๊ฒ์ฆ ๋ก์ง์ ๋ด๋น\r\n */\r\nexport class EditorConfig {\r\n /**\r\n * ํ
์ด๋ธ ์ค์ ๊ธฐ๋ณธ๊ฐ ์ ์ฉ\r\n * @param userTables ์ฌ์ฉ์ ํ
์ด๋ธ ์ค์ \r\n * @returns ๊ธฐ๋ณธ๊ฐ์ด ์ ์ฉ๋ ํ
์ด๋ธ ์ค์ \r\n */\r\n static getDefaultTableConfig(userTables?: LumirEditorProps[\"tables\"]) {\r\n return {\r\n splitCells: userTables?.splitCells ?? true,\r\n cellBackgroundColor: userTables?.cellBackgroundColor ?? true,\r\n cellTextColor: userTables?.cellTextColor ?? true,\r\n headers: userTables?.headers ?? true,\r\n };\r\n }\r\n\r\n /**\r\n * ํค๋ฉ ์ค์ ๊ธฐ๋ณธ๊ฐ ์ ์ฉ\r\n * @param userHeading ์ฌ์ฉ์ ํค๋ฉ ์ค์ \r\n * @returns ๊ธฐ๋ณธ๊ฐ์ด ์ ์ฉ๋ ํค๋ฉ ์ค์ \r\n */\r\n static getDefaultHeadingConfig(userHeading?: LumirEditorProps[\"heading\"]) {\r\n return userHeading?.levels && userHeading.levels.length > 0\r\n ? userHeading\r\n : { levels: [1, 2, 3, 4, 5, 6] as (1 | 2 | 3 | 4 | 5 | 6)[] };\r\n }\r\n\r\n /**\r\n * ๋นํ์ฑํํ ํ์ฅ ๊ธฐ๋ฅ ๋ชฉ๋ก ์์ฑ\r\n * @param userExtensions ์ฌ์ฉ์ ์ ์ ๋นํ์ฑ ํ์ฅ\r\n * @param allowVideo ๋น๋์ค ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @param allowAudio ์ค๋์ค ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @param allowFile ์ผ๋ฐ ํ์ผ ์
๋ก๋ ํ์ฉ ์ฌ๋ถ\r\n * @returns ๋นํ์ฑํํ ํ์ฅ ๊ธฐ๋ฅ ๋ชฉ๋ก\r\n */\r\n static getDisabledExtensions(\r\n userExtensions?: string[],\r\n allowVideo = false,\r\n allowAudio = false,\r\n allowFile = false\r\n ): string[] {\r\n const set = new Set<string>(userExtensions ?? []);\r\n if (!allowVideo) set.add(\"video\");\r\n if (!allowAudio) set.add(\"audio\");\r\n if (!allowFile) set.add(\"file\");\r\n return Array.from(set);\r\n }\r\n}\r\n\r\n// ํ์ผ ํ์
๊ฒ์ฆ ํจ์\r\nconst isImageFile = (file: File): boolean => {\r\n return (\r\n file.size > 0 &&\r\n (file.type?.startsWith(\"image/\") ||\r\n (!file.type && /\\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(file.name || \"\")))\r\n );\r\n};\r\n\r\nexport default function LumirEditor({\r\n // editor options\r\n initialContent,\r\n initialEmptyBlocks = 3,\r\n uploadFile,\r\n s3Upload,\r\n tables,\r\n heading,\r\n defaultStyles = true,\r\n disableExtensions,\r\n tabBehavior = \"prefer-navigate-ui\",\r\n trailingBlock = true,\r\n allowVideoUpload = false,\r\n allowAudioUpload = false,\r\n allowFileUpload = false,\r\n // view options\r\n editable = true,\r\n theme = \"light\",\r\n formattingToolbar = true,\r\n linkToolbar = true,\r\n sideMenu = true,\r\n emojiPicker = true,\r\n filePanel = true,\r\n tableHandles = true,\r\n onSelectionChange,\r\n className = \"\",\r\n sideMenuAddButton = false,\r\n // callbacks / refs\r\n onContentChange,\r\n}: LumirEditorProps) {\r\n // ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ์ํ\r\n const [isUploading, setIsUploading] = useState(false);\r\n const validatedContent = useMemo<DefaultPartialBlock[]>(() => {\r\n return ContentUtils.validateContent(initialContent, initialEmptyBlocks);\r\n }, [initialContent, initialEmptyBlocks]);\r\n\r\n // ํ
์ด๋ธ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const tableConfig = useMemo(() => {\r\n return EditorConfig.getDefaultTableConfig(tables);\r\n }, [\r\n tables?.splitCells,\r\n tables?.cellBackgroundColor,\r\n tables?.cellTextColor,\r\n tables?.headers,\r\n ]);\r\n\r\n // ํค๋ฉ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const headingConfig = useMemo(() => {\r\n return EditorConfig.getDefaultHeadingConfig(heading);\r\n }, [heading?.levels?.join(\",\") ?? \"\"]);\r\n\r\n // ๋นํ์ฑํ ํ์ฅ ๋ฉ๋ชจ์ด์ ์ด์
\r\n const disabledExtensions = useMemo(() => {\r\n return EditorConfig.getDisabledExtensions(\r\n disableExtensions,\r\n allowVideoUpload,\r\n allowAudioUpload,\r\n allowFileUpload\r\n );\r\n }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);\r\n\r\n // fileNameTransform ์ฝ๋ฐฑ์ ref๋ก ๊ด๋ฆฌ (์๋ํฐ ์ฌ์์ฑ ๋ฐฉ์ง)\r\n const fileNameTransformRef = useRef(s3Upload?.fileNameTransform);\r\n useEffect(() => {\r\n fileNameTransformRef.current = s3Upload?.fileNameTransform;\r\n }, [s3Upload?.fileNameTransform]);\r\n\r\n // S3 ์
๋ก๋ ์ค์ ๋ฉ๋ชจ์ด์ ์ด์
(๊ฐ์ฒด ์ฐธ์กฐ ์์ ํ)\r\n // ์ฃผ์: fileNameTransform์ ref๋ก ๊ด๋ฆฌํ๋ฏ๋ก ์์กด์ฑ์์ ์ ์ธ\r\n const memoizedS3Upload = useMemo(() => {\r\n if (!s3Upload) return undefined;\r\n return {\r\n apiEndpoint: s3Upload.apiEndpoint,\r\n env: s3Upload.env,\r\n path: s3Upload.path,\r\n appendUUID: s3Upload.appendUUID,\r\n preserveExtension: s3Upload.preserveExtension,\r\n // ์ต์ ์ฝ๋ฐฑ์ ํญ์ ์ฌ์ฉํ๋๋ก ref๋ฅผ ํตํด ์ ๊ทผ\r\n fileNameTransform: ((originalName: string, file: File) => {\r\n return fileNameTransformRef.current\r\n ? fileNameTransformRef.current(originalName, file)\r\n : originalName;\r\n }) as ((originalName: string, file: File) => string) | undefined,\r\n };\r\n }, [\r\n s3Upload?.apiEndpoint,\r\n s3Upload?.env,\r\n s3Upload?.path,\r\n s3Upload?.appendUUID,\r\n s3Upload?.preserveExtension,\r\n ]);\r\n\r\n const editor = useCreateBlockNote<\r\n DefaultBlockSchema,\r\n DefaultInlineContentSchema,\r\n DefaultStyleSchema\r\n >(\r\n {\r\n initialContent: validatedContent as DefaultPartialBlock[],\r\n tables: tableConfig,\r\n heading: headingConfig,\r\n animations: false, // ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๋๋ฉ์ด์
๋นํ์ฑํ\r\n defaultStyles,\r\n // ํ์ฅ ๋นํ์ฑ: ๋น๋์ค/์ค๋์ค/ํ์ผ ์ ์ด\r\n disableExtensions: disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile: async (file) => {\r\n // ์ด๋ฏธ์ง ํ์ผ๋ง ํ์ฉ (์ด๋ฏธ์ง ์ ์ฉ ์๋ํฐ)\r\n if (!isImageFile(file)) {\r\n throw new Error(\"Only image files are allowed\");\r\n }\r\n\r\n try {\r\n let imageUrl: string;\r\n\r\n // 1. ์ฌ์ฉ์ ์ ์ uploadFile ์ฐ์ \r\n if (uploadFile) {\r\n imageUrl = await uploadFile(file);\r\n }\r\n // 2. S3 ์
๋ก๋ (uploadFile ์์ ๋)\r\n else if (memoizedS3Upload?.apiEndpoint) {\r\n const s3Uploader = createS3Uploader(memoizedS3Upload);\r\n imageUrl = await s3Uploader(file);\r\n }\r\n // 3. ์
๋ก๋ ๋ฐฉ๋ฒ์ด ์์ผ๋ฉด ์๋ฌ\r\n else {\r\n throw new Error(\"No upload method available\");\r\n }\r\n\r\n // BlockNote๊ฐ ์๋์ผ๋ก ์ด๋ฏธ์ง ๋ธ๋ก์ ์์ฑํ๋๋ก URL๋ง ๋ฐํ\r\n return imageUrl;\r\n } catch (error) {\r\n console.error(\"Image upload failed:\", error);\r\n throw new Error(\r\n \"Upload failed: \" +\r\n (error instanceof Error ? error.message : String(error))\r\n );\r\n }\r\n },\r\n pasteHandler: (ctx) => {\r\n const { event, editor, defaultPasteHandler } = ctx as any;\r\n const fileList =\r\n (event?.clipboardData?.files as FileList | null) ?? null;\r\n const files: File[] = fileList ? Array.from(fileList) : [];\r\n const acceptedFiles: File[] = files.filter(isImageFile);\r\n\r\n // ํ์ผ์ด ์์ง๋ง ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ ์ฒ๋ฆฌ ๋ง๊ณ ๋ฌด์\r\n if (files.length > 0 && acceptedFiles.length === 0) {\r\n event.preventDefault();\r\n return true;\r\n }\r\n\r\n // ์ด๋ฏธ์ง๊ฐ ์์ผ๋ฉด ๊ธฐ๋ณธ ์ฒ๋ฆฌ\r\n if (acceptedFiles.length === 0) {\r\n return defaultPasteHandler() ?? false;\r\n }\r\n\r\n event.preventDefault();\r\n (async () => {\r\n // ๋ถ์ฌ๋ฃ๊ธฐ๋ก ์ฌ๋ฌ ์ด๋ฏธ์ง ์
๋ก๋ ์ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // ์๋ํฐ์ uploadFile ํจ์ ์ฌ์ฉ (ํต์ผ๋ ๋ก์ง)\r\n const url = await editor.uploadFile(file);\r\n editor.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n return true;\r\n },\r\n },\r\n [\r\n validatedContent,\r\n tableConfig,\r\n headingConfig,\r\n defaultStyles,\r\n disabledExtensions,\r\n tabBehavior,\r\n trailingBlock,\r\n uploadFile,\r\n memoizedS3Upload,\r\n ]\r\n );\r\n\r\n // ํธ์ง ๊ฐ๋ฅ ์ฌ๋ถ ์ค์ \r\n useEffect(() => {\r\n if (editor) {\r\n editor.isEditable = editable;\r\n }\r\n }, [editor, editable]);\r\n\r\n // ์ฝํ
์ธ ๋ณ๊ฒฝ ๊ฐ์ง\r\n useEffect(() => {\r\n if (!editor || !onContentChange) return;\r\n\r\n const handleContentChange = () => {\r\n // BlockNote์ ์ฌ๋ฐ๋ฅธ API ์ฌ์ฉ\r\n const blocks = editor.topLevelBlocks as DefaultPartialBlock[];\r\n onContentChange(blocks);\r\n };\r\n\r\n return editor.onEditorContentChange(handleContentChange);\r\n }, [editor, onContentChange]);\r\n\r\n // ๋๋๊ทธ์ค๋๋กญ ์ด๋ฏธ์ง ์ฒ๋ฆฌ\r\n useEffect(() => {\r\n const el = editor?.domElement as HTMLElement | undefined;\r\n if (!el) return;\r\n\r\n const handleDragOver = (e: DragEvent) => {\r\n if (e.defaultPrevented) return;\r\n const hasFiles = (\r\n e.dataTransfer?.types as unknown as string[] | undefined\r\n )?.includes?.(\"Files\");\r\n if (hasFiles) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n }\r\n };\r\n\r\n const handleDrop = (e: DragEvent) => {\r\n if (!e.dataTransfer) return;\r\n const hasFiles = (\r\n (e.dataTransfer.types as unknown as string[] | undefined) ?? []\r\n ).includes(\"Files\");\r\n if (!hasFiles) return;\r\n\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const items = Array.from(e.dataTransfer.items ?? []);\r\n const files = items\r\n .filter((it) => it.kind === \"file\")\r\n .map((it) => it.getAsFile())\r\n .filter((f): f is File => !!f);\r\n\r\n // ์ด๋ฏธ์ง ํ์ผ๋ง ํ์ฉ\r\n const acceptedFiles = files.filter(isImageFile);\r\n\r\n if (acceptedFiles.length === 0) return;\r\n\r\n (async () => {\r\n // ๋๋๊ทธ์ค๋๋กญ์ผ๋ก ์ฌ๋ฌ ์ด๋ฏธ์ง ์
๋ก๋ ์ ๋ก๋ฉ ์ํ ๊ด๋ฆฌ\r\n setIsUploading(true);\r\n try {\r\n for (const file of acceptedFiles) {\r\n try {\r\n // ์๋ํฐ์ uploadFile ํจ์ ์ฌ์ฉ (์ผ๊ด๋ ๋ก์ง)\r\n if (editor?.uploadFile) {\r\n const url = await editor.uploadFile(file);\r\n if (url) {\r\n editor.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\r\n }\r\n }\r\n } catch (err) {\r\n console.warn(\r\n \"Image upload failed, skipped:\",\r\n file.name || \"\",\r\n err\r\n );\r\n }\r\n }\r\n } finally {\r\n setIsUploading(false);\r\n }\r\n })();\r\n };\r\n\r\n el.addEventListener(\"dragover\", handleDragOver, { capture: true });\r\n el.addEventListener(\"drop\", handleDrop, { capture: true });\r\n\r\n return () => {\r\n el.removeEventListener(\"dragover\", handleDragOver, {\r\n capture: true,\r\n } as any);\r\n el.removeEventListener(\"drop\", handleDrop, { capture: true } as any);\r\n };\r\n }, [editor]);\r\n\r\n // SideMenu ์ค์ (Add ๋ฒํผ ์ ์ด)\r\n const computedSideMenu = useMemo(() => {\r\n return sideMenuAddButton ? sideMenu : false;\r\n }, [sideMenuAddButton, sideMenu]);\r\n\r\n // Add ๋ฒํผ ์๋ ์ฌ์ด๋ ๋ฉ๋ด (๋๋๊ทธ ํธ๋ค๋ง) - ๋ฉ๋ชจ์ด์ ์ด์
\r\n const DragHandleOnlySideMenu = useMemo(() => {\r\n return (props: any) => (\r\n <BlockSideMenu {...props}>\r\n <DragHandleButton {...props} />\r\n </BlockSideMenu>\r\n );\r\n }, []);\r\n\r\n return (\r\n <div\r\n className={cn(\"lumirEditor\", className)}\r\n style={{ position: \"relative\" }}\r\n >\r\n <BlockNoteView\r\n editor={editor}\r\n editable={editable}\r\n theme={theme}\r\n formattingToolbar={formattingToolbar}\r\n linkToolbar={linkToolbar}\r\n sideMenu={computedSideMenu}\r\n slashMenu={false}\r\n emojiPicker={emojiPicker}\r\n filePanel={filePanel}\r\n tableHandles={tableHandles}\r\n onSelectionChange={onSelectionChange}\r\n >\r\n {\r\n <SuggestionMenuController\r\n triggerCharacter=\"/\"\r\n getItems={useCallback(\r\n async (query: string) => {\r\n const items = getDefaultReactSlashMenuItems(editor);\r\n // ๋น๋์ค, ์ค๋์ค, ํ์ผ ๊ด๋ จ ํญ๋ชฉ ์ ๊ฑฐ\r\n const filtered = items.filter((item: any) => {\r\n const key = (item?.key || \"\").toString().toLowerCase();\r\n const title = (item?.title || \"\").toString().toLowerCase();\r\n // ๋น๋์ค, ์ค๋์ค, ํ์ผ ๊ด๋ จ ํญ๋ชฉ ์ ๊ฑฐ\r\n if ([\"video\", \"audio\", \"file\"].includes(key)) return false;\r\n if (\r\n title.includes(\"video\") ||\r\n title.includes(\"audio\") ||\r\n title.includes(\"file\")\r\n )\r\n return false;\r\n return true;\r\n });\r\n\r\n if (!query) return filtered;\r\n const q = query.toLowerCase();\r\n return filtered.filter(\r\n (item: any) =>\r\n item.title?.toLowerCase().includes(q) ||\r\n (item.aliases || []).some((a: string) =>\r\n a.toLowerCase().includes(q)\r\n )\r\n );\r\n },\r\n [editor]\r\n )}\r\n />\r\n }\r\n {!sideMenuAddButton && (\r\n <SideMenuController sideMenu={DragHandleOnlySideMenu} />\r\n )}\r\n </BlockNoteView>\r\n\r\n {/* ์ด๋ฏธ์ง ์
๋ก๋ ๋ก๋ฉ ์คํผ๋ */}\r\n {isUploading && (\r\n <div className=\"lumirEditor-upload-overlay\">\r\n <div className=\"lumirEditor-spinner\" />\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","// clsx์ tailwind-merge๋ฅผ ์ฌ์ฉํ className ์ ํธ๋ฆฌํฐ\r\n// ์ฌ์ฉ์๊ฐ ์ง์ ์ค์นํ๋๋ก ๊ถ์ฅํ๊ฑฐ๋, ๊ฐ๋จํ ๋ฒ์ ์ ๊ณต\r\n\r\nexport function cn(...inputs: (string | undefined | null | false)[]) {\r\n return inputs.filter(Boolean).join(' ');\r\n}\r\n","export interface S3UploaderConfig {\r\n apiEndpoint: string; // '/api/s3/presigned'(ํ์)\r\n env: \"production\" | \"development\"; // ํ๊ฒฝ (ํ์)\r\n path: string; // ํ์ผ ๊ฒฝ๋ก (ํ์)\r\n /** ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ - ํ์ฅ์๋ฅผ ์ ์ธํ ํ์ผ๋ช
์ ๋ฐ์ ๋ณํํฉ๋๋ค */\r\n fileNameTransform?: (nameWithoutExt: string, file: File) => string;\r\n /** true์ผ ๊ฒฝ์ฐ ํ์ผ๋ช
๋ค์ UUID๋ฅผ ์๋์ผ๋ก ์ถ๊ฐํฉ๋๋ค (์: image_abc123.png) */\r\n appendUUID?: boolean;\r\n /** false๋ก ์ค์ ํ๋ฉด ํ์ฅ์๋ฅผ ์๋์ผ๋ก ๋ถ์ด์ง ์์ (๊ธฐ๋ณธ: true) */\r\n preserveExtension?: boolean;\r\n}\r\n\r\n// UUID ์์ฑ ํจ์ (crypto.randomUUID ๋๋ ํด๋ฐฑ)\r\nconst generateUUID = (): string => {\r\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\r\n return crypto.randomUUID();\r\n }\r\n // ํด๋ฐฑ: ๊ฐ๋จํ UUID v4 ํ์ ์์ฑ\r\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\r\n const r = (Math.random() * 16) | 0;\r\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n};\r\n\r\nexport const createS3Uploader = (config: S3UploaderConfig) => {\r\n const {\r\n apiEndpoint,\r\n env,\r\n path,\r\n fileNameTransform,\r\n appendUUID,\r\n preserveExtension = true,\r\n } = config;\r\n\r\n // ํ์ ํ๋ผ๋ฏธํฐ ๊ฒ์ฆ\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"apiEndpoint is required for S3 upload. Please provide a valid API endpoint.\"\r\n );\r\n }\r\n\r\n if (!env) {\r\n throw new Error(\"env is required. Must be 'development' or 'production'.\");\r\n }\r\n\r\n if (!path || path.trim() === \"\") {\r\n throw new Error(\"path is required and cannot be empty.\");\r\n }\r\n\r\n // ํ์ผ๋ช
์ UUID ์ถ๊ฐํ๋ ํจ์\r\n const appendUUIDToFileName = (filename: string): string => {\r\n const lastDotIndex = filename.lastIndexOf(\".\");\r\n if (lastDotIndex === -1) {\r\n // ํ์ฅ์๊ฐ ์๋ ๊ฒฝ์ฐ\r\n return `${filename}_${generateUUID()}`;\r\n }\r\n const name = filename.substring(0, lastDotIndex);\r\n const ext = filename.substring(lastDotIndex);\r\n return `${name}_${generateUUID()}${ext}`;\r\n };\r\n\r\n // ๊ณ์ธต ๊ตฌ์กฐ ํ์ผ๋ช
์์ฑ ํจ์\r\n const generateHierarchicalFileName = (file: File): string => {\r\n // 0. ํ์ฅ์ ๋ถ๋ฆฌ\r\n const originalName = file.name;\r\n const lastDotIndex = originalName.lastIndexOf(\".\");\r\n const nameWithoutExt =\r\n lastDotIndex === -1\r\n ? originalName\r\n : originalName.substring(0, lastDotIndex);\r\n const extension =\r\n lastDotIndex === -1 ? \"\" : originalName.substring(lastDotIndex);\r\n\r\n let filename = nameWithoutExt;\r\n\r\n // 1. ์ฌ์ฉ์ ์ ์ ํ์ผ๋ช
๋ณํ ์ฝ๋ฐฑ ์ ์ฉ (ํ์ฅ์ ์ ์ธํ ์ด๋ฆ๋ง)\r\n if (fileNameTransform) {\r\n filename = fileNameTransform(filename, file);\r\n }\r\n\r\n // 2. UUID ์๋ ์ถ๊ฐ (appendUUID๊ฐ true์ธ ๊ฒฝ์ฐ)\r\n if (appendUUID) {\r\n filename = `${filename}_${generateUUID()}`;\r\n }\r\n\r\n // 3. ํ์ฅ์ ๋ค์ ๋ถ์ด๊ธฐ (preserveExtension์ด true์ธ ๊ฒฝ์ฐ๋ง)\r\n if (preserveExtension) {\r\n filename = `${filename}${extension}`;\r\n }\r\n\r\n // {env}/{path}/{filename}\r\n return `${env}/${path}/${filename}`;\r\n };\r\n\r\n return async (file: File): Promise<string> => {\r\n try {\r\n // ํ์ผ ์
๋ก๋ ์์๋ apiEndpoint ์ฌ๊ฒ์ฆ\r\n if (!apiEndpoint || apiEndpoint.trim() === \"\") {\r\n throw new Error(\r\n \"Invalid apiEndpoint: Cannot upload file without a valid API ENDPOINT\"\r\n );\r\n }\r\n\r\n // 1. ๊ณ์ธต ๊ตฌ์กฐ ํ์ผ๋ช
์์ฑ\r\n const fileName = generateHierarchicalFileName(file);\r\n\r\n // 2. presigned URL ์์ฒญ\r\n const response = await fetch(\r\n `${apiEndpoint}?key=${encodeURIComponent(fileName)}`\r\n );\r\n\r\n if (!response.ok) {\r\n const errorText = (await response.text()) || \"\";\r\n throw new Error(\r\n `Failed to get presigned URL: ${response.statusText}, ${errorText}`\r\n );\r\n }\r\n\r\n const responseData = await response.json();\r\n const { presignedUrl, publicUrl } = responseData;\r\n\r\n // 3. S3์ ์
๋ก๋\r\n const uploadResponse = await fetch(presignedUrl, {\r\n method: \"PUT\",\r\n headers: {\r\n \"Content-Type\": file.type || \"application/octet-stream\",\r\n },\r\n body: file,\r\n });\r\n\r\n if (!uploadResponse.ok) {\r\n throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);\r\n }\r\n\r\n // 4. ๊ณต๊ฐ URL ๋ฐํ\r\n return publicUrl;\r\n } catch (error) {\r\n console.error(\"S3 upload failed:\", error);\r\n throw error;\r\n }\r\n };\r\n};\r\n"],"mappings":"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","names":["editor"]}
|
package/package.json
CHANGED