jodit
Version:
Jodit is awesome and usefully wysiwyg editor with filebrowser
485 lines (412 loc) • 10.9 kB
text/typescript
/*!
* Jodit Editor (https://xdsoft.net/jodit/)
* Licensed under GNU General Public License version 2 or later or a commercial license or MIT;
* For GPL see LICENSE-GPL.txt in the project root for license information.
* For MIT see LICENSE-MIT.txt in the project root for license information.
* For commercial licenses see https://xdsoft.net/jodit/commercial/
* Copyright (c) 2013-2019 Valeriy Chupurnov. All rights reserved. https://xdsoft.net
*/
import * as consts from '../constants';
import { MAY_BE_REMOVED_WITH_KEY } from '../constants';
import { Dom } from '../modules/Dom';
import { normalizeNode, trim } from '../modules/helpers/';
import { IJodit } from '../types';
/**
* Plug-in process entering Backspace key
*
* @module backspace
*/
export function backspace(editor: IJodit) {
const removeEmptyBlocks = (container: HTMLElement) => {
let box: HTMLElement | null = container,
parent: Node | null;
normalizeNode(container);
do {
const html: string = box.innerHTML.replace(
consts.INVISIBLE_SPACE_REG_EXP,
''
);
if (
(!html.length || html === '<br>') &&
!Dom.isCell(box, editor.editorWindow) &&
box.parentNode &&
container !== editor.editor
) {
parent = box.parentNode;
Dom.safeRemove(box);
} else {
break;
}
box = parent as HTMLElement | null;
} while (box && box !== editor.editor);
};
const removeChar = (
box: { node: Node | null },
toLeft: boolean,
range: Range
): void | boolean => {
if (
box.node &&
box.node.nodeType === Node.TEXT_NODE &&
typeof box.node.nodeValue === 'string'
) {
// remove invisible spaces
let value: string = box.node.nodeValue,
startOffset: number = toLeft ? value.length : 0;
const increment: number = toLeft ? -1 : 1,
startOffsetInRange: number = startOffset;
while (
startOffset >= 0 &&
startOffset <= value.length &&
value[startOffset + (toLeft ? -1 : 0)] ===
consts.INVISIBLE_SPACE
) {
startOffset += increment;
}
if (startOffset !== startOffsetInRange) {
if (toLeft) {
value =
value.substr(0, startOffset) +
value.substr(startOffsetInRange);
} else {
value =
value.substr(0, startOffsetInRange) +
value.substr(startOffset);
startOffset = startOffsetInRange;
}
box.node.nodeValue = value;
}
range.setStart(box.node, startOffset);
range.collapse(true);
editor.selection.selectRange(range);
let nextElement: Node | null = Dom.findInline(
box.node,
toLeft,
editor.editor
);
if (value.length) {
let setRange: boolean = false;
if (toLeft) {
if (startOffset) {
setRange = true;
}
} else {
if (startOffset < value.length) {
setRange = true;
}
}
if (setRange) {
return true;
}
} else {
range.setStartBefore(box.node);
range.collapse(true);
editor.selection.selectRange(range);
Dom.safeRemove(box.node);
box.node = nextElement;
}
if (nextElement) {
if (Dom.isInlineBlock(nextElement)) {
nextElement = toLeft
? nextElement.lastChild
: nextElement.firstChild;
}
if (nextElement && nextElement.nodeType === Node.TEXT_NODE) {
box.node = nextElement;
return removeChar(box, toLeft, range);
}
}
}
};
const potentialRemovable: RegExp = MAY_BE_REMOVED_WITH_KEY;
const removePotential = (node: Node | null): false | void => {
if (node && potentialRemovable.test(node.nodeName)) {
Dom.safeRemove(node);
return false;
}
};
const removeInline = (
box: { node: Node | null },
toLeft: boolean,
range: Range
): boolean | void => {
if (box.node) {
const workElement: Node = box.node;
const removeCharFlag: void | boolean = removeChar(
box,
toLeft,
range
);
if (removeCharFlag !== undefined) {
return true;
}
if (!box.node) {
box.node = workElement.parentNode;
}
if (box.node === editor.editor) {
return false;
}
let node: Node | null = box.node;
if (removePotential(node) === false) {
return false;
}
if (node) {
node = toLeft ? node.previousSibling : node.nextSibling;
}
while (
node &&
node.nodeType === Node.TEXT_NODE &&
node.nodeValue &&
node.nodeValue.match(/^[\n\r]+$/)
) {
node = toLeft ? node.previousSibling : node.nextSibling;
}
return removePotential(node);
}
};
const isEmpty = (node: Node): boolean => {
if (node.nodeName.match(/^(TD|TH|TR|TABLE|LI)$/) !== null) {
return false;
}
if (
Dom.isEmpty(node) ||
node.nodeName.match(potentialRemovable) !== null
) {
return true;
}
if (node.nodeType === Node.TEXT_NODE && !Dom.isEmptyTextNode(node)) {
return false;
}
return node.childNodes.length
? Array.from(node.childNodes).every(isEmpty)
: true;
};
editor.events
.on('afterCommand', (command: string) => {
if (command === 'delete') {
const current: Node | false = editor.selection.current();
if (
current &&
current.firstChild &&
current.firstChild.nodeName === 'BR'
) {
Dom.safeRemove(current.firstChild);
}
if (
!trim(editor.editor.innerText) &&
!editor.editor.querySelector('img') &&
(!current || !Dom.closest(current, 'table', editor.editor))
) {
editor.editor.innerHTML = '';
const node: Node = editor.selection.setCursorIn(
editor.editor
);
Dom.safeRemove(node);
}
}
})
.on(
'keydown',
(event: KeyboardEvent): false | void => {
if (
event.which === consts.KEY_BACKSPACE ||
event.which === consts.KEY_DELETE
) {
const toLeft: boolean =
event.which === consts.KEY_BACKSPACE;
if (!editor.selection.isFocused()) {
editor.selection.focus();
}
if (!editor.selection.isCollapsed()) {
editor.execCommand('Delete');
return false;
}
const
sel = editor.selection.sel,
range = sel && sel.rangeCount ? sel.getRangeAt(0) : false;
if (!range) {
return false;
}
const fakeNode: Node = editor.ownerDocument.createTextNode(
consts.INVISIBLE_SPACE
);
const marker: HTMLElement = editor.editorDocument.createElement(
'span'
);
try {
range.insertNode(fakeNode);
if (!Dom.isOrContains(editor.editor, fakeNode)) {
return false;
}
let container: HTMLElement | null = Dom.up(
fakeNode,
node => Dom.isBlock(node, editor.editorWindow),
editor.editor
) as HTMLElement | null;
const workElement: Node | null = Dom.findInline(
fakeNode,
toLeft,
editor.editor
);
const box = {
node: workElement
};
let tryRemoveInline: boolean | void;
if (workElement) {
tryRemoveInline = removeInline(box, toLeft, range);
} else if (fakeNode.parentNode) {
tryRemoveInline = removeInline(
{
node: toLeft
? fakeNode.parentNode.previousSibling
: fakeNode.parentNode.nextSibling
},
toLeft,
range
);
}
if (tryRemoveInline !== undefined) {
return tryRemoveInline ? undefined : false;
}
if (container && container.nodeName.match(/^(TD)$/)) {
return false;
}
let prevBox: Node | false | null = toLeft
? Dom.prev(
box.node || fakeNode,
node =>
Dom.isBlock(node, editor.editorWindow),
editor.editor
)
: Dom.next(
box.node || fakeNode,
node =>
Dom.isBlock(node, editor.editorWindow),
editor.editor
);
if (!prevBox && container && container.parentNode) {
prevBox = editor.create.inside.element(
editor.options.enter
);
let boxNode: Node = container;
while (
boxNode &&
boxNode.parentNode &&
boxNode.parentNode !== editor.editor
) {
boxNode = boxNode.parentNode;
}
boxNode.parentNode &&
boxNode.parentNode.insertBefore(
prevBox,
boxNode
);
} else {
if (prevBox && isEmpty(prevBox)) {
Dom.safeRemove(prevBox);
return false;
}
}
if (prevBox) {
const tmpNode: Node = editor.selection.setCursorIn(
prevBox,
!toLeft
);
editor.selection.insertNode(marker, false, false);
if (
tmpNode.nodeType === Node.TEXT_NODE &&
tmpNode.nodeValue === consts.INVISIBLE_SPACE
) {
Dom.safeRemove(tmpNode);
}
}
if (container) {
removeEmptyBlocks(container);
if (prevBox && container.parentNode) {
if (
container.nodeName === prevBox.nodeName &&
container.parentNode &&
prevBox.parentNode &&
container.parentNode !== editor.editor &&
prevBox.parentNode !== editor.editor &&
container.parentNode !==
prevBox.parentNode &&
container.parentNode.nodeName ===
prevBox.parentNode.nodeName
) {
container = container.parentNode as HTMLElement;
prevBox = prevBox.parentNode as HTMLElement;
}
Dom.moveContent(container, prevBox, !toLeft);
normalizeNode(prevBox);
}
if (prevBox && prevBox.nodeName === 'LI') {
const UL: Node | false = Dom.closest(
prevBox,
'Ul|OL',
editor.editor
);
if (UL) {
const nextBox: Node | null = UL.nextSibling;
if (
nextBox &&
nextBox.nodeName === UL.nodeName &&
UL !== nextBox
) {
Dom.moveContent(nextBox, UL, !toLeft);
Dom.safeRemove(nextBox);
}
}
}
removeEmptyBlocks(container);
return false;
}
} finally {
if (
fakeNode.parentNode &&
fakeNode.nodeValue === consts.INVISIBLE_SPACE
) {
const parent: Node = fakeNode.parentNode;
Dom.safeRemove(fakeNode);
if (
!parent.firstChild &&
parent.parentNode &&
parent !== editor.editor
) {
Dom.safeRemove(parent);
}
}
if (
marker &&
Dom.isOrContains(editor.editor, marker, true)
) {
const tmpNode:
| Text
| false = editor.selection.setCursorBefore(
marker
);
Dom.safeRemove(marker);
if (
tmpNode &&
tmpNode.parentNode &&
(Dom.findInline(
tmpNode,
true,
tmpNode.parentNode
) ||
Dom.findInline(
tmpNode,
true,
tmpNode.parentNode
))
) {
Dom.safeRemove(tmpNode);
}
}
editor.setEditorValue();
}
return false;
}
}
);
}