@mbs-dev/react-editor 1.0.8 → 1.1.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.
Files changed (2) hide show
  1. package/README.md +210 -56
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
-
2
1
  # @mbs-dev/react-editor
3
2
 
4
- A lightweight, typed React wrapper around **Jodit** rich-text editor, designed for real-world CRUD forms (blogs, products, CMS pages, etc.).
3
+ A lightweight, typed React wrapper around **Jodit** richtext editor, designed for realworld CRUD forms (blogs, products, CMS pages, etc.).
5
4
 
6
5
  It provides:
7
6
 
8
7
  - A **simple React component** `<ReactEditor />`
9
8
  - A reusable **`config()` helper** for configuration
10
- - An optional **`uploaderConfig()` helper** to handle image uploads and automatic insertion into the editor
9
+ - A reusable **`uploaderConfig()` helper**
10
+ - A new **`onDeleteImage` callback** to delete images on server when removed from editor
11
11
 
12
12
  ---
13
13
 
@@ -19,11 +19,12 @@ It provides:
19
19
 
20
20
  ## ✨ Features
21
21
 
22
- - 🧩 **Simple React API**: controlled `value` + `onChange`
23
- - ⚙️ **Helper config**: `config()` to quickly build configuration
24
- - 🖼️ **Image upload integration**: optional `uploaderConfig()` for file uploading + auto-insert `<img />`
25
- - 🧪 **TypeScript-ready**: ships with types & TSX-friendly usage
26
- - 🧱 **Library-friendly**: minimal surface area, built to be reused across apps
22
+ - 🧩 Simple React API
23
+ - ⚙️ Powerful config builder (`config()`)
24
+ - 🖼️ Image upload support
25
+ - 🗑️ **New: Server‑side image delete support (`onDeleteImage`)**
26
+ - 🔥 Fully typed (TS/TSX)
27
+ - 📦 Built for npm libraries
27
28
 
28
29
  ---
29
30
 
@@ -38,19 +39,19 @@ npm install @mbs-dev/react-editor
38
39
  ## 🚀 Quick Start
39
40
 
