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

@mydrift/etch

v0.5.0

Published

A clean, fully-featured WYSIWYG editor built on Tiptap v3

Readme


Install

bun add @mydrift/etch
npm install @mydrift/etch
pnpm add @mydrift/etch
yarn add @mydrift/etch

Usage

import { EtchEditor } from "@mydrift/etch/react";
import "@mydrift/etch/styles";

export default function App() {
  return (
    <EtchEditor
      content="<p>Hello, Etch.</p>"
      onUpload={async (file) => {
        // upload to your CDN, return the public URL
        return "https://example.com/uploaded.png";
      }}
    />
  );
}

SSR (Next.js App Router, Remix)

Etch defaults to immediatelyRender={false} and is safe to render directly in React Server Component trees as long as the component itself is a Client Component. In Next.js App Router, mark the file with "use client" (or load the editor with next/dynamic and { ssr: false } if you want to skip hydration entirely).

Props

| Prop | Type | Default | What | | --- | --- | --- | --- | | content | string | "" | Initial HTML (or Tiptap JSON) | | onUpdate | (editor) => void | — | Fires on every content change | | onStats | ({chars, words}) => void | — | Live character / word count callback | | onSubmit | (editor) => void | — | Fires on the submit keybinding (see submitOn) | | submitOn | "enter" \| "mod-enter" | "mod-enter" | "enter" → Enter submits, Shift+Enter inserts hard break. "mod-enter" → Cmd/Ctrl+Enter submits, Enter newlines. | | onUpload | (file) => Promise<string> | — | File upload handler; required for media slash commands | | theme | "light" \| "dark" \| "system" | "system" | Scope theme to the editor wrapper (sets data-theme) | | placeholder | string | "Type '/' for commands…" | Empty-line placeholder text | | density | "comfortable" \| "compact" | "comfortable" | "compact" collapses min-height to 2.5rem and padding to 0.5rem for chat-style composers | | editable | boolean | true | Disable to render read-only | | slashItems | SlashCommandItem[] | built-in | Override the slash command palette | | extensions | Extension[] | [] | Additional Tiptap extensions to merge in | | className | string | — | Applied to the outermost wrapper | | immediatelyRender | boolean | false | Tiptap's hydration knob; leave false for SSR safety | | disableX (12 flags) | boolean | false | Drop entire extensions — see "Disabling extensions" below | | nodeAttrs | { [node]: string[] } | — | Roundtrip custom HTML attrs on built-in nodes | | mentions | MentionConfig | — | Enable @-autocomplete — see "Mentions" below | | mobileToolbar | boolean \| "auto" | "auto" | Floating "+" trigger on coarse-pointer devices |

Sizing can also be tuned directly with CSS variables — --etch-min-height and --etch-padding on .etch-editor-wrapper and .ProseMirror respectively.

Imperative ref

const ref = useRef<EtchEditorHandle>(null);
// …
ref.current?.getHTML();             // "<p>…</p>"
ref.current?.getText();             // plain text (Tiptap's editor.getText())
ref.current?.getMarkdown();         // "# heading\n…" (requires tiptap-markdown, bundled)
ref.current?.getEmailSafeHTML();    // HTML rewritten for email clients (see below)
ref.current?.getCharCount();        // number
ref.current?.getWordCount();        // number
ref.current?.editor;                // raw Tiptap Editor instance

Chat-style composer

<EtchEditor
  density="compact"
  placeholder="Write a comment…"
  submitOn="enter"
  onSubmit={(editor) => {
    sendComment(editor.getHTML());
    editor.commands.clearContent();
  }}
/>

Mentions

<EtchEditor
  mentions={{
    items: async (query) => searchUsers(query),
    // optional: custom row render
    renderItem: ({ item }) => <UserRow user={item.data as User} />,
    // optional: override how the chip is inserted
    onInsert: ({ item, editor, range }) => {
      editor.chain().focus().deleteRange(range).insertContent({
        type: "mention",
        attrs: { id: item.id, label: item.label },
      }).run();
    },
  }}
/>

Triggered by @ by default — override via mentions.char. The popup auto-positions, supports arrow/Enter/Tab keyboard nav, and respects the editor's theme.

Disabling extensions

Twelve flags drop entire extensions (slash menu and input-rule shortcuts):

<EtchEditor
  disableCodeBlock
  disableTaskList
  disableMath
  disableCallouts
  disableToggleHeadings
  disableFileEmbeds
/>

Available: disableCodeBlock, disableHorizontalRule, disableTaskList, disableMath, disableCallouts, disableToggleHeadings, disableFileEmbeds (video / audio / file attachment / file paste-drop), disableImage, disableTable, disableYoutube, disableTypography, disableTableOfContents.

Custom node attributes

Roundtrip app-side IDs (or any data-*) through built-in nodes:

<EtchEditor
  nodeAttrs={{
    image: ["data-fs-node-id"],
    fileAttachment: ["data-fs-node-id", "data-file-version"],
  }}
/>
// later: editor.getHTML() preserves the attrs

Read-only viewer

For rendering saved Etch HTML outside an editor (feed rows, archived comments):

import { EtchViewer } from "@mydrift/etch/react";

<EtchViewer
  html={comment.body_html}
  theme="dark"
  density="compact"
  // same disable / nodeAttrs / theme / density props as EtchEditor
/>

Renders identical to <EtchEditor editable={false}> minus the editor chrome (no slash menu, bubble menu, drag handle, upload pipeline).

Email-safe HTML

const html = ref.current?.getEmailSafeHTML();
// or as a static utility:
import { toEmailSafeHTML } from "@mydrift/etch";
const html = toEmailSafeHTML(editor.getHTML());
  • Strips Etch-internal data-* attributes
  • Inlines styles on blockquote, code, pre, th, td
  • Rewrites callouts → blockquote with bold first line
  • Rewrites toggle headings → plain headings
  • Rewrites file-attachment embeds → <a href> links

Requires DOMParser (browser, or a DOM-shimmed Node environment).

Mobile

A floating "+" trigger appears on coarse-pointer devices (phones, tablets); tapping it inserts a / so the slash menu opens above the on-screen keyboard. Toggle via mobileToolbar={true | false | "auto"} (default "auto").

Features

  • Slash commands — Type / for a grouped command palette with 25+ block types
  • Rich formatting — Bold, italic, links, colors, highlights via bubble menu
  • Code blocks — 55 languages with syntax highlighting
  • Drag & drop — Reorder any block by the left-side handle
  • Media uploads — Images (with resize), videos, audio, file attachments with upload progress
  • Callout blocks — 8 styled callout types with SVG icons
  • Markdown paste/copy — Auto-converts pasted markdown to rich text
  • Tables, task lists, math — All the usual suspects, no plugin fishing

Entrypoints

| Path | What | | --- | --- | | @mydrift/etch | Framework-agnostic Tiptap extensions | | @mydrift/etch/react | React components (<EtchEditor>, hooks) | | @mydrift/etch/styles | CSS (also exposed as dist/etch.css) |

Development

bun install            # Install deps
bun run build          # Build with tsup (ESM + CJS + .d.ts)
bun run dev            # Watch mode
bun run typecheck      # TypeScript check

cd demo && bun install # Run the demo locally
bun run dev

License

MIT