diff --git a/docs/component-spec/annotationsSpec.js b/docs/component-spec/annotationsSpec.js
new file mode 100644
index 000000000000..b4c8cd9decbd
--- /dev/null
+++ b/docs/component-spec/annotationsSpec.js
@@ -0,0 +1,186 @@
+describe('Docs Annotations', function() {
+
+ beforeEach(module('docsApp'));
+
+ var body;
+ beforeEach(function() {
+ body = angular.element(document.body);
+ body.html('');
+ });
+
+ describe('popover directive', function() {
+
+ var $scope, element;
+ beforeEach(inject(function($rootScope, $compile) {
+ $scope = $rootScope.$new();
+ element = angular.element(
+ '
'
+ );
+ element.attr('id','idx');
+ body.append(element);
+ $compile(element)($scope);
+ $scope.$apply();
+ }));
+
+ it('should be hidden by default', inject(function(popoverElement) {
+ expect(popoverElement.visible()).toBe(false);
+ }));
+
+ it('should capture the click event and set the title and content and position the tip', inject(function(popoverElement) {
+ element.triggerHandler('click');
+ expect(popoverElement.isSituatedAt(element)).toBe(true);
+ expect(popoverElement.visible()).toBe(true);
+ expect(popoverElement.title()).toBe('title_text');
+ expect(popoverElement.content()).toContain('content_text');
+ expect(popoverElement.besideElement.attr('id')).toBe('idx');
+ }));
+
+ it('should hide and clear the title and content if the same element is clicked again', inject(function(popoverElement) {
+ //show the element
+ element.triggerHandler('click');
+ expect(popoverElement.isSituatedAt(element)).toBe(true);
+
+ //hide the element
+ element.triggerHandler('click');
+ expect(popoverElement.isSituatedAt(element)).toBe(false);
+ expect(popoverElement.visible()).toBe(false);
+ expect(popoverElement.title()).toBe('');
+ expect(popoverElement.content()).toBe('');
+ }));
+
+ it('should parse markdown content', inject(function(popoverElement, $compile) {
+ element = angular.element(
+ ''
+ );
+ body.append(element);
+ $compile(element)($scope);
+ $scope.$apply();
+ element.triggerHandler('click');
+ expect(popoverElement.title()).toBe('#title_text');
+ expect(popoverElement.content()).toBe('heading
');
+ }));
+
+ });
+
+
+ describe('foldout directive', function() {
+
+ var $scope, parent, element, url, window;
+ beforeEach(function() {
+ module(function($provide, $animationProvider) {
+ $provide.value('$window', window = angular.mock.createMockWindow());
+ $animationProvider.register('foldout-enter', function($window) {
+ return {
+ start : function(element, done) {
+ $window.setTimeout(done, 1000);
+ }
+ }
+ });
+ $animationProvider.register('foldout-hide', function($window) {
+ return {
+ start : function(element, done) {
+ $window.setTimeout(done, 500);
+ }
+ }
+ });
+ $animationProvider.register('foldout-show', function($window) {
+ return {
+ start : function(element, done) {
+ $window.setTimeout(done, 200);
+ }
+ }
+ });
+ });
+ inject(function($rootScope, $compile, $templateCache) {
+ url = '/page.html';
+ $scope = $rootScope.$new();
+ parent = angular.element('');
+ element = angular.element('');
+ body.append(parent);
+ parent.append(element);
+ $compile(parent)($scope);
+ $scope.$apply();
+ });
+ });
+
+ it('should inform that it is loading', inject(function($httpBackend) {
+ $httpBackend.expect('GET', url).respond('hello');
+ element.triggerHandler('click');
+
+ var kids = body.children();
+ var foldout = angular.element(kids[kids.length-1]);
+ expect(foldout.html()).toContain('loading');
+ }));
+
+ it('should download a foldout HTML page and animate the contents', inject(function($httpBackend) {
+ $httpBackend.expect('GET', url).respond('hello');
+
+ element.triggerHandler('click');
+ $httpBackend.flush();
+
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(1000).process();
+
+ var kids = body.children();
+ var foldout = angular.element(kids[kids.length-1]);
+ expect(foldout.text()).toContain('hello');
+ }));
+
+ it('should hide then show when clicked again', inject(function($httpBackend) {
+ $httpBackend.expect('GET', url).respond('hello');
+
+ //enter
+ element.triggerHandler('click');
+ $httpBackend.flush();
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(1000).process();
+
+ //hide
+ element.triggerHandler('click');
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(500).process();
+
+ //show
+ element.triggerHandler('click');
+ window.setTimeout.expect(1).process();
+ window.setTimeout.expect(200).process();
+ }));
+
+ });
+
+ describe('DocsController fold', function() {
+
+ var window, $scope, ctrl;
+ beforeEach(function() {
+ module(function($provide, $animationProvider) {
+ $provide.value('$window', window = angular.mock.createMockWindow());
+ });
+ inject(function($rootScope, $controller, $location, $cookies, sections) {
+ $scope = $rootScope.$new();
+ ctrl = $controller('DocsController',{
+ $scope : $scope,
+ $location : $location,
+ $window : window,
+ $cookies : $cookies,
+ sections : sections
+ });
+ });
+ });
+
+ it('should download and reveal the foldover container', inject(function($compile, $httpBackend) {
+ var url = '/page.html';
+ var fullUrl = '/notes/' + url;
+ $httpBackend.expect('GET', fullUrl).respond('hello');
+
+ var element = angular.element('');
+ $compile(element)($scope);
+ $scope.$apply();
+
+ $scope.fold(url);
+
+ $httpBackend.flush();
+ }));
+
+ });
+
+});
diff --git a/docs/components/angular-bootstrap/bootstrap-prettify.js b/docs/components/angular-bootstrap/bootstrap-prettify.js
index f1c3fccb3e4a..dc2a34e2b9bd 100644
--- a/docs/components/angular-bootstrap/bootstrap-prettify.js
+++ b/docs/components/angular-bootstrap/bootstrap-prettify.js
@@ -96,9 +96,12 @@ directive.code = function() {
directive.prettyprint = ['reindentCode', function(reindentCode) {
return {
restrict: 'C',
- terminal: true,
compile: function(element) {
- element.html(window.prettyPrintOne(reindentCode(element.html()), undefined, true));
+ var html = element.html();
+ //ensure that angular won't compile {{ curly }} values
+ html = html.replace(/\{\{/g, '{{')
+ .replace(/\}\}/g, '}}');
+ element.html(window.prettyPrintOne(reindentCode(html), undefined, true));
}
};
}];
diff --git a/docs/components/angular-bootstrap/bootstrap.js b/docs/components/angular-bootstrap/bootstrap.js
index 3e1c8d00ffc2..3bcc18fae6f7 100644
--- a/docs/components/angular-bootstrap/bootstrap.js
+++ b/docs/components/angular-bootstrap/bootstrap.js
@@ -198,6 +198,133 @@ directive.table = function() {
};
};
+var popoverElement = function() {
+ var object = {
+ init : function() {
+ this.element = angular.element(
+ ''
+ );
+ this.node = this.element[0];
+ this.element.css({
+ 'display':'block',
+ 'position':'absolute'
+ });
+ angular.element(document.body).append(this.element);
+
+ var inner = this.element.children()[1];
+ this.titleElement = angular.element(inner.childNodes[0].firstChild);
+ this.contentElement = angular.element(inner.childNodes[1]);
+
+ //stop the click on the tooltip
+ this.element.bind('click', function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ });
+
+ var self = this;
+ angular.element(document.body).bind('click',function(event) {
+ if(self.visible()) self.hide();
+ });
+ },
+
+ show : function(x,y) {
+ this.element.addClass('visible');
+ this.position(x || 0, y || 0);
+ },
+
+ hide : function() {
+ this.element.removeClass('visible');
+ this.position(-9999,-9999);
+ },
+
+ visible : function() {
+ return this.position().y >= 0;
+ },
+
+ isSituatedAt : function(element) {
+ return this.besideElement ? element[0] == this.besideElement[0] : false;
+ },
+
+ title : function(value) {
+ return this.titleElement.html(value);
+ },
+
+ content : function(value) {
+ if(value && value.length > 0) {
+ value = new Showdown.converter().makeHtml(value);
+ }
+ return this.contentElement.html(value);
+ },
+
+ positionArrow : function(position) {
+ this.node.className = 'popover ' + position;
+ },
+
+ positionAway : function() {
+ this.besideElement = null;
+ this.hide();
+ },
+
+ positionBeside : function(element) {
+ this.besideElement = element;
+
+ var elm = element[0];
+ var x = elm.offsetLeft;
+ var y = elm.offsetTop;
+ x -= 30;
+ y -= this.node.offsetHeight + 10;
+ this.show(x,y);
+ },
+
+ position : function(x,y) {
+ if(x != null && y != null) {
+ this.element.css('left',x + 'px');
+ this.element.css('top', y + 'px');
+ }
+ else {
+ return {
+ x : this.node.offsetLeft,
+ y : this.node.offsetTop
+ };
+ }
+ }
+ };
+
+ object.init();
+ object.hide();
+
+ return object;
+};
+
+directive.popover = ['popoverElement', function(popover) {
+ return {
+ restrict: 'A',
+ priority : 500,
+ link: function(scope, element, attrs) {
+ element.bind('click',function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ if(popover.isSituatedAt(element) && popover.visible()) {
+ popover.title('');
+ popover.content('');
+ popover.positionAway();
+ }
+ else {
+ popover.title(attrs.title);
+ popover.content(attrs.content);
+ popover.positionBeside(element);
+ }
+ });
+ }
+ }
+}];
+
directive.tabPane = function() {
return {
require: '^tabbable',
@@ -208,5 +335,49 @@ directive.tabPane = function() {
};
};
+directive.foldout = ['$http', '$animator','$window', function($http, $animator, $window) {
+ return {
+ restrict: 'A',
+ priority : 500,
+ link: function(scope, element, attrs) {
+ var animator = $animator(scope, { ngAnimate: "'foldout'" });
+ var container, loading, url = attrs.url;
+ if(/\/build\//.test($window.location.href)) {
+ url = '/build/docs' + url;
+ }
+ element.bind('click',function() {
+ scope.$apply(function() {
+ if(!container) {
+ if(loading) return;
+
+ loading = true;
+ var par = element.parent();
+ container = angular.element('loading...
');
+ animator.enter(container, null, par);
+
+ $http.get(url, { cache : true }).success(function(html) {
+ loading = false;
+
+ html = '';
+ container.html(html);
+
+ //avoid showing the element if the user has already closed it
+ if(container.css('display') == 'block') {
+ container.css('display','none');
+ animator.show(container);
+ }
+ });
+ }
+ else {
+ container.css('display') == 'none' ? animator.show(container) : animator.hide(container);
+ }
+ });
+ });
+ }
+ }
+}];
-angular.module('bootstrap', []).directive(directive);
+angular.module('bootstrap', []).directive(directive).factory('popoverElement', popoverElement);
diff --git a/docs/content/notes/empty.tmp b/docs/content/notes/empty.tmp
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/docs/spec/ngdocSpec.js b/docs/spec/ngdocSpec.js
index 9eed24ca28db..7a038e892450 100644
--- a/docs/spec/ngdocSpec.js
+++ b/docs/spec/ngdocSpec.js
@@ -196,6 +196,28 @@ describe('ngdoc', function() {
});
+ describe('inline annotations', function() {
+ it('should convert inline docs annotations into proper HTML', function() {
+ expect(new Doc().markdown(
+ "\n//!annotate supertext\n
\n
"
+ )
+ ).toContain('data-popover data-content="supertext"')
+ });
+
+ it('should allow for a custom regular expression for matching', function() {
+ expect(new Doc().markdown(
+ "\n//!annotate=\"soon\" supertext\nsoon
\n
"
+ )
+ ).toContain('data-popover data-content="supertext" data-title="Info">soon')
+ });
+
+ it('should allow for a custom title to be set', function() {
+ expect(new Doc().markdown(
+ "\n//!annotate=\"soon\" coming soon|supertext\nsoon
\n
"
+ )
+ ).toContain('data-popover data-content="supertext" data-title="coming soon">soon')
+ });
+ });
});
describe('trim', function() {
diff --git a/docs/src/dom.js b/docs/src/dom.js
index 9404812028b7..897a1831d786 100644
--- a/docs/src/dom.js
+++ b/docs/src/dom.js
@@ -8,7 +8,12 @@ exports.htmlEscape = htmlEscape;
//////////////////////////////////////////////////////////
function htmlEscape(text){
- return text.replace(/&/g, '&').replace(//g, '>');
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\{\{/g, '{{')
+ .replace(/\}\}/g, '}}');
}
diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js
index 4fd3d4f2d68a..52d1f6298532 100755
--- a/docs/src/gen-docs.js
+++ b/docs/src/gen-docs.js
@@ -44,6 +44,7 @@ writer.makeDir('build/docs/', true).then(function() {
function writeTheRest(writesFuture) {
var metadata = ngdoc.metadata(docs);
+ writesFuture.push(writer.symlink('../../docs/content/notes', 'build/docs/notes', 'dir'));
writesFuture.push(writer.symlinkTemplate('css', 'dir'));
writesFuture.push(writer.symlink('../../docs/img', 'build/docs/img', 'dir'));
writesFuture.push(writer.symlinkTemplate('js', 'dir'));
diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js
index c34b8475f6a7..1870f87c7246 100644
--- a/docs/src/ngdoc.js
+++ b/docs/src/ngdoc.js
@@ -225,6 +225,44 @@ Doc.prototype = {
text = text.replace(/(?:)?(REPLACEME\d+)(?:<\/p>)?/g, function(_, id) {
return placeholderMap[id];
});
+
+ //!annotate CONTENT
+ //!annotate="REGEX" CONTENT
+ //!annotate="REGEX" TITLE|CONTENT
+ text = text.replace(/\n?\/\/!annotate\s*(?:=\s*['"](.+?)['"])?\s+(.+?)\n\s*(.+?\n)/img,
+ function(_, pattern, content, line) {
+ var pattern = new RegExp(pattern || '.+');
+ var title, text, split = content.split(/\|/);
+ if(split.length > 1) {
+ text = split[1];
+ title = split[0];
+ }
+ else {
+ title = 'Info';
+ text = content;
+ }
+ return "\n" + line.replace(pattern, function(match) {
+ return '
' +
+ match +
+ '
';
+ });
+ }
+ );
+
+ //!details /path/to/local/docs/file.html
+ //!details="REGEX" /path/to/local/docs/file.html
+ text = text.replace(/\/\/!details\s*(?:=\s*['"](.+?)['"])?\s+(.+?)\n\s*(.+?\n)/img,
+ function(_, pattern, url, line) {
+ url = '/notes/' + url;
+ var pattern = new RegExp(pattern || '.+');
+ return line.replace(pattern, function(match) {
+ return '' + match + '
';
+ });
+ }
+ );
+
return text;
},
diff --git a/docs/src/templates/css/animations.css b/docs/src/templates/css/animations.css
index d8c983a32a97..2d54bbfba994 100644
--- a/docs/src/templates/css/animations.css
+++ b/docs/src/templates/css/animations.css
@@ -79,3 +79,26 @@
-o-transition: color 0 ease-in; /* opera is special :) */
transition: none;
}
+
+.foldout-show, .foldout-enter, .foldout-hide {
+ -webkit-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
+ -moz-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
+ -o-transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
+ transition:0.3s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
+}
+
+.foldout-show, .foldout-enter {
+ opacity:0;
+}
+
+.foldout-show.foldout-show-active, .foldout-hide.foldout-hide-active {
+ opacity:1;
+}
+
+.foldout-hide {
+ opacity:1;
+}
+
+.foldout-hide.foldout-hide-active {
+ opacity:0;
+}
diff --git a/docs/src/templates/css/docs.css b/docs/src/templates/css/docs.css
index a98f7429cae3..1b87c551282d 100644
--- a/docs/src/templates/css/docs.css
+++ b/docs/src/templates/css/docs.css
@@ -303,3 +303,162 @@ ul.events > li > h3 {
top:0;
right:0;
}
+
+.nocode-content {
+ cursor:pointer;
+ display:inline-block;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+
+ -webkit-transition:0.5s linear all;
+ -moz-transition:0.5s linear all;
+ -o-transition:0.5s linear all;
+ transition:0.5s linear all;
+ color: #223f7a;
+ background:#ddd;
+ border: 1px solid #ccc;
+}
+
+.nocode-content:hover {
+ background-color: #99c2ff;
+ border: 1px solid #e1e1e8;
+}
+
+.popover-incode .popover-inner {
+ width:auto;
+ min-width:200px;
+ max-width:500px;
+}
+
+.popover-incode {
+ -webkit-transition:0.2s linear opacity;
+ -moz-transition:0.2s linear opacity;
+ -o-transition:0.2s linear opacity;
+ transition:0.2s linear opacity;
+ opacity:0;
+}
+
+.popover-incode.visible {
+ opacity:1;
+}
+
+.popover-incode code,
+.popover-incode pre {
+ white-space:nowrap;
+}
+
+.popover-incode .arrow {
+ left:50px!important;
+}
+
+.foldover-content {
+ display:none;
+}
+
+.foldout:after {
+ content:"";
+ position:absolute;
+ left:50%;
+ top:-1px;
+ margin-left:-10px;
+ border-width:10px;
+ border-style:solid;
+ border-color:#f7f7f9 transparent transparent;
+}
+
+.foldout:before {
+ content:"";
+ position:absolute;
+ left:50%;
+ top:0px;
+ margin-left:-10px;
+ border-width:10px;
+ border-style:solid;
+ border-color:#bbb transparent transparent;
+}
+
+.foldout {
+ padding:8px 15px 5px;
+ position:relative;
+ background:#eee;
+ white-space:normal;
+ box-shadow:inset 0 0 20px #ccc;
+ border-top:1px solid #bbb;
+}
+
+.prettyprint {
+ padding-right:0!important;
+ padding-bottom:0!important;
+}
+
+pre ol li {
+ padding-bottom:2px;
+ padding-right:5px;
+}
+
+#docs-fold {
+ position:absolute;
+ top:0;
+ right:0;
+ width:500px;
+ min-height:100%;
+ padding-top:50px;
+ padding:50px 20px 20px 20px;
+ background:white;
+ border-left:1px solid #999;
+ box-shadow:0 0 10px #555;
+ z-index:1002;
+}
+
+#docs-fold.fold-show {
+ -webkit-transition:0.4s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
+ -moz-transition:0.4s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
+ -o-transition:0.4s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
+ transition:0.4s cubic-bezier(0.250, 0.460, 0.450, 0.940) all;
+}
+
+#docs-fold.fold-show {
+ right:-200px;
+ opacity:0;
+}
+
+#docs-fold.fold-show.fold-show-active {
+ right:0;
+ opacity:1;
+}
+
+#docs-fold-overlay {
+ background:rgba(255,255,255,0.5);
+ position:fixed;
+ left:0;
+ bottom:0;
+ right:0;
+ top:0;
+ z-index:1001;
+ cursor:pointer;
+}
+
+.fixed_body {
+ position:fixed;
+ top:0;
+ z-index:1000;
+ left:0;
+ right:0;
+}
+
+#docs-fold-close {
+ z-index: 1029;
+ position: absolute;
+ left: -30px;
+ top: 60px;
+ cursor:pointer;
+ text-align: center;
+ width:50px;
+ line-height:50px;
+ font-size: 2em;
+ background: #fff;
+ box-shadow:-6px 0 5px #555;
+ display:block;
+ border-radius:10px;
+}
diff --git a/docs/src/templates/index.html b/docs/src/templates/index.html
index 3b75207e7435..8e2fbc37f6eb 100644
--- a/docs/src/templates/index.html
+++ b/docs/src/templates/index.html
@@ -198,6 +198,15 @@ {{ key }}
+
+
+
+
@@ -359,6 +368,7 @@
Would you like full offline support for this AngularJS Docs App?
+