Skip to content

Commit

Permalink
feat: Improved metadata definition with frontmatter
Browse files Browse the repository at this point in the history
  • Loading branch information
akabekobeko committed Jun 27, 2021
1 parent f7d61f4 commit 449fa46
Show file tree
Hide file tree
Showing 8 changed files with 664 additions and 383 deletions.
132 changes: 103 additions & 29 deletions docs/vfm.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,55 +399,129 @@ It also outputs `<script>` for processing MathJax if `math` is enabled and the m

## Frontmatter

Frontmatter is a way of defining metadata in Markdown (file) units.
Frontmatter is a way of defining metadata in Markdown (file) units. Write YAML at the beginning of the file.

**VFM**

```yaml
---
title: 'Introduction to VFM'
author: 'Author'
id: 'my-page'
lang: 'ja'
dir: 'ltr'
class: 'my-class'
math: true
title: 'Title'
html:
data-color-mode: 'dark'
data-light-theme: 'light'
data-dark-theme: 'dark'
body:
id: 'body'
class: 'foo bar'
base:
target: '_top'
href: 'https://www.example.com/'
meta:
- name: 'theme-color'
media: '(prefers-color-scheme: light)'
content: 'red'
- name: 'theme-color'
media: '(prefers-color-scheme: dark)'
content: 'darkred'
link:
- rel: 'stylesheet'
href: 'sample1.css'
- rel: 'stylesheet'
href: 'sample2.css'
script:
- type: 'text/javascript'
src: 'sample1.js'
- type: 'text/javascript'
src: 'sample2.js'
vfm:
math: false
theme: 'theme.css'
author: 'Author'
---

```

#### Reserved words
**HTML**

| 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. |
```html
<!doctype html>
<html data-color-mode="dark" data-light-theme="light" data-dark-theme="dark" id="my-page" lang="ja" dir="ltr" class="my-class">
<head>
<meta charset="utf-8">
<title>Title</title>
<base target="_top" href="https://www.example.com/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="red">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="darkred">
<meta name="author" content="Author">
<link rel="stylesheet" href="sample1.css">
<link rel="stylesheet" href="sample2.css">
<script type="text/javascript" src="sample1.js"></script>
<script type="text/javascript" src="sample2.js"></script>
</head>
<body id="body" class="foo bar">
<p>Text</p>
</body>
</html>
```

The priority of `title` is as follows.
**CSS**

1. `title` property of the frontmatter
2. First heading `#` of the content
3. `title` option of VFM
```css
.my-class {
}

The priority of `math` is as follows.
.foo.bar {
}
```

### Reserved properties

|Property|Type|Description|
|---|---|---|
|`id`|`String`|`<html id="...">`|
|`lang`|`String`|`<html lang="...">`|
|`dir`|`String`|`<html dir="...">`, value is `ltr`, `rtl` or `auto`.|
|`class`|`String`|`<html class="...">`|
|`title`|`String`|`<title>...</title>`, if missing, very first heading of the content will be treated as title.|
|`html`|`Object`|`<html key="value">`, key/value pair becomes attribute of `<html>`.|
|`body`|`Object`|`<body key="value">`, key/value pair becomes attribute of `<body>`.|
|`base`|`Object`|`<base key="value">`, key/value pair becomes attribute of `<base>`.|
|`meta`|`Object[]`|`<meta key="value">`, key/value pair becomes attribute of `<meta>`.|
|`link`|`Object[]`|`<link key="value">`, key/value pair becomes attribute of `<link>`.|
|`script`|`Object[]`|`<script key="value">`, key/value pair becomes attribute of `<script>`.|
|`vfm`|`Object`|VFM settings.|
|Other|`String`|`<meta name="key" content="value">`, key/value pair becomes one `<meta>`.|

**vfm**

|Property|Type|Description|
|---|---|---|
|`math`|Boolean|Enable math syntax, default 'true'.|
|`theme`|String|Vivliostyle theme package or bare CSS file.|

### Priority with options

1. `math` option of `VFM` API
2. `math` property of the frontmatter
3. `math` option of `stringify` API
If there are multiple specifications for the same purpose, the priority is as follows.

