remark-directive

repository·main·Indexed 19 days ago

https://github.com/remarkjs/remark-directive

A remark plugin that adds support for the generic directives proposal, allowing custom syntax such as :cite[], ::youtube[], or :::note[] in Markdown. It parses directive syntax into the MDAST syntax tree (containerDirective, leafDirective, and textDirective) but requires a custom remark plugin to transform these nodes into HTML elements.

Tokens
2.2K
Snippets
7
Records
8
Agent score
15%

What's inside remark-directive

  1. Use remark-directive in a unified pipeline

    main

    To support generic directives in your markdown processing pipeline, include remarkDirective in your unified() chain.

    Note that remark-directive only parses the syntax into the syntax tree (MDAST). It does not automatically convert directives into HTML. You must write a custom remark plugin to transform the directive nodes (e.g., containerDirective, leafDirective, or textDirective) into HTML elements by setting node.data.hName and node.data.hProperties.

    import {unified} from 'unified'
    import remarkParse from 'remark-parse'
    import remarkDirective from 'remark-directive'
    import remarkRehype from 'remark-rehype'
    import rehypeStringify from 'rehype-stringify'
    
    const file = await unified()
      .use(remarkParse)
      .use(remarkDirective)
      .use(myRemarkPlugin) // Your custom transformation plugin
      .use(remarkRehype)
      .use(rehypeStringify)
      .process(await read('example.md'))
  2. Configure remarkDirective options

    main

    The remarkDirective plugin accepts an optional options object to control how directives are parsed and serialized.

    FieldTypeDefaultDescription
    collapseEmptyAttributesbooleantrueCollapse empty attributes: get title instead of title=""
    preferShortcutbooleantruePrefer # and . shortcuts for id and class
    preferUnquotedbooleanfalseLeave attributes unquoted if that results in less bytes
    quoteSmartbooleanfalseUse the other quote if that results in less bytes
    quote'"' or '"'(from remark-stringify)Preferred quote to use around attribute values
  3. Transform YouTube directives into iframes

    main

    You can create a plugin to transform a custom ::youtube leaf directive into an HTML <iframe>. The directive syntax used is ::youtube[Label]{#id}.

    In the plugin, check for node.name === 'youtube', extract the id from node.attributes, and set node.data.hName to 'iframe' and node.data.hProperties to the iframe attributes.

    import {visit} from 'unist-util-visit'
    
    function myRemarkPlugin() {
      return (tree, file) => {
        visit(tree, function (node) {
          if (node.type !== 'containerDirective' && 
              node.type !== 'leafDirective' && 
              node.type !== 'textDirective') return
    
          if (node.name !== 'youtube') return
    
          const data = node.data || (node.data = {})
          const attributes = node.attributes || {}
          const id = attributes.id
    
          if (node.type === 'textDirective') {
            file.fail('Unexpected `:youtube` text directive, use two colons for a leaf directive', node)
          }
    
          if (!id) {
            file.fail('Unexpected missing `id` on `youtube` directive', node)
          }
    
          data.hName = 'iframe'
          data.hProperties = {
            src: 'https://www.youtube.com/embed/' + id,
            width: 200,
            height: 200,
            frameBorder: 0,
            allow: 'picture-in-picture',
            allowFullScreen: true
          }
        })
      }
    }
  4. Transform styled block directives into divs

    main

    You can use directives to create styled callouts or admonitions (e.g., :::note{.warning}).

    In your plugin, check for the directive name (e.g., note). Set node.data.hName to 'div' for container/leaf directives, or 'span' for text directives. Pass the attributes through to node.data.hProperties to ensure classes and IDs are applied correctly.

    import {h} from 'hastscript'
    import {visit} from 'unist-util-visit'
    
    function myRemarkPlugin() {
      return (tree) => {
        visit(tree, (node) => {
          if (
            node.type === 'containerDirective' ||
            node.type === 'leafDirective' ||
            node.type === 'textDirective'
          ) {
            if (node.name !== 'note') return
    
            const data = node.data || (node.data = {})
            const tagName = node.type === 'textDirective' ? 'span' : 'div'
    
            data.hName = tagName
            data.hProperties = h(tagName, node.attributes || {}).properties
          }
        })
      }
    }
  5. Register directive types in TypeScript

    main

    If you are working with the syntax tree in TypeScript, you can register the new directive node types with @types/mdast by adding a reference. This allows node to be recognized as one of the directive node types within unist-util-visit loops.

    /**
     * @import {} from 'mdast-util-directive'
     * @import {} from 'mdast'
     */
    
    import {visit} from 'unist-util-visit'
    
    function myRemarkPlugin() {
      return (tree) => {
        visit(tree, function (node) {
          // `node` can now be one of the nodes for directives.
          console.log(node)
        })
      }
    }
  6. Use remark-directive with unified()

    main

    To use remark-directive in a unified or remark pipeline, import the default export and pass it to .use(). This plugin enables support for directive syntax (like :name[label]{attr=val}) in your Markdown processing pipeline.

    import { unified } from 'unified'
    import remarkParse from 'remark-parse'
    import remarkDirective from 'remark-directive'
    import remarkStringify from 'remark-stringify'
    
    // Example pipeline setup
    const processor = unified()
      .use(remarkParse)
      .use(remarkDirective)
      .use(remarkStringify)
    import { unified } from 'unified'
    import remarkParse from 'remark-parse'
    import remarkDirective from 'remark-directive'
    import remarkStringify from 'remark-stringify'
    
    const processor = unified()
      .use(remarkParse)
      .use(remarkDirective)
      .use(remarkStringify)
  7. Use remark-directive as a unified plugin

    main

    To add support for generic directives in your markdown processing pipeline, use remarkDirective as a plugin with unified().use().

    Important Note: This plugin only adds support for the directive syntax (parsing and stringifying). It does not handle the logic or transformation of the directives themselves. To perform actions based on specific directives (e.g., turning a :youtube directive into an iframe), you must create and use your own custom plugin to traverse the resulting AST.

    import { unified } from 'unified'
    import remarkParse from 'remark-parse'
    import remarkDirective from 'remark-directive'
    
    const processor = unified()
      .use(remarkParse)
      .use(remarkDirective)
    
    // Now the processor can parse directive syntax into the AST