/** * @license * Copyright (C) 2015 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. */ 'use strict'; function GrLinkTextParser(linkConfig, callback, opt_removeZeroWidthSpace) { this.linkConfig = linkConfig; this.callback = callback; this.removeZeroWidthSpace = opt_removeZeroWidthSpace; Object.preventExtensions(this); } GrLinkTextParser.prototype.addText = function(text, href) { if (!text) { return; } this.callback(text, href); }; GrLinkTextParser.prototype.processLinks = function(text, outputArray) { this.sortArrayReverse(outputArray); var fragment = document.createDocumentFragment(); var cursor = text.length; // Start inserting linkified URLs from the end of the String. That way, the // string positions of the items don't change as we iterate through. outputArray.forEach(function(item) { // Add any text between the current linkified item and the item added before // if it exists. if (item.position + item.length !== cursor) { fragment.insertBefore( document.createTextNode( text.slice(item.position + item.length, cursor)), fragment.firstChild); } fragment.insertBefore(item.html, fragment.firstChild); cursor = item.position; }); // Add the beginning portion at the end. if (cursor !== 0) { fragment.insertBefore( document.createTextNode(text.slice(0, cursor)), fragment.firstChild); } this.callback(null, null, fragment); }; GrLinkTextParser.prototype.sortArrayReverse = function(outputArray) { outputArray.sort(function(a, b) {return b.position - a.position}); }; GrLinkTextParser.prototype.addItem = function(text, href, html, position, length, outputArray) { var htmlOutput = ''; if (href) { var a = document.createElement('a'); a.href = href; a.textContent = text; a.target = '_blank'; a.rel = 'noopener'; htmlOutput = a; } else if (html) { var fragment = document.createDocumentFragment(); // Create temporary div to hold the nodes in. var div = document.createElement('div'); div.innerHTML = html; while (div.firstChild) { fragment.appendChild(div.firstChild); } htmlOutput = fragment; } outputArray.push({ html: htmlOutput, position: position, length: length, }); }; GrLinkTextParser.prototype.addLink = function(text, href, position, length, outputArray) { if (!text) { return; } if (!this.hasOverlap(position, length, outputArray)) { this.addItem(text, href, null, position, length, outputArray); } }; GrLinkTextParser.prototype.addHTML = function(html, position, length, outputArray) { if (!this.hasOverlap(position, length, outputArray)) { this.addItem(null, null, html, position, length, outputArray); } }; GrLinkTextParser.prototype.hasOverlap = function(position, length, outputArray) { var endPosition = position + length; for (var i = 0; i < outputArray.length; i++) { var arrayItemStart = outputArray[i].position; var arrayItemEnd = outputArray[i].position + outputArray[i].length; if ((position >= arrayItemStart && position < arrayItemEnd) || (endPosition > arrayItemStart && endPosition <= arrayItemEnd) || (position === arrayItemStart && position === arrayItemEnd)) { return true; } } return false; }; GrLinkTextParser.prototype.parse = function(text) { linkify(text, { callback: this.parseChunk.bind(this), }); }; GrLinkTextParser.prototype.parseChunk = function(text, href) { // TODO(wyatta) switch linkify sequence, see issue 5526. if (this.removeZeroWidthSpace) { // Remove the zero-width space added in gr-change-view. text = text.replace(/^(CC|R)=\u200B/gm, '$1='); } if (href) { this.addText(text, href); } else { this.parseLinks(text, this.linkConfig); } }; GrLinkTextParser.prototype.parseLinks = function(text, patterns) { // The outputArray is used to store all of the matches found for all patterns. var outputArray = []; for (var p in patterns) { if (patterns[p].enabled != null && patterns[p].enabled == false) { continue; } // PolyGerrit doesn't use hash-based navigation like GWT. // Account for this. // TODO(andybons): Support Gerrit being served from a base other than /, // e.g. https://git.eclipse.org/r/ if (patterns[p].html) { patterns[p].html = patterns[p].html.replace(/