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 an option to specify the Highlight.js instance and provide a core plugin #15

Merged
merged 1 commit into from
Dec 4, 2020
Merged
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
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# markdown-it-highlightjs [![npm version](http://img.shields.io/npm/v/markdown-it-highlightjs.svg?style=flat-square)](https://www.npmjs.org/package/markdown-it-highlightjs)
# markdown-it-highlightjs [![npm version](https://img.shields.io/npm/v/markdown-it-highlightjs.svg?style=flat-square)](https://www.npmjs.org/package/markdown-it-highlightjs)

> Preset to use [highlight.js] with [markdown-it].

Expand All @@ -23,6 +23,7 @@ Name | Type | Description
`code` | boolean | Whether to add the `hljs` class to raw code blocks (not fenced blocks). | `true`
`register` | object | Register other languages which are not included in the standard pack. | `null`
`inline` | boolean | Whether to highlight inline code. | `false`
`hljs` | object | Provide the instance of [highlight.js] to use for highlighting | `require('highlight.js')`

### Register languages

Expand Down Expand Up @@ -57,3 +58,29 @@ or [Kramdown IAL syntax](https://kramdown.gettalong.org/syntax.html#inline-attri
```

If you do not specify a language, then highlight.js will attempt to guess the language if `auto` is true (which it is by default).

### Provide the [highlight.js] instance

You can specify the `hljs` option to override the default [highlight.js] instance with your own:

```js
const hljs = require('highlight.js/lib/core')

hljs.registerLanguage(
'javascript',
require('highlight.js/lib/languages/javascript')
)

const md = require('markdown-it')()
.use(require('markdown-it-highlightjs'), { hljs })
```

### Core plugin

You may import the core `markdown-it-highlightjs` plugin directly, without any default options. You must specify an instance of [highlight.js] for the `hljs` option.

```js
const hljs = require('highlight.js/lib/core')
const md = require('markdown-it')()
.use(require('markdown-it-highlightjs/core'), { hljs })
```
76 changes: 76 additions & 0 deletions core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
function maybe (f) {
try {
return f()
} catch (e) {
return false
}
}

let hljs

// Allow registration of other languages.
const registerLangs = (register) => register &&
Object.entries(register).map(([lang, pack]) => { hljs.registerLanguage(lang, pack) })

// Highlight with given language.
const highlight = (code, lang) =>
maybe(() => hljs.highlight(lang || 'plaintext', code, true).value) || ''

// Highlight with given language or automatically.
const highlightAuto = (code, lang) =>
lang
? highlight(code, lang)
: maybe(() => hljs.highlightAuto(code).value) || ''

// Wrap a render function to add `hljs` class to code blocks.
const wrap = render =>
function (...args) {
return render.apply(this, args)
.replace('<code class="', '<code class="hljs ')
.replace('<code>', '<code class="hljs">')
}

function inlineCodeRenderer (md, tokens, idx, options) {
const code = tokens[idx]
const next = tokens[idx + 1]
let lang

if (next && next.type === 'text') {
// Match kramdown- or pandoc-style language specifier.
// e.g. `code`{:.ruby} or `code`{.haskell}
const match = /^{:?\.([^}]+)}/.exec(next.content)

if (match) {
lang = match[1]

// Remove the language specification from text following the code.
next.content = next.content.slice(match[0].length)
}
}

const highlighted = options.highlight(code.content, lang)
const cls = lang ? ` class="${options.langPrefix}${md.utils.escapeHtml(lang)}"` : ''

return `<code${cls}>${highlighted}</code>`
}

module.exports = (md, opts) => {
opts = Object.assign({}, opts)
hljs = opts.hljs
if (!hljs) {
throw new Error('A hljs instance is required.')
}

registerLangs(opts.register)

md.options.highlight = opts.auto ? highlightAuto : highlight
md.renderer.rules.fence = wrap(md.renderer.rules.fence)

if (opts.code) {
md.renderer.rules.code_block = wrap(md.renderer.rules.code_block)
}

if (opts.inline) {
md.renderer.rules.code_inline = inlineCodeRenderer.bind(null, md)
}
}
76 changes: 7 additions & 69 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,11 @@
const hljs = require('highlight.js')

function maybe (f) {
try {
return f()
} catch (e) {
return false
}
}

// Allow registration of other languages.
const registerLangs = (register) => register &&
Object.entries(register).map(([lang, pack]) => { hljs.registerLanguage(lang, pack) })

// Highlight with given language.
const highlight = (code, lang) =>
maybe(() => hljs.highlight(lang || 'plaintext', code, true).value) || ''

// Highlight with given language or automatically.
const highlightAuto = (code, lang) =>
lang
? highlight(code, lang)
: maybe(() => hljs.highlightAuto(code).value) || ''

// Wrap a render function to add `hljs` class to code blocks.
const wrap = render =>
function (...args) {
return render.apply(this, args)
.replace('<code class="', '<code class="hljs ')
.replace('<code>', '<code class="hljs">')
}

function inlineCodeRenderer (md, tokens, idx, options) {
const code = tokens[idx]
const next = tokens[idx + 1]
let lang

if (next && next.type === 'text') {
// Match kramdown- or pandoc-style language specifier.
// e.g. `code`{:.ruby} or `code`{.haskell}
const match = /^{:?\.([^}]+)}/.exec(next.content)

if (match) {
lang = match[1]

// Remove the language specification from text following the code.
next.content = next.content.slice(match[0].length)
}
}

const highlighted = options.highlight(code.content, lang)
const cls = lang ? ` class="${options.langPrefix}${md.utils.escapeHtml(lang)}"` : ''

return `<code${cls}>${highlighted}</code>`
}

const highlightjs = (md, opts) => {
opts = Object.assign({}, highlightjs.defaults, opts)
registerLangs(opts.register)

md.options.highlight = opts.auto ? highlightAuto : highlight
md.renderer.rules.fence = wrap(md.renderer.rules.fence)

if (opts.code) {
md.renderer.rules.code_block = wrap(md.renderer.rules.code_block)
}

if (opts.inline) {
md.renderer.rules.code_inline = inlineCodeRenderer.bind(null, md)
}
opts = Object.assign(
{},
highlightjs.defaults,
{ hljs: (opts && opts.hljs) || require('highlight.js') },
opts
)
return require('./core')(md, opts)
}

highlightjs.defaults = {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"files": [
"README.md",
"UNLICENSE",
"core.js",
"index.js"
],
"repository": {
Expand Down
16 changes: 16 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@ const { strictEqual: equal } = require('assert')
const md = require('markdown-it')
const highlightjs = require('./')

equal(
(() => {
try {
return md().use(require('./core'))
} catch (ex) {
return ex.message
}
})(),
'A hljs instance is required.'
)

equal(
md().use(highlightjs, { hljs: require('highlight.js/lib/core') }).render('```js\nconsole.log(42)\n```'),
`<pre><code class="hljs language-js">console.log(42)\n</code></pre>
`)

equal(
md().use(highlightjs).render('```js\nconsole.log(42)\n```'),
`<pre><code class="hljs language-js"><span class="hljs-built_in">console</span>.log(<span class="hljs-number">42</span>)
Expand Down