suneditor
Version:
Vanilla JavaScript based WYSIWYG web editor
219 lines (198 loc) • 6.81 kB
JavaScript
import { dom } from '../../../helper';
import { hardDelete, isUneditableNode, isRtlBidiMismatch } from '../effects/ruleHelpers';
import { A } from '../actions';
/**
* @typedef {import('../actions').Action[]} EventActions
* @typedef {import('../ports').EventReducerPorts} EventPorts
* @typedef {import('../reducers/keydown.reducer').KeydownReducerCtx} EventKeydownCtx
*/
/**
* @this {void}
* @description Delete key down rule
* @param {EventActions} actions - Action list
* @param {EventPorts} ports - Ports for interacting with editor
* @param {EventKeydownCtx} ctx - Context object
* @returns {boolean} Return `false` to stop the processing
*/
export function reduceDeleteDown(actions, ports, ctx) {
const { format, component } = ports;
const { fc, range, selectionNode } = ctx;
let { formatEl } = ctx;
const selectRange = !range.collapsed || range.startContainer !== range.endContainer;
// RTL bidi guard: if offset=length is actually at the visual start due to LTR text in RTL line, skip end-edge handling
const bidiNotEnd =
ctx.options.get('_rtl') &&
!selectRange &&
range.endOffset >= (range.endContainer.textContent?.length || 0) &&
isRtlBidiMismatch(range, formatEl, 'end', fc.get('_wd'));
actions.push(A.componentDeselect());
actions.push(A.cacheStyleNode());
if (selectRange && hardDelete(ports)) {
actions.push(A.preventStop());
return true;
}
if (
!selectRange &&
!bidiNotEnd &&
format.isEdgeLine(range.endContainer, range.endOffset, 'end') &&
!formatEl.nextSibling
) {
actions.push(A.preventStop());
return false;
}
// line delete
if (
format.isLine(formatEl) &&
selectRange &&
dom.check.isEdgePoint(range.endContainer, range.endOffset, 'end') &&
(!range.endContainer.nextSibling || dom.check.isZeroWidth(range.endContainer.nextSibling)) &&
format.getLine(range.startContainer) !== format.getLine(range.endContainer) &&
(format.isLine(formatEl.nextElementSibling) || dom.check.isListCell(formatEl))
) {
actions.push(A.preventStop());
actions.push(A.delFormatRemoveAndMove(range.endContainer, formatEl));
actions.push(A.historyPush(true));
return false;
}
// line component
if (
!selectRange &&
formatEl &&
(range.endOffset === range.endContainer.textContent.length || selectionNode === formatEl)
) {
const sel =
selectionNode === formatEl
? isUneditableNode(ports, range, false)
: dom.check.isElement(selectionNode.nextSibling)
? selectionNode.nextSibling
: dom.check.isEdgePoint(range.endContainer, range.endOffset)
? dom.query.getNextDeepestNode(range.endContainer, null)
: null;
if (component.is(sel)) {
const fileComponentInfo = component.get(sel);
if (fileComponentInfo) {
actions.push(A.preventStop());
actions.push(A.deleteComponentSelect(formatEl, fileComponentInfo));
return true;
}
}
}
// tag[contenteditable='false']
if (isUneditableNode(ports, range, false)) {
actions.push(A.preventStop());
return true;
}
// component
if (
(format.isLine(selectionNode) ||
selectionNode.nextSibling === null ||
(dom.check.isZeroWidth(selectionNode.nextSibling) && selectionNode.nextSibling.nextSibling === null)) &&
range.startOffset === selectionNode.textContent.length
) {
const nextEl = formatEl.nextElementSibling;
if (!nextEl) return true;
if (component.is(nextEl)) {
actions.push(A.prevent());
actions.push(A.deleteComponentSelectNext(formatEl, nextEl));
return true;
}
}
if (
!selectRange &&
(dom.check.isEdgePoint(range.endContainer, range.endOffset) ||
(selectionNode === formatEl ? formatEl.childNodes[range.startOffset] : false))
) {
const sel =
selectionNode === formatEl ? formatEl.childNodes[range.startOffset] || selectionNode : selectionNode;
// delete nonEditable
if (sel && dom.check.isNonEditable(sel.nextSibling)) {
actions.push(A.preventStop());
actions.push(A.domUtilsRemoveItem(sel.nextSibling));
return true;
} else if (component.is(sel)) {
actions.push(A.preventStop());
actions.push(A.domUtilsRemoveItem(sel));
return true;
}
}
// format attributes
if (!selectRange && !bidiNotEnd && format.isEdgeLine(range.endContainer, range.endOffset, 'end')) {
if (format.isLine(formatEl.nextElementSibling)) {
actions.push(A.cacheFormatAttrsTemp(formatEl.attributes));
}
}
// nested list
formatEl = format.getLine(range.startContainer, null);
const rangeEl = format.getBlock(formatEl, null);
if (
dom.check.isListCell(formatEl) &&
dom.check.isList(rangeEl) &&
(selectionNode === formatEl ||
(selectionNode.nodeType === 3 &&
(!selectionNode.nextSibling || dom.check.isList(selectionNode.nextSibling)) &&
(format.getLine(range.startContainer, null) !== format.getLine(range.endContainer, null)
? rangeEl.contains(range.endContainer)
: range.endOffset === selectionNode.textContent.length && range.collapsed && !bidiNotEnd)))
) {
actions.push(A.deleteListRemoveNested(range, formatEl, rangeEl));
return true;
}
// empty row inside a brLine (PRE): Enter leaves the caret on a bare <br> that ends an empty row
if (
!selectRange &&
format.isBrLine(formatEl) &&
dom.check.isBreak(range.startContainer) &&
range.startOffset === 0
) {
let rowStartBr = range.startContainer.previousSibling;
if (rowStartBr?.nodeType === 3 && dom.check.isZeroWidth(rowStartBr)) rowStartBr = rowStartBr.previousSibling;
if (dom.check.isBreak(rowStartBr)) {
actions.push(A.preventStop());
actions.push(A.deleteBrLineRowMerge(range.startContainer));
actions.push(A.historyPush(true));
return false;
}
}
// soft line break (Shift+Enter)
if (!selectRange) {
const dcon = range.startContainer;
let softBr = null;
if (
dcon.nodeType === 3 &&
range.startOffset >= dcon.textContent.length &&
dom.check.isBreak(dcon.nextSibling)
) {
const br = dcon.nextSibling;
if (br.nextSibling?.nodeType === 3 && dom.check.isZeroWidth(br.nextSibling.textContent)) softBr = br;
} else if (
dcon.nodeType === 3 &&
dom.check.isZeroWidth(dcon) &&
range.startOffset === 0 &&
dom.check.isBreak(dcon.previousSibling)
) {
softBr = dcon.previousSibling;
}
if (softBr) {
actions.push(A.preventStop());
actions.push(A.deleteSoftBreakMerge(softBr));
actions.push(A.historyPush(true));
return false;
}
}
// empty line
const emptyLineNext = formatEl?.nextElementSibling;
if (
!selectRange &&
formatEl &&
format.isNormalLine(formatEl) &&
!dom.check.isListCell(formatEl) &&
dom.check.isEmptyLine(formatEl) &&
(format.isNormalLine(emptyLineNext) || format.isBrLine(emptyLineNext))
) {
actions.push(A.preventStop());
actions.push(A.deleteEmptyLineMergeNext(formatEl, emptyLineNext));
actions.push(A.historyPush(true));
return false;
}
return true;
}