| // 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} A text node. |
| * @return {Number} The length of the text. |
| */ |
| _getLength(node) { |
| return node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length; |
| }, |
| }; |
| |
| window.GrRangeNormalizer = GrRangeNormalizer; |
| })(window); |