diff --git a/README.md b/README.md index cfbcbac..e55bedf 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ A Metalsmith plugin to render markdown files to HTML, using [Marked](https://git ## Features - Compiles `.md` and `.markdown` files in `metalsmith.source()` to HTML. -- Enables rendering file metadata keys to HTML through the [keys option](#rendering-file-metadata) +- Enables rendering file or metalsmith metadata keys to HTML through the [keys option](#rendering-metadata) - Define a dictionary of markdown globalRefs (for links, images) available to all render targets - Supports using the markdown library of your choice through the [render option](#using-another-markdown-library) @@ -75,17 +75,28 @@ metalsmith.use( - `render` - Specify a custom render function with the signature `(source, engineOptions, context) => string`. `context` is an object with the signature `{ path:string, key:string }` where the `path` key contains the current file path, and `key` contains the target metadata key. - `engineOptions` Options to pass to the markdown engine (default [marked](https://github.com/markedjs/marked)) -### Rendering file metadata +### Rendering metadata -You can render markdown to HTML in file metadata keys by specifying the `keys` option. -The `keys` option also supports dot-delimited key-paths. +You can render markdown to HTML in file or metalsmith metadata keys by specifying the `keys` option. +The `keys` option also supports dot-delimited key-paths. You can also use [globalRefs within them](#defining-a-dictionary-of-markdown-globalrefs) ```js -metalsmith.use( - markdown({ - keys: ['html_desc', 'nested.data'] +metalsmith + .metadata({ + from_metalsmith_metadata: 'I _shall_ become **markdown** and can even use a [globalref][globalref_link]', + markdownRefs: { + globalref_link: 'https://johndoe.com' + } }) -) + .use( + markdown({ + keys: { + files: ['html_desc', 'nested.data'], + global: ['from_metalsmith_metadata'] + }, + globalRefs: 'markdownRefs' + }) + ) ``` You can even render all keys at a certain path by setting the `wildcard` option and using a globstar `*` in the keypaths. @@ -170,7 +181,7 @@ metalsith // eg in a markdown file: [My Twitter profile][twitter] .use(markdown({ globalRefs: 'global.links' })) // eg in a handlebars layout: {{ global.links.twitter }} - .use(layouts()) + .use(layouts({ pattern: '**/*.html' })) ``` ### Custom markdown rendering diff --git a/lib/index.d.ts b/lib/index.d.ts index 1f7057c..fd2422e 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -10,11 +10,15 @@ export type Render = (source: string, engineOptions: EngineOptions, contex export type Options = { /** - * - Key names of file metadata to render to HTML - can be nested + * - Array of file metadata key names or object with arrays of key names of file or global metadata key names to render to HTML - can be nested keypaths */ - keys?: string[]; + keys?: string[] | { + files: string[] + global: string[] + }; /** * - Expand `*` wildcards in keypaths + * @default false */ wildcard?: boolean; /** diff --git a/src/index.js b/src/index.js index 9ef4d0e..82dc5cb 100644 --- a/src/index.js +++ b/src/index.js @@ -23,7 +23,7 @@ function refsObjectToMarkdown(refsObject) { /** * @typedef Options - * @property {string[]} [keys] - Key names of file metadata to render to HTML - can be nested + * @property {string[]|{files: string[], global: string[]}} [keys] - Key names of file metadata to render to HTML - can be nested * @property {boolean} [wildcard=false] - Expand `*` wildcards in keypaths * @property {string|Object} [globalRefs] An object of `{ refname: 'link' }` pairs that will be made available for all markdown files and keys, * or a `metalsmith.metadata()` keypath containing such object @@ -33,7 +33,7 @@ function refsObjectToMarkdown(refsObject) { **/ const defaultOptions = { - keys: [], + keys: {}, wildcard: false, render: defaultRender, engineOptions: {}, @@ -52,10 +52,31 @@ function markdown(options = defaultOptions) { options = Object.assign({}, defaultOptions, options) } + if (Array.isArray(options.keys)) { + options.keys = { files: options.keys } + } + return function markdown(files, metalsmith, done) { const debug = metalsmith.debug('@metalsmith/markdown') const matches = metalsmith.match('**/*.{md,markdown}', Object.keys(files)) + function renderKeys(keys, prepend, target, path) { + if (options.wildcard) { + keys = expandWildcardKeypaths(target, keys, '*') + } + + keys.forEach((key) => { + const value = get(target, key) + if (typeof value === 'string') { + const context = path === 'metalsmith.metadata()' ? { key } : { path, key } + debug.info('Rendering key "%s" of target "%s"', key.join ? key.join('.') : key, path) + set(target, key, options.render(prepend + value, options.engineOptions, context)) + } else if (typeof value !== 'undefined') { + debug.warn('Couldn\'t render key "%s" of target "%s": not a string', key.join ? key.join('.') : key, path) + } + }) + } + const legacyEngineOptions = Object.keys(options).filter((opt) => !Object.keys(defaultOptions).includes(opt)) if (legacyEngineOptions.length) { debug.warn('Starting from version 2.0 marked engine options will need to be specified as options.engineOptions') @@ -101,25 +122,19 @@ function markdown(options = defaultOptions) { }) data.contents = Buffer.from(str) - let keys = options.keys - if (options.wildcard) { - keys = expandWildcardKeypaths(data, options.keys, '*') - } - keys.forEach((key) => { - const value = get(data, key) - if (typeof value === 'string') { - debug.info('Rendering key "%s" of file "%s"', key.join ? key.join('.') : key, file) - set(data, key, options.render(globalRefsMarkdown + value, options.engineOptions, { path: file, key })) - // log a warning if the key is defined and of an unexpected type, but not if the property simply is not defined - } else if (typeof value !== 'undefined') { - debug.warn('Couldn\'t render key "%s" of file "%s": not a string', key.join ? key.join('.') : key, file) - } - }) + const keys = options.keys && options.keys.files ? options.keys.files : [] + renderKeys(keys, globalRefsMarkdown, data, file) delete files[file] files[html] = data }) + if (options.keys && options.keys.global) { + debug.info('Processing metalsmith.metadata()') + const meta = metalsmith.metadata() + renderKeys(options.keys.global, globalRefsMarkdown, meta, 'metalsmith.metadata()') + } + done() } } diff --git a/test/index.js b/test/index.js index 31c0289..b50a58b 100644 --- a/test/index.js +++ b/test/index.js @@ -272,7 +272,7 @@ describe('@metalsmith/markdown', function () { if (err) done(err) try { assert.deepStrictEqual(output.slice(0, 1), [ - ['warn', 'Couldn\'t render key "%s" of file "%s": not a string', 'not_a_string', 'index.md'] + ['warn', 'Couldn\'t render key "%s" of target "%s": not a string', 'not_a_string', 'index.md'] ]) done() } catch (err) { @@ -326,6 +326,37 @@ describe('@metalsmith/markdown', function () { }) }) + it('should render keys in metalsmith.metadata()', function (done) { + const ms = msCommon('test/fixtures/basic') + ms.env('DEBUG', '@metalsmith/mardown*') + .metadata({ + markdownRefs: { + defined_link: 'https://globalref.io' + }, + has_markdown: '**[globalref_link][defined_link]**' + }) + .use( + markdown({ + keys: { + global: ['has_markdown'] + }, + globalRefs: 'markdownRefs' + }) + ) + .process((err) => { + if (err) done(err) + try { + assert.strictEqual( + ms.metadata().has_markdown, + '

globalref_link

\n' + ) + done() + } catch (err) { + done(err) + } + }) + }) + it('expandWildCardKeyPath should throw if root is not an object', function () { try { expandWildcardKeypath(null, [], '*')