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

@fedoup/markdown-editor

v0.1.0

Published

Obsidian-style Live Preview markdown editor for React, built on CodeMirror 6. One editor instance — rendered markdown by default, click any line to edit the raw source with no buffer swap.

Readme

@fedoup/markdown-editor

Obsidian-style Live Preview markdown editor for React, built on CodeMirror 6.

One editor instance throughout. Rendered markdown by default. Click any line to edit the raw source for that line, with the cursor at your click position. Move away, the rendering returns. There is no preview/editor swap, no buffer, no lag — the editor is one CodeMirror view; toggling between "rendered" and "source" is a single decoration update.

npm install @fedoup/markdown-editor \
  @codemirror/commands @codemirror/lang-markdown @codemirror/language \
  @codemirror/state @codemirror/view @lezer/common
import { MarkdownEditor } from "@fedoup/markdown-editor";
import "@fedoup/markdown-editor/styles.css";

export function MyNote() {
  const [doc, setDoc] = useState("# Hello\n\nClick any line.");
  return <MarkdownEditor initialValue={doc} onChange={setDoc} />;
}

What you get

  • Headings h1–h6 with proportional sizing and stable line-height (no twitch on cursor-on/off).
  • Bold (**x**), italic (*x* / _x_), inline code (`x`) — syntax tokens hide off-line, reappear on-line.
  • Links[label](url) shows the styled label only; brackets and URL hide off-line.
  • Images![alt](url) renders as an inline <img> widget off-line, with a host-supplied imageResolver for path translation.
  • Fenced code — opening ```lang and closing ``` lines hide entirely when the cursor isn't in the block; reveal the moment it enters.
  • Lists — bullet and ordered list markers stay visible but muted.
  • Source mode — pass sourceMode={true} to disable Live Preview entirely (Obsidian's escape hatch).
  • State preservation — selection, scroll, undo history all survive alt-tab because the editor instance never unmounts. No "preview snaps back to editor" lag, no lost cursor.
  • Tiny — ~9 kB raw, ~3 kB gzipped (excludes CodeMirror, which you bring as peer deps).

How it works

This is not a WYSIWYG editor. The document model is plain markdown text — the editor literally holds **bold** as characters. What changes is what you see: a CodeMirror 6 ViewPlugin walks the Lezer markdown syntax tree on every doc, viewport, or selection change, and emits two kinds of decorations:

  1. Decoration.mark over the content (e.g. the word bold), adding CSS classes that style it.
  2. Decoration.replace over the syntax tokens (the **s), hiding them — but only on lines the cursor isn't currently on.

That single conditional is what makes the experience feel like Obsidian's Live Preview: the line the cursor lives on is always shown as raw source; every other line is shown as if it were rendered. Click moves the cursor, the decoration set rebuilds in the same frame, and the line you clicked unfolds. No component swap, no race.

The pattern is documented in the canonical discuss.codemirror.net thread. Obsidian's own implementation is closed source, but uses CodeMirror 6 and the same fundamental approach (their CM5→CM6 migration shipped Live Preview as its headline feature). kenforthewin/atomic-editor is a related open-source clone in the same family.

API

interface MarkdownEditorProps {
  initialValue: string;
  onChange?: (next: string) => void;
  sourceMode?: boolean;          // disable Live Preview decorations
  extraExtensions?: Extension[]; // add validation, paste handlers, line numbers, etc.
  placeholder?: string;
  className?: string;
  /**
   * Rewrite an image's `src` before the widget loads it. Useful for vault-
   * relative paths or auth-protected sources. Return `null`/`undefined` to
   * skip the widget for that image (alt + raw markdown fall back).
   */
  imageResolver?: (src: string) => string | null | undefined;
}

interface MarkdownEditorHandle {
  insertAtCursor: (text: string) => void;
  focus: () => void;
  getValue: () => string;
  view: EditorView | null;       // escape hatch for custom transactions
}

initialValue is read once. The editor is uncontrolled afterwards — listen via onChange and write back via the imperative handle if needed.

Custom extensions

import { MarkdownEditor } from "@fedoup/markdown-editor";
import { lineNumbers } from "@codemirror/view";
import { closeBrackets } from "@codemirror/autocomplete";

<MarkdownEditor
  initialValue={doc}
  onChange={setDoc}
  extraExtensions={[lineNumbers(), closeBrackets()]}
/>

Line numbers are not built in — they're an opt-in extension. Same goes for autocomplete, keymaps, paste handlers.

Theming

The editor reads design-token CSS custom properties. Any of these will be picked up:

| Token | Default fallback | Purpose | |-------|------------------|---------| | --me-fg | var(--foreground, currentColor) | text + caret | | --me-fg-muted | var(--muted-foreground, #888) | gutter, h6, list markers | | --me-border | var(--border, #ddd) | gutter divider | | --me-link | var(--primary, #2563eb) | link labels | | --me-font-mono | system monospace stack | inline + fenced code | | --me-font-prose | system sans stack | body | | --me-font-size | 14px | base size |

If you already use shadcn/ui or Tailwind tokens, you get the editor themed for free — the --foreground / --border / --muted-foreground / --primary fallbacks pull straight from the same tokens.

Try it locally

git clone https://github.com/fedoup/markdown-editor
cd markdown-editor
npm install
cd examples && npm install && npm run dev

Open the URL Vite prints. Click around. Alt-tab. Pop the Source mode checkbox.

Roadmap

  • v0.2 — sub-line activation (only hide tokens not directly under the cursor, instead of activating the whole line — matches Obsidian's behavior on long lines).
  • v0.2 — paste-as-markdown helper (incoming HTML → markdown, drop-anywhere image upload via host callback).
  • v0.2 — task-list checkbox widget (- [ ] / - [x] clickable).

What this isn't

  • Not a WYSIWYG editor. The model is markdown text. Round-tripping is byte-for-byte. If you want a model where the source is HTML/JSON and markdown is lossy I/O, look at Tiptap or Milkdown.
  • Not Obsidian. No graph view, no plugins, no vault layer. This is just the editing experience as a React component.

License

MIT