Coder Social home page Coder Social logo

markdown's Introduction

@metalsmith/markdown

A Metalsmith plugin to render markdown files to HTML, using Marked (by default).

metalsmith: core plugin npm: version ci: build code coverage license: MIT

Features

  • Compiles .md and .markdown files in metalsmith.source() to HTML.
  • Enables rendering file or metalsmith metadata keys to HTML through the keys option
  • 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

Installation

NPM:

npm install @metalsmith/markdown

Yarn:

yarn add @metalsmith/markdown

Usage

@metalsmith/markdown is powered by Marked (by default), and you can pass any of the Marked options to it, including the 'pro' options: renderer, tokenizer, walkTokens and extensions.

import markdown from '@metalsmith/markdown'
import hljs from 'highlight.js'

// use defaults
metalsmith.use(markdown())

// use explicit defaults
metalsmith.use({
  wildcard: false,
  keys: [],
  engineOptions: {}
})

// custom
metalsmith.use(
  markdown({
    engineOptions: {
      highlight: function (code) {
        return hljs.highlightAuto(code).value
      },
      pedantic: false,
      gfm: true,
      tables: true,
      breaks: false,
      sanitize: false,
      smartLists: true,
      smartypants: false,
      xhtml: false
    }
  })
)

@metalsmith/markdown provides the following options:

  • keys: Key names of file metadata to render to HTML in addition to its contents - can be nested key paths
  • wildcard (default: false) - Expand * wildcards in keys option keypaths
  • globalRefs - An object of { refname: 'link' } pairs that will be available for all markdown files and keys, or a metalsmith.metadata() keypath containing such object
  • 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)

Rendering metadata

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

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.
This is especially useful for arrays like the faq below:

metalsmith.use(
  markdown({
    wildcard: true,
    keys: ['html_desc', 'nested.data', 'faq.*.*']
  })
)

A file page.md with front-matter:

---
html_desc: A **markdown-enabled** _description_
nested:
  data: '#metalsmith'
faq:
  - q: '**Question1?**'
    a: _answer1_
  - q: '**Question2?**'
    a: _answer2_
---

would be transformed into:

{
  "html_desc": "A <strong>markdown-enabled</strong> <em>description</em>\n",
  "nested": {
    "data": "<h1 id=\"metalsmith\">metalsmith</h1>\n"
  },
  "faq": [
    { "q": "<p><strong>Question1?</strong></p>\n", "a": "<p><em>answer1</em></p>\n" },
    { "q": "<p><strong>Question2?</strong></p>\n", "a": "<p><em>answer2</em></p>\n" }
  ]
}

Notes about the wildcard

  • It acts like the single bash globstar. If you specify * this would only match the properties at the first level of the metadata.
  • If a wildcard keypath matches a key whose value is not a string, it will be ignored.
  • It is set to false by default because it can incur some overhead if it is applied too broadly.

Defining a dictionary of markdown globalRefs

Markdown allows users to define links in reference style ([]:).
In a Metalsmith build it may be especially desirable to be able to refer to some links globally. The globalRefs options allows this:

metalsmith.use(
  markdown({
    globalRefs: {
      twitter_link: 'https://twitter.com/johndoe',
      github_link: 'https://github.com/johndoe',
      photo: '/assets/img/me.png'
    }
  })
)

Now contents of any file or metadata key processed by @metalsmith/markdown will be able to refer to these links as [My Twitter][twitter_link] or ![Me][photo]. You can also store the globalRefs object of the previous example in a metalsmith.metadata() key and pass its keypath as globalRefs option instead.

This enables a flow where you can load the refs into global metadata from a source file with @metalsmith/metadata, and use them both in markdown and templating plugins like @metalsmith/layouts:

metalsith
  .metadata({
    global: {
      links: {
        twitter: 'https://twitter.com/johndoe',
        github: 'https://github.com/johndoe'
      }
    }
  })
  // eg in a markdown file: [My Twitter profile][twitter]
  .use(markdown({ globalRefs: 'global.links' }))
  // eg in a handlebars layout: {{ global.links.twitter }}
  .use(layouts({ pattern: '**/*.html' }))

Custom markdown rendering

