@mbs-dev/react-editor 1.0.7 → 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
@@ -1,28 +1,50 @@
1
1
  "use strict";
2
- var __read = (this && this.__read) || function (o, n) {
3
- var m = typeof Symbol === "function" && o[Symbol.iterator];
4
- if (!m) return o;
5
- var i = m.call(o), r, ar = [], e;
6
- try {
7
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
8
- }
9
- catch (error) { e = { error: error }; }
10
- finally {
11
- try {
12
- if (r && !r.done && (m = i["return"])) m.call(i);
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
13
8
  }
14
- finally { if (e) throw e.error; }
15
- }
16
- return ar;
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
17
12
  };
18
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
19
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
20
- if (ar || !(i in from)) {
21
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
22
- ar[i] = from[i];
23
- }
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 };
24
47
  }
25
- return to.concat(ar || Array.prototype.slice.call(from));
26
48
  };
27
49
  var __importDefault = (this && this.__importDefault) || function (mod) {
28
50
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -48,42 +70,45 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
48
70
  return formdata;
49
71
  },
50
72
  isSuccess: function (e) {
73
+ var _a;
51
74
  var fn = this.jodit;
52
- if (e.data.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) {
53
76
  var tagName_1 = 'img';
54
77
  e.data.files.forEach(function (filename) {
55
78
  var elm = fn.createInside.element(tagName_1);
56
- elm.setAttribute('src', "".concat(imageUrl, "/").concat(filename));
79
+ var src = imageUrl ? "".concat(imageUrl, "/").concat(filename) : filename;
80
+ elm.setAttribute('src', src);
57
81
  fn.s.insertImage(elm, null, fn.o.imageDefaultWidth);
58
82
  });
59
83
  }
60
- return e.success;
84
+ return !!(e === null || e === void 0 ? void 0 : e.success);
61
85
  },
62
86
  getMessage: function (e) {
63
- return e.data.messages && Array.isArray(e.data.messages)
87
+ var _a;
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)
64
89
  ? e.data.messages.join('')
65
90
  : '';
66
91
  },
67
92
  process: function (resp) {
68
93
  var files = [];
69
- files.unshift(resp.data);
94
+ files.unshift(resp === null || resp === void 0 ? void 0 : resp.data);
70
95
  return {
71
- files: resp.data,
72
- error: resp.msg,
73
- 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,
74
99
  };
75
100
  },
76
101
  error: function (e) {
77
102
  this.j.e.fire('errorMessage', e.message, 'error', 4000);
78
103
  },
79
104
  defaultHandlerError: function (e) {
80
- this.j.e.fire('errorMessage', e.message);
105
+ this.j.e.fire('errorMessage', (e === null || e === void 0 ? void 0 : e.message) || 'Upload error');
81
106
  },
82
107
  }); };
83
108
  exports.uploaderConfig = uploaderConfig;
84
109
  var config = function (_a) {
85
110
  var _b = _a === void 0 ? {} : _a, includeUploader = _b.includeUploader, apiUrl = _b.apiUrl, imageUrl = _b.imageUrl, onDeleteImage = _b.onDeleteImage;
86
- return ({
111
+ var base = {
87
112
  readonly: false,
88
113
  placeholder: 'Start typing...',
89
114
  toolbarAdaptive: false,
@@ -127,40 +152,43 @@ var config = function (_a) {
127
152
  'table',
128
153
  'fullsize',
129
154
  ],
130
- uploader: includeUploader ? (0, exports.uploaderConfig)(apiUrl, imageUrl) : undefined,
131
- events: onDeleteImage
132
- ? {
133
- afterInit: function (editor) {
134
- var root = editor.editor;
135
- var observer = new MutationObserver(function (mutations) {
136
- mutations.forEach(function (mutation) {
137
- mutation.removedNodes.forEach(function (node) {
138
- if (node.nodeType !== Node.ELEMENT_NODE)
139
- return;
140
- var el = node;
141
- var imgs = __spreadArray(__spreadArray([], __read((el.matches('img') ? [el] : [])), false), __read(Array.from(el.querySelectorAll('img'))), false);
142
- imgs.forEach(function (img) {
143
- var src = img.getAttribute('src');
144
- if (!src)
145
- return;
146
- if (imageUrl && !src.startsWith(imageUrl))
147
- return;
148
- onDeleteImage(src);
149
- });
150
- });
155
+ };
156
+ if (includeUploader) {
157
+ base.uploader = (0, exports.uploaderConfig)(apiUrl, imageUrl);
158
+ }
159
+ if (onDeleteImage) {
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
+ }
151
183
  });
184
+ prevValue = currentValue;
185
+ prevSrcs = currentSrcs;
186
+ return [2];
152
187
  });
153
- observer.observe(root, {
154
- childList: true,
155
- subtree: true,
156
- });
157
- editor.events.on('beforeDestruct', function () {
158
- observer.disconnect();
159
- });
160
- },
161
- }
162
- : undefined,
163
- });
188
+ }); });
189
+ } });
190
+ }
191
+ return base;
164
192
  };
