build(aio): map H3 headings into H4 headings for certain templates (#24000)

The sections such as methods and decorator options are already headed
by a H3 heading so we need to map the H3 headings in the API doc source
down to H4 headings.

This commit includes general heading mapping functionality accessible via
the `marked` Nunjucks filter.

PR Close #24000
This commit is contained in:
Pete Bacon Darwin 2018-05-18 15:07:11 +01:00 committed by Miško Hevery
parent e371b226fa
commit 77309e2ea4
6 changed files with 107 additions and 16 deletions

View File

@ -5,5 +5,5 @@ var Package = require('dgeni').Package;
* @description Overrides the renderMarkdown service with an implementation based on remark * @description Overrides the renderMarkdown service with an implementation based on remark
*/ */
module.exports = new Package('remark', ['nunjucks']) module.exports = new Package('remark', ['nunjucks'])
.factory(require('./services/markedNunjucksFilter'))
.factory(require('./services/renderMarkdown')); .factory(require('./services/renderMarkdown'));

View File

@ -0,0 +1,12 @@
/**
* Convert the value, as markdown, into HTML
* @param headingMappings A map of headings to convert (e.g. from h3 to h4).
*/
module.exports = function markedNunjucksFilter(renderMarkdown) {
return {
name: 'marked',
process: function(str, headingMappings) {
return str && renderMarkdown(str, headingMappings);
}
};
};

View File

@ -0,0 +1,31 @@
const visit = require('unist-util-visit');
function headingToLevel(heading) {
const match = /^h(\d+)/.exec(heading);
return match ? match[1] : '0';
}
function parseMappings(mappings) {
const mapping = {};
Object.keys(mappings).forEach(key => mapping[headingToLevel(key)] = headingToLevel(mappings[key]));
return mapping;
}
module.exports = function mapHeadings(mappings) {
const headings = parseMappings(mappings || {});
return () => ast => {
const nodesToFix = [];
Object.keys(headings).forEach(heading => {
visit(ast, 'heading', node => {
if (node.depth === Number(heading)) {
nodesToFix.push(node);
}
});
});
// Update the depth of the matched nodes
nodesToFix.forEach(node => node.depth = headings[node.depth]);
return ast;
};
};

View File

@ -1,25 +1,29 @@
const remark = require('remark'); const remark = require('remark');
const html = require('remark-html'); const html = require('remark-html');
const code = require('./handlers/code'); const code = require('./handlers/code');
const mapHeadings = require('./plugins/mapHeadings');
/** /**
* @dgService renderMarkdown * @dgService renderMarkdown
* @description * @description
* Render the markdown in the given string as HTML. * Render the markdown in the given string as HTML.
* @param headingMap A map of headings to convert.
* E.g. `{h3: 'h4'} will map heading 3 level into heading 4.
*/ */
module.exports = function renderMarkdown() { module.exports = function renderMarkdown() {
const handlers = { code }; return function renderMarkdownImpl(content, headingMap) {
const renderer = remark()
.use(inlineTagDefs) const renderer = remark()
.use(noIndentedCodeBlocks) .use(inlineTagDefs)
.use(plainHTMLBlocks) .use(noIndentedCodeBlocks)
// USEFUL DEBUGGING CODE .use(plainHTMLBlocks)
// .use(() => tree => { // USEFUL DEBUGGING CODE
// console.log(require('util').inspect(tree, { colors: true, depth: 4 })); // .use(() => tree => {
// }) // console.log(require('util').inspect(tree, { colors: true, depth: 4 }));
.use(html, { handlers }); // })
.use(mapHeadings(headingMap))
.use(html, { handlers: { code } });
return function renderMarkdownImpl(content) {
return renderer.processSync(content).toString(); return renderer.processSync(content).toString();
}; };

View File

@ -95,4 +95,36 @@ describe('remark: renderMarkdown service', () => {
'</code-example>\n' '</code-example>\n'
); );
}); });
it('should map heading levels as specified', () => {
const content =
'# heading 1\n' +
'\n' +
'some paragraph\n' +
'\n' +
'## heading 2a\n' +
'\n' +
'some paragraph\n' +
'\n' +
'### heading 3\n' +
'\n' +
'some paragraph\n' +
'\n' +
'## heading 2b\n' +
'\n' +
'some paragraph\n' +
'\n';
const headingMappings = { h2: 'h3', h3: 'h5' };
const output = renderMarkdown(content, headingMappings);
expect(output).toEqual(
'<h1>heading 1</h1>\n' +
'<p>some paragraph</p>\n' +
'<h3>heading 2a</h3>\n' +
'<p>some paragraph</p>\n' +
'<h5>heading 3</h5>\n' +
'<p>some paragraph</p>\n' +
'<h3>heading 2b</h3>\n' +
'<p>some paragraph</p>\n'
);
});
}); });

View File

@ -113,12 +113,24 @@
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if method.description %}<tr>
{% if method.description -%}
<tr>
<td class="description"> <td class="description">
{$ method.description | marked $} {$ method.description | marked({ h3: 'h4' }) $}
</td> </td>
</tr>{% endif %} </tr>
</tbody> {%- endif %}
{% if method.usageNotes -%}
<tr>
<td class="usage-notes">
<h3>Usage Notes</h3>
{$ method.usageNotes | marked({ h3: 'h4' }) $}
</td>
</tr>
{%- endif %}
</tbody>
</table> </table>
{% endmacro -%} {% endmacro -%}