diff --git a/docs/dockhand.js b/docs/dockhand.js index 5594ece73e24f..41d01b1cff685 100644 --- a/docs/dockhand.js +++ b/docs/dockhand.js @@ -75,12 +75,15 @@ function translate(childPath) { const html = template.replace(/\{\{\s*([\w\.]+)\s*\}\}/g, (token, key) => { switch (key) { case 'content': - return content; + return `
${content}
`; case 'path': return childPath; case 'url_path': return encodeURI(childPath); + case 'toc': + return '
'; + case 'title': case 'section': case 'description': @@ -141,7 +144,7 @@ function translate(childPath) { let headerId = headerText; let headerIncrement = 1; - while (headerIds.includes(headerId)) { + while (document.getElementById(headerId) !== null) { headerId = headerText + (++headerIncrement); } @@ -149,12 +152,76 @@ function translate(childPath) { header.setAttribute('id', headerId); } + // Walk the dom and build a table of contents + const toc = document.getElementById('_table_of_contents'); + + if (toc) { + toc.appendChild(generateTableOfContents(document)); + } + + // Write the final output const output = dom.serialize(); mkdirp.sync(path.dirname(outputPath)); fs.writeFileSync(outputPath, output); } +function generateTableOfContents(document) { + const headers = [ ]; + walkHeaders(document.getElementById('_content'), headers); + + let parent = null; + + // The nesting depth of headers are not necessarily the header level. + // (eg, h1 > h3 > h5 is a depth of three even though there's an h5.) + const hierarchy = [ ]; + for (let header of headers) { + const level = headerLevel(header); + + while (hierarchy.length && hierarchy[hierarchy.length - 1].headerLevel > level) { + hierarchy.pop(); + } + + if (!hierarchy.length || hierarchy[hierarchy.length - 1].headerLevel < level) { + const newList = document.createElement('ul'); + newList.headerLevel = level; + + if (hierarchy.length) { + hierarchy[hierarchy.length - 1].appendChild(newList); + } + + hierarchy.push(newList); + } + + const element = document.createElement('li'); + + const link = document.createElement('a'); + link.setAttribute('href', `#${header.getAttribute('id')}`); + link.innerHTML = header.innerHTML; + element.appendChild(link); + + const list = hierarchy[hierarchy.length - 1]; + list.appendChild(element); + } + + return hierarchy[0]; +} + +function walkHeaders(element, headers) { + for (let child of element.childNodes) { + if (headerLevel(child)) { + headers.push(child); + } + + walkHeaders(child, headers); + } +} + +function headerLevel(node) { + const level = node.tagName ? node.tagName.match(/^[Hh]([123456])$/) : null; + return level ? level[1] : 0; +} + function debug(str) { console.log(str); } diff --git a/docs/template.html b/docs/template.html index 1a44953acb739..fea1eb949517c 100644 --- a/docs/template.html +++ b/docs/template.html @@ -80,6 +80,27 @@ padding: 0 4em; } +#table_of_contents > h2 { + font-size: 1.17em; +} +#table_of_contents ul:first-child { + border: solid 1px #e1e4e8; + border-radius: 6px; + padding: 1em; + background-color: #f6f8fa; + color: #393a34; +} +#table_of_contents ul { + list-style-type: none; + padding-left: 1.5em; +} +#table_of_contents li { + font-size: 0.9em; +} +#table_of_contents li a { + color: #000000; +} + header.title { border-bottom: solid 1px #e1e4e8; } @@ -119,6 +140,11 @@

{{ title }}

{{ description }} +
+

Table of contents

+{{ toc }} +
+ {{ content }}