165
193
  exports.config = config;
166
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.7",
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
@@ -1,7 +1,6 @@
1
- // Editor.tsx
2
1
  import React from 'react';
3
2
  import JoditEditor from 'jodit-react';
4
- import { EditorProps, OnDeleteImage } from './Editor.types';
3
+ import { EditorProps } from './Editor.types';
5
4
 
6
5
  const ReactEditor: React.FC<EditorProps> = ({ onChange, onBlur, value, config }) => {
7
6
  return (
@@ -14,7 +13,14 @@ const ReactEditor: React.FC<EditorProps> = ({ onChange, onBlur, value, config })
14
13
  );
15
14
  };
16
15
 
17
- export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
16
+ /**
17
+ * Uploader configuration for Jodit
18
+ * Handles image upload + insertion in the editor
19
+ */
20
+ export const uploaderConfig = (
21
+ apiUrl?: string,
22
+ imageUrl?: string
23
+ ) => ({
18
24
  imagesExtensions: ['jpg', 'png', 'jpeg', 'gif', 'webp'],
19
25
  filesVariableName(t: number): string {
20
26
  return 'files[' + t + ']';
@@ -29,38 +35,39 @@ export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
29
35
  isSuccess(this: any, e: any): boolean {
30
36
  const fn = this.jodit;
31
37
 
32
- if (e.data.files && e.data.files.length) {
38
+ if (e?.data?.files && e.data.files.length) {
33
39
  const tagName = 'img';
34
40
 
35
41
  e.data.files.forEach((filename: string) => {
36
42
  const elm = fn.createInside.element(tagName);
37
- elm.setAttribute('src', `${imageUrl}/${filename}`);
43
+ const src = imageUrl ? `${imageUrl}/${filename}` : filename;
44
+ elm.setAttribute('src', src);
38
45
  fn.s.insertImage(elm as HTMLImageElement, null, fn.o.imageDefaultWidth);
39
46
  });
40
47
  }
41
48
 
42
- return e.success;
49
+ return !!e?.success;
43
50
  },
44
51
  getMessage(e: any): string {
45
- return e.data.messages && Array.isArray(e.data.messages)
52
+ return e?.data?.messages && Array.isArray(e.data.messages)
46
53
  ? e.data.messages.join('')
47
54
  : '';
48
55
  },
49
56
  process(resp: any): { files: any[]; error: string; msg: string } {
50
57
  const files: any[] = [];
51
- files.unshift(resp.data);
58
+ files.unshift(resp?.data);
52
59
 
53
60
  return {
54
- files: resp.data,
55
- error: resp.msg,
56
- msg: resp.msg,
61
+ files: resp?.data,
62
+ error: resp?.msg,
63
+ msg: resp?.msg,
57
64
  };
58
65
  },
59
66
  error(this: any, e: Error): void {
60
67
  this.j.e.fire('errorMessage', e.message, 'error', 4000);
61
68
  },
62
69
  defaultHandlerError(this: any, e: any): void {
63
- this.j.e.fire('errorMessage', e.message);
70
+ this.j.e.fire('errorMessage', e?.message || 'Upload error');
64
71
  },
65
72
  });
66
73
 
@@ -68,105 +75,118 @@ type ConfigParams = {
68
75
  includeUploader?: boolean;
69
76
  apiUrl?: string;
70
77
  imageUrl?: string;
71
- onDeleteImage?: OnDeleteImage;
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>;
72
83
  };
73
84
 
85
+ /**
86
+ * Build Jodit config for ReactEditor
87
+ */
74
88
  export const config = ({
75
89
  includeUploader,
76
90
  apiUrl,
77
91
  imageUrl,
78
92
  onDeleteImage,
79
- }: ConfigParams = {}) => ({
80
- readonly: false,
81
- placeholder: 'Start typing...',
82
- toolbarAdaptive: false,
83
- useSearch: false,
84
- language: 'en',
85
- allowResizeX: false,
86
- allowResizeY: false,
87
- height: 400,
88
- enableDragAndDropFileToEditor: true,
89
- showCharsCounter: true,
90
- showWordsCounter: true,
91
- showXPathInStatusbar: false,
92
-
93
- buttons: [
94
- 'source',
95
- '|',
96
- 'bold',
97
- 'italic',
98
- 'underline',
99
- '|',
100
- 'ul',
101
- 'ol',
102
- '|',
103
- 'image',
104
- '|',
105
- 'video',
106
- '|',
107
- 'link',
108
- '|',
109
- 'undo',
110
- 'redo',
111
- '|',
112
- 'hr',
113
- '|',
114
- 'eraser',
115
- '|',
116
- 'font',
117
- 'fontsize',
118
- 'paragraph',
119
- 'brush',
120
- '|',
121
- 'table',
122
- 'fullsize',
123
- ],
124
-
125
- uploader: includeUploader ? uploaderConfig(apiUrl, imageUrl) : undefined,
126
-
127
- // 👇 This part ensures delete callback runs when image is removed
128
- events: onDeleteImage
129
- ? {
93
+ }: ConfigParams = {}) => {
94
+ const base: any = {
95
+ readonly: false,
96
+ placeholder: 'Start typing...',
97
+ toolbarAdaptive: false,
98
+ useSearch: false,
99
+ language: 'en',
100
+ allowResizeX: false,
101
+ allowResizeY: false,
102
+ height: 400,
103
+ enableDragAndDropFileToEditor: true,
104
+ showCharsCounter: true,
105
+ showWordsCounter: true,
106
+ showXPathInStatusbar: false,
107
+
108
+ buttons: [
109
+ 'source',
110
+ '|',
111
+ 'bold',
112
+ 'italic',
113
+ 'underline',
114
+ '|',
115
+ 'ul',
116
+ 'ol',
117
+ '|',
118
+ 'image',
119
+ '|',
120
+ 'video',
121
+ '|',
122
+ 'link',
123
+ '|',
124
+ 'undo',
125
+ 'redo',
126
+ '|',
127
+ 'hr',
128
+ '|',
129
+ 'eraser',
130
+ '|',
131
+ 'font',
132
+ 'fontsize',
133
+ 'paragraph',
134
+ 'brush',
135
+ '|',
136
+ 'table',
137
+ 'fullsize',
138
+ ],
139
+ };
140
+
141
+ if (includeUploader) {
142
+ base.uploader = uploaderConfig(apiUrl, imageUrl);
143
+ }
144
+
145
+ if (onDeleteImage) {
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
+ */
130
152
  afterInit(editor: any) {
131
- const root: HTMLElement = editor.editor;
132
-
133
- const observer = new MutationObserver((mutations) => {
134
- mutations.forEach((mutation) => {
135
- mutation.removedNodes.forEach((node) => {
136
- if (node.nodeType !== Node.ELEMENT_NODE) return;
137
-
138
- const el = node as HTMLElement;
139
-
140
- const imgs: HTMLImageElement[] = [
141
- ...(el.matches('img') ? [el as HTMLImageElement] : []),
142
- ...Array.from(el.querySelectorAll('img')),
143
- ];
144
-
145
- imgs.forEach((img) => {
146
- const src = img.getAttribute('src');
147
- if (!src) return;
148
-
149
- // Optionally restrict to your image base URL
150
- if (imageUrl && !src.startsWith(imageUrl)) return;
151
-
152
- onDeleteImage(src);
153
- });
154
- });
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
+ }
155
180
  });
156
- });
157
181
 
158
- observer.observe(root, {
159
- childList: true,
160
- subtree: true,
161
- });
162
-
163
- // Cleanup when editor is destroyed
164
- editor.events.on('beforeDestruct', () => {
165
- observer.disconnect();
182
+ prevValue = currentValue;
183
+ prevSrcs = currentSrcs;
166
184
  });
167
185
  },
168
- }
169
- : undefined,
170
- });
186
+ };
187
+ }
188
+
189
+ return base;
190
+ };
171
191
 
