Skip to content

Commit

Permalink
feat: implemented math syntax with MathJax
Browse files Browse the repository at this point in the history
  • Loading branch information
akabekobeko committed Apr 19, 2021
1 parent 8ea434f commit 1f62c24
Show file tree
Hide file tree
Showing 15 changed files with 422 additions and 236 deletions.
72 changes: 38 additions & 34 deletions docs/vfm.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,73 +299,77 @@ section.author {

## Math equation

<Badge type="warning">PRE-RELEASE</Badge>
Outputs HTML processed by [MathJax](https://www.mathjax.org/).

It is disabled by default. It is activated by satisfying one of the following.

- VFM options: `math: true`
- CLI options: `--math`
- Frontmatter: `math: true` (Priority over others)

**VFM**

The VFM syntax for MathJax inline is `$...$` and the display is `$$...$$`.

```markdown
$$\sum$$
inline: $x = y$

display: $$1 + 1 = 2$$
```

**HTML**

```html
<p>
<span class="math math-inline">
<span class="katex">
<span class="katex-html" aria-hidden="true">
<span class="base">
<span class="strut" style="height:0.43056em;vertical-align:0em;">
</span>
<span class="mord mathdefault">s</span>
<span class="mord mathdefault">u</span>
<span class="mord mathdefault">m</span>
</span>
</span>
</span>
</span>
</p>
```

**CSS**
It also outputs the `<script>` and `<body>` attributes for processing MathJax in Vivliostyle if `math` is enabled.

```css
span.math {
}
span.math.math-inline {
}
```html
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_CHTML"></script>
</head>
<body data-math-typeset>
<p>inline: \(x = y\)</p>
<p>display: $$1 + 1 = 2$$</p>
</body>
</html>
```

## Frontmatter

<Badge type="warning">PRE-RELEASE</Badge>

Frontmatter is a way of defining metadata in Markdown (file) units.

```yaml
---
title: 'Introduction to VFM'
author: 'Author'
class: 'my-class'
math: true
---

```

#### Reserved words

| Property | Type | Description |
| -------- | ------ | ------------------------------------------------------------------------------------------- |
| title | String | Document title. If missing, very first heading `#` of the content will be treated as title. |
| author | String | Document author. |
| class | String | Custom classes applied to `<body>` |
| theme | String | Vivliostyle theme package or bare CSS file. |
| Property | Type | Description |
| -------- | ------- | ------------------------------------------------------------------------------------------- |
| title | String | Document title. If missing, very first heading `#` of the content will be treated as title. |
| author | String | Document author. |
| class | String | Custom classes applied to `<body>` |
| math | Boolean | Enable math syntax. |
| theme | String | Vivliostyle theme package or bare CSS file. |

The priority of `title` is as follows.

1. `title` property of the frontmatter
2. First heading `#` of the content
3. `title` option of VFM

The priority of `math` is as follows.

1. `math` property of the frontmatter
2. `math` option of VFM

**class**

```yaml
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@
"release": "release-it",
"release:pre": "release-it --preRelease --npm.tag=latest",
"test": "jest",
"test:debug": "jest tests/xxx.test.ts --silent=false --verbose false"
"test:debug": "jest tests/math.test.ts --silent=false --verbose false"
},
"dependencies": {
"debug": "^4.3.1",
"hast-util-find-and-replace": "^3.2.0",
"hast-util-is-element": "^1.1.0",
"hastscript": "^6.0.0",
"js-yaml": "^4.0.0",
"mdast-util-find-and-replace": "^1.1.1",
"mdast-util-to-hast": "^10.1.1",
"mdast-util-to-string": "^2.0.0",
"meow": "^9.0.0",
Expand Down
6 changes: 6 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const cli = meow(
--language Document language (ignored in partial mode)
--hard-line-breaks Add <br> at the position of hard line breaks, without needing spaces
--disable-format-html Disable automatic HTML format
--math Enable math syntax
Examples
$ vfm input.md
Expand Down Expand Up @@ -45,6 +46,9 @@ const cli = meow(
disableFormatHtml: {
type: 'boolean',
},
math: {
type: 'boolean',
},
},
},
);
Expand All @@ -59,6 +63,7 @@ function compile(input: string) {
language: cli.flags.language,
hardLineBreaks: cli.flags.hardLineBreaks,
disableFormatHtml: cli.flags.disableFormatHtml,
math: cli.flags.math,
}),
);
}
Expand All @@ -71,6 +76,7 @@ function main(
language: { type: 'string' };
hardLineBreaks: { type: 'boolean' };
disableFormatHtml: { type: 'boolean' };
math: { type: 'boolean' };
}>,
) {
try {
Expand Down
45 changes: 36 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import doc from 'rehype-document';
import rehypeFormat from 'rehype-format';
import rehypeStringify from 'rehype-stringify';
import unified, { Processor } from 'unified';
import { hast as clearHtmlLang } from './plugins/clear-html-lang';
import { hast as metadata } from './plugins/metadata';
import { hast as hastClearHtmlLang } from './plugins/clear-html-lang';
import { hast as hastMath } from './plugins/math';
import { hast as hastMetadata, MetadataVFile } from './plugins/metadata';
import { replace as handleReplace, ReplaceRule } from './plugins/replace';
import { reviveParse as markdown } from './revive-parse';
import html from './revive-rehype';
import { reviveRehype as html } from './revive-rehype';
import { debug } from './utils';

/**
Expand All @@ -27,14 +28,34 @@ export interface StringifyMarkdownOptions {
hardLineBreaks?: boolean;
/** Disable automatic HTML format. */
disableFormatHtml?: boolean;
/** Enable math syntax. */
math?: boolean;
}

export interface Hooks {
afterParse: ReplaceRule[];
}

/**
* Create Unified processor for MDAST and HAST.
* Update the settings by comparing the options with the frontmatter metadata.
* @param options Options.
* @param md Markdown string.
* @returns Options updated by checking.
*/
const checkOptions = (options: StringifyMarkdownOptions, md: string) => {
// Reduce processing as much as possible because it only reads metadata.
const processor = VFM({ partial: true, disableFormatHtml: true });
const metadata = (processor.processSync(md) as MetadataVFile).data;
const opts = options;

opts.title = metadata.title === undefined ? opts.title : metadata.title;
opts.math = metadata.math === undefined ? opts.math : metadata.math;

return opts;
};

/**
* Create Unified processor for Markdown AST and Hypertext AST.
* @param options Options.
* @returns Unified processor.
*/
Expand All @@ -46,9 +67,10 @@ export function VFM({
replace = undefined,
hardLineBreaks = false,
disableFormatHtml = false,
math = false,
}: StringifyMarkdownOptions = {}): Processor {
const processor = unified()
.use(markdown({ hardLineBreaks }))
.use(markdown(hardLineBreaks, math))
.data('settings', { position: false })
.use(html);

Expand All @@ -59,13 +81,18 @@ export function VFM({
if (!partial) {
processor.use(doc, { language, css: style, title });
if (!language) {
processor.use(clearHtmlLang);
processor.use(hastClearHtmlLang);
}
}

processor.use(metadata);
processor.use(hastMetadata);
processor.use(rehypeStringify);

// Must be run after `rehype-document` to write to `<head>`
if (math) {
processor.use(hastMath);
}

// Explicitly specify true if want unformatted HTML during development or debug
if (!disableFormatHtml) {
processor.use(rehypeFormat);
Expand All @@ -75,7 +102,7 @@ export function VFM({
}

/**
* Convert Markdown to a stringify (HTML).
* Convert markdown to a stringify (HTML).
* @param markdownString Markdown string.
* @param options Options.
* @returns HTML string.
Expand All @@ -84,7 +111,7 @@ export function stringify(
markdownString: string,
options: StringifyMarkdownOptions = {},
): string {
const processor = VFM(options);
const processor = VFM(checkOptions(options, markdownString));
const vfile = processor.processSync(markdownString);
debug(vfile.data);
return String(vfile);
Expand Down
Loading

0 comments on commit 1f62c24

Please sign in to comment.