Skip to content

hasna/docs

Repository files navigation

@hasna/docs

npm license

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` CLI

React is an optional peer dependency; install it only if you use @hasna/docs/react:

bun add react react-dom

Headless 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 stats

Development

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 demo

License

MIT © Hasna. Built on TipTap and ProseMirror (both MIT).

About

Headless rich-text document SDK over the ProseMirror/TipTap JSON model — Markdown/HTML/JSON import-export, outline + word-count, plus a TipTap React Editor

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors