@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # LumirEditor
2
2
 
3
- ๐Ÿ–ผ๏ธ **์ด๋ฏธ์ง€ ์ „์šฉ** BlockNote ๊ธฐ๋ฐ˜ Rich Text ์—๋””ํ„ฐ
3
+ **์ด๋ฏธ์ง€ ์ „์šฉ** BlockNote ๊ธฐ๋ฐ˜ Rich Text ์—๋””ํ„ฐ
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@lumir-company/editor.svg)](https://www.npmjs.com/package/@lumir-company/editor)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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](#-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
- | โ˜๏ธ **S3 ์—ฐ๋™** | Presigned URL ๊ธฐ๋ฐ˜ S3 ์—…๋กœ๋“œ ๋‚ด์žฅ |
33
- | ๐Ÿท๏ธ **ํŒŒ์ผ๋ช… ์ปค์Šคํ„ฐ๋งˆ์ด์ง•** | ์—…๋กœ๋“œ ํŒŒ์ผ๋ช… ๋ณ€๊ฒฝ ์ฝœ๋ฐฑ + UUID ์ž๋™ ์ถ”๊ฐ€ ์ง€์› |
34
- | โณ **๋กœ๋”ฉ ์Šคํ”ผ๋„ˆ** | ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ค‘ ์ž๋™ ์Šคํ”ผ๋„ˆ ํ‘œ์‹œ |
35
- | ๐Ÿš€ **์„ฑ๋Šฅ ์ตœ์ ํ™”** | ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋น„ํ™œ์„ฑํ™”๋กœ ๋น ๋ฅธ ๋ Œ๋”๋ง |
36
- | ๐Ÿ“ **TypeScript** | ์™„์ „ํ•œ ํƒ€์ž… ์•ˆ์ „์„ฑ |
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
- > โš ๏ธ **์ค‘์š”**: `style.css`๋ฅผ ์ž„ํฌํŠธํ•˜์ง€ ์•Š์œผ๋ฉด ์—๋””ํ„ฐ๊ฐ€ ์ •์ƒ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
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: (originalName, file) => {
176
- // ์˜ˆ: ์‚ฌ์šฉ์ž ID ์ถ”๊ฐ€
177
+ fileNameTransform: (nameWithoutExt, file) => {
178
+ // nameWithoutExt๋Š” ํ™•์žฅ์ž๊ฐ€ ์ œ๊ฑฐ๋œ ํŒŒ์ผ๋ช… (์˜ˆ: "photo")
179
+ // ํ™•์žฅ์ž๋Š” ์ž๋™์œผ๋กœ ๋ถ™์Šต๋‹ˆ๋‹ค
177
180
  const userId = getCurrentUserId();
178
- return `${userId}_${originalName}`;
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
- ์—…๋กœ๋“œ: user123_photo.png
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: (originalName) => `user123_${originalName}`,
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
- 1. fileNameTransform ์ ์šฉ: user123_photo.png
210
- 2. appendUUID ์ ์šฉ: user123_photo_550e8400-e29b-41d4.png
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: (originalName, file) => {
230
+ fileNameTransform: (nameWithoutExt, file) => {
231
+ // nameWithoutExt๋Š” ์ด๋ฏธ ํ™•์žฅ์ž๊ฐ€ ์ œ๊ฑฐ๋จ
224
232
  const timestamp = new Date().toISOString().split("T")[0]; // 2024-01-15
225
- const ext = originalName.split(".").pop();
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
- 2024-01-15_photo_550e8400-e29b-41d4.png
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
- ## ๐Ÿ“š Props API
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?: (originalName: string, file: File) => string;
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?: S3UploaderConfig; // S3 ์—…๋กœ๋“œ ์„ค์ • (apiEndpoint, env, path ๋“ฑ)
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
- // โœ… ํ•ด๊ฒฐ: appendUUID ์‚ฌ์šฉ
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
- ## ๐Ÿ› ๏ธ ์œ ํ‹ธ๋ฆฌํ‹ฐ API
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
- - โœจ ํŒŒ์ผ๋ช… ๋ณ€ํ™˜ ์ฝœ๋ฐฑ (`fileNameTransform`) ์ถ”๊ฐ€
569
- - โœจ UUID ์ž๋™ ์ถ”๊ฐ€ ์˜ต์…˜ (`appendUUID`) ์ถ”๊ฐ€
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?: (originalName: string, file: File) => string;
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?: (originalName: string, file: File) => string;
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?: (originalName: string, file: File) => string;
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?: (originalName: string, file: File) => string;
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 { apiEndpoint, env, path, fileNameTransform, appendUUID } = config;
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
- let filename = file.name;
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 = appendUUIDToFileName(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":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,mBAAkE;AAClE,IAAAA,gBAOO;AACP,qBAA8B;;;ACRvB,SAAS,MAAM,QAA+C;AACnE,SAAO,OAAO,OAAO,OAAO,EAAE,KAAK,GAAG;AACxC;;;ACQA,IAAM,eAAe,MAAc;AACjC,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACtD,WAAO,OAAO,WAAW;AAAA,EAC3B;AAEA,SAAO,uCAAuC,QAAQ,SAAS,CAAC,MAAM;AACpE,UAAM,IAAK,KAAK,OAAO,IAAI,KAAM;AACjC,UAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAO;AACtC,WAAO,EAAE,SAAS,EAAE;AAAA,EACtB,CAAC;AACH;AAEO,IAAM,mBAAmB,CAAC,WAA6B;AAC5D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,oBAAoB;AAAA,EACtB,IAAI;AAGJ,MAAI,CAAC,eAAe,YAAY,KAAK,MAAM,IAAI;AAC7C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AAEA,MAAI,CAAC,QAAQ,KAAK,KAAK,MAAM,IAAI;AAC/B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAGA,QAAM,uBAAuB,CAAC,aAA6B;AACzD,UAAM,eAAe,SAAS,YAAY,GAAG;AAC7C,QAAI,iBAAiB,IAAI;AAEvB,aAAO,GAAG,QAAQ,IAAI,aAAa,CAAC;AAAA,IACtC;AACA,UAAM,OAAO,SAAS,UAAU,GAAG,YAAY;AAC/C,UAAM,MAAM,SAAS,UAAU,YAAY;AAC3C,WAAO,GAAG,IAAI,IAAI,aAAa,CAAC,GAAG,GAAG;AAAA,EACxC;AAGA,QAAM,+BAA+B,CAAC,SAAuB;AAE3D,UAAM,eAAe,KAAK;AAC1B,UAAM,eAAe,aAAa,YAAY,GAAG;AACjD,UAAM,iBACJ,iBAAiB,KACb,eACA,aAAa,UAAU,GAAG,YAAY;AAC5C,UAAM,YACJ,iBAAiB,KAAK,KAAK,aAAa,UAAU,YAAY;AAEhE,QAAI,WAAW;AAGf,QAAI,mBAAmB;AACrB,iBAAW,kBAAkB,UAAU,IAAI;AAAA,IAC7C;AAGA,QAAI,YAAY;AACd,iBAAW,GAAG,QAAQ,IAAI,aAAa,CAAC;AAAA,IAC1C;AAGA,QAAI,mBAAmB;AACrB,iBAAW,GAAG,QAAQ,GAAG,SAAS;AAAA,IACpC;AAGA,WAAO,GAAG,GAAG,IAAI,IAAI,IAAI,QAAQ;AAAA,EACnC;AAEA,SAAO,OAAO,SAAgC;AAC5C,QAAI;AAEF,UAAI,CAAC,eAAe,YAAY,KAAK,MAAM,IAAI;AAC7C,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAGA,YAAM,WAAW,6BAA6B,IAAI;AAGlD,YAAM,WAAW,MAAM;AAAA,QACrB,GAAG,WAAW,QAAQ,mBAAmB,QAAQ,CAAC;AAAA,MACpD;AAEA,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,YAAa,MAAM,SAAS,KAAK,KAAM;AAC7C,cAAM,IAAI;AAAA,UACR,gCAAgC,SAAS,UAAU,KAAK,SAAS;AAAA,QACnE;AAAA,MACF;AAEA,YAAM,eAAe,MAAM,SAAS,KAAK;AACzC,YAAM,EAAE,cAAc,UAAU,IAAI;AAGpC,YAAM,iBAAiB,MAAM,MAAM,cAAc;AAAA,QAC/C,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB,KAAK,QAAQ;AAAA,QAC/B;AAAA,QACA,MAAM;AAAA,MACR,CAAC;AAED,UAAI,CAAC,eAAe,IAAI;AACtB,cAAM,IAAI,MAAM,0BAA0B,eAAe,UAAU,EAAE;AAAA,MACvE;AAGA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,MAAM,qBAAqB,KAAK;AACxC,YAAM;AAAA,IACR;AAAA,EACF;AACF;;;AF2VQ;AAzcD,IAAM,eAAN,MAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMxB,OAAO,kBAAkB,YAA6B;AACpD,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,UAAU;AACpC,aAAO,MAAM,QAAQ,MAAM;AAAA,IAC7B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,iBAAiB,YAAkD;AACxE,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,UAAU;AACpC,UAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,qBAA0C;AAC/C,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,QACL,WAAW;AAAA,QACX,iBAAiB;AAAA,QACjB,eAAe;AAAA,MACjB;AAAA,MACA,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,IAAI,QAAQ,CAAC,EAAE,CAAC;AAAA,MAChD,UAAU,CAAC;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,gBACL,SACA,kBAA0B,GACH;AAEvB,QAAI,OAAO,YAAY,UAAU;AAC/B,UAAI,QAAQ,KAAK,MAAM,IAAI;AACzB,eAAO,KAAK,kBAAkB,eAAe;AAAA,MAC/C;AAEA,YAAM,gBAAgB,KAAK,iBAAiB,OAAO;AACnD,UAAI,iBAAiB,cAAc,SAAS,GAAG;AAC7C,eAAO;AAAA,MACT;AAGA,aAAO,KAAK,kBAAkB,eAAe;AAAA,IAC/C;AAGA,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,aAAO,KAAK,kBAAkB,eAAe;AAAA,IAC/C;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAe,kBACb,iBACuB;AACvB,WAAO,MAAM;AAAA,MAAK,EAAE,QAAQ,gBAAgB;AAAA,MAAG,MAC7C,KAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AACF;AAMO,IAAM,eAAN,MAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMxB,OAAO,sBAAsB,YAAyC;AACpE,WAAO;AAAA,MACL,YAAY,YAAY,cAAc;AAAA,MACtC,qBAAqB,YAAY,uBAAuB;AAAA,MACxD,eAAe,YAAY,iBAAiB;AAAA,MAC5C,SAAS,YAAY,WAAW;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,wBAAwB,aAA2C;AACxE,WAAO,aAAa,UAAU,YAAY,OAAO,SAAS,IACtD,cACA,EAAE,QAAQ,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC,EAA+B;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAO,sBACL,gBACA,aAAa,OACb,aAAa,OACb,YAAY,OACF;AACV,UAAM,MAAM,IAAI,IAAY,kBAAkB,CAAC,CAAC;AAChD,QAAI,CAAC,WAAY,KAAI,IAAI,OAAO;AAChC,QAAI,CAAC,WAAY,KAAI,IAAI,OAAO;AAChC,QAAI,CAAC,UAAW,KAAI,IAAI,MAAM;AAC9B,WAAO,MAAM,KAAK,GAAG;AAAA,EACvB;AACF;AAGA,IAAM,cAAc,CAAC,SAAwB;AAC3C,SACE,KAAK,OAAO,MACX,KAAK,MAAM,WAAW,QAAQ,KAC5B,CAAC,KAAK,QAAQ,mCAAmC,KAAK,KAAK,QAAQ,EAAE;AAE5E;AAEe,SAAR,YAA6B;AAAA;AAAA,EAElC;AAAA,EACA,qBAAqB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,cAAc;AAAA,EACd,gBAAgB;AAAA,EAChB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,kBAAkB;AAAA;AAAA,EAElB,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,eAAe;AAAA,EACf;AAAA,EACA,YAAY;AAAA,EACZ,oBAAoB;AAAA;AAAA,EAEpB;AACF,GAAqB;AAEnB,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAS,KAAK;AACpD,QAAM,uBAAmB,sBAA+B,MAAM;AAC5D,WAAO,aAAa,gBAAgB,gBAAgB,kBAAkB;AAAA,EACxE,GAAG,CAAC,gBAAgB,kBAAkB,CAAC;AAGvC,QAAM,kBAAc,sBAAQ,MAAM;AAChC,WAAO,aAAa,sBAAsB,MAAM;AAAA,EAClD,GAAG;AAAA,IACD,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AAGD,QAAM,oBAAgB,sBAAQ,MAAM;AAClC,WAAO,aAAa,wBAAwB,OAAO;AAAA,EACrD,GAAG,CAAC,SAAS,QAAQ,KAAK,GAAG,KAAK,EAAE,CAAC;AAGrC,QAAM,yBAAqB,sBAAQ,MAAM;AACvC,WAAO,aAAa;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,GAAG,CAAC,mBAAmB,kBAAkB,kBAAkB,eAAe,CAAC;AAG3E,QAAM,2BAAuB,qBAAO,UAAU,iBAAiB;AAC/D,8BAAU,MAAM;AACd,yBAAqB,UAAU,UAAU;AAAA,EAC3C,GAAG,CAAC,UAAU,iBAAiB,CAAC;AAIhC,QAAM,uBAAmB,sBAAQ,MAAM;AACrC,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO;AAAA,MACL,aAAa,SAAS;AAAA,MACtB,KAAK,SAAS;AAAA,MACd,MAAM,SAAS;AAAA,MACf,YAAY,SAAS;AAAA,MACrB,mBAAmB,SAAS;AAAA;AAAA,MAE5B,mBAAoB,CAAC,cAAsB,SAAe;AACxD,eAAO,qBAAqB,UACxB,qBAAqB,QAAQ,cAAc,IAAI,IAC/C;AAAA,MACN;AAAA,IACF;AAAA,EACF,GAAG;AAAA,IACD,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,EACZ,CAAC;AAED,QAAM,aAAS;AAAA,IAKb;AAAA,MACE,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY;AAAA;AAAA,MACZ;AAAA;AAAA,MAEA,mBAAmB;AAAA,MACnB;AAAA,MACA;AAAA,MACA,YAAY,OAAO,SAAS;AAE1B,YAAI,CAAC,YAAY,IAAI,GAAG;AACtB,gBAAM,IAAI,MAAM,8BAA8B;AAAA,QAChD;AAEA,YAAI;AACF,cAAI;AAGJ,cAAI,YAAY;AACd,uBAAW,MAAM,WAAW,IAAI;AAAA,UAClC,WAES,kBAAkB,aAAa;AACtC,kBAAM,aAAa,iBAAiB,gBAAgB;AACpD,uBAAW,MAAM,WAAW,IAAI;AAAA,UAClC,OAEK;AACH,kBAAM,IAAI,MAAM,4BAA4B;AAAA,UAC9C;AAGA,iBAAO;AAAA,QACT,SAAS,OAAO;AACd,kBAAQ,MAAM,wBAAwB,KAAK;AAC3C,gBAAM,IAAI;AAAA,YACR,qBACG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AAAA,MACA,cAAc,CAAC,QAAQ;AACrB,cAAM,EAAE,OAAO,QAAAC,SAAQ,oBAAoB,IAAI;AAC/C,cAAM,WACH,OAAO,eAAe,SAA6B;AACtD,cAAM,QAAgB,WAAW,MAAM,KAAK,QAAQ,IAAI,CAAC;AACzD,cAAM,gBAAwB,MAAM,OAAO,WAAW;AAGtD,YAAI,MAAM,SAAS,KAAK,cAAc,WAAW,GAAG;AAClD,gBAAM,eAAe;AACrB,iBAAO;AAAA,QACT;AAGA,YAAI,cAAc,WAAW,GAAG;AAC9B,iBAAO,oBAAoB,KAAK;AAAA,QAClC;AAEA,cAAM,eAAe;AACrB,SAAC,YAAY;AAEX,yBAAe,IAAI;AACnB,cAAI;AACF,uBAAW,QAAQ,eAAe;AAChC,kBAAI;AAEF,sBAAM,MAAM,MAAMA,QAAO,WAAW,IAAI;AACxC,gBAAAA,QAAO,UAAU,aAAa,GAAG,kBAAkB;AAAA,cACrD,SAAS,KAAK;AACZ,wBAAQ;AAAA,kBACN;AAAA,kBACA,KAAK,QAAQ;AAAA,kBACb;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF,UAAE;AACA,2BAAe,KAAK;AAAA,UACtB;AAAA,QACF,GAAG;AACH,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,8BAAU,MAAM;AACd,QAAI,QAAQ;AACV,aAAO,aAAa;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,QAAQ,CAAC;AAGrB,8BAAU,MAAM;AACd,QAAI,CAAC,UAAU,CAAC,gBAAiB;AAEjC,UAAM,sBAAsB,MAAM;AAEhC,YAAM,SAAS,OAAO;AACtB,sBAAgB,MAAM;AAAA,IACxB;AAEA,WAAO,OAAO,sBAAsB,mBAAmB;AAAA,EACzD,GAAG,CAAC,QAAQ,eAAe,CAAC;AAG5B,8BAAU,MAAM;AACd,UAAM,KAAK,QAAQ;AACnB,QAAI,CAAC,GAAI;AAET,UAAM,iBAAiB,CAAC,MAAiB;AACvC,UAAI,EAAE,iBAAkB;AACxB,YAAM,WACJ,EAAE,cAAc,OACf,WAAW,OAAO;AACrB,UAAI,UAAU;AACZ,UAAE,eAAe;AACjB,UAAE,gBAAgB;AAAA,MACpB;AAAA,IACF;AAEA,UAAM,aAAa,CAAC,MAAiB;AACnC,UAAI,CAAC,EAAE,aAAc;AACrB,YAAM,YACH,EAAE,aAAa,SAA6C,CAAC,GAC9D,SAAS,OAAO;AAClB,UAAI,CAAC,SAAU;AAEf,QAAE,eAAe;AACjB,QAAE,gBAAgB;AAElB,YAAM,QAAQ,MAAM,KAAK,EAAE,aAAa,SAAS,CAAC,CAAC;AACnD,YAAM,QAAQ,MACX,OAAO,CAAC,OAAO,GAAG,SAAS,MAAM,EACjC,IAAI,CAAC,OAAO,GAAG,UAAU,CAAC,EAC1B,OAAO,CAAC,MAAiB,CAAC,CAAC,CAAC;AAG/B,YAAM,gBAAgB,MAAM,OAAO,WAAW;AAE9C,UAAI,cAAc,WAAW,EAAG;AAEhC,OAAC,YAAY;AAEX,uBAAe,IAAI;AACnB,YAAI;AACF,qBAAW,QAAQ,eAAe;AAChC,gBAAI;AAEF,kBAAI,QAAQ,YAAY;AACtB,sBAAM,MAAM,MAAM,OAAO,WAAW,IAAI;AACxC,oBAAI,KAAK;AACP,yBAAO,UAAU,aAAa,GAAG,kBAAkB;AAAA,gBACrD;AAAA,cACF;AAAA,YACF,SAAS,KAAK;AACZ,sBAAQ;AAAA,gBACN;AAAA,gBACA,KAAK,QAAQ;AAAA,gBACb;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF,UAAE;AACA,yBAAe,KAAK;AAAA,QACtB;AAAA,MACF,GAAG;AAAA,IACL;AAEA,OAAG,iBAAiB,YAAY,gBAAgB,EAAE,SAAS,KAAK,CAAC;AACjE,OAAG,iBAAiB,QAAQ,YAAY,EAAE,SAAS,KAAK,CAAC;AAEzD,WAAO,MAAM;AACX,SAAG,oBAAoB,YAAY,gBAAgB;AAAA,QACjD,SAAS;AAAA,MACX,CAAQ;AACR,SAAG,oBAAoB,QAAQ,YAAY,EAAE,SAAS,KAAK,CAAQ;AAAA,IACrE;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAGX,QAAM,uBAAmB,sBAAQ,MAAM;AACrC,WAAO,oBAAoB,WAAW;AAAA,EACxC,GAAG,CAAC,mBAAmB,QAAQ,CAAC;AAGhC,QAAM,6BAAyB,sBAAQ,MAAM;AAC3C,WAAO,CAAC,UACN,4CAAC,cAAAC,UAAA,EAAe,GAAG,OACjB,sDAAC,kCAAkB,GAAG,OAAO,GAC/B;AAAA,EAEJ,GAAG,CAAC,CAAC;AAEL,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,eAAe,SAAS;AAAA,MACtC,OAAO,EAAE,UAAU,WAAW;AAAA,MAE9B;AAAA;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,UAAU;AAAA,YACV,WAAW;AAAA,YACX;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YAGE;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,kBAAiB;AAAA,kBACjB,cAAU;AAAA,oBACR,OAAO,UAAkB;AACvB,4BAAM,YAAQ,6CAA8B,MAAM;AAElD,4BAAM,WAAW,MAAM,OAAO,CAAC,SAAc;AAC3C,8BAAM,OAAO,MAAM,OAAO,IAAI,SAAS,EAAE,YAAY;AACrD,8BAAM,SAAS,MAAM,SAAS,IAAI,SAAS,EAAE,YAAY;AAEzD,4BAAI,CAAC,SAAS,SAAS,MAAM,EAAE,SAAS,GAAG,EAAG,QAAO;AACrD,4BACE,MAAM,SAAS,OAAO,KACtB,MAAM,SAAS,OAAO,KACtB,MAAM,SAAS,MAAM;AAErB,iCAAO;AACT,+BAAO;AAAA,sBACT,CAAC;AAED,0BAAI,CAAC,MAAO,QAAO;AACnB,4BAAM,IAAI,MAAM,YAAY;AAC5B,6BAAO,SAAS;AAAA,wBACd,CAAC,SACC,KAAK,OAAO,YAAY,EAAE,SAAS,CAAC,MACnC,KAAK,WAAW,CAAC,GAAG;AAAA,0BAAK,CAAC,MACzB,EAAE,YAAY,EAAE,SAAS,CAAC;AAAA,wBAC5B;AAAA,sBACJ;AAAA,oBACF;AAAA,oBACA,CAAC,MAAM;AAAA,kBACT;AAAA;AAAA,cACF;AAAA,cAED,CAAC,qBACA,4CAAC,oCAAmB,UAAU,wBAAwB;AAAA;AAAA;AAAA,QAE1D;AAAA,QAGC,eACC,4CAAC,SAAI,WAAU,8BACb,sDAAC,SAAI,WAAU,uBAAsB,GACvC;AAAA;AAAA;AAAA,EAEJ;AAEJ;","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 { apiEndpoint, env, path, fileNameTransform, appendUUID } = config;
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
- let filename = file.name;
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 = appendUUIDToFileName(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
  {
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumir-company/editor",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "description": "Image-only BlockNote rich text editor with S3 upload, customizable filename transforms, UUID support, and loading spinner",
6
6
  "keywords": [