40
41
  ```tsx
41
- import React, { useMemo, useState } from 'react';
42
- import ReactEditor, { config } from '@mbs-dev/react-editor';
42
+ import React, { useMemo, useState } from "react";
43
+ import ReactEditor, { config } from "@mbs-dev/react-editor";
43
44
 
44
45
  const MyPage = () => {
45
- const [content, setContent] = useState('');
46
+ const [content, setContent] = useState("");
46
47
 
47
- const editorConfig = useMemo(() => config(false), []);
48
+ const editorConfig = useMemo(() => config({}), []);
48
49
 
49
50
  return (
50
51
  <ReactEditor
51
52
  config={editorConfig}
52
53
  value={content}
53
- onChange={(value) => setContent(value)}
54
+ onChange={setContent}
54
55
  />
55
56
  );
56
57
  };
@@ -58,82 +59,235 @@ const MyPage = () => {
58
59
 
59
60
  ---
60
61
 
61
- ## 🔧 Configuration Helpers
62
+ # 🔧 Configuration (Full API)
62
63
 
63
- ### `config(includeUploader?: boolean, apiUrl?: string, imageUrl?: string)`
64
+ ## `config(params: ConfigParams)`
64
65
 
65
- ### `uploaderConfig(apiUrl?: string, imageUrl?: string)`
66
+ ```ts
67
+ type ConfigParams = {
68
+ includeUploader?: boolean;
69
+ apiUrl?: string;
70
+ imageUrl?: string;
71
+ onDeleteImage?: (imageUrl: string) => void | Promise<void>;
72
+ };
73
+ ```
66
74
 
67
- Both helpers simplify setting up configuration and upload support.
75
+ ### Example
76
+
77
+ ```tsx
78
+ const editorConfig = useMemo(
79
+ () =>
80
+ config({
81
+ includeUploader: true,
82
+ apiUrl: `${apiUrl}/upload`,
83
+ imageUrl: blogPostImgUrl,
84
+ onDeleteImage: handleDeleteImage,
85
+ }),
86
+ []
87
+ );
88
+ ```
68
89
 
69
90
  ---
70
91
 
71
- ## 🧩 Symfony API Platform Example (Recommended Integration)
92
+ # 🖼️ Image Uploading
93
+
94
+ The backend must return:
95
+
96
+ ```json
97
+ {
98
+ "success": true,
99
+ "data": {
100
+ "files": ["uploaded.webp"],
101
+ "messages": []
102
+ },
103
+ "msg": "Upload successful"
104
+ }
105
+ ```
106
+
107
+ Images get inserted automatically:
108
+
109
+ ```html
110
+ <img src="https://domain.com/uploads/images/filename.webp" />
111
+ ```
112
+
113
+ ---
72
114
 
73
- If your backend is **Symfony + API Platform**, here is the **most common real‑world setup**:
115
+ # 🗑️ NEW Image Delete (Server Sync)
74
116
 
75
- ### **📌 1. Expose your upload endpoint**
117
+ The editor detects removed `<img>` tags and calls your function:
76
118
 
77
- In your Symfony API (`config/routes.yaml`):
119
+ ### **TSX Implementation**
120
+
121
+ ```tsx
122
+ const handleDeleteImage = async (imageSrc: string) => {
123
+ const filename = imageSrc.split("/").pop();
124
+ if (!filename) return;
125
+
126
+ await axios.delete(`${apiUrl}/delete/${filename}`);
127
+ };
128
+ ```
129
+
130
+ ---
131
+
132
+ # 🧩 Full TSX Example (Add Blog)
133
+
134
+ ```tsx
135
+ const AddBlog = () => {
136
+ const [description, setDescription] = useState("");
137
+
138
+ const handleDeleteImage = async (imageSrc: string) => {
139
+ const filename = imageSrc.split("/").pop();
140
+ if (!filename) return;
141
+ await axios.delete(`${apiUrl}/delete/${filename}`);
142
+ };
143
+
144
+ const editorConfig = useMemo(
145
+ () =>
146
+ config({
147
+ includeUploader: true,
148
+ apiUrl: `${apiUrl}/upload`,
149
+ imageUrl: blogPostImgUrl,
150
+ onDeleteImage: handleDeleteImage,
151
+ }),
152
+ []
153
+ );
154
+
155
+ return (
156
+ <ReactEditor
157
+ config={editorConfig}
158
+ value={description}
159
+ onChange={setDescription}
160
+ />
161
+ );
162
+ };
163
+ ```
164
+
165
+ ---
166
+
167
+ # 🧩 Symfony API Platform Full Example
168
+
169
+ ## **1. Upload base directory**
170
+
171
+ `config/services.yaml`
78
172
 
79
173
  ```yaml
80
- upload_image:
81
- path: /upload
82
- controller: App\Controller\ImageUploadController
83
- methods: [POST]
174
+ parameters:
175
+ blog_post_images: '%kernel.project_dir%/public/uploads/images_dir'
84
176
  ```
85
177
 
86
- ### **📌 2. Store uploaded images in `/public/uploads/images`**
178
+ ---
179
+
180
+ ## **2. Symfony Upload & Delete Service**
87
181
 
88
182
  ```php
89
- // App/Controller/ImageUploadController.php
183
+ <?php
90
184
 
