mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
committed by
Jeremy Elbourn
parent
47e7aac6da
commit
bd9645edae
@@ -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,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;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user