@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/README.md +379 -925
- package/dist/index.d.mts +8 -20
- package/dist/index.d.ts +8 -20
- package/dist/index.js +104 -134
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +105 -135
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +37 -13
- package/package.json +6 -5
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
|
-
|
|
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?:
|
|
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
|
|
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[
|
|
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[
|
|
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,
|
|
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
|
-
|
|
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?:
|
|
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
|
|
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[
|
|
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[
|
|
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,
|
|
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
|
|
95
|
+
static validateContent(content, emptyBlockCount = 3) {
|
|
97
96
|
if (typeof content === "string") {
|
|
98
97
|
if (content.trim() === "") {
|
|
99
|
-
return this.createEmptyBlocks(emptyBlockCount
|
|
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
|
|
104
|
+
return this.createEmptyBlocks(emptyBlockCount);
|
|
106
105
|
}
|
|
107
106
|
if (!content || content.length === 0) {
|
|
108
|
-
return this.createEmptyBlocks(emptyBlockCount
|
|
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
|
|
119
|
-
return Array.from(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
}, [
|
|
227
|
+
}, [disableExtensions, allowVideoUpload, allowAudioUpload, allowFileUpload]);
|
|
219
228
|
const editor = (0, import_react2.useCreateBlockNote)(
|
|
220
229
|
{
|
|
221
230
|
initialContent: validatedContent,
|
|
222
|
-
tables:
|
|
223
|
-
heading:
|
|
231
|
+
tables: tableConfig,
|
|
232
|
+
heading: headingConfig,
|
|
224
233
|
animations,
|
|
225
234
|
defaultStyles,
|
|
226
|
-
// 확장 비활성:
|
|
227
|
-
disableExtensions:
|
|
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
|
|
257
|
-
|
|
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 (
|
|
266
|
+
if (acceptedFiles.length === 0) {
|
|
267
|
+
return defaultPasteHandler() ?? false;
|
|
268
|
+
}
|
|
264
269
|
event.preventDefault();
|
|
265
270
|
(async () => {
|
|
266
|
-
|
|
267
|
-
for (const file of accepted) {
|
|
271
|
+
for (const file of acceptedFiles) {
|
|
268
272
|
try {
|
|
269
|
-
const url = await
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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 (
|
|
307
|
-
|
|
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
|
|
321
|
-
|
|
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
|
|
361
|
-
|
|
362
|
-
);
|
|
363
|
-
if (accepted.length === 0) return;
|
|
333
|
+
const acceptedFiles = files.filter(isImageFile);
|
|
334
|
+
if (acceptedFiles.length === 0) return;
|
|
364
335
|
(async () => {
|
|
365
|
-
|
|
366
|
-
for (const f of accepted) {
|
|
336
|
+
for (const file of acceptedFiles) {
|
|
367
337
|
try {
|
|
368
|
-
|
|
369
|
-
|
|
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:",
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
]);
|
|
392
|
-
|
|
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:
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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 = {
|