You can use a custom renderer by using marked.Renderer()

import markdown from '@metalsmith/markdown'
import { marked } from 'marked'
const markdownRenderer = new marked.Renderer()

markdownRenderer.image = function (href, title, text) {
  return `
  <figure>
    <img src="${href}" alt="${title}" title="${title}" />
    <figcaption>
      <p>${text}</p>
    </figcaption>
  </figure>`
}

metalsmith.use(
  markdown({
    engineOptions: {
      renderer: markdownRenderer,
      pedantic: false,
      gfm: true,
      tables: true,
      breaks: false,
      sanitize: false,
      smartLists: true,
      smartypants: false,
      xhtml: false
    }
  })
)

Using another markdown library

If you don't want to use marked, you can use another markdown rendering library through the render option. For example, this is how you could use markdown-it instead:

import MarkdownIt from 'markdown-it'

let markdownIt
metalsmith.use(markdown({
  render(source, opts, context) {
    if (!markdownIt) markdownIt = new MarkdownIt(opts)
    if (context.key == 'contents') return mdIt.render(source)
    return markdownIt.renderInline(source)
  },
  // specify markdownIt options here
  engineOptions: { ... }
}))

Debug

To enable debug logs, set the DEBUG environment variable to @metalsmith/markdown*:

metalsmith.env('DEBUG', '@metalsmith/markdown*')

CLI Usage

Add @metalsmith/markdown key to your metalsmith.json plugins key

{
  "plugins": {
    "@metalsmith/markdown": {
      "engineOptions": {
        "pedantic": false,
        "gfm": true,
        "tables": true,
        "breaks": false,
        "sanitize": false,
        "smartLists": true,
        "smartypants": false,
        "xhtml": false
      }
    }
  }
}

License

MIT

markdown's People

Contributors

blakeembrey avatar datesss avatar dependabot[bot] avatar emergentbit avatar haohui avatar ianrose avatar ianstormtaylor avatar ilyaigpetrov avatar martinvehmas avatar trott avatar webketje avatar woodyrew avatar zearin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

markdown's Issues

Allow processing global metadata with the keys option

Currently the keys option is an array that will check all file keys.
There are use cases where it is desirable to be able to format global metadata (for example when fetching rich text translations from a third party provider). A flexible implementation could change the keys option to accept both an array and an object. When an array is specified it is remapped to keys.files for backwards-compatibility, and when an object is defined both are allowed. In a future version the current keys: Array signature can be removed

EG.

