161 lines
5.5 KiB
JavaScript
161 lines
5.5 KiB
JavaScript
/*!
|
|
* Jodit Editor (https://xdsoft.net/jodit/)
|
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
* Copyright (c) 2013-2025 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
|
|
*/
|
|
import { INVISIBLE_SPACE, NBSP_SPACE } from "../../../core/constants.js";
|
|
import { Dom } from "../../../core/dom/index.js";
|
|
import { call, isVoid, toArray, trimInv } from "../../../core/helpers/index.js";
|
|
import { findMostNestedNeighbor } from "../helpers.js";
|
|
/**
|
|
* Check possibility the char can be removed
|
|
*
|
|
* @example
|
|
* ```html
|
|
* te|st
|
|
* ```
|
|
* result
|
|
* ```html
|
|
* t|st
|
|
* ```
|
|
* @private
|
|
*/
|
|
// eslint-disable-next-line complexity
|
|
export function checkRemoveChar(jodit, fakeNode, backspace, mode) {
|
|
var _a, _b;
|
|
const step = backspace ? -1 : 1;
|
|
const anotherSibling = Dom.sibling(fakeNode, !backspace);
|
|
let sibling = Dom.sibling(fakeNode, backspace);
|
|
let removeNeighbor = null;
|
|
let charRemoved = false;
|
|
let removed;
|
|
if (!sibling) {
|
|
sibling = getNextInlineSibling(fakeNode, backspace, jodit.editor);
|
|
}
|
|
while (sibling && (Dom.isText(sibling) || Dom.isInlineBlock(sibling))) {
|
|
while (Dom.isInlineBlock(sibling)) {
|
|
sibling = (backspace ? sibling === null || sibling === void 0 ? void 0 : sibling.lastChild : sibling === null || sibling === void 0 ? void 0 : sibling.firstChild);
|
|
}
|
|
if (!sibling) {
|
|
break;
|
|
}
|
|
if ((_a = sibling.nodeValue) === null || _a === void 0 ? void 0 : _a.length) {
|
|
removed = tryRemoveChar(sibling, backspace, step, anotherSibling);
|
|
if (!sibling.nodeValue.length &&
|
|
Dom.isInlineBlock(sibling.parentNode)) {
|
|
sibling.nodeValue = INVISIBLE_SPACE;
|
|
}
|
|
}
|
|
if (!((_b = sibling.nodeValue) === null || _b === void 0 ? void 0 : _b.length)) {
|
|
removeNeighbor = sibling;
|
|
}
|
|
if (!isVoid(removed) && removed !== INVISIBLE_SPACE) {
|
|
checkRepeatRemoveCharAction(backspace, sibling, fakeNode, mode, removed, jodit);
|
|
charRemoved = true;
|
|
break;
|
|
}
|
|
const nextSibling = getNextInlineSibling(sibling, backspace, jodit.editor);
|
|
if (removeNeighbor) {
|
|
Dom.safeRemove(removeNeighbor);
|
|
removeNeighbor = null;
|
|
}
|
|
sibling = nextSibling;
|
|
}
|
|
if (removeNeighbor) {
|
|
Dom.safeRemove(removeNeighbor);
|
|
removeNeighbor = null;
|
|
}
|
|
if (charRemoved) {
|
|
removeEmptyForParent(fakeNode, 'a');
|
|
addBRInsideEmptyBlock(jodit, fakeNode);
|
|
jodit.s.setCursorBefore(fakeNode);
|
|
if (Dom.isTag(fakeNode.previousSibling, 'br') &&
|
|
!Dom.findNotEmptySibling(fakeNode, false)) {
|
|
Dom.after(fakeNode, jodit.createInside.element('br'));
|
|
}
|
|
}
|
|
return charRemoved;
|
|
}
|
|
function getNextInlineSibling(sibling, backspace, root) {
|
|
let nextSibling = Dom.sibling(sibling, backspace);
|
|
if (!nextSibling && sibling.parentNode && sibling.parentNode !== root) {
|
|
nextSibling = findMostNestedNeighbor(sibling, !backspace, root, true);
|
|
}
|
|
return nextSibling;
|
|
}
|
|
/**
|
|
* Helper removes all empty inline parents
|
|
*/
|
|
function removeEmptyForParent(node, tags) {
|
|
let parent = node.parentElement;
|
|
while (parent && Dom.isInlineBlock(parent) && Dom.isTag(parent, tags)) {
|
|
const p = parent.parentElement;
|
|
if (Dom.isEmpty(parent)) {
|
|
Dom.after(parent, node);
|
|
Dom.safeRemove(parent);
|
|
}
|
|
parent = p;
|
|
}
|
|
}
|
|
/**
|
|
* Helper add BR element inside empty block element
|
|
*/
|
|
function addBRInsideEmptyBlock(jodit, node) {
|
|
if (node.parentElement !== jodit.editor &&
|
|
Dom.isBlock(node.parentElement) &&
|
|
Dom.each(node.parentElement, Dom.isEmptyTextNode)) {
|
|
Dom.after(node, jodit.createInside.element('br'));
|
|
}
|
|
}
|
|
function tryRemoveChar(sibling, backspace, step, anotherSibling) {
|
|
// For Unicode escapes
|
|
let value = toArray(sibling.nodeValue);
|
|
const length = value.length;
|
|
let index = backspace ? length - 1 : 0;
|
|
if (value[index] === INVISIBLE_SPACE) {
|
|
while (value[index] === INVISIBLE_SPACE) {
|
|
index += step;
|
|
}
|
|
}
|
|
const removed = value[index];
|
|
if (value[index + step] === INVISIBLE_SPACE) {
|
|
index += step;
|
|
while (value[index] === INVISIBLE_SPACE) {
|
|
index += step;
|
|
}
|
|
index += backspace ? 1 : -1;
|
|
}
|
|
if (backspace && index < 0) {
|
|
value = [];
|
|
}
|
|
else {
|
|
value = value.slice(backspace ? 0 : index + 1, backspace ? index : length);
|
|
}
|
|
replaceSpaceOnNBSP(anotherSibling, backspace, value);
|
|
sibling.nodeValue = value.join('');
|
|
return removed;
|
|
}
|
|
function replaceSpaceOnNBSP(anotherSibling, backspace, value) {
|
|
var _a;
|
|
if (!anotherSibling ||
|
|
!Dom.isText(anotherSibling) ||
|
|
(!backspace ? / $/ : /^ /).test((_a = anotherSibling.nodeValue) !== null && _a !== void 0 ? _a : '') ||
|
|
!trimInv(anotherSibling.nodeValue || '').length) {
|
|
for (let i = backspace ? value.length - 1 : 0; backspace ? i >= 0 : i < value.length; i += backspace ? -1 : 1) {
|
|
if (value[i] === ' ') {
|
|
value[i] = NBSP_SPACE;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function checkRepeatRemoveCharAction(backspace, sibling, fakeNode, mode, removed, jodit) {
|
|
call(backspace ? Dom.after : Dom.before, sibling, fakeNode);
|
|
if (mode === 'sentence' ||
|
|
(mode === 'word' && removed !== ' ' && removed !== NBSP_SPACE)) {
|
|
checkRemoveChar(jodit, fakeNode, backspace, mode);
|
|
}
|
|
}
|