Add documentation and light refactoring to linkification code
Change-Id: Ia5f70e44d3b35130376ef2961ea88511d85279d9
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
index 2c45311..22e14e9 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.js
@@ -43,7 +43,7 @@
'_contentOrConfigChanged(content, config)',
],
- _contentChanged: function(content) {
+ _contentChanged(content) {
// In the case where the config may not be set (perhaps due to the
// request for it still being in flight), set the content anyway to
// prevent waiting on the config to display the text.
@@ -51,22 +51,19 @@
this.$.output.textContent = content;
},
- _contentOrConfigChanged: function(content, config) {
+ /**
+ * Because either the source text or the linkification config has changed,
+ * the content should be re-parsed.
+ * @param {string|null|undefined} content The raw, un-linkified source
+ * string to parse.
+ * @param {Object|null|undefined} config The server config specifying
+ * commentLink patterns
+ */
+ _contentOrConfigChanged(content, config) {
var output = Polymer.dom(this.$.output);
output.textContent = '';
- var parser = new GrLinkTextParser(
- config, function(text, href, fragment) {
- if (href) {
- var a = document.createElement('a');
- a.href = href;
- a.textContent = text;
- a.target = '_blank';
- a.rel = 'noopener';
- output.appendChild(a);
- } else if (fragment) {
- output.appendChild(fragment);
- }
- }, this.removeZeroWidthSpace);
+ var parser = new GrLinkTextParser(config,
+ this._handleParseResult.bind(this), this.removeZeroWidthSpace);
parser.parse(content);
// Ensure that links originating from HTML commentlink configs open in a
@@ -76,5 +73,31 @@
anchor.setAttribute('rel', 'noopener');
});
},
+
+ /**
+ * This method is called when the GrLikTextParser emits a partial result
+ * (used as the "callback" parameter). It will be called in either of two
+ * ways:
+ * - To create a link: when called with `text` and `href` arguments, a link
+ * element should be created and attached to the resulting DOM.
+ * - To attach an arbitrary fragment: when called with only the `fragment`
+ * argument, the fragment should be attached to the resulting DOM as is.
+ * @param {string|null} text
+ * @param {string|null} href
+ * @param {DocumentFragment|undefined} fragment
+ */
+ _handleParseResult(text, href, fragment) {
+ var output = Polymer.dom(this.$.output);
+ if (href) {
+ var a = document.createElement('a');
+ a.href = href;
+ a.textContent = text;
+ a.target = '_blank';
+ a.rel = 'noopener';
+ output.appendChild(a);
+ } else if (fragment) {
+ output.appendChild(fragment);
+ }
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index 81074be..8b49ca0 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -17,8 +17,35 @@
(function() {
'use strict';
+ const Defs = {};
+
+ /**
+ * @typedef {{
+ * html: Node,
+ * position: number,
+ * length: number,
+ * }}
+ */
+ Defs.CommentLinkItem;
+
+ /**
+ * Pattern describing URLs with supported protocols.
+ * @type {RegExp}
+ */
const URL_PROTOCOL_PATTERN = /^(https?:\/\/|mailto:)/;
+ /**
+ * Construct a parser for linkifying text. Will linkify plain URLs that appear
+ * in the text as well as custom links if any are specified in the linkConfig
+ * parameter.
+ * @param {Object|null|undefined} linkConfig Comment links as specified by the
+ * commentlinks field on a project config.
+ * @param {Function} callback The callback to be fired when an intermediate
+ * parse result is emitted. The callback is passed text and href strings if
+ * a link is to be created, or a document fragment otherwise.
+ * @param {boolean|undefined} opt_removeZeroWidthSpace If true, zero-width
+ * spaces will be removed from R=<email> and CC=<email> expressions.
+ */
function GrLinkTextParser(linkConfig, callback, opt_removeZeroWidthSpace) {
this.linkConfig = linkConfig;
this.callback = callback;
@@ -26,13 +53,24 @@
Object.preventExtensions(this);
}
+ /**
+ * Emit a callback to create a link element.
+ * @param {string} text The text of the link.
+ * @param {string} href The URL to use as the href of the link.
+ */
GrLinkTextParser.prototype.addText = function(text, href) {
- if (!text) {
- return;
- }
+ if (!text) { return; }
this.callback(text, href);
};
+ /**
+ * Given the source text and a list of CommentLinkItem objects that were
+ * generated by the commentlinks config, emit parsing callbacks.
+ * @param {string} text The chuml of source text over which the outputArray
+ * items range.
+ * @param {!Array<Defs.CommentLinkItem>} outputArray The list of items to add
+ * resulting from commentlink matches.
+ */
GrLinkTextParser.prototype.processLinks = function(text, outputArray) {
this.sortArrayReverse(outputArray);
var fragment = document.createDocumentFragment();
@@ -62,10 +100,34 @@
this.callback(null, null, fragment);
};
+ /**
+ * Sort the given array of CommentLinkItems such that the positions are in
+ * reverse order.
+ * @param {!Array<Defs.CommentLinkItem>} outputArray
+ */
GrLinkTextParser.prototype.sortArrayReverse = function(outputArray) {
- outputArray.sort(function(a, b) {return b.position - a.position});
+ outputArray.sort((a, b) => b.position - a.position);
};
+ /**
+ * Create a CommentLinkItem and append it to the given output array. This
+ * method can be called in either of two ways:
+ * - With `text` and `href` parameters provided, and the `html` parameter
+ * passed as `null`. In this case, the new CommentLinkItem will be a link
+ * element with the given text and href value.
+ * - With the `html` paremeter provided, and the `text` and `href` parameters
+ * passed as `null`. In this case, the string of HTML will be parsed and the
+ * first resulting node will be used as the resulting content.
+ * @param {string|null} text The text to use if creating a link.
+ * @param {string|null} href The href to use as the URL if creating a link.
+ * @param {string|null} html The html to parse and use as the result.
+ * @param {number} position The position inside the source text where the item
+ * starts.
+ * @param {number} length The number of characters in the source text
+ * represented by the item.
+ * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+ * new item is to be appended.
+ */
GrLinkTextParser.prototype.addItem =
function(text, href, html, position, length, outputArray) {
var htmlOutput = '';
@@ -95,23 +157,47 @@
});
};
+ /**
+ * Create a CommentLinkItem for a link and append it to the given output
+ * array.
+ * @param {string|null} text The text for the link.
+ * @param {string|null} href The href to use as the URL of the link.
+ * @param {number} position The position inside the source text where the link
+ * starts.
+ * @param {number} length The number of characters in the source text
+ * represented by the link.
+ * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+ * new item is to be appended.
+ */
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);
- }
+ if (!text || this.hasOverlap(position, length, outputArray)) { return; }
+ this.addItem(text, href, null, position, length, outputArray);
};
+ /**
+ * Create a CommentLinkItem specified by an HTMl string and append it to the
+ * given output array.
+ * @param {string|null} html The html to parse and use as the result.
+ * @param {number} position The position inside the source text where the item
+ * starts.
+ * @param {number} length The number of characters in the source text
+ * represented by the item.
+ * @param {!Array<Defs.CommentLinkItem>} outputArray The array to which the
+ * new item is to be appended.
+ */
GrLinkTextParser.prototype.addHTML =
function(html, position, length, outputArray) {
- if (!this.hasOverlap(position, length, outputArray)) {
- this.addItem(null, null, html, position, length, outputArray);
- }
+ if (this.hasOverlap(position, length, outputArray)) { return; }
+ this.addItem(null, null, html, position, length, outputArray);
};
+ /**
+ * Does the given range overlap with anything already in the item list.
+ * @param {number} position
+ * @param {number} length
+ * @param {!Array<Defs.CommentLinkItem>} outputArray
+ */
GrLinkTextParser.prototype.hasOverlap =
function(position, length, outputArray) {
var endPosition = position + length;
@@ -127,12 +213,27 @@
return false;
};
+ /**
+ * Parse the given source text and emit callbacks for the items that are
+ * parsed.
+ * @param {string} text
+ */
GrLinkTextParser.prototype.parse = function(text) {
linkify(text, {
callback: this.parseChunk.bind(this),
});
};
+ /**
+ * Callback that is pased into the linkify function. ba-linkify will call this
+ * method in either of two ways:
+ * - With both a `text` and `href` parameter provided: this indicates that
+ * ba-linkify has found a plain URL and wants it linkified.
+ * - With only a `text` parameter provided: this represents the non-link
+ * content that lies between the links the library has found.
+ * @param {string} text
+ * @param {string|null|undefined} href
+ */
GrLinkTextParser.prototype.parseChunk = function(text, href) {
// TODO(wyatta) switch linkify sequence, see issue 5526.
if (this.removeZeroWidthSpace) {
@@ -147,10 +248,19 @@
if (href && URL_PROTOCOL_PATTERN.test(href)) {
this.addText(text, href);
} else {
+ // For the sections of text that lie between the links found by
+ // ba-linkify, we search for the project-config-specified link patterns.
this.parseLinks(text, this.linkConfig);
}
};
+ /**
+ * Walk over the given source text to find matches for comemntlink patterns
+ * and emit parse result callbacks.
+ * @param {string} text The raw source text.
+ * @param {Object|null|undefined} patterns A comment links specification
+ * object.
+ */
GrLinkTextParser.prototype.parseLinks = function(text, patterns) {
// The outputArray is used to store all of the matches found for all patterns.
var outputArray = [];
@@ -158,10 +268,8 @@
if (patterns[p].enabled != null && patterns[p].enabled == false) {
continue;
}
- // PolyGerrit doesn't use hash-based navigation like GWT.
+ // PolyGerrit doesn't use hash-based navigation like the GWT UI.
// 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="/');
@@ -217,4 +325,4 @@
};
window.GrLinkTextParser = GrLinkTextParser;
-})();
\ No newline at end of file
+})();