@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 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
- if ((_b = fn === null || fn === void 0 ? void 0 : fn.s) === null || _b === void 0 ? void 0 : _b.restore) {
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 (_d) {
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 ((_c = fn === null || fn === void 0 ? void 0 : fn.s) === null || _c === void 0 ? void 0 : _c.setCursorAfter) {
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 (_e) {
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) ? e.data.messages.join('') : '';
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 = _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;
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.map(function (img) { return img.getAttribute('src') || ''; }).filter(function (src) { return !!src; }));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbs-dev/react-editor",
3
- "version": "1.6.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,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
- // If it's an image => insert <img>, otherwise insert <a href="...">
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 before inserting file link
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
- if (fn?.s?.restore) {
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) ? e.data.messages.join('') : '';
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
- * Build Jodit config for ReactEditor
136
- */
137
- export const config = ({ includeUploader, apiUrl, imageUrl, onDeleteImage }: ConfigParams = {}) => {
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(imgs.map((img) => img.getAttribute('src') || '').filter((src) => !!src));
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;