mirror of
https://github.com/fastmail/Squire.git
synced 2024-12-22 15:23:29 -05:00
1904405ab8
This means if you make a mistake and backspace, you don't end up accidentally fixing the text but leaving the link to the wrong URL.
110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
import type { Squire } from '../Editor';
|
|
import { getPreviousBlock } from '../node/Block';
|
|
import {
|
|
fixContainer,
|
|
mergeContainers,
|
|
mergeWithBlock,
|
|
} from '../node/MergeSplit';
|
|
import { getNearest } from '../node/Node';
|
|
import {
|
|
getStartBlockOfRange,
|
|
rangeDoesStartAtBlockBoundary,
|
|
} from '../range/Block';
|
|
import { moveRangeBoundariesDownTree } from '../range/Boundaries';
|
|
import { deleteContentsOfRange } from '../range/InsertDelete';
|
|
import { afterDelete, detachUneditableNode } from './KeyHelpers';
|
|
|
|
// ---
|
|
|
|
const Backspace = (self: Squire, event: KeyboardEvent, range: Range): void => {
|
|
const root: Element = self._root;
|
|
self._removeZWS();
|
|
// Record undo checkpoint.
|
|
self.saveUndoState(range);
|
|
if (!range.collapsed) {
|
|
// If not collapsed, delete contents
|
|
event.preventDefault();
|
|
deleteContentsOfRange(range, root);
|
|
afterDelete(self, range);
|
|
} else if (rangeDoesStartAtBlockBoundary(range, root)) {
|
|
// If at beginning of block, merge with previous
|
|
event.preventDefault();
|
|
const startBlock = getStartBlockOfRange(range, root);
|
|
if (!startBlock) {
|
|
return;
|
|
}
|
|
let current = startBlock;
|
|
// In case inline data has somehow got between blocks.
|
|
fixContainer(current.parentNode!, root);
|
|
// Now get previous block
|
|
const previous = getPreviousBlock(current, root);
|
|
// Must not be at the very beginning of the text area.
|
|
if (previous) {
|
|
// If not editable, just delete whole block.
|
|
if (!(previous as HTMLElement).isContentEditable) {
|
|
detachUneditableNode(previous, root);
|
|
return;
|
|
}
|
|
// Otherwise merge.
|
|
mergeWithBlock(previous, current, range, root);
|
|
// If deleted line between containers, merge newly adjacent
|
|
// containers.
|
|
current = previous.parentNode as HTMLElement;
|
|
while (current !== root && !current.nextSibling) {
|
|
current = current.parentNode as HTMLElement;
|
|
}
|
|
if (
|
|
current !== root &&
|
|
(current = current.nextSibling as HTMLElement)
|
|
) {
|
|
mergeContainers(current, root);
|
|
}
|
|
self.setSelection(range);
|
|
// If at very beginning of text area, allow backspace
|
|
// to break lists/blockquote.
|
|
} else if (current) {
|
|
if (
|
|
getNearest(current, root, 'UL') ||
|
|
getNearest(current, root, 'OL')
|
|
) {
|
|
// Break list
|
|
self.decreaseListLevel(range);
|
|
return;
|
|
} else if (getNearest(current, root, 'BLOCKQUOTE')) {
|
|
// Break blockquote
|
|
self.removeQuote(range);
|
|
return;
|
|
}
|
|
self.setSelection(range);
|
|
self._updatePath(range, true);
|
|
}
|
|
} else {
|
|
// If deleting text inside a link that looks like a URL, delink.
|
|
// This is to allow you to easily correct auto-linked text.
|
|
moveRangeBoundariesDownTree(range);
|
|
const text = range.startContainer;
|
|
const offset = range.startOffset;
|
|
const a = text.parentNode;
|
|
if (
|
|
text instanceof Text &&
|
|
a instanceof HTMLAnchorElement &&
|
|
offset &&
|
|
a.href.includes(text.data)
|
|
) {
|
|
text.deleteData(offset - 1, 1);
|
|
self.setSelection(range);
|
|
self.removeLink();
|
|
} else {
|
|
// Otherwise, leave to browser but check afterwards whether it has
|
|
// left behind an empty inline tag.
|
|
self.setSelection(range);
|
|
setTimeout(() => {
|
|
afterDelete(self);
|
|
}, 0);
|
|
}
|
|
}
|
|
};
|
|
|
|
// ---
|
|
|
|
export { Backspace };
|