@mbs-dev/react-editor 1.6.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 +125 -40
- package/package.json +1 -1
- package/src/Editor.tsx +148 -44
- package/types/Editor.d.ts +4 -1
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,26 +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
|
|
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
|
-
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
140
|
var tagName = 'a';
|
|
116
141
|
var elm = fn.createInside.element(tagName);
|
|
117
142
|
elm.setAttribute('href', src);
|
|
@@ -119,13 +144,14 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
|
|
|
119
144
|
elm.setAttribute('rel', 'noopener noreferrer');
|
|
120
145
|
elm.textContent = getDisplayNameFromPath(filename);
|
|
121
146
|
fn.s.insertNode(elm);
|
|
122
|
-
if ((
|
|
147
|
+
if ((_a = fn === null || fn === void 0 ? void 0 : fn.s) === null || _a === void 0 ? void 0 : _a.setCursorAfter) {
|
|
123
148
|
try {
|
|
124
149
|
fn.s.setCursorAfter(elm);
|
|
125
150
|
}
|
|
126
|
-
catch (
|
|
151
|
+
catch (_b) {
|
|
127
152
|
}
|
|
128
153
|
}
|
|
154
|
+
refreshSavedRange();
|
|
129
155
|
}
|
|
130
156
|
});
|
|
131
157
|
}
|
|
@@ -154,6 +180,7 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
|
|
|
154
180
|
exports.uploaderConfig = uploaderConfig;
|
|
155
181
|
var config = function (_a) {
|
|
156
182
|
var _b = _a === void 0 ? {} : _a, includeUploader = _b.includeUploader, apiUrl = _b.apiUrl, imageUrl = _b.imageUrl, onDeleteImage = _b.onDeleteImage;
|
|
183
|
+
var selectionRef = { current: null };
|
|
157
184
|
var base = {
|
|
158
185
|
readonly: false,
|
|
159
186
|
placeholder: 'Start typing...',
|
|
@@ -167,6 +194,7 @@ var config = function (_a) {
|
|
|
167
194
|
showCharsCounter: true,
|
|
168
195
|
showWordsCounter: true,
|
|
169
196
|
showXPathInStatusbar: false,
|
|
197
|
+
saveSelectionOnBlur: true,
|
|
170
198
|
buttons: [
|
|
171
199
|
'source',
|
|
172
200
|
'|',
|
|
@@ -200,38 +228,95 @@ var config = function (_a) {
|
|
|
200
228
|
'fullsize',
|
|
201
229
|
],
|
|
202
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
|
+
};
|
|
203
239
|
if (includeUploader) {
|
|
204
|
-
base.uploader = (0, exports.uploaderConfig)(apiUrl, imageUrl);
|
|
240
|
+
base.uploader = (0, exports.uploaderConfig)(apiUrl, imageUrl, selectionRef);
|
|
241
|
+
var selectionCaptureAfterInit = function (editor) {
|
|
242
|
+
var _a, _b, _c, _d;
|
|
243
|
+
var capture = function () {
|
|
244
|
+
try {
|
|
245
|
+
var r = editor.s.range;
|
|
246
|
+
selectionRef.current = r ? r.cloneRange() : null;
|
|
247
|
+
}
|
|
248
|
+
catch (_a) {
|
|
249
|
+
selectionRef.current = null;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
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();
|
|
271
|
+
}
|
|
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);
|
|
205
289
|
}
|
|
206
290
|
if (onDeleteImage) {
|
|
207
|
-
base.events = __assign(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
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);
|
|
227
310
|
}
|
|
228
|
-
}
|
|
229
|
-
prevValue = currentValue;
|
|
230
|
-
prevSrcs = currentSrcs;
|
|
231
|
-
return [2];
|
|
311
|
+
}
|
|
232
312
|
});
|
|
233
|
-
|
|
234
|
-
|
|
313
|
+
prevValue = currentValue;
|
|
314
|
+
prevSrcs = currentSrcs;
|
|
315
|
+
return [2];
|
|
316
|
+
});
|
|
317
|
+
}); });
|
|
318
|
+
};
|
|
319
|
+
base.events.afterInit = composeAfterInit(base.events.afterInit, deleteImageAfterInit);
|
|
235
320
|
}
|
|
236
321
|
return base;
|
|
237
322
|
};
|
package/package.json
CHANGED
package/src/Editor.tsx
CHANGED
|
@@ -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,27 +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
|
|
|
99
|
+
// ✅ Restore caret BEFORE inserting anything (image or file)
|
|
100
|
+
restoreToLastCaret();
|
|
101
|
+
|
|
63
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 caret/selection before inserting file link
|
|
71
|
-
if (fn?.s?.focus) fn.s.focus();
|
|
72
|
-
if (fn?.s?.restore) {
|
|
73
|
-
try {
|
|
74
|
-
fn.s.restore();
|
|
75
|
-
} catch {
|
|
76
|
-
// ignore
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
110
|
const tagName = 'a';
|
|
81
111
|
const elm = fn.createInside.element(tagName);
|
|
82
112
|
elm.setAttribute('href', src);
|
|
@@ -93,6 +123,8 @@ export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
|
|
|
93
123
|
// ignore
|
|
94
124
|
}
|
|
95
125
|
}
|
|
126
|
+
|
|
127
|
+
refreshSavedRange();
|
|
96
128
|
}
|
|
97
129
|
});
|
|
98
130
|
}
|
|
@@ -135,6 +167,8 @@ type ConfigParams = {
|
|
|
135
167
|
* Build Jodit config for ReactEditor
|
|
136
168
|
*/
|
|
137
169
|
export const config = ({ includeUploader, apiUrl, imageUrl, onDeleteImage }: ConfigParams = {}) => {
|
|
170
|
+
const selectionRef: SelectionRef = { current: null };
|
|
171
|
+
|
|
138
172
|
const base: any = {
|
|
139
173
|
readonly: false,
|
|
140
174
|
placeholder: 'Start typing...',
|
|
@@ -149,6 +183,9 @@ export const config = ({ includeUploader, apiUrl, imageUrl, onDeleteImage }: Con
|
|
|
149
183
|
showWordsCounter: true,
|
|
150
184
|
showXPathInStatusbar: false,
|
|
151
185
|
|
|
186
|
+
// ✅ Helps preserve selection when editor loses focus (e.g., file dialog)
|
|
187
|
+
saveSelectionOnBlur: true,
|
|
188
|
+
|
|
152
189
|
buttons: [
|
|
153
190
|
'source',
|
|
154
191
|
'|',
|
|
@@ -183,48 +220,115 @@ export const config = ({ includeUploader, apiUrl, imageUrl, onDeleteImage }: Con
|
|
|
183
220
|
],
|
|
184
221
|
};
|
|
185
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
|
+
|
|
186
230
|
if (includeUploader) {
|
|
187
|
-
base.uploader = uploaderConfig(apiUrl, imageUrl);
|
|
231
|
+
base.uploader = uploaderConfig(apiUrl, imageUrl, selectionRef);
|
|
232
|
+
|
|
233
|
+
const selectionCaptureAfterInit = (editor: any) => {
|
|
234
|
+
const capture = () => {
|
|
235
|
+
try {
|
|
236
|
+
const r: Range = editor.s.range;
|
|
237
|
+
selectionRef.current = r ? r.cloneRange() : null;
|
|
238
|
+
} catch {
|
|
239
|
+
selectionRef.current = null;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
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();
|
|
248
|
+
|
|
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
|
+
};
|
|
288
|
+
|
|
289
|
+
base.events = {
|
|
290
|
+
...(base.events || {}),
|
|
291
|
+
};
|
|
292
|
+
base.events.afterInit = composeAfterInit(base.events.afterInit, selectionCaptureAfterInit);
|
|
188
293
|
}
|
|
189
294
|
|
|
190
295
|
if (onDeleteImage) {
|
|
191
296
|
base.events = {
|
|
192
297
|
...(base.events || {}),
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
let prevValue: string = editor.value || '';
|
|
207
|
-
let prevSrcs: Set<string> = extractImageSrcs(prevValue);
|
|
208
|
-
|
|
209
|
-
editor.events.on('change', async () => {
|
|
210
|
-
const currentValue: string = editor.value || '';
|
|
211
|
-
const currentSrcs = extractImageSrcs(currentValue);
|
|
212
|
-
|
|
213
|
-
// src present before, not present now -> deleted
|
|
214
|
-
prevSrcs.forEach((src) => {
|
|
215
|
-
if (!currentSrcs.has(src)) {
|
|
216
|
-
// If imageUrl is defined, you can filter to only your own assets
|
|
217
|
-
if (!imageUrl || src.startsWith(imageUrl)) {
|
|
218
|
-
void onDeleteImage(src);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
});
|
|
298
|
+
};
|
|
299
|
+
|
|
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);
|
|
222
311
|
|
|
223
|
-
|
|
224
|
-
|
|
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
|
+
}
|
|
225
324
|
});
|
|
226
|
-
|
|
325
|
+
|
|
326
|
+
prevValue = currentValue;
|
|
327
|
+
prevSrcs = currentSrcs;
|
|
328
|
+
});
|
|
227
329
|
};
|
|
330
|
+
|
|
331
|
+
base.events.afterInit = composeAfterInit(base.events.afterInit, deleteImageAfterInit);
|
|
228
332
|
}
|
|
229
333
|
|
|
230
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
|
-
|
|
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;
|