@lumir-company/editor 0.2.1 → 0.4.0

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/dist/index.d.mts CHANGED
@@ -18,7 +18,15 @@ interface LumirEditorProps {
18
18
  initialEmptyBlocks?: number;
19
19
  placeholder?: string;
20
20
  uploadFile?: (file: File) => Promise<string>;
21
- storeImagesAsBase64?: boolean;
21
+ s3Upload?: {
22
+ apiEndpoint: string;
23
+ env: "development" | "production";
24
+ path: string;
25
+ /** 파일명 변환 콜백 - 업로드 전 파일명을 변경할 수 있습니다 */
26
+ fileNameTransform?: (originalName: string, file: File) => string;
27
+ /** true일 경우 파일명 뒤에 UUID를 자동으로 추가합니다 (예: image_abc123.png) */
28
+ appendUUID?: boolean;
29
+ };
22
30
  allowVideoUpload?: boolean;
23
31
  allowAudioUpload?: boolean;
24
32
  allowFileUpload?: boolean;
@@ -31,21 +39,18 @@ interface LumirEditorProps {
31
39
  heading?: {
32
40
  levels?: (1 | 2 | 3 | 4 | 5 | 6)[];
33
41
  };
34
- animations?: boolean;
35
42
  defaultStyles?: boolean;
36
43
  disableExtensions?: string[];
37
- tabBehavior?: 'prefer-navigate-ui' | 'prefer-indent';
44
+ tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
38
45
  trailingBlock?: boolean;
39
- resolveFileUrl?: (url: string) => Promise<string>;
40
46
  editable?: boolean;
41
- theme?: 'light' | 'dark' | Partial<Record<string, unknown>> | {
47
+ theme?: "light" | "dark" | Partial<Record<string, unknown>> | {
42
48
  light: Partial<Record<string, unknown>>;
43
49
  dark: Partial<Record<string, unknown>>;
44
50
  };
45
51
  formattingToolbar?: boolean;
46
52
  linkToolbar?: boolean;
47
53
  sideMenu?: boolean;
48
- slashMenu?: boolean;
49
54
  emojiPicker?: boolean;
50
55
  filePanel?: boolean;
51
56
  tableHandles?: boolean;
@@ -101,7 +106,7 @@ declare class EditorConfig {
101
106
  * @param userTables 사용자 테이블 설정
102
107
  * @returns 기본값이 적용된 테이블 설정
103
108
  */
104
- static getDefaultTableConfig(userTables?: LumirEditorProps['tables']): {
109
+ static getDefaultTableConfig(userTables?: LumirEditorProps["tables"]): {
105
110
  splitCells: boolean;
106
111
  cellBackgroundColor: boolean;
107
112
  cellTextColor: boolean;
@@ -112,7 +117,7 @@ declare class EditorConfig {
112
117
  * @param userHeading 사용자 헤딩 설정
113
118
  * @returns 기본값이 적용된 헤딩 설정
114
119
  */
115
- static getDefaultHeadingConfig(userHeading?: LumirEditorProps['heading']): {
120
+ static getDefaultHeadingConfig(userHeading?: LumirEditorProps["heading"]): {
116
121
  levels?: (1 | 2 | 3 | 4 | 5 | 6)[];
117
122
  };
118
123
  /**
@@ -125,8 +130,19 @@ declare class EditorConfig {
125
130
  */
126
131
  static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean, allowFile?: boolean): string[];
127
132
  }
128
- declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, tables, heading, animations, defaultStyles, disableExtensions, tabBehavior, trailingBlock, resolveFileUrl, storeImagesAsBase64, allowVideoUpload, allowAudioUpload, allowFileUpload, editable, theme, formattingToolbar, linkToolbar, sideMenu, slashMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, sideMenuAddButton, onContentChange, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
133
+ declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, sideMenuAddButton, onContentChange, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
129
134
 
130
135
  declare function cn(...inputs: (string | undefined | null | false)[]): string;
131
136
 
132
- export { ContentUtils, type DefaultPartialBlock, EditorConfig, type EditorType, LumirEditor, type LumirEditorProps, cn };
137
+ interface S3UploaderConfig {
138
+ apiEndpoint: string;
139
+ env: "production" | "development";
140
+ path: string;
141
+ /** 파일명 변환 콜백 - 업로드 전 파일명을 변경할 수 있습니다 */
142
+ fileNameTransform?: (originalName: string, file: File) => string;
143
+ /** true일 경우 파일명 뒤에 UUID를 자동으로 추가합니다 (예: image_abc123.png) */
144
+ appendUUID?: boolean;
145
+ }
146
+ declare const createS3Uploader: (config: S3UploaderConfig) => (file: File) => Promise<string>;
147
+
148
+ export { ContentUtils, type DefaultPartialBlock, EditorConfig, type EditorType, LumirEditor, type LumirEditorProps, type S3UploaderConfig, cn, createS3Uploader };
package/dist/index.d.ts CHANGED
@@ -18,7 +18,15 @@ interface LumirEditorProps {
18
18
  initialEmptyBlocks?: number;
19
19
  placeholder?: string;
20
20
  uploadFile?: (file: File) => Promise<string>;
21
- storeImagesAsBase64?: boolean;
21
+ s3Upload?: {
22
+ apiEndpoint: string;
23
+ env: "development" | "production";
24
+ path: string;
25
+ /** 파일명 변환 콜백 - 업로드 전 파일명을 변경할 수 있습니다 */
26
+ fileNameTransform?: (originalName: string, file: File) => string;
27
+ /** true일 경우 파일명 뒤에 UUID를 자동으로 추가합니다 (예: image_abc123.png) */
28
+ appendUUID?: boolean;
29
+ };
22
30
  allowVideoUpload?: boolean;
23
31
  allowAudioUpload?: boolean;
24
32
  allowFileUpload?: boolean;
@@ -31,21 +39,18 @@ interface LumirEditorProps {
31
39
  heading?: {
32
40
  levels?: (1 | 2 | 3 | 4 | 5 | 6)[];
33
41
  };
34
- animations?: boolean;
35
42
  defaultStyles?: boolean;
36
43
  disableExtensions?: string[];
37
- tabBehavior?: 'prefer-navigate-ui' | 'prefer-indent';
44
+ tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
38
45
  trailingBlock?: boolean;
39
- resolveFileUrl?: (url: string) => Promise<string>;
40
46
  editable?: boolean;
41
- theme?: 'light' | 'dark' | Partial<Record<string, unknown>> | {
47
+ theme?: "light" | "dark" | Partial<Record<string, unknown>> | {
42
48
  light: Partial<Record<string, unknown>>;
43
49
  dark: Partial<Record<string, unknown>>;
44
50
  };
45
51
  formattingToolbar?: boolean;
46
52
  linkToolbar?: boolean;
47
53
  sideMenu?: boolean;
48
- slashMenu?: boolean;
49
54
  emojiPicker?: boolean;
50
55
  filePanel?: boolean;
51
56
  tableHandles?: boolean;
@@ -101,7 +106,7 @@ declare class EditorConfig {
101
106
  * @param userTables 사용자 테이블 설정
102
107
  * @returns 기본값이 적용된 테이블 설정
103
108
  */
104
- static getDefaultTableConfig(userTables?: LumirEditorProps['tables']): {
109
+ static getDefaultTableConfig(userTables?: LumirEditorProps["tables"]): {
105
110
  splitCells: boolean;
106
111
  cellBackgroundColor: boolean;
107
112
  cellTextColor: boolean;
@@ -112,7 +117,7 @@ declare class EditorConfig {
112
117
  * @param userHeading 사용자 헤딩 설정
113
118
  * @returns 기본값이 적용된 헤딩 설정
114
119
  */
115
- static getDefaultHeadingConfig(userHeading?: LumirEditorProps['heading']): {
120
+ static getDefaultHeadingConfig(userHeading?: LumirEditorProps["heading"]): {
116
121
  levels?: (1 | 2 | 3 | 4 | 5 | 6)[];
117
122
  };
118
123
  /**
@@ -125,8 +130,19 @@ declare class EditorConfig {
125
130
  */
126
131
  static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean, allowFile?: boolean): string[];
127
132
  }
128
- declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, tables, heading, animations, defaultStyles, disableExtensions, tabBehavior, trailingBlock, resolveFileUrl, storeImagesAsBase64, allowVideoUpload, allowAudioUpload, allowFileUpload, editable, theme, formattingToolbar, linkToolbar, sideMenu, slashMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, sideMenuAddButton, onContentChange, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
133
+ declare function LumirEditor({ initialContent, initialEmptyBlocks, uploadFile, s3Upload, tables, heading, defaultStyles, disableExtensions, tabBehavior, trailingBlock, allowVideoUpload, allowAudioUpload, allowFileUpload, editable, theme, formattingToolbar, linkToolbar, sideMenu, emojiPicker, filePanel, tableHandles, onSelectionChange, className, sideMenuAddButton, onContentChange, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
129
134
 
130
135
  declare function cn(...inputs: (string | undefined | null | false)[]): string;
131
136
 
132
- export { ContentUtils, type DefaultPartialBlock, EditorConfig, type EditorType, LumirEditor, type LumirEditorProps, cn };
137
+ interface S3UploaderConfig {
138
+ apiEndpoint: string;
139
+ env: "production" | "development";
140
+ path: string;
141
+ /** 파일명 변환 콜백 - 업로드 전 파일명을 변경할 수 있습니다 */
142
+ fileNameTransform?: (originalName: string, file: File) => string;
143
+ /** true일 경우 파일명 뒤에 UUID를 자동으로 추가합니다 (예: image_abc123.png) */
144
+ appendUUID?: boolean;
145
+ }
146
+ declare const createS3Uploader: (config: S3UploaderConfig) => (file: File) => Promise<string>;
147
+
148
+ export { ContentUtils, type DefaultPartialBlock, EditorConfig, type EditorType, LumirEditor, type LumirEditorProps, type S3UploaderConfig, cn, createS3Uploader };
package/dist/index.js CHANGED
@@ -24,7 +24,8 @@ __export(index_exports, {
24
24
  ContentUtils: () => ContentUtils,
25
25
  EditorConfig: () => EditorConfig,
26
26
  LumirEditor: () => LumirEditor,
27
- cn: () => cn
27
+ cn: () => cn,
28
+ createS3Uploader: () => createS3Uploader
28
29
  });
29
30
  module.exports = __toCommonJS(index_exports);
30
31
 
@@ -38,6 +39,86 @@ function cn(...inputs) {
38
39
  return inputs.filter(Boolean).join(" ");
39
40
  }
40
41
 
42
+ // src/utils/s3-uploader.ts
43
+ var generateUUID = () => {
44
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
45
+ return crypto.randomUUID();
46
+ }
47
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
48
+ const r = Math.random() * 16 | 0;
49
+ const v = c === "x" ? r : r & 3 | 8;
50
+ return v.toString(16);
51
+ });
52
+ };
53
+ var createS3Uploader = (config) => {
54
+ const { apiEndpoint, env, path, fileNameTransform, appendUUID } = config;
55
+ if (!apiEndpoint || apiEndpoint.trim() === "") {
56
+ throw new Error(
57
+ "apiEndpoint is required for S3 upload. Please provide a valid API endpoint."
58
+ );
59
+ }
60
+ if (!env) {
61
+ throw new Error("env is required. Must be 'development' or 'production'.");
62
+ }
63
+ if (!path || path.trim() === "") {
64
+ throw new Error("path is required and cannot be empty.");
65
+ }
66
+ const appendUUIDToFileName = (filename) => {
67
+ const lastDotIndex = filename.lastIndexOf(".");
68
+ if (lastDotIndex === -1) {
69
+ return `${filename}_${generateUUID()}`;
70
+ }
71
+ const name = filename.substring(0, lastDotIndex);
72
+ const ext = filename.substring(lastDotIndex);
73
+ return `${name}_${generateUUID()}${ext}`;
74
+ };
75
+ const generateHierarchicalFileName = (file) => {
76
+ let filename = file.name;
77
+ if (fileNameTransform) {
78
+ filename = fileNameTransform(filename, file);
79
+ }
80
+ if (appendUUID) {
81
+ filename = appendUUIDToFileName(filename);
82
+ }
83
+ return `${env}/${path}/${filename}`;
84
+ };
85
+ return async (file) => {
86
+ try {
87
+ if (!apiEndpoint || apiEndpoint.trim() === "") {
88
+ throw new Error(
89
+ "Invalid apiEndpoint: Cannot upload file without a valid API ENDPOINT"
90
+ );
91
+ }
92
+ const fileName = generateHierarchicalFileName(file);
93
+ const response = await fetch(
94
+ `${apiEndpoint}?key=${encodeURIComponent(fileName)}`
95
+ );
96
+ if (!response.ok) {
97
+ const errorText = await response.text() || "";
98
+ throw new Error(
99
+ `Failed to get presigned URL: ${response.statusText}, ${errorText}`
100
+ );
101
+ }
102
+ const responseData = await response.json();
103
+ const { presignedUrl, publicUrl } = responseData;
104
+ const uploadResponse = await fetch(presignedUrl, {
105
+ method: "PUT",
106
+ headers: {
107
+ "Content-Type": file.type || "application/octet-stream"
108
+ },
109
+ body: file
110
+ });
111
+ if (!uploadResponse.ok) {
112
+ throw new Error(`Failed to upload file: ${uploadResponse.statusText}`);
113
+ }
114
+ return publicUrl;
115
+ } catch (error) {
116
+ console.error("S3 upload failed:", error);
117
+ throw error;
118
+ }
119
+ };
120
+ };
121
+
41
122
  // src/components/LumirEditor.tsx
42
123
  var import_jsx_runtime = require("react/jsx-runtime");
43
124
  var ContentUtils = class {
@@ -158,32 +239,21 @@ var EditorConfig = class {
158
239
  return Array.from(set);
159
240
  }
160
241
  };
161
- var createObjectUrlUploader = async (file) => {
162
- return URL.createObjectURL(file);
163
- };
164
242
  var isImageFile = (file) => {
165
243
  return file.size > 0 && (file.type?.startsWith("image/") || !file.type && /\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(file.name || ""));
166
244
  };
167
- var fileToBase64 = async (file) => await new Promise((resolve, reject) => {
168
- const reader = new FileReader();
169
- reader.onload = () => resolve(String(reader.result));
170
- reader.onerror = () => reject(new Error("FileReader failed"));
171
- reader.readAsDataURL(file);
172
- });
173
245
  function LumirEditor({
174
246
  // editor options
175
247
  initialContent,
176
248
  initialEmptyBlocks = 3,
177
249
  uploadFile,
250
+ s3Upload,
178
251
  tables,
179
252
  heading,
180
- animations = true,
181
253
  defaultStyles = true,
182
254
  disableExtensions,
183
255
  tabBehavior = "prefer-navigate-ui",
184
256
  trailingBlock = true,
185
- resolveFileUrl,
186
- storeImagesAsBase64 = true,
187
257
  allowVideoUpload = false,
188
258
  allowAudioUpload = false,
189
259
  allowFileUpload = false,
@@ -193,7 +263,6 @@ function LumirEditor({
193
263
  formattingToolbar = true,
194
264
  linkToolbar = true,
195
265
  sideMenu = true,
196
- slashMenu = true,
197
266
  emojiPicker = true,
198
267
  filePanel = true,
199
268
  tableHandles = true,
@@ -203,6 +272,7 @@ function LumirEditor({
203
272
  // callbacks / refs
204
273
  onContentChange
205
274
  }) {
275
+ const [isUploading, setIsUploading] = (0, import_react.useState)(false);
206
276
  const validatedContent = (0, import_react.useMemo)(() => {
207
277
  return ContentUtils.validateContent(initialContent, initialEmptyBlocks);
208
278
  }, [initialContent, initialEmptyBlocks]);
@@ -225,33 +295,60 @@ function LumirEditor({
225
295
  allowFileUpload
226
296
  );
227
297
  }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);
298
+ const fileNameTransformRef = (0, import_react.useRef)(s3Upload?.fileNameTransform);
299
+ (0, import_react.useEffect)(() => {
300
+ fileNameTransformRef.current = s3Upload?.fileNameTransform;
301
+ }, [s3Upload?.fileNameTransform]);
302
+ const memoizedS3Upload = (0, import_react.useMemo)(() => {
303
+ if (!s3Upload) return void 0;
304
+ return {
305
+ apiEndpoint: s3Upload.apiEndpoint,
306
+ env: s3Upload.env,
307
+ path: s3Upload.path,
308
+ appendUUID: s3Upload.appendUUID,
309
+ // 최신 콜백을 항상 사용하도록 ref를 통해 접근
310
+ fileNameTransform: (originalName, file) => {
311
+ return fileNameTransformRef.current ? fileNameTransformRef.current(originalName, file) : originalName;
312
+ }
313
+ };
314
+ }, [
315
+ s3Upload?.apiEndpoint,
316
+ s3Upload?.env,
317
+ s3Upload?.path,
318
+ s3Upload?.appendUUID
319
+ ]);
228
320
  const editor = (0, import_react2.useCreateBlockNote)(
229
321
  {
230
322
  initialContent: validatedContent,
231
323
  tables: tableConfig,
232
324
  heading: headingConfig,
233
- animations,
325
+ animations: false,
326
+ // 기본적으로 애니메이션 비활성화
234
327
  defaultStyles,
235
328
  // 확장 비활성: 비디오/오디오/파일 제어
236
329
  disableExtensions: disabledExtensions,
237
330
  tabBehavior,
238
331
  trailingBlock,
239
- resolveFileUrl,
240
332
  uploadFile: async (file) => {
241
333
  if (!isImageFile(file)) {
242
334
  throw new Error("Only image files are allowed");
243
335
  }
244
- const custom = uploadFile;
245
- const fallback = storeImagesAsBase64 ? fileToBase64 : createObjectUrlUploader;
246
336
  try {
247
- if (custom) return await custom(file);
248
- return await fallback(file);
249
- } catch (_) {
250
- try {
251
- return await createObjectUrlUploader(file);
252
- } catch {
253
- throw new Error("Failed to process file for upload");
337
+ let imageUrl;
338
+ if (uploadFile) {
339
+ imageUrl = await uploadFile(file);
340
+ } else if (memoizedS3Upload?.apiEndpoint) {
341
+ const s3Uploader = createS3Uploader(memoizedS3Upload);
342
+ imageUrl = await s3Uploader(file);
343
+ } else {
344
+ throw new Error("No upload method available");
254
345
  }
346
+ return imageUrl;
347
+ } catch (error) {
348
+ console.error("Image upload failed:", error);
349
+ throw new Error(
350
+ "Upload failed: " + (error instanceof Error ? error.message : String(error))
351
+ );
255
352
  }
256
353
  },
257
354
  pasteHandler: (ctx) => {
@@ -268,17 +365,22 @@ function LumirEditor({
268
365
  }
269
366
  event.preventDefault();
270
367
  (async () => {
271
- for (const file of acceptedFiles) {
272
- try {
273
- const url = await editor2.uploadFile(file);
274
- editor2.pasteHTML(`<img src="${url}" alt="image" />`);
275
- } catch (err) {
276
- console.warn(
277
- "Image upload failed, skipped:",
278
- file.name || "",
279
- err
280
- );
368
+ setIsUploading(true);
369
+ try {
370
+ for (const file of acceptedFiles) {
371
+ try {
372
+ const url = await editor2.uploadFile(file);
373
+ editor2.pasteHTML(`<img src="${url}" alt="image" />`);
374
+ } catch (err) {
375
+ console.warn(
376
+ "Image upload failed, skipped:",
377
+ file.name || "",
378
+ err
379
+ );
380
+ }
281
381
  }
382
+ } finally {
383
+ setIsUploading(false);
282
384
  }
283
385
  })();
284
386
  return true;
@@ -288,14 +390,12 @@ function LumirEditor({
288
390
  validatedContent,
289
391
  tableConfig,
290
392
  headingConfig,
291
- animations,
292
393
  defaultStyles,
293
394
  disabledExtensions,
294
395
  tabBehavior,
295
396
  trailingBlock,
296
- resolveFileUrl,
297
397
  uploadFile,
298
- storeImagesAsBase64
398
+ memoizedS3Upload
299
399
  ]
300
400
  );
301
401
  (0, import_react.useEffect)(() => {
@@ -333,17 +433,26 @@ function LumirEditor({
333
433
  const acceptedFiles = files.filter(isImageFile);
334
434
  if (acceptedFiles.length === 0) return;
335
435
  (async () => {
336
- for (const file of acceptedFiles) {
337
- try {
338
- if (editor?.uploadFile) {
339
- const url = await editor.uploadFile(file);
340
- if (url) {
341
- editor.pasteHTML(`<img src="${url}" alt="image" />`);
436
+ setIsUploading(true);
437
+ try {
438
+ for (const file of acceptedFiles) {
439
+ try {
440
+ if (editor?.uploadFile) {
441
+ const url = await editor.uploadFile(file);
442
+ if (url) {
443
+ editor.pasteHTML(`<img src="${url}" alt="image" />`);
444
+ }
342
445
  }
446
+ } catch (err) {
447
+ console.warn(
448
+ "Image upload failed, skipped:",
449
+ file.name || "",
450
+ err
451
+ );
343
452
  }
344
- } catch (err) {
345
- console.warn("Image upload failed, skipped:", file.name || "", err);
346
453
  }
454
+ } finally {
455
+ setIsUploading(false);
347
456
  }
348
457
  })();
349
458
  };
@@ -362,58 +471,69 @@ function LumirEditor({
362
471
  const DragHandleOnlySideMenu = (0, import_react.useMemo)(() => {
363
472
  return (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.SideMenu, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.DragHandleButton, { ...props }) });
364
473
  }, []);
365
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("lumirEditor", className), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
366
- import_mantine.BlockNoteView,
474
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
475
+ "div",
367
476
  {
368
- editor,
369
- editable,
370
- theme,
371
- formattingToolbar,
372
- linkToolbar,
373
- sideMenu: computedSideMenu,
374
- slashMenu: false,
375
- emojiPicker,
376
- filePanel,
377
- tableHandles,
378
- onSelectionChange,
477
+ className: cn("lumirEditor", className),
478
+ style: { position: "relative" },
379
479
  children: [
380
- slashMenu && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
381
- import_react2.SuggestionMenuController,
480
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
481
+ import_mantine.BlockNoteView,
382
482
  {
383
- triggerCharacter: "/",
384
- getItems: (0, import_react.useCallback)(
385
- async (query) => {
386
- const items = (0, import_react2.getDefaultReactSlashMenuItems)(editor);
387
- const filtered = items.filter((item) => {
388
- const key = (item?.key || "").toString().toLowerCase();
389
- const title = (item?.title || "").toString().toLowerCase();
390
- if (["video", "audio", "file"].includes(key)) return false;
391
- if (title.includes("video") || title.includes("audio") || title.includes("file"))
392
- return false;
393
- return true;
394
- });
395
- if (!query) return filtered;
396
- const q = query.toLowerCase();
397
- return filtered.filter(
398
- (item) => item.title?.toLowerCase().includes(q) || (item.aliases || []).some(
399
- (a) => a.toLowerCase().includes(q)
483
+ editor,
484
+ editable,
485
+ theme,
486
+ formattingToolbar,
487
+ linkToolbar,
488
+ sideMenu: computedSideMenu,
489
+ slashMenu: false,
490
+ emojiPicker,
491
+ filePanel,
492
+ tableHandles,
493
+ onSelectionChange,
494
+ children: [
495
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
496
+ import_react2.SuggestionMenuController,
497
+ {
498
+ triggerCharacter: "/",
499
+ getItems: (0, import_react.useCallback)(
500
+ async (query) => {
501
+ const items = (0, import_react2.getDefaultReactSlashMenuItems)(editor);
502
+ const filtered = items.filter((item) => {
503
+ const key = (item?.key || "").toString().toLowerCase();
504
+ const title = (item?.title || "").toString().toLowerCase();
505
+ if (["video", "audio", "file"].includes(key)) return false;
506
+ if (title.includes("video") || title.includes("audio") || title.includes("file"))
507
+ return false;
508
+ return true;
509
+ });
510
+ if (!query) return filtered;
511
+ const q = query.toLowerCase();
512
+ return filtered.filter(
513
+ (item) => item.title?.toLowerCase().includes(q) || (item.aliases || []).some(
514
+ (a) => a.toLowerCase().includes(q)
515
+ )
516
+ );
517
+ },
518
+ [editor]
400
519
  )
401
- );
402
- },
403
- [editor]
404
- )
520
+ }
521
+ ),
522
+ !sideMenuAddButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.SideMenuController, { sideMenu: DragHandleOnlySideMenu })
523
+ ]
405
524
  }
406
525
  ),
407
- !sideMenuAddButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.SideMenuController, { sideMenu: DragHandleOnlySideMenu })
526
+ isUploading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lumirEditor-upload-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lumirEditor-spinner" }) })
408
527
  ]
409
528
  }
410
- ) });
529
+ );
411
530
  }
412
531
  // Annotate the CommonJS export names for ESM import in node:
413
532
  0 && (module.exports = {
414
533
  ContentUtils,
415
534
  EditorConfig,
416
535
  LumirEditor,
417
- cn
536
+ cn,
537
+ createS3Uploader
418
538
  });
419
539
  //# sourceMappingURL=index.js.map