91
- public function __invoke(Request $request): JsonResponse
185
+ final class UploaderService
92
186
  {
93
- $files = $request->files->get('files');
94
- $storedFiles = [];
187
+ public function __construct(
188
+ private readonly EntityManagerInterface $entityManager,
189
+ private readonly ParameterBagInterface $params
190
+ ) {}
191
+
192
+ public function uploadBlogPostImages(array $uploadedFiles): array
193
+ {
194
+ if ($uploadedFiles === []) {
195
+ throw new BadRequestHttpException('Les fichiers sont requis.');
196
+ }
197
+
198
+ $uploadedFileNames = [];
199
+
200
+ foreach ($uploadedFiles as $file) {
201
+ if (!$file instanceof UploadedFile) {
202
+ continue;
203
+ }
204
+
205
+ $image = new BlogPostImages();
206
+ $image->setImageFile($file);
207
+
208
+ $this->entityManager->persist($image);
209
+ $uploadedFileNames[] = $image->getImage();
210
+ }
211
+
212
+ if ($uploadedFileNames === []) {
213
+ throw new BadRequestHttpException('Aucun fichier valide n’a été fourni.');
214
+ }
215
+
216
+ $this->entityManager->flush();
217
+
218
+ return $uploadedFileNames;
219
+ }
220
+
221
+ public function deleteBlogPostImage(string $filename): void
222
+ {
223
+ $cleanName = basename($filename);
224
+
225
+ $imageEntity = $this->entityManager
226
+ ->getRepository(BlogPostImages::class)
227
+ ->findOneBy(['image' => $cleanName]);
228
+
229
+ if (!$imageEntity) {
230
+ throw new NotFoundHttpException("L'image demandée n'existe pas.");
231
+ }
232
+
233
+ $fileDir = str_replace('\\', '/', $this->params->get('blog_post_images'));
234
+ $filePath = rtrim($fileDir, '/') . '/' . $cleanName;
235
+
236
+ if (is_file($filePath)) {
237
+ unlink($filePath);
238
+ }
95
239
 
96
- foreach ($files as $file) {
97
- $newName = uniqid().'.'.$file->guessExtension();
98
- $file->move($this->getParameter('kernel.project_dir').'/public/uploads/images', $newName);
99
- $storedFiles[] = $newName;
240
+ $this->entityManager->remove($imageEntity);
241
+ $this->entityManager->flush();
100
242
  }
243
+ }
244
+ ```
245
+
246
+ ---
247
+
248
+ ## **3. Delete endpoint**
249
+
250
+ ```php
251
+ public function deleteImage(string $filename): JsonResponse
252
+ {
253
+ $this->uploaderService->deleteBlogPostImage($filename);
101
254
 
102
255
  return new JsonResponse([
103
256
  'success' => true,
104
- 'data' => [
105
- 'files' => $storedFiles,
106
- 'messages' => [],
107
- ],
108
- 'msg' => 'Upload successful'
257
+ 'message' => "L'image a été supprimée avec succès.",
109
258
  ]);
110
259
  }
111
260
  ```
112
261
 
113
- ### **📌 3. Front-end React configuration**
262
+ ---
263
+
264
+ ## **4. React Integration**
114
265
 
115
266
  ```ts
116
- export const uploadUrl = `${apiUrl}/upload`;
117
- export const blogPostImgUrl = `${uploadUrl}/images`;
267
+ export const blogPostImgUrl = `${apiUrl}/uploads/post`;
268
+ ```
118
269
 
119
- const editorConfig = useMemo(
120
- () => config(
121
- true, // enable uploader
122
- `${apiUrl}/upload`, // Symfony upload endpoint
123
- blogPostImgUrl // URL to access public images
124
- ),
125
- []
126
- );
270
+ ```tsx
271
+ const handleDeleteImage = async (src: string) => {
272
+ const filename = src.split("/").pop();
273
+ if (filename)
274
+ await axios.delete(`${apiUrl}/post/delete/${filename}`);
275
+ };
127
276
  ```
128
277
 
129
- This will automatically insert images as:
278
+ ---
130
279
 
131
- ```html
132
- <img src="https://your-domain.com/uploads/images/filename.webp" />
133
- ```
280
+ # 📘 API Summary
281
+
282
+ | Feature | Supported |
283
+ |-------------------|-----------|
284
+ | Image upload | ✅ |
285
+ | Image delete sync | ✅ |
286
+ | TypeScript ready | ✅ |
287
+ | Config builder | ✅ |
134
288
 
135
289
  ---
136
290
 
137
- ## 📝 License
291
+ # 📝 License
138
292
 
139
- MIT — feel free to use it in commercial and private applications.
293
+ MIT — free for commercial and private use.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbs-dev/react-editor",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "react editor",
5
5
  "main": "dist/index.js",
6
6
  "types": "types/index.d.ts",