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

@mina-editor/core

v0.3.2

Published

A flexible, TypeScript-first rich text editor with JSON-based document model

Downloads

317

Readme

Mina Rich Editor

A block-based rich text editor for React — Notion-style editing with full CMS integration.

npm license

Features

  • Block-based editing (Notion-style paragraphs, headings, lists, images, tables)
  • Markdown shortcuts — type # for H1, **bold**, > for blockquote, etc.
  • Smart paste — paste Markdown text and it auto-converts to rich blocks
  • Two editor variants: self-contained CompactEditor and full-featured Editor
  • Extension system — Node.create(), Mark.create(), Extension.create() with StarterKit included
  • 3 CSS theme presets — Notion, Minimal, GitHub
  • Export to JSON, semantic HTML, or Markdown
  • CMS-ready: onChange, getHTML(), getJSON(), getMarkdown(), setContent()
  • Zero re-renders on content changes for non-active blocks (BlockWrapper pattern)
  • TypeScript-first with full type exports
  • AI selection editing — rephrase, fix grammar, change tone, plus AI-powered styling (bold, italic, code)
  • Works at any width — 300 px sidebar to full-width page layout
  • 969 tests (759 unit + 210 E2E)

Quick Start

Installation

npm / yarn / pnpm:

npm install @mina-editor/core zustand

react, react-dom, and zustand are peer dependencies.

shadcn CLI:

npx shadcn@latest add https://ui-v4-livid.vercel.app/r/styles/new-york-v4/rich-editor.json

This installs the editor as a local component in your shadcn/ui project — no npm dependency needed.

Import styles

Add this import once in your app (e.g. layout.tsx or globals.css):

import "@mina-editor/core/styles.css";

This includes all the Tailwind classes the editor needs — no extra Tailwind config required.

CompactEditor — recommended for CMS

The CompactEditor is self-contained. Drop it anywhere and wire up onChange:

import { CompactEditor } from '@mina-editor/core';

export function ArticleForm() {
  const handleChange = ({ json, html }) => {
    // json  → native block model (store in your DB)
    // html  → clean semantic HTML (no Tailwind classes)
    console.log(json, html);
  };

  return (
    <CompactEditor
      onChange={handleChange}
      minHeight="400px"
    />
  );
}

Restore saved content by passing initialContent:

import { CompactEditor, type ContainerNode } from '@mina-editor/core';

const saved: ContainerNode = JSON.parse(localStorage.getItem('doc') ?? 'null');

<CompactEditor initialContent={saved} onChange={({ json }) => save(json)} />

Programmatic access via useEditorAPI

Use useEditorAPI inside the EditorProvider tree to read or update content imperatively — useful for Save buttons, auto-save, word-count displays, etc.

import { CompactEditor, EditorProvider, useEditorAPI } from '@mina-editor/core';

function SaveButton() {
  const api = useEditorAPI();

  const handleSave = async () => {
    await fetch('/api/articles', {
      method: 'POST',
      body: JSON.stringify({
        html: api.getHTML(),
        json: api.getJSON(),
        markdown: api.getMarkdown(),
      }),
    });
  };

  return <button onClick={handleSave}>Save</button>;
}

export function CmsPage() {
  return (
    <EditorProvider>
      <CompactEditor />
      <SaveButton />
    </EditorProvider>
  );
}

Full Editor (Notion-style, with cover image and toolbar)

import { Editor, EditorProvider } from '@mina-editor/core';

export function NotionPage() {
  return (
    <EditorProvider>
      <Editor
        readOnly={false}
        onUploadImage={async (file) => {
          const url = await uploadToS3(file);
          return url;
        }}
      />
    </EditorProvider>
  );
}

Extension System

Build custom block types and inline marks as plain objects — no ProseMirror schema required.

import { Extension, Node, Mark, StarterKit, EditorProvider } from '@mina-editor/core';

// Create a custom node extension
const Callout = Node.create({
  name: 'callout',
  nodeType: 'callout',
  group: 'block',
  addStyles: () => 'bg-blue-50 border-l-4 border-blue-500 p-4 rounded',
  addInputRules: () => [{
    find: /^!!! (.+)$/,
    handler: (match, ctx) => { /* auto-convert */ return true; },
  }],
});

// Create a custom mark
const Highlight = Mark.create({
  name: 'highlight',
  markName: 'highlight',
  inlineProperty: 'className',
  renderHTML: () => '<mark>',
});

// Use with EditorProvider
<EditorProvider extensions={[...StarterKit, Callout, Highlight]}>
  <MyEditor />
</EditorProvider>

