@lumir-company/editor 0.4.8 → 0.4.9
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 +37 -6
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +22 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +22 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +93 -93
package/README.md
CHANGED
|
@@ -488,14 +488,34 @@ const imageUrl = await s3Uploader(imageFile);
|
|
|
488
488
|
|
|
489
489
|
- **MIME**: `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/bmp`
|
|
490
490
|
- **확장자**: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.bmp`
|
|
491
|
-
- **용량**: 최대 10MB
|
|
491
|
+
- **용량**: 기본 최대 10MB. `maxImageFileSize`(바이트)로 변경 가능.
|
|
492
492
|
- **제외**: SVG (XSS 방지로 업로드 불가)
|
|
493
493
|
|
|
494
494
|
**동영상** (`allowVideoUpload={true}`일 때)
|
|
495
495
|
|
|
496
496
|
- **MIME**: `video/mp4`, `video/webm`, `video/ogg`, `video/quicktime`
|
|
497
497
|
- **확장자**: `.mp4`, `.webm`, `.ogg`, `.mov`
|
|
498
|
-
- **용량**: 최대 100MB
|
|
498
|
+
- **용량**: 기본 최대 100MB. `maxVideoFileSize`(바이트)로 변경 가능.
|
|
499
|
+
|
|
500
|
+
**업로드 타임아웃**: S3 업로드 시 PUT 요청 타임아웃은 `s3Upload.uploadTimeoutMs`로 설정합니다. 미설정 시 120초(120000ms)가 적용됩니다.
|
|
501
|
+
|
|
502
|
+
**용량 및 타임아웃 사용자 설정**
|
|
503
|
+
|
|
504
|
+
이미지·동영상 최대 용량과 S3 PUT 타임아웃을 props로 변경할 수 있습니다. 미설정 시 기본값(이미지 10MB, 동영상 100MB, 타임아웃 120초)이 적용됩니다.
|
|
505
|
+
|
|
506
|
+
```tsx
|
|
507
|
+
<LumirEditor
|
|
508
|
+
allowVideoUpload={true}
|
|
509
|
+
maxImageFileSize={5 * 1024 * 1024} // 5MB
|
|
510
|
+
maxVideoFileSize={200 * 1024 * 1024} // 200MB
|
|
511
|
+
s3Upload={{
|
|
512
|
+
apiEndpoint: "/api/s3/presigned",
|
|
513
|
+
env: "production",
|
|
514
|
+
path: "uploads",
|
|
515
|
+
uploadTimeoutMs: 180000, // 180초 (대용량 동영상 시 연장)
|
|
516
|
+
}}
|
|
517
|
+
/>
|
|
518
|
+
```
|
|
499
519
|
|
|
500
520
|
### 3. 삽입 경로 (공통)
|
|
501
521
|
|
|
@@ -643,7 +663,7 @@ Undo로 블록을 복원해도 이미 삭제 API를 호출했다면 서버 상
|
|
|
643
663
|
### 7. 에러 처리
|
|
644
664
|
|
|
645
665
|
- **지원하지 않는 형식**: 업로드 시 `LumirEditorError`가 발생할 수 있으며, `onError`로 처리할 수 있습니다.
|
|
646
|
-
- **용량 초과**: 이미지 10MB·동영상 100MB를 넘으면 업로드가 거부됩니다.
|
|
666
|
+
- **용량 초과**: 기본 한도(이미지 10MB·동영상 100MB)를 넘으면 업로드가 거부됩니다. `maxImageFileSize`, `maxVideoFileSize`로 한도를 변경할 수 있습니다.
|
|
647
667
|
- **업로드 실패**: `uploadFile` 또는 S3 업로드에서 예외가 나면 해당 파일만 삽입되지 않고, 콘솔에 경고가 출력됩니다.
|
|
648
668
|
|
|
649
669
|
```tsx
|
|
@@ -1049,8 +1069,10 @@ app.get("/api/link-preview", async (req, res) => {
|
|
|
1049
1069
|
| `editable` | `boolean` | `true` | 편집 가능 여부 |
|
|
1050
1070
|
| `placeholder` | `string` | `undefined` | 빈 블록 안내 텍스트 |
|
|
1051
1071
|
| `linkPreview` | `{ apiEndpoint: string }` | `undefined` | 링크 프리뷰 설정 |
|
|
1052
|
-
| `theme`
|
|
1053
|
-
| `className`
|
|
1072
|
+
| `theme` | `"light" \| "dark"` | `"light"` | 테마 |
|
|
1073
|
+
| `className` | `string` | `""` | CSS 클래스 |
|
|
1074
|
+
| `maxImageFileSize` | `number` | `undefined` | 이미지 최대 용량(바이트). 미설정 시 10MB |
|
|
1075
|
+
| `maxVideoFileSize` | `number` | `undefined` | 동영상 최대 용량(바이트). 미설정 시 100MB |
|
|
1054
1076
|
|
|
1055
1077
|
### S3UploaderConfig
|
|
1056
1078
|
|
|
@@ -1124,6 +1146,8 @@ interface LumirEditorProps {
|
|
|
1124
1146
|
allowVideoUpload?: boolean; // 비디오 업로드 허용 (기본: false)
|
|
1125
1147
|
allowAudioUpload?: boolean; // 오디오 업로드 허용 (기본: false)
|
|
1126
1148
|
allowFileUpload?: boolean; // 일반 파일 업로드 허용 (기본: false)
|
|
1149
|
+
maxImageFileSize?: number; // 이미지 최대 파일 크기(바이트). 미설정 시 10MB
|
|
1150
|
+
maxVideoFileSize?: number; // 동영상 최대 파일 크기(바이트). 미설정 시 100MB
|
|
1127
1151
|
}
|
|
1128
1152
|
```
|
|
1129
1153
|
|
|
@@ -1328,9 +1352,16 @@ const url = await uploader(imageFile);
|
|
|
1328
1352
|
|
|
1329
1353
|
## 변경 로그
|
|
1330
1354
|
|
|
1355
|
+
### v0.4.9
|
|
1356
|
+
|
|
1357
|
+
- **업로드 용량·타임아웃 사용자 설정**
|
|
1358
|
+
- `maxImageFileSize`, `maxVideoFileSize` prop 추가 (미설정 시 기본 10MB/100MB)
|
|
1359
|
+
- `isImageFile(file, maxSize?)`, `isVideoFile(file, maxSize?)` 시그니처 확장
|
|
1360
|
+
- README: 용량 및 타임아웃 설정 예시 및 `s3Upload.uploadTimeoutMs` 안내 보강
|
|
1361
|
+
|
|
1331
1362
|
### v0.4.8
|
|
1332
1363
|
|
|
1333
|
-
-
|
|
1364
|
+
- README update (video & image upload)
|
|
1334
1365
|
- 버전 배포
|
|
1335
1366
|
|
|
1336
1367
|
### v0.4.6
|
package/dist/index.d.mts
CHANGED
|
@@ -88,6 +88,10 @@ interface LumirEditorProps {
|
|
|
88
88
|
allowVideoUpload?: boolean;
|
|
89
89
|
allowAudioUpload?: boolean;
|
|
90
90
|
allowFileUpload?: boolean;
|
|
91
|
+
/** 이미지 최대 파일 크기(바이트). 미설정 시 기본 제한(10MB) 사용 */
|
|
92
|
+
maxImageFileSize?: number;
|
|
93
|
+
/** 동영상 최대 파일 크기(바이트). 미설정 시 기본 제한(100MB) 사용 */
|
|
94
|
+
maxVideoFileSize?: number;
|
|
91
95
|
tables?: {
|
|
92
96
|
splitCells?: boolean;
|
|
93
97
|
cellBackgroundColor?: boolean;
|
|
@@ -204,7 +208,7 @@ declare class EditorConfig {
|
|
|
204
208
|
*/
|
|
205
209
|
static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean, allowFile?: boolean): string[];
|
|
206
210
|
}
|
|
207
|
-
declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, linkPreview, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, placeholder, sideMenuAddButton, floatingMenu, floatingMenuPosition, onContentChange, onError, onImageDelete, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
|
|
211
|
+
declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, maxImageFileSize, maxVideoFileSize, linkPreview, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, placeholder, sideMenuAddButton, floatingMenu, floatingMenuPosition, onContentChange, onError, onImageDelete, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
|
|
208
212
|
|
|
209
213
|
declare function cn(...inputs: (string | undefined | null | false)[]): string;
|
|
210
214
|
|
package/dist/index.d.ts
CHANGED
|
@@ -88,6 +88,10 @@ interface LumirEditorProps {
|
|
|
88
88
|
allowVideoUpload?: boolean;
|
|
89
89
|
allowAudioUpload?: boolean;
|
|
90
90
|
allowFileUpload?: boolean;
|
|
91
|
+
/** 이미지 최대 파일 크기(바이트). 미설정 시 기본 제한(10MB) 사용 */
|
|
92
|
+
maxImageFileSize?: number;
|
|
93
|
+
/** 동영상 최대 파일 크기(바이트). 미설정 시 기본 제한(100MB) 사용 */
|
|
94
|
+
maxVideoFileSize?: number;
|
|
91
95
|
tables?: {
|
|
92
96
|
splitCells?: boolean;
|
|
93
97
|
cellBackgroundColor?: boolean;
|
|
@@ -204,7 +208,7 @@ declare class EditorConfig {
|
|
|
204
208
|
*/
|
|
205
209
|
static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean, allowFile?: boolean): string[];
|
|
206
210
|
}
|
|
207
|
-
declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, linkPreview, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, placeholder, sideMenuAddButton, floatingMenu, floatingMenuPosition, onContentChange, onError, onImageDelete, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
|
|
211
|
+
declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, maxImageFileSize, maxVideoFileSize, linkPreview, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, placeholder, sideMenuAddButton, floatingMenu, floatingMenuPosition, onContentChange, onError, onImageDelete, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
|
|
208
212
|
|
|
209
213
|
declare function cn(...inputs: (string | undefined | null | false)[]): string;
|
|
210
214
|
|
package/dist/index.js
CHANGED
|
@@ -2817,7 +2817,7 @@ var ALLOWED_VIDEO_EXTENSIONS = [
|
|
|
2817
2817
|
// src/components/LumirEditor.tsx
|
|
2818
2818
|
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
2819
2819
|
var DEBUG_LOG = (loc, msg, data) => {
|
|
2820
|
-
fetch("http://127.0.0.1:7686/ingest/1f8ee1c5-0cf0-4ae7-91ed-5ea7ed17130a", {
|
|
2820
|
+
const p = fetch("http://127.0.0.1:7686/ingest/1f8ee1c5-0cf0-4ae7-91ed-5ea7ed17130a", {
|
|
2821
2821
|
method: "POST",
|
|
2822
2822
|
headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "b73262" },
|
|
2823
2823
|
body: JSON.stringify({
|
|
@@ -2827,7 +2827,8 @@ var DEBUG_LOG = (loc, msg, data) => {
|
|
|
2827
2827
|
data,
|
|
2828
2828
|
timestamp: Date.now()
|
|
2829
2829
|
})
|
|
2830
|
-
})
|
|
2830
|
+
});
|
|
2831
|
+
if (p && typeof p.catch === "function") p.catch(() => {
|
|
2831
2832
|
});
|
|
2832
2833
|
};
|
|
2833
2834
|
var ContentUtils = class {
|
|
@@ -2948,8 +2949,9 @@ var EditorConfig = class {
|
|
|
2948
2949
|
return Array.from(set);
|
|
2949
2950
|
}
|
|
2950
2951
|
};
|
|
2951
|
-
var isImageFile = (file) => {
|
|
2952
|
-
|
|
2952
|
+
var isImageFile = (file, maxSize) => {
|
|
2953
|
+
const limit = maxSize !== void 0 ? maxSize : MAX_FILE_SIZE;
|
|
2954
|
+
if (file.size === 0 || file.size > limit) {
|
|
2953
2955
|
return false;
|
|
2954
2956
|
}
|
|
2955
2957
|
const fileName = file.name?.toLowerCase() || "";
|
|
@@ -2958,8 +2960,9 @@ var isImageFile = (file) => {
|
|
|
2958
2960
|
}
|
|
2959
2961
|
return file.type?.startsWith("image/") || !file.type && /\.(png|jpe?g|gif|webp|bmp)$/i.test(fileName);
|
|
2960
2962
|
};
|
|
2961
|
-
var isVideoFile = (file) => {
|
|
2962
|
-
const
|
|
2963
|
+
var isVideoFile = (file, maxSize) => {
|
|
2964
|
+
const limit = maxSize !== void 0 ? maxSize : MAX_VIDEO_FILE_SIZE;
|
|
2965
|
+
const sizeOk = file.size > 0 && file.size <= limit;
|
|
2963
2966
|
const fileName = file.name?.toLowerCase() || "";
|
|
2964
2967
|
const mimeMatch = ALLOWED_VIDEO_MIME_TYPES.has(file.type);
|
|
2965
2968
|
const videoPrefix = typeof file.type === "string" && file.type.startsWith("video/");
|
|
@@ -3100,6 +3103,8 @@ function LumirEditor({
|
|
|
3100
3103
|
allowVideoUpload = false,
|
|
3101
3104
|
allowAudioUpload = false,
|
|
3102
3105
|
allowFileUpload = false,
|
|
3106
|
+
maxImageFileSize,
|
|
3107
|
+
maxVideoFileSize,
|
|
3103
3108
|
// link preview
|
|
3104
3109
|
linkPreview,
|
|
3105
3110
|
// view options
|
|
@@ -3214,8 +3219,8 @@ function LumirEditor({
|
|
|
3214
3219
|
tabBehavior,
|
|
3215
3220
|
trailingBlock,
|
|
3216
3221
|
uploadFile: async (file) => {
|
|
3217
|
-
const allowedImage = isImageFile(file);
|
|
3218
|
-
const allowedVideo = allowVideoUpload && isVideoFile(file);
|
|
3222
|
+
const allowedImage = isImageFile(file, maxImageFileSize);
|
|
3223
|
+
const allowedVideo = allowVideoUpload && isVideoFile(file, maxVideoFileSize);
|
|
3219
3224
|
DEBUG_LOG("uploadFile:step1:entry", "editor uploadFile callback invoked", {
|
|
3220
3225
|
fileName: file.name,
|
|
3221
3226
|
fileType: file.type,
|
|
@@ -3309,7 +3314,7 @@ function LumirEditor({
|
|
|
3309
3314
|
const fileList = event?.clipboardData?.files ?? null;
|
|
3310
3315
|
const files = fileList ? Array.from(fileList) : [];
|
|
3311
3316
|
const acceptedFiles = files.filter(
|
|
3312
|
-
(f) => isImageFile(f) || allowVideoUpload && isVideoFile(f)
|
|
3317
|
+
(f) => isImageFile(f, maxImageFileSize) || allowVideoUpload && isVideoFile(f, maxVideoFileSize)
|
|
3313
3318
|
);
|
|
3314
3319
|
DEBUG_LOG("paste:step1:files", "paste clipboard files", {
|
|
3315
3320
|
filesCount: files.length,
|
|
@@ -3335,11 +3340,11 @@ function LumirEditor({
|
|
|
3335
3340
|
fileType: file.type
|
|
3336
3341
|
});
|
|
3337
3342
|
const url = await editor2.uploadFile(file);
|
|
3338
|
-
if (isImageFile(file)) {
|
|
3343
|
+
if (isImageFile(file, maxImageFileSize)) {
|
|
3339
3344
|
editor2.pasteHTML(
|
|
3340
3345
|
`<img src="${escapeHtml(url)}" alt="image" />`
|
|
3341
3346
|
);
|
|
3342
|
-
} else if (isVideoFile(file)) {
|
|
3347
|
+
} else if (isVideoFile(file, maxVideoFileSize)) {
|
|
3343
3348
|
const currentBlock = editor2.getTextCursorPosition().block;
|
|
3344
3349
|
editor2.insertBlocks(
|
|
3345
3350
|
[{ type: "video", props: { url } }],
|
|
@@ -3432,8 +3437,8 @@ function LumirEditor({
|
|
|
3432
3437
|
e.stopPropagation();
|
|
3433
3438
|
const items = Array.from(e.dataTransfer.items ?? []);
|
|
3434
3439
|
const files = items.filter((it) => it.kind === "file").map((it) => it.getAsFile()).filter((f) => !!f);
|
|
3435
|
-
const imageFiles = files.filter(isImageFile);
|
|
3436
|
-
const videoFiles = allowVideoUpload ? files.filter(isVideoFile) : [];
|
|
3440
|
+
const imageFiles = files.filter((f) => isImageFile(f, maxImageFileSize));
|
|
3441
|
+
const videoFiles = allowVideoUpload ? files.filter((f) => isVideoFile(f, maxVideoFileSize)) : [];
|
|
3437
3442
|
const htmlFiles = files.filter(isHtmlFile);
|
|
3438
3443
|
DEBUG_LOG("drop:step1:files", "drop received", {
|
|
3439
3444
|
filesCount: files.length,
|
|
@@ -3445,8 +3450,8 @@ function LumirEditor({
|
|
|
3445
3450
|
name: files[0].name,
|
|
3446
3451
|
type: files[0].type,
|
|
3447
3452
|
size: files[0].size,
|
|
3448
|
-
isImage: isImageFile(files[0]),
|
|
3449
|
-
isVideo: isVideoFile(files[0])
|
|
3453
|
+
isImage: isImageFile(files[0], maxImageFileSize),
|
|
3454
|
+
isVideo: isVideoFile(files[0], maxVideoFileSize)
|
|
3450
3455
|
} : null
|
|
3451
3456
|
});
|
|
3452
3457
|
if (imageFiles.length === 0 && htmlFiles.length === 0 && videoFiles.length === 0)
|
|
@@ -3581,8 +3586,8 @@ function LumirEditor({
|
|
|
3581
3586
|
});
|
|
3582
3587
|
const blockToInsertAfter = floatingMenuBlockRef.current;
|
|
3583
3588
|
if (file && editor.uploadFile && blockToInsertAfter) {
|
|
3584
|
-
const allowedImage = isImageFile(file);
|
|
3585
|
-
const allowedVideo = allowVideoUpload && isVideoFile(file);
|
|
3589
|
+
const allowedImage = isImageFile(file, maxImageFileSize);
|
|
3590
|
+
const allowedVideo = allowVideoUpload && isVideoFile(file, maxVideoFileSize);
|
|
3586
3591
|
DEBUG_LOG("FloatingMenu:step4:fileCheck", "allowed check", {
|
|
3587
3592
|
fileName: file.name,
|
|
3588
3593
|
allowedImage,
|