@lumir-company/editor 0.2.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.js ADDED
@@ -0,0 +1,449 @@
1
+ "use strict";
2
+ "use client";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ ContentUtils: () => ContentUtils,
25
+ EditorConfig: () => EditorConfig,
26
+ LumirEditor: () => LumirEditor,
27
+ cn: () => cn
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/components/LumirEditor.tsx
32
+ var import_react = require("react");
33
+ var import_react2 = require("@blocknote/react");
34
+ var import_mantine = require("@blocknote/mantine");
35
+
36
+ // src/utils/cn.ts
37
+ function cn(...inputs) {
38
+ return inputs.filter(Boolean).join(" ");
39
+ }
40
+
41
+ // src/components/LumirEditor.tsx
42
+ var import_jsx_runtime = require("react/jsx-runtime");
43
+ var ContentUtils = class {
44
+ /**
45
+ * JSON 문자열의 유효성을 검증합니다
46
+ * @param jsonString 검증할 JSON 문자열
47
+ * @returns 유효한 JSON 문자열인지 여부
48
+ */
49
+ static isValidJSONString(jsonString) {
50
+ try {
51
+ const parsed = JSON.parse(jsonString);
52
+ return Array.isArray(parsed);
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+ /**
58
+ * JSON 문자열을 DefaultPartialBlock 배열로 파싱합니다
59
+ * @param jsonString JSON 문자열
60
+ * @returns 파싱된 블록 배열 또는 null (파싱 실패 시)
61
+ */
62
+ static parseJSONContent(jsonString) {
63
+ try {
64
+ const parsed = JSON.parse(jsonString);
65
+ if (Array.isArray(parsed)) {
66
+ return parsed;
67
+ }
68
+ return null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+ /**
74
+ * 기본 paragraph 블록 생성
75
+ * @returns 기본 설정이 적용된 DefaultPartialBlock
76
+ */
77
+ static createDefaultBlock() {
78
+ return {
79
+ type: "paragraph",
80
+ props: {
81
+ textColor: "default",
82
+ backgroundColor: "default",
83
+ textAlignment: "left"
84
+ },
85
+ content: [{ type: "text", text: "", styles: {} }],
86
+ children: []
87
+ };
88
+ }
89
+ /**
90
+ * 콘텐츠 유효성 검증 및 기본값 설정
91
+ * @param content 사용자 제공 콘텐츠 (객체 배열 또는 JSON 문자열)
92
+ * @param emptyBlockCount 빈 블록 개수 (기본값: 3)
93
+ * @param placeholder 첫 번째 블록의 placeholder 텍스트
94
+ * @returns 검증된 콘텐츠 배열
95
+ */
96
+ static validateContent(content, emptyBlockCount = 3, placeholder) {
97
+ if (typeof content === "string") {
98
+ if (content.trim() === "") {
99
+ return this.createEmptyBlocks(emptyBlockCount, placeholder);
100
+ }
101
+ const parsedContent = this.parseJSONContent(content);
102
+ if (parsedContent && parsedContent.length > 0) {
103
+ return parsedContent;
104
+ }
105
+ return this.createEmptyBlocks(emptyBlockCount, placeholder);
106
+ }
107
+ if (!content || content.length === 0) {
108
+ return this.createEmptyBlocks(emptyBlockCount, placeholder);
109
+ }
110
+ return content;
111
+ }
112
+ /**
113
+ * 빈 블록들을 생성합니다
114
+ * @param emptyBlockCount 생성할 블록 개수
115
+ * @param placeholder 첫 번째 블록의 placeholder 텍스트
116
+ * @returns 생성된 빈 블록 배열
117
+ */
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
+ });
126
+ }
127
+ };
128
+ var EditorConfig = class {
129
+ /**
130
+ * 테이블 설정 기본값 적용
131
+ * @param userTables 사용자 테이블 설정
132
+ * @returns 기본값이 적용된 테이블 설정
133
+ */
134
+ static getDefaultTableConfig(userTables) {
135
+ return {
136
+ splitCells: userTables?.splitCells ?? true,
137
+ cellBackgroundColor: userTables?.cellBackgroundColor ?? true,
138
+ cellTextColor: userTables?.cellTextColor ?? true,
139
+ headers: userTables?.headers ?? true
140
+ };
141
+ }
142
+ /**
143
+ * 헤딩 설정 기본값 적용
144
+ * @param userHeading 사용자 헤딩 설정
145
+ * @returns 기본값이 적용된 헤딩 설정
146
+ */
147
+ static getDefaultHeadingConfig(userHeading) {
148
+ return userHeading?.levels && userHeading.levels.length > 0 ? userHeading : { levels: [1, 2, 3, 4, 5, 6] };
149
+ }
150
+ /**
151
+ * 비활성화할 확장 기능 목록 생성
152
+ * @param userExtensions 사용자 정의 비활성 확장
153
+ * @param allowVideo 비디오 업로드 허용 여부
154
+ * @param allowAudio 오디오 업로드 허용 여부
155
+ * @returns 비활성화할 확장 기능 목록
156
+ */
157
+ static getDisabledExtensions(userExtensions, allowVideo = false, allowAudio = false) {
158
+ const set = new Set(userExtensions ?? []);
159
+ if (!allowVideo) set.add("video");
160
+ if (!allowAudio) set.add("audio");
161
+ return Array.from(set);
162
+ }
163
+ };
164
+ var createObjectUrlUploader = async (file) => {
165
+ return URL.createObjectURL(file);
166
+ };
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
+ function LumirEditor({
174
+ // editor options
175
+ initialContent,
176
+ initialEmptyBlocks = 3,
177
+ placeholder,
178
+ uploadFile,
179
+ pasteHandler,
180
+ tables,
181
+ heading,
182
+ animations = true,
183
+ defaultStyles = true,
184
+ disableExtensions,
185
+ domAttributes,
186
+ tabBehavior = "prefer-navigate-ui",
187
+ trailingBlock = true,
188
+ resolveFileUrl,
189
+ storeImagesAsBase64 = true,
190
+ allowVideoUpload = false,
191
+ allowAudioUpload = false,
192
+ allowFileUpload = false,
193
+ // view options
194
+ editable = true,
195
+ theme = "light",
196
+ formattingToolbar = true,
197
+ linkToolbar = true,
198
+ sideMenu = true,
199
+ slashMenu = true,
200
+ emojiPicker = true,
201
+ filePanel = true,
202
+ tableHandles = true,
203
+ comments = true,
204
+ onSelectionChange,
205
+ className = "",
206
+ includeDefaultStyles = true,
207
+ sideMenuAddButton = true,
208
+ // callbacks / refs
209
+ onContentChange,
210
+ editorRef
211
+ }) {
212
+ const validatedContent = (0, import_react.useMemo)(() => {
213
+ return ContentUtils.validateContent(
214
+ initialContent,
215
+ initialEmptyBlocks,
216
+ placeholder
217
+ );
218
+ }, [initialContent, initialEmptyBlocks, placeholder]);
219
+ const editor = (0, import_react2.useCreateBlockNote)(
220
+ {
221
+ initialContent: validatedContent,
222
+ tables: EditorConfig.getDefaultTableConfig(tables),
223
+ heading: EditorConfig.getDefaultHeadingConfig(heading),
224
+ animations,
225
+ 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
+ tabBehavior,
236
+ trailingBlock,
237
+ resolveFileUrl,
238
+ uploadFile: async (file) => {
239
+ const custom = uploadFile;
240
+ const fallback = storeImagesAsBase64 ? fileToBase64 : createObjectUrlUploader;
241
+ try {
242
+ if (custom) return await custom(file);
243
+ return await fallback(file);
244
+ } catch (_) {
245
+ try {
246
+ return await createObjectUrlUploader(file);
247
+ } catch {
248
+ throw new Error("Failed to process file for upload");
249
+ }
250
+ }
251
+ },
252
+ pasteHandler: (ctx) => {
253
+ const { event, editor: editor2, defaultPasteHandler } = ctx;
254
+ const fileList = event?.clipboardData?.files ?? null;
255
+ 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) {
260
+ event.preventDefault();
261
+ return true;
262
+ }
263
+ if (accepted.length === 0) return defaultPasteHandler() ?? false;
264
+ event.preventDefault();
265
+ (async () => {
266
+ const doUpload = uploadFile ?? (storeImagesAsBase64 ? fileToBase64 : createObjectUrlUploader);
267
+ for (const file of accepted) {
268
+ try {
269
+ const url = await doUpload(file);
270
+ editor2.pasteHTML(`<img src="${url}" alt="image" />`);
271
+ } catch (err) {
272
+ console.warn(
273
+ "Image upload failed, skipped:",
274
+ file.name || "",
275
+ err
276
+ );
277
+ continue;
278
+ }
279
+ }
280
+ })();
281
+ return true;
282
+ }
283
+ },
284
+ [
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(","),
296
+ animations,
297
+ defaultStyles,
298
+ disableExtensions?.join(","),
299
+ domAttributes ? JSON.stringify(domAttributes) : void 0,
300
+ tabBehavior,
301
+ trailingBlock,
302
+ resolveFileUrl
303
+ ]
304
+ );
305
+ (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
+ }
314
+ }
315
+ }, [editor, editable]);
316
+ (0, import_react.useEffect)(() => {
317
+ if (!editor || !onContentChange) return;
318
+ let lastContent = "";
319
+ 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 () => {
328
+ };
329
+ }, [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
+ (0, import_react.useEffect)(() => {
338
+ const el = editor?.domElement;
339
+ if (!el) return;
340
+ const handleDragOver = (e) => {
341
+ if (e.defaultPrevented) return;
342
+ const hasFiles = e.dataTransfer?.types?.includes?.("Files");
343
+ if (hasFiles) {
344
+ e.preventDefault();
345
+ e.stopPropagation();
346
+ if (typeof e.stopImmediatePropagation === "function") {
347
+ e.stopImmediatePropagation();
348
+ }
349
+ }
350
+ };
351
+ const handleDrop = (e) => {
352
+ if (!e.dataTransfer) return;
353
+ const hasFiles = (e.dataTransfer.types ?? []).includes("Files");
354
+ if (!hasFiles) return;
355
+ e.preventDefault();
356
+ e.stopPropagation();
357
+ e.stopImmediatePropagation?.();
358
+ const items = Array.from(e.dataTransfer.items ?? []);
359
+ 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;
364
+ (async () => {
365
+ const doUpload = uploadFile ?? (storeImagesAsBase64 ? fileToBase64 : createObjectUrlUploader);
366
+ for (const f of accepted) {
367
+ try {
368
+ const url = await doUpload(f);
369
+ editor?.pasteHTML(`<img src="${url}" alt="image" />`);
370
+ } catch (err) {
371
+ console.warn("Image upload failed, skipped:", f.name || "", err);
372
+ continue;
373
+ }
374
+ }
375
+ })();
376
+ };
377
+ el.addEventListener("dragover", handleDragOver, { capture: true });
378
+ el.addEventListener("drop", handleDrop, { capture: true });
379
+ return () => {
380
+ el.removeEventListener("dragover", handleDragOver, {
381
+ capture: true
382
+ });
383
+ el.removeEventListener("drop", handleDrop, { capture: true });
384
+ };
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)(
397
+ import_mantine.BlockNoteView,
398
+ {
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
+ editor,
404
+ editable,
405
+ theme,
406
+ formattingToolbar,
407
+ linkToolbar,
408
+ sideMenu: computedSideMenu,
409
+ slashMenu: false,
410
+ emojiPicker,
411
+ filePanel,
412
+ tableHandles,
413
+ comments,
414
+ onSelectionChange,
415
+ children: [
416
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
417
+ import_react2.SuggestionMenuController,
418
+ {
419
+ 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
+ }
435
+ }
436
+ ),
437
+ !sideMenuAddButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.SideMenuController, { sideMenu: DragHandleOnlySideMenu })
438
+ ]
439
+ }
440
+ );
441
+ }
442
+ // Annotate the CommonJS export names for ESM import in node:
443
+ 0 && (module.exports = {
444
+ ContentUtils,
445
+ EditorConfig,
446
+ LumirEditor,
447
+ cn
448
+ });
449
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/components/LumirEditor.tsx","../src/utils/cn.ts"],"sourcesContent":["\"use client\";\n\n// 컴포넌트 및 유틸리티 export\nexport {\n default as LumirEditor,\n ContentUtils,\n EditorConfig,\n} from \"./components/LumirEditor\";\nexport { cn } from \"./utils/cn\";\n\n// 타입 export (별도 파일에서 관리)\nexport type {\n LumirEditorProps,\n EditorType,\n DefaultPartialBlock,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n PartialBlock,\n BlockNoteEditor,\n} from \"./types\";\n","\"use client\";\n\nimport { useEffect, useMemo } from \"react\";\nimport {\n useCreateBlockNote,\n SideMenu as BlockSideMenu,\n SideMenuController,\n DragHandleButton,\n SuggestionMenuController,\n getDefaultReactSlashMenuItems,\n} from \"@blocknote/react\";\nimport { BlockNoteView } from \"@blocknote/mantine\";\nimport { cn } from \"../utils/cn\";\n\nimport type {\n DefaultPartialBlock,\n LumirEditorProps,\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema,\n} from \"../types\";\n\n// ==========================================\n// 유틸리티 클래스들 (안전한 리팩토링)\n// ==========================================\n\n/**\n * 콘텐츠 관리 유틸리티\n * 기본 블록 생성 및 콘텐츠 검증 로직을 담당\n */\nexport class ContentUtils {\n /**\n * JSON 문자열의 유효성을 검증합니다\n * @param jsonString 검증할 JSON 문자열\n * @returns 유효한 JSON 문자열인지 여부\n */\n static isValidJSONString(jsonString: string): boolean {\n try {\n const parsed = JSON.parse(jsonString);\n return Array.isArray(parsed);\n } catch {\n return false;\n }\n }\n\n /**\n * JSON 문자열을 DefaultPartialBlock 배열로 파싱합니다\n * @param jsonString JSON 문자열\n * @returns 파싱된 블록 배열 또는 null (파싱 실패 시)\n */\n static parseJSONContent(jsonString: string): DefaultPartialBlock[] | null {\n try {\n const parsed = JSON.parse(jsonString);\n if (Array.isArray(parsed)) {\n return parsed as DefaultPartialBlock[];\n }\n return null;\n } catch {\n return null;\n }\n }\n /**\n * 기본 paragraph 블록 생성\n * @returns 기본 설정이 적용된 DefaultPartialBlock\n */\n static createDefaultBlock(): DefaultPartialBlock {\n return {\n type: \"paragraph\",\n props: {\n textColor: \"default\",\n backgroundColor: \"default\",\n textAlignment: \"left\",\n },\n content: [{ type: \"text\", text: \"\", styles: {} }],\n children: [],\n };\n }\n\n /**\n * 콘텐츠 유효성 검증 및 기본값 설정\n * @param content 사용자 제공 콘텐츠 (객체 배열 또는 JSON 문자열)\n * @param emptyBlockCount 빈 블록 개수 (기본값: 3)\n * @param placeholder 첫 번째 블록의 placeholder 텍스트\n * @returns 검증된 콘텐츠 배열\n */\n static validateContent(\n content?: DefaultPartialBlock[] | string,\n emptyBlockCount: number = 3,\n placeholder?: string\n ): DefaultPartialBlock[] {\n // 1. 문자열인 경우 JSON 파싱 시도\n if (typeof content === \"string\") {\n if (content.trim() === \"\") {\n return this.createEmptyBlocks(emptyBlockCount, placeholder);\n }\n\n const parsedContent = this.parseJSONContent(content);\n if (parsedContent && parsedContent.length > 0) {\n return parsedContent;\n }\n\n // 파싱 실패 시 빈 블록 생성\n return this.createEmptyBlocks(emptyBlockCount, placeholder);\n }\n\n // 2. 배열인 경우 기존 로직\n if (!content || content.length === 0) {\n return this.createEmptyBlocks(emptyBlockCount, placeholder);\n }\n\n return content;\n }\n\n /**\n * 빈 블록들을 생성합니다\n * @param emptyBlockCount 생성할 블록 개수\n * @param placeholder 첫 번째 블록의 placeholder 텍스트\n * @returns 생성된 빈 블록 배열\n */\n private static createEmptyBlocks(\n emptyBlockCount: number,\n placeholder?: string\n ): DefaultPartialBlock[] {\n return Array.from({ length: emptyBlockCount }, (_, index) => {\n const block = this.createDefaultBlock();\n // 첫 번째 블록에 placeholder 텍스트 적용\n if (index === 0 && placeholder) {\n block.content = [{ type: \"text\", text: placeholder, styles: {} }];\n }\n return block;\n });\n }\n}\n\n/**\n * 에디터 설정 관리 유틸리티\n * 각종 설정의 기본값과 검증 로직을 담당\n */\nexport class EditorConfig {\n /**\n * 테이블 설정 기본값 적용\n * @param userTables 사용자 테이블 설정\n * @returns 기본값이 적용된 테이블 설정\n */\n static getDefaultTableConfig(userTables?: LumirEditorProps[\"tables\"]) {\n return {\n splitCells: userTables?.splitCells ?? true,\n cellBackgroundColor: userTables?.cellBackgroundColor ?? true,\n cellTextColor: userTables?.cellTextColor ?? true,\n headers: userTables?.headers ?? true,\n };\n }\n\n /**\n * 헤딩 설정 기본값 적용\n * @param userHeading 사용자 헤딩 설정\n * @returns 기본값이 적용된 헤딩 설정\n */\n static getDefaultHeadingConfig(userHeading?: LumirEditorProps[\"heading\"]) {\n return userHeading?.levels && userHeading.levels.length > 0\n ? userHeading\n : { levels: [1, 2, 3, 4, 5, 6] as (1 | 2 | 3 | 4 | 5 | 6)[] };\n }\n\n /**\n * 비활성화할 확장 기능 목록 생성\n * @param userExtensions 사용자 정의 비활성 확장\n * @param allowVideo 비디오 업로드 허용 여부\n * @param allowAudio 오디오 업로드 허용 여부\n * @returns 비활성화할 확장 기능 목록\n */\n static getDisabledExtensions(\n userExtensions?: string[],\n allowVideo = false,\n allowAudio = false\n ): string[] {\n const set = new Set<string>(userExtensions ?? []);\n if (!allowVideo) set.add(\"video\");\n if (!allowAudio) set.add(\"audio\");\n return Array.from(set);\n }\n}\n\nconst createObjectUrlUploader = async (file: File): Promise<string> => {\n return URL.createObjectURL(file);\n};\n\nconst fileToBase64 = async (file: File): Promise<string> =>\n await new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(String(reader.result));\n reader.onerror = () => reject(new Error(\"FileReader failed\"));\n reader.readAsDataURL(file);\n });\n\nexport default function LumirEditor({\n // editor options\n initialContent,\n initialEmptyBlocks = 3,\n placeholder,\n uploadFile,\n pasteHandler,\n tables,\n heading,\n animations = true,\n defaultStyles = true,\n disableExtensions,\n domAttributes,\n tabBehavior = \"prefer-navigate-ui\",\n trailingBlock = true,\n resolveFileUrl,\n storeImagesAsBase64 = true,\n allowVideoUpload = false,\n allowAudioUpload = false,\n allowFileUpload = false,\n // view options\n editable = true,\n theme = \"light\",\n formattingToolbar = true,\n linkToolbar = true,\n sideMenu = true,\n slashMenu = true,\n emojiPicker = true,\n filePanel = true,\n tableHandles = true,\n comments = true,\n onSelectionChange,\n className = \"\",\n includeDefaultStyles = true,\n sideMenuAddButton = true,\n // callbacks / refs\n onContentChange,\n editorRef,\n}: LumirEditorProps) {\n const validatedContent = useMemo<DefaultPartialBlock[]>(() => {\n return ContentUtils.validateContent(\n initialContent,\n initialEmptyBlocks,\n placeholder\n );\n }, [initialContent, initialEmptyBlocks, placeholder]);\n\n const editor = useCreateBlockNote<\n DefaultBlockSchema,\n DefaultInlineContentSchema,\n DefaultStyleSchema\n >(\n {\n initialContent: validatedContent as DefaultPartialBlock[],\n tables: EditorConfig.getDefaultTableConfig(tables),\n heading: EditorConfig.getDefaultHeadingConfig(heading),\n animations,\n defaultStyles,\n // 확장 비활성: 비디오/오디오만 제어(파일 확장은 내부 드롭 로직 의존 → 비활성화하지 않음)\n disableExtensions: useMemo(() => {\n return EditorConfig.getDisabledExtensions(\n disableExtensions,\n allowVideoUpload,\n allowAudioUpload\n );\n }, [disableExtensions, allowVideoUpload, allowAudioUpload]),\n domAttributes,\n tabBehavior,\n trailingBlock,\n resolveFileUrl,\n uploadFile: async (file) => {\n const custom = uploadFile;\n const fallback = storeImagesAsBase64\n ? fileToBase64\n : createObjectUrlUploader;\n try {\n if (custom) return await custom(file);\n return await fallback(file);\n } catch (_) {\n // Fallback to ObjectURL when FileReader or custom upload fails\n try {\n return await createObjectUrlUploader(file);\n } catch {\n throw new Error(\"Failed to process file for upload\");\n }\n }\n },\n pasteHandler: (ctx) => {\n const { event, editor, defaultPasteHandler } = ctx as any;\n const fileList =\n (event?.clipboardData?.files as FileList | null) ?? null;\n const files: File[] = fileList ? Array.from(fileList) : [];\n const accepted: File[] = files.filter(\n (f: File) =>\n f.size > 0 &&\n (f.type?.startsWith(\"image/\") ||\n (!f.type &&\n /\\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(f.name || \"\")))\n );\n // 파일 항목이 있으나 허용되지 않으면 기본 처리도 막고 무시\n if (files.length > 0 && accepted.length === 0) {\n event.preventDefault();\n return true;\n }\n if (accepted.length === 0) return defaultPasteHandler() ?? false;\n event.preventDefault();\n (async () => {\n const doUpload =\n uploadFile ??\n (storeImagesAsBase64 ? fileToBase64 : createObjectUrlUploader);\n for (const file of accepted) {\n try {\n const url = await doUpload(file);\n editor.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\n } catch (err) {\n // 업로드 실패 파일은 삽입하지 않음 (삭제/스킵)\n // console.warn로만 기록하여 UI 오류를 막음\n console.warn(\n \"Image upload failed, skipped:\",\n file.name || \"\",\n err\n );\n continue;\n }\n }\n })();\n return true;\n },\n },\n [\n uploadFile,\n pasteHandler,\n storeImagesAsBase64,\n allowVideoUpload,\n allowAudioUpload,\n allowFileUpload,\n tables?.splitCells,\n tables?.cellBackgroundColor,\n tables?.cellTextColor,\n tables?.headers,\n heading?.levels?.join(\",\"),\n animations,\n defaultStyles,\n disableExtensions?.join(\",\"),\n domAttributes ? JSON.stringify(domAttributes) : undefined,\n tabBehavior,\n trailingBlock,\n resolveFileUrl,\n ]\n );\n\n useEffect(() => {\n if (!editor) return;\n editor.isEditable = editable;\n const el = editor.domElement as HTMLElement | undefined;\n if (!editable) {\n if (el) {\n el.style.userSelect = \"text\";\n (\n el.style as CSSStyleDeclaration & { webkitUserSelect?: string }\n ).webkitUserSelect = \"text\";\n }\n }\n }, [editor, editable]);\n\n useEffect(() => {\n if (!editor || !onContentChange) return;\n let lastContent = \"\";\n const handleContentChange = () => {\n const topLevelBlocks =\n editor.topLevelBlocks as unknown as DefaultPartialBlock[];\n const currentContent = JSON.stringify(topLevelBlocks);\n if (lastContent === currentContent) return;\n lastContent = currentContent;\n onContentChange(topLevelBlocks);\n };\n editor.onEditorContentChange(handleContentChange);\n return () => {};\n }, [editor, onContentChange]);\n\n // 외부에서 imperative API 접근을 위한 ref 연결\n useEffect(() => {\n if (!editorRef) return;\n editorRef.current = editor ?? null;\n return () => {\n if (editorRef) editorRef.current = null;\n };\n }, [editor, editorRef]);\n\n useEffect(() => {\n const el = editor?.domElement as HTMLElement | undefined;\n if (!el) return;\n const handleDragOver = (e: DragEvent) => {\n if (e.defaultPrevented) return;\n const hasFiles = (\n e.dataTransfer?.types as unknown as string[] | undefined\n )?.includes?.(\"Files\");\n if (hasFiles) {\n e.preventDefault();\n e.stopPropagation();\n // @ts-ignore\n if (typeof (e as any).stopImmediatePropagation === \"function\") {\n // @ts-ignore\n (e as any).stopImmediatePropagation();\n }\n }\n };\n\n const handleDrop = (e: DragEvent) => {\n if (!e.dataTransfer) return;\n const hasFiles = (\n (e.dataTransfer.types as unknown as string[] | undefined) ?? []\n ).includes(\"Files\");\n if (!hasFiles) return;\n\n // 기본 드롭 동작을 항상 차단해, 허용되지 않는 파일이 렌더링되지 않도록 함\n e.preventDefault();\n e.stopPropagation();\n // @ts-ignore\n (e as any).stopImmediatePropagation?.();\n\n // DataTransferItem 우선 (디렉토리/가짜 항목 배제)\n const items = Array.from(e.dataTransfer.items ?? []);\n const files = items\n .filter((it) => it.kind === \"file\")\n .map((it) => it.getAsFile())\n .filter((f): f is File => !!f);\n\n const accepted = files.filter(\n (f) =>\n f.size > 0 &&\n (f.type?.startsWith(\"image/\") ||\n (!f.type && /\\.(png|jpe?g|gif|webp|bmp|svg)$/i.test(f.name || \"\")))\n );\n if (accepted.length === 0) return; // 차단만 하고 아무것도 삽입하지 않음\n\n (async () => {\n const doUpload =\n uploadFile ??\n (storeImagesAsBase64 ? fileToBase64 : createObjectUrlUploader);\n for (const f of accepted) {\n try {\n const url = await doUpload(f);\n editor?.pasteHTML(`<img src=\"${url}\" alt=\"image\" />`);\n } catch (err) {\n // 실패 파일은 삽입하지 않음\n console.warn(\"Image upload failed, skipped:\", f.name || \"\", err);\n continue;\n }\n }\n })();\n };\n el.addEventListener(\"dragover\", handleDragOver, { capture: true });\n el.addEventListener(\"drop\", handleDrop, { capture: true });\n return () => {\n el.removeEventListener(\"dragover\", handleDragOver, {\n capture: true,\n } as any);\n el.removeEventListener(\"drop\", handleDrop, { capture: true } as any);\n };\n }, [\n editor,\n uploadFile,\n storeImagesAsBase64,\n allowVideoUpload,\n allowAudioUpload,\n ]);\n\n // Add block 버튼을 끄면 기본 SideMenu를 비활성화하고 커스텀 SideMenu만 렌더(드래그 핸들 유지)\n const computedSideMenu = sideMenuAddButton ? sideMenu : false;\n\n // 공식 가이드 방식: 커스텀 SideMenu 컴포넌트 전달 (버튼 미제공 → 플러스 버튼 없음)\n // 공식 가이드 패턴: props를 전달받아 DragHandleButton만 유지\n const DragHandleOnlySideMenu = (props: any) => {\n return (\n <BlockSideMenu {...props}>\n <DragHandleButton {...props} />\n </BlockSideMenu>\n );\n };\n\n return (\n <BlockNoteView\n className={cn(\n includeDefaultStyles &&\n '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',\n className\n )}\n editor={editor}\n editable={editable}\n theme={theme as \"light\" | \"dark\" | undefined}\n formattingToolbar={formattingToolbar}\n linkToolbar={linkToolbar}\n sideMenu={computedSideMenu}\n slashMenu={false}\n emojiPicker={emojiPicker}\n filePanel={filePanel}\n tableHandles={tableHandles}\n comments={comments}\n onSelectionChange={onSelectionChange}\n >\n <SuggestionMenuController\n triggerCharacter=\"/\"\n getItems={async (query: string) => {\n const items = getDefaultReactSlashMenuItems(editor);\n const filtered = items.filter((it: any) => {\n const k = (it?.key || \"\").toString();\n if ([\"video\", \"audio\", \"file\"].includes(k)) return false;\n return true;\n });\n if (!query) return filtered;\n const q = query.toLowerCase();\n return filtered.filter(\n (it: any) =>\n (it.title || \"\").toLowerCase().includes(q) ||\n (it.aliases || []).some((a: string) =>\n a.toLowerCase().includes(q)\n )\n );\n }}\n />\n {!sideMenuAddButton && (\n <SideMenuController sideMenu={DragHandleOnlySideMenu} />\n )}\n </BlockNoteView>\n );\n}\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"],"mappings":"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","names":["import_react","editor","BlockSideMenu"]}