The StarterKit array contains all 22 built-in extensions (16 nodes + 6 marks). You can spread it and append your own, or replace it entirely for a minimal setup.

Themes

// Import base styles (required)
import '@mina-editor/core/styles';

// Import a theme preset (optional)
import '@mina-editor/core/themes/notion';   // Notion-inspired
import '@mina-editor/core/themes/minimal';  // Ultra-clean
import '@mina-editor/core/themes/github';   // GitHub-flavored

// Apply theme via className
<div className="mina-editor theme-notion">
  <EditorProvider>...</EditorProvider>
</div>

API Reference

Components

| Component | Description | |---|---| | CompactEditor | Self-contained editor with inline formatting toolbar. Manages its own EditorProvider. | | Editor | Full-featured editor with cover image, floating toolbar, and table support. Requires an EditorProvider wrapper. | | EditorProvider | Zustand-backed state management wrapper. Share one provider across multiple components. |

CompactEditor props

| Prop | Type | Default | Description | |---|---|---|---| | initialContent | ContainerNode | — | Initial document content | | initialState | EditorState | — | Full initial state (preserves history) | | readOnly | boolean | false | View-only mode | | onChange | (data: { json: ContainerNode; html: string }) => void | — | Debounced (300 ms) content-change callback | | onUploadImage | (file: File) => Promise<string> | — | Custom image upload handler | | minHeight | string | "200px" | CSS min-height of the editing area | | className | string | — | Extra classes on the outer wrapper |

Hooks

useEditorAPI(): EditorAPI

Must be called inside an <EditorProvider> tree. Returns a stable, non-reactive object — calling it never causes a re-render.

| Method | Returns | Description | |---|---|---| | getJSON() | ContainerNode | Native block model | | getHTML() | string | Clean semantic HTML | | getMarkdown() | string | Markdown export | | getPlainText() | string | All text concatenated | | setContent(container) | void | Replace entire document | | isDirty() | boolean | True if unsaved edits exist | | getBlockCount() | number | Number of top-level blocks |

useExtensionManager(): ExtensionManager

Returns the live extension registry mounted by the nearest EditorProvider. Use this to inspect registered nodes, marks, commands, and input rules at runtime.

Store hooks (low-level)

| Hook | Description | |---|---| | useEditorState() | Full editor state (reactive) | | useEditorDispatch() | Dispatch EditorAction | | useBlockNode(id) | Subscribe to a single node | | useContainer() | Current root container | | useActiveNodeId() | Currently focused block id | | useEditorStoreInstance() | Raw Zustand store (for non-reactive reads) |

Serialization

| Function | Description | |---|---| | serializeToSemanticHtml(container) | Clean HTML — no Tailwind, no data attributes | | serializeToMarkdown(container) | Full Markdown serialization | | parseMarkdownToNodes(markdown) | Markdown → block model | | parseHtmlToNodes(html) | HTML → block model | | parsePlainTextToNodes(text) | Plain text → block model |

Extensions

| Export | Description | |---|---| | Extension.create(config) | Create a generic extension (commands, keyboard shortcuts, input rules) | | Node.create(config) | Create a custom block node type | | Mark.create(config) | Create a custom inline mark | | ExtensionManager | Central registry class — instantiated by EditorProvider | | CommandManager | Chainable command executor: editor.chain().toggleBold().run() | | StarterKit | Extension[] — all 22 built-in extensions ready to pass to EditorProvider |

Actions

Dispatch actions via useEditorDispatch() or the EditorActions factory:

import { useEditorDispatch, EditorActions } from '@mina-editor/core';

const dispatch = useEditorDispatch();

// Insert a paragraph after an existing block
dispatch(EditorActions.insertNode(
  { id: 'p-1', type: 'p', content: 'Hello!' },
  parentId,
  'append'
));

Markdown Shortcuts

Type these at the start of a new block and press Space:

| Shortcut | Block type | |---|---| | # | Heading 1 | | ## | Heading 2 | | ### | Heading 3 | | > | Blockquote | | - or * | Unordered list | | 1. | Ordered list | | ``` | Code block | | --- | Divider |

Inline formatting (works inside any text block):

| Shortcut | Format | |---|---| | **text** | Bold | | *text* | Italic | | `text` | Inline code |

Customization

The editor uses standard Tailwind CSS classes and respects your project's --background, --foreground, and --primary CSS variables. Pass className to CompactEditor to control border, radius, or shadow:

<CompactEditor
  className="shadow-lg rounded-2xl border-2 border-indigo-200"
  minHeight="500px"
/>

For dark mode, the editor inherits whichever color scheme is active on the page.

License

MIT — Copyright (c) 2025-2026 Mina Massoud