build: markdown-to-html rule should apply docs-specific transforms (#14315)

Currently when building the overview and guide files with Bazel, the following things are missing:

* Replacing `<!-- example` comments with the corresponding HTML elements
* Wrapping the HTML output with a `<div class="docs-markdown">` element
* Rewriting relative links to the proper public guide URL.
* Convert headings to "anchored" headings that can be referenced through URL fragments.

All of these points are handled with this commit.
This commit is contained in:
Paul Gschwendtner
2018-11-29 01:16:03 +01:00
committed by Jeremy Elbourn
parent 47e7aac6da
commit bd9645edae
6 changed files with 110 additions and 19 deletions
+1
View File
@@ -69,6 +69,7 @@
"@types/hammerjs": "^2.0.35",
"@types/inquirer": "^0.0.43",
"@types/jasmine": "^3.0.0",
"@types/marked": "^0.4.2",
"@types/merge2": "^0.3.30",
"@types/minimist": "^1.2.0",
"@types/mock-fs": "^3.6.30",
+5 -2
View File
@@ -5,8 +5,11 @@ load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "transform-markdown",
srcs = [":transform-markdown.ts"],
deps = ["@matdeps//@types/node"],
srcs = glob(["**/*.ts"]),
deps = [
"@matdeps//@types/node",
"@matdeps//@types/marked"
],
tsconfig = ":tsconfig.json",
)
@@ -0,0 +1,74 @@
import {MarkedOptions, Renderer} from 'marked';
import {basename, extname} from 'path';
import {highlightCodeBlock} from './highlight-code-block';
/** Regular expression that matches whitespace. */
const whitespaceRegex = /\W+/g;
/** Regular expression that matches example comments. */
const exampleCommentRegex = /<!--\W*example\(([^)]+)\)\W*-->/g;
/**
* Custom renderer for marked that will be used to transform markdown files to HTML
* files that can be used in the Angular Material docs.
*/
export class DocsMarkdownRenderer extends Renderer {
constructor(options?: MarkedOptions) {
super({highlight: highlightCodeBlock, baseUrl: 'material.angular.io/', ...options});
}
/**
* Transforms a markdown heading into the corresponding HTML output. In our case, we
* want to create a header-link for each H3 and H4 heading. This allows users to jump to
* specific parts of the docs.
*/
heading(label: string, level: number, _raw: string) {
if (level === 3 || level === 4) {
const headingId = label.toLowerCase().replace(whitespaceRegex, '-');
return `
<h${level} id="${headingId}" class="docs-header-link">
<span header-link="${headingId}"></span>
${label}
</h${level}>
`;
}
return `<h${level}>${label}</h${level}>`;
}
/** Transforms markdown links into the corresponding HTML output. */
link(href: string, title: string, text: string) {
// We only want to fix up markdown links that are relative and do not refer to guides already.
// Otherwise we always map the link to the "guide/" path.
// TODO(devversion): remove this logic and just disallow relative paths.
if (!href.startsWith('http') && !href.includes('guide/')) {
return super.link(`guide/${basename(href, extname(href))}`, title, text);
}
return super.link(href, title, text);
}
/**
* Method that will be called whenever inline HTML is processed by marked. In that case,
* we can easily transform the example comments into real HTML elements. For example:
*
* `<!-- example(name) -->` turns into `<div material-docs-example="name"></div>`
*/
html(html: string) {
html = html.replace(exampleCommentRegex, (_match: string, name: string) =>
`<div material-docs-example="${name}"></div>`
);
return super.html(html);
}
/**
* Method that will be called after a markdown file has been transformed to HTML. This method
* can be used to finalize the content (e.g. by adding an additional wrapper HTML element)
*/
finalizeOutput(output: string): string {
return `<div class="docs-markdown">${output}</div>`;
}
}
@@ -0,0 +1,15 @@
// These type lacks type definitions.
const highlightJs = require('highlight.js');
/**
* Transforms a given code block into its corresponding HTML output. We do this using
* highlight.js because it allows us to show colored code blocks in our documentation.
*/
export function highlightCodeBlock(code: string, language: string) {
if (language) {
return highlightJs.highlight(
language.toLowerCase() === 'ts' ? 'typescript' : language, code).value;
}
return code;
}
+10 -17
View File
@@ -5,26 +5,17 @@
import {readFileSync, writeFileSync} from 'fs';
import {join} from 'path';
// These types lack type definitions.
const marked = require('marked');
const highlightJs = require('highlight.js');
import {DocsMarkdownRenderer} from './docs-marked-renderer';
import * as marked from 'marked';
// Regular expression that matches the markdown extension of a given path.
const markdownExtension = /.md$/;
// Setup the default options for converting markdown to HTML.
marked.setOptions({
// Implement a highlight function that converts the code block into a highlighted
// HTML snippet that uses HighlightJS.
highlight: (code: string, language: string): string => {
if (language) {
return highlightJs.highlight(
language.toLowerCase() === 'ts' ? 'typescript' : language, code).value;
}
return code;
}
});
// Custom markdown renderer for transforming markdown files for the docs.
const markdownRenderer = new DocsMarkdownRenderer();
// Setup our custom docs renderer by default.
marked.setOptions({renderer: markdownRenderer});
if (require.main === module) {
// The script expects the bazel-bin path as first argument. All remaining arguments will be
@@ -35,6 +26,8 @@ if (require.main === module) {
// Bazel bin directory.
inputFiles.forEach(inputPath => {
const outputPath = join(bazelBinPath, inputPath.replace(markdownExtension, '.html'));
writeFileSync(outputPath, marked(readFileSync(inputPath, 'utf8')));
const htmlOutput = markdownRenderer.finalizeOutput(marked(readFileSync(inputPath, 'utf8')));
writeFileSync(outputPath, htmlOutput);
});
}
+5
View File
@@ -772,6 +772,11 @@
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef"
integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==
"@types/marked@^0.4.2":
version "0.4.2"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.4.2.tgz#64a89e53ea37f61cc0f3ee1732c555c2dbf6452f"
integrity sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg==
"@types/merge2@^0.3.30":
version "0.3.30"
resolved "https://registry.yarnpkg.com/@types/merge2/-/merge2-0.3.30.tgz#9e39d04f6fe4f36fa7477566cad1faf80b2a671f"