diff --git a/CHANGELOG.md b/CHANGELOG.md index c92bdffd..2afbd94a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,19 @@ ## [Unreleased] +### Added + +- Documentation of [custom directives](https://marpit.marp.app/directives?id=custom-directives) ([#183](https://github.com/marp-team/marpit/pull/183)) + ### Changed +- Allow aliasing from custom directive to built-in directives ([#183](https://github.com/marp-team/marpit/pull/183)) - Upgrade dependent packages to the latest version ([#184](https://github.com/marp-team/marpit/pull/184)) +### Deprecated + +- Dollar prefix for global directive ([#182](https://github.com/marp-team/marpit/issues/182), [#183](https://github.com/marp-team/marpit/pull/183)) + ## v1.3.0 - 2019-07-11 ### Added diff --git a/docs/directives.md b/docs/directives.md index 79871adb..0e473831 100644 --- a/docs/directives.md +++ b/docs/directives.md @@ -30,7 +30,7 @@ paginate: true --- ``` -?> Please not confuse to the ruler for paging slides. The actual slide contents would start after the ending ruler of front-matter. +Please not confuse to the ruler for paging slides. The actual slide contents would start after the ending ruler of front-matter. ## Type of directives @@ -38,11 +38,7 @@ paginate: true Global directives are _the setting value of the whole slide deck_, like `theme`. Marpit recognizes only the last value if you wrote a same global directives many times. -You may use prefix `$` to the name of global directives for clarity type. - -```markdown - -``` +!> We used to support `$` prefix for global directives (``), but it has marked as deprecated in v1.3.1. Developer may re-define dollar-prefixed [custom directives](#custom-directives) as an alias to built-in directive if necessary. ### Local directives {docsify-ignore} @@ -65,7 +61,7 @@ If you want to apply local directives only to current page, you have to use pref ```markdown -Add underbar prefix `_` to the name of local directives. +Add underscore prefix `_` to the name of local directives. --- @@ -320,3 +316,61 @@ In addition, we have supported customize for these declarations: - `color` ?> It also can use [extended image syntax](/image-syntax#slide-backgrounds) if you want to set image or color as background to single page. + +## Advanced + +### Custom directives + +Developer can extend recognizable directives. For example, [Marp Core](https://github.com/marp-team/marp-core) has extended `size` global directive to change slide size in Markdown. [Marp CLI](https://github.com/marp-team/marp-cli) will add directives for setting [meta properties of converted HTML](https://github.com/marp-team/marp-cli#metadata). + +Marpit instance has [`customDirectives.global` and `customDirectives.local` object](https://marpit-api.marp.app/marpit#customDirectives) to allow adding directives as you like. + +#### Custom global directive + +The following example is defining dollar-prefixed alias of built-in [`theme` global directive](#theme). + +```javascript +marpit.customDirectives.global.$theme = (value, marpit) => { + return { theme: value } +} +``` + +Please define a function to handle passed value from Markdown. The first argument is the passed value(s), and the second is the current Marpit instance. It should return an object includes pairs of key-value for passing to same kind directives. + +#### Custom local directive + +Custom directives also can provide a way of assigning multiple same kind directives at once. Let's define `colorPreset` local directive for assigning preset of slide colors. + +```javascript +marpit.customDirectives.local.colorPreset = (value, marpit) => { + switch (value) { + case 'sunset': + return { backgroundColor: '#e62e00', color: '#fffff2' } + case 'dark': + return { backgroundColor: '#303033', color: '#f8f8ff' } + default: + // Return an empty object if not have to assign new values + return {} + } +} +``` + +Now you can use the defined `colorPreset` local directive with same way of built-in local directives. The underscore prefix (`_colorPreset`) for applying preset to single slide also works well. + +```markdown + + +# Sunset color preset + +--- + + + +# Dark color preset + +--- + +# Sunset color preset +``` + +?> The returned key-value will assign to `marpitDirectives` property in [`meta` object](https://markdown-it.github.io/markdown-it/#Token.prototype.meta) of predetermined markdown-it token(s) by the kind of directive. It would be useful for using assigned value in [markdown-it plugin](./usage.md#extend-marpit-by-plugins). diff --git a/src/markdown/directives/directives.js b/src/markdown/directives/directives.js index 93523e89..2d340191 100644 --- a/src/markdown/directives/directives.js +++ b/src/markdown/directives/directives.js @@ -16,14 +16,11 @@ * Each global directive assigns to the whole slide deck. If you wrote a same * directive many times, Marpit only recognizes the last value. * - * You can use prefix `$` as the name of a directive for the clarity (or - * compatibility with the old version of Marp). - * * @prop {Directive} headingDivider Specify heading divider option. * @prop {Directive} style Specify the CSS style to apply additionally. * @prop {Directive} theme Specify theme of the slide deck. */ -export const globals = { +export const globals = Object.assign(Object.create(null), { headingDivider: value => { const headings = [1, 2, 3, 4, 5, 6] const toInt = v => @@ -44,7 +41,7 @@ export const globals = { }, style: v => ({ style: v }), theme: (v, marpit) => (marpit.themeSet.has(v) ? { theme: v } : {}), -} +}) /** * Local directives. @@ -71,7 +68,7 @@ export const globals = { * a `
` element to the first of each slide contents. * @prop {Directive} paginate Show page number on the slide if you set `true`. */ -export const locals = { +export const locals = Object.assign(Object.create(null), { backgroundColor: v => ({ backgroundColor: v }), backgroundImage: v => ({ backgroundImage: v }), backgroundPosition: v => ({ backgroundPosition: v }), @@ -82,6 +79,6 @@ export const locals = { footer: v => (typeof v === 'string' ? { footer: v } : {}), header: v => (typeof v === 'string' ? { header: v } : {}), paginate: v => ({ paginate: (v || '').toLowerCase() === 'true' }), -} +}) export default [...Object.keys(globals), ...Object.keys(locals)] diff --git a/src/markdown/directives/parse.js b/src/markdown/directives/parse.js index decf77ee..a49a29e2 100644 --- a/src/markdown/directives/parse.js +++ b/src/markdown/directives/parse.js @@ -4,6 +4,14 @@ import yaml from './yaml' import * as directives from './directives' import marpitPlugin from '../marpit_plugin' +const isComment = token => + token.type === 'marpit_comment' && token.meta.marpitParsedDirectives + +const markAsParsed = token => { + token.meta = token.meta || {} + token.meta.marpitCommentParsed = 'directive' +} + /** * Parse Marpit directives and store result to the slide token meta. * @@ -20,6 +28,20 @@ import marpitPlugin from '../marpit_plugin' 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 = {} @@ -45,25 +67,6 @@ function parse(md, opts = {}) { }) } - const isComment = token => - token.type === 'marpit_comment' && token.meta.marpitParsedDirectives - - const markAsParsed = token => { - token.meta = token.meta || {} - token.meta.marpitCommentParsed = 'directive' - } - - const filterBuiltinDirective = newProps => { - const ret = {} - - for (const prop of Object.keys(newProps).filter( - p => !directives.default.includes(p) - )) - ret[prop] = newProps[prop] - - return ret - } - // Parse global directives md.core.ruler.after('inline', 'marpit_directives_global_parse', state => { if (state.inlineMode) return @@ -73,7 +76,18 @@ function parse(md, opts = {}) { let recognized = false for (const key of Object.keys(obj)) { - const globalKey = key.startsWith('$') ? key.slice(1) : key + const globalKey = key.startsWith('$') + ? (() => { + if (marpit.customDirectives.global[key]) return key + + console.warn( + `Deprecation warning: Dollar prefix support for global directive "${key}" is deprecated and will remove soon. Just remove "$" from "${key}" to fix ("${key.slice( + 1 + )}").` + ) + return key.slice(1) + })() + : key if (directives.globals[globalKey]) { recognized = true @@ -85,8 +99,9 @@ function parse(md, opts = {}) { recognized = true globalDirectives = { ...globalDirectives, - ...filterBuiltinDirective( - marpit.customDirectives.global[globalKey](obj[key], marpit) + ...applyBuiltinDirectives( + marpit.customDirectives.global[globalKey](obj[key], marpit), + directives.globals ), } } @@ -135,8 +150,9 @@ function parse(md, opts = {}) { recognized = true cursor.local = { ...cursor.local, - ...filterBuiltinDirective( - marpit.customDirectives.local[key](obj[key], marpit) + ...applyBuiltinDirectives( + marpit.customDirectives.local[key](obj[key], marpit), + directives.locals ), } } @@ -156,8 +172,9 @@ function parse(md, opts = {}) { recognized = true cursor.spot = { ...cursor.spot, - ...filterBuiltinDirective( - marpit.customDirectives.local[spotKey](obj[key], marpit) + ...applyBuiltinDirectives( + marpit.customDirectives.local[spotKey](obj[key], marpit), + directives.locals ), } } diff --git a/src/marpit.js b/src/marpit.js index e834a3a7..681922c4 100644 --- a/src/marpit.js +++ b/src/marpit.js @@ -65,7 +65,7 @@ class Marpit { * value of options after creating instance. * * @member {Object} options - * @memberOf Marpit + * @memberOf Marpit# * @readonly */ Object.defineProperty(this, 'options', { @@ -82,11 +82,14 @@ class Marpit { * token. * * @member {Object} customDirectives - * @memberOf Marpit + * @memberOf Marpit# * @readonly */ Object.defineProperty(this, 'customDirectives', { - value: Object.seal({ global: {}, local: {} }), + value: Object.seal({ + global: Object.create(null), + local: Object.create(null), + }), }) /** diff --git a/test/markdown/directives/parse.js b/test/markdown/directives/parse.js index 1b4daa58..32340c86 100644 --- a/test/markdown/directives/parse.js +++ b/test/markdown/directives/parse.js @@ -86,7 +86,7 @@ describe('Marpit directives parse plugin', () => { expect(marpitStub.lastGlobalDirectives).toStrictEqual({}) }) - it('allows global directive name prefixed "$"', () => { + it('allows global directive name prefixed "$" [DEPRECATED]', () => { md().parse('') expect(marpitStub.lastGlobalDirectives).toStrictEqual(expected) }) diff --git a/test/marpit.js b/test/marpit.js index 2b02750b..50469590 100644 --- a/test/marpit.js +++ b/test/marpit.js @@ -92,15 +92,35 @@ describe('Marpit', () => { expect(token.meta.marpitDirectives).toStrictEqual({ class: 'ok' }) }) - it('cannot assign built-in directive as meta', () => { + it('can assign built-in directive as alias', () => { + const $theme = jest.fn(v => ({ theme: v })) const marpit = new Marpit({ container: undefined }) + + marpit.themeSet.add('/* @theme foobar */') + marpit.customDirectives.global.$theme = $theme marpit.customDirectives.local.test = v => ({ test: v, class: v }) - const [first, , , second] = marpit.markdown.parse( + // Global directive (Dollar prefix) + marpit.markdown.render('') + expect($theme).toBeCalledWith('foobar', marpit) + expect(marpit.lastGlobalDirectives.theme).toBe('foobar') + + marpit.markdown.render('') + expect($theme).toBeCalledWith('unknown', marpit) + expect(marpit.lastGlobalDirectives.theme).toBeUndefined() + + // Local directive (Alias + internal meta) + const [localFirst, , , localSecond] = marpit.markdown.parse( '\n***\n' ) - expect(first.meta.marpitDirectives).toStrictEqual({ test: 'local' }) - expect(second.meta.marpitDirectives).toStrictEqual({ test: 'spot' }) + expect(localFirst.meta.marpitDirectives).toStrictEqual({ + test: 'local', + class: 'local', + }) + expect(localSecond.meta.marpitDirectives).toStrictEqual({ + test: 'spot', + class: 'spot', + }) }) context('with looseYAML option as true', () => {