aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Rakefile29
-rw-r--r--docs/content/cookbook/deeplinking.ngdoc10
-rw-r--r--docs/spec/ngdocSpec.js14
-rw-r--r--docs/src/appCache.js4
-rwxr-xr-xdocs/src/gen-docs.js21
-rw-r--r--docs/src/ngdoc.js12
-rw-r--r--docs/src/templates/.htaccess6
-rw-r--r--docs/src/templates/docs.js11
-rw-r--r--docs/src/templates/index.html86
-rw-r--r--lib/nodeserver/server.js12
-rw-r--r--src/markups.js15
-rw-r--r--src/widgets.js10
12 files changed, 150 insertions, 80 deletions
diff --git a/Rakefile b/Rakefile
index f7183c09..642ef567 100644
--- a/Rakefile
+++ b/Rakefile
@@ -209,7 +209,8 @@ task :package => [:clean, :compile, :docs] do
text = f.read
f.truncate 0
f.rewind
- f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
+ f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
+ sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end
@@ -217,10 +218,28 @@ task :package => [:clean, :compile, :docs] do
text = f.read
f.truncate 0
f.rewind
- f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
+ f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
+ sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end
+ File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-debug.html", File::RDWR) do |f|
+ text = f.read
+ f.truncate 0
+ f.rewind
+ f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
+ sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
+ end
+
+
+ File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-debug.html", File::RDWR) do |f|
+ text = f.read
+ f.truncate 0
+ f.rewind
+ f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
+ sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
+ end
+
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/docs-scenario.html", File::RDWR) do |f|
text = f.read
f.truncate 0
@@ -232,14 +251,16 @@ task :package => [:clean, :compile, :docs] do
text = f.read
f.truncate 0
f.rewind
- f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
+ f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
+ sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache-offline.manifest", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
- f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
+ f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
+ sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
end
diff --git a/docs/content/cookbook/deeplinking.ngdoc b/docs/content/cookbook/deeplinking.ngdoc
index 10c80f21..6747b55f 100644
--- a/docs/content/cookbook/deeplinking.ngdoc
+++ b/docs/content/cookbook/deeplinking.ngdoc
@@ -28,14 +28,14 @@ controller.
In this example we have a simple app which consist of two screens:
-* Welcome: url `#` Show the user contact information.
-* Settings: url `#/settings` Show an edit screen for user contact information.
+* Welcome: url `welcome` Show the user contact information.
+* Settings: url `settings` Show an edit screen for user contact information.
The two partials are defined in the following URLs:
-* {@link ./examples/settings.html}
-* {@link ./examples/welcome.html}
+* <a href="./examples/settings.html" ng:ext-link>./examples/settings.html</a>
+* <a href="./examples/welcome.html" ng:ext-link>./examples/welcome.html</a>
<doc:example>
<doc:source>
@@ -79,7 +79,7 @@ The two partials are defined in the following URLs:
</script>
<div ng:controller="AppCntl">
<h1>Your App Chrome</h1>
- [ <a href="#!/welcome">Welcome</a> | <a href="#!/settings">Settings</a> ]
+ [ <a href="welcome">Welcome</a> | <a href="settings">Settings</a> ]
<hr/>
<span style="background-color: blue; color: white; padding: 3px;">
Partial: {{$route.current.template}}
diff --git a/docs/spec/ngdocSpec.js b/docs/spec/ngdocSpec.js
index 44d8dc3c..106fd22b 100644
--- a/docs/spec/ngdocSpec.js
+++ b/docs/spec/ngdocSpec.js
@@ -327,8 +327,8 @@ describe('ngdoc', function(){
expect(doc.requires).toEqual([
{name:'$service', text:'<p>for \n<code>A</code></p>'},
{name:'$another', text:'<p>for <code>B</code></p>'}]);
- expect(doc.html()).toContain('<a href="#!/api/angular.service.$service">$service</a>');
- expect(doc.html()).toContain('<a href="#!/api/angular.service.$another">$another</a>');
+ expect(doc.html()).toContain('<a href="api/angular.service.$service">$service</a>');
+ expect(doc.html()).toContain('<a href="api/angular.service.$another">$another</a>');
expect(doc.html()).toContain('<p>for \n<code>A</code></p>');
expect(doc.html()).toContain('<p>for <code>B</code></p>');
});
@@ -429,13 +429,13 @@ describe('ngdoc', function(){
doc.parse();
expect(doc.description).
- toContain('foo <a href="#!/api/angular.foo"><code>angular.foo</code></a>');
+ toContain('foo <a href="api/angular.foo"><code>angular.foo</code></a>');
expect(doc.description).
- toContain('da <a href="#!/api/angular.foo"><code>bar foo bar</code></a>');
+ toContain('da <a href="api/angular.foo"><code>bar foo bar</code></a>');
expect(doc.description).
- toContain('dad<a href="#!/api/angular.foo"><code>angular.foo</code></a>');
+ toContain('dad<a href="api/angular.foo"><code>angular.foo</code></a>');
expect(doc.description).
- toContain('<a href="#!/api/angular.directive.ng:foo"><code>ng:foo</code></a>');
+ toContain('<a href="api/angular.directive.ng:foo"><code>ng:foo</code></a>');
expect(doc.description).
toContain('<a href="http://angularjs.org">http://angularjs.org</a>');
expect(doc.description).
@@ -447,7 +447,7 @@ describe('ngdoc', function(){
'{@link\napi/angular.foo\na\nb}');
doc.parse();
expect(doc.description).
- toContain('<a href="#!/api/angular.foo"><code>a b</code></a>');
+ toContain('<a href="api/angular.foo"><code>a b</code></a>');
});
});
diff --git a/docs/src/appCache.js b/docs/src/appCache.js
index ed35eb79..7b21624e 100644
--- a/docs/src/appCache.js
+++ b/docs/src/appCache.js
@@ -29,7 +29,7 @@ function appCache(path) {
var resultPostfix = ["",
"FALLBACK:",
- "/offline.html",
+ "/ /build/docs/index.html",
"",
"# allow access to google analytics and twitter when we are online",
"NETWORK:",
@@ -68,7 +68,7 @@ function appCacheTemplate() {
"img/yellow_bkgnd.jpg",
"",
"FALLBACK:",
- "/ offline.html",
+ "/ /build/docs/offline.html",
"",
"# allow access to google analytics and twitter when we are online",
"NETWORK:",
diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js
index e1778bb7..c7b37025 100755
--- a/docs/src/gen-docs.js
+++ b/docs/src/gen-docs.js
@@ -22,7 +22,7 @@ writer.makeDir('build/docs/syntaxhighlighter').then(function() {
ngdoc.merge(docs);
var fileFutures = [];
docs.forEach(function(doc){
- fileFutures.push(writer.output(doc.section + '/' + doc.id + '.html', doc.html()));
+ fileFutures.push(writer.output('partials/' + doc.section + '/' + doc.id + '.html', doc.html()));
});
writeTheRest(fileFutures);
@@ -43,28 +43,19 @@ function writeTheRest(writesFuture) {
writesFuture.push(writer.copyDir('img'));
writesFuture.push(writer.copyDir('examples'));
- var manifest = 'manifest="appcache.manifest"',
- jq = '<script src="jquery.min.js"></script>',
- ngMin = '<script src="../angular.min.js" ng:autobind></script>',
- ng = '<script src="../angular.js" ng:autobind></script>';
+ var manifest = 'manifest="/build/docs/appcache.manifest"';
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index.html',
- writer.replace, {'doc:manifest': manifest,
- '<!-- angular script place holder -->': ngMin}));
+ writer.replace, {'doc:manifest': manifest}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq.html',
- writer.replace, {'doc:manifest': manifest,
- '<!-- angular script place holder -->': ngMin,
- '<!-- jquery place holder -->': jq}));
+ writer.replace, {'doc:manifest': manifest}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-debug.html',
- writer.replace, {'doc:manifest': '',
- '<!-- angular script place holder -->': ng}));
+ writer.replace, {'doc:manifest': ''}));
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-debug.html',
- writer.replace, {'doc:manifest': '',
- '<!-- angular script place holder -->': ng,
- '<!-- jquery place holder -->': jq}));
+ writer.replace, {'doc:manifest': ''}));
writesFuture.push(writer.copyTpl('offline.html'));
writesFuture.push(writer.copyTpl('docs-scenario.html'));
diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js
index 1045d39d..abe5e1d7 100644
--- a/docs/src/ngdoc.js
+++ b/docs/src/ngdoc.js
@@ -133,7 +133,7 @@ Doc.prototype = {
if (!isFullUrl) self.links.push(absUrl);
- return '<a href="' + (isFullUrl ? '' + url : '#!/' + absUrl) + '">'
+ return '<a href="' + absUrl + '">'
+ (isAngular ? '<code>' : '')
+ (title || url).replace(/\n/g, ' ')
+ (isAngular ? '</code>' : '')
@@ -243,7 +243,7 @@ Doc.prototype = {
}
dom.h('Dependencies', self.requires, function(require){
dom.tag('code', function(){
- dom.tag('a', {href:"#!/api/angular.service." + require.name}, require.name);
+ dom.tag('a', {href: 'api/angular.service.' + require.name}, require.name);
});
dom.html(require.text);
});
@@ -570,23 +570,23 @@ function scenarios(docs){
var specs = [];
specs.push('describe("angular+jqlite", function() {');
- appendSpecs('index.html');
+ appendSpecs('');
specs.push('});');
specs.push('');
specs.push('');
specs.push('describe("angular+jquery", function() {');
- appendSpecs('index-jq.html');
+ appendSpecs('index-jq.html#!/');
specs.push('});');
return specs.join('\n');
- function appendSpecs(htmlFile) {
+ function appendSpecs(urlPrefix) {
docs.forEach(function(doc){
specs.push(' describe("' + doc.section + '/' + doc.id + '", function(){');
specs.push(' beforeEach(function(){');
- specs.push(' browser().navigateTo("' + htmlFile + '#!/' + doc.section + '/' + doc.id + '");');
+ specs.push(' browser().navigateTo("' + urlPrefix + doc.section + '/' + doc.id + '");');
specs.push(' });');
specs.push(' ');
doc.scenarios.forEach(function(scenario){
diff --git a/docs/src/templates/.htaccess b/docs/src/templates/.htaccess
index 87487e9e..9f9a152c 100644
--- a/docs/src/templates/.htaccess
+++ b/docs/src/templates/.htaccess
@@ -8,4 +8,8 @@
RewriteEngine on
RewriteCond %{HTTP_COOKIE} ng-offline="NG_VERSION_FULL"
-RewriteRule appcache.manifest appcache-offline.manifest \ No newline at end of file
+RewriteRule appcache.manifest appcache-offline.manifest
+
+
+## HTML5 URL Support ##
+RewriteRule ^(guide|api|cookbook|misc|tutorial)(/.*)?$ index.html
diff --git a/docs/src/templates/docs.js b/docs/src/templates/docs.js
index d1069a77..505aed60 100644
--- a/docs/src/templates/docs.js
+++ b/docs/src/templates/docs.js
@@ -4,7 +4,8 @@ function DocsController($location, $browser, $window, $cookies) {
var self = this,
OFFLINE_COOKIE_NAME = 'ng-offline',
- DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/;
+ DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/,
+ INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
this.$location = $location;
@@ -13,7 +14,7 @@ function DocsController($location, $browser, $window, $cookies) {
self.subpage = false;
self.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
- if (!$location.path()) {
+ if (!$location.path() || INDEX_PATH.test($location.path())) {
$location.path('/api').replace();
}
@@ -40,11 +41,11 @@ function DocsController($location, $browser, $window, $cookies) {
});
this.getUrl = function(page){
- return '#!/' + page.section + '/' + page.id;
+ return page.section + '/' + page.id;
};
this.getCurrentPartial = function(){
- return this.partialId ? ('./' + this.sectionId + '/' + this.partialId + '.html') : '';
+ return this.partialId ? ('./partials/' + this.sectionId + '/' + this.partialId + '.html') : '';
};
this.getClass = function(page) {
@@ -127,7 +128,7 @@ function TutorialInstructionsCtrl($cookieStore) {
angular.service('$locationConfig', function() {
return {
- html5Mode: false,
+ html5Mode: true,
hashPrefix: '!'
};
});
diff --git a/docs/src/templates/index.html b/docs/src/templates/index.html
index d5cfaed2..a2def7a6 100644
--- a/docs/src/templates/index.html
+++ b/docs/src/templates/index.html
@@ -5,23 +5,57 @@
doc:manifest>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
- <title ng:bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
<meta name="fragment" content="!">
- <link rel="stylesheet" href="docs-combined.css" type="text/css"/>
- <link rel="stylesheet" href="syntaxhighlighter/syntaxhighlighter-combined.css" type="text/css"/>
+ <title ng:bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
+ <script type="text/javascript">
+ // dynamically add base tag as well as css and javascript files.
+ // we can't add css/js the usual way, because some browsers (FF) eagerly prefetch resources
+ // before the base attribute is added, causing 404 and terribly slow loading of the docs app.
+ (function() {
+ var indexFile = (location.pathname.match(/\/(index[^\.]*\.html)/) || ['', 'index.html'])[1],
+ rUrl = /(api|guide|misc|tutorial|cookbook|index[^\.]*\.html).*$/,
+ baseUrl = location.href.replace(rUrl, indexFile),
+ jQuery = /index-jq[^\.]*\.html$/.test(baseUrl),
+ debug = /index[^\.]*-debug\.html$/.test(baseUrl),
+ angularPath = debug ? '../angular.js' : '../angular.min.js',
+ headEl = document.getElementsByTagName('head')[0],
+ sync = true;
- <script>
- // GA asynchronous tracker
- var _gaq = _gaq || [];
- _gaq.push(['_setAccount', 'UA-8594346-3']);
- _gaq.push(['_setDomainName', '.angularjs.org']);
+ addTag('base', {href: baseUrl});
+ addTag('link', {rel: 'stylesheet', href: 'docs-combined.css', type: 'text/css'});
+ addTag('link', {rel: 'stylesheet', href: 'syntaxhighlighter/syntaxhighlighter-combined.css',
+ type: 'text/css'});
+ addTag('script', {src: 'syntaxhighlighter/syntaxhighlighter-combined.js'}, sync);
+ if (jQuery) addTag('script', {src: 'jquery.min.js'});
+ addTag('script', {src: angularPath, 'ng:autobind':''}, sync);
+ addTag('script', {src: 'docs-combined.js'}, sync);
+ addTag('script', {src: 'docs-keywords.js'}, sync);
- (function() {
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ function addTag(name, attributes, sync) {
+ var el = document.createElement(name),
+ attrName;
+
+ for (attrName in attributes) {
+ el.setAttribute(attrName, attributes[attrName]);
+ }
+
+ sync ? document.write(outerHTML(el)) : headEl.appendChild(el);
+ }
+
+ function outerHTML(node){
+ // if IE, Chrome take the internal method otherwise build one
+ return node.outerHTML || (
+ function(n){
+ var div = document.createElement('div'), h;
+ div.appendChild(n);
+ h = div.innerHTML;
+ div = null;
+ return h;
+ })(node);
+ }
})();
+
// force page reload when new update is available
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
@@ -29,6 +63,18 @@
window.location.reload();
}
}, false);
+
+
+ // GA asynchronous tracker
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', 'UA-8594346-3']);
+ _gaq.push(['_setDomainName', '.angularjs.org']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
</script>
</head>
@@ -45,11 +91,11 @@
<ul id="navbar">
<li><a href="http://angularjs.org/">AngularJS</a></li>
- <li><a href="#!/misc/started" ng:class="selectedSection('misc')">Getting Started</a></li>
- <li><a href="#!/tutorial" ng:class="selectedSection('tutorial')">Tutorial</a></li>
- <li><a href="#!/api" ng:class="selectedSection('api')">API Reference</a></li>
- <li><a href="#!/cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
- <li><a href="#!/guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
+ <li><a href="misc/started" ng:class="selectedSection('misc')">Getting Started</a></li>
+ <li><a href="tutorial" ng:class="selectedSection('tutorial')">Tutorial</a></li>
+ <li><a href="api" ng:class="selectedSection('api')">API Reference</a></li>
+ <li><a href="cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
+ <li><a href="guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
</ul>
<div id="sidebar">
@@ -100,11 +146,5 @@
<button id="cacheButton" ng:click="enableOffline()">Let me have them all!</button>
</div>
</div>
-
- <script src="syntaxhighlighter/syntaxhighlighter-combined.js"></script>
- <!-- jquery place holder -->
- <!-- angular script place holder -->
- <script src="docs-combined.js"></script>
- <script src="docs-keywords.js"></script>
</body>
</html>
diff --git a/lib/nodeserver/server.js b/lib/nodeserver/server.js
index 471bba94..54ae78fc 100644
--- a/lib/nodeserver/server.js
+++ b/lib/nodeserver/server.js
@@ -91,6 +91,18 @@ StaticServlet.prototype.handleRequest = function(req, res) {
var parts = path.split('/');
if (parts[parts.length-1].charAt(0) === '.')
return self.sendForbidden_(req, res, path);
+
+ // docs rewriting
+ var REWRITE = /\/(guide|api|cookbook|misc|tutorial)\/.*$/,
+ IGNORED = /(\.(css|js|png|jpg)$|partials\/.*\.html$)/,
+ match;
+
+ if (!IGNORED.test(path) && (match = path.match(REWRITE))) {
+ path = path.replace(match[0], '/index.html');
+ sys.puts('Rewrite to ' + path);
+ }
+ // end of docs rewriting
+
fs.stat(path, function(err, stat) {
if (err)
return self.sendMissing_(req, res, path);
diff --git a/src/markups.js b/src/markups.js
index 5c9c14b4..1adad3e0 100644
--- a/src/markups.js
+++ b/src/markups.js
@@ -166,10 +166,10 @@ angularTextMarkup('option', function(text, textNode, parentElement){
<input name="value" /><br />
<a id="link-1" href ng:click="value = 1">link 1</a> (link, don't reload)<br />
<a id="link-2" href="" ng:click="value = 2">link 2</a> (link, don't reload)<br />
- <a id="link-3" ng:href="#!/{{'123'}}" ng:click="value = 3">link 3</a> (link, reload!)<br />
+ <a id="link-3" ng:href="/{{'123'}}" ng:ext-link>link 3</a> (link, reload!)<br />
<a id="link-4" href="" name="xx" ng:click="value = 4">anchor</a> (link, don't reload)<br />
<a id="link-5" name="xxx" ng:click="value = 5">anchor</a> (no link)<br />
- <a id="link-6" ng:href="#!/{{value}}">link</a> (link, change hash)
+ <a id="link-6" ng:href="/{{value}}" ng:ext-link>link</a> (link, change hash)
</doc:source>
<doc:scenario>
it('should execute ng:click but not reload when href without value', function() {
@@ -185,10 +185,10 @@ angularTextMarkup('option', function(text, textNode, parentElement){
});
it('should execute ng:click and change url when ng:href specified', function() {
+ expect(element('#link-3').attr('href')).toBe("/123");
+
element('#link-3').click();
- expect(input('value').val()).toEqual('3');
- expect(element('#link-3').attr('href')).toBe("#!/123");
- expect(browser().location().hash()).toEqual('!/123');
+ expect(browser().location().path()).toEqual('/123');
});
it('should execute ng:click but not reload when href empty string and name specified', function() {
@@ -205,9 +205,10 @@ angularTextMarkup('option', function(text, textNode, parentElement){
it('should only change url when only ng:href', function() {
input('value').enter('6');
+ expect(element('#link-6').attr('href')).toBe("/6");
+
element('#link-6').click();
- expect(browser().location().hash()).toEqual('!/6');
- expect(element('#link-6').attr('href')).toBe("#!/6");
+ expect(browser().location().path()).toEqual('/6');
});
</doc:scenario>
</doc:example>
diff --git a/src/widgets.js b/src/widgets.js
index 1d0217b8..bd7c3d7f 100644
--- a/src/widgets.js
+++ b/src/widgets.js
@@ -1390,10 +1390,10 @@ angularWidget("@ng:non-bindable", noop);
function MyCtrl($route) {
$route.when('/overview',
{ controller: OverviewCtrl,
- template: 'guide/dev_guide.overview.html'});
+ template: 'partials/guide/dev_guide.overview.html'});
$route.when('/bootstrap',
{ controller: BootstrapCtrl,
- template: 'guide/dev_guide.bootstrap.auto_bootstrap.html'});
+ template: 'partials/guide/dev_guide.bootstrap.auto_bootstrap.html'});
};
MyCtrl.$inject = ['$route'];
@@ -1401,9 +1401,9 @@ angularWidget("@ng:non-bindable", noop);
function OverviewCtrl(){}
</script>
<div ng:controller="MyCtrl">
- <a href="#!/overview">overview</a> |
- <a href="#!/bootstrap">bootstrap</a> |
- <a href="#!/undefined">undefined</a>
+ <a href="overview">overview</a> |
+ <a href="bootstrap">bootstrap</a> |
+ <a href="undefined">undefined</a>
<br/>