@mbs-dev/react-editor 1.5.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 CHANGED
@@ -94,6 +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, _d;
97
98
  var src = imageUrl ? "".concat(imageUrl, "/").concat(filename) : filename;
98
99
  if (isImageByExtension(filename, _this.imagesExtensions || ['jpg', 'png', 'jpeg', 'gif', 'webp'])) {
99
100
  var tagName = 'img';
@@ -102,6 +103,23 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
102
103
  fn.s.insertImage(elm, null, fn.o.imageDefaultWidth);
103
104
  }
104
105
  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
+ }
105
123
  var tagName = 'a';
106
124
  var elm = fn.createInside.element(tagName);
107
125
  elm.setAttribute('href', src);
@@ -109,6 +127,13 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
109
127
  elm.setAttribute('rel', 'noopener noreferrer');
110
128
  elm.textContent = getDisplayNameFromPath(filename);
111
129
  fn.s.insertNode(elm);
130
+ if ((_d = fn === null || fn === void 0 ? void 0 : fn.s) === null || _d === void 0 ? void 0 : _d.setCursorAfter) {
131
+ try {
132
+ fn.s.setCursorAfter(elm);
133
+ }
134
+ catch (_g) {
135
+ }
136
+ }
112
137
  }
113
138
  });
114
139
  }
@@ -138,7 +163,8 @@ var uploaderConfig = function (apiUrl, imageUrl) { return ({
138
163
  }); };
139
164
  exports.uploaderConfig = uploaderConfig;
