diff options
| author | Vojta Jina | 2013-10-17 14:16:32 -0700 | 
|---|---|---|
| committer | Igor Minar | 2013-10-18 15:35:41 -0700 | 
| commit | e8cc85f733a49ca53e8cda5a96bbaacc9a20ac7e (patch) | |
| tree | f145db33b29ee9cce531492a8332adc537033b70 /docs/src | |
| parent | c22adbf160f32c1839fbb35382b7a8c6bcec2927 (diff) | |
| download | angular.js-e8cc85f733a49ca53e8cda5a96bbaacc9a20ac7e.tar.bz2 | |
chore(docs): generate header ids for better linking
- generate ids for all headers
- collect defined anchors
- check broken links (even if the page exists, but the anchor/id does not)
Diffstat (limited to 'docs/src')
| -rw-r--r-- | docs/src/dom.js | 73 | ||||
| -rwxr-xr-x | docs/src/gen-docs.js | 2 | ||||
| -rw-r--r-- | docs/src/ngdoc.js | 63 | 
3 files changed, 103 insertions, 35 deletions
| diff --git a/docs/src/dom.js b/docs/src/dom.js index 897a1831..e696faf4 100644 --- a/docs/src/dom.js +++ b/docs/src/dom.js @@ -4,6 +4,7 @@  exports.DOM = DOM;  exports.htmlEscape = htmlEscape; +exports.normalizeHeaderToId = normalizeHeaderToId;  ////////////////////////////////////////////////////////// @@ -16,10 +17,36 @@ function htmlEscape(text){            .replace(/\}\}/g, '<span>}}</span>');  } +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 = { @@ -44,17 +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 + '(.*?)>([\\s\\S]+)<\/h' + i +'>', 'gm'), function(_, attrs, content){ -            var tag = 'h' + (i + headingDepth); -            return '<' + tag + attrs + '>' + content + '</' + tag + '>'; -          }); -      } -      this.out.push(html); -    } +    if (!html) return; + +    var self = this; +    // rewrite header levels, add ids and collect the ids +    html = html.replace(/<h(\d)(.*?)>([\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 '<h' + level + attrs + ' id="' + id + '">' + content + '</h' + level + '>'; +    }); + +    // collect anchors +    html = html.replace(/<a name="(\w*)">/g, function(match, anchor) { +      self.anchors.push(anchor); +      return match; +    }); + +    this.out.push(html);    },    tag: function(name, attr, text) { @@ -85,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/docs/src/gen-docs.js b/docs/src/gen-docs.js index 61fd3b3a..ffbf0068 100755 --- a/docs/src/gen-docs.js +++ b/docs/src/gen-docs.js @@ -55,6 +55,8 @@ writer.makeDir('build/docs/', true).then(function() {      fileFutures.push(writer.output('partials/' + doc.section + '/' + id + '.html', doc.html()));    }); +  ngdoc.checkBrokenLinks(docs); +    writeTheRest(fileFutures);    return Q.deep(fileFutures); diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js index 0f94f6eb..24d1aa26 100644 --- a/docs/src/ngdoc.js +++ b/docs/src/ngdoc.js @@ -36,6 +36,7 @@ exports.trim = trim;  exports.metadata = metadata;  exports.scenarios = scenarios;  exports.merge = merge; +exports.checkBrokenLinks = checkBrokenLinks;  exports.Doc = Doc;  exports.ngVersions = function() { @@ -169,6 +170,7 @@ function Doc(text, file, line) {    this.methods = this.methods || [];    this.events = this.events || [];    this.links = this.links || []; +  this.anchors = this.anchors || [];  }  Doc.METADATA_IGNORE = (function() {    var words = fs.readFileSync(__dirname + '/ignore.words', 'utf8'); @@ -242,6 +244,14 @@ Doc.prototype = {     * @returns {string} Absolute url     */    convertUrlToAbsolute: function(url) { +    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 url;      return this.section + '/' + url; @@ -569,6 +579,8 @@ Doc.prototype = {        dom.h('Example', self.example, dom.html);      }); +    self.anchors = dom.anchors; +      return dom.toString();      ////////////////////////// @@ -606,7 +618,7 @@ Doc.prototype = {        dom.html('<a href="api/ngAnimate.$animate">Click here</a> to learn more about the steps involved in the animation.');      }      if(params.length > 0) { -      dom.html('<h2 id="parameters">Parameters</h2>'); +      dom.html('<h2>Parameters</h2>');        dom.html('<table class="variables-matrix table table-bordered table-striped">');        dom.html('<thead>');        dom.html('<tr>'); @@ -660,7 +672,7 @@ Doc.prototype = {    html_usage_returns: function(dom) {      var self = this;      if (self.returns) { -      dom.html('<h2 id="returns">Returns</h2>'); +      dom.html('<h2>Returns</h2>');        dom.html('<table class="variables-matrix">');        dom.html('<tr>');        dom.html('<td>'); @@ -1211,22 +1223,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++; @@ -1255,6 +1252,36 @@ function merge(docs){  }  ////////////////////////////////////////////////////////// + +function checkBrokenLinks(docs) { +  var byFullId = Object.create(null); + +  docs.forEach(function(doc) { +    byFullId[doc.section + '/' + doc.id] = doc; +  }); + +  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]; | 
