import { Cell, Fun, Optional, Optionals } from '@ephox/katamari';
import { SugarElement } from '@ephox/sugar';
import Editor from '../api/Editor';
import * as Options from '../api/Options';
import * as CaretFinder from '../caret/CaretFinder';
import CaretPosition from '../caret/CaretPosition';
import * as CaretUtils from '../caret/CaretUtils';
import * as BoundaryCaret from '../keyboard/BoundaryCaret';
import * as BoundaryLocation from '../keyboard/BoundaryLocation';
import * as BoundarySelection from '../keyboard/BoundarySelection';
import * as InlineUtils from '../keyboard/InlineUtils';
import * as DeleteElement from './DeleteElement';
import { execNativeDeleteCommand } from './DeleteUtils';
const rangeFromPositions = (from: CaretPosition, to: CaretPosition): Range => {
const range = document.createRange();
range.setStart(from.container(), from.offset());
range.setEnd(to.container(), to.offset());
return range;
};
// Checks for delete at |a when there is only one item left except the zwsp caret container nodes
const hasOnlyTwoOrLessPositionsLeft = (elm: Node): boolean =>
Optionals.lift2(
CaretFinder.firstPositionIn(elm),
CaretFinder.lastPositionIn(elm),
(firstPos, lastPos) => {
const normalizedFirstPos = InlineUtils.normalizePosition(true, firstPos);
const normalizedLastPos = InlineUtils.normalizePosition(false, lastPos);
return CaretFinder.nextPosition(elm, normalizedFirstPos).forall((pos) => pos.isEqual(normalizedLastPos));
}).getOr(true);
const setCaretLocation = (editor: Editor, caret: Cell) => (location: BoundaryLocation.LocationAdt): Optional<() => void> =>
BoundaryCaret.renderCaret(caret, location).map((pos) =>
() => BoundarySelection.setCaretPosition(editor, pos)
);
const deleteFromTo = (editor: Editor, caret: Cell, from: CaretPosition, to: CaretPosition): void => {
const rootNode = editor.getBody();
const isInlineTarget = Fun.curry(InlineUtils.isInlineTarget, editor);
editor.undoManager.ignore(() => {
editor.selection.setRng(rangeFromPositions(from, to));
// TODO: TINY-9120 - Investigate if this should be using our custom overrides
execNativeDeleteCommand(editor);
BoundaryLocation.readLocation(isInlineTarget, rootNode, CaretPosition.fromRangeStart(editor.selection.getRng()))
.map(BoundaryLocation.inside)
.bind(setCaretLocation(editor, caret))
.each(Fun.call);
});
editor.nodeChanged();
};
const rescope = (rootNode: Node, node: Node): Node => {
const parentBlock = CaretUtils.getParentBlock(node, rootNode);
return parentBlock ? parentBlock : rootNode;
};
const backspaceDeleteCollapsed = (editor: Editor, caret: Cell, forward: boolean, from: CaretPosition): Optional<() => void> => {
const rootNode = rescope(editor.getBody(), from.container());
const isInlineTarget = Fun.curry(InlineUtils.isInlineTarget, editor);
const fromLocation = BoundaryLocation.readLocation(isInlineTarget, rootNode, from);
const location = fromLocation.bind((location) => {
if (forward) {
return location.fold(
Fun.constant(Optional.some(BoundaryLocation.inside(location))), // Before
Optional.none, // Start
Fun.constant(Optional.some(BoundaryLocation.outside(location))), // End
Optional.none // After
);
} else {
return location.fold(
Optional.none, // Before
Fun.constant(Optional.some(BoundaryLocation.outside(location))), // Start
Optional.none, // End
Fun.constant(Optional.some(BoundaryLocation.inside(location))) // After
);
}
});
return location.map(setCaretLocation(editor, caret))
.getOrThunk((): Optional<() => void> => {
const toPosition = CaretFinder.navigate(forward, rootNode, from);
const toLocation = toPosition.bind((pos) => BoundaryLocation.readLocation(isInlineTarget, rootNode, pos));
return Optionals.lift2(fromLocation, toLocation, () =>
InlineUtils.findRootInline(isInlineTarget, rootNode, from).bind((elm) => {
if (hasOnlyTwoOrLessPositionsLeft(elm)) {
return Optional.some(() => {
DeleteElement.deleteElement(editor, forward, SugarElement.fromDom(elm));
});
} else {
return Optional.none();
}
})
).getOrThunk(() => toLocation.bind(() =>
toPosition.map((to) => {
return () => {
if (forward) {
deleteFromTo(editor, caret, from, to);
} else {
deleteFromTo(editor, caret, to, from);
}
};
})
));
});
};
const backspaceDelete = (editor: Editor, caret: Cell, forward: boolean): Optional<() => void> => {
if (editor.selection.isCollapsed() && Options.isInlineBoundariesEnabled(editor)) {
const from = CaretPosition.fromRangeStart(editor.selection.getRng());
return backspaceDeleteCollapsed(editor, caret, forward, from);
}
return Optional.none();
};
export {
backspaceDelete
};