@mbs-dev/react-editor 1.7.0 → 1.8.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/dist/Editor.js CHANGED
@@ -76,7 +76,7 @@ var getDisplayNameFromPath = function (filename) {
76
76
  }
77
77
  return noExt;
78
78
  };
79
- var uploaderConfig = function (apiUrl, imageUrl) { return ({
79
+ var uploaderConfig = function (apiUrl, imageUrl, selectionRef) { return ({
80
80
  imagesExtensions: ['jpg', 'png', 'jpeg', 'gif', 'webp'],
81
81
  filesVariableName: function (t) {
82
82
  return 'files[' + t + ']';
@@ -92,34 +92,51 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
92
92
  var _this = this;
93
93
  var _a;
94
94
  var fn = this.jodit;
95
+ var restoreToLastCaret = function () {
96
+ var _a, _b;
97
+ var saved = selectionRef === null || selectionRef === void 0 ? void 0 : selectionRef.current;
98
+ if (saved) {
99
+ try {
100
+ fn.s.selectRange(saved, true);
101
+ return;
102
+ }
103
+ catch (_c) {
104
+ }
105
+ }
106
+ if ((_a = fn === null || fn === void 0 ? void 0 : fn.s) === null || _a === void 0 ? void 0 : _a.focus)
107
+ fn.s.focus();
108
+ if ((_b = fn === null || fn === void 0 ? void 0 : fn.s) === null || _b === void 0 ? void 0 : _b.restore) {
109
+ try {
110
+ fn.s.restore();
111
+ }
112
+ catch (_d) {
113
+ }
114
+ }
115
+ };
116
+ var refreshSavedRange = function () {
117
+ if (!selectionRef)
118
+ return;
119
+ try {
120
+ var r = fn.s.range;
121
+ selectionRef.current = r ? r.cloneRange() : null;
122
+ }
123
+ catch (_a) {
124
+ selectionRef.current = null;
125
+ }
126
+ };
95
127
  if (((_a = e === null || e === void 0 ? void 0 : e.data) === null || _a === void 0 ? void 0 : _a.files) && e.data.files.length) {
96
128
  e.data.files.forEach(function (filename) {
97
- var _a, _b, _c, _d;
129
+ var _a;
98
130
  var src = imageUrl ? "".concat(imageUrl, "/").concat(filename) : filename;
131
+ restoreToLastCaret();
99
132
  if (isImageByExtension(filename, _this.imagesExtensions || ['jpg', 'png', 'jpeg', 'gif', 'webp'])) {
100
133
  var tagName = 'img';
101
134
  var elm = fn.createInside.element(tagName);
102
135
  elm.setAttribute('src', src);
103
136
  fn.s.insertImage(elm, null, fn.o.imageDefaultWidth);
137
+ refreshSavedRange();
104
138
  }
105
139
  else {
106
- if ((_a = fn === null || fn === void 0 ? void 0 : fn.s) === null || _a === void 0 ? void 0 : _a.focus)
107
- fn.s.focus();
108
- var savedRange = fn && fn.__mbs_lastRange;
109
- if (savedRange && ((_b = fn === null || fn === void 0 ? void 0 : fn.s) === null || _b === void 0 ? void 0 : _b.selectRange)) {
110
- try {
111
- fn.s.selectRange(savedRange, true);
112
- }
113
- catch (_e) {
114
- }
115
- }
116
- else if ((_c = fn === null || fn === void 0 ? void 0 : fn.s) === null || _c === void 0 ? void 0 : _c.restore) {
117
- try {
118
- fn.s.restore();
119
- }
120
- catch (_f) {
121
- }
122
- }
123
140
  var tagName = 'a';
124
141
  var elm = fn.createInside.element(tagName);
125
142
  elm.setAttribute('href', src);
@@ -127,13 +144,14 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
127
144
  elm.setAttribute('rel', 'noopener noreferrer');
128
145
  elm.textContent = getDisplayNameFromPath(filename);
129
146
  fn.s.insertNode(elm);
130
- if ((_d = fn === null || fn === void 0 ? void 0 : fn.s) === null || _d === void 0 ? void 0 : _d.setCursorAfter) {
147
+ if ((_a = fn === null || fn === void 0 ? void 0 : fn.s) === null || _a === void 0 ? void 0 : _a.setCursorAfter) {
131
148
  try {
132
149
  fn.s.setCursorAfter(elm);
133
150
  }
134
- catch (_g) {
151
+ catch (_b) {
135
152
  }
136
153
  }
154
+ refreshSavedRange();
137
155
  }
138
156
  });
139
157
  }
@@ -141,9 +159,7 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
141
159
  },
142
160
  getMessage: function (e) {
143
161
  var _a;
144
- return ((_a = e === null || e === void 0 ? void 0 : e.data) === null || _a === void 0 ? void 0 : _a.messages) && Array.isArray(e.data.messages)
145
- ? e.data.messages.join('')
146
- : '';
162
+ return ((_a = e === null || e === void 0 ? void 0 : e.data) === null || _a === void 0 ? void 0 : _a.messages) && Array.isArray(e.data.messages) ? e.data.messages.join('') : '';
147
163
  },
148
164
  process: function (resp) {
149
165
  var files = [];
@@ -163,8 +179,8 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
163
179
  }); };
164
180
  exports.uploaderConfig = uploaderConfig;
165
181
  var config = function (_a) {
166
- var _b;
167
- var _c = _a === void 0 ? {} : _a, includeUploader = _c.includeUploader, apiUrl = _c.apiUrl, imageUrl = _c.imageUrl, onDeleteImage = _c.onDeleteImage;
182
+ var _b = _a === void 0 ? {} : _a, includeUploader = _b.includeUploader, apiUrl = _b.apiUrl, imageUrl = _b.imageUrl, onDeleteImage = _b.onDeleteImage;
183
+ var selectionRef = { current: null };
168
184
  var base = {
169
185
  readonly: false,
170
186
  placeholder: 'Start typing...',
@@ -178,6 +194,7 @@ var config = function (_a) {
178
194
  showCharsCounter: true,
179
195
  showWordsCounter: true,
180
196
  showXPathInStatusbar: false,
197
+ saveSelectionOnBlur: true,
181
198
  buttons: [
182
199
  'source',
183
200
  '|',
@@ -211,61 +228,95 @@ var config = function (_a) {
211
228
  'fullsize',
212
229
  ],
213
230
  };
231
+ var composeAfterInit = function (a, b) {
232
+ return function (editor) {
233
+ if (a)
234
+ a(editor);
235
+ if (b)
236
+ b(editor);
237
+ };
238
+ };
214
239
  if (includeUploader) {
215
- base.uploader = (0, exports.uploaderConfig)(apiUrl, imageUrl);
216
- }
217
- base.events = __assign(__assign({}, (base.events || {})), { afterInit: function (editor) {
218
- var saveRange = function () {
219
- var _a;
240
+ base.uploader = (0, exports.uploaderConfig)(apiUrl, imageUrl, selectionRef);
241
+ var selectionCaptureAfterInit = function (editor) {
242
+ var _a, _b, _c, _d;
243
+ var capture = function () {
220
244
  try {
221
- if ((_a = editor === null || editor === void 0 ? void 0 : editor.s) === null || _a === void 0 ? void 0 : _a.range) {
222
- editor.__mbs_lastRange = editor.s.range.cloneRange();
223
- }
245
+ var r = editor.s.range;
246
+ selectionRef.current = r ? r.cloneRange() : null;
224
247
  }
225
- catch (_b) {
248
+ catch (_a) {
249
+ selectionRef.current = null;
226
250
  }
227
251
  };
228
- saveRange();
229
- editor.events.on('mouseup', saveRange);
230
- editor.events.on('keyup', saveRange);
231
- editor.events.on('focus', saveRange);
232
- editor.events.on('change', saveRange);
233
- } });
234
- if (onDeleteImage) {
235
- var prevAfterInit_1 = (_b = base.events) === null || _b === void 0 ? void 0 : _b.afterInit;
236
- base.events = __assign(__assign({}, (base.events || {})), { afterInit: function (editor) {
237
- var _this = this;
238
- if (typeof prevAfterInit_1 === 'function') {
239
- prevAfterInit_1(editor);
252
+ var editorEl = (_a = editor === null || editor === void 0 ? void 0 : editor.editor) !== null && _a !== void 0 ? _a : null;
253
+ var onMouseUp = function () { return capture(); };
254
+ var onKeyUp = function () { return capture(); };
255
+ var onTouchEnd = function () { return capture(); };
256
+ if (editorEl) {
257
+ editorEl.addEventListener('mouseup', onMouseUp);
258
+ editorEl.addEventListener('keyup', onKeyUp);
259
+ editorEl.addEventListener('touchend', onTouchEnd);
260
+ }
261
+ var toolbarEl = (_b = editor === null || editor === void 0 ? void 0 : editor.toolbarContainer) !== null && _b !== void 0 ? _b : null;
262
+ var onToolbarMouseDownCapture = function (ev) {
263
+ var target = ev.target;
264
+ if (!target)
265
+ return;
266
+ var isFileBtn = !!target.closest('[data-ref="file"]') ||
267
+ !!target.closest('[data-name="file"]') ||
268
+ !!target.closest('.jodit-toolbar-button_file');
269
+ if (isFileBtn) {
270
+ capture();
240
271
  }
241
- var extractImageSrcs = function (html) {
242
- var container = document.createElement('div');
243
- container.innerHTML = html || '';
244
- var imgs = Array.from(container.getElementsByTagName('img'));
245
- return new Set(imgs
246
- .map(function (img) { return img.getAttribute('src') || ''; })
247
- .filter(function (src) { return !!src; }));
248
- };
249
- var prevValue = editor.value || '';
250
- var prevSrcs = extractImageSrcs(prevValue);
251
- editor.events.on('change', function () { return __awaiter(_this, void 0, void 0, function () {
252
- var currentValue, currentSrcs;
253
- return __generator(this, function (_a) {
254
- currentValue = editor.value || '';
255
- currentSrcs = extractImageSrcs(currentValue);
256
- prevSrcs.forEach(function (src) {
257
- if (!currentSrcs.has(src)) {
258
- if (!imageUrl || src.startsWith(imageUrl)) {
259
- void onDeleteImage(src);
260
- }
272
+ };
273
+ if (toolbarEl) {
274
+ toolbarEl.addEventListener('mousedown', onToolbarMouseDownCapture, true);
275
+ }
276
+ (_d = (_c = editor === null || editor === void 0 ? void 0 : editor.e) === null || _c === void 0 ? void 0 : _c.on) === null || _d === void 0 ? void 0 : _d.call(_c, 'beforeDestruct', function () {
277
+ if (editorEl) {
278
+ editorEl.removeEventListener('mouseup', onMouseUp);
279
+ editorEl.removeEventListener('keyup', onKeyUp);
280
+ editorEl.removeEventListener('touchend', onTouchEnd);
281
+ }
282
+ if (toolbarEl) {
283
+ toolbarEl.removeEventListener('mousedown', onToolbarMouseDownCapture, true);
284
+ }
285
+ });
286
+ };
287
+ base.events = __assign({}, (base.events || {}));
288
+ base.events.afterInit = composeAfterInit(base.events.afterInit, selectionCaptureAfterInit);
289
+ }
290
+ if (onDeleteImage) {
291
+ base.events = __assign({}, (base.events || {}));
292
+ var deleteImageAfterInit = function (editor) {
293
+ var extractImageSrcs = function (html) {
294
+ var container = document.createElement('div');
295
+ container.innerHTML = html || '';
296
+ var imgs = Array.from(container.getElementsByTagName('img'));
297
+ return new Set(imgs.map(function (img) { return img.getAttribute('src') || ''; }).filter(function (src) { return !!src; }));
298
+ };
299
+ var prevValue = editor.value || '';
300
+ var prevSrcs = extractImageSrcs(prevValue);
301
+ editor.events.on('change', function () { return __awaiter(void 0, void 0, void 0, function () {
302
+ var currentValue, currentSrcs;
303
+ return __generator(this, function (_a) {
304
+ currentValue = editor.value || '';
305
+ currentSrcs = extractImageSrcs(currentValue);
306
+ prevSrcs.forEach(function (src) {
307
+ if (!currentSrcs.has(src)) {
308
+ if (!imageUrl || src.startsWith(imageUrl)) {
309
+ void onDeleteImage(src);
261
310
  }
262
- });
263
- prevValue = currentValue;
264
- prevSrcs = currentSrcs;
265
- return [2];
311
+ }
266
312
  });
267
- }); });
268
- } });
313
+ prevValue = currentValue;
314
+ prevSrcs = currentSrcs;
315
+ return [2];
316
+ });
317
+ }); });
318
+ };
319
+ base.events.afterInit = composeAfterInit(base.events.afterInit, deleteImageAfterInit);
269
320
  }
270
321
  return base;
271
322
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbs-dev/react-editor",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "react editor",
5
5
  "main": "dist/index.js",
6
6
  "types": "types/index.d.ts",
package/src/Editor.tsx CHANGED
@@ -19,16 +19,16 @@ const isImageByExtension = (filename: string, imageExts: string[]): boolean => {
19
19
  return !!ext && imageExts.includes(ext);
20
20
  };
21
21
 
22
- // display name without extension and without last "-..."
23
- // Example: recu-202600004-2-69956651a3b98099024323.pdf -> recu-202600004-2
24
22
  const getDisplayNameFromPath = (filename: string): string => {
25
23
  const clean = (filename || '').split('?')[0]?.split('#')[0] ?? '';
26
24
  const last = clean.split('/').pop();
27
25
  const base = last ? decodeURIComponent(last) : filename;
28
26
 
27
+ // remove extension
29
28
  const dotIndex = base.lastIndexOf('.');
30
29
  const noExt = dotIndex > 0 ? base.slice(0, dotIndex) : base;
31
30
 
31
+ // remove last "-..." suffix
32
32
  const dashIndex = noExt.lastIndexOf('-');
33
33
  if (dashIndex > 0) {
34
34
  return noExt.slice(0, dashIndex);
@@ -37,11 +37,13 @@ const getDisplayNameFromPath = (filename: string): string => {
37
37
  return noExt;
38
38
  };
39
39
 
40
+ type SelectionRef = { current: Range | null };
41
+
40
42
  /**
41
43
  * Uploader configuration for Jodit
42
44
  * Handles image upload + insertion in the editor
43
45
  */
44
- export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
46
+ export const uploaderConfig = (apiUrl?: string, imageUrl?: string, selectionRef?: SelectionRef) => ({
45
47
  imagesExtensions: ['jpg', 'png', 'jpeg', 'gif', 'webp'],
46
48
  filesVariableName(t: number): string {
47
49
  return 'files[' + t + ']';
@@ -56,35 +58,55 @@ export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
56
58
  isSuccess(this: any, e: any): boolean {
57
59
  const fn = this.jodit;
58
60
 
61
+ const restoreToLastCaret = () => {
62
+ const saved = selectionRef?.current;
63
+
64
+ if (saved) {
65
+ try {
66
+ fn.s.selectRange(saved, true);
67
+ return;
68
+ } catch {
69
+ // ignore and fallback
70
+ }
71
+ }
72
+
73
+ // fallback: jodit internal save/restore
74
+ if (fn?.s?.focus) fn.s.focus();
75
+ if (fn?.s?.restore) {
76
+ try {
77
+ fn.s.restore();
78
+ } catch {
79
+ // ignore
80
+ }
81
+ }
82
+ };
83
+
84
+ const refreshSavedRange = () => {
85
+ if (!selectionRef) return;
86
+
87
+ try {
88
+ const r: Range = fn.s.range;
89
+ selectionRef.current = r ? r.cloneRange() : null;
90
+ } catch {
91
+ selectionRef.current = null;
92
+ }
93
+ };
94
+
59
95
  if (e?.data?.files && e.data.files.length) {
60
96
  e.data.files.forEach((filename: string) => {
61
97
  const src = imageUrl ? `${imageUrl}/${filename}` : filename;
62
98
 
63
- // If it's an image => insert <img>, otherwise insert <a href="...">
99
+ // Restore caret BEFORE inserting anything (image or file)
100
+ restoreToLastCaret();
101
+
102
+ // ✅ If it's an image => insert <img>, otherwise insert <a href="...">
64
103
  if (isImageByExtension(filename, this.imagesExtensions || ['jpg', 'png', 'jpeg', 'gif', 'webp'])) {
65
104
  const tagName = 'img';
66
105
  const elm = fn.createInside.element(tagName);
67
106
  elm.setAttribute('src', src);
68
107
  fn.s.insertImage(elm as HTMLImageElement, null, fn.o.imageDefaultWidth);
108
+ refreshSavedRange();
69
109
  } else {
70
- // ✅ FIX: restore the last known caret/selection inside the editor (works in table cells)
71
- if (fn?.s?.focus) fn.s.focus();
72
-
73
- const savedRange = fn && (fn as any).__mbs_lastRange;
74
- if (savedRange && fn?.s?.selectRange) {
75
- try {
76
- fn.s.selectRange(savedRange, true);
77
- } catch {
78
- // ignore
79
- }
80
- } else if (fn?.s?.restore) {
81
- try {
82
- fn.s.restore();
83
- } catch {
84
- // ignore
85
- }
86
- }
87
-
88
110
  const tagName = 'a';
89
111
  const elm = fn.createInside.element(tagName);
90
112
  elm.setAttribute('href', src);
@@ -101,6 +123,8 @@ export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
101
123
  // ignore
102
124
  }
103
125
  }
126
+
127
+ refreshSavedRange();
104
128
  }
105
129
  });
106
130
  }
@@ -108,9 +132,7 @@ export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
108
132
  return !!e?.success;
109
133
  },
110
134
  getMessage(e: any): string {
111
- return e?.data?.messages && Array.isArray(e.data.messages)
112
- ? e.data.messages.join('')
113
- : '';
135
+ return e?.data?.messages && Array.isArray(e.data.messages) ? e.data.messages.join('') : '';
114
136
  },
115
137
  process(resp: any): { files: any[]; error: string; msg: string } {
116
138
  const files: any[] = [];
@@ -141,12 +163,12 @@ type ConfigParams = {
141
163
  onDeleteImage?: (imageUrl: string) => void | Promise<void>;
142
164
  };
143
165
 
144
- export const config = ({
145
- includeUploader,
146
- apiUrl,
147
- imageUrl,
148
- onDeleteImage,
149
- }: ConfigParams = {}) => {
166
+ /**
167
+ * Build Jodit config for ReactEditor
168
+ */
169
+ export const config = ({ includeUploader, apiUrl, imageUrl, onDeleteImage }: ConfigParams = {}) => {
170
+ const selectionRef: SelectionRef = { current: null };
171
+
150
172
  const base: any = {
151
173
  readonly: false,
152
174
  placeholder: 'Start typing...',
@@ -161,6 +183,9 @@ export const config = ({
161
183
  showWordsCounter: true,
162
184
  showXPathInStatusbar: false,
163
185
 
186
+ // ✅ Helps preserve selection when editor loses focus (e.g., file dialog)
187
+ saveSelectionOnBlur: true,
188
+
164
189
  buttons: [
165
190
  'source',
166
191
  '|',
@@ -195,76 +220,115 @@ export const config = ({
195
220
  ],
196
221
  };
197
222
 
223
+ const composeAfterInit =
224
+ (a?: (editor: any) => void, b?: (editor: any) => void) =>
225
+ (editor: any) => {
226
+ if (a) a(editor);
227
+ if (b) b(editor);
228
+ };
229
+
198
230
  if (includeUploader) {
199
- base.uploader = uploaderConfig(apiUrl, imageUrl);
200
- }
231
+ base.uploader = uploaderConfig(apiUrl, imageUrl, selectionRef);
201
232
 
202
- // Always keep last caret/selection (needed for file uploads to insert in the right cell)
203
- base.events = {
204
- ...(base.events || {}),
205
- afterInit(editor: any) {
206
- const saveRange = () => {
233
+ const selectionCaptureAfterInit = (editor: any) => {
234
+ const capture = () => {
207
235
  try {
208
- if (editor?.s?.range) {
209
- (editor as any).__mbs_lastRange = editor.s.range.cloneRange();
210
- }
236
+ const r: Range = editor.s.range;
237
+ selectionRef.current = r ? r.cloneRange() : null;
211
238
  } catch {
212
- // ignore
239
+ selectionRef.current = null;
213
240
  }
214
241
  };
215
242
 
216
- // initial + common interactions
217
- saveRange();
218
- editor.events.on('mouseup', saveRange);
219
- editor.events.on('keyup', saveRange);
220
- editor.events.on('focus', saveRange);
221
- editor.events.on('change', saveRange);
222
- },
223
- };
243
+ // Capture during normal editing (helps even without toolbar click)
244
+ const editorEl: HTMLElement | null = editor?.editor ?? null;
245
+ const onMouseUp = () => capture();
246
+ const onKeyUp = () => capture();
247
+ const onTouchEnd = () => capture();
224
248
 
225
- if (onDeleteImage) {
226
- const prevAfterInit = base.events?.afterInit;
249
+ if (editorEl) {
250
+ editorEl.addEventListener('mouseup', onMouseUp);
251
+ editorEl.addEventListener('keyup', onKeyUp);
252
+ editorEl.addEventListener('touchend', onTouchEnd);
253
+ }
254
+
255
+ // ✅ IMPORTANT: capture BEFORE toolbar steals focus.
256
+ // We listen on toolbar mousedown in CAPTURE phase.
257
+ const toolbarEl: HTMLElement | null = editor?.toolbarContainer ?? null;
258
+ const onToolbarMouseDownCapture = (ev: MouseEvent) => {
259
+ const target = ev.target as HTMLElement | null;
260
+ if (!target) return;
261
+
262
+ const isFileBtn =
263
+ !!target.closest('[data-ref="file"]') ||
264
+ !!target.closest('[data-name="file"]') ||
265
+ !!target.closest('.jodit-toolbar-button_file');
266
+
267
+ if (isFileBtn) {
268
+ capture();
269
+ }
270
+ };
271
+
272
+ if (toolbarEl) {
273
+ toolbarEl.addEventListener('mousedown', onToolbarMouseDownCapture, true);
274
+ }
275
+
276
+ // cleanup
277
+ editor?.e?.on?.('beforeDestruct', () => {
278
+ if (editorEl) {
279
+ editorEl.removeEventListener('mouseup', onMouseUp);
280
+ editorEl.removeEventListener('keyup', onKeyUp);
281
+ editorEl.removeEventListener('touchend', onTouchEnd);
282
+ }
283
+ if (toolbarEl) {
284
+ toolbarEl.removeEventListener('mousedown', onToolbarMouseDownCapture, true);
285
+ }
286
+ });
287
+ };
227
288
 
228
289
  base.events = {
229
290
  ...(base.events || {}),
230
- afterInit(editor: any) {
231
- // keep existing selection saver
232
- if (typeof prevAfterInit === 'function') {
233
- prevAfterInit(editor);
234
- }
291
+ };
292
+ base.events.afterInit = composeAfterInit(base.events.afterInit, selectionCaptureAfterInit);
293
+ }
235
294
 
236
- const extractImageSrcs = (html: string): Set<string> => {
237
- const container = document.createElement('div');
238
- container.innerHTML = html || '';
239
- const imgs = Array.from(container.getElementsByTagName('img')) as HTMLImageElement[];
240
-
241
- return new Set(
242
- imgs
243
- .map((img) => img.getAttribute('src') || '')
244
- .filter((src) => !!src)
245
- );
246
- };
247
-
248
- let prevValue: string = editor.value || '';
249
- let prevSrcs: Set<string> = extractImageSrcs(prevValue);
250
-
251
- editor.events.on('change', async () => {
252
- const currentValue: string = editor.value || '';
253
- const currentSrcs = extractImageSrcs(currentValue);
254
-
255
- prevSrcs.forEach((src) => {
256
- if (!currentSrcs.has(src)) {
257
- if (!imageUrl || src.startsWith(imageUrl)) {
258
- void onDeleteImage(src);
259
- }
260
- }
261
- });
295
+ if (onDeleteImage) {
296
+ base.events = {
297
+ ...(base.events || {}),
298
+ };
262
299
 
263
- prevValue = currentValue;
264
- prevSrcs = currentSrcs;
300
+ const deleteImageAfterInit = (editor: any) => {
301
+ const extractImageSrcs = (html: string): Set<string> => {
302
+ const container = document.createElement('div');
303
+ container.innerHTML = html || '';
304
+ const imgs = Array.from(container.getElementsByTagName('img')) as HTMLImageElement[];
305
+
306
+ return new Set(imgs.map((img) => img.getAttribute('src') || '').filter((src) => !!src));
307
+ };
308
+
309
+ let prevValue: string = editor.value || '';
310
+ let prevSrcs: Set<string> = extractImageSrcs(prevValue);
311
+
312
+ editor.events.on('change', async () => {
313
+ const currentValue: string = editor.value || '';
314
+ const currentSrcs = extractImageSrcs(currentValue);
315
+
316
+ // src present before, not present now -> deleted
317
+ prevSrcs.forEach((src) => {
318
+ if (!currentSrcs.has(src)) {
319
+ // If imageUrl is defined, you can filter to only your own assets
320
+ if (!imageUrl || src.startsWith(imageUrl)) {
321
+ void onDeleteImage(src);
322
+ }
323
+ }
265
324
  });
266
- },
325
+
326
+ prevValue = currentValue;
327
+ prevSrcs = currentSrcs;
328
+ });
267
329
  };
330
+
331
+ base.events.afterInit = composeAfterInit(base.events.afterInit, deleteImageAfterInit);
268
332
  }
269
333
 
270
334
  return base;
package/types/Editor.d.ts CHANGED
@@ -1,7 +1,10 @@
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, imageUrl?: string) => {
4
+ type SelectionRef = {
5
+ current: Range | null;
6
+ };
7
+ export declare const uploaderConfig: (apiUrl?: string, imageUrl?: string, selectionRef?: SelectionRef) => {
5
8
  imagesExtensions: string[];
6
9
  filesVariableName(t: number): string;
7
10
  url: string | undefined;
@@ -25,5 +28,5 @@ type ConfigParams = {
25
28
  imageUrl?: string;
26
29
  onDeleteImage?: (imageUrl: string) => void | Promise<void>;
27
30
  };
28
- export declare const config: ({ includeUploader, apiUrl, imageUrl, onDeleteImage, }?: ConfigParams) => any;
31
+ export declare const config: ({ includeUploader, apiUrl, imageUrl, onDeleteImage }?: ConfigParams) => any;
29
32
  export default ReactEditor;