140
165
  var config = function (_a) {
141
- var _b = _a === void 0 ? {} : _a, includeUploader = _b.includeUploader, apiUrl = _b.apiUrl, imageUrl = _b.imageUrl, onDeleteImage = _b.onDeleteImage;
166
+ var _b;
167
+ var _c = _a === void 0 ? {} : _a, includeUploader = _c.includeUploader, apiUrl = _c.apiUrl, imageUrl = _c.imageUrl, onDeleteImage = _c.onDeleteImage;
142
168
  var base = {
143
169
  readonly: false,
144
170
  placeholder: 'Start typing...',
@@ -188,9 +214,30 @@ var config = function (_a) {
188
214
  if (includeUploader) {
189
215
  base.uploader = (0, exports.uploaderConfig)(apiUrl, imageUrl);
190
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
+ } });
191
234
  if (onDeleteImage) {
235
+ var prevAfterInit_1 = (_b = base.events) === null || _b === void 0 ? void 0 : _b.afterInit;
192
236
  base.events = __assign(__assign({}, (base.events || {})), { afterInit: function (editor) {
193
237
  var _this = this;
238
+ if (typeof prevAfterInit_1 === 'function') {
239
+ prevAfterInit_1(editor);
240
+ }
194
241
  var extractImageSrcs = function (html) {
195
242
  var container = document.createElement('div');
196
243
  container.innerHTML = html || '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbs-dev/react-editor",
3
- "version": "1.5.0",
3
+ "version": "1.7.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,18 +19,16 @@ const isImageByExtension = (filename: string, imageExts: string[]): boolean => {
19
19
  return !!ext && imageExts.includes(ext);
20
20
  };
21
21
 
22
- // ✅ UPDATED: for files, display name without extension and without last "-..."
22
+ // display name without extension and without last "-..."
23
23
  // Example: recu-202600004-2-69956651a3b98099024323.pdf -> recu-202600004-2
24
24
  const getDisplayNameFromPath = (filename: string): string => {
25
25
  const clean = (filename || '').split('?')[0]?.split('#')[0] ?? '';
26
26
  const last = clean.split('/').pop();
27
27
  const base = last ? decodeURIComponent(last) : filename;
28
28
 
29
- // remove extension
30
29
  const dotIndex = base.lastIndexOf('.');
31
30
  const noExt = dotIndex > 0 ? base.slice(0, dotIndex) : base;
32
31
 
33
- // remove last "-..." suffix
34
32
  const dashIndex = noExt.lastIndexOf('-');
35
33
  if (dashIndex > 0) {
36
34
  return noExt.slice(0, dashIndex);
@@ -43,10 +41,7 @@ const getDisplayNameFromPath = (filename: string): string => {
43
41
  * Uploader configuration for Jodit
44
42
  * Handles image upload + insertion in the editor
45
43
  */
46
- export const uploaderConfig = (
47
- apiUrl?: string,
48
- imageUrl?: string
49
- ) => ({
44
+ export const uploaderConfig = (apiUrl?: string, imageUrl?: string) => ({
50
45
  imagesExtensions: ['jpg', 'png', 'jpeg', 'gif', 'webp'],
51
46
  filesVariableName(t: number): string {
52
47
  return 'files[' + t + ']';
@@ -65,20 +60,47 @@ export const uploaderConfig = (
65
60
  e.data.files.forEach((filename: string) => {
66
61
  const src = imageUrl ? `${imageUrl}/${filename}` : filename;
67
62
 
68
- // If it's an image => insert <img>, otherwise insert <a href="...">
63
+ // If it's an image => insert <img>, otherwise insert <a href="...">
69
64
  if (isImageByExtension(filename, this.imagesExtensions || ['jpg', 'png', 'jpeg', 'gif', 'webp'])) {
70
65
  const tagName = 'img';
71
66
  const elm = fn.createInside.element(tagName);
72
67
  elm.setAttribute('src', src);
73
68
  fn.s.insertImage(elm as HTMLImageElement, null, fn.o.imageDefaultWidth);
74
69
  } 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
+
75
88
  const tagName = 'a';
76
89
  const elm = fn.createInside.element(tagName);
77
90
  elm.setAttribute('href', src);
78
91
  elm.setAttribute('target', '_blank');
79
92
  elm.setAttribute('rel', 'noopener noreferrer');
80
93
  elm.textContent = getDisplayNameFromPath(filename);
94
+
81
95
  fn.s.insertNode(elm);
96
+
97
+ if (fn?.s?.setCursorAfter) {
98
+ try {
99
+ fn.s.setCursorAfter(elm);
100
+ } catch {
101
+ // ignore
102
+ }
103
+ }
82
104
  }
83
105
  });
84
106
  }
@@ -119,9 +141,6 @@ type ConfigParams = {
119
141
  onDeleteImage?: (imageUrl: string) => void | Promise<void>;
120
142
  };
121
143
 
122
- /**
123
- * Build Jodit config for ReactEditor
124
- */
125
144
  export const config = ({
126
145
  includeUploader,
127
146
  apiUrl,
@@ -180,14 +199,40 @@ export const config = ({
180
199
  base.uploader = uploaderConfig(apiUrl, imageUrl);
181
200
  }
182
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
+
183
225
  if (onDeleteImage) {
226
+ const prevAfterInit = base.events?.afterInit;
227
+
184
228
  base.events = {
185
229
  ...(base.events || {}),
186
- /**
187
- * We use the value diff to detect removed <img> src.
188
- * This avoids false calls during upload (where DOM nodes can be replaced).
189
- */
190
230
  afterInit(editor: any) {
231
+ // keep existing selection saver
232
+ if (typeof prevAfterInit === 'function') {
233
+ prevAfterInit(editor);
234
+ }
235
+
191
236
  const extractImageSrcs = (html: string): Set<string> => {
192
237
  const container = document.createElement('div');
193
238
  container.innerHTML = html || '';
@@ -207,10 +252,8 @@ export const config = ({
207
252
  const currentValue: string = editor.value || '';
208
253
  const currentSrcs = extractImageSrcs(currentValue);
209
254
 
210
- // src present before, not present now -> deleted
211
255
  prevSrcs.forEach((src) => {
212
256
  if (!currentSrcs.has(src)) {
213
- // If imageUrl is defined, you can filter to only your own assets
214
257
  if (!imageUrl || src.startsWith(imageUrl)) {
215
258
  void onDeleteImage(src);
216
259
  }