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

Enhance plugin system to control Marpit features #127

Merged
merged 7 commits into from
Jan 30, 2019
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added

- Make custom directives definable via `customDirectives` member ([#124](https://github.com/marp-team/marpit/issues/124), [#125](https://github.com/marp-team/marpit/pull/125), [#128](https://github.com/marp-team/marpit/pull/128))
- Enhance plugin system to control Marpit features ([#127](https://github.com/marp-team/marpit/pull/127))

### Changed

Expand Down
80 changes: 80 additions & 0 deletions src/helpers/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/** @module */

export const MarpitSymbol = Symbol('Marpit')

function onInitializeStateCore(state) {
state.md[MarpitSymbol] = true

state.marpit = function marpit(value) {
state.md[MarpitSymbol] = value
}
}

function ruleWrapper(basePlugin) {
return function wrappedRule(state, ...args) {
if (!state.md[MarpitSymbol]) return false

return basePlugin(state, ...args)
}
}

/**
* Enhance markdown-it plugin system to support Marpit.
*
* @alias module:helpers/plugin
* @param {MarkdownIt} md markdown-it instance.
* @param {Function} callback Callback function. markdown-it rules extended
* within the callback will check the state of Marpit features toggled by
* `StateCore#marpit`.
*/
function useMarpitPlugin(md, callback) {
if (!Object.prototype.hasOwnProperty.call(md, MarpitSymbol)) {
md[MarpitSymbol] = true

const { State } = md.core

md.core.State = function StateCore(src, markdownIt, env) {
const state = new State(src, markdownIt, env)

onInitializeStateCore(state)
return state
}
}

const ruler = Object.getPrototypeOf(md.core.ruler)
const { after, at, before, push } = ruler

const resetRuler = () => {
ruler.after = after
ruler.at = at
ruler.before = before
ruler.push = push
}

try {
ruler.after = function marpitAfter(afterName, ruleName, fn, options) {
return after.call(this, afterName, ruleName, ruleWrapper(fn), options)
}

ruler.at = function marpitAt(name, fn, options) {
return at.call(this, name, ruleWrapper(fn), options)
}

ruler.before = function marpitBefore(beforeName, ruleName, fn, options) {
return before.call(this, beforeName, ruleName, ruleWrapper(fn), options)
}

ruler.push = function marpitPush(ruleName, fn, options) {
return push.call(this, ruleName, ruleWrapper(fn), options)
}

callback()
} catch (e) {
resetRuler()
throw e
}

resetRuler()
}

export default useMarpitPlugin
5 changes: 4 additions & 1 deletion src/markdown/background_image.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ import parse from './background_image/parse'
*
* @alias module:markdown/background_image
* @param {MarkdownIt} md markdown-it instance.
* @param {Marpit} marpit Marpit instance.
*/
function backgroundImage(md) {
function backgroundImage(md, marpit) {
if (!marpit.options.backgroundSyntax) return

parse(md)
apply(md)
advanced(md)
Expand Down
60 changes: 34 additions & 26 deletions src/marpit.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import MarkdownIt from 'markdown-it'
import useMarpitPlugin, { MarpitSymbol } from './helpers/plugin'
import wrapArray from './helpers/wrap_array'
import ThemeSet from './theme_set'
import { marpitContainer } from './element'
Expand Down Expand Up @@ -132,24 +133,25 @@ class Marpit {

/** @private */
applyMarkdownItPlugins(md = this.markdown) {
const { backgroundSyntax, filters, looseYAML, scopedStyle } = this.options

md.use(marpitComment, { looseYAML })
.use(marpitStyleParse, this)
.use(marpitSlide)
.use(marpitParseDirectives, this, { looseYAML })
.use(marpitApplyDirectives, this)
.use(marpitHeaderAndFooter)
.use(marpitHeadingDivider, this)
.use(marpitSlideContainer, this.slideContainers)
.use(marpitContainerPlugin, this.containers)
.use(marpitParseImage, { filters })
.use(marpitSweep)
.use(marpitInlineSVG, this)
.use(marpitStyleAssign, this, { supportScoped: scopedStyle })
.use(marpitCollect, this)

if (backgroundSyntax) md.use(marpitBackgroundImage)
const { filters, looseYAML, scopedStyle } = this.options

useMarpitPlugin(md, () => {
md.use(marpitComment, { looseYAML })
.use(marpitStyleParse, this)
.use(marpitSlide)
.use(marpitParseDirectives, this, { looseYAML })
.use(marpitApplyDirectives, this)
.use(marpitHeaderAndFooter)
.use(marpitHeadingDivider, this)
.use(marpitSlideContainer, this.slideContainers)
.use(marpitContainerPlugin, this.containers)
.use(marpitParseImage, { filters })
.use(marpitSweep)
.use(marpitInlineSVG, this)
.use(marpitStyleAssign, this, { supportScoped: scopedStyle })
.use(marpitCollect, this)
.use(marpitBackgroundImage, this)
})
}

/**
Expand All @@ -164,14 +166,17 @@ class Marpit {
* Render Markdown into HTML and CSS string.
*
* @param {string} markdown A Markdown string.
* @param {Object} [env] Environment object for passing to markdown-it.
* @param {Object} [env={}] Environment object for passing to markdown-it.
* @param {boolean} [env.htmlAsArray=false] Output rendered HTML as array per
* slide.
* @returns {Marpit~RenderResult} An object of rendering result.
*/
render(markdown, env = {}) {
const html = this.renderMarkdown(markdown, env)
if (!this.markdown[MarpitSymbol]) return { html, css: '', comments: [] }

return {
html: this.renderMarkdown(markdown, env),
html,
css: this.renderStyle(this.lastGlobalDirectives.theme),
comments: this.lastComments,
}
Expand All @@ -191,15 +196,15 @@ class Marpit {
* @returns {string|string[]} The result string(s) of rendering Markdown.
*/
renderMarkdown(markdown, env = {}) {
if (env.htmlAsArray) {
this.markdown.parse(markdown, env)
const tokens = this.markdown.parse(markdown, env)

return this.lastSlideTokens.map(slide =>
this.markdown.renderer.render(slide, this.markdown.options, env)
if (env.htmlAsArray && this.markdown[MarpitSymbol]) {
return this.lastSlideTokens.map(slideTokens =>
this.markdown.renderer.render(slideTokens, this.markdown.options, env)
)
}

return this.markdown.render(markdown, env)
return this.markdown.renderer.render(tokens, this.markdown.options, env)
}

/**
Expand Down Expand Up @@ -233,7 +238,10 @@ class Marpit {
* @returns {Marpit} The called {@link Marpit} instance for chainable.
*/
use(plugin, ...params) {
plugin.call(this.markdown, this.markdown, ...params)
useMarpitPlugin(this.markdown, () =>
plugin.call(this.markdown, this.markdown, ...params)
)

return this
}
}
Expand Down
17 changes: 10 additions & 7 deletions test/markdown/background_image.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,21 @@ describe('Marpit background image plugin', () => {
customDirectives: { global: {}, local: {} },
lastGlobalDirectives: {},
themeSet: { getThemeProp: () => 100 },
options: { inlineSVG: svg },
options: { backgroundSyntax: true, inlineSVG: svg },
})

const md = (svg = false, filters = false) =>
new MarkdownIt()
const md = (svg = false, filters = false) => {
const stub = marpitStub(svg)

return new MarkdownIt()
.use(comment)
.use(slide)
.use(parseDirectives, marpitStub(svg))
.use(applyDirectives, marpitStub(svg))
.use(inlineSVG, marpitStub(svg))
.use(parseDirectives, stub)
.use(applyDirectives, stub)
.use(inlineSVG, stub)
.use(parseImage, { filters })
.use(backgroundImage)
.use(backgroundImage, stub)
}

const bgDirective = (url, mdInstance) => {
const [first, second] = mdInstance.parse(`![bg](${url})\n\n---`)
Expand Down
4 changes: 2 additions & 2 deletions test/markdown/sweep.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('Marpit sweep plugin', () => {
const marpitStub = {
customDirectives: { global: {}, local: {} },
themeSet: new Map(),
options: { inlineSVG: false },
options: { backgroundSyntax: true, inlineSVG: false },
}

const markdown = md({ breaks: true })
Expand All @@ -29,7 +29,7 @@ describe('Marpit sweep plugin', () => {
.use(applyDirectives, marpitStub)
.use(inlineSVG, marpitStub)
.use(parseImage)
.use(backgroundImage)
.use(backgroundImage, marpitStub)

const $ = cheerio.load(
markdown.render(dedent`
Expand Down
67 changes: 62 additions & 5 deletions test/marpit.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,15 @@ describe('Marpit', () => {
context('with env argument', () => {
it('passes env option to markdown#render', () => {
const instance = new Marpit()
const render = jest.spyOn(instance.markdown, 'render')
const render = jest.spyOn(instance.markdown.renderer, 'render')

instance.render('Markdown', { env: 'env' })
expect(render).toBeCalledWith('Markdown', { env: 'env' })

expect(render).toBeCalledWith(
expect.any(Array),
instance.markdown.options,
{ env: 'env' }
)
})

context('when passed htmlAsArray prop as true', () => {
Expand Down Expand Up @@ -328,12 +333,17 @@ describe('Marpit', () => {
})

describe('#renderMarkdown', () => {
it('returns the result of markdown#render', () => {
it('returns the result of rendering', () => {
const instance = new Marpit()
const spy = jest.spyOn(instance.markdown, 'render').mockImplementation()
const spy = jest
.spyOn(instance.markdown.renderer, 'render')
.mockImplementation()

instance.renderMarkdown('render', { env: 'env' })
expect(spy).toBeCalledWith('render', { env: 'env' })

expect(spy).toBeCalledWith(expect.any(Array), instance.markdown.options, {
env: 'env',
})
})
})

Expand Down Expand Up @@ -364,5 +374,52 @@ describe('Marpit', () => {
expect(plugin).toBeCalledWith(instance.markdown, 'parameter')
expect(instance.markdown.extended).toBe('parameter')
})

describe('Enhancement of markdown-it plugin system', () => {
describe('StateCore#marpit', () => {
const controlMarpitPlugin = md =>
md.core.ruler.before('normalize', 'disable_marpit', state =>
state.marpit(state.env.marpit)
)

it('toggles enable or disable Marpit core rules', () => {
const marpitRule = jest.fn()
const mdRule = jest.fn()

const marpit = new Marpit()
.use(controlMarpitPlugin)
.use(md => md.core.ruler.after('normalize', 'test', marpitRule))

marpit.markdown.use(md =>
md.core.ruler.after('normalize', 'test', mdRule)
)

const retDisabled = marpit.render('', { marpit: false })
expect(marpitRule).not.toBeCalled()
expect(mdRule).toBeCalled()

expect(retDisabled.html).not.toContain('section')
expect(retDisabled.css).toBe('')
expect(retDisabled.comments).toStrictEqual([])

const retEnabled = marpit.render('', { marpit: true })
expect(marpitRule).toBeCalled()
expect(mdRule).toBeCalledTimes(2)

expect(retEnabled.html).toContain('section')
expect(retEnabled.css).not.toBe('')
expect(retEnabled.comments).toStrictEqual([[]])
})

it('allows control also in the instance of markdown-it', () => {
const md = new MarkdownIt()
.use(new Marpit().markdownItPlugins)
.use(controlMarpitPlugin)

expect(md.render('', { marpit: false })).not.toContain('section')
expect(md.render('', { marpit: true })).toContain('section')
})
})
})
})
})