From bd9645edae15dd10d4466359e034bfc493fb4dce Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 29 Nov 2018 01:16:03 +0100 Subject: [PATCH] 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 `/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 ` + + + ${label} + + `; + } + + 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"