Skip to content

Commit e386b1e

Browse files
added ckeditor image uploader
1 parent d8c3070 commit e386b1e

8 files changed

Lines changed: 170 additions & 53 deletions

File tree

fixture/adminizerConfig.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ const models: AdminpanelConfig["models"] = {
203203
'|',
204204
// 'horizontalLine',
205205
'link',
206-
'insertImageViaUrl',
206+
'insertImage',
207207
'insertTable',
208208
'blockQuote',
209209
'|',
@@ -503,6 +503,10 @@ const config: AdminpanelConfig = {
503503
title: 'Json',
504504
type: 'jsoneditor'
505505
},
506+
text: {
507+
title: 'Editor',
508+
type: 'wysiwyg',
509+
}
506510
}
507511
}
508512
},

src/assets/js/components/ckeditor/ckeditor.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,23 @@ import {
1515
Heading,
1616
HorizontalLine,
1717
ImageBlock,
18-
ImageEditing,
18+
ImageCaption,
1919
ImageInline,
20+
ImageInsert,
2021
ImageInsertViaUrl,
22+
ImageResize,
23+
ImageStyle,
2124
ImageTextAlternative,
2225
ImageToolbar,
23-
ImageUtils,
24-
Indent,
25-
IndentBlock,
26+
ImageUpload,
2627
Italic,
2728
Link,
2829
List,
2930
ListProperties,
3031
Paragraph,
3132
ShowBlocks,
3233
SourceEditing,
34+
SimpleUploadAdapter,
3335
Table,
3436
TableCaption,
3537
TableCellProperties,
@@ -54,6 +56,8 @@ import enTanslations from 'ckeditor5/translations/en.js';
5456

5557
import 'ckeditor5/ckeditor5.css';
5658

59+
import UploadAdapterPlugin from './uploadAdapterPlugin';
60+
5761

5862
interface EditorProps {
5963
initialValue: string,
@@ -98,6 +102,8 @@ export default function AdminCKEditor({initialValue, onChange, options, disabled
98102

99103
return () => setIsLayoutReady(false);
100104
}, []);
105+
106+
101107
const editorConfig = useMemo<EditorConfig>((): EditorConfig => {
102108
if (!isLayoutReady) {
103109
return {};
@@ -117,21 +123,24 @@ export default function AdminCKEditor({initialValue, onChange, options, disabled
117123
Heading,
118124
HorizontalLine,
119125
ImageBlock,
120-
ImageEditing,
126+
ImageCaption,
121127
ImageInline,
122128
ImageInsertViaUrl,
129+
ImageResize,
130+
ImageStyle,
123131
ImageTextAlternative,
124132
ImageToolbar,
125-
ImageUtils,
126-
Indent,
127-
IndentBlock,
133+
ImageUpload,
134+
UploadAdapterPlugin,
135+
ImageInsert,
128136
Italic,
129137
Link,
130138
List,
131139
ListProperties,
132140
Paragraph,
133141
ShowBlocks,
134142
SourceEditing,
143+
SimpleUploadAdapter,
135144
Table,
136145
TableCaption,
137146
TableCellProperties,
@@ -186,7 +195,16 @@ export default function AdminCKEditor({initialValue, onChange, options, disabled
186195
]
187196
},
188197
image: {
189-
toolbar: ['imageTextAlternative']
198+
toolbar: [
199+
'toggleImageCaption',
200+
'imageTextAlternative',
201+
'|',
202+
'imageStyle:inline',
203+
'imageStyle:wrapText',
204+
'imageStyle:breakText',
205+
'|',
206+
'resizeImage'
207+
]
190208
},
191209
initialData: initialValue,
192210
licenseKey: 'GPL',
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import axios from 'axios';
2+
3+
export default class UploadAdapter {
4+
private loader: any;
5+
private url: string;
6+
7+
constructor(loader: any, url: string) {
8+
this.loader = loader;
9+
this.url = url;
10+
}
11+
12+
async upload() {
13+
const data = new FormData();
14+
let file = await this.loader.file;
15+
data.append("name", file.name);
16+
data.append("file", file);
17+
18+
try {
19+
let response = await axios.post(this.url, data, {
20+
headers: {
21+
'Content-Type': 'multipart/form-data',
22+
},
23+
});
24+
25+
let result = {
26+
msg: response.data.msg,
27+
url: window.bindPublic ? `/public${response.data.url}` : response.data.url
28+
};
29+
// Ожидаемый формат ответа: {"code":0,"msg":"success","data":{"url":"/upload/struts2.jpeg"}}
30+
31+
return {
32+
default: result.url,
33+
};
34+
} catch (error) {
35+
console.error('Upload error:', error);
36+
throw error;
37+
}
38+
}
39+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Plugin } from 'ckeditor5';
2+
import UploadAdapter from '@/components/ckeditor/uploadAdapter';
3+
4+
class UploadAdapterPlugin extends Plugin {
5+
static get requires() {
6+
return ['ImageUpload']; // Зависимость от ImageUpload плагина
7+
}
8+
9+
init() {
10+
const editor = this.editor;
11+
const urlParts = window.location.pathname.split('/').filter(part => part !== '');
12+
const entityType = urlParts[1];
13+
const entityName = urlParts[2];
14+
15+
console.log(entityType, entityName);
16+
editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
17+
const uploadUrl = `${window.routePrefix}/${entityType}/${entityName}/ckeditor5/upload`;
18+
return new UploadAdapter(loader, uploadUrl);
19+
};
20+
}
21+
}
22+
23+
export default UploadAdapterPlugin;

src/controllers/ckeditorUpload.ts

Lines changed: 65 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { ControllerHelper } from "../helpers/controllerHelper";
2+
import {Entity} from "../interfaces/types";
23
import * as fs from "fs";
3-
import {Adminizer} from "../lib/Adminizer";
4+
import multer from "multer";
45

5-
export default function upload(req: ReqType, res: ResType): void {
6+
export async function ckEditorUpload(req: ReqType, res: ResType) {
67
let entity = ControllerHelper.findEntityObject(req);
78

89
if (req.adminizer.config.auth.enable) {
@@ -20,37 +21,66 @@ export default function upload(req: ReqType, res: ResType): void {
2021
}
2122
}
2223

23-
if (req.method.toUpperCase() === "POST") {
24-
try {
25-
// set upload directory
26-
const dirDownload = `uploads/${entity.type}/${entity.name}/ckeditor`;
27-
const dir = `${process.cwd()}/.tmp/public/${dirDownload}/`;
28-
29-
if (!fs.existsSync(dir)) {
30-
fs.mkdirSync(dir, { recursive: true });
31-
}
32-
33-
// save file
34-
const filenameOrig = req.body.name.replace(' ', '_');
35-
let filename = filenameOrig.replace(/$/, '_prefix');
36-
37-
req.upload({
38-
destination: dir,
39-
filename: () => filename
40-
}).single("image")(req, res, (err) => {
41-
if (err) {
42-
Adminizer.logger.error("Error uploading file:", err);
43-
return res.status(500).send({ error: err.message || "Internal Server Error" });
44-
}
45-
46-
return res.send({
47-
msg: "success",
48-
url: `/${dirDownload}/${filename}`,
49-
});
50-
});
51-
} catch (error) {
52-
Adminizer.logger.error("Error in uploadImage:", error);
53-
res.status(500).send({ error: "Internal Server Error" });
54-
}
55-
}
24+
const dirDownload = `uploads/${entity.type}/${entity.name}/ckeditor`;
25+
26+
await handleUpload(req, res, dirDownload)
27+
28+
}
29+
30+
async function handleUpload(req: ReqType, res: ResType, dirDownload: string) {
31+
const upload = multer(getUploadConfig(dirDownload)).single("file");
32+
33+
upload(req, res, async (err) => {
34+
try {
35+
if (err) {
36+
let errorMessage = err.message;
37+
if (err.code === 'LIMIT_FILE_SIZE') {
38+
const maxSizeMB = (5 * 1024 * 1024) / (1024 * 1024);
39+
errorMessage = `${req.i18n.__('The file exceeds the size limit')} ${maxSizeMB} MB`;
40+
}
41+
return res.status(400).json({msg: "error", error: errorMessage});
42+
}
43+
44+
return res.send({
45+
msg: "success",
46+
url: `/${dirDownload}/${req.file.filename}`,
47+
});
48+
} catch (e) {
49+
console.error(e);
50+
return res.status(500).send({error: e.message || 'Upload failed'});
51+
}
52+
});
53+
}
54+
55+
function getUploadConfig(dirDownload: string) {
56+
return {
57+
storage: setStorage(checkDirectory(dirDownload)),
58+
limits: {
59+
fileSize: 5 * 1024 * 1024,
60+
},
61+
fileFilter: (req: ReqType, file: any, cb: any) => {
62+
cb(null, true);
63+
}
64+
};
5665
}
66+
67+
function checkDirectory(dirDownload: string): string {
68+
const outputDir = `${process.cwd()}/.tmp/public/${dirDownload}`;
69+
70+
if (!fs.existsSync(outputDir)) {
71+
fs.mkdirSync(outputDir, {recursive: true});
72+
}
73+
return outputDir
74+
}
75+
76+
function setStorage(outputDir: string) {
77+
return multer.diskStorage({
78+
destination: (req, file, cb) => {
79+
cb(null, outputDir);
80+
},
81+
filename: (req, file, cb) => {
82+
const filename = req.body.name;
83+
cb(null, filename);
84+
}
85+
});
86+
}

src/helpers/controllerHelper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export class ControllerHelper {
146146
return Object.keys(models).find(key => key.toLowerCase() === entityName.toLowerCase());
147147
}
148148

149-
throw new Error(`Unsupported entity type "${entityType}" in URL`);
149+
throw new Error(`Unsupported entity type ${entityType} in URL`);
150150
}
151151

152152
/**

src/lib/controls/wysiwyg/CKeditor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class CKeditor extends AbstractControls {
2525
'|',
2626
'horizontalLine',
2727
'link',
28-
'insertImageViaUrl',
28+
'insertImage',
2929
'insertTable',
3030
'blockQuote',
3131
'|',

src/system/Router.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import _edit from "../controllers/edit";
55
import _add from "../controllers/add";
66
import _view from "../controllers/view";
77
import _remove from "../controllers/remove";
8-
import _uploadCKeditor5 from "../controllers/ckeditorUpload";
8+
import {ckEditorUpload} from "../controllers/ckeditorUpload";
99
import _form from "../controllers/form";
1010
import {CreateUpdateConfig} from "../interfaces/adminpanelConfig";
1111
import {widgetSwitchController} from "../controllers/widgets/switch";
@@ -111,6 +111,11 @@ export default class Router {
111111
);
112112
adminizer.app.all(`${adminizer.config.routePrefix}/get-thumbs`, adminizer.policyManager.bindPolicies(policies, thumbController));
113113

114+
/**
115+
* Upload images CKeditor5
116+
*/
117+
adminizer.app.post(`${baseRoute}/ckeditor5/upload`, adminizer.policyManager.bindPolicies(policies, ckEditorUpload));
118+
114119
/**
115120
* Notifications
116121
*/
@@ -222,11 +227,9 @@ export default class Router {
222227
* Remove record
223228
*/
224229
adminizer.app.all(baseRoute + "/remove/:id", adminizer.policyManager.bindPolicies(policies, _remove));
225-
/**
226-
* Upload images CKeditor5
227-
*/
228-
//TODO check after mediamanager upgrade possible is not need
229-
adminizer.app.all(`${baseRoute}/ckeditor5/upload`, adminizer.policyManager.bindPolicies(policies, _uploadCKeditor5));
230+
231+
232+
230233
/**
231234
* Create a default dashboard
232235
*/

0 commit comments

Comments
 (0)