@mbs-dev/react-editor 1.0.6 → 1.0.8

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/Editor.js CHANGED
@@ -10,6 +10,42 @@ var __assign = (this && this.__assign) || function () {
10
10
  };
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
24
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
13
49
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
50
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
51
  };
@@ -36,41 +72,43 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
36
72
  isSuccess: function (e) {
37
73
  var _a;
38
74
  var fn = this.jodit;
39
- if (((_a = e.data) === null || _a === void 0 ? void 0 : _a.files) && e.data.files.length) {
75
+ if (((_a = e === null || e === void 0 ? void 0 : e.data) === null || _a === void 0 ? void 0 : _a.files) && e.data.files.length) {
40
76
  var tagName_1 = 'img';
41
77
  e.data.files.forEach(function (filename) {
42
78
  var elm = fn.createInside.element(tagName_1);
43
- elm.setAttribute('src', "".concat(imageUrl, "/").concat(filename));
79
+ var src = imageUrl ? "".concat(imageUrl, "/").concat(filename) : filename;
80
+ elm.setAttribute('src', src);
44
81
  fn.s.insertImage(elm, null, fn.o.imageDefaultWidth);
45
82
  });
46
83
  }
47
- return e.success;
84
+ return !!(e === null || e === void 0 ? void 0 : e.success);
48
85
  },
49
86
  getMessage: function (e) {
50
87
  var _a;
51
- return ((_a = e.data) === null || _a === void 0 ? void 0 : _a.messages) && Array.isArray(e.data.messages)
88
+ return ((_a = e === null || e === void 0 ? void 0 : e.data) === null || _a === void 0 ? void 0 : _a.messages) && Array.isArray(e.data.messages)
52
89
  ? e.data.messages.join('')
53
90
  : '';
54
91
  },
55
92
  process: function (resp) {
56
93
  var files = [];
57
- files.unshift(resp.data);
94
+ files.unshift(resp === null || resp === void 0 ? void 0 : resp.data);
58
95
  return {
59
- files: resp.data,
60
- error: resp.msg,
61
- msg: resp.msg,
96
+ files: resp === null || resp === void 0 ? void 0 : resp.data,
97
+ error: resp === null || resp === void 0 ? void 0 : resp.msg,
98
+ msg: resp === null || resp === void 0 ? void 0 : resp.msg,
62
99
  };
63
100
  },
64
101
  error: function (e) {
65
102
  this.j.e.fire('errorMessage', e.message, 'error', 4000);
66
103
  },
67
104
  defaultHandlerError: function (e) {
68
- this.j.e.fire('errorMessage', e.message);
105
+ this.j.e.fire('errorMessage', (e === null || e === void 0 ? void 0 : e.message) || 'Upload error');
69
106
  },
70
107
  }); };
71
108
  exports.uploaderConfig = uploaderConfig;