172
192
  export default ReactEditor;
package/types/Editor.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { EditorProps, OnDeleteImage } from './Editor.types';
2
+ import { EditorProps } from './Editor.types';
3
3
  declare const ReactEditor: React.FC<EditorProps>;
4
4
  export declare const uploaderConfig: (apiUrl?: string, imageUrl?: string) => {
5
5
  imagesExtensions: string[];
@@ -23,42 +23,7 @@ type ConfigParams = {
23
23
  includeUploader?: boolean;
24
24
  apiUrl?: string;
25
25
  imageUrl?: string;
26
- onDeleteImage?: OnDeleteImage;
27
- };
28
- export declare const config: ({ includeUploader, apiUrl, imageUrl, onDeleteImage, }?: ConfigParams) => {
29
- readonly: boolean;
30
- placeholder: string;
31
- toolbarAdaptive: boolean;
32
- useSearch: boolean;
33
- language: string;
34
- allowResizeX: boolean;
35
- allowResizeY: boolean;
36
- height: number;
37
- enableDragAndDropFileToEditor: boolean;
38
- showCharsCounter: boolean;
39
- showWordsCounter: boolean;
40
- showXPathInStatusbar: boolean;
41
- buttons: string[];
42
- uploader: {
43
- imagesExtensions: string[];
44
- filesVariableName(t: number): string;
45
- url: string | undefined;
46
- withCredentials: boolean;
47
- format: string;
48
- method: string;
49
- prepareData(formdata: FormData): FormData;
50
- isSuccess(this: any, e: any): boolean;
51
- getMessage(e: any): string;
52
- process(resp: any): {
53
- files: any[];
54
- error: string;
55
- msg: string;
56
- };
57
- error(this: any, e: Error): void;
58
- defaultHandlerError(this: any, e: any): void;
59
- } | undefined;
60
- events: {
61
- afterInit(editor: any): void;
62
- } | undefined;
26
+ onDeleteImage?: (imageUrl: string) => void | Promise<void>;
63
27
  };
28
+ export declare const config: ({ includeUploader, apiUrl, imageUrl, onDeleteImage, }?: ConfigParams) => any;
64
29
  export default ReactEditor;