@lumir-company/editor 0.2.0 → 0.2.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/dist/index.d.mts CHANGED
@@ -22,13 +22,6 @@ interface LumirEditorProps {
22
22
  allowVideoUpload?: boolean;
23
23
  allowAudioUpload?: boolean;
24
24
  allowFileUpload?: boolean;
25
- pasteHandler?: (ctx: {
26
- event: ClipboardEvent;
27
- editor: EditorType;
28
- defaultPasteHandler: (context?: {
29
- pasteBehavior?: "prefer-markdown" | "prefer-html";
30
- }) => boolean | undefined;
31
- }) => boolean | undefined;
32
25
  tables?: {
33
26
  splitCells?: boolean;
34
27
  cellBackgroundColor?: boolean;
@@ -41,12 +34,11 @@ interface LumirEditorProps {
41
34
  animations?: boolean;
42
35
  defaultStyles?: boolean;
43
36
  disableExtensions?: string[];
44
- domAttributes?: Record<string, string>;
45
- tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
37
+ tabBehavior?: 'prefer-navigate-ui' | 'prefer-indent';
46
38
  trailingBlock?: boolean;
47
39
  resolveFileUrl?: (url: string) => Promise<string>;
48
40
  editable?: boolean;
49
- theme?: "light" | "dark" | Partial<Record<string, unknown>> | {
41
+ theme?: 'light' | 'dark' | Partial<Record<string, unknown>> | {
50
42
  light: Partial<Record<string, unknown>>;
51
43
  dark: Partial<Record<string, unknown>>;
52
44
  };
@@ -57,13 +49,10 @@ interface LumirEditorProps {
57
49
  emojiPicker?: boolean;
58
50
  filePanel?: boolean;
59
51
  tableHandles?: boolean;
60
- comments?: boolean;
61
52
  onSelectionChange?: () => void;
62
53
  className?: string;
63
- includeDefaultStyles?: boolean;
64
54
  sideMenuAddButton?: boolean;
65
55
  onContentChange?: (content: DefaultPartialBlock[]) => void;
66
- editorRef?: React.MutableRefObject<EditorType | null>;
67
56
  }
68
57
 
69
58
  /**
@@ -92,14 +81,12 @@ declare class ContentUtils {
92
81
  * 콘텐츠 유효성 검증 및 기본값 설정
93
82
  * @param content 사용자 제공 콘텐츠 (객체 배열 또는 JSON 문자열)
94
83
  * @param emptyBlockCount 빈 블록 개수 (기본값: 3)
95
- * @param placeholder 첫 번째 블록의 placeholder 텍스트
96
84
  * @returns 검증된 콘텐츠 배열
97
85
  */
98
- static validateContent(content?: DefaultPartialBlock[] | string, emptyBlockCount?: number, placeholder?: string): DefaultPartialBlock[];
86
+ static validateContent(content?: DefaultPartialBlock[] | string, emptyBlockCount?: number): DefaultPartialBlock[];
99
87
  /**
100
88
  * 빈 블록들을 생성합니다
101
89
  * @param emptyBlockCount 생성할 블록 개수
102
- * @param placeholder 첫 번째 블록의 placeholder 텍스트
103
90
  * @returns 생성된 빈 블록 배열
104
91
  */
105
92
  private static createEmptyBlocks;
@@ -114,7 +101,7 @@ declare class EditorConfig {
114
101
  * @param userTables 사용자 테이블 설정
115
102
  * @returns 기본값이 적용된 테이블 설정
116
103
  */
117
- static getDefaultTableConfig(userTables?: LumirEditorProps["tables"]): {
104
+ static getDefaultTableConfig(userTables?: LumirEditorProps['tables']): {
118
105
  splitCells: boolean;
119
106
  cellBackgroundColor: boolean;
120
107
  cellTextColor: boolean;
@@ -125,7 +112,7 @@ declare class EditorConfig {
125
112
  * @param userHeading 사용자 헤딩 설정
126
113
  * @returns 기본값이 적용된 헤딩 설정
127
114
  */
128
- static getDefaultHeadingConfig(userHeading?: LumirEditorProps["heading"]): {
115
+ static getDefaultHeadingConfig(userHeading?: LumirEditorProps['heading']): {
129
116
  levels?: (1 | 2 | 3 | 4 | 5 | 6)[];
130
117
  };
131
118
  /**
@@ -133,11 +120,12 @@ declare class EditorConfig {
133
120
  * @param userExtensions 사용자 정의 비활성 확장
134
121
  * @param allowVideo 비디오 업로드 허용 여부
135
122
  * @param allowAudio 오디오 업로드 허용 여부
123
+ * @param allowFile 일반 파일 업로드 허용 여부
136
124
  * @returns 비활성화할 확장 기능 목록
137
125
  */
138
- static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean): string[];
126
+ static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean, allowFile?: boolean): string[];
139
127
  }
140
- declare function LumirEditor({ initialContent, initialEmptyBlocks, placeholder, uploadFile, pasteHandler, tables, heading, animations, defaultStyles, disableExtensions, domAttributes, tabBehavior, trailingBlock, resolveFileUrl, storeImagesAsBase64, allowVideoUpload, allowAudioUpload, allowFileUpload, editable, theme, formattingToolbar, linkToolbar, sideMenu, slashMenu, emojiPicker, filePanel, tableHandles, comments, onSelectionChange, className, includeDefaultStyles, sideMenuAddButton, onContentChange, editorRef, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
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;
141
129
 
142
130
  declare function cn(...inputs: (string | undefined | null | false)[]): string;
143
131
 
package/dist/index.d.ts CHANGED
@@ -22,13 +22,6 @@ interface LumirEditorProps {
22
22
  allowVideoUpload?: boolean;
23
23
  allowAudioUpload?: boolean;
24
24
  allowFileUpload?: boolean;
25
- pasteHandler?: (ctx: {
26
- event: ClipboardEvent;
27
- editor: EditorType;
28
- defaultPasteHandler: (context?: {
29
- pasteBehavior?: "prefer-markdown" | "prefer-html";
30
- }) => boolean | undefined;
31
- }) => boolean | undefined;
32
25
  tables?: {
33
26
  splitCells?: boolean;
34
27
  cellBackgroundColor?: boolean;
@@ -41,12 +34,11 @@ interface LumirEditorProps {
41
34
  animations?: boolean;
42
35
  defaultStyles?: boolean;
43
36
  disableExtensions?: string[];
44
- domAttributes?: Record<string, string>;
45
- tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
37
+ tabBehavior?: 'prefer-navigate-ui' | 'prefer-indent';
46
38
  trailingBlock?: boolean;
47
39
  resolveFileUrl?: (url: string) => Promise<string>;
48
40
  editable?: boolean;
49
- theme?: "light" | "dark" | Partial<Record<string, unknown>> | {
41
+ theme?: 'light' | 'dark' | Partial<Record<string, unknown>> | {
50
42
  light: Partial<Record<string, unknown>>;
51
43
  dark: Partial<Record<string, unknown>>;
52
44
  };
@@ -57,13 +49,10 @@ interface LumirEditorProps {
57
49
  emojiPicker?: boolean;
58
50
  filePanel?: boolean;
59
51
  tableHandles?: boolean;
60
- comments?: boolean;
61
52
  onSelectionChange?: () => void;
62
53
  className?: string;
63
- includeDefaultStyles?: boolean;
64
54
  sideMenuAddButton?: boolean;
65
55
  onContentChange?: (content: DefaultPartialBlock[]) => void;
66
- editorRef?: React.MutableRefObject<EditorType | null>;
67
56
  }
68
57
 
69
58
  /**
@@ -92,14 +81,12 @@ declare class ContentUtils {
92
81
  * 콘텐츠 유효성 검증 및 기본값 설정
93
82
  * @param content 사용자 제공 콘텐츠 (객체 배열 또는 JSON 문자열)
94
83
  * @param emptyBlockCount 빈 블록 개수 (기본값: 3)
95
- * @param placeholder 첫 번째 블록의 placeholder 텍스트
96
84
  * @returns 검증된 콘텐츠 배열
97
85
  */
98
- static validateContent(content?: DefaultPartialBlock[] | string, emptyBlockCount?: number, placeholder?: string): DefaultPartialBlock[];
86
+ static validateContent(content?: DefaultPartialBlock[] | string, emptyBlockCount?: number): DefaultPartialBlock[];
99
87
  /**
100
88
  * 빈 블록들을 생성합니다
101
89
  * @param emptyBlockCount 생성할 블록 개수
102
- * @param placeholder 첫 번째 블록의 placeholder 텍스트
103
90
  * @returns 생성된 빈 블록 배열
104
91
  */
105
92
  private static createEmptyBlocks;
@@ -114,7 +101,7 @@ declare class EditorConfig {
114
101
  * @param userTables 사용자 테이블 설정
115
102
  * @returns 기본값이 적용된 테이블 설정
116
103
  */
117
- static getDefaultTableConfig(userTables?: LumirEditorProps["tables"]): {
104
+ static getDefaultTableConfig(userTables?: LumirEditorProps['tables']): {
118
105
  splitCells: boolean;
119
106
  cellBackgroundColor: boolean;
120
107
  cellTextColor: boolean;
@@ -125,7 +112,7 @@ declare class EditorConfig {
125
112
  * @param userHeading 사용자 헤딩 설정
126
113
  * @returns 기본값이 적용된 헤딩 설정
127
114
  */
128
- static getDefaultHeadingConfig(userHeading?: LumirEditorProps["heading"]): {
115
+ static getDefaultHeadingConfig(userHeading?: LumirEditorProps['heading']): {
129
116
  levels?: (1 | 2 | 3 | 4 | 5 | 6)[];
130
117
  };
131
118
  /**
@@ -133,11 +120,12 @@ declare class EditorConfig {
133
120
  * @param userExtensions 사용자 정의 비활성 확장
134
121
  * @param allowVideo 비디오 업로드 허용 여부
135
122
  * @param allowAudio 오디오 업로드 허용 여부
123
+ * @param allowFile 일반 파일 업로드 허용 여부
136
124
  * @returns 비활성화할 확장 기능 목록
137
125
  */
138
- static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean): string[];
126
+ static getDisabledExtensions(userExtensions?: string[], allowVideo?: boolean, allowAudio?: boolean, allowFile?: boolean): string[];
139
127
  }
140
- declare function LumirEditor({ initialContent, initialEmptyBlocks, placeholder, uploadFile, pasteHandler, tables, heading, animations, defaultStyles, disableExtensions, domAttributes, tabBehavior, trailingBlock, resolveFileUrl, storeImagesAsBase64, allowVideoUpload, allowAudioUpload, allowFileUpload, editable, theme, formattingToolbar, linkToolbar, sideMenu, slashMenu, emojiPicker, filePanel, tableHandles, comments, onSelectionChange, className, includeDefaultStyles, sideMenuAddButton, onContentChange, editorRef, }: LumirEditorProps): react_jsx_runtime.JSX.Element;
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;
141
129
 
142
130
  declare function cn(...inputs: (string | undefined | null | false)[]): string;
143
131
 
package/dist/index.js CHANGED
@@ -90,39 +90,34 @@ var ContentUtils = class {
90
90
  * 콘텐츠 유효성 검증 및 기본값 설정
91
91
  * @param content 사용자 제공 콘텐츠 (객체 배열 또는 JSON 문자열)
92
92
  * @param emptyBlockCount 빈 블록 개수 (기본값: 3)
93
- * @param placeholder 첫 번째 블록의 placeholder 텍스트
94
93
  * @returns 검증된 콘텐츠 배열
95
94
  */
96
- static validateContent(content, emptyBlockCount = 3, placeholder) {
95
+ static validateContent(content, emptyBlockCount = 3) {
97
96
  if (typeof content === "string") {
98
97
  if (content.trim() === "") {
99
- return this.createEmptyBlocks(emptyBlockCount, placeholder);
98
+ return this.createEmptyBlocks(emptyBlockCount);
100
99
  }
101
100
  const parsedContent = this.parseJSONContent(content);
102
101
  if (parsedContent && parsedContent.length > 0) {
103
102
  return parsedContent;
104
103
  }
105
- return this.createEmptyBlocks(emptyBlockCount, placeholder);
104
+ return this.createEmptyBlocks(emptyBlockCount);
106
105
  }
107
106
  if (!content || content.length === 0) {
108
- return this.createEmptyBlocks(emptyBlockCount, placeholder);
107
+ return this.createEmptyBlocks(emptyBlockCount);
109
108
  }
110
109
  return content;
111
110
  }
112
111
  /**
113
112
  * 빈 블록들을 생성합니다
114
113
  * @param emptyBlockCount 생성할 블록 개수
115
- * @param placeholder 첫 번째 블록의 placeholder 텍스트
116
114
  * @returns 생성된 빈 블록 배열
117
115
  */
118
- static createEmptyBlocks(emptyBlockCount, placeholder) {
119
- return Array.from({ length: emptyBlockCount }, (_, index) => {
120
- const block = this.createDefaultBlock();
121
- if (index === 0 && placeholder) {
122
- block.content = [{ type: "text", text: placeholder, styles: {} }];
123
- }
124
- return block;
125
- });
116
+ static createEmptyBlocks(emptyBlockCount) {
117
+ return Array.from(
118
+ { length: emptyBlockCount },
119
+ () => this.createDefaultBlock()
120
+ );
126
121
  }
127
122
  };
128
123
  var EditorConfig = class {
@@ -152,18 +147,23 @@ var EditorConfig = class {
152
147
  * @param userExtensions 사용자 정의 비활성 확장
153
148
  * @param allowVideo 비디오 업로드 허용 여부
154
149
  * @param allowAudio 오디오 업로드 허용 여부
150
+ * @param allowFile 일반 파일 업로드 허용 여부
155
151
  * @returns 비활성화할 확장 기능 목록
156
152
  */
157
- static getDisabledExtensions(userExtensions, allowVideo = false, allowAudio = false) {
153
+ static getDisabledExtensions(userExtensions, allowVideo = false, allowAudio = false, allowFile = false) {
158
154
  const set = new Set(userExtensions ?? []);
159
155
  if (!allowVideo) set.add("video");
160
156
  if (!allowAudio) set.add("audio");
157
+ if (!allowFile) set.add("file");
161
158
  return Array.from(set);
162
159
  }
163
160
  };
164
161
  var createObjectUrlUploader = async (file) => {
165
162
  return URL.createObjectURL(file);
166
163
  };
164
+ var isImageFile = (file) => {
165
+ return file.size > 0 && (file.type?.startsWith("image/") || !file.type && /\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(file.name || ""));
166
+ };
167
167
  var fileToBase64 = async (file) => await new Promise((resolve, reject) => {
168
168
  const reader = new FileReader();
169
169
  reader.onload = () => resolve(String(reader.result));
@@ -174,15 +174,12 @@ function LumirEditor({
174
174
  // editor options
175
175
  initialContent,
176
176
  initialEmptyBlocks = 3,
177
- placeholder,
178
177
  uploadFile,
179
- pasteHandler,
180
178
  tables,
181
179
  heading,
182
180
  animations = true,
183
181
  defaultStyles = true,
184
182
  disableExtensions,
185
- domAttributes,
186
183
  tabBehavior = "prefer-navigate-ui",
187
184
  trailingBlock = true,
188
185
  resolveFileUrl,
@@ -200,42 +197,50 @@ function LumirEditor({
200
197
  emojiPicker = true,
201
198
  filePanel = true,
202
199
  tableHandles = true,
203
- comments = true,
204
200
  onSelectionChange,
205
201
  className = "",
206
- includeDefaultStyles = true,
207
- sideMenuAddButton = true,
202
+ sideMenuAddButton = false,
208
203
  // callbacks / refs
209
- onContentChange,
210
- editorRef
204
+ onContentChange
211
205
  }) {
212
206
  const validatedContent = (0, import_react.useMemo)(() => {
213
- return ContentUtils.validateContent(
214
- initialContent,
215
- initialEmptyBlocks,
216
- placeholder
207
+ return ContentUtils.validateContent(initialContent, initialEmptyBlocks);
208
+ }, [initialContent, initialEmptyBlocks]);
209
+ const tableConfig = (0, import_react.useMemo)(() => {
210
+ return EditorConfig.getDefaultTableConfig(tables);
211
+ }, [
212
+ tables?.splitCells,
213
+ tables?.cellBackgroundColor,
214
+ tables?.cellTextColor,
215
+ tables?.headers
216
+ ]);
217
+ const headingConfig = (0, import_react.useMemo)(() => {
218
+ return EditorConfig.getDefaultHeadingConfig(heading);
219
+ }, [heading?.levels?.join(",") ?? ""]);
220
+ const disabledExtensions = (0, import_react.useMemo)(() => {
221
+ return EditorConfig.getDisabledExtensions(
222
+ disableExtensions,
223
+ allowVideoUpload,
224
+ allowAudioUpload,
225
+ allowFileUpload
217
226
  );
218
- }, [initialContent, initialEmptyBlocks, placeholder]);
227
+ }, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);
219
228
  const editor = (0, import_react2.useCreateBlockNote)(
220
229
  {
221
230
  initialContent: validatedContent,
222
- tables: EditorConfig.getDefaultTableConfig(tables),
223
- heading: EditorConfig.getDefaultHeadingConfig(heading),
231
+ tables: tableConfig,
232
+ heading: headingConfig,
224
233
  animations,
225
234
  defaultStyles,
226
- // 확장 비활성: 비디오/오디오만 제어(파일 확장은 내부 드롭 로직 의존 → 비활성화하지 않음)
227
- disableExtensions: (0, import_react.useMemo)(() => {
228
- return EditorConfig.getDisabledExtensions(
229
- disableExtensions,
230
- allowVideoUpload,
231
- allowAudioUpload
232
- );
233
- }, [disableExtensions, allowVideoUpload, allowAudioUpload]),
234
- domAttributes,
235
+ // 확장 비활성: 비디오/오디오/파일 제어
236
+ disableExtensions: disabledExtensions,
235
237
  tabBehavior,
236
238
  trailingBlock,
237
239
  resolveFileUrl,
238
240
  uploadFile: async (file) => {
241
+ if (!isImageFile(file)) {
242
+ throw new Error("Only image files are allowed");
243
+ }
239
244
  const custom = uploadFile;
240
245
  const fallback = storeImagesAsBase64 ? fileToBase64 : createObjectUrlUploader;
241
246
  try {
@@ -253,20 +258,19 @@ function LumirEditor({
253
258
  const { event, editor: editor2, defaultPasteHandler } = ctx;
254
259
  const fileList = event?.clipboardData?.files ?? null;
255
260
  const files = fileList ? Array.from(fileList) : [];
256
- const accepted = files.filter(
257
- (f) => f.size > 0 && (f.type?.startsWith("image/") || !f.type && /\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(f.name || ""))
258
- );
259
- if (files.length > 0 && accepted.length === 0) {
261
+ const acceptedFiles = files.filter(isImageFile);
262
+ if (files.length > 0 && acceptedFiles.length === 0) {
260
263
  event.preventDefault();
261
264
  return true;
262
265
  }
263
- if (accepted.length === 0) return defaultPasteHandler() ?? false;
266
+ if (acceptedFiles.length === 0) {
267
+ return defaultPasteHandler() ?? false;
268
+ }
264
269
  event.preventDefault();
265
270
  (async () => {
266
- const doUpload = uploadFile ?? (storeImagesAsBase64 ? fileToBase64 : createObjectUrlUploader);
267
- for (const file of accepted) {
271
+ for (const file of acceptedFiles) {
268
272
  try {
269
- const url = await doUpload(file);
273
+ const url = await editor2.uploadFile(file);
270
274
  editor2.pasteHTML(`<img src="${url}" alt="image" />`);
271
275
  } catch (err) {
272
276
  console.warn(
@@ -274,7 +278,6 @@ function LumirEditor({
274
278
  file.name || "",
275
279
  err
276
280
  );
277
- continue;
278
281
  }
279
282
  }
280
283
  })();
@@ -282,58 +285,32 @@ function LumirEditor({
282
285
  }
283
286
  },
284
287
  [
285
- uploadFile,
286
- pasteHandler,
287
- storeImagesAsBase64,
288
- allowVideoUpload,
289
- allowAudioUpload,
290
- allowFileUpload,
291
- tables?.splitCells,
292
- tables?.cellBackgroundColor,
293
- tables?.cellTextColor,
294
- tables?.headers,
295
- heading?.levels?.join(","),
288
+ validatedContent,
289
+ tableConfig,
290
+ headingConfig,
296
291
  animations,
297
292
  defaultStyles,
298
- disableExtensions?.join(","),
299
- domAttributes ? JSON.stringify(domAttributes) : void 0,
293
+ disabledExtensions,
300
294
  tabBehavior,
301
295
  trailingBlock,
302
- resolveFileUrl
296
+ resolveFileUrl,
297
+ uploadFile,
298
+ storeImagesAsBase64
303
299
  ]
304
300
  );
305
301
  (0, import_react.useEffect)(() => {
306
- if (!editor) return;
307
- editor.isEditable = editable;
308
- const el = editor.domElement;
309
- if (!editable) {
310
- if (el) {
311
- el.style.userSelect = "text";
312
- el.style.webkitUserSelect = "text";
313
- }
302
+ if (editor) {
303
+ editor.isEditable = editable;
314
304
  }
315
305
  }, [editor, editable]);
316
306
  (0, import_react.useEffect)(() => {
317
307
  if (!editor || !onContentChange) return;
318
- let lastContent = "";
319
308
  const handleContentChange = () => {
320
- const topLevelBlocks = editor.topLevelBlocks;
321
- const currentContent = JSON.stringify(topLevelBlocks);
322
- if (lastContent === currentContent) return;
323
- lastContent = currentContent;
324
- onContentChange(topLevelBlocks);
325
- };
326
- editor.onEditorContentChange(handleContentChange);
327
- return () => {
309
+ const blocks = editor.topLevelBlocks;
310
+ onContentChange(blocks);
328
311
  };
312
+ return editor.onEditorContentChange(handleContentChange);
329
313
  }, [editor, onContentChange]);
330
- (0, import_react.useEffect)(() => {
331
- if (!editorRef) return;
332
- editorRef.current = editor ?? null;
333
- return () => {
334
- if (editorRef) editorRef.current = null;
335
- };
336
- }, [editor, editorRef]);
337
314
  (0, import_react.useEffect)(() => {
338
315
  const el = editor?.domElement;
339
316
  if (!el) return;
@@ -343,9 +320,6 @@ function LumirEditor({
343
320
  if (hasFiles) {
344
321
  e.preventDefault();
345
322
  e.stopPropagation();
346
- if (typeof e.stopImmediatePropagation === "function") {
347
- e.stopImmediatePropagation();
348
- }
349
323
  }
350
324
  };
351
325
  const handleDrop = (e) => {
@@ -354,22 +328,21 @@ function LumirEditor({
354
328
  if (!hasFiles) return;
355
329
  e.preventDefault();
356
330
  e.stopPropagation();
357
- e.stopImmediatePropagation?.();
358
331
  const items = Array.from(e.dataTransfer.items ?? []);
359
332
  const files = items.filter((it) => it.kind === "file").map((it) => it.getAsFile()).filter((f) => !!f);
360
- const accepted = files.filter(
361
- (f) => f.size > 0 && (f.type?.startsWith("image/") || !f.type && /\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(f.name || ""))
362
- );
363
- if (accepted.length === 0) return;
333
+ const acceptedFiles = files.filter(isImageFile);
334
+ if (acceptedFiles.length === 0) return;
364
335
  (async () => {
365
- const doUpload = uploadFile ?? (storeImagesAsBase64 ? fileToBase64 : createObjectUrlUploader);
366
- for (const f of accepted) {
336
+ for (const file of acceptedFiles) {
367
337
  try {
368
- const url = await doUpload(f);
369
- editor?.pasteHTML(`<img src="${url}" alt="image" />`);
338
+ if (editor?.uploadFile) {
339
+ const url = await editor.uploadFile(file);
340
+ if (url) {
341
+ editor.pasteHTML(`<img src="${url}" alt="image" />`);
342
+ }
343
+ }
370
344
  } catch (err) {
371
- console.warn("Image upload failed, skipped:", f.name || "", err);
372
- continue;
345
+ console.warn("Image upload failed, skipped:", file.name || "", err);
373
346
  }
374
347
  }
375
348
  })();
@@ -382,24 +355,16 @@ function LumirEditor({
382
355
  });
383
356
  el.removeEventListener("drop", handleDrop, { capture: true });
384
357
  };
385
- }, [
386
- editor,
387
- uploadFile,
388
- storeImagesAsBase64,
389
- allowVideoUpload,
390
- allowAudioUpload
391
- ]);
392
- const computedSideMenu = sideMenuAddButton ? sideMenu : false;
393
- const DragHandleOnlySideMenu = (props) => {
394
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.SideMenu, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.DragHandleButton, { ...props }) });
395
- };
396
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
358
+ }, [editor]);
359
+ const computedSideMenu = (0, import_react.useMemo)(() => {
360
+ return sideMenuAddButton ? sideMenu : false;
361
+ }, [sideMenuAddButton, sideMenu]);
362
+ const DragHandleOnlySideMenu = (0, import_react.useMemo)(() => {
363
+ return (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.SideMenu, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.DragHandleButton, { ...props }) });
364
+ }, []);
365
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("lumirEditor", className), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
397
366
  import_mantine.BlockNoteView,
398
367
  {
399
- className: cn(
400
- includeDefaultStyles && 'lumirEditor w-full h-full min-w-[300px] overflow-auto rounded-md border border-gray-300 focus-within:ring-2 focus-within:ring-black [&_.bn-editor]:px-[12px] [&_[data-content-type="paragraph"]]:text-[14px] bg-white',
401
- className
402
- ),
403
368
  editor,
404
369
  editable,
405
370
  theme,
@@ -410,34 +375,39 @@ function LumirEditor({
410
375
  emojiPicker,
411
376
  filePanel,
412
377
  tableHandles,
413
- comments,
414
378
  onSelectionChange,
415
379
  children: [
416
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
380
+ slashMenu && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
417
381
  import_react2.SuggestionMenuController,
418
382
  {
419
383
  triggerCharacter: "/",
420
- getItems: async (query) => {
421
- const items = (0, import_react2.getDefaultReactSlashMenuItems)(editor);
422
- const filtered = items.filter((it) => {
423
- const k = (it?.key || "").toString();
424
- if (["video", "audio", "file"].includes(k)) return false;
425
- return true;
426
- });
427
- if (!query) return filtered;
428
- const q = query.toLowerCase();
429
- return filtered.filter(
430
- (it) => (it.title || "").toLowerCase().includes(q) || (it.aliases || []).some(
431
- (a) => a.toLowerCase().includes(q)
432
- )
433
- );
434
- }
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)
400
+ )
401
+ );
402
+ },
403
+ [editor]
404
+ )
435
405
  }
436
406
  ),
437
407
  !sideMenuAddButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.SideMenuController, { sideMenu: DragHandleOnlySideMenu })
438
408
  ]
439
409
  }
440
- );
410
+ ) });
441
411
  }
442
412
  // Annotate the CommonJS export names for ESM import in node:
443
413
  0 && (module.exports = {