ms.use(markdown({
  wildcard: true,
  keys: {
    global: ['labels.*'],
    files: ['faq.*',- 'description']
  },
  globalRefs: './global-refs.md'
})

Prepare for marked 5.x

Marked 5.x introduces a lot of almost breaking changes: it replaces most options with separate extension npm packages named marked-* (bummer). See: https://marked.js.org/using_advanced#extensions. Essentially this means @metalsmith/markdown must either:

  1. switch away from marked as the default markdown renderer
  2. include a lot of extra NPM packages by default just to keep feature parity.
  3. not include any extra NPM packages and throw with an informative error.

Anyhow this will require a major release.

Remove id attributes from heading elements

Is there a way to remove id attributes from heading elements?

## Awesome Title Here
produces:
<h2 id="awesome-title-here">Awesome Title Here</h2>

I'd much rather prefer clean markup such as:
<h2>Awesome Title Here</h2>

Windows Linebreaks

I've got stuck with some mardown files genrerated on a windows machine using markdownpad.com. All meta-fields in the header section won't pop up.

Is Node.js 18.x supported?

Hello:

As what you see in nodejs/nodejs.org#4978, on Windows when we try to upgrade the Node.js website from 1.6.x to 1.8.x, it seems an exception thrown out.

Nothing but just a confirm:Is Nodejs 18.x not supported yet?

Deprecate marked options at the root of the options object

It is confusing that metalsmith & marked options must be specified at the same level of the options object.

markdown({
  gfm: true,  // -> marked opt
  keys: ['desc'] // -> metalsmith opt
})

This has the risk of name collision and passes irrelevant options to marked.
Better would be, as in metalsmith-in-place & layouts:

markdown({
  keys: ['desc'], // -> metalsmith opt,
  engineOptions: {
    gfm: true,  // -> marked opt
  }
})

For 1.x both will continue to be supported, but add a deprecation warning.
For 2.x root-level marked options will be removed

Broken recursive duplication of files upon addition of non-markdown files

Hi, I've been having an issue.

  1. Start with the static-site example metalsmith project
  2. Add test/test.txt to the src directory
    • mkdir src/test && touch src/test/test.txt
  3. Build with the metalsmith command
  4. Observe that the produced file structure is very broken (http://pastebin.com/f2DN5Feq)

My use case is putting some less files in a less directory. I haven't looked into it yet, but I have narrowed down this issue to metalsmith-markdown.

Headers with identical text generates invalid HTML

If a Markdown document contains at least two headers with the same text, they will have identical IDs after conversion, which is not allowed according to the latest HTML specifications.

This occurs even if the pedantic option is turned on.

Note that there is a Marked option called "headerIds" which can be set to false to work around this, but this option was added in Marked v0.4.0, and the latest release of metalsmith-markdown on npm uses Marked v0.3.9.

Provide scoped option to help with CSS styling

Please thumbs up 👍 this comment if you would like to use this feature, or thumbs down 👎 if you don't think it should be added . Your feedback is valued


scoped option wraps the contents in a <div class="rich-text">${contents}</div> when true, but defaults to false. scoped also accepts a string that will be used as the class for the wrapping div (rich-text by default).
This can already be accomplished by using a @metalsmith/layouts layout but it's a nice extra to have it as a built-in plugin option. CSS rules can be easily scoped to .rich-text * { ... }

Usage example:

metalsmith.use(markdown({ scoped: true }))
metalsmith.use(markdown({ scoped: 'rich-text' }))

// both render a file '*Hello _world_*' as '<div class="rich-text"><strong>Hello <em>world</em></strong></div>'

Readme must be updated, test added

Allow usage of any markdown parser

Can be achieved by parameterizing the render function, providing two new options:

  • engineOptions (pass options to render function)
  • render (see below for examples)

Most common ones are:

  • marked (default, included)
    import { marked } from 'marked'
    function render(str, opts) {
      return marked(str, opts)
    }
  • markdown-it: https://github.com/markdown-it/markdown-it
    import MarkdownIt  from 'markdown-it'
    let md
    function render(str, opts) {
      if (!md) md = new MarkdownIt(opts);
      return md.render(str)
    }
  • showdown: https://github.com/showdownjs/showdown
    import { Converter }  from 'showdown'
    let md
    function render(str, opts) {
      if (!md) md = new Converter(opts)
      return converter.makeHtml(str)
    }
  • remarkable: https://github.com/jonschlinkert/remarkable
    import { Remarkable } from 'remarkable';
    let md
    function render(str, opts) {
      if (!md) md = new Remarkable(opts);
      return md.render(str)
    }

Must be done after #62

Add option "indentedCodeblocks: true"

To help with post-processing with layouts, an option can be added that will result in stripping 4 or more leading spaces following a newline. Eleventy already has such an option. It helps with post-processing markdown files with a templating language.

Global linkrefs option

The option would look like:

metalsmith.use(markdown({
  linkrefs: {
    google: 'https://google.be',
    metalsmith: 'https://metalsmith.io'
  }
})

The plugin would prepend a string of markdown with links outputted as

Object.entries(options.links)
  .map(([name, url]) => `[${name}]: ${url}`)
  .join('\n')

And this can be used to great effect to define link references in one place and use them everywhere, in documentation builds for example

*You can search on the [the google website][google]*
**You can go to the [metalsmith website][metalsmith]**

It must be prepended not appended, because file contents markdown refs take precedence over globally defined refs.

Upgrade `marked` to 0.4.0

Input:

[abc](https://foo.com/baz(beep))

marked 0.3.9 output: <p><a href="https://foo.com/baz(beep">abc</a>)</p>
marked 0.4.0 output: <p><a href="https://foo.com/baz(beep)">abc</a></p>

Markdown in nested HTML

👋Using 0.2.2 I was able to write code blocks inside of tables like this:

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>`framework.alert.beforecollapse`</td>
      <td>Before alert collapse</td>
    </tr>
    <tr>
      <td>`framework.alert.collapse`</td>
      <td>On alert collapse</td>
    </tr>
    <tr>
      <td>`framework.alert.beforereveal`</td>
      <td>Before alert reveal</td>
    </tr>
    <tr>
      <td>`framework.alert.reveal`</td>
      <td>On alert reveal</td>
    </tr>
  </tbody>
</table>

but now it looks like the markdown is being escaped. I've heard that markdown="1" on HTML elements in certain frameworks like kramdown can preserve this functionality; but just wanted to know if that was intended behavior of the new version. Thanks!

Cannot read property 'slug' of undefined

After upgrading plugin metalsmith-markdown from version 0.2.1 to 1.0.0, I now get the following error:

TypeError: Cannot read property 'slug' of undefined
Please report this to https://github.com/markedjs/marked.
at Renderer.heading (/home/romain/Documents/dev/recommendations/metalsmith/node_modules/marked/lib/marked.js:962:17)

Do you have any idea where it could come from?

Backslash separator in dirname on windows

On Windows the directory path include backslash (). The file path is then added with a slash (/).

      var data = files[file];
      var dir = dirname(file);
      var html = basename(file, extname(file)) + '.html';
      if ('.' != dir) html = dir + '/' + html;

This will result in paths like "services\marketing/email.html".

path.join should be used instead of hard coding the separator.

Provide marked walkTokens option to help with relative markdown links

Please thumbs up 👍 this comment if you would like to use this feature, or thumbs down 👎 if you don't think it should be added . Your feedback is valued


Various editors provide typehint support for linking to local files.
A function like below could help auto-transform source links to html permalinks.

walkTokens(token) {
    if (token.type === 'link') {
      if (token.href.endsWith('index.md')) {
        token.href = token.href.replace(/\index.md$/, '')
      }
      if (token.href.startsWith('.')) {
        token.href = baseUrl + '/' + relative(metalsmith.source(), metalsmith.path('src',token.href)).replace(/\\/g, '/')
        console.log(token)
      }
    }
  }

To consider:

  • need to resolve relative to linking page's __filename
  • complications when the root of the repo !== metalsmith.directory
  • complications when a path-transforming plugin like metalsmith/permalinks is used after the markdown conversion

Doctype escaped as paragraph

A template starting with the following HTML markup:

<!doctype html>
<html lang="en">

<head>

comes out as:

<p>&lt;!doctype html&gt;</p>
<html lang="en">

<head>

while using the latest release of metalsmith and metalsmith-templates, and tried with mustache, handlebars, and swig.

These are configured via metalsmith.json, that looks like:

{
  "source": "stories",
  "destination": "public_html",
  "plugins": {
    "metalsmith-templates": {
      "engine": "swig",
      "directory": "./templates",
      "default": "index.html"
    },
    "metalsmith-markdown": {
      "smartypants": true,
      "gfm": true,
      "tables": true
    }
  }
}

While switching over to https://github.com/markdown-it/markdown-it and using the option html: true, the doctype comes out as expected (non escaped).

segment-boneyard/metalsmith-templates#48

Update converted file's path with extension .html

Hi there,
Thanks a million for the plugin. I was wondering if it wouldn't make sense to not only update the contents with the converted HTML but also update the converted file's path with the .html extension. I am using your plugin with markdown-collections and when I loop through them using the {{path}} variable in handlebars I still get the old .md link.
Happy to help with a PR, thanks in advance,
Stephan

Markdown code blocks are badly rendered

so apparently I'm having a problem with the code blocks in markdown, where all the whitespace preceding the first line is being trimmed off, or there is some more that is being appended on the subsequent lines, no idea which one of the two is the case.

to give you more context, if I write:

$ git remote rm origin
$ git remote add origin git://your/repository/git

(that is prepending 4 spaces before each line)

the output is:

$ git remote rm origin
       $ git remote add origin git://your/repository/git

the generated markup is the following:

<pre><code>$ git remote rm origin
       $ git remote add origin git://your/repository/git
       </code></pre>

😭

doesn't change anything if I use

    ```

to wrap the code in markdown

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.