@hasna/docs
v0.1.0
Published
Headless rich-text document SDK over the ProseMirror/TipTap JSON model — create/edit documents, import/export Markdown, HTML, and JSON, extract outlines and word counts, plus a TipTap-based React <Editor>
Maintainers
Readme
@hasna/docs
Headless rich-text document SDK built on the ProseMirror /
TipTap document model, plus a ready-to-drop-in TipTap-based React
<Editor>.
- Headless core (
@hasna/docs) — framework-agnostic and dependency-free. Runs anywhere (Node, Bun, edge, browser) with no DOM. Create and edit documents, import/export Markdown, HTML, and JSON, and extract outlines + word counts. - React editor (
@hasna/docs/react) — a TipTap-powered<Editor>with a formatting toolbar (bold/italic/strike/code, headings, lists, links, blockquote, code block, undo/redo). It emits and accepts the same JSON the headless SDK reads. - CLI (
docs) — convert document files between Markdown/HTML/JSON/text and print outlines and statistics.
The document JSON is shape-compatible with ProseMirror/TipTap, so anything the editor produces round-trips through the SDK unchanged.
Install
bun add @hasna/docs # or: npm i @hasna/docs
bun install -g @hasna/docs # for the `docs` CLIReact is an optional peer dependency; install it only if you use @hasna/docs/react:
bun add react react-domHeadless SDK
import { Document, createDocument, heading, paragraph, text } from "@hasna/docs";
// Build a document programmatically
const doc = createDocument()
.setContent([heading(1, [text("Release notes")])])
.append(paragraph([text("Ships today.")]));
doc.toMarkdown(); // "# Release notes\n\nShips today.\n"
doc.toHTML(); // "<h1>Release notes</h1>\n<p>Ships today.</p>"
doc.outline(); // [{ level: 1, text: "Release notes", index: 0, id: "release-notes" }]
doc.stats(); // { words, characters, readingTimeMinutes, ... }
// Import from Markdown or HTML
const fromMd = Document.fromMarkdown("# Hi\n\n- a\n- b");
const fromHtml = Document.fromHTML("<h1>Hi</h1><ul><li>a</li></ul>");
// Load / validate existing ProseMirror-TipTap JSON
const loaded = Document.fromJSON(fromMd.toJSON());Functional helpers are exported too (toMarkdown, fromMarkdown, toHTML, fromHTML,
getOutline, countWords, applyStep, node builders, …). See
docs/sdk.md.
Programmatic edits (steps)
import { applyStep, appendParagraph } from "@hasna/docs";
const next = applyStep(doc.toJSON(), {
type: "insertNode",
index: 0,
node: { type: "heading", attrs: { level: 2 }, content: [{ type: "text", text: "Intro" }] },
});Steps are a small, serializable JSON model — easy to log, queue, and replay server-side.
React editor
import { useState } from "react";
import { Editor } from "@hasna/docs/react";
import { toMarkdown } from "@hasna/docs";
import type { DocJSON } from "@hasna/docs";
export function MyEditor() {
const [doc, setDoc] = useState<DocJSON>();
return (
<>
<Editor markdown="# Start typing" onChange={setDoc} />
<button onClick={() => console.log(doc && toMarkdown(doc))}>Export</button>
</>
);
}The component ships no CSS of its own — it renders semantic classes
(hasna-docs-editor, hasna-docs-toolbar, hasna-docs-toolbar__button[data-active]) you
style yourself. See docs/react-editor.md and the dashboard/ demo.
CLI
docs convert notes.md --to html # Markdown/HTML/JSON -> md | html | json | text
docs outline notes.md # print the heading outline
docs stats notes.md # word / character / reading-time statsDevelopment
bun install
bun test # unit tests
bun run typecheck # tsc --noEmit
bun run build # library + react + cli + type declarations
cd dashboard && bun install && bun run dev # live editor demoLicense
MIT © Hasna. Built on TipTap and ProseMirror (both MIT).
