| /** | |
| * @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. | |
| */ | |
| (function(window) { | |
| 'use strict'; | |
| // Prevent redefinition. | |
| if (window.GrRangeNormalizer) { return; } | |
| // Astral code point as per https://mathiasbynens.be/notes/javascript-unicode | |
| const REGEX_ASTRAL_SYMBOL = /[\uD800-\uDBFF][\uDC00-\uDFFF]/; | |
| const GrRangeNormalizer = { | |
| /** | |
| * 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 {!Object} range - the standard DOM selector range. | |
| * @return {!Object} A modified version of the range that correctly accounts | |
| * for syntax highlighting. | |
| */ | |
| normalize(range) { | |
| const startContainer = this._getContentTextParent(range.startContainer); | |
| const startOffset = range.startOffset + | |
| this._getTextOffset(startContainer, range.startContainer); | |
| const endContainer = this._getContentTextParent(range.endContainer); | |
| const endOffset = range.endOffset + this._getTextOffset(endContainer, | |
| range.endContainer); | |
| return { | |
| startContainer, | |
| startOffset, | |
| endContainer, | |
| endOffset, | |
| }; | |
| }, | |
| _getContentTextParent(target) { | |
| let element = target; | |
| if (element.nodeName === '#text') { | |
| element = element.parentElement; | |
| } | |
| while (!element.classList.contains('contentText')) { | |
| if (element.parentElement === null) { | |
| return target; | |
| } | |
| element = element.parentElement; | |
| } | |
| return element; | |
| }, | |
| /** | |
| * 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 {!Element} node The root DOM element to be searched through. | |
| * @param {!Element} child The child element being searched for. | |
| * @return {number} | |
| */ | |
| _getTextOffset(node, child) { | |
| 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 += this._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 {text} node A text node. | |
| * @return {number} The length of the text. | |
| */ | |
| _getLength(node) { | |
| return node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length; | |
| }, | |
| }; | |
| window.GrRangeNormalizer = GrRangeNormalizer; | |
| })(window); |