@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/README.md +454 -257
- package/dist/index.d.mts +26 -10
- package/dist/index.d.ts +26 -10
- package/dist/index.js +205 -85
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +204 -85
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +86 -37
- package/package.json +8 -5
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
|
-
|
|
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?:
|
|
44
|
+
tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
|
|
38
45
|
trailingBlock?: boolean;
|
|
39
|
-
resolveFileUrl?: (url: string) => Promise<string>;
|
|
40
46
|
editable?: boolean;
|
|
41
|
-
theme?:
|
|
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[
|
|
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[
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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?:
|
|
44
|
+
tabBehavior?: "prefer-navigate-ui" | "prefer-indent";
|
|
38
45
|
trailingBlock?: boolean;
|
|
39
|
-
resolveFileUrl?: (url: string) => Promise<string>;
|
|
40
46
|
editable?: boolean;
|
|
41
|
-
theme?:
|
|
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[
|
|
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[
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (
|
|
341
|
-
|
|
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.
|
|
366
|
-
|
|
474
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
475
|
+
"div",
|
|
367
476
|
{
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
381
|
-
|
|
480
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
481
|
+
import_mantine.BlockNoteView,
|
|
382
482
|
{
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
404
|
-
|
|
520
|
+
}
|
|
521
|
+
),
|
|
522
|
+
!sideMenuAddButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react2.SideMenuController, { sideMenu: DragHandleOnlySideMenu })
|
|
523
|
+
]
|
|
405
524
|
}
|
|
406
525
|
),
|
|
407
|
-
|
|
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
|