markdown_directives_parse.js
/** @module */
import MarkdownItFrontMatter from 'markdown-it-front-matter'
import marpitPlugin from '../../plugin'
import { markAsParsed } from '../comment'
import * as directives from './directives'
import { yaml } from './yaml'
const isDirectiveComment = (token) =>
token.type === 'marpit_comment' && token.meta.marpitParsedDirectives
/**
* Parse Marpit directives and store result to the slide token meta.
*
* Marpit comment plugin ans slide plugin requires already loaded to
* markdown-it instance.
*
* @function parse
* @param {MarkdownIt} md markdown-it instance.
* @param {Object} [opts]
* @param {boolean} [opts.frontMatter=true] Switch feature to support YAML
* front-matter. If true, you can use Jekyll style directive setting to the
* first page.
*/
function _parse(md, opts = {}) {
const { marpit } = md
const applyBuiltinDirectives = (newProps, builtinDirectives) => {
let ret = {}
for (const prop of Object.keys(newProps)) {
if (builtinDirectives[prop]) {
ret = { ...ret, ...builtinDirectives[prop](newProps[prop], marpit) }
} else {
ret[prop] = newProps[prop]
}
}
return ret
}
// Front-matter support
const frontMatter = opts.frontMatter === undefined ? true : !!opts.frontMatter
let frontMatterObject = {}
if (frontMatter) {
md.core.ruler.before('block', 'marpit_directives_front_matter', (state) => {
frontMatterObject = {}
if (!state.inlineMode) marpit.lastGlobalDirectives = {}
})
md.use(MarkdownItFrontMatter, (fm) => {
frontMatterObject.text = fm
const parsed = yaml(
fm,
marpit.options.looseYAML
? [
...Object.keys(marpit.customDirectives.global),
...Object.keys(marpit.customDirectives.local),
]
: false,
)
if (parsed !== false) frontMatterObject.yaml = parsed
})
}
// Parse global directives
md.core.ruler.after('inline', 'marpit_directives_global_parse', (state) => {
if (state.inlineMode) return
let globalDirectives = {}
const applyDirectives = (obj) => {
let recognized = false
for (const key of Object.keys(obj)) {
if (directives.globals[key]) {
recognized = true
globalDirectives = {
...globalDirectives,
...directives.globals[key](obj[key], marpit),
}
} else if (marpit.customDirectives.global[key]) {
recognized = true
globalDirectives = {
...globalDirectives,
...applyBuiltinDirectives(
marpit.customDirectives.global[key](obj[key], marpit),
directives.globals,
),
}
}
}
return recognized
}
if (frontMatterObject.yaml) applyDirectives(frontMatterObject.yaml)
for (const token of state.tokens) {
if (
isDirectiveComment(token) &&
applyDirectives(token.meta.marpitParsedDirectives)
) {
markAsParsed(token, 'directive')
} else if (token.type === 'inline') {
for (const t of token.children) {
if (
isDirectiveComment(t) &&
applyDirectives(t.meta.marpitParsedDirectives)
)
markAsParsed(t, 'directive')
}
}
}
marpit.lastGlobalDirectives = { ...globalDirectives }
})
// Parse local directives and apply meta to slide
md.core.ruler.after('marpit_slide', 'marpit_directives_parse', (state) => {
if (state.inlineMode) return
const slides = []
const cursor = { slide: undefined, local: {}, spot: {} }
const applyDirectives = (obj) => {
let recognized = false
for (const key of Object.keys(obj)) {
if (directives.locals[key]) {
recognized = true
cursor.local = {
...cursor.local,
...directives.locals[key](obj[key], marpit),
}
} else if (marpit.customDirectives.local[key]) {
recognized = true
cursor.local = {
...cursor.local,
...applyBuiltinDirectives(
marpit.customDirectives.local[key](obj[key], marpit),
directives.locals,
),
}
}
// Spot directives
// (Apply local directive to only current slide by prefix "_")
if (key.startsWith('_')) {
const spotKey = key.slice(1)
if (directives.locals[spotKey]) {
recognized = true
cursor.spot = {
...cursor.spot,
...directives.locals[spotKey](obj[key], marpit),
}
} else if (marpit.customDirectives.local[spotKey]) {
recognized = true
cursor.spot = {
...cursor.spot,
...applyBuiltinDirectives(
marpit.customDirectives.local[spotKey](obj[key], marpit),
directives.locals,
),
}
}
}
}
return recognized
}
if (frontMatterObject.yaml) applyDirectives(frontMatterObject.yaml)
for (const token of state.tokens) {
if (token.meta && token.meta.marpitSlideElement === 1) {
// Initialize Marpit directives meta
token.meta.marpitDirectives = {}
slides.push(token)
cursor.slide = token
} else if (token.meta && token.meta.marpitSlideElement === -1) {
// Assign local and spot directives to meta
cursor.slide.meta.marpitDirectives = {
...cursor.slide.meta.marpitDirectives,
...cursor.local,
...cursor.spot,
}
cursor.spot = {}
} else if (
isDirectiveComment(token) &&
applyDirectives(token.meta.marpitParsedDirectives)
) {
markAsParsed(token, 'directive')
} else if (token.type === 'inline') {
for (const t of token.children) {
if (
isDirectiveComment(t) &&
applyDirectives(t.meta.marpitParsedDirectives)
)
markAsParsed(t, 'directive')
}
}
}
// Assign global directives to meta
for (const token of slides)
token.meta.marpitDirectives = {
...token.meta.marpitDirectives,
...marpit.lastGlobalDirectives,
}
})
}
export const parse = marpitPlugin(_parse)
export default parse