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

@illusions-lab/milkdown-plugin-mdi

v0.7.0

Published

MDI syntax support for Milkdown

Readme

@illusions-lab/milkdown-plugin-mdi

0.5.0 migration

Semantic blank lines are now native editable paragraph nodes with an internal mdiBlank attribute. Consumers should treat them as ordinary textblocks: the public commands, input rules, and clipboard APIs produce TextSelection, and the attribute is not part of the public application contract. Legacy blank div DOM is still accepted and is emitted as <p class="mdi-blank">.

MDI syntax support for Milkdown, built for Japanese novel and long-form writing workflows.

The plugin supports document front matter, all inline MDI constructs, semantic blank and pagebreak blocks, and indent/bottom paragraph layout attributes.

MDI documentation

MDI's specification and complete syntax live in the official documentation:

Goals

  • Parse inline and supported block MDI constructs into ProseMirror nodes, marks, and paragraph attributes.
  • Preserve nested inline semantics through Milkdown's Markdown serializer.
  • Produce canonical persistence output through Rust's MDI serializer.
  • Keep document semantics independent from visual writing direction.

Installation

npm install @illusions-lab/milkdown-plugin-mdi @milkdown/core @milkdown/ctx @milkdown/preset-commonmark @milkdown/prose @milkdown/utils

Usage

import { Editor } from '@milkdown/core'
import { commonmark } from '@milkdown/preset-commonmark'
import {
  createMdiEditorMapping,
  getMdi,
  initializeMdi,
  mdi,
  mdiClipboard,
  mdiInputRules,
} from '@illusions-lab/milkdown-plugin-mdi'
import '@illusions-lab/milkdown-plugin-mdi/style.css'

await initializeMdi()

const editor = await Editor.make()
  .use(commonmark)
  .use(mdi())
  // Optional authoring behavior:
  .use([mdiInputRules(), mdiClipboard()])
  .create()

const canonicalSource = editor.action(getMdi())

Preparing large initial documents in a Worker

0.7.0 adds a structured-clone-safe initial-document path. Run the complete Rust parse and mdast normalization in a module Worker, then give Milkdown the prepared result. The initial editor build validates all transport versions and does not canonicalize or parse the source again.

// Worker
import {
  initializeMdi,
  prepareMdiDocument,
} from '@illusions-lab/milkdown-plugin-mdi/prepared'

await initializeMdi()
const prepared = await prepareMdiDocument(source)
postMessage(prepared)

// Renderer (after receiving the Worker message)
const editor = await Editor.make()
  .use(commonmark)
  .use(mdi({ initialDocument: prepared }))
  .create()

Use the ./prepared entrypoint inside a Worker. It intentionally excludes Milkdown, ProseMirror, and their DOM-only modules.

Treat an incompatible prepared-document error as an open failure. Do not fall back to synchronous initial parsing on the UI thread. Calling mdi() without options remains supported for small documents and for synchronous editing, paste, and serialization behavior.

Browser consumers must await initializeMdi() before creating the editor. It is idempotent and safe to call more than once.

getMarkdown() from @milkdown/utils emits valid MDI through the registered remark handlers. Use getMdi() when persisting a .mdi file: it additionally runs the Markdown through Rust's canonical serializer.

Treat that complete canonical source as the boundary for downstream MDI analysis. Import analysis APIs from @illusions-lab/mdi directly; this plugin does not proxy IR, text projection, text blocks, or search APIs.

import { getMdiTextBlocks, parse, renderText } from '@illusions-lab/mdi'

const source = editor.action(getMdi())
const ir = parse(source)
const text = renderText(source)
const { blocks } = getMdiTextBlocks(source)

getMdiTextBlocks() returns Rust-owned source-order blocks, diagnostics, ruby annotations, and grapheme-precise source maps. A coordinate such as 3:18 is valid only for the exact source revision that produced it. Applications own the revision or hash, persistent paragraph IDs, indexes, ranking, and AI context policy.

Scope

The plugin supports YAML front matter, group and split ruby, tate-chu-yoko, boten, no-break, warichu, kern, explicit breaks, blank blocks, pagebreaks, indent/bottom paragraph layout, and valid nesting. Front matter is retained as document metadata rather than displayed as editable body content. Ruby and the standalone block constructs are atomic; the other text constructs remain editable marks.

Typed transaction primitives and opt-in MDI input/clipboard plugins are included. They do not install keybindings, menus, confirmations, or other product UX. The package does not impose vertical writing or application-specific file-extension logic. Use @illusions-lab/milkdown-plugin-vertical-writing for visual writing direction.

Source-coordinate consumers can create an immutable mapping snapshot with createMdiEditorMapping(). It binds Rust-owned UTF-8 spans and canonical grapheme ranges to the exact current ProseMirror document through transient Rust mdast provenance captured by the parse bridge. Batch lookups use mapMdiSourceSpansToEditorRanges() and one Rust resolution pass. No editor-text, substring, DOM, or source-order association is used. After any editor-state transaction, including undo/redo, create a new snapshot instead of reusing stale positions.

Development

npm install
npm test
npm run test:coverage
npm run typecheck
npm run test:browser
npm run test:tarball
npm run test:consumer

Run npm run test:performance on a dedicated machine to measure end-to-end editor loading for generated one-million- and ten-million-character MDI books. The test logs elapsed time and uses conservative default limits; set MDI_1M_LOAD_MAX_MS or MDI_10M_LOAD_MAX_MS to apply your own regression budget.

Run npm run test:browser:performance to measure the same documents in Chromium, Firefox, and WebKit. It additionally records time to two animation frames after editor creation and time to scroll to the document end. Configure browser load limits with MDI_BROWSER_1M_LOAD_MAX_MS and MDI_BROWSER_10M_LOAD_MAX_MS.

test:browser uses Chromium for quick local feedback. Run npm run test:browser:all after installing Playwright's three browser engines to exercise Chromium, Firefox, and WebKit.

Contributing

Please read CONTRIBUTING.md before opening an issue or pull request.

License

MIT © Iktahana

Automatic warichu

Warichu uses an editable inline node with one ProseMirror content DOM. Rust chooses the two-row fragments; browser measurements position existing editable text and reserve space with empty, inaccessible widgets. Note glyphs stay at 50% of body size in horizontal and vertical writing. Nested annotations and ruby can increase a row's block extent; adjacent rows have no added gap. Automatic visual boundaries never become [[br]] or document edits.

Use mdiEditCommand({ type: 'setWarichu' }) and mdiEditCommand({ type: 'removeWarichu' }); legacy warichu mark operations and inspectMdiSelection(state).marks.warichu remain available. IME composition freezes geometry; Enter outside composition announces automatic layout.

serializeMdiClipboardHtml(slice)(ctx) returns portable static HTML for the selection. Clipboard v2 carries semantic ancestor paths and inline/block shape; v1 remains readable with canonical-MDI fallback where depths are ambiguous. Migrate saved ProseMirror JSON through the old schema's canonical MDI serializer before upgrading; canonical MDI documents need no migration. Exact proportional font balancing and native OS IME coverage are not claimed by synthetic browser tests.