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

Use recommended extension export #91

Merged
merged 1 commit into from
Dec 10, 2023
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
22 changes: 9 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,20 @@ Node.js v18 or later.

## Usage

Import `GovukHTMLRenderer` and set it as the `renderer` in marked’s options:

```js
const { marked } = require('marked');
const GovukHTMLRenderer = require('govuk-markdown')
const govukMarkdown = require('govuk-markdown')

marked.setOptions({
renderer: new GovukHTMLRenderer()
})
marked.use(govukMarkdown())
```

If you are using [ES modules](https://nodejs.org/api/esm.html#introduction), import as follows:

```js
import { marked } from 'marked'
import GovukHTMLRenderer from 'govuk-markdown'
import govukMarkdown from 'govuk-markdown'

marked.setOptions({
renderer: new GovukHTMLRenderer()
})
marked.use(govukMarkdown())
```

When you call `marked`, the generated HTML will include the classes from GOV.UK Frontend. For example:
Expand Down Expand Up @@ -87,10 +81,12 @@ In addition to [marked’s options](https://marked.js.org/using_advanced#options
For example:

```js
marked.setOptions({
renderer: new GovukHTMLRenderer(),
const { marked } = require('marked');
const { govukMarkdown } = require('govuk-markdown')

marked.use(govukMarkdown({
headingsStartWith: 'xl'
})
}))

marked('# Extra large heading')
```
Expand Down
205 changes: 105 additions & 100 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,109 +1,114 @@
const highlightJs = require('highlight.js')
const { Renderer } = require('marked').marked

/**
* Creates a new marked Renderer. Adds GOV.UK typography classes to block
* quotes, headings, paragraphs, links, lists, section breaks and tables and
* updates references to local files in links and images to friendly URLs.
* Add GOV.UK typography classes to blockquotes, headings, paragraphs, links,
* lists, section breaks and tables.
*
* @class
* @param {object} [options] Options for the extension
* @returns {object} A MarkedExtension to be passed to `marked.use()`
*/
module.exports = class GovukHTMLRenderer extends Renderer {
// Block quotes
blockquote (quote) {
return `<blockquote class="govuk-inset-text govuk-!-margin-left-0">${quote}</blockquote>\n`
}

// Headings
heading (text, level, raw) {
const id = raw.toLowerCase().replace(/[^\w]+/g, '-')

// Make modifiers relative to the starting heading level
const modifiers = ['xl', 'l', 'm', 's']
const headingsStartWith = (modifiers.includes(this.options.headingsStartWith)) ? this.options.headingsStartWith : 'l'
const modifierStartIndex = modifiers.indexOf(headingsStartWith)

const modifier = modifiers[modifierStartIndex + level - 1] || 's'

return `<h${level} class="govuk-heading-${modifier}" id="${id}">${text}</h${level}>`
}

// Paragraphs
paragraph (string) {
// Don’t place figure (or figure within an anchor) within paragraph
const FIGURE_RE = /(<a([^>]+)>)?<figure/
if (FIGURE_RE.test(string)) {
return string
} else {
return `<p class="govuk-body">${string}</p>\n`
}
}

// Links
link (href, title, text) {
if (title) {
return `<a class="govuk-link" href="${href}" title="${title}">${text}</a>`
} else {
return `<a class="govuk-link" href="${href}">${text}</a>`
}
}

// Lists
list (body, ordered) {
const element = ordered ? 'ol' : 'ul'
const modifier = ordered ? 'number' : 'bullet'

return `<${element} class="govuk-list govuk-list--${modifier}">${body}</${element}>\n`
}

// Checkbox
checkbox (checked) {
return `<span class="x-govuk-checkbox"><input class="x-govuk-checkbox__input" type="checkbox"${checked ? ' checked' : ''} disabled><span class="x-govuk-checkbox__pseudo"></span></span>`
}

// Section break
hr () {
return '<hr class="govuk-section-break govuk-section-break--xl govuk-section-break--visible">\n'
}

// Tables
table (header, body) {
return `<table class="govuk-table">\n<thead class="govuk-table__head">\n${header}</thead>\n<tbody class="govuk-table__body">${body}</tbody>\n</table>\n`
}

tablerow (content) {
return `<tr class="govuk-table__row">\n${content}</tr>\n`
}

tablecell (content, { header, align }) {
const element = header ? 'th' : 'td'
const className = header ? 'govuk-table__header' : 'govuk-table__cell'
const alignClass = align ? ` govuk-!-text-align-${align}` : ''
return `<${element} class="${className}${alignClass}">${content}</${element}>\n`
}

// Block code
// By not using marked’s `highlight` option, we can add a class to the container
code (string, language) {
highlightJs.configure({ classPrefix: 'x-govuk-code__' })

if (language) {
// Code language has been set, or can be determined
let code
if (highlightJs.getLanguage(language)) {
code = highlightJs.highlight(string, { language }).value
} else {
code = highlightJs.highlightAuto(string).value
module.exports = function (options = {}) {
return {
renderer: {
// Block quotes
blockquote (quote) {
return `<blockquote class="govuk-inset-text govuk-!-margin-left-0">${quote}</blockquote>\n`
},

// Headings
heading (text, level, raw) {
const id = raw.toLowerCase().replace(/[^\w]+/g, '-')

// Make modifiers relative to the starting heading level
const modifiers = ['xl', 'l', 'm', 's']
const headingsStartWith = modifiers.includes(options.headingsStartWith)
? options.headingsStartWith
: 'l'
const modifierStartIndex = modifiers.indexOf(headingsStartWith)

const modifier = modifiers[modifierStartIndex + level - 1] || 's'

return `<h${level} class="govuk-heading-${modifier}" id="${id}">${text}</h${level}>`
},

// Paragraphs
paragraph (string) {
// Don’t place figure (or figure within an anchor) within paragraph
const FIGURE_RE = /(<a([^>]+)>)?<figure/
if (FIGURE_RE.test(string)) {
return string
} else {
return `<p class="govuk-body">${string}</p>\n`
}
},

// Links
link (href, title, text) {
if (title) {
return `<a class="govuk-link" href="${href}" title="${title}">${text}</a>`
} else {
return `<a class="govuk-link" href="${href}">${text}</a>`
}
},

// Lists
list (body, ordered) {
const element = ordered ? 'ol' : 'ul'
const modifier = ordered ? 'number' : 'bullet'

return `<${element} class="govuk-list govuk-list--${modifier}">${body}</${element}>\n`
},

// Checkbox
checkbox (checked) {
return `<span class="x-govuk-checkbox"><input class="x-govuk-checkbox__input" type="checkbox"${checked ? ' checked' : ''} disabled><span class="x-govuk-checkbox__pseudo"></span></span>`
},

// Section break
hr () {
return '<hr class="govuk-section-break govuk-section-break--xl govuk-section-break--visible">\n'
},

// Tables
table (header, body) {
return `<table class="govuk-table">\n<thead class="govuk-table__head">\n${header}</thead>\n<tbody class="govuk-table__body">${body}</tbody>\n</table>\n`
},

tablerow (content) {
return `<tr class="govuk-table__row">\n${content}</tr>\n`
},

tablecell (content, { header, align }) {
const element = header ? 'th' : 'td'
const className = header ? 'govuk-table__header' : 'govuk-table__cell'
const alignClass = align ? ` govuk-!-text-align-${align}` : ''
return `<${element} class="${className}${alignClass}">${content}</${element}>\n`
},

// Block code
// By not using marked’s `highlight` option, we can add a class to the container
code (string, language) {
highlightJs.configure({ classPrefix: 'x-govuk-code__' })

if (language) {
// Code language has been set, or can be determined
let code
if (highlightJs.getLanguage(language)) {
code = highlightJs.highlight(string, { language }).value
} else {
code = highlightJs.highlightAuto(string).value
}
return `<pre class="x-govuk-code x-govuk-code--block x-govuk-code__language--${language}" tabindex="0"><code>${code}</code></pre>\n`
} else {
// No language found, so render as plain text
return `<pre class="x-govuk-code x-govuk-code--block" tabindex="0">${string}</pre>\n`
}
},

// Inline code
codespan (code) {
return `<code class="x-govuk-code x-govuk-code--inline">${code}</code>`
}
return `<pre class="x-govuk-code x-govuk-code--block x-govuk-code__language--${language}" tabindex="0"><code>${code}</code></pre>\n`
} else {
// No language found, so render as plain text
return `<pre class="x-govuk-code x-govuk-code--block" tabindex="0">${string}</pre>\n`
}
}

// Inline code
codespan (code) {
return `<code class="x-govuk-code x-govuk-code--inline">${code}</code>`
}
}
8 changes: 3 additions & 5 deletions test/index.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { strict as assert } from 'node:assert'
import test from 'node:test'
import { marked } from 'marked'
import GovukHTMLRenderer from '../index.js'
import govukMarkdown from '../index.js'

marked.setOptions({
renderer: new GovukHTMLRenderer()
})
marked.use(govukMarkdown())

test('Renders blockquote', () => {
const result = marked('> Blockquote')
Expand All @@ -26,7 +24,7 @@ test('Renders heading with id', () => {
})

test('Renders headings, using classes relative to given starting level', () => {
marked.setOptions({ headingsStartWith: 'xl' })
marked.use(govukMarkdown({ headingsStartWith: 'xl' }))

const result = marked('# Heading 1\n## Heading 2\n### Heading 3\n#### Heading 4')

Expand Down