 8cdc76ba4c
			
		
	
	8cdc76ba4c
	
	
	
		
			
			These tags are preserved by the Closure compiler and vulcanize in order to serve the license notices embedded in the outputs. In a standalone Gerrit server, these license are also covered in the LICENSES.txt served with the documentation. When serving PG assets from a CDN, it's less obvious what the corresponding LICENSES.txt file is, since the CDN is not directly linked to a running Gerrit server. Safer to embed the licenses in the assets themselves. Change-Id: Id1add1451fad1baa7916882a6bda02c326ccc988
		
			
				
	
	
		
			212 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			212 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | ||
|  * @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(/<a href=\"#\//g, '<a href="/');
 | ||
|     } else if (patterns[p].link) {
 | ||
|       if (patterns[p].link[0] == '#') {
 | ||
|         patterns[p].link = patterns[p].link.substr(1);
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     var pattern = new RegExp(patterns[p].match, 'g');
 | ||
| 
 | ||
|     var match;
 | ||
|     var textToCheck = text;
 | ||
|     var susbtrIndex = 0;
 | ||
| 
 | ||
|     while ((match = pattern.exec(textToCheck)) != null) {
 | ||
|       textToCheck = textToCheck.substr(match.index + match[0].length);
 | ||
|       var result = match[0].replace(pattern,
 | ||
|           patterns[p].html || patterns[p].link);
 | ||
| 
 | ||
|       // Skip portion of replacement string that is equal to original.
 | ||
|       for (var i = 0; i < result.length; i++) {
 | ||
|         if (result[i] !== match[0][i]) {
 | ||
|           break;
 | ||
|         }
 | ||
|       }
 | ||
|       result = result.slice(i);
 | ||
| 
 | ||
|       if (patterns[p].html) {
 | ||
|         this.addHTML(
 | ||
|           result,
 | ||
|           susbtrIndex + match.index + i,
 | ||
|           match[0].length - i,
 | ||
|           outputArray);
 | ||
|       } else if (patterns[p].link) {
 | ||
|         this.addLink(
 | ||
|           match[0],
 | ||
|           result,
 | ||
|           susbtrIndex + match.index + i,
 | ||
|           match[0].length - i,
 | ||
|           outputArray);
 | ||
|       } else {
 | ||
|         throw Error('linkconfig entry ' + p +
 | ||
|             ' doesn’t contain a link or html attribute.');
 | ||
|       }
 | ||
| 
 | ||
|       // Update the substring location so we know where we are in relation to
 | ||
|       // the initial full text string.
 | ||
|       susbtrIndex = susbtrIndex + match.index + match[0].length;
 | ||
|     }
 | ||
|   }
 | ||
|   this.processLinks(text, outputArray);
 | ||
| };
 |