Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for using markdown-it instead of marked #314

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,16 @@
"highlight.js": "^11.7.0",
"iconv-lite": "^0.6.3",
"listr": "^0.14.3",
"markdown-it": "^14.1.0",
"markdown-it-highlightjs": "^4.2.0",
"marked": "^4.2.12",
"puppeteer": ">=8.0.0",
"semver": "^7.3.7",
"serve-handler": "^6.1.3"
},
"devDependencies": {
"@types/listr": "0.14.5",
"@types/markdown-it": "^14.1.2",
"@types/marked": "4.3.0",
"@types/semver": "7.5.4",
"@types/serve-handler": "6.1.3",
Expand Down
59 changes: 58 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

![Screenshot of markdown file and resulting PDF](https://files-iiiuxybjc.now.sh)

**A simple and hackable CLI tool for converting markdown to pdf**. It uses [Marked](https://github.com/markedjs/marked) to convert `markdown` to `html` and [Puppeteer](https://github.com/GoogleChrome/puppeteer) (headless Chromium) to further convert the `html` to `pdf`. It also uses [highlight.js](https://github.com/isagalaev/highlight.js) for code highlighting. The whole source code of this tool is ~only \~250 lines of JS~ ~500 lines of Typescript and ~100 lines of CSS, so it is easy to clone and customize.
**A simple and hackable CLI tool for converting markdown to pdf**. It uses [Marked](https://github.com/markedjs/marked) or [markdown-it](https://github.com/markdown-it/markdown-it) to convert `markdown` to `html` and [Puppeteer](https://github.com/GoogleChrome/puppeteer) (headless Chromium) to further convert the `html` to `pdf`. It also uses [highlight.js](https://github.com/isagalaev/highlight.js) for code highlighting. The whole source code of this tool is ~only \~250 lines of JS~ ~500 lines of Typescript and ~100 lines of CSS, so it is easy to clone and customize.

**Highlights:**

Expand Down Expand Up @@ -63,7 +63,9 @@ Options:
--body-class ............. Classes to be added to the body tag (can be passed multiple times)
--page-media-type ........ Media type to emulate the page with (default: screen)
--highlight-style ........ Style to be used by highlight.js (default: github)
--markdown-parser ........ Set the markdown parser to use. Defaults to marked, but accept `marked` or `markdown-it`
--marked-options ......... Set custom options for marked (as a JSON string)
--markdown-it-options .... Sets custom options for markdown-it (as a JSON string)
--pdf-options ............ Set custom options for the generated PDF (as a JSON string)
--launch-options ......... Set custom launch options for Puppeteer
--gray-matter-options .... Set custom options for gray-matter
Expand Down Expand Up @@ -175,6 +177,7 @@ This can be achieved with [MathJax](https://www.mathjax.org/). A simple example
For default and advanced options see the following links. The default highlight.js styling for code blocks is `github`. The default PDF options are the A4 format and some margin (see `lib/config.ts` for the full default config).

- [Marked Advanced Options](https://marked.js.org/using_advanced)
- [Markdown-it Advanced Options](https://markdown-it.github.io/markdown-it/#MarkdownIt.new)
- [Puppeteer PDF Options](https://pptr.dev/api/puppeteer.pdfoptions)
- [Puppeteer Launch Options](https://pptr.dev/next/api/puppeteer.launchoptions)
- [highlight.js Styles](https://github.com/highlightjs/highlight.js/tree/main/src/styles)
Expand All @@ -190,7 +193,9 @@ For default and advanced options see the following links. The default highlight.
| `--body-class` | `markdown-body` |
| `--page-media-type` | `print` |
| `--highlight-style` | `monokai`, `solarized-light` |
| `--markdown-parser` | `marked`, `markdown-it` |
| `--marked-options` | `'{ "gfm": false }'` |
| `--markdown-it-options` | `{ "linkify": true }` |
| `--pdf-options` | `'{ "format": "Letter", "margin": "20mm", "printBackground": true }'` |
| `--launch-options` | `'{ "args": ["--no-sandbox"] }'` |
| `--gray-matter-options` | `null` |
Expand Down Expand Up @@ -256,6 +261,24 @@ Example `config.json`:
}
```

Example `config.json`:

```json
{
"highlight_style": "monokai",
"body_class": ["dark", "content"],
"markdown_parser": "markdown-it",
"markdown_it_options": {
"linkify": true,
"html": false
}
}
```

If you want to add extensions to marked or markdown-it, you need to use a `.js` file.
See `src/test/marked-extensions/config.js` for an example of using extensions with marked.
See `src/test/markdown-it-extensions/config.js` for an example of using extensions with markdown-it.

#### Github Styles

Here is an example front-matter for how to get Github-like output:
Expand All @@ -271,6 +294,40 @@ css: |-
---
```

### Markdown-it header IDs

Markdown-it does not add `id` attributes to header elements by default. You can enable this using [markdown-it-anchor](https://www.npmjs.com/package/markdown-it-anchor).
Just using the extension is enough to generate the `id` tags, but if you want to exactly match the marked behavior you can customize the extension.

```javascript
// config.js
const markdownItAnchor = require('markdown-it-anchor');

module.exports = {
markdown_parser: 'markdown-it',
// This will generate id tags, and may be sufficient for your use case
// markdown_it_plugins: [markdownItAnchor],

// This will match the output of marked
markdown_it_plugins: [(markdownIt) => {
markdownIt.use(markdownItAnchor, {
tabIndex: false,
slugify: (text: string) => {
const id = text
.toLowerCase()
.trim()
// remove html tags
.replace(/<[!/a-z].*?>/gi, '')
// remove unwanted chars
.replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '')
.replace(/\s/g, '-');
return id;
},
});
}]
};
```

## Security Considerations

### Local file server
Expand Down
12 changes: 12 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export const cliFlags = arg({
'--as-html': Boolean,
'--config-file': String,
'--devtools': Boolean,
'--markdown-parser': String,
'--markdown-it-options': String,

// aliases
'-h': '--help',
Expand Down Expand Up @@ -116,6 +118,16 @@ async function main(args: typeof cliFlags, config: Config) {
config.basedir = args['--basedir'];
}

const VALID_MARKDOWN_PARSER_VALUES: Array<typeof config.markdown_parser> = ['marked', 'markdown-it'];
if (args['--markdown-parser']) {
config.markdown_parser = args['--markdown-parser'] as typeof config.markdown_parser;

// Validate the parser argument
if (!VALID_MARKDOWN_PARSER_VALUES.includes(config.markdown_parser)) {
throw new Error(`Unsupported markdown parser '${config.markdown_parser}'`);
}
}

config.port = args['--port'] ?? (await getPort());

const server = await serveDirectory(config);
Expand Down
28 changes: 27 additions & 1 deletion src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { WatchOptions } from 'chokidar';
import { GrayMatterOption } from 'gray-matter';
import type MarkdownIt from 'markdown-it';
import { marked } from 'marked';
import { resolve } from 'path';
import { FrameAddScriptTagOptions, launch, PDFOptions } from 'puppeteer';
Expand Down Expand Up @@ -38,6 +39,9 @@ export const defaultConfig: Config = {
as_html: false,
devtools: false,
marked_extensions: [],
markdown_parser: 'marked',
markdown_it_options: {},
markdown_it_plugins: [],
};

/**
Expand Down Expand Up @@ -168,11 +172,33 @@ interface BasicConfig {
watch_options?: WatchOptions;

/**
* Custm Extensions to be passed to marked.
* Custom Extensions to be passed to marked.
*
* @see https://marked.js.org/using_pro#extensions
*/
marked_extensions: marked.MarkedExtension[];

/**
* The parser to use. Defaults to marked
*/
markdown_parser: 'marked' | 'markdown-it';

/**
* Options for markdown-it parser
*/
markdown_it_options: MarkdownIt.Options;

/**
* Plugins to be passed to markdown-it
*
* If the extension uses arguments, wrap it in an arrow function
*
* markdown_it_plugins: [
* extensionWithoutOptions,
* (md) => extensionWithOptions(md, {strict: true})
* ]
*/
markdown_it_plugins: MarkdownIt.PluginSimple[];
}

export type PuppeteerLaunchOptions = Parameters<typeof launch>[0];
19 changes: 18 additions & 1 deletion src/lib/get-html.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import { Config } from './config';
import { getMarkdownIt } from './get-markdown-it-with-highlighter';
import { getMarked } from './get-marked-with-highlighter';

/**
* Gets a markdown parser based on the configuration
*/
const getMarkdownConverter = (config: Config): ((_: string) => string) => {
switch (config.markdown_parser) {
case 'markdown-it': {
const markdownIt = getMarkdownIt(config.markdown_it_options, config.markdown_it_plugins);
return markdownIt.render.bind(markdownIt);
}

case 'marked':
default:
return getMarked(config.marked_options, config.marked_extensions);
}
};

/**
* Generates a HTML document from a markdown string and returns it as a string.
*/
export const getHtml = (md: string, config: Config) => `<!DOCTYPE html>
<html>
<head><title>${config.document_title}</title><meta charset="utf-8"></head>
<body class="${config.body_class.join(' ')}">
${getMarked(config.marked_options, config.marked_extensions)(md)}
${getMarkdownConverter(config)(md)}
</body>
</html>
`;
Loading