Skip to content

Commit

Permalink
Add style plugins for appending additional styles to theme (#25)
Browse files Browse the repository at this point in the history
* Add inline style elements plugin

* Add test case of inline style elements plugin (ignore in code)

* Re-implement style elements plugin as a block rule

* Fix style elements plugin to allow rough style element

* Rename style elements plugin to style parse plugin

* Add inlineStyle option to Marpit class

* Add style assign plugin to assign parsed styles to property

* Add test case of style assign plugin with #renderInline

* Add appendStyle option to ThemeSet#pack

* Add test case of inlineStyle option on Marpit class

* Improve JSDoc

* Update README.md to add the section how to tweak theme in Markdown

* Update README.md to add note about tweaking slide size

* Ignore invalid inline styles

* [ci skip] Update CHANGELOG.md
  • Loading branch information
yhatt authored May 27, 2018
1 parent b9d5c6a commit 79d61e0
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

* Add `header` and `footer` directives ([#22](https://github.com/marp-team/marpit/pull/22))
* Support importing other theme CSS with `@import` (or `@import-theme`) ([#24](https://github.com/marp-team/marpit/pull/24))
* Support tweaking theme style through `<style>` element or `style` global directive ([#25](https://github.com/marp-team/marpit/pull/25))
* Add PostCSS import rollup plugin to work `@charset` and `@import` at-rules correctly ([#26](https://github.com/marp-team/marpit/pull/26))
* Change role of pagination layer to pseudo layer on advanced background ([#27](https://github.com/marp-team/marpit/pull/27))
* Hide `section::after` pseudo-element without pagination ([#29](https://github.com/marp-team/marpit/pull/29))
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ section {

Please notice _these must define a length in **an absolute unit.**_ We support `cm`, `in`, `mm`, `pc`, `pt`, and `px`.

> :warning: Currently, you cannot tweak slide size through [`<style>` elements](#tweak-theme-in-markdown) or [`style` global directive](#style-global-directive).
##### Styling paginations

You can style the page number through `section::after` pseudo-element. (It is shown by `paginate` local directive)
Expand Down Expand Up @@ -420,6 +422,42 @@ section {

`@import-theme` can place on anywhere of the root of CSS, and the imported contents is inserted to the beginning of CSS in order.

#### Tweak theme in Markdown

You can tweak the theme's style in Markdown. Marpit parses `<style>` HTML element in the context of as same as a theme.

```markdown
---
theme: base
---

<style>
section {
background: yellow;
}
</style>

# Tweak theme through the `style` element

You would see a yellow slide.
```

> :information_source: By default, `<style>` elements will not render in HTML because of processing to bundle additional CSS with theme.
>
> You can set `inlineStyle: false` in Marpit constructor option to disable bundling the style elements. In this case, it follows `html` markdown-it option whether render `<style>` as HTML element. Marpit would NOT apply post-processing even though the raw style was rendered as HTML. (e.g. scoping, import theme, etc.)
##### `style` global directive

Instead of a `<style>` element, you can use a [global directive](#directives) too. It could prevent additional styling in the other Markdown editor.

```yaml
theme: base
style: |
section {
background: yellow;
}
```
#### Theme set
The `Marpit` instance has a `themeSet` member that manages usable themes in the `theme` directive of Marpit Markdown. You have to add theme CSS by using `themeSet.add(string)`.
Expand Down
4 changes: 4 additions & 0 deletions src/markdown/directives/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@
* You can use prefix `$` as the name of a directive for the clarity (or
* compatibility with the old version of Marp).
*
* @prop {Directive} style Specify the CSS style to apply additionally.
* @prop {Directive} theme Specify theme of the slide deck.
*/
export const globals = {
style(value) {
return { style: value }
},
theme(value, marpit) {
if (!marpit.themeSet.has(value)) return {}
return { theme: value }
Expand Down
26 changes: 26 additions & 0 deletions src/markdown/style/assign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @module */

/**
* Marpit style assign plugin.
*
* Assign style global directive and parsed styles to Marpit instance's
* `lastStyles' property.
*
* @alias module:markdown/style/assign
* @param {MarkdownIt} md markdown-it instance.
* @param {Marpit} marpit Marpit instance.
*/
function assign(md, marpit) {
md.core.ruler.push('marpit_style_assign', state => {
if (state.inlineMode) return

const directives = marpit.lastGlobalDirectives || {}
marpit.lastStyles = directives.style ? [directives.style] : []

state.tokens.forEach(token => {
if (token.type === 'marpit_style') marpit.lastStyles.push(token.content)
})
})
}

export default assign
72 changes: 72 additions & 0 deletions src/markdown/style/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/** @module */
const styleMatcher = /<style[\s\S]*?>([\s\S]*?)<\/style>/i
const styleMatcherOpening = /^<style(?=(\s|>|$))/i
const styleMatcherClosing = /<\/style>/i

/**
* Marpit style parse plugin.
*
* Parse `<style>` elements as the hidden `marpit_style` token. The parsed style
* will use in {@link ThemeSet#pack} to append the style additionally.
*
* `<style>` elements will strip regardless of html setting provided by
* markdown-it.
*
* @alias module:markdown/style/parse
* @param {MarkdownIt} md markdown-it instance.
* @param {Marpit} marpit Marpit instance.
*/
function parse(md, marpit) {
/**
* Based on markdown-it html_block rule
* https://github.com/markdown-it/markdown-it/blob/master/lib/rules_block/html_block.js
*/
md.block.ruler.before(
'html_block',
'marpit_style_parse',
(state, startLine, endLine, silent) => {
if (!marpit.options.inlineStyle) return false

// Fast fail
let pos = state.bMarks[startLine] + state.tShift[startLine]
if (state.src.charCodeAt(pos) !== 0x3c) return false

let max = state.eMarks[startLine]
let line = state.src.slice(pos, max)

// Match to opening element
if (!styleMatcherOpening.test(line)) return false
if (silent) return true

// Parse ending element
let nextLine = startLine + 1
if (!styleMatcherClosing.test(line)) {
while (nextLine < endLine) {
if (state.sCount[nextLine] < state.blkIndent) break

pos = state.bMarks[nextLine] + state.tShift[nextLine]
max = state.eMarks[nextLine]
line = state.src.slice(pos, max)
nextLine += 1

if (styleMatcherClosing.test(line)) break
}
}

state.line = nextLine

// Create token
const token = state.push('marpit_style', '', 0)
token.map = [startLine, nextLine]
token.markup = state.getLines(startLine, nextLine, state.blkIndent, true)
token.hidden = true

const matchedContent = styleMatcher.exec(token.markup)
token.content = matchedContent ? matchedContent[1].trim() : ''

return true
}
)
}

export default parse
9 changes: 9 additions & 0 deletions src/marpit.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import marpitParseDirectives from './markdown/directives/parse'
import marpitParseImage from './markdown/parse_image'
import marpitSlide from './markdown/slide'
import marpitSlideContainer from './markdown/slide_container'
import marpitStyleAssign from './markdown/style/assign'
import marpitStyleParse from './markdown/style/parse'
import marpitSweep from './markdown/sweep'
import marpitUnicodeEmoji from './markdown/unicode_emoji'

const defaultOptions = {
backgroundSyntax: true,
container: marpitContainer,
filters: true,
inlineStyle: true,
markdown: 'commonmark',
printable: true,
slideContainer: undefined,
Expand All @@ -42,6 +45,9 @@ class Marpit {
* element(s) wrapping whole slide deck.
* @param {boolean} [opts.filters=true] Support filter syntax for markdown
* image. It can apply to inline image and the advanced backgrounds.
* @param {boolean} [opts.inlineStyle=true] Recognize `<style>` elements to
* append additional styles to theme. When it is `true`, Marpit will parse
* style regardless markdown-it's `html` option.
* @param {string|Object|Array} [opts.markdown='commonmark'] markdown-it
* initialize option(s).
* @param {boolean} [opts.printable=true] Make style printable to PDF.
Expand Down Expand Up @@ -87,6 +93,7 @@ class Marpit {
applyMarkdownItPlugins(md = this.markdown) {
md
.use(marpitComment)
.use(marpitStyleParse, this)
.use(marpitSlide)
.use(marpitParseDirectives, this)
.use(marpitApplyDirectives)
Expand All @@ -97,6 +104,7 @@ class Marpit {
.use(marpitUnicodeEmoji)
.use(marpitSweep)
.use(marpitInlineSVG, this)
.use(marpitStyleAssign, this)

if (this.options.backgroundSyntax) md.use(marpitBackgroundImage)
}
Expand Down Expand Up @@ -145,6 +153,7 @@ class Marpit {
*/
renderStyle(theme) {
return this.themeSet.pack(theme, {
appendStyle: this.lastStyles && this.lastStyles.join('\n'),
containers: [...this.containers, ...this.slideContainers],
inlineSVG: this.options.inlineSVG,
printable: this.options.printable,
Expand Down
9 changes: 9 additions & 0 deletions src/theme_set.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class ThemeSet {
* @param {string} name The theme name. It will use the instance's default
* theme or scaffold theme when a specific named theme does not exist.
* @param {Object} [opts] The option object passed by {@link Marpit#render}.
* @param {string} [opts.appendStyle] A CSS string to append into theme.
* @param {Element[]} [opts.containers] Container elements wrapping whole
* slide deck.
* @param {boolean} [opts.printable] Make style printable to PDF.
Expand All @@ -184,8 +185,16 @@ class ThemeSet {
if (opts.inlineSVG)
slideElements.unshift({ tag: 'svg' }, { tag: 'foreignObject' })

let appendStyle
try {
appendStyle = postcss.parse(opts.appendStyle, { from: undefined })
} catch (e) {
// Ignore invalid style
}

const packer = postcss(
[
appendStyle && (css => css.last.after(appendStyle)),
postcssImportReplace(this),
opts.printable &&
postcssPrintable({
Expand Down
95 changes: 95 additions & 0 deletions test/markdown/style/assign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import assert from 'assert'
import dedent from 'dedent'
import MarkdownIt from 'markdown-it'
import applyDirectives from '../../../src/markdown/directives/apply'
import comment from '../../../src/markdown/comment'
import parseDirectives from '../../../src/markdown/directives/parse'
import slide from '../../../src/markdown/slide'
import styleAssign from '../../../src/markdown/style/assign'
import styleParse from '../../../src/markdown/style/parse'

describe('Marpit style assign plugin', () => {
const marpitStub = (...opts) => ({
options: { inlineStyle: true },
themeSet: new Map(),
...opts,
})

context('with inline style elements', () => {
const md = marpit =>
new MarkdownIt('commonmark')
.use(styleParse, marpit)
.use(styleAssign, marpit)

it('assigns parsed styles to Marpit lastStyles property', () => {
const marpit = marpitStub()
md(marpit).render('<style>b { color: red; }</style>')

assert.deepStrictEqual(marpit.lastStyles, ['b { color: red; }'])
})

it('ignores parsing style in #renderInline', () => {
const marpit = marpitStub()
const text = '<style>b { color: red; }</style>'

assert(md(marpit).renderInline(text) === text)
assert(!marpit.lastStyles)
})
})

context('with style global directive', () => {
const md = marpit =>
new MarkdownIt('commonmark')
.use(comment)
.use(slide)
.use(parseDirectives, marpit)
.use(applyDirectives)
.use(styleAssign, marpit)

it('assigns parsed style global directive to Marpit lastStyles property', () => {
const marpit = marpitStub()
md(marpit).render(dedent`
<!--
style: "b { color: red; }"
-->
`)

assert.deepStrictEqual(marpit.lastStyles, ['b { color: red; }'])
})
})

context('with muiltiple style elements and a style directive', () => {
const md = marpit =>
new MarkdownIt('commonmark')
.use(comment)
.use(styleParse, marpit)
.use(slide)
.use(parseDirectives, marpit)
.use(applyDirectives)
.use(styleAssign, marpit)

it('assigns inline styles prior to directive style', () => {
const marpit = marpitStub()
md(marpit).render(dedent`
<style>
h2 { font-size: 2em; }
</style>
<style>
h3 { font-size: 1em; }
</style>
<!--
style: |-
h1 { font-size: 3em; }
-->
`)

assert.deepStrictEqual(marpit.lastStyles, [
'h1 { font-size: 3em; }',
'h2 { font-size: 2em; }',
'h3 { font-size: 1em; }',
])
})
})
})
Loading

0 comments on commit 79d61e0

Please sign in to comment.