https://github.com/ngtrio/mdian
Obsidian Flavored Markdown for unified + react-markdown
https://github.com/ngtrio/mdian
obsidian react-markdown unified
Last synced: 20 days ago
JSON representation
Obsidian Flavored Markdown for unified + react-markdown
- Host: GitHub
- URL: https://github.com/ngtrio/mdian
- Owner: ngtrio
- License: apache-2.0
- Created: 2026-04-19T07:16:36.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-04-28T02:01:28.000Z (23 days ago)
- Last Synced: 2026-04-28T04:04:38.254Z (22 days ago)
- Topics: obsidian, react-markdown, unified
- Language: TypeScript
- Homepage: https://ngtrio.github.io/mdian/
- Size: 233 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# mdian
`mdian` is a TypeScript toolkit for parsing and rendering Obsidian Flavored Markdown (OFM) with unified-compatible pipelines.
It provides:
- `remarkOfm` and `rehypeOfm` for OFM syntax and HTML transforms
- URL and anchor helpers for applications that need OFM-style wiki navigation
- a small CSS entrypoint for sensible default styling
## Supported OFM Syntax
`mdian` currently supports:
- Wikilinks: `[[Page]]`, `[[Page#Heading]]`, `[[Page#^block-id]]`, `[[Page|Alias]]`
- Embeds: `![[Page]]`, `![[Page#Heading]]`, `![[Page#^block-id]]`
- Highlights: `==highlight==`
- Comments: `%%hidden note%%`
- Callouts
- External embeds from standard Markdown image syntax for supported YouTube and X/Twitter URLs
See the showcase at https://ngtrio.github.io/mdian
Regular Markdown still works as usual. GFM and math are not bundled by this package; add `remark-gfm`, `remark-math`, `rehype-katex`, or other plugins yourself when needed.
## Usage
### Core unified usage
`mdian` ships the core OFM plugins for unified-compatible pipelines.
Install the package and styles entrypoint:
```bash
pnpm add mdian
```
Use `remarkOfm` and `rehypeOfm` in any unified-compatible pipeline:
```ts
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 html = String(
await unified()
.use(remarkParse)
.use(remarkOfm, {wikilinks: true, embeds: true})
.use(remarkRehype)
.use(rehypeOfm, {hrefPrefix: 'wiki'})
.use(rehypeStringify)
.process(source)
)
```
If you also need GFM or math, add those plugins around `remarkOfm` and `rehypeOfm` in the same pipeline.
## ReactMarkdown Usage
If you are using `react-markdown`, prefer the high-level React preset from `mdian/react`.
Install the React dependencies in your app before using this subpath:
```bash
pnpm add mdian react react-markdown
```
```ts
import ReactMarkdown from 'react-markdown'
import {createOfmReactPreset} from 'mdian/react'
const notes = new Map([
['Project Notes', {markdown: '# Project Notes\n\nHello world.', title: 'Project Notes'}]
])
const ofm = createOfmReactPreset({
ofm: {
remark: {wikilinks: true, embeds: true},
rehype: {externalEmbeds: true}
},
wikiLink: {
resolve(target) {
return {
href: `/wiki/${encodeURIComponent(target.path)}`,
title: target.fragment
? `${target.path}#${target.fragment}`
: target.path
}
}
},
noteEmbed: {
resolve(target) {
const note = notes.get(target.path)
return note ? {markdown: note.markdown, title: note.title} : undefined
}
},
image: {
transformSrc(src) {
return src.startsWith('assets/') ? `/${src}` : src
}
},
externalEmbeds: {
twitter: {
enhance: true
}
}
})
export function Markdown({markdown}: {markdown: string}) {
return (
{markdown}
)
}
```
This path keeps OFM parsing in `mdian` while letting your app inject link resolution, note resolution, image URL rewriting, and optional router rendering. Set `externalEmbeds.twitter.enhance` to `true` only when you want the built-in X/Twitter widget enhancement path; otherwise the preset keeps the static fallback container while the core OFM output remains a single Twitter container `div` with canonical URL text content. You can also override the script loader with `loadTwitterWidgets({loadScript})`.
## Breaking Target Model
`mdian` models OFM targets as `path + fragment`:
- `path: string`
- `fragment?: string`
`fragment` never includes the leading `#`.
Block refs are represented as `fragment: '^block-id'`.
Nested headings are represented as `fragment: 'Heading#Subheading'`.
`[[Page#Heading#^block-id]]` is intentionally unsupported and is not assigned special semantics.
`buildOfmTargetUrl()` accepts that shape directly. Rendered OFM metadata exposes `data-ofm-fragment`, while wikilinks and embeds no longer emit `data-ofm-block-id`.
## External Embeds
`rehypeOfm` also upgrades plain Markdown image URLs from supported providers into embeds:
- ``, ``, and `` render as YouTube `` embeds with the `ofm-external-embed` class.
- `` and `` render as tweet embed containers with `data-ofm-provider="twitter"` and the `ofm-external-embed` class; the static core output is a single `div` whose text content is the canonical Twitter status URL.
Set `externalEmbeds: false` to keep those URLs on the normal Markdown image path.
If you want interactive X/Twitter widgets instead of the static container output, load the provider script in your app after render.
## Development
From the repository root:
```bash
pnpm install
pnpm build
pnpm test
pnpm demo:dev
```
## 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` 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.