b2049209ff
There are some style differences compared to prettier, and not all are necessarily an improvement, but it's much faster now.
95 lines
2.5 KiB
TypeScript
95 lines
2.5 KiB
TypeScript
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
import { nodeIsText } from "../../lib/dom";
|
|
|
|
/**
|
|
* @link https://dom.spec.whatwg.org/#concept-node-length
|
|
*/
|
|
function length(node: Node): number {
|
|
if (node instanceof CharacterData) {
|
|
return node.length;
|
|
} else if (
|
|
node.nodeType === Node.DOCUMENT_TYPE_NODE
|
|
|| node.nodeType === Node.ATTRIBUTE_NODE
|
|
) {
|
|
return 0;
|
|
}
|
|
|
|
return node.childNodes.length;
|
|
}
|
|
|
|
/**
|
|
* Wrapper around DOM ranges that are passed into evaluation and are adjusted,
|
|
* if its start or end nodes are to be removed
|
|
*/
|
|
export class SplitRange {
|
|
constructor(protected start: Node, protected end: Node) {}
|
|
|
|
private adjustStart(): void {
|
|
if (this.start.firstChild) {
|
|
this.start = this.start.firstChild;
|
|
} else if (this.start.nextSibling) {
|
|
this.start = this.start.nextSibling!;
|
|
}
|
|
}
|
|
|
|
private adjustEnd(): void {
|
|
if (this.end.lastChild) {
|
|
this.end = this.end.lastChild!;
|
|
} else if (this.end.previousSibling) {
|
|
this.end = this.end.previousSibling;
|
|
}
|
|
}
|
|
|
|
adjustRange(element: Element): void {
|
|
if (this.start === element) {
|
|
this.adjustStart();
|
|
} else if (this.end === element) {
|
|
this.adjustEnd();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a range with boundary points `(start, 0)` and `(end, end.length)`.
|
|
*/
|
|
toDOMRange(): Range {
|
|
const range = new Range();
|
|
range.setStart(this.start, 0);
|
|
range.setEnd(this.end, length(this.end));
|
|
|
|
return range;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns Split text node to end direction or text itself if a split is
|
|
* not necessary
|
|
*/
|
|
function splitTextIfNecessary(text: Text, offset: number): Text {
|
|
if (offset === 0 || offset === text.length) {
|
|
return text;
|
|
}
|
|
|
|
return text.splitText(offset);
|
|
}
|
|
|
|
export function splitPartiallySelected(range: Range): SplitRange {
|
|
let start: Node;
|
|
if (nodeIsText(range.startContainer)) {
|
|
start = splitTextIfNecessary(range.startContainer, range.startOffset);
|
|
} else {
|
|
start = range.startContainer.childNodes[range.startOffset];
|
|
}
|
|
|
|
let end: Node;
|
|
if (nodeIsText(range.endContainer)) {
|
|
end = range.endContainer;
|
|
splitTextIfNecessary(range.endContainer, range.endOffset);
|
|
} else {
|
|
end = range.endContainer.childNodes[range.endOffset - 1];
|
|
}
|
|
|
|
return new SplitRange(start, end);
|
|
}
|