**class**
1. Frontmatter
2. VFM options

In Frontmatter, if there is a duplicate of the root `id` and` id` in the `html` property, the root definition takes precedence.

```yaml
---
class: 'twocolumn'
id: 'sample1'
html:
id: 'sample2'
---

```

```css
body.twocolumn {
}
```

To specify multiple classes, define as `class:'foo bar'`.
In this example, `sample1` is adopted.

## Footnotes

Expand Down
87 changes: 58 additions & 29 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import rehypeFormat from 'rehype-format';
import rehypeStringify from 'rehype-stringify';
import unified, { Processor } from 'unified';
import { hast as hastClearHtmlLang } from './plugins/clear-html-lang';
import { mdast as doc } from './plugins/document';
//import { mdast as doc } from './plugins/document.old';
import { hast as hastMath } from './plugins/math';
import { hast as hastMetadata, MetadataVFile } from './plugins/metadata';
import { Metadata, readMetadata } from './plugins/metadata';
import { replace as handleReplace, ReplaceRule } from './plugins/replace';
import { reviveParse as markdown } from './revive-parse';
import { reviveRehype as html } from './revive-rehype';
Expand Down Expand Up @@ -37,38 +37,71 @@ export interface Hooks {
}

/**
* Update the settings by comparing the options with the frontmatter metadata.
* Check the metadata with options.
* @param metadata Metadata.
* @param options Options.
* @param md Markdown string.
* @returns Options updated by checking.
* @returns Checked metadata.
*/
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;
const checkMetadata = (
metadata: Metadata,
options: StringifyMarkdownOptions,
) => {
const result = { ...metadata };

opts.title = metadata.title === undefined ? opts.title : metadata.title;
opts.math = metadata.math === undefined ? opts.math : metadata.math;
if (metadata.title === undefined && options.title !== undefined) {
result.title = options.title;
}

if (metadata.lang === undefined && options.language !== undefined) {
result.lang = options.language;
}

return opts;
if (options.style) {
if (metadata.link === undefined) {
metadata.link = [];
}

if (typeof options.style === 'string') {
metadata.link.push([
{ name: 'rel', value: 'stylesheet' },
{ name: 'href', value: options.style },
]);
} else if (Array.isArray(options.style)) {
for (const style of options.style) {
metadata.link.push([
{ name: 'rel', value: 'stylesheet' },
{ name: 'href', value: style },
]);
}
}
}

return result;
};

/**
* Create Unified processor for Markdown AST and Hypertext AST.
* @param options Options.
* @returns Unified processor.
*/
export function VFM({
style = undefined,
partial = false,
title = undefined,
language = undefined,
replace = undefined,
hardLineBreaks = false,
disableFormatHtml = false,
math = true,
}: StringifyMarkdownOptions = {}): Processor {
export function VFM(
{
style = undefined,
partial = false,
title = undefined,
language = undefined,
replace = undefined,
hardLineBreaks = false,
disableFormatHtml = false,
math = true,
}: StringifyMarkdownOptions = {},
metadata: Metadata = {},
): Processor {
const meta = checkMetadata(metadata, { style, title, language });
if (meta.vfm && meta.vfm.math !== undefined) {
math = meta.vfm.math;
}

const processor = unified()
.use(markdown(hardLineBreaks, math))
.data('settings', { position: false })
Expand All @@ -79,13 +112,9 @@ export function VFM({
}

if (!partial) {
processor.use(doc, { language, css: style, title, responsive: true });
if (!language) {
processor.use(hastClearHtmlLang);
}
processor.use(doc, meta);
}

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

// Must be run after `rehype-document` to write to `<head>`
Expand All @@ -111,7 +140,7 @@ export function stringify(
markdownString: string,
options: StringifyMarkdownOptions = {},
): string {
const processor = VFM(checkOptions(options, markdownString));
const processor = VFM(options, readMetadata(markdownString));
const vfile = processor.processSync(markdownString);
debug(vfile.data);
return String(vfile);
Expand Down
16 changes: 0 additions & 16 deletions src/plugins/clear-html-lang.ts

This file was deleted.

Loading

0 comments on commit 449fa46

Please sign in to comment.