jodit 4.18.6 → 4.18.8
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/CHANGELOG.md +19 -0
- package/es2015/jodit.css +1 -1
- package/es2015/jodit.fat.min.js +9 -9
- package/es2015/jodit.js +265 -36
- package/es2015/jodit.min.js +9 -9
- package/es2015/plugins/debug/debug.css +1 -1
- package/es2015/plugins/debug/debug.js +1 -1
- package/es2015/plugins/debug/debug.min.js +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es2018/jodit.fat.min.js +9 -9
- package/es2018/jodit.min.js +7 -7
- package/es2018/plugins/debug/debug.min.js +1 -1
- package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es2021/jodit.css +1 -1
- package/es2021/jodit.fat.min.js +11 -11
- package/es2021/jodit.js +264 -36
- package/es2021/jodit.min.js +7 -7
- package/es2021/plugins/debug/debug.css +1 -1
- package/es2021/plugins/debug/debug.js +1 -1
- package/es2021/plugins/debug/debug.min.js +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es2021.en/jodit.css +1 -1
- package/es2021.en/jodit.fat.min.js +8 -8
- package/es2021.en/jodit.js +264 -36
- package/es2021.en/jodit.min.js +7 -7
- package/es2021.en/plugins/debug/debug.css +1 -1
- package/es2021.en/plugins/debug/debug.js +1 -1
- package/es2021.en/plugins/debug/debug.min.js +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es5/jodit.css +2 -2
- package/es5/jodit.fat.min.js +2 -2
- package/es5/jodit.js +280 -35
- package/es5/jodit.min.css +2 -2
- package/es5/jodit.min.js +2 -2
- package/es5/plugins/debug/debug.css +1 -1
- package/es5/plugins/debug/debug.js +1 -1
- package/es5/plugins/debug/debug.min.js +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es5/polyfills.fat.min.js +1 -1
- package/es5/polyfills.js +1 -1
- package/es5/polyfills.min.js +1 -1
- package/esm/config.d.ts +4 -2
- package/esm/core/constants.js +1 -1
- package/esm/core/decorators/component/component.js +12 -0
- package/esm/core/ui/popup/popup.js +14 -9
- package/esm/jodit.js +5 -1
- package/esm/modules/dialog/dialog.js +18 -3
- package/esm/modules/file-browser/ui/files/files.js +17 -2
- package/esm/modules/file-browser/ui/tree/tree.js +17 -2
- package/esm/plugins/backspace/cases/check-join-neighbors.js +81 -0
- package/esm/plugins/backspace/cases/check-remove-char.d.ts +2 -1
- package/esm/plugins/backspace/cases/check-remove-char.js +31 -9
- package/esm/plugins/backspace/cases/check-unwrap-list-item.d.ts +26 -0
- package/esm/plugins/backspace/cases/check-unwrap-list-item.js +56 -0
- package/esm/plugins/backspace/cases/index.js +2 -0
- package/esm/plugins/backspace/config.d.ts +4 -2
- package/esm/plugins/backspace/config.js +6 -3
- package/esm/plugins/enter/enter.js +1 -1
- package/esm/plugins/enter/helpers/check-br.js +7 -2
- package/esm/plugins/image-properties/image-properties.d.ts +5 -0
- package/esm/plugins/image-properties/image-properties.js +30 -5
- package/esm/plugins/mobile/mobile.js +7 -4
- package/package.json +1 -1
- package/types/config.d.ts +4 -2
- package/types/plugins/backspace/cases/check-remove-char.d.ts +2 -1
- package/types/plugins/backspace/cases/check-unwrap-list-item.d.ts +26 -0
- package/types/plugins/backspace/config.d.ts +4 -2
- package/types/plugins/image-properties/image-properties.d.ts +5 -0
package/es5/polyfills.fat.min.js
CHANGED
package/es5/polyfills.js
CHANGED
package/es5/polyfills.min.js
CHANGED
package/esm/config.d.ts
CHANGED
|
@@ -1034,6 +1034,8 @@ interface Config {
|
|
|
1034
1034
|
interface Config {
|
|
1035
1035
|
/**
|
|
1036
1036
|
* Keyboard hotkey mappings for delete and backspace operations (character, word, sentence).
|
|
1037
|
+
* On macOS the defaults leave out Cmd+Backspace, Ctrl+Backspace, Ctrl+Option+Backspace
|
|
1038
|
+
* and Cmd+Option+Backspace, so the browser applies the system's own bindings to them.
|
|
1037
1039
|
*/
|
|
1038
1040
|
delete: {
|
|
1039
1041
|
hotkeys: {
|
|
@@ -1049,8 +1051,8 @@ interface Config {
|
|
|
1049
1051
|
* key, so the plugin no longer applies that particular behavior.
|
|
1050
1052
|
* Available keys: `remove-unbreakable`, `remove-not-editable`,
|
|
1051
1053
|
* `remove-char`, `table-cell`, `remove-empty-parent`,
|
|
1052
|
-
* `remove-empty-neighbor`, `join-two-lists`, `
|
|
1053
|
-
* `unwrap-first-list-item`.
|
|
1054
|
+
* `remove-empty-neighbor`, `join-two-lists`, `unwrap-list-item`,
|
|
1055
|
+
* `join-neighbors`, `unwrap-first-list-item`.
|
|
1054
1056
|
*
|
|
1055
1057
|
* ```javascript
|
|
1056
1058
|
* Jodit.make('#editor', {
|
package/esm/core/constants.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
4
4
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
5
5
|
*/
|
|
6
|
-
export const APP_VERSION = "4.18.
|
|
6
|
+
export const APP_VERSION = "4.18.8";
|
|
7
7
|
// prettier-ignore
|
|
8
8
|
export const ES = "es2020";
|
|
9
9
|
export const IS_ES_MODERN = true;
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import { IS_PROD } from "../../constants.js";
|
|
7
7
|
import { getClassName } from "../../helpers/utils/get-class-name.js";
|
|
8
8
|
const componentRegistry = new Map();
|
|
9
|
+
const decoratedClasses = new WeakSet();
|
|
10
|
+
const warnedClasses = new WeakSet();
|
|
9
11
|
/**
|
|
10
12
|
* Decorate components and set status isReady after constructor
|
|
11
13
|
* @param constructorFunction - Component constructor class
|
|
@@ -24,8 +26,18 @@ export function component(constructorFunction) {
|
|
|
24
26
|
}
|
|
25
27
|
this.setStatus('ready');
|
|
26
28
|
}
|
|
29
|
+
else if (!IS_PROD &&
|
|
30
|
+
!decoratedClasses.has(this.constructor) &&
|
|
31
|
+
!warnedClasses.has(this.constructor)) {
|
|
32
|
+
// Only the outermost class's decorator sets `ready`, so a subclass
|
|
33
|
+
// without its own decorator stays in `beforeInit`. See #1685
|
|
34
|
+
warnedClasses.add(this.constructor);
|
|
35
|
+
console.warn(`Component "${this.constructor.name || getClassName(this)}" extends a @component class without being decorated itself, so it never becomes ready and its @watch, @debounce, @throttle and @hook handlers never run`);
|
|
36
|
+
}
|
|
27
37
|
}
|
|
28
38
|
}
|
|
39
|
+
decoratedClasses.add(constructorFunction);
|
|
40
|
+
decoratedClasses.add(newConstructorFunction);
|
|
29
41
|
const name = getClassName(constructorFunction.prototype);
|
|
30
42
|
if (componentRegistry.has(name) && !IS_PROD) {
|
|
31
43
|
throw new Error(`Component with name "${name}" is already registered`);
|
|
@@ -13,8 +13,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
13
13
|
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
14
14
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
15
15
|
};
|
|
16
|
+
var Popup_1;
|
|
16
17
|
import { Component } from "../../component/component.js";
|
|
17
|
-
import { autobind, throttle } from "../../decorators/index.js";
|
|
18
|
+
import { autobind, component, throttle } from "../../decorators/index.js";
|
|
18
19
|
import { Dom } from "../../dom/dom.js";
|
|
19
20
|
import { eventEmitter, getContainer } from "../../global.js";
|
|
20
21
|
import { attr, css, getFixedPositionOffset, isFunction, isString, kebabCase, markOwner, position, ucfirst } from "../../helpers/index.js";
|
|
@@ -29,7 +30,7 @@ const EVENTS_FOR_AUTOCLOSE = [
|
|
|
29
30
|
'backSpaceAfterDelete',
|
|
30
31
|
'beforeCommandDelete'
|
|
31
32
|
];
|
|
32
|
-
|
|
33
|
+
let Popup = Popup_1 = class Popup extends UIGroup {
|
|
33
34
|
className() {
|
|
34
35
|
return 'Popup';
|
|
35
36
|
}
|
|
@@ -39,7 +40,7 @@ export class Popup extends UIGroup {
|
|
|
39
40
|
Dom.append(content, childContainer);
|
|
40
41
|
}
|
|
41
42
|
updateParentElement(target) {
|
|
42
|
-
if (target !== this && Component.isInstanceOf(target,
|
|
43
|
+
if (target !== this && Component.isInstanceOf(target, Popup_1)) {
|
|
43
44
|
this.__childrenPopups.forEach(popup => {
|
|
44
45
|
if (!target.closest(popup) && popup.isOpened) {
|
|
45
46
|
popup.close();
|
|
@@ -89,7 +90,7 @@ export class Popup extends UIGroup {
|
|
|
89
90
|
Dom.append(parentContainer, this.container);
|
|
90
91
|
}
|
|
91
92
|
else {
|
|
92
|
-
const popupContainer = getContainer(this.jodit,
|
|
93
|
+
const popupContainer = getContainer(this.jodit, Popup_1);
|
|
93
94
|
if (parentContainer !== this.container.parentElement) {
|
|
94
95
|
Dom.append(popupContainer, this.container);
|
|
95
96
|
}
|
|
@@ -207,13 +208,13 @@ export class Popup extends UIGroup {
|
|
|
207
208
|
};
|
|
208
209
|
const getMatchStrategy = (inBox) => {
|
|
209
210
|
let strategy;
|
|
210
|
-
if (
|
|
211
|
+
if (Popup_1.boxInView(getPointByStrategy(defaultStrategy), inBox)) {
|
|
211
212
|
strategy = defaultStrategy;
|
|
212
213
|
}
|
|
213
214
|
else {
|
|
214
215
|
strategy =
|
|
215
216
|
list.find((key) => {
|
|
216
|
-
if (
|
|
217
|
+
if (Popup_1.boxInView(getPointByStrategy(key), inBox)) {
|
|
217
218
|
return key;
|
|
218
219
|
}
|
|
219
220
|
return;
|
|
@@ -224,7 +225,7 @@ export class Popup extends UIGroup {
|
|
|
224
225
|
// Try to find match position inside Jodit.container
|
|
225
226
|
let strategy = getMatchStrategy(position(this.j.container, this.j));
|
|
226
227
|
// If not found or is not inside window view
|
|
227
|
-
if (!strategy || !
|
|
228
|
+
if (!strategy || !Popup_1.boxInView(getPointByStrategy(strategy), view)) {
|
|
228
229
|
// Find match strategy inside window view
|
|
229
230
|
strategy = getMatchStrategy(view) || strategy || defaultStrategy;
|
|
230
231
|
}
|
|
@@ -280,7 +281,7 @@ export class Popup extends UIGroup {
|
|
|
280
281
|
if (!e.target) {
|
|
281
282
|
return false;
|
|
282
283
|
}
|
|
283
|
-
const box = UIElement.closestElement(e.target,
|
|
284
|
+
const box = UIElement.closestElement(e.target, Popup_1);
|
|
284
285
|
return Boolean(box && (this === box || box.closest(this)));
|
|
285
286
|
}
|
|
286
287
|
__addGlobalListeners() {
|
|
@@ -353,7 +354,7 @@ export class Popup extends UIGroup {
|
|
|
353
354
|
this.close();
|
|
354
355
|
return super.destruct();
|
|
355
356
|
}
|
|
356
|
-
}
|
|
357
|
+
};
|
|
357
358
|
__decorate([
|
|
358
359
|
autobind
|
|
359
360
|
], Popup.prototype, "updatePosition", null);
|
|
@@ -367,3 +368,7 @@ __decorate([
|
|
|
367
368
|
__decorate([
|
|
368
369
|
autobind
|
|
369
370
|
], Popup.prototype, "__closeOnOutsideClick", null);
|
|
371
|
+
Popup = Popup_1 = __decorate([
|
|
372
|
+
component
|
|
373
|
+
], Popup);
|
|
374
|
+
export { Popup };
|
package/esm/jodit.js
CHANGED
|
@@ -557,7 +557,7 @@ let Jodit = Jodit_1 = class Jodit extends ViewWithToolbar {
|
|
|
557
557
|
const hotkeys = this.o.commandToHotkeys[commandName] ||
|
|
558
558
|
this.o.commandToHotkeys[commandNameOriginal] ||
|
|
559
559
|
command.hotkeys;
|
|
560
|
-
if (hotkeys) {
|
|
560
|
+
if (hotkeys && hotkeys.length) {
|
|
561
561
|
this.registerHotkeyToCommand(hotkeys, commandName, options === null || options === void 0 ? void 0 : options.stopPropagation);
|
|
562
562
|
}
|
|
563
563
|
}
|
|
@@ -567,6 +567,10 @@ let Jodit = Jodit_1 = class Jodit extends ViewWithToolbar {
|
|
|
567
567
|
* Register hotkey for command
|
|
568
568
|
*/
|
|
569
569
|
registerHotkeyToCommand(hotkeys, commandName, shouldStop = true) {
|
|
570
|
+
// An empty list (e.g. `commandToHotkeys: { cmd: [] }`) turns the key off
|
|
571
|
+
if (!hotkeys || !hotkeys.length) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
570
574
|
const shortcuts = asArray(hotkeys)
|
|
571
575
|
.map(normalizeKeyAliases)
|
|
572
576
|
.map(hotkey => hotkey + '.hotkey')
|
|
@@ -28,6 +28,10 @@ import { Icon } from "../../core/ui/index.js";
|
|
|
28
28
|
import { View } from "../../core/view/view.js";
|
|
29
29
|
import { ViewWithToolbar } from "../../core/view/view-with-toolbar.js";
|
|
30
30
|
import { Config } from "../../config.js";
|
|
31
|
+
/**
|
|
32
|
+
* A position from 0 up to `max`, or 0 when the panel is larger than the window
|
|
33
|
+
*/
|
|
34
|
+
const clampToWindow = (value, max) => Math.max(0, Math.min(value, max));
|
|
31
35
|
Config.prototype.dialog = {
|
|
32
36
|
namespace: '',
|
|
33
37
|
extraButtons: [],
|
|
@@ -126,6 +130,11 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
|
|
|
126
130
|
this.startY = e.clientY;
|
|
127
131
|
this.startPoint.x = css(this.dialog, 'left');
|
|
128
132
|
this.startPoint.y = css(this.dialog, 'top');
|
|
133
|
+
// Rounded up: at the right edge a fraction of a pixel less room would
|
|
134
|
+
// wrap the header of a panel that has no width of its own
|
|
135
|
+
const { width, height } = this.dialog.getBoundingClientRect();
|
|
136
|
+
this.startPoint.w = Math.ceil(width);
|
|
137
|
+
this.startPoint.h = Math.ceil(height);
|
|
129
138
|
this.setMaxZIndex();
|
|
130
139
|
if (e.cancelable) {
|
|
131
140
|
e.preventDefault();
|
|
@@ -142,7 +151,13 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
|
|
|
142
151
|
}
|
|
143
152
|
__onMouseMove(e) {
|
|
144
153
|
if (this.draggable && this.o.draggable) {
|
|
145
|
-
|
|
154
|
+
const { documentElement } = this.od;
|
|
155
|
+
// Keep the whole panel in the window at the size it had when the
|
|
156
|
+
// drag began. A panel with no width of its own would otherwise
|
|
157
|
+
// shrink as it neared the right edge, and then leave the window
|
|
158
|
+
this.setPosition(clampToWindow(this.startPoint.x + e.clientX - this.startX, (documentElement.clientWidth || this.ow.innerWidth) -
|
|
159
|
+
this.startPoint.w), clampToWindow(this.startPoint.y + e.clientY - this.startY, (documentElement.clientHeight || this.ow.innerHeight) -
|
|
160
|
+
this.startPoint.h));
|
|
146
161
|
if (this.e) {
|
|
147
162
|
/**
|
|
148
163
|
* Fired when dialog box is moved
|
|
@@ -291,8 +306,8 @@ let Dialog = Dialog_1 = class Dialog extends ViewWithToolbar {
|
|
|
291
306
|
}
|
|
292
307
|
// strings on purpose: `css()` would truncate fractional pixels
|
|
293
308
|
css(this.dialog, {
|
|
294
|
-
left: (x
|
|
295
|
-
top: (y
|
|
309
|
+
left: (x !== null && x !== void 0 ? x : left) + 'px',
|
|
310
|
+
top: (y !== null && y !== void 0 ? y : top) + 'px'
|
|
296
311
|
});
|
|
297
312
|
return this;
|
|
298
313
|
}
|
|
@@ -3,9 +3,24 @@
|
|
|
3
3
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
4
4
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
5
5
|
*/
|
|
6
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
7
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
9
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
10
|
+
else
|
|
11
|
+
for (var i = decorators.length - 1; i >= 0; i--)
|
|
12
|
+
if (d = decorators[i])
|
|
13
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
14
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
15
|
+
};
|
|
16
|
+
import { component } from "../../../../core/decorators/index.js";
|
|
6
17
|
import { UIGroup } from "../../../../core/ui/index.js";
|
|
7
|
-
|
|
18
|
+
let FileBrowserFiles = class FileBrowserFiles extends UIGroup {
|
|
8
19
|
className() {
|
|
9
20
|
return 'FileBrowserFiles';
|
|
10
21
|
}
|
|
11
|
-
}
|
|
22
|
+
};
|
|
23
|
+
FileBrowserFiles = __decorate([
|
|
24
|
+
component
|
|
25
|
+
], FileBrowserFiles);
|
|
26
|
+
export { FileBrowserFiles };
|
|
@@ -3,9 +3,24 @@
|
|
|
3
3
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
4
4
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
5
5
|
*/
|
|
6
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
7
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
9
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
10
|
+
else
|
|
11
|
+
for (var i = decorators.length - 1; i >= 0; i--)
|
|
12
|
+
if (d = decorators[i])
|
|
13
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
14
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
15
|
+
};
|
|
16
|
+
import { component } from "../../../../core/decorators/index.js";
|
|
6
17
|
import { UIGroup } from "../../../../core/ui/index.js";
|
|
7
|
-
|
|
18
|
+
let FileBrowserTree = class FileBrowserTree extends UIGroup {
|
|
8
19
|
className() {
|
|
9
20
|
return 'FileBrowserTree';
|
|
10
21
|
}
|
|
11
|
-
}
|
|
22
|
+
};
|
|
23
|
+
FileBrowserTree = __decorate([
|
|
24
|
+
component
|
|
25
|
+
], FileBrowserTree);
|
|
26
|
+
export { FileBrowserTree };
|
|
@@ -11,6 +11,9 @@ import { getMoveFilter } from "../helpers.js";
|
|
|
11
11
|
* @private
|
|
12
12
|
*/
|
|
13
13
|
export function checkJoinNeighbors(jodit, fakeNode, backspace) {
|
|
14
|
+
if (checkJoinBareText(jodit, fakeNode, backspace)) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
14
17
|
let nextBox = fakeNode, mainClosestBox = nextBox;
|
|
15
18
|
// Find the main big closest element
|
|
16
19
|
while (nextBox &&
|
|
@@ -44,6 +47,7 @@ export function checkJoinNeighbors(jodit, fakeNode, backspace) {
|
|
|
44
47
|
if (sibling &&
|
|
45
48
|
(checkPullNextLine(jodit, fakeNode, box, sibling, backspace) ||
|
|
46
49
|
checkMoveListContent(jodit, box, sibling, backspace) ||
|
|
50
|
+
checkJoinInlineSibling(jodit, box, sibling, backspace) ||
|
|
47
51
|
moveContentAndRemoveEmpty(jodit, box, findEdgeBlock(resolveTableSibling(sibling, backspace), backspace, jodit.editor), backspace))) {
|
|
48
52
|
jodit.s.setCursorBefore(fakeNode);
|
|
49
53
|
return true;
|
|
@@ -143,6 +147,83 @@ function checkPullNextLine(jodit, fakeNode, box, sibling, backspace) {
|
|
|
143
147
|
}
|
|
144
148
|
return true;
|
|
145
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* A block next to bare text in the same parent joins that text, as the
|
|
152
|
+
* browsers do: `<li>one<p>|two</p></li>` gives `<li>one|two</li>`, and
|
|
153
|
+
* `<li><p>one|</p>two</li>` gives `<li><p>one|two</p></li>`
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
function checkJoinInlineSibling(jodit, box, sibling, backspace) {
|
|
157
|
+
if (Dom.isBlock(sibling) ||
|
|
158
|
+
!Dom.isBlock(box) ||
|
|
159
|
+
Dom.isList(box) ||
|
|
160
|
+
// The caret's block is nested deeper than the text: `text<div><p>|x</p></div>`
|
|
161
|
+
box.parentNode !== sibling.parentNode ||
|
|
162
|
+
Array.from(box.childNodes).some(node => Dom.isBlock(node))) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (backspace) {
|
|
166
|
+
// Text after the block keeps its own line
|
|
167
|
+
const next = Dom.findNotEmptySibling(box, false);
|
|
168
|
+
if (next && !Dom.isBlock(next) && !Dom.isTag(next, 'br')) {
|
|
169
|
+
Dom.after(box, jodit.createInside.element('br'));
|
|
170
|
+
}
|
|
171
|
+
Dom.unwrap(box);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
const filter = getMoveFilter(jodit);
|
|
175
|
+
for (let node = sibling; node && !Dom.isBlock(node);) {
|
|
176
|
+
const next = node.nextSibling;
|
|
177
|
+
if (Dom.isTag(node, 'br')) {
|
|
178
|
+
Dom.safeRemove(node);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
filter(node) ? Dom.append(box, node) : Dom.safeRemove(node);
|
|
182
|
+
node = next;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Bare text next to a block in the same parent, as the browsers join them:
|
|
188
|
+
* Backspace in `<div><p>a</p>|b</div>` gives `<div><p>a|b</p></div>`, Delete
|
|
189
|
+
* in `<div>a|<p>b</p>c</div>` gives `<div>a|b<br>c</div>`
|
|
190
|
+
*/
|
|
191
|
+
function checkJoinBareText(jodit, fakeNode, backspace) {
|
|
192
|
+
const block = Dom.findNotEmptySibling(fakeNode, backspace);
|
|
193
|
+
if (!Dom.isElement(block) ||
|
|
194
|
+
!Dom.isBlock(block) ||
|
|
195
|
+
Dom.isList(block) ||
|
|
196
|
+
Dom.isTag(block, 'table') ||
|
|
197
|
+
Dom.isCell(block) ||
|
|
198
|
+
!Dom.isContentEditable(block, jodit.editor) ||
|
|
199
|
+
Array.from(block.childNodes).some(node => Dom.isBlock(node))) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
const filter = getMoveFilter(jodit);
|
|
203
|
+
if (backspace) {
|
|
204
|
+
// The caret's line goes to the end of the block before it
|
|
205
|
+
for (let node = fakeNode; node && !Dom.isBlock(node);) {
|
|
206
|
+
const next = node.nextSibling;
|
|
207
|
+
if (Dom.isTag(node, 'br')) {
|
|
208
|
+
Dom.safeRemove(node);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
filter(node) ? Dom.append(block, node) : Dom.safeRemove(node);
|
|
212
|
+
node = next;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// The block's content joins the caret's line, and text after the
|
|
217
|
+
// block keeps its own line
|
|
218
|
+
const next = Dom.findNotEmptySibling(block, false);
|
|
219
|
+
if (next && !Dom.isBlock(next) && !Dom.isTag(next, 'br')) {
|
|
220
|
+
Dom.after(block, jodit.createInside.element('br'));
|
|
221
|
+
}
|
|
222
|
+
Dom.unwrap(block);
|
|
223
|
+
}
|
|
224
|
+
jodit.s.setCursorBefore(fakeNode);
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
146
227
|
function checkMoveListContent(jodit, mainClosestBox, sibling, backspace) {
|
|
147
228
|
// Process UL/LI/OL cases
|
|
148
229
|
const siblingIsList = Dom.isTag(sibling, LIST_TAGS);
|
|
@@ -19,6 +19,7 @@ import type { DeleteMode } from "../interface";
|
|
|
19
19
|
* ```html
|
|
20
20
|
* t|st
|
|
21
21
|
* ```
|
|
22
|
+
* With `stopBeforeSpace`, nothing is removed when the next char is a space.
|
|
22
23
|
* @private
|
|
23
24
|
*/
|
|
24
|
-
export declare function checkRemoveChar(jodit: IJodit, fakeNode: Node, backspace: boolean, mode: DeleteMode): boolean;
|
|
25
|
+
export declare function checkRemoveChar(jodit: IJodit, fakeNode: Node, backspace: boolean, mode: DeleteMode, stopBeforeSpace?: boolean): boolean;
|
|
@@ -18,10 +18,11 @@ import { findMostNestedNeighbor } from "../helpers.js";
|
|
|
18
18
|
* ```html
|
|
19
19
|
* t|st
|
|
20
20
|
* ```
|
|
21
|
+
* With `stopBeforeSpace`, nothing is removed when the next char is a space.
|
|
21
22
|
* @private
|
|
22
23
|
*/
|
|
23
24
|
// eslint-disable-next-line complexity
|
|
24
|
-
export function checkRemoveChar(jodit, fakeNode, backspace, mode) {
|
|
25
|
+
export function checkRemoveChar(jodit, fakeNode, backspace, mode, stopBeforeSpace = false) {
|
|
25
26
|
var _a, _b;
|
|
26
27
|
const step = backspace ? -1 : 1;
|
|
27
28
|
const anotherSibling = Dom.sibling(fakeNode, !backspace);
|
|
@@ -40,6 +41,10 @@ export function checkRemoveChar(jodit, fakeNode, backspace, mode) {
|
|
|
40
41
|
break;
|
|
41
42
|
}
|
|
42
43
|
if ((_a = sibling.nodeValue) === null || _a === void 0 ? void 0 : _a.length) {
|
|
44
|
+
if (stopBeforeSpace &&
|
|
45
|
+
isSpace(nextChar(sibling.nodeValue, backspace))) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
43
48
|
removed = tryRemoveChar(sibling, backspace, step, anotherSibling);
|
|
44
49
|
if (!sibling.nodeValue.length &&
|
|
45
50
|
Dom.isInlineBlock(sibling.parentNode)) {
|
|
@@ -79,12 +84,13 @@ function getNextInlineSibling(sibling, backspace, root) {
|
|
|
79
84
|
let nextSibling = Dom.sibling(sibling, backspace);
|
|
80
85
|
if (!nextSibling && sibling.parentNode && sibling.parentNode !== root) {
|
|
81
86
|
nextSibling = findMostNestedNeighbor(sibling, !backspace, root, true);
|
|
82
|
-
// Do not cross a
|
|
83
|
-
// neighbor can be the text of the parent/previous item
|
|
84
|
-
//
|
|
87
|
+
// Do not cross a block boundary. At the start of a list item the
|
|
88
|
+
// neighbor can be the text of the parent/previous item (#1277), and at
|
|
89
|
+
// the start of a paragraph the text before it in the same parent
|
|
90
|
+
// (#1693); deleting a char there is wrong — the join cases handle them.
|
|
85
91
|
if (nextSibling &&
|
|
86
|
-
Dom.closest(sibling,
|
|
87
|
-
Dom.closest(nextSibling,
|
|
92
|
+
Dom.closest(sibling, Dom.isBlock, root) !==
|
|
93
|
+
Dom.closest(nextSibling, Dom.isBlock, root)) {
|
|
88
94
|
return null;
|
|
89
95
|
}
|
|
90
96
|
}
|
|
@@ -160,8 +166,24 @@ function replaceSpaceOnNBSP(anotherSibling, backspace, value) {
|
|
|
160
166
|
}
|
|
161
167
|
function checkRepeatRemoveCharAction(backspace, sibling, fakeNode, mode, removed, jodit) {
|
|
162
168
|
call(backspace ? Dom.after : Dom.before, sibling, fakeNode);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
const wordChar = mode === 'word' && !isSpace(removed);
|
|
170
|
+
// Backspace by word removes the spaces before the caret, then the word, and keeps the space in front of it
|
|
171
|
+
if (mode === 'sentence' || wordChar || (mode === 'word' && backspace)) {
|
|
172
|
+
checkRemoveChar(jodit, fakeNode, backspace, mode, wordChar && backspace);
|
|
166
173
|
}
|
|
167
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* The char tryRemoveChar would remove next
|
|
177
|
+
*/
|
|
178
|
+
function nextChar(value, backspace) {
|
|
179
|
+
const chars = toArray(value);
|
|
180
|
+
const step = backspace ? -1 : 1;
|
|
181
|
+
let index = backspace ? chars.length - 1 : 0;
|
|
182
|
+
while (chars[index] === INVISIBLE_SPACE) {
|
|
183
|
+
index += step;
|
|
184
|
+
}
|
|
185
|
+
return chars[index];
|
|
186
|
+
}
|
|
187
|
+
function isSpace(char) {
|
|
188
|
+
return char === ' ' || char === NBSP_SPACE;
|
|
189
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Jodit Editor (https://xdsoft.net/jodit/)
|
|
3
|
+
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
4
|
+
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @module plugins/backspace
|
|
8
|
+
*/
|
|
9
|
+
import type { IJodit } from "../../../types/index";
|
|
10
|
+
/**
|
|
11
|
+
* Backspace at the start of a later list item takes the bullet off, as Word
|
|
12
|
+
* does: the item becomes a paragraph where it was, and the list is split
|
|
13
|
+
* around it. A split `<ol>` carries its numbering on with `start`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```html
|
|
17
|
+
* <ol><li>first</li><li>|second</li><li>third</li></ol>
|
|
18
|
+
* ```
|
|
19
|
+
* Result
|
|
20
|
+
* ```html
|
|
21
|
+
* <ol><li>first</li></ol><p>|second</p><ol start="2"><li>third</li></ol>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkUnwrapListItem(jodit: IJodit, fakeNode: Node, backspace: boolean): boolean;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Jodit Editor (https://xdsoft.net/jodit/)
|
|
3
|
+
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
4
|
+
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
5
|
+
*/
|
|
6
|
+
import { Dom } from "../../../core/dom/dom.js";
|
|
7
|
+
/**
|
|
8
|
+
* Backspace at the start of a later list item takes the bullet off, as Word
|
|
9
|
+
* does: the item becomes a paragraph where it was, and the list is split
|
|
10
|
+
* around it. A split `<ol>` carries its numbering on with `start`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```html
|
|
14
|
+
* <ol><li>first</li><li>|second</li><li>third</li></ol>
|
|
15
|
+
* ```
|
|
16
|
+
* Result
|
|
17
|
+
* ```html
|
|
18
|
+
* <ol><li>first</li></ol><p>|second</p><ol start="2"><li>third</li></ol>
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @private
|
|
22
|
+
*/
|
|
23
|
+
export function checkUnwrapListItem(jodit, fakeNode, backspace) {
|
|
24
|
+
if (!backspace) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const li = Dom.closest(fakeNode, 'li', jodit.editor);
|
|
28
|
+
const list = li ? li.parentElement : null;
|
|
29
|
+
if (!li ||
|
|
30
|
+
!Dom.isList(list) ||
|
|
31
|
+
!li.previousElementSibling ||
|
|
32
|
+
Dom.isEmpty(li) ||
|
|
33
|
+
Array.from(li.childNodes).some(node => Dom.isBlock(node)) ||
|
|
34
|
+
!jodit.s.cursorInTheEdge(true, li)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const p = jodit.createInside.element(jodit.o.enterBlock);
|
|
38
|
+
Dom.after(list, p);
|
|
39
|
+
if (li.nextElementSibling) {
|
|
40
|
+
const rest = list.cloneNode(false);
|
|
41
|
+
// Two lists cannot share one id
|
|
42
|
+
rest.removeAttribute('id');
|
|
43
|
+
while (li.nextSibling) {
|
|
44
|
+
Dom.append(rest, li.nextSibling);
|
|
45
|
+
}
|
|
46
|
+
if (Dom.isTag(list, 'ol')) {
|
|
47
|
+
const start = parseInt(list.getAttribute('start') || '1', 10) || 1;
|
|
48
|
+
rest.setAttribute('start', String(start + list.children.length - 1));
|
|
49
|
+
}
|
|
50
|
+
Dom.after(p, rest);
|
|
51
|
+
}
|
|
52
|
+
Dom.moveContent(li, p);
|
|
53
|
+
Dom.safeRemove(li);
|
|
54
|
+
jodit.s.setCursorBefore(fakeNode);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
@@ -12,6 +12,7 @@ import { checkRemoveEmptyParent } from "./check-remove-empty-parent.js";
|
|
|
12
12
|
import { checkRemoveUnbreakableElement } from "./check-remove-unbreakable-element.js";
|
|
13
13
|
import { checkTableCell } from "./check-table-cell.js";
|
|
14
14
|
import { checkUnwrapFirstListItem } from "./check-unwrap-first-list-item.js";
|
|
15
|
+
import { checkUnwrapListItem } from "./check-unwrap-list-item.js";
|
|
15
16
|
/**
|
|
16
17
|
* Ordered delete/backspace cases with stable keys. The first one returning
|
|
17
18
|
* `true` wins. The keys are stable across minified builds (function names are
|
|
@@ -26,6 +27,7 @@ export const casesMap = [
|
|
|
26
27
|
['remove-empty-parent', checkRemoveEmptyParent],
|
|
27
28
|
['remove-empty-neighbor', checkRemoveEmptyNeighbor],
|
|
28
29
|
['join-two-lists', checkJoinTwoLists],
|
|
30
|
+
['unwrap-list-item', checkUnwrapListItem],
|
|
29
31
|
['join-neighbors', checkJoinNeighbors],
|
|
30
32
|
['unwrap-first-list-item', checkUnwrapFirstListItem]
|
|
31
33
|
];
|