npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mdian

v0.3.7

Published

remark and rehype plugins for Obsidian Flavored Markdown

Downloads

149

Readme

mdian

npm version CI license

Obsidian Flavored Markdown support for unified, remark, rehype, and React Markdown.

mdian adds Obsidian-style links, embeds, callouts, highlights, comments, and anchor behavior to Markdown pipelines without taking over the rest of your Markdown stack. Use it with plain unified pipelines, or use the React preset when rendering with react-markdown.

Try the demo

Features

  • Unified-compatible remarkOfm and rehypeOfm plugins.
  • Wikilinks, note embeds, image/file embeds, highlights, comments, and callouts.
  • Heading and block anchor output that works with native browser hash navigation.
  • Optional YouTube and X/Twitter external embeds from standard Markdown image syntax.
  • Stable data-ofm-* attributes and ofm-* class names for app integrations.
  • Optional mdian/react preset for react-markdown components, note embeds, image URL rewriting, and X/Twitter enhancement.
  • Small optional stylesheet at mdian/styles.css.

Regular Markdown still works as usual. GFM, math, syntax highlighting, and other Markdown extensions are not bundled; add plugins such as remark-gfm, remark-math, or rehype-katex in your own pipeline.

Supported Syntax

| Feature | Examples | | --- | --- | | Wikilinks | [[Page]], [[Page#Heading]], [[Page#^block-id]], [[Page|Alias]] | | Embeds | ![[Page]], ![[Page#Heading]], ![[Page#^block-id]], ![[image.png|500]] | | Highlights | ==important== | | Comments | %%hidden note%% | | Callouts | > [!warning] Caution | | External embeds | ![Video](https://www.youtube.com/watch?v=...), ![](https://x.com/user/status/...) |

Installation

pnpm add mdian

For a complete unified HTML pipeline, install the surrounding unified packages you use in your app:

pnpm add unified remark-parse remark-rehype rehype-stringify

For React Markdown usage:

pnpm add mdian react react-markdown

mdian requires Node.js 18 or newer.

Quick Start

import rehypeStringify from 'rehype-stringify'
import {rehypeOfm, remarkOfm} from 'mdian'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import {unified} from 'unified'
import 'mdian/styles.css'

const source = [
  '# Project Notes',
  '',
  'See [[Roadmap|the roadmap]].',
  '',
  '> [!tip] Remember',
  '> You can highlight ==important== details.',
  '',
  '![[Architecture#Overview]]'
].join('\n')

const html = String(
  await unified()
    .use(remarkParse)
    .use(remarkOfm)
    .use(remarkRehype)
    .use(rehypeOfm, {
      resolveHref(href) {
        return href.startsWith('#') ? href : `/wiki/${href}`
      },
      resolvePathCandidates(path) {
        return path === 'Roadmap' ? ['docs/Roadmap'] : []
      }
    })
    .use(rehypeStringify)
    .process(source)
)

resolvePathCandidates(path) receives the raw OFM target path. When it returns one or more candidates, the first candidate becomes the canonical rendered target for data-ofm-path, title, and generated hrefs.

React Markdown

Use createOfmReactPreset from mdian/react when rendering with react-markdown. The preset wires the remark and rehype plugins together and adds components for wiki links, embeds, images, and supported external embeds.

import ReactMarkdown from 'react-markdown'
import {createOfmReactPreset} from 'mdian/react'
import remarkGfm from 'remark-gfm'
import 'mdian/styles.css'

const notes = new Map([
  ['Project Notes', {markdown: '# Project Notes\n\nHello world.', title: 'Project Notes'}]
])

const ofm = createOfmReactPreset({
  markdown: {
    remarkPlugins: [remarkGfm]
  },
  ofm: {
    rehype: {
      resolveHref(href) {
        return href.startsWith('#') ? href : `/wiki/${href}`
      },
      resolveResourceUrl(url) {
        return url.startsWith('assets/') ? `/${url}` : url
      },
      resolvePathCandidates(path) {
        return path === 'Project Notes' ? ['workspace/Project Notes'] : []
      }
    }
  },
  noteEmbed: {
    resolve({path}) {
      return notes.get(path) ?? {
        markdown: `# ${path}\n\nMissing note content.`,
        title: path
      }
    }
  },
  externalEmbeds: {
    twitter: {
      enhance: true
    }
  }
})

export function Markdown({markdown}: {markdown: string}) {
  return (
    <ReactMarkdown
      components={ofm.components}
      rehypePlugins={ofm.rehypePlugins}
      remarkPlugins={ofm.remarkPlugins}
    >
      {markdown}
    </ReactMarkdown>
  )
}

The React preset keeps application-specific behavior in your app:

  • markdown.remarkPlugins and markdown.rehypePlugins add shared Markdown extensions once.
  • ofm.rehype.resolveHref rewrites generated OFM link and embed hrefs.
  • ofm.rehype.resolveResourceUrl rewrites resource URLs for OFM image/file embeds.
  • wikiLink.render can replace the default <a> rendering with your router link.
  • noteEmbed.resolve resolves ![[note]] embeds to Markdown content.
  • noteEmbed.maxDepth limits recursive note embed rendering.
  • externalEmbeds.twitter.enhance enables the built-in X/Twitter widget enhancement path.

API

Package Exports

| Import | Exports | | --- | --- | | mdian | remarkOfm, rehypeOfm, buildOfmSlugPath, ofmClassNames, ofmPublicKind, ofmPublicPropKeys, ofmPublicProvider, ofmPublicVariant, readOfmPublicProps | | mdian/react | createOfmReactPreset, loadTwitterWidgets, React preset types | | mdian/styles.css | Optional default styles for generated OFM markup |

remarkOfm(options)

remarkOfm registers the micromark extensions, mdast bridges, and post-parse remark transforms for enabled OFM syntax. All syntax options default to true.

| Option | Syntax | | --- | --- | | wikilinks | [[Page]], [[Page#Heading]], [[Page|Alias]] | | embeds | ![[Page]], ![[image.png|500]] | | highlights | ==important== | | comments | %%hidden note%% | | callouts | > [!note] Title | | softBreaks | Newline-to-<br> inside paragraphs (Obsidian default) |

Extended syntax patterns:

| Pattern | Example | Notes | | --- | --- | --- | | Callout image metadata | > [!grid|masonry] | Primary token: grid; metadata: masonry |

rehypeOfm(options)

rehypeOfm walks the hast tree once and applies OFM HTML transforms.

| Option | Type | Default | Effect | | --- | --- | --- | --- | | externalEmbeds | {youtube?: boolean; twitter?: boolean} | both enabled | Toggle external Markdown image upgrades per provider. | | resolveHref | (href: string) => string | undefined | Resolve the final href for wikilinks and embed links. | | resolveResourceUrl | (url: string) => string | undefined | Resolve the final resource URL for image/file embeds. | | resolvePathCandidates | (path: string) => readonly string[] | undefined | Resolve a raw OFM target path to zero or more canonical candidate paths. |

URL Helpers

buildOfmSlugPath(path) returns the canonical slug used for OFM page routes. Use it to align your router with the slugs mdian generates internally.

HTML Contract

Generated elements use stable public attributes accessible through ofmPublicPropKeys:

  • data-ofm-kind="wikilink"
  • data-ofm-kind="embed" with data-ofm-variant="note" | "image" | "file" | "external"
  • data-ofm-kind="embed" with data-ofm-provider="youtube" | "twitter" for external embeds
  • data-ofm-kind="callout" with data-ofm-callout, optional data-ofm-callout-metadata, and the boolean-presence attrs data-ofm-foldable / data-ofm-collapsed
  • data-ofm-kind="highlight"
  • data-ofm-kind="block-anchor" with data-ofm-block-id on paragraphs/list items carrying a block anchor

Additional metadata: data-ofm-path, data-ofm-alias, data-ofm-fragment.

Generated elements also use stable class names from ofmClassNames: ofm-wikilink, ofm-embed, ofm-external-embed, ofm-highlight, ofm-callout, ofm-callout-title, ofm-callout-content, ofm-heading-target, ofm-block-target, ofm-block-anchor-label.

Heading and block anchor targets render native HTML id attributes whose values match the canonical OFM fragment slug, so browser hash navigation works without custom JavaScript.

To consume these attributes in TypeScript with type-safe narrowing:

import {readOfmPublicProps} from 'mdian'

const props = readOfmPublicProps(element)
if (!props) return
switch (props.kind) {
  case 'wikilink':
    console.log('wikilink to', props.path, props.fragment)
    break
  case 'embed':
    if (props.variant === 'image') console.log('image at', props.path)
    if (props.variant === 'external') console.log('external embed', props.provider)
    break
  case 'callout':
    console.log('callout', props.calloutType, props.metadata)
    break
  case 'block-anchor':
    console.log('block anchor', props.blockId)
    break
  // highlight has no extra fields
}

Development

pnpm install
pnpm build
pnpm typecheck
pnpm test
pnpm demo:dev

Tests live in test/ and run against built output in dist/. The demo app in demo/ consumes the workspace package directly and is useful for checking parser and rendering behavior interactively.

Releasing

  • Regular feature and fix PRs do not change package.json.version.
  • To cut a release, merge the desired version bump to main, then run pnpm release.
  • pnpm release tags the current main commit as v<version> and pushes that tag to origin.
  • The publish workflow runs from that tag, verifies the package, publishes to npm, and creates the matching GitHub Release.

License

Apache-2.0. See LICENSE.