diff --git a/package.json b/package.json
index 05cb4d7dfc5..bb4310c636a 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/tools/markdown-to-html/BUILD.bazel b/tools/markdown-to-html/BUILD.bazel
index 48088ccb2d9..e57bd8f5113 100644
--- a/tools/markdown-to-html/BUILD.bazel
+++ b/tools/markdown-to-html/BUILD.bazel
@@ -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",
)
diff --git a/tools/markdown-to-html/docs-marked-renderer.ts b/tools/markdown-to-html/docs-marked-renderer.ts
new file mode 100644
index 00000000000..6a4cc75c55b
--- /dev/null
+++ b/tools/markdown-to-html/docs-marked-renderer.ts
@@ -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 = //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 `
+
+ `;
+ }
+
+ return `${label}`;
+ }
+
+ /** 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:
+ *
+ * `` turns into `
`
+ */
+ html(html: string) {
+ html = html.replace(exampleCommentRegex, (_match: string, name: string) =>
+ ``
+ );
+
+ 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 `${output}
`;
+ }
+}
diff --git a/tools/markdown-to-html/highlight-code-block.ts b/tools/markdown-to-html/highlight-code-block.ts
new file mode 100644
index 00000000000..c742b911b9a
--- /dev/null
+++ b/tools/markdown-to-html/highlight-code-block.ts
@@ -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;
+}
diff --git a/tools/markdown-to-html/transform-markdown.ts b/tools/markdown-to-html/transform-markdown.ts
index 1651af2c246..3c56d456113 100644
--- a/tools/markdown-to-html/transform-markdown.ts
+++ b/tools/markdown-to-html/transform-markdown.ts
@@ -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);
});
}
diff --git a/yarn.lock b/yarn.lock
index f994dad0a27..69286713f94 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"