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

@openleaf-editor/plugins-insert

v0.1.0-beta.9

Published

Opt-in insert tools for OpenLeaf: media embeds, collapsible sections, anchors, character map, emoji, snippets, page breaks, and image resize handles. The matching schema nodes live in @openleaf-editor/core so stored documents round-trip whether or not thi

Readme

@openleaf-editor/plugins-insert

Insert and structure tools for OpenLeaf: media embeds, collapsible sections, named anchors, a character map, emoji, date and time, page breaks, snippets, and drag-resize handles for images and video.

The matching schema nodes live in @openleaf-editor/core, so stored <video>, <iframe>, <details> and <figure> round-trip whether or not this package is loaded. This bundle is the editing chrome.

The Anchor control inserts an empty named destination (<a id="…"></a>), the TinyMCE-style jump target. That is a modelled atom, including pretty-printed whitespace-only interiors. An <a id> that wraps text — <h2><a id="sec">Title</a></h2> — is not that atom: core parses it as a link mark with id and no href, so the heading stays editable and the id round-trips. <a id href> is an ordinary link. <a name> is unmatched and preserved.

Iframes are stored only when their src is an https: URL on a known player host (youtube.com/embed, player.vimeo.com/video, and the rest of the allowlist). Arbitrary iframes are still dropped.

A <video> renders as an inert preview in the editor — no control bar, and no pointer events — because native media chrome takes those events for itself, and in Firefox it takes them for the whole element: nothing in the editor would ever see the click, so ProseMirror would never select the node and the player could be inserted once and never edited again. Selecting a video puts a play button of this package's own over the frame; pressing it hands that one element its controls and its pointer events back for as long as it stays selected, and moving the selection away or pressing Escape returns it to a preview. Stored HTML is serialized from the node rather than from the editing DOM, so controls round-trips untouched in every state and the player is fully interactive on the published page. A caption track the author turned on stays on across a resize: the <source> and <track> children are rebuilt only when the stored markup changes, since a fresh <track> element means a fresh TextTrack with its mode back at disabled.

The drag-resize handle is withheld until the image has decoded or the video has metadata, and while it is withheld a drag or arrow key does not write a width. A handle sitting on the broken-image box would otherwise commit that box's ~16–40px width, which then stays in the document after the real bitmap arrives. A width the document already stores is enough to offer the handle: changing it does not need the ratio, and height is left unset until the file can supply one — the same rule a video follows before loadedmetadata. A cached image that is already complete is checked synchronously, so the handle appears without waiting for a load that will never fire.

Editor output is untrusted input. Whatever the editor produces — and whatever a user pasted into it — must be sanitized on your server before it is stored or rendered as HTML. Client-side sanitization is a user-experience feature, not a security control: anything the editor strips can be put back with developer tools, because the editor runs under the user's control.

@openleaf-editor/sanitize ships the canonical allowlist as data and generates configuration for DOMPurify, Python bleach and PHP HTMLPurifier from it, so client and server enforce the same rules. Read SECURITY.md before you ship.

Install

npm install @openleaf-editor/element@beta @openleaf-editor/plugins-insert@beta
import '@openleaf-editor/element'
import { installInsertTools } from '@openleaf-editor/plugins-insert'

installInsertTools({
  snippets: [{ id: 'byline', title: 'Byline', html: '<p><em>Staff writer</em></p>' }],
})

Installing does not rearrange the toolbar. Name the items you want:

<openleaf-editor
  toolbar="bold italic | link image media details | charmap emoji datetime pagebreak nbsp | source"
></openleaf-editor>

Shared file picker and lists

import {
  registerFilePicker,
  registerLinkList,
  registerImageList,
  registerImageClasses,
} from '@openleaf-editor/element'

registerFilePicker(async ({ kind }) => {
  const url = await openCmsLibrary(kind)
  return url ? { url } : null
})

registerLinkList([
  { title: 'Home', value: '/' },
  { title: 'About', value: '/about' },
])

registerImageList([{ title: 'Hero', value: '/media/hero.jpg' }])
registerImageClasses(['align-left', 'full-width'])

Toolbar item ids

| Id | Control | | --- | --- | | media | Insert media (video, audio, allowlisted embed) — or edit the selected player | | details | Collapsible section | | anchor | Named anchor | | charmap | Character map | | emoji | Emoji picker | | datetime | Insert date and time | | pagebreak | Page break | | nbsp | Non-breaking space | | snippet | Snippet |

Name the ones you want in the element's toolbar attribute. An id nothing has registered logs a warning rather than being skipped silently.

Character map and emoji

Both pickers are the same control: a toolbar trigger and a grid of named glyphs. Arrow keys move by one and by a row, Home and End go to the ends of a row, Escape closes and returns focus to the trigger, and Tab leaves the widget (there is one tab stop inside the grid). The grid is role="grid" with row and gridcell structure, matching the colour picker, so a screen reader can say which cell the author is on.