diff --git a/package.json b/package.json index 30dc865..542b2eb 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "license": "MIT", "readmeFilename": "README.md", "dependencies": { - "showdown": "0.3.1" + "marked": "0.2.9" }, "peerDependencies": { "grunt": "0.4.x" diff --git a/src/dom.js b/src/dom.js index 1bb9578..e696faf 100644 --- a/src/dom.js +++ b/src/dom.js @@ -4,17 +4,49 @@ exports.DOM = DOM; exports.htmlEscape = htmlEscape; +exports.normalizeHeaderToId = normalizeHeaderToId; ////////////////////////////////////////////////////////// function htmlEscape(text){ - return text.replace(/&/g, '&').replace(//g, '>'); + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/\{\{/g, '{{') + .replace(/\}\}/g, '}}'); +} + +function nonEmpty(header) { + return !!header; +} + +function idFromCurrentHeaders(headers) { + if (headers.length === 1) return headers[0]; + // Do not include the first level title, as that's the title of the page. + return headers.slice(1).filter(nonEmpty).join('_'); +} + +function normalizeHeaderToId(header) { + if (typeof header !== 'string') { + return ''; + } + + return header.toLowerCase() + .replace(/<.*>/g, '') // html tags + .replace(/[\!\?\:\.\']/g, '') // special characters + .replace(/&#\d\d;/g, '') // html entities + .replace(/\(.*\)/mg, '') // stuff in parenthesis + .replace(/\s$/, '') // trailing spaces + .replace(/\s+/g, '-'); // replace whitespaces with dashes } function DOM() { this.out = []; this.headingDepth = 0; + this.currentHeaders = []; + this.anchors = []; } var INLINE_TAGS = { @@ -39,16 +71,28 @@ DOM.prototype = { }, html: function(html) { - if (html) { - var headingDepth = this.headingDepth; - for ( var i = 10; i > 0; --i) { - html = html - .replace(new RegExp('(<\/?h)' + i + '(>)', 'gm'), function(all, start, end){ - return start + (i + headingDepth) + end; - }); - } - this.out.push(html); - } + if (!html) return; + + var self = this; + // rewrite header levels, add ids and collect the ids + html = html.replace(/([\s\S]+?)<\/h\1>/gm, function(_, level, attrs, content) { + level = parseInt(level, 10) + self.headingDepth; // change header level based on the context + + self.currentHeaders[level - 1] = normalizeHeaderToId(content); + self.currentHeaders.length = level; + + var id = idFromCurrentHeaders(self.currentHeaders); + self.anchors.push(id); + return '' + content + ''; + }); + + // collect anchors + html = html.replace(//g, function(match, anchor) { + self.anchors.push(anchor); + return match; + }); + + this.out.push(html); }, tag: function(name, attr, text) { @@ -79,17 +123,18 @@ DOM.prototype = { h: function(heading, content, fn){ if (content==undefined || (content instanceof Array && content.length == 0)) return; + this.headingDepth++; + this.currentHeaders[this.headingDepth - 1] = normalizeHeaderToId(heading); + this.currentHeaders.length = this.headingDepth; + var className = null, anchor = null; if (typeof heading == 'string') { - var id = heading. - replace(/\(.*\)/mg, ''). - replace(/[^\d\w\$]/mg, '.'). - replace(/-+/gm, '-'). - replace(/-*$/gm, ''); + var id = idFromCurrentHeaders(this.currentHeaders); + this.anchors.push(id); anchor = {'id': id}; - var classNameValue = id.toLowerCase().replace(/[._]/mg, '-'); + var classNameValue = this.currentHeaders[this.headingDepth - 1] if(classNameValue == 'hide') classNameValue = ''; className = {'class': classNameValue}; } diff --git a/src/example.js b/src/example.js index ab42e29..9471b3f 100644 --- a/src/example.js +++ b/src/example.js @@ -20,6 +20,7 @@ exports.Example = function(scenarios) { this.html = []; this.css = []; this.js = []; + this.json = []; this.unit = []; this.scenario = []; this.scenarios = scenarios; @@ -88,6 +89,7 @@ exports.Example.prototype.toHtmlEdit = function() { out.push(' source-edit-html="' + ids(this.html) + '"'); out.push(' source-edit-css="' + ids(this.css) + '"'); out.push(' source-edit-js="' + ids(this.js) + '"'); + out.push(' source-edit-json="' + ids(this.json) + '"'); out.push(' source-edit-unit="' + ids(this.unit) + '"'); out.push(' source-edit-scenario="' + ids(this.scenario) + '"'); out.push('>\n'); @@ -102,6 +104,7 @@ exports.Example.prototype.toHtmlTabs = function() { htmlTabs(this.html); htmlTabs(this.css); htmlTabs(this.js); + htmlTabs(this.json); htmlTabs(this.unit); htmlTabs(this.scenario); out.push(''); @@ -114,7 +117,7 @@ exports.Example.prototype.toHtmlTabs = function() { name = source.name; if (name === 'index.html') { - wrap = ' ng-html-wrap-loaded="' + self.module + ' ' + self.deps.join(' ') + '"'; + wrap = ' ng-html-wrap="' + self.module + ' ' + self.deps.join(' ') + '"'; } if (name == 'scenario.js') name = 'End to end test'; @@ -131,7 +134,7 @@ exports.Example.prototype.toHtmlTabs = function() { exports.Example.prototype.toHtmlEmbed = function() { var out = []; - out.push('
/g, ''); + text = text.replace(/{/g,'{'); + text = text.replace(/}/g,'}'); + return text; + }, + + getMinerrNamespace: function () { + if (this.ngdoc !== 'error') { + throw new Error('Tried to get the minErr namespace, but @ngdoc ' + + this.ngdoc + ' was supplied. It should be @ngdoc error'); + } + return this.name.split(':')[0]; + }, + + getMinerrCode: function () { + if (this.ngdoc !== 'error') { + throw new Error('Tried to get the minErr error code, but @ngdoc ' + + this.ngdoc + ' was supplied. It should be @ngdoc error'); + } + return this.name.split(':')[1]; + }, + /** * Converts relative urls (without section) into absolute * Absolute url means url with section @@ -94,10 +143,17 @@ Doc.prototype = { * @returns {string} Absolute url */ convertUrlToAbsolute: function(url) { - var prefix = this.options.html5Mode ? '' : '#/'; + var hashIdx = url.indexOf('#'); + + // Lowercase hash parts of the links, + // so that we can keep correct API names even when the urls are lowercased. + if (hashIdx !== -1) { + url = url.substr(0, hashIdx) + url.substr(hashIdx).toLowerCase(); + } + if (url.substr(-1) == '/') return url + 'index'; - if (url.match(/\//)) return prefix + url; - return prefix + this.section + '/' + url; + if (url.match(/\//)) return url; + return this.section + '/' + url; }, markdown: function(text) { @@ -107,7 +163,7 @@ Doc.prototype = { IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/, IS_ANGULAR = /^(api\/)?(angular|ng|AUTO)\./, IS_HASH = /^#/, - parts = trim(text).split(/(
[\s\S]*?<\/pre>|[\s\S]*?<\/doc:example>|]*>[\s\S]*?<\/example>)/),
+      parts = trim(text).split(/([\s\S]*?<\/pre>|[\s\S]*?<\/doc:example>|]*>[\s\S]*?<\/example>)/),
       seq = 0,
       placeholderMap = {};
 
