@mbs-dev/react-editor 1.6.0 → 1.7.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 +42 -8
- package/package.json +1 -1
- package/src/Editor.tsx +57 -17
- package/types/Editor.d.ts +1 -1
package/dist/Editor.js
CHANGED
|
@@ -94,7 +94,7 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
|
|
|
94
94
|
var fn = this.jodit;
|
|
95
95
|
if (((_a = e === null || e === void 0 ? void 0 : e.data) === null || _a === void 0 ? void 0 : _a.files) && e.data.files.length) {
|
|
96
96
|
e.data.files.forEach(function (filename) {
|
|
97
|
-
var _a, _b, _c;
|
|
97
|
+
var _a, _b, _c, _d;
|
|
98
98
|
var src = imageUrl ? "".concat(imageUrl, "/").concat(filename) : filename;
|
|
99
99
|
if (isImageByExtension(filename, _this.imagesExtensions || ['jpg', 'png', 'jpeg', 'gif', 'webp'])) {
|
|
100
100
|
var tagName = 'img';
|
|
@@ -105,11 +105,19 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
|
|
|
105
105
|
else {
|
|
106
106
|
if ((_a = fn === null || fn === void 0 ? void 0 : fn.s) === null || _a === void 0 ? void 0 : _a.focus)
|
|
107
107
|
fn.s.focus();
|
|
108
|
-
|
|
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) {
|
|
109
117
|
try {
|
|
110
118
|
fn.s.restore();
|
|
111
119
|
}
|
|
112
|
-
catch (
|
|
120
|
+
catch (_f) {
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
123
|
var tagName = 'a';
|
|
@@ -119,11 +127,11 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
|
|
|
119
127
|
elm.setAttribute('rel', 'noopener noreferrer');
|
|
120
128
|
elm.textContent = getDisplayNameFromPath(filename);
|
|
121
129
|
fn.s.insertNode(elm);
|
|
122
|
-
if ((
|
|
130
|
+
if ((_d = fn === null || fn === void 0 ? void 0 : fn.s) === null || _d === void 0 ? void 0 : _d.setCursorAfter) {
|
|
123
131
|
try {
|
|
124
132
|
fn.s.setCursorAfter(elm);
|
|
125
133
|
}
|
|
126
|
-
catch (
|
|
134
|
+
catch (_g) {
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
}
|
|
@@ -133,7 +141,9 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
|
|
|
133
141
|
},
|
|
134
142
|
getMessage: function (e) {
|
|
135
143
|
var _a;
|
|
136
|
-
return ((_a = e === null || e === void 0 ? void 0 : e.data) === null || _a === void 0 ? void 0 : _a.messages) && Array.isArray(e.data.messages)
|
|
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
|
+
: '';
|
|
137
147
|
},
|
|
138
148
|
process: function (resp) {
|
|
139
149
|
var files = [];
|
|
@@ -153,7 +163,8 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
|
|
|
153
163
|
}); };
|
|
154
164
|
exports.uploaderConfig = uploaderConfig;
|
|
155
165
|
var config = function (_a) {
|
|
156
|
-
var _b
|
|
166
|
+
var _b;
|
|
167
|
+
var _c = _a === void 0 ? {} : _a, includeUploader = _c.includeUploader, apiUrl = _c.apiUrl, imageUrl = _c.imageUrl, onDeleteImage = _c.onDeleteImage;
|
|
157
168
|
var base = {
|
|
158
169
|
readonly: false,
|
|
159
170
|
placeholder: 'Start typing...',
|
|
@@ -203,14 +214,37 @@ var config = function (_a) {
|
|
|
203
214
|
if (includeUploader) {
|
|
204
215
|
base.uploader = (0, exports.uploaderConfig)(apiUrl, imageUrl);
|
|
205
216
|
}
|
|
217
|
+
base.events = __assign(__assign({}, (base.events || {})), { afterInit: function (editor) {
|
|
218
|
+
var saveRange = function () {
|
|
219
|
+
var _a;
|
|
220
|
+
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
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (_b) {
|
|
226
|
+
}
|
|
227
|
+
};
|
|
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
|
+
} });
|
|
206
234
|
if (onDeleteImage) {
|
|
235
|
+
var prevAfterInit_1 = (_b = base.events) === null || _b === void 0 ? void 0 : _b.afterInit;
|
|
207
236
|
base.events = __assign(__assign({}, (base.events || {})), { afterInit: function (editor) {
|
|
208
237
|
var _this = this;
|
|
238
|
+
if (typeof prevAfterInit_1 === 'function') {
|
|
239
|
+
prevAfterInit_1(editor);
|
|
240
|
+
}
|
|
209
241
|
var extractImageSrcs = function (html) {
|
|
210
242
|
var container = document.createElement('div');
|
|
211
243
|
container.innerHTML = html || '';
|
|
212
244
|
var imgs = Array.from(container.getElementsByTagName('img'));
|
|
213
|
-
return new Set(imgs
|
|
245
|
+
return new Set(imgs
|
|
246
|
+
.map(function (img) { return img.getAttribute('src') || ''; })
|
|
247
|
+
.filter(function (src) { return !!src; }));
|
|
214
248
|
};
|
|
215
249
|
var prevValue = editor.value || '';
|
|
216
250
|
var prevSrcs = extractImageSrcs(prevValue);
|
package/package.json
CHANGED
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
|
|
22
24
|
const getDisplayNameFromPath = (filename: string): string => {
|
|
23
25
|
const clean = (filename || '').split('?')[0]?.split('#')[0] ?? '';
|
|
24
26
|
const last = clean.split('/').pop();
|
|
25
27
|
const base = last ? decodeURIComponent(last) : filename;
|
|
26
28
|
|
|
27
|
-
// remove extension
|
|
28
29
|
const dotIndex = base.lastIndexOf('.');
|
|
29
30
|
const noExt = dotIndex > 0 ? base.slice(0, dotIndex) : base;
|
|
30
31
|
|
|
31
|
-
// remove last "-..." suffix
|
|
32
32
|
const dashIndex = noExt.lastIndexOf('-');
|
|
33
33
|
if (dashIndex > 0) {
|
|
34
34
|
return noExt.slice(0, dashIndex);
|
|
@@ -60,16 +60,24 @@ export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
|
|
|
60
60
|
e.data.files.forEach((filename: string) => {
|
|
61
61
|
const src = imageUrl ? `${imageUrl}/${filename}` : filename;
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// If it's an image => insert <img>, otherwise insert <a href="...">
|
|
64
64
|
if (isImageByExtension(filename, this.imagesExtensions || ['jpg', 'png', 'jpeg', 'gif', 'webp'])) {
|
|
65
65
|
const tagName = 'img';
|
|
66
66
|
const elm = fn.createInside.element(tagName);
|
|
67
67
|
elm.setAttribute('src', src);
|
|
68
68
|
fn.s.insertImage(elm as HTMLImageElement, null, fn.o.imageDefaultWidth);
|
|
69
69
|
} else {
|
|
70
|
-
// ✅ FIX: restore caret/selection
|
|
70
|
+
// ✅ FIX: restore the last known caret/selection inside the editor (works in table cells)
|
|
71
71
|
if (fn?.s?.focus) fn.s.focus();
|
|
72
|
-
|
|
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) {
|
|
73
81
|
try {
|
|
74
82
|
fn.s.restore();
|
|
75
83
|
} catch {
|
|
@@ -100,7 +108,9 @@ export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
|
|
|
100
108
|
return !!e?.success;
|
|
101
109
|
},
|
|
102
110
|
getMessage(e: any): string {
|
|
103
|
-
return e?.data?.messages && Array.isArray(e.data.messages)
|
|
111
|
+
return e?.data?.messages && Array.isArray(e.data.messages)
|
|
112
|
+
? e.data.messages.join('')
|
|
113
|
+
: '';
|
|
104
114
|
},
|
|
105
115
|
process(resp: any): { files: any[]; error: string; msg: string } {
|
|
106
116
|
const files: any[] = [];
|
|
@@ -131,10 +141,12 @@ type ConfigParams = {
|
|
|
131
141
|
onDeleteImage?: (imageUrl: string) => void | Promise<void>;
|
|
132
142
|
};
|
|
133
143
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
144
|
+
export const config = ({
|
|
145
|
+
includeUploader,
|
|
146
|
+
apiUrl,
|
|
147
|
+
imageUrl,
|
|
148
|
+
onDeleteImage,
|
|
149
|
+
}: ConfigParams = {}) => {
|
|
138
150
|
const base: any = {
|
|
139
151
|
readonly: false,
|
|
140
152
|
placeholder: 'Start typing...',
|
|
@@ -187,20 +199,50 @@ export const config = ({ includeUploader, apiUrl, imageUrl, onDeleteImage }: Con
|
|
|
187
199
|
base.uploader = uploaderConfig(apiUrl, imageUrl);
|
|
188
200
|
}
|
|
189
201
|
|
|
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 = () => {
|
|
207
|
+
try {
|
|
208
|
+
if (editor?.s?.range) {
|
|
209
|
+
(editor as any).__mbs_lastRange = editor.s.range.cloneRange();
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// ignore
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
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
|
+
};
|
|
224
|
+
|
|
190
225
|
if (onDeleteImage) {
|
|
226
|
+
const prevAfterInit = base.events?.afterInit;
|
|
227
|
+
|
|
191
228
|
base.events = {
|
|
192
229
|
...(base.events || {}),
|
|
193
|
-
/**
|
|
194
|
-
* We use the value diff to detect removed <img> src.
|
|
195
|
-
* This avoids false calls during upload (where DOM nodes can be replaced).
|
|
196
|
-
*/
|
|
197
230
|
afterInit(editor: any) {
|
|
231
|
+
// keep existing selection saver
|
|
232
|
+
if (typeof prevAfterInit === 'function') {
|
|
233
|
+
prevAfterInit(editor);
|
|
234
|
+
}
|
|
235
|
+
|
|
198
236
|
const extractImageSrcs = (html: string): Set<string> => {
|
|
199
237
|
const container = document.createElement('div');
|
|
200
238
|
container.innerHTML = html || '';
|
|
201
239
|
const imgs = Array.from(container.getElementsByTagName('img')) as HTMLImageElement[];
|
|
202
240
|
|
|
203
|
-
return new Set(
|
|
241
|
+
return new Set(
|
|
242
|
+
imgs
|
|
243
|
+
.map((img) => img.getAttribute('src') || '')
|
|
244
|
+
.filter((src) => !!src)
|
|
245
|
+
);
|
|
204
246
|
};
|
|
205
247
|
|
|
206
248
|
let prevValue: string = editor.value || '';
|
|
@@ -210,10 +252,8 @@ export const config = ({ includeUploader, apiUrl, imageUrl, onDeleteImage }: Con
|
|
|
210
252
|
const currentValue: string = editor.value || '';
|
|
211
253
|
const currentSrcs = extractImageSrcs(currentValue);
|
|
212
254
|
|
|
213
|
-
// src present before, not present now -> deleted
|
|
214
255
|
prevSrcs.forEach((src) => {
|
|
215
256
|
if (!currentSrcs.has(src)) {
|
|
216
|
-
// If imageUrl is defined, you can filter to only your own assets
|
|
217
257
|
if (!imageUrl || src.startsWith(imageUrl)) {
|
|
218
258
|
void onDeleteImage(src);
|
|
219
259
|
}
|
package/types/Editor.d.ts
CHANGED
|
@@ -25,5 +25,5 @@ type ConfigParams = {
|
|
|
25
25
|
imageUrl?: string;
|
|
26
26
|
onDeleteImage?: (imageUrl: string) => void | Promise<void>;
|
|
27
27
|
};
|
|
28
|
-
export declare const config: ({ includeUploader, apiUrl, imageUrl, onDeleteImage }?: ConfigParams) => any;
|
|
28
|
+
export declare const config: ({ includeUploader, apiUrl, imageUrl, onDeleteImage, }?: ConfigParams) => any;
|
|
29
29
|
export default ReactEditor;
|