diff --git a/docs/component-spec/errorLinkFilterSpec.js b/docs/component-spec/errorLinkFilterSpec.js
new file mode 100644
index 000000000000..36f5f46b2725
--- /dev/null
+++ b/docs/component-spec/errorLinkFilterSpec.js
@@ -0,0 +1,49 @@
+describe("errorLinkFilter", function () {
+
+ var errorLinkFilter;
+
+ beforeEach(module('docsApp'));
+
+ beforeEach(inject(function ($filter) {
+ errorLinkFilter = $filter('errorLink');
+ }));
+
+ it('should not change text that does not contain links', function () {
+ expect(errorLinkFilter('This is a test')).toBe('This is a test');
+ });
+
+ it('should find links in text and linkify them', function () {
+ expect(errorLinkFilter("http://ab/ (http://a/) http://1.2/v:~-123. c")).
+ toBe('http://ab/ ' +
+ '(http://a/) ' +
+ 'http://1.2/v:~-123. c');
+ expect(errorLinkFilter(undefined)).not.toBeDefined();
+ });
+
+ it('should handle mailto', function () {
+ expect(errorLinkFilter("mailto:me@example.com")).
+ toBe('me@example.com');
+ expect(errorLinkFilter("me@example.com")).
+ toBe('me@example.com');
+ expect(errorLinkFilter("send email to me@example.com, but")).
+ toBe('send email to me@example.com, but');
+ });
+
+ it('should handle target', function () {
+ expect(errorLinkFilter("http://example.com", "_blank")).
+ toBe('http://example.com')
+ expect(errorLinkFilter("http://example.com", "someNamedIFrame")).
+ toBe('http://example.com')
+ });
+
+ it('should not linkify stack trace URLs', function () {
+ expect(errorLinkFilter("http://example.com/angular.min.js:42:1337")).
+ toBe("http://example.com/angular.min.js:42:1337");
+ });
+
+ it('should truncate linked URLs at 60 characters', function () {
+ expect(errorLinkFilter("http://errors.angularjs.org/very-long-version-string/$injector/nomod?p0=myApp")).
+ toBe('' +
+ 'http://errors.angularjs.org/very-long-version-string/$inj...');
+ });
+});
\ No newline at end of file
diff --git a/docs/src/templates/js/docs.js b/docs/src/templates/js/docs.js
index 9b38313b5e93..b8c42ef877dc 100644
--- a/docs/src/templates/js/docs.js
+++ b/docs/src/templates/js/docs.js
@@ -1,7 +1,8 @@
var docsApp = {
controller: {},
directive: {},
- serviceFactory: {}
+ serviceFactory: {},
+ filter: {}
};
docsApp.controller.DocsVersionsCtrl = ['$scope', '$window', 'NG_VERSIONS', 'NG_VERSION', function($scope, $window, NG_VERSIONS, NG_VERSION) {
@@ -255,7 +256,40 @@ docsApp.directive.docTutorialReset = function() {
};
-docsApp.directive.errorDisplay = ['$location', function ($location) {
+docsApp.filter.errorLink = ['$sanitize', function ($sanitize) {
+ var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/g,
+ MAILTO_REGEXP = /^mailto:/,
+ STACK_TRACE_REGEXP = /:\d+:\d+$/;
+
+ var truncate = function (text, nchars) {
+ if (text.length > nchars) {
+ return text.substr(0, nchars - 3) + '...';
+ }
+ return text;
+ };
+
+ return function (text, target) {
+ var targetHtml = target ? ' target="' + target + '"' : '';
+
+ if (!text) return text;
+
+ return $sanitize(text.replace(LINKY_URL_REGEXP, function (url) {
+ if (STACK_TRACE_REGEXP.test(url)) {
+ return url;
+ }
+
+ // if we did not match ftp/http/mailto then assume mailto
+ if (!/^((ftp|https?):\/\/|mailto:)/.test(url)) url = 'mailto:' + url;
+
+ return '' +
+ truncate(url.replace(MAILTO_REGEXP, ''), 60) +
+ '';
+ }));
+ };
+}];
+
+
+docsApp.directive.errorDisplay = ['$location', 'errorLinkFilter', function ($location, errorLinkFilter) {
var interpolate = function (formatString) {
var formatArgs = arguments;
return formatString.replace(/\{\d+\}/g, function (match) {
@@ -278,7 +312,7 @@ docsApp.directive.errorDisplay = ['$location', function ($location) {
for (i = 0; angular.isDefined(search['p'+i]); i++) {
formatArgs.push(search['p'+i]);
}
- element.text(interpolate.apply(null, formatArgs));
+ element.html(errorLinkFilter(interpolate.apply(null, formatArgs), '_blank'));
}
};
}];
@@ -873,3 +907,7 @@ angular.module('docsApp', ['ngResource', 'ngRoute', 'ngCookies', 'ngSanitize', '
factory(docsApp.serviceFactory).
directive(docsApp.directive).
controller(docsApp.controller);
+
+angular.forEach(docsApp.filter, function (docsAppFilter, filterName) {
+ angular.module('docsApp').filter(filterName, docsAppFilter);
+});