72
- var config = function (includeUploader, apiUrl, imageUrl, onDeleteImage) {
73
- var baseConfig = {
109
+ var config = function (_a) {
110
+ var _b = _a === void 0 ? {} : _a, includeUploader = _b.includeUploader, apiUrl = _b.apiUrl, imageUrl = _b.imageUrl, onDeleteImage = _b.onDeleteImage;
111
+ var base = {
74
112
  readonly: false,
75
113
  placeholder: 'Start typing...',
76
114
  toolbarAdaptive: false,
@@ -116,17 +154,41 @@ var config = function (includeUploader, apiUrl, imageUrl, onDeleteImage) {
116
154
  ],
117
155
  };
118
156
  if (includeUploader) {
119
- baseConfig.uploader = (0, exports.uploaderConfig)(apiUrl, imageUrl);
157
+ base.uploader = (0, exports.uploaderConfig)(apiUrl, imageUrl);
120
158
  }
121
159
  if (onDeleteImage) {
122
- baseConfig.events = __assign(__assign({}, (baseConfig.events || {})), { afterRemoveImage: function (image) {
123
- var src = image.getAttribute('src');
124
- if (src) {
125
- onDeleteImage(src);
126
- }
160
+ base.events = __assign(__assign({}, (base.events || {})), { afterInit: function (editor) {
161
+ var _this = this;
162
+ var extractImageSrcs = function (html) {
163
+ var container = document.createElement('div');
164
+ container.innerHTML = html || '';
165
+ var imgs = Array.from(container.getElementsByTagName('img'));
166
+ return new Set(imgs
167
+ .map(function (img) { return img.getAttribute('src') || ''; })
168
+ .filter(function (src) { return !!src; }));
169
+ };
170
+ var prevValue = editor.value || '';
171
+ var prevSrcs = extractImageSrcs(prevValue);
172
+ editor.events.on('change', function () { return __awaiter(_this, void 0, void 0, function () {
173
+ var currentValue, currentSrcs;
174
+ return __generator(this, function (_a) {
175
+ currentValue = editor.value || '';
176
+ currentSrcs = extractImageSrcs(currentValue);
177
+ prevSrcs.forEach(function (src) {
178
+ if (!currentSrcs.has(src)) {
179
+ if (!imageUrl || src.startsWith(imageUrl)) {
180
+ void onDeleteImage(src);
181
+ }
182
+ }
183
+ });
184
+ prevValue = currentValue;
185
+ prevSrcs = currentSrcs;
186
+ return [2];
187
+ });
188
+ }); });
127
189
  } });
128
190
  }
129
- return baseConfig;
191
+ return base;
130
192
  };
131
193
  exports.config = config;
132
194
  exports.default = ReactEditor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbs-dev/react-editor",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "react editor",
5
5
  "main": "dist/index.js",
6
6
  "types": "types/index.d.ts",
package/src/Editor.tsx CHANGED
@@ -18,73 +18,80 @@ const ReactEditor: React.FC<EditorProps> = ({ onChange, onBlur, value, config })
18
18
  * Handles image upload + insertion in the editor
19
19
  */
20
20
  export const uploaderConfig = (
21
- apiUrl: string | undefined,
22
- imageUrl: string | undefined
21
+ apiUrl?: string,
22
+ imageUrl?: string
23
23
  ) => ({
24
24
  imagesExtensions: ['jpg', 'png', 'jpeg', 'gif', 'webp'],
25
- filesVariableName: function (t: number): string {
25
+ filesVariableName(t: number): string {
26
26
  return 'files[' + t + ']';
27
27
  },
28
28
  url: apiUrl,
29
29
  withCredentials: false,
30
30
  format: 'json',
31
31
  method: 'POST',
32
- prepareData: function (formdata: FormData): FormData {
32
+ prepareData(formdata: FormData): FormData {
33
33
  return formdata;
34
34
  },
35
- isSuccess: function (this: any, e: any): boolean {
35
+ isSuccess(this: any, e: any): boolean {
36
36
  const fn = this.jodit;
37
37
 
38
- if (e.data?.files && e.data.files.length) {
38
+ if (e?.data?.files && e.data.files.length) {
39
39
  const tagName = 'img';
40
+
40
41
  e.data.files.forEach((filename: string) => {
41
42
  const elm = fn.createInside.element(tagName);
42
- elm.setAttribute('src', `${imageUrl}/${filename}`);
43
+ const src = imageUrl ? `${imageUrl}/${filename}` : filename;
44
+ elm.setAttribute('src', src);
43
45
  fn.s.insertImage(elm as HTMLImageElement, null, fn.o.imageDefaultWidth);
44
46
  });
45
47
  }
46
48
 
47
- return e.success;
49
+ return !!e?.success;
48
50
  },
49
- getMessage: function (e: any): string {
50
- return e.data?.messages && Array.isArray(e.data.messages)
51
+ getMessage(e: any): string {
52
+ return e?.data?.messages && Array.isArray(e.data.messages)
51
53
  ? e.data.messages.join('')
52
54
  : '';
53
55
  },
54
- process: function (resp: any): { files: any[]; error: string; msg: string } {
55
- let files: any[] = [];
56
- files.unshift(resp.data);
56
+ process(resp: any): { files: any[]; error: string; msg: string } {
57
+ const files: any[] = [];
58
+ files.unshift(resp?.data);
59
+
57
60
  return {
58
- files: resp.data,
59
- error: resp.msg,
60
- msg: resp.msg,
61
+ files: resp?.data,
62
+ error: resp?.msg,
63
+ msg: resp?.msg,
61
64
  };
62
65
  },
63
-
64
- error: function (this: any, e: Error): void {
66
+ error(this: any, e: Error): void {
65
67
  this.j.e.fire('errorMessage', e.message, 'error', 4000);
66
68
  },
67
-
68
- defaultHandlerError: function (this: any, e: any): void {
69
- this.j.e.fire('errorMessage', e.message);
69
+ defaultHandlerError(this: any, e: any): void {
70
+ this.j.e.fire('errorMessage', e?.message || 'Upload error');
70
71
  },
71
72
  });
72
73
 
74
+ type ConfigParams = {
75
+ includeUploader?: boolean;
76
+ apiUrl?: string;
77
+ imageUrl?: string;
78
+ /**
79
+ * Called when an image is really removed from the editor content.
80
+ * You receive the image src URL and can call your API to delete it on server.
81
+ */
82
+ onDeleteImage?: (imageUrl: string) => void | Promise<void>;
83
+ };
84
+
73
85
  /**
74
- * Global editor config
75
- * - includeUploader: enable/disable upload integration
76
- * - apiUrl: upload endpoint
77
- * - imageUrl: base URL for stored images
78
- * - onDeleteImage: callback called when an image is removed from the editor
79
- * receives the image src URL so you can call your API to delete it on server
86
+ * Build Jodit config for ReactEditor
80
87
  */
81
- export const config = (
82
- includeUploader?: boolean,
83
- apiUrl?: string,
84
- imageUrl?: string,
85
- onDeleteImage?: (imageUrl: string) => void
86
- ) => {
87
- const baseConfig: any = {
88
+ export const config = ({
89
+ includeUploader,
90
+ apiUrl,
91
+ imageUrl,
92
+ onDeleteImage,
93
+ }: ConfigParams = {}) => {
94
+ const base: any = {
88
95
  readonly: false,
89
96
  placeholder: 'Start typing...',
90
97
  toolbarAdaptive: false,
@@ -131,28 +138,55 @@ export const config = (
131
138
  ],
132
139
  };
133
140
 
134
- // Attach uploader config if requested
135
141
  if (includeUploader) {
136
- baseConfig.uploader = uploaderConfig(apiUrl, imageUrl);
142
+ base.uploader = uploaderConfig(apiUrl, imageUrl);
137
143
  }
138
144
 
139
- // Handle delete image: when an image is removed in the editor,
140
- // we call the provided callback with the image src URL.
141
145
  if (onDeleteImage) {
142
- baseConfig.events = {
143
- ...(baseConfig.events || {}),
144
- // Depending on Jodit version, this event name may vary; this
145
- // handler is intended to be called when an image is removed.
146
- afterRemoveImage: function (this: any, image: HTMLImageElement) {
147
- const src = image.getAttribute('src');
148
- if (src) {
149
- onDeleteImage(src);
150
- }
146
+ base.events = {
147
+ ...(base.events || {}),
148
+ /**
149
+ * We use the value diff to detect removed <img> src.
150
+ * This avoids false calls during upload (where DOM nodes can be replaced).
151
+ */
152
+ afterInit(editor: any) {
153
+ const extractImageSrcs = (html: string): Set<string> => {
154
+ const container = document.createElement('div');
155
+ container.innerHTML = html || '';
156
+ const imgs = Array.from(container.getElementsByTagName('img')) as HTMLImageElement[];
157
+
158
+ return new Set(
159
+ imgs
160
+ .map((img) => img.getAttribute('src') || '')
161
+ .filter((src) => !!src)
162
+ );
163
+ };
164
+
165
+ let prevValue: string = editor.value || '';
166
+ let prevSrcs: Set<string> = extractImageSrcs(prevValue);
167
+
168
+ editor.events.on('change', async () => {
169
+ const currentValue: string = editor.value || '';
170
+ const currentSrcs = extractImageSrcs(currentValue);
171
+
172
+ // src present before, not present now -> deleted
173
+ prevSrcs.forEach((src) => {
174
+ if (!currentSrcs.has(src)) {
175
+ // If imageUrl is defined, you can filter to only your own assets
176
+ if (!imageUrl || src.startsWith(imageUrl)) {
177
+ void onDeleteImage(src);
178
+ }
179
+ }
180
+ });
181
+
182
+ prevValue = currentValue;
183
+ prevSrcs = currentSrcs;
184
+ });
151
185
  },
152
186
  };
153
187
  }
154
188
 
155
- return baseConfig;
189
+ return base;
156
190
  };
157
191
 
158
192
  export default ReactEditor;
@@ -7,4 +7,6 @@ export interface EditorProps {
7
7
  imageUrl?: string;
8
8
  config?: any
9
9
  // ref?: any;
10
- }
10
+ }
11
+
12
+ export type OnDeleteImage = (src: string) => void | Promise<void>;
package/types/Editor.d.ts CHANGED
@@ -1,23 +1,29 @@
1
1
  import React from 'react';
2
2
  import { EditorProps } from './Editor.types';
3
3
  declare const ReactEditor: React.FC<EditorProps>;
4
- export declare const uploaderConfig: (apiUrl: string | undefined, imageUrl: string | undefined) => {
4
+ export declare const uploaderConfig: (apiUrl?: string, imageUrl?: string) => {
5
5
  imagesExtensions: string[];
6
- filesVariableName: (t: number) => string;
6
+ filesVariableName(t: number): string;
7
7
  url: string | undefined;
8
8
  withCredentials: boolean;
9
9
  format: string;
10
10
  method: string;
11
- prepareData: (formdata: FormData) => FormData;
12
- isSuccess: (this: any, e: any) => boolean;
13
- getMessage: (e: any) => string;
14
- process: (resp: any) => {
11
+ prepareData(formdata: FormData): FormData;
12
+ isSuccess(this: any, e: any): boolean;
13
+ getMessage(e: any): string;
14
+ process(resp: any): {
15
15
  files: any[];
16
16
  error: string;
17
17
  msg: string;
18
18
  };
19
- error: (this: any, e: Error) => void;
20
- defaultHandlerError: (this: any, e: any) => void;
19
+ error(this: any, e: Error): void;
20
+ defaultHandlerError(this: any, e: any): void;
21
21
  };
22
- export declare const config: (includeUploader?: boolean, apiUrl?: string, imageUrl?: string, onDeleteImage?: ((imageUrl: string) => void) | undefined) => any;
22
+ type ConfigParams = {
23
+ includeUploader?: boolean;
24
+ apiUrl?: string;
25
+ imageUrl?: string;
26
+ onDeleteImage?: (imageUrl: string) => void | Promise<void>;
27
+ };
28
+ export declare const config: ({ includeUploader, apiUrl, imageUrl, onDeleteImage, }?: ConfigParams) => any;
23
29
  export default ReactEditor;
@@ -7,3 +7,4 @@ export interface EditorProps {
7
7
  imageUrl?: string;
8
8
  config?: any;
9
9
  }
10
+ export type OnDeleteImage = (src: string) => void | Promise<void>;