@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 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` | `"light" \| "dark"` | `"light"` | 테마 |
1053
- | `className` | `string` | `""` | CSS 클래스 |
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
- - Redme update (video & image upload)
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
- }).catch(() => {
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
- if (file.size === 0 || file.size > MAX_FILE_SIZE) {
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 sizeOk = file.size > 0 && file.size <= MAX_VIDEO_FILE_SIZE;
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,