Also remove empty jsdocs Optional chaining: https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#optional-chaining Change-Id: If427cd077b92e6ec6b497a8db47dd29addb896ab
115 lines
3.4 KiB
TypeScript
115 lines
3.4 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
// Astral code point as per https://mathiasbynens.be/notes/javascript-unicode
|
|
const REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
|
|
|
|
export interface NormalizedRange {
|
|
endContainer: Node;
|
|
endOffset: number;
|
|
startContainer: Node;
|
|
startOffset: number;
|
|
}
|
|
|
|
/**
|
|
* Remap DOM range to whole lines of a diff if necessary. If the start or
|
|
* end containers are DOM elements that are singular pieces of syntax
|
|
* highlighting, the containers are remapped to the .contentText divs that
|
|
* contain the entire line of code.
|
|
*
|
|
* @param range - the standard DOM selector range.
|
|
* @return A modified version of the range that correctly accounts
|
|
* for syntax highlighting.
|
|
*/
|
|
export function normalize(range: Range): NormalizedRange {
|
|
const startContainer = _getContentTextParent(range.startContainer);
|
|
const startOffset =
|
|
range.startOffset + _getTextOffset(startContainer, range.startContainer);
|
|
const endContainer = _getContentTextParent(range.endContainer);
|
|
const endOffset =
|
|
range.endOffset + _getTextOffset(endContainer, range.endContainer);
|
|
return {
|
|
startContainer,
|
|
startOffset,
|
|
endContainer,
|
|
endOffset,
|
|
};
|
|
}
|
|
|
|
function _getContentTextParent(target: Node): Node {
|
|
if (!target.parentElement) return target;
|
|
|
|
let element: Element | null;
|
|
if (target instanceof Element) {
|
|
element = target;
|
|
} else {
|
|
element = target.parentElement;
|
|
}
|
|
|
|
while (element && !element.classList.contains('contentText')) {
|
|
if (element.parentElement === null) {
|
|
return target;
|
|
}
|
|
element = element.parentElement;
|
|
}
|
|
return element ? element : target;
|
|
}
|
|
|
|
/**
|
|
* Gets the character offset of the child within the parent.
|
|
* Performs a synchronous in-order traversal from top to bottom of the node
|
|
* element, counting the length of the syntax until child is found.
|
|
*
|
|
* @param node The root DOM element to be searched through.
|
|
* @param child The child element being searched for.
|
|
*/
|
|
// TODO(TS): Only export for test.
|
|
export function _getTextOffset(node: Node | null, child: Node): number {
|
|
let count = 0;
|
|
let stack = [node];
|
|
while (stack.length) {
|
|
const n = stack.pop();
|
|
if (n === child) {
|
|
break;
|
|
}
|
|
if (n?.childNodes && n.childNodes.length !== 0) {
|
|
const arr = [];
|
|
for (const childNode of n.childNodes) {
|
|
arr.push(childNode);
|
|
}
|
|
arr.reverse();
|
|
stack = stack.concat(arr);
|
|
} else {
|
|
count += _getLength(n);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* The DOM API textContent.length calculation is broken when the text
|
|
* contains Unicode. See https://mathiasbynens.be/notes/javascript-unicode .
|
|
*
|
|
* @param node A text node.
|
|
* @return The length of the text.
|
|
*/
|
|
function _getLength(node?: Node | null) {
|
|
return node && node.textContent
|
|
? node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length
|
|
: 0;
|
|
}
|