@@ -138,6 +194,7 @@ Doc.prototype = {
           var example = new Example(self.scenarios);
           if(animations) {
             example.enableAnimations();
+            example.addDeps('angular-animate.js');
           }
 
           example.setModule(module);
@@ -146,7 +203,7 @@ Doc.prototype = {
             example.addSource(name, content);
           });
           content.replace(//gmi, function(_, file, tag, name) {
-            if(fspath.existsSync(file)) {
+            if(fs.existsSync(file)) {
               var content = fs.readFileSync(file, 'utf8');
               if(content && content.length > 0) {
                 if(tag && tag.length > 0) {
@@ -161,7 +218,7 @@ Doc.prototype = {
           return placeholder(example.toHtml());
         }).
         replace(/(?:\*\s+)?/i, function(_, file, tag) {
-          if(fspath.existsSync(file)) {
+          if(fs.existsSync(file)) {
             var content = fs.readFileSync(file, 'utf8');
             if(tag && tag.length > 0) {
               content = extractInlineDocCode(content, tag);
@@ -193,9 +250,9 @@ Doc.prototype = {
 
           return placeholder(example.toHtml());
         }).
-        replace(/^
([\s\S]*?)<\/pre>/mi, function(_, content){
+        replace(/^([\s\S]*?)<\/pre>/mi, function(_, attrs, content){
           return placeholder(
-            '
' +
+            '' +
               content.replace(//g, '>') +
               '
'); }). @@ -215,13 +272,76 @@ Doc.prototype = { (title || url).replace(/^#/g, '').replace(/\n/g, ' ') + (isAngular ? '' : '') + '
'; + }). + replace(/{@type\s+(\S+)(?:\s+(\S+))?}/g, function(_, type, url) { + url = url || '#'; + return '' + type + ''; + }). + replace(/{@installModule\s+(\S+)?}/g, function(_, module) { + return explainModuleInstallation(module); }); }); text = parts.join(''); - text = new Showdown.converter({ extensions : ['table'] }).makeHtml(text); + + function prepareClassName(text) { + return text.toLowerCase().replace(/[_\W]+/g, '-'); + }; + + var pageClassName, suffix = '-page'; + if(this.name) { + var split = this.name.match(/^\s*(.+?)\s*:\s*(.+)/); + if(split && split.length > 1) { + var before = prepareClassName(split[1]); + var after = prepareClassName(split[2]); + pageClassName = before + suffix + ' ' + before + '-' + after + suffix; + } + } + pageClassName = pageClassName || prepareClassName(this.name || 'docs') + suffix; + + text = '
' + + marked(text) + + '
'; 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; }, @@ -247,22 +367,27 @@ Doc.prototype = { flush(); this.shortName = this.name.split(/[\.:#]/).pop().trim(); this.id = this.id || // if we have an id just use it + (this.ngdoc === 'error' ? this.name : '') || (((this.file||'').match(/.*(\/|\\)([^(\/|\\)]*)\.ngdoc/)||{})[2]) || // try to extract it from file name this.name; // default to name + this.moduleName = parseModuleName(this.id); this.description = this.markdown(this.description); this.example = this.markdown(this.example); this['this'] = this.markdown(this['this']); return this; + function parseModuleName(id) { + var module = id.split('.')[0]; + if(module == 'angular') { + module = 'ng'; + } + return module; + } + function flush() { if (atName) { var text = trim(atText.join('\n')), match; - if (atName == 'module') { - match = text.match(/^\s*(\S+)\s*$/); - if (match) { - self.moduleName = match[1]; - } - } else if (atName == 'param') { + if (atName == 'param') { match = text.match(/^\{([^}]+)\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/); // 1 1 23 3 4 4 5 5 2 6 6 if (!match) { @@ -275,15 +400,8 @@ Doc.prototype = { description:self.markdown(text.replace(match[0], match[6])), type: optional ? match[1].substring(0, match[1].length-1) : match[1], optional: optional, - 'default':match[5] + default: match[5] }; - - //if param name is a part of an object passed to a method - //mark it, so it's not included in the rendering later - if(param.name.indexOf(".") > 0){ - param.isProperty = true; - } - self.param.push(param); } else if (atName == 'returns' || atName == 'return') { match = text.match(/^\{([^}]+)\}\s+(.*)/); @@ -325,19 +443,53 @@ Doc.prototype = { html: function() { var dom = new DOM(), - self = this; + self = this, + minerrMsg; - dom.h(title(this.moduleName, this.name, this.ngdoc == 'overview'), function() { + var gitTagFromFullVersion = function(version) { + var match = version.match(/-(\w{7})/); + if (match) { + // git sha + return match[1]; + } + + // git tag + return 'v' + version; + }; + + /* + if (this.section === 'api') { + dom.tag('a', { + href: 'http://github.com/angular/angular.js/tree/' + + gitTagFromFullVersion(gruntUtil.getVersion().full) + '/' + self.file + '#L' + self.line, + class: 'view-source btn btn-action' }, function(dom) { + dom.tag('i', {class:'icon-zoom-in'}, ' '); + dom.text(' View source'); + }); + } + dom.tag('a', { + href: 'http://github.com/angular/angular.js/edit/master/' + self.file, + class: 'improve-docs btn btn-primary' }, function(dom) { + dom.tag('i', {class:'icon-edit'}, ' '); + dom.text(' Improve this doc'); + }); + */ + dom.h(title(this), function() { notice('deprecated', 'Deprecated API', self.deprecated); + if (self.ngdoc === 'error') { + minerrMsg = lookupMinerrMsg(self); + dom.tag('pre', { + class:'minerr-errmsg', + 'error-display': minerrMsg.replace(/"/g, '"') + }, minerrMsg); + } if (self.ngdoc != 'overview') { dom.h('Description', self.description, dom.html); } dom.h('Dependencies', self.requires, function(require){ dom.tag('code', function() { - var id = /[\.\/]/.test(require.name) ? require.name : 'ng.' + require.name, - name = require.name.split(/[\.:#\/]/).pop(); - dom.tag('a', {href: self.convertUrlToAbsolute(id)}, name); + dom.tag('a', {href: 'api/ng.' + require.name}, require.name); }); dom.html(require.text); }); @@ -349,12 +501,14 @@ Doc.prototype = { dom.h('Example', self.example, dom.html); }); + self.anchors = dom.anchors; + return dom.toString(); ////////////////////////// function notice(name, legend, msg){ - if (self[name] == undefined) return; + if (self[name] === undefined) return; dom.tag('fieldset', {'class':name}, function(dom){ dom.tag('legend', legend); dom.text(msg); @@ -363,28 +517,15 @@ Doc.prototype = { }, + prepare_type_hint_class_name : function(type) { + var typeClass = type.toLowerCase().match(/^[-\w]+/) || []; + typeClass = typeClass[0] ? typeClass[0] : 'object'; + return 'label type-hint type-hint-' + typeClass; + }, + html_usage_parameters: function(dom) { - dom.h('Parameters', this.param, function(param){ - dom.tag('code', function() { - dom.text(param.name); - if (param.optional) { - dom.tag('i', function() { - dom.text('(optional'); - if(param['default']) { - dom.text('=' + param['default']); - } - dom.text(')'); - }); - } - dom.text(' – {'); - dom.text(param.type); - if (param.optional) { - dom.text('='); - } - dom.text('} – '); - }); - dom.html(param.description); - }); + var self = this; + var params = this.param ? this.param : []; if(this.animations) { dom.h('Animations', this.animations, function(animations){ dom.html('
    '); @@ -396,17 +537,76 @@ Doc.prototype = { }); dom.html('
'); }); + dom.html('Click here to learn more about the steps involved in the animation.'); + } + if(params.length > 0) { + dom.html('

Parameters

'); + dom.html(''); + dom.html(''); + dom.html(''); + dom.html(''); + dom.html(''); + dom.html(''); + dom.html(''); + dom.html(''); + dom.html(''); + for(var i=0;i)/); + if (param.optional) { + name += '
(optional)
'; + } + dom.html('
'); + dom.html(''); + dom.html(''); + var description = ''; + dom.html(description); + dom.html(''); + }; + dom.html(''); + dom.html('
ParamTypeDetails
' + name + ''); + for(var j=0;j'); + dom.text(type); + dom.html(''); + } + + dom.html(''; + description += param.description; + if (param.default) { + description += '

(default: ' + param.default + ')

'; + } + description += '
'); } }, html_usage_returns: function(dom) { var self = this; if (self.returns) { - dom.h('Returns', function() { - dom.tag('code', '{' + self.returns.type + '}'); - dom.text('– '); - dom.html(self.returns.description); - }); + dom.html('

Returns

'); + dom.html(''); + dom.html(''); + dom.html(''); + dom.html(''); + dom.html(''); + dom.html('
'); + dom.html(''); + dom.text(self.returns.type); + dom.html(''); + dom.html(''); + dom.html(self.returns.description); + dom.html('
'); } }, @@ -423,11 +623,11 @@ Doc.prototype = { html_usage_function: function(dom){ var self = this; - var name = self.name.match(/^angular(\.mock)?\.(\w+)$/) ? self.name : self.name.split(/\./).pop(); + var name = self.name.match(/^angular(\.mock)?\.(\w+)$/) ? self.name : self.name.split(/\./).pop() dom.h('Usage', function() { dom.code(function() { - dom.text(name.split(':').pop()); + dom.text(name); dom.text('('); self.parameters(dom, ', '); dom.text(');'); @@ -454,15 +654,15 @@ Doc.prototype = { html_usage_directive: function(dom){ var self = this; dom.h('Usage', function() { - var restrict = self.restrict || 'AC'; + var restrict = self.restrict || 'A'; - /*if (restrict.match(/E/)) { + if (restrict.match(/E/)) { dom.html('

'); dom.text('This directive can be used as custom element, but be aware of '); - dom.tag('a', {href:'http://docs.angularjs.org/guide/ie'}, 'IE restrictions'); + dom.tag('a', {href:'guide/ie'}, 'IE restrictions'); dom.text('.'); dom.html('

'); - }*/ + } if (self.usage) { dom.tag('pre', function() { @@ -504,48 +704,6 @@ Doc.prototype = { dom.text(''); }); } - if(self.animations) { - var animations = [], matches = self.animations.split("\n"); - matches.forEach(function(ani) { - var name = ani.match(/^\s*(.+?)\s*-/)[1]; - animations.push(name); - }); - - dom.html('with animations'); - var comment; - if(animations.length == 1) { - comment = 'The ' + animations[0] + ' animation is supported'; - } - else { - var rhs = animations[animations.length-1]; - var lhs = ''; - for(var i=0;i0) { - lhs += ', '; - } - lhs += animations[i]; - } - comment = 'The ' + lhs + ' and ' + rhs + ' animations are supported'; - } - var element = self.element || 'ANY'; - dom.code(function() { - dom.text('//' + comment + "\n"); - dom.text('<' + element + ' '); - dom.text(dashCase(self.shortName)); - renderParams('\n ', '="', '"', true); - dom.text(' ng-animate="{'); - animations.forEach(function(ani, index) { - if (index) { - dom.text(', '); - } - dom.text(ani + ': \'' + ani + '-animation\''); - }); - dom.text('}">\n ...\n'); - dom.text(''); - }); - - dom.html('Click here to learn more about the steps involved in the animation.'); - } } self.html_usage_directiveInfo(dom); self.html_usage_parameters(dom); @@ -560,7 +718,7 @@ Doc.prototype = { dom.text(prefix); dom.text(param.optional ? '[' : ''); var parts = param.name.split('|'); - dom.text(parts[skipSelf ? 0 : 1] || parts[0]); + dom.text(dashCase(parts[skipSelf ? 0 : 1] || parts[0])); } if (BOOLEAN_ATTR[param.name]) { dom.text(param.optional ? ']' : ''); @@ -650,6 +808,10 @@ Doc.prototype = { dom.html(this.description); }, + html_usage_error: function (dom) { + dom.html(); + }, + html_usage_interface: function(dom){ var self = this; @@ -683,9 +845,7 @@ Doc.prototype = { if (self.methods.length) { dom.div({class:'member method'}, function(){ dom.h('Methods', self.methods, function(method){ - //filters out .IsProperty parameters from the method signature - var signature = (method.param || []).filter(function(e) { return e.isProperty !== true}).map(property('name')); - + var signature = (method.param || []).map(property('name')); dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function() { dom.html(method.description); method.html_usage_parameters(dom); @@ -742,7 +902,6 @@ Doc.prototype = { var sep = prefix ? separator : ''; (this.param||[]).forEach(function(param, i){ if (!(skipFirst && i==0)) { - if (param.isProperty) { return; } if (param.optional) { dom.text('[' + sep + param.name + ']'); } else { @@ -759,77 +918,59 @@ Doc.prototype = { ////////////////////////////////////////////////////////// var GLOBALS = /^angular\.([^\.]+)$/, - MODULE = /^([^\.]+)$/, + MODULE = /^((?:(?!^angular\.)[^\.])+)$/, MODULE_MOCK = /^angular\.mock\.([^\.]+)$/, - MODULE_DIRECTIVE = /^(.+)\.directive:([^\.]+)$/, - MODULE_DIRECTIVE_INPUT = /^(.+)\.directive:input\.([^\.]+)$/, - MODULE_CUSTOM = /^(.+)\.([^\.]+):([^\.]+)$/, - MODULE_SERVICE = /^(.+)\.([^\.]+?)(Provider)?$/, - MODULE_TYPE = /^([^\.]+)\..+\.([A-Z][^\.]+)$/; + MODULE_DIRECTIVE = /^((?:(?!^angular\.)[^\.])+)\.directive:([^\.]+)$/, + MODULE_DIRECTIVE_INPUT = /^((?:(?!^angular\.)[^\.])+)\.directive:input\.([^\.]+)$/, + MODULE_FILTER = /^((?:(?!^angular\.)[^\.])+)\.filter:([^\.]+)$/, + MODULE_SERVICE = /^((?:(?!^angular\.)[^\.])+)\.([^\.]+?)(Provider)?$/, + MODULE_TYPE = /^((?:(?!^angular\.)[^\.])+)\..+\.([A-Z][^\.]+)$/; -function title(module, text, overview) { - if (!text) return text; +function title(doc) { + if (!doc.name) return doc.name; var match, - module, - type, - name; - - if (text == 'angular.Module') { - module = 'ng'; - name = 'Module'; - type = 'Type'; + text = doc.name; + + var makeTitle = function (name, type, componentType, component) { + // Makes title markup. + // makeTitle('Foo', 'directive', 'module', 'ng') -> + // Foo is a directive in module ng + return function () { + this.tag('code', name); + this.tag('div', function () { + this.tag('span', {class: 'hint'}, function () { + if (type && component) { + this.text(type + ' in ' + componentType + ' '); + this.tag('code', component); + } + }); + }); + }; + }; + + if (doc.ngdoc === 'error') { + return makeTitle(doc.fullName, 'error', 'component', doc.getMinerrNamespace()); + } else if (text == 'angular.Module') { + return makeTitle('Module', 'Type', 'module', 'ng'); } else if (match = text.match(GLOBALS)) { - module = 'ng'; - name = 'angular.' + match[1]; - type = 'API'; + return makeTitle('angular.' + match[1], 'API', 'module', 'ng'); } else if (match = text.match(MODULE)) { - module = match[1]; - if (!overview) { name = match[1]; } + return makeTitle('', '', 'module', match[1]); } else if (match = text.match(MODULE_MOCK)) { - module = 'ng'; - name = 'angular.mock.' + match[1]; - type = 'API'; + return makeTitle('angular.mock.' + match[1], 'API', 'module', 'ng'); } else if (match = text.match(MODULE_DIRECTIVE)) { - module = match[1]; - name = match[2]; - type = 'directive'; + return makeTitle(match[2], 'directive', 'module', match[1]); } else if (match = text.match(MODULE_DIRECTIVE_INPUT)) { - module = match[1]; - name = 'input [' + match[2] + ']'; - type = 'directive'; - } else if (match = text.match(MODULE_CUSTOM)) { - module = match[1]; - name = match[3]; - type = match[2]; - } else if (match = text.match(MODULE_TYPE)) { - module = module || match[1]; - name = match[2]; - type = 'type'; + return makeTitle('input [' + match[2] + ']', 'directive', 'module', match[1]); + } else if (match = text.match(MODULE_FILTER)) { + return makeTitle(match[2], 'filter', 'module', match[1]); } else if (match = text.match(MODULE_SERVICE)) { - if (overview) { - // module name with dots looks like a service - module = text; - } else { - module = module || match[1]; - name = match[2] + (match[3] || ''); - type = 'service'; - } - } else { - return text; + return makeTitle(match[2] + (match[3] || ''), 'service', 'module', match[1]); + } else if (match = text.match(MODULE_TYPE)) { + return makeTitle(match[2], 'type', 'module', match[1]); } - return function() { - this.tag('code', name); - this.tag('span', { class: 'hint'}, function() { - if (type) { - this.text('('); - this.text(type); - this.text(' in module '); - this.tag('code', module); - this.text(')'); - } - }); - }; + return text; } @@ -871,7 +1012,7 @@ function scenarios(docs){ function metadata(docs){ var pages = []; docs.forEach(function(doc){ - var path = (doc.name || '').split(/(\.|\:\s*)/); + var path = (doc.name || '').split(/(\:\s*)/); for ( var i = 1; i < path.length; i++) { path.splice(i, 1); } @@ -884,14 +1025,15 @@ function metadata(docs){ pages.push({ section: doc.section, id: doc.id, - name: title(doc.moduleName, doc.name), - moduleName: doc.moduleName, + name: title(doc), shortName: shortName, type: doc.ngdoc, - keywords:doc.keywords() + moduleName: doc.moduleName, + shortDescription: doc.shortDescription(), + keywords: doc.keywords() }); }); - pages.sort(keywordSort); + pages.sort(sidebarSort); return pages; } @@ -924,7 +1066,60 @@ var KEYWORD_PRIORITY = { '.dev_guide.di': 8, '.dev_guide.unit-testing': 9 }; -function keywordSort(a, b){ + +var GUIDE_PRIORITY = [ + 'introduction', + 'overview', + 'concepts', + 'dev_guide.mvc', + + 'dev_guide.mvc.understanding_controller', + 'dev_guide.mvc.understanding_model', + 'dev_guide.mvc.understanding_view', + + 'dev_guide.services.understanding_services', + 'dev_guide.services.managing_dependencies', + 'dev_guide.services.creating_services', + 'dev_guide.services.injecting_controllers', + 'dev_guide.services.testing_services', + 'dev_guide.services.$location', + 'dev_guide.services', + + 'databinding', + 'dev_guide.templates.css-styling', + 'dev_guide.templates.filters.creating_filters', + 'dev_guide.templates.filters', + 'dev_guide.templates.filters.using_filters', + 'dev_guide.templates', + + 'di', + 'providers', + 'module', + 'scope', + 'expression', + 'bootstrap', + 'directive', + 'compiler', + + 'forms', + 'animations', + + 'dev_guide.e2e-testing', + 'dev_guide.unit-testing', + + 'i18n', + 'ie', + 'migration', +]; + +function sidebarSort(a, b){ + priorityA = GUIDE_PRIORITY.indexOf(a.id); + priorityB = GUIDE_PRIORITY.indexOf(b.id); + + if (priorityA > -1 || priorityB > -1) { + return priorityA < priorityB ? -1 : (priorityA > priorityB ? 1 : 0); + } + function mangleName(doc) { var path = doc.id.split(/\./); var mangled = []; @@ -1005,22 +1200,7 @@ function merge(docs){ }); for(var i = 0; i < docs.length;) { - var doc = docs[i]; - - // check links - do they exist ? - doc.links.forEach(function(link) { - // convert #id to path#id - if (link[0] == '#') { - link = doc.section + '/' + doc.id.split('#').shift() + link; - } - link = link.split('#').shift(); - if (!byFullId[link]) { - console.log('WARNING: In ' + doc.section + '/' + doc.id + ', non existing link: "' + link + '"'); - } - }); - - // merge into parents - if (findParent(doc, 'method') || findParent(doc, 'property') || findParent(doc, 'event')) { + if (findParent(docs[i], 'method') || findParent(docs[i], 'property') || findParent(docs[i], 'event')) { docs.splice(i, 1); } else { i++; @@ -1031,7 +1211,7 @@ function merge(docs){ var parentName = doc[name + 'Of']; if (!parentName) return false; - var parent = byFullId[doc.section + '/' + parentName]; + var parent = byFullId['api/' + parentName]; if (!parent) throw new Error("No parent named '" + parentName + "' for '" + doc.name + "' in @" + name + "Of."); @@ -1049,6 +1229,39 @@ function merge(docs){ } ////////////////////////////////////////////////////////// + +function checkBrokenLinks(docs) { + var byFullId = Object.create(null); + + docs.forEach(function(doc) { + byFullId[doc.section + '/' + doc.id] = doc; + if (doc.section === 'api') { + doc.anchors.push('directive', 'service', 'filter', 'function'); + } + }); + + docs.forEach(function(doc) { + doc.links.forEach(function(link) { + // convert #id to path#id + if (link[0] == '#') { + link = doc.section + '/' + doc.id.split('#').shift() + link; + } + + var parts = link.split('#'); + var pageLink = parts[0]; + var anchorLink = parts[1]; + var linkedPage = byFullId[pageLink]; + + if (!linkedPage) { + console.log('WARNING: ' + doc.section + '/' + doc.id + ' (defined in ' + doc.file + ') points to a non existing page "' + link + '"!'); + } else if (anchorLink && linkedPage.anchors.indexOf(anchorLink) === -1) { + console.log('WARNING: ' + doc.section + '/' + doc.id + ' (defined in ' + doc.file + ') points to a non existing anchor "' + link + '"!'); + } + }); + }); +} + + function property(name) { return function(value){ return value[name]; @@ -1062,3 +1275,34 @@ function dashCase(name){ return (pos ? '-' : '') + letter.toLowerCase(); }); } +////////////////////////////////////////////////////////// + +function explainModuleInstallation(moduleName){ + var ngMod = ngModule(moduleName), + modulePackage = 'angular-' + moduleName, + modulePackageFile = modulePackage + '.js'; + + return '

Installation

' + + '

First include ' + modulePackageFile +' in your HTML:

' +
+    '    <script src="angular.js">\n' +
+    '    <script src="' + modulePackageFile + '">
' + + + '

You can download this file from the following places:

' + + '
    ' + + '
  • [Google CDN](https://developers.google.com/speed/libraries/devguide#angularjs)
    ' + + 'e.g. "//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/' + modulePackageFile + '"
  • ' + + '
  • [Bower](http://bower.io)
    ' + + 'e.g. bower install ' + modulePackage + '@X.Y.Z
  • ' + + '
  • code.angularjs.org
    ' + + 'e.g. "//code.angularjs.org/X.Y.Z/' + modulePackageFile + '"
  • ' + + '
' + + '

where X.Y.Z is the AngularJS version you are running.

' + + '

Then load the module in your application by adding it as a dependent module:

' +
+    '    angular.module(\'app\', [\'' + ngMod + '\']);
' + + + '

With that you\'re ready to get started!

'; +} + +function ngModule(moduleName) { + return 'ng' + moduleName[0].toUpperCase() + moduleName.substr(1); +}