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

@futelab/folio

v0.2.1

Published

Composable React document editor and server-renderable reader built on Tiptap

Readme

Folio

简体中文 · Live demo · Release guide · Editing capabilities and integration

Folio is a React document editor and reader built on Tiptap. The public package is @futelab/folio; documents use Tiptap-compatible JSON. MIT licensed.

Features

  • Paragraphs, H1–H3, nested lists and tasks, quotes, dividers, code, resizable tables, images with captions, bookmarks, callouts, toggles, mentions and a table of contents.
  • Searchable slash commands, selection formatting, block insertion, drag ordering and keyboard alternatives.
  • Image URL and upload flows, editable media metadata, links, mention candidates and table management.
  • Transaction-level read-only mode, controlled/uncontrolled state and isolated document histories.
  • React reader with extension renderers, server rendering, stable heading anchors and bounded rendering.
  • Validated/versioned JSON persistence; HTML and Markdown import/export with explicit errors before content loss.
  • English and Simplified Chinese, per-instance translations and CSS-variable themes.
  • Block action menus, multi-block selection and batch operations, persistent block links, rich/plain clipboard handling and image paste uploads.
  • Nested block moves, two/three-column layouts, find/replace with one-step undo, toolbar modes, component slots and external floating-UI containers.
  • Yjs collaboration with presence and isolated local undo; anchored comments and revision previews/restoration through replaceable host adapters.

Install

pnpm add @futelab/folio react react-dom

React 18.3 and 19 are covered by packed-consumer tests. Development and CI use Node 24 and the pnpm version in package.json.

"use client";
import { useState } from "react";
import "@futelab/folio/styles.css";
import { createEmptyFolioDocument } from "@futelab/folio/core";
import { FolioEditor, FolioReader } from "@futelab/folio/react";

export function Article() {
  const [document, setDocument] = useState(createEmptyFolioDocument);
  return (
    <>
      <FolioEditor documentId="article-42" value={document} onChange={setDocument} />
      <FolioReader value={document} />
    </>
  );
}

Package entry points

| Entry | Purpose | | ------------------------------ | ------------------------------------------------------------------------------------------- | | @futelab/folio/core | Documents, validation, snapshots, extensions, commands and URL policy. | | @futelab/folio/react | Client editor, reader, hooks, menus and controls. | | @futelab/folio/reader | Reader and rendering API without the editor client boundary; use in server components. | | @futelab/folio/serialization | HTML/Markdown conversions; browser and Node implementations selected by package conditions. | | @futelab/folio/styles.css | Complete default styles. | | @futelab/folio | Combined client entry; prefer the specific subpaths above. |

ESM and CommonJS include matching declaration files. Next.js editors belong in a client component. Server pages can import FolioReader from /reader; import the CSS in the root layout. Do not call the combined client entry from server-only code.

Editor contract

  • Use value + onChange for controlled state, or defaultValue for initial uncontrolled state.
  • Pass a stable documentId; change it when switching documents. A genuine external replacement resets selection and undo history. Normal parent echoes retain the editor and history.
  • onChange returns JSON, not an HTML string. Debounce persistence in the host application and handle server conflicts there.
  • editable={false} hides write controls and rejects document-changing transactions, including direct editor commands. Invalid input freezes writes and reports through onError(error) while retaining the last valid document.
  • uploadImage(file) resolves to a hosted URL or { src, alt?, caption? }. The component handles loading, errors, retry and cancellation; authentication/storage/size policy belong to the host. Keep callbacks free of credentials in document JSON.
  • mentionSuggestions accepts { id, label }[]. Select a candidate through the toolbar, @ entry or slash command.
  • locale="zh-CN", translations and labels customize messages. placeholder, className and classNames customize presentation.
  • useFolioEditor exposes the underlying editor for custom shells. Standalone menus and controls are also exported.

Block + inserts a paragraph below the current top-level block. Hover a block and drag its six-dot handle to reorder it, or click the handle for block actions. A blue line identifies a compatible destination inside the same editor/document, including nested callouts, list items and columns. Hold near a scroll edge to scroll, and press Escape to cancel. Tables stay intact; invalid structural drops are rejected. Each move is a separate undo step. Escape selects the current top-level block; Shift+ArrowUp/ArrowDown extends the range, and a selected range can be copied, duplicated, deleted or dragged together. Alt+Shift+ArrowUp/ArrowDown provides keyboard movement. Table edge buttons append to the end; focused-table actions manage rows, columns, headers and cell merging.

ui.toolbar selects default, compact, floating or false; components replaces presentation slots and portalContainer mounts floating UI outside the editor. Ctrl/Cmd+F opens find/replace; Ctrl/Cmd+Shift+V pastes plain text. collaboration, comments and history accept host adapters. The demo's collaboration transport, comments and versions are in memory and reset on reload; production transport, storage and server authorization belong to the host. Columns and linked block metadata require JSON or HTML for lossless export; Markdown rejects them explicitly. See the integration guide for contracts and local acceptance steps.

Safe persistence and recovery

import {
  createFolioSnapshot,
  parseFolioSnapshot,
  validateFolioDocument,
} from "@futelab/folio/core";

const stored = JSON.stringify(createFolioSnapshot(document));
// Returns { format: "folio", version: 1, document } and migrates legacy bare JSON.
const loaded = parseFolioSnapshot(stored).document;
const result = validateFolioDocument(loaded); // { valid, errors: [{ code, path, message }] }

parseFolioDocument and parseFolioSnapshot throw FolioDocumentValidationError with issues. isFolioDocument performs validation, rather than checking only the root type. Future snapshot versions and unsupported data are rejected. Catch failures, retain the original saved value, offer a backup/recovery UI and avoid replacing failed loads with an automatically saved empty document. The demo demonstrates this behavior, quota failures and legacy migration.

Default validation limits are 10,000 nodes, depth 64 and two million text/attribute characters. Configure limits for your application. createFolioDocumentValidator(options) compiles a reusable validator for a stable schema. Snapshot JSON is separate from the package version.

Reader and extensions

FolioReader accepts value: unknown, extensions, headingIdPrefix, limits, maxRenderedNodes, onError, renderError, className and classNames.root. It preserves list start numbers, table spans/widths, code languages and image captions. Invalid, unknown or failing content produces visible diagnostics. Reader onError is a synchronous render diagnostic: it must be pure and may run more than once. Use renderError to return fallback UI; do not set React state in that callback.

Readers in one React root get unique IDs automatically. For independent roots or renderFolioNode calls, pass distinct, stable headingIdPrefix values. Rendering defaults to 50,000 generated elements, including table-of-contents expansion.

An extension can contribute tiptapExtensions, command metadata and renderers. A renderer has { type, kind?: "node" | "mark", render(node, { children, path }) }; register it with the same extensions in the reader. Node/mark schema definitions come from Tiptap. Extension names and contributions must be unique; custom command names override matching defaults. Pass the same extensions to serialization. Conversion rejects nodes without the required format handlers instead of silently discarding them.

HTML and Markdown

import { fromHTML, toHTML, fromMarkdown, toMarkdown } from "@futelab/folio/serialization";
const document = fromHTML("<h2>Notes</h2><p>Hello</p>");
const html = toHTML(document);
const markdown = toMarkdown(document);
const parsed = fromMarkdown(markdown);

Conversions accept a second { extensions, limits } argument. HTML uses the configured schema, validates URLs and rejects unsupported or active markup. It exports a populated table of contents, visible image captions and bookmark descriptions. Reader, media input and conversions share the URL policy: HTTP/HTTPS and safe relative image URLs, raster base64 images, and HTTP/HTTPS/mailto/tel/safe relative links. SVG data URLs and unsafe protocols are rejected.

Markdown export supports the lossless subset: basic blocks, lists/tasks, code, quotes, dividers, plain images and common marks. Tables, callouts, bookmarks, toggles, captions, colors, underline and unsupported attributes raise FolioMarkdownSerializationError. Custom extensions require compatible Markdown handlers and must pass round-trip checks. Raw HTML tokens in Markdown are rejected consistently in Node and browsers; use fromHTML for HTML input. Fenced/inline code and Markdown autolinks remain valid.

Tiptap generates matching id/data-toc-id pairs on headings. To explicitly discard those regenerable anchors when exporting Markdown, use toMarkdown(document, { headingIds: "omit-matching-toc" }). The default preserves IDs and reports unrepresentable attributes. Standalone or mismatched author IDs are always preserved/rejected, never silently discarded; the input document is not mutated.

Styling

Override --folio-color-bg, --folio-color-text, --folio-color-border, --folio-color-accent, --folio-font-family and related variables globally or on a wrapping element. Editor class hooks are root, content, blockControls, bubbleToolbar and slashMenu. Menus, dialogs, table controls and reader diagnostics are included in the CSS entry.

Develop and verify

pnpm install --frozen-lockfile
pnpm demo
pnpm check
pnpm exec playwright install chromium firefox webkit
pnpm test:e2e
pnpm test:package

The demo runs at http://127.0.0.1:55399/. pnpm format/format:check use Oxfmt; pnpm lint/lint:fix use Oxlint. No ESLint or Prettier process is required.

CI checks formatting, lint, types, unit/Node SSR tests, builds, production dependency advisories, Chromium/Firefox/WebKit/mobile flows and isolated React 18/19/Next.js tarball consumers. Consumer checks cover ESM/CJS types and runtime, SSR, production bundles and browser hydration/interactions. Release and Pages setup documents the external npm authorization requirements and tag workflow.