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

@archiva/temper-md

v0.1.0

Published

Markdown-first WYSIWYG editor with configurable AI generation and formatting for React frameworks

Downloads

25

Readme

TemperMD

Markdown-first WYSIWYG editor for React (Next.js, Astro, etc.) with configurable AI generation and formatting endpoints.

Install

temper-md is published as a private npm package (restricted access). Authenticate before installing.

# ~/.npmrc or project .npmrc (read-only token with access to CedarLabs packages)
//registry.npmjs.org/:_authToken=${NPM_TOKEN}

npm install temper-md

Peer dependencies: react >= 18, react-dom >= 18.

Quick start

import {
  EditorContent,
  EditorRoot,
  EMPTY_EDITOR_CONTENT,
  StarterKit,
  MarkdownExtension,
} from "temper-md";

export function Editor() {
  return (
    <EditorRoot
      generateEndpoint="https://api.example.com/ai/generate"
      formatEndpoint="https://api.example.com/ai/format"
      headers={{ Authorization: "Bearer …" }}
    >
      <EditorContent
        initialContent={EMPTY_EDITOR_CONTENT}
        extensions={[StarterKit, MarkdownExtension]}
      />
    </EditorRoot>
  );
}

Component model

The library is headless: it provides editor primitives, Tiptap extensions, and AI wiring. You compose chrome (toolbar, bubble menu, slash menus) yourself, or use the Tailwind reference implementation in apps/web.

EditorRoot          AI config + Jotai store + command tunnel
└── EditorContent   Tiptap EditorProvider wrapper
    ├── slotBefore  Optional fixed toolbar (e.g. EditorToolbar)
    ├── children    EditorCommand, EditorBubble, custom UI
    └── slotAfter   Optional post-content UI (e.g. ImageResizer)

EditorRoot

| Prop | Type | Default | Effect | |------|------|---------|--------| | generateEndpoint | string | "/api/generate" | POST URL for AI writing assistance | | formatEndpoint | string | "/api/format" | POST URL for AI markdown formatting | | headers | Record<string, string> | — | Merged into every AI fetch (auth, tenancy, tracing) |

Provides useAIConfig() to descendants. Wrap once at the top of your editor tree.

EditorContent

Extends Tiptap EditorProvider (minus content). Common props:

| Prop | Type | Effect | |------|------|--------| | initialContent | JSONContent | Document loaded on mount. Use EMPTY_EDITOR_CONTENT for blank. | | extensions | Extension[] | Tiptap extensions (include MarkdownExtension for markdown helpers). | | className | string | Wrapper div class. | | slotBefore | ReactNode | Renders above the editable surface (typical home for a toolbar). | | slotAfter | ReactNode | Renders below the surface (e.g. ImageResizer). | | editorProps | EditorProps | ProseMirror hooks: handlePaste, handleDrop, handleDOMEvents, attributes. | | onUpdate | ({ editor }) => void | Fires on every document change. |

EditorToolbar

Fixed toolbar shell with role="toolbar". Pass your own buttons/selectors as children. Does not include controls by default.

EditorBubble / EditorBubbleItem

Floating menu anchored to a non-empty text selection. Hidden when the selection is empty, the editor is read-only, the selection is an image node, or the selection is a node drag handle.

| Interaction | Component API | |-------------|---------------| | Show on text select | <EditorBubble> with children | | Run action on click | <EditorBubbleItem onSelect={(editor) => …}> | | Placement / lifecycle | tippyOptions (e.g. placement: "top", onHidden) |

Slash & inline commands

| Extension | Trigger | Menu element id | Disabled in | |-----------|---------|-----------------|-------------| | Command (slash) | / | #slash-command | Code blocks | | createCommandExtension({ char: "++" }) | ++ | #ai-command | Code blocks |

Both use createSuggestionItems, renderItems, and <EditorCommand> + <EditorCommandItem> children tunneled from EditorRoot.

Keyboard while a menu is open: , , Enter to navigate/select; Esc closes the popup.

Markdown helpers

| Function | Returns | |----------|---------| | getSelectionMarkdown(editor) | Markdown for the current selection | | getDocumentMarkdown(editor) | Full document markdown | | getPrevTextMarkdown(editor, pos) | Markdown from doc start through pos (used by Continue writing) |

Requires MarkdownExtension in your extension list.

MarkdownReader

Read-only, sanitized markdown renderer for publish/preview views. Unlike the WYSIWYG editor, this path is safe for untrusted markdown input.

import { MarkdownReader } from "temper-md";

<MarkdownReader
  markdown={"# Hello\n\n| A | B |\n|---|---|\n| 1 | 2 |"}
  className="prose"
/>

| Prop | Type | Effect | |------|------|--------| | markdown | string | Markdown source to render | | className | string | Wrapper div classes | | components | Components | Optional react-markdown element overrides |

Pipeline: remark-gfm (tables, task lists, strikethrough) → rehype-sanitize (strip raw HTML) → safe link/image components.

URL safety: safeHref and safeSrc allow http, https, mailto, relative paths, and data:image/ for images. Other protocols (e.g. javascript:) are dropped.

Limitations: Renders markdown only. Editor-only nodes (math, YouTube, Twitter) are not shown unless exported as plain markdown. For pixel-perfect WYSIWYG preview of JSONContent, use a read-only EditorContent instance instead.

The reference demo wraps this as TemperReader in apps/web with Tailwind prose styling and optional storageKey hydration from localStorage.

User interactions

Below is the full interaction surface from the reference demo (TemperEditor in apps/web). When building your own UI, wire the same Tiptap commands or reuse the demo selectors.

Top toolbar (sticky)

Shown via slotBefore={<TopEditorToolbar … />}. Controls apply to the current selection or block at the cursor.

| Control | Action | Tiptap command | |---------|--------|----------------| | Block type dropdown | Text, H1–H3, to-do, bullet/numbered list, quote, code block | clearNodes(), toggleHeading, toggleTaskList, toggleBulletList, toggleOrderedList, toggleBlockquote, toggleCodeBlock | | Link (Lucide link icon) | Set/unset hyperlink URL | setLink({ href }), unsetLink() | | Math (Σ) | Wrap selection in LaTeX, or unset if already math | setLatex({ latex }), unsetLatex() | | Bold / Italic / Underline / Strike / Code | Toggle inline marks | toggleBold, toggleItalic, toggleUnderline, toggleStrike, toggleCode | | Color | Text color + highlight/background | setColor, unsetColor, setHighlight, unsetHighlight | | AI (sparkles) | Opens Generate or Format submenus (see AI features) | Opens AI panel via command bridge | | Save (optional) | Calls your onSave callback | Only rendered when onSave prop is provided |

Selection bubble menu

Same controls as the toolbar (block type, link, math, text marks, color, AI), but appears above selected text. Configure with <EditorBubble> inside EditorContent.

Closing the bubble (click away or hide) also closes any open AI panel and clears AI highlight.

Slash menu (/)

Type / at the start of a line (not inside a code block) to open the block insert menu.

| Item | Effect | |------|--------| | Text | Plain paragraph | | To-do List | Task list with checkboxes | | Heading 1 / 2 / 3 | Section headings | | Bullet List / Numbered List | Lists | | Quote | Blockquote | | Code | Fenced code block | | Table | 3×3 table with header row | | Image | File picker → uploads via your uploadFn | | Youtube | Prompt for URL → embed | | Twitter | Prompt for X/Twitter URL → embed |

Filter items by typing after /. Select with click or keyboard.

Inside a table, the slash menu shows row/column edit actions instead of block inserts. Empty table cells suppress the Press '/' for commands placeholder.

AI inline menu (++)

Type ++ (not inside a code block) for quick AI actions without opening the full panel first.

| Group | Options | |-------|---------| | Generate | Improve writing, Fix grammar, Make shorter, Make longer, Continue writing | | Format | Restructure sections, Fix heading hierarchy, Normalize lists, Clean markdown syntax |

Selecting an item removes the trigger text and opens the matching AI panel with that option pre-run.

Text source rules:

  • Continue writing — markdown from document start through cursor (getPrevTextMarkdown)
  • All other generate options — current selection markdown (getSelectionMarkdown)
  • Format options — selection markdown, or full document if nothing is selected

AI features

Three entry points share the same behavior:

  1. Toolbar / bubble AI menu — sparkles button → Generate or Format submenu
  2. ++ inline menu — runs immediately
  3. Custom prompt — "Custom prompt…" / "Custom format…" opens the panel for free-text zap commands

Generate panel

| Control | Behavior | |---------|----------| | Preset commands | Improve, Fix grammar, Shorter, Longer, Continue (same as ++) | | Custom input + submit | Sends option: "zap" with your command string | | After streaming completes | Replace selection, Insert below, or Discard |

Uses generateEndpoint. Highlights affected text while the panel is open (addAIHighlight / removeAIHighlight).

Format panel

| Control | Behavior | |---------|----------| | Preset commands | Structure, Headings, Lists, Cleanup | | Custom input + submit | Sends option: "zap" with your format instruction | | After streaming completes | Replace selection, Insert below, or Discard |

Uses formatEndpoint. Operates on selection markdown, falling back to the full document.

Keyboard shortcuts

| Shortcut | Effect | |----------|--------| | Cmd/Ctrl + A (first press) | Select all text within the current block/node | | Cmd/Ctrl + A (second press) | Normal select-all | | / / Enter | Navigate slash (/) or AI (++) suggestion menus | | Esc | Close inline suggestion popup or AI panel |

Paste, drop, and images

| Interaction | Hook | Effect | |-------------|------|--------| | Paste image | editorProps.handlePaste + handleImagePaste | Uploads and inserts image at cursor | | Drop image | editorProps.handleDrop + handleImageDrop | Same, when not moving existing content | | Resize | <ImageResizer /> in slotAfter | Drag handles on selected images |

Provide your own uploadFn (see demo createImageUpload / Vercel Blob route).

Drag handle

GlobalDragHandle extension (included in demo defaults) adds a block drag handle for reordering. Tables are included in drag-handle custom nodes.

Reference demo: TemperEditor

The demo wraps the headless primitives with Tailwind UI. Import from your app copy or mirror the pattern in apps/web/components/temper-editor.tsx.

import TemperEditor from "@/components/temper-editor";

<TemperEditor
  generateEndpoint="https://api.example.com/ai/generate"
  formatEndpoint="https://api.example.com/ai/format"
  aiHeaders={{ Authorization: "Bearer …" }}
  initialContent={EMPTY_EDITOR_CONTENT}
  storageKey="my-doc"
  onSave={() => persistToApi()}
  className="rounded-lg border"
/>

| Prop | Type | Effect | |------|------|--------| | generateEndpoint | string | Required. POST URL for AI writing assistance | | formatEndpoint | string | Required. POST URL for AI markdown formatting | | aiHeaders | Record<string, string> | Optional headers for AI fetches | | initialContent | JSONContent | Starting document. Defaults to EMPTY_EDITOR_CONTENT. | | storageKey | string | Enables localStorage persistence (JSON + HTML + markdown, 500 ms debounce). Shows save status and word count. | | onSave | () => void | When provided, renders a Save icon button in the toolbar that calls this function. | | className | string | Outer editor container classes. |

The demo app wires these from its own .env via getAIEndpointConfig() — that helper is not part of the component API.

Reference demo: TemperReader

The demo provides a styled reader at apps/web/components/temper-reader.tsx and a /read route.

import TemperReader from "@/components/temper-reader";

<TemperReader storageKey="my-doc" />

When TemperEditor uses the same storageKey, it persists markdown to ${storageKey}-markdown in localStorage. TemperReader reads that key for a live preview workflow: edit on /, read on /read.

AI endpoints

Both endpoints accept POST JSON:

{
  "prompt": "markdown or selected text",
  "option": "continue | improve | shorter | longer | fix | zap | structure | headings | lists | cleanup",
  "command": "optional free-text command for zap",
  "mode": "generate | format"
}

Responses should be Vercel AI SDK data streams (toDataStreamResponse()), or any compatible streaming completion consumed by useCompletion.

| Option | Mode | Purpose | |--------|------|---------| | continue | generate | Extend text from prior context | | improve | generate | Polish clarity and flow | | fix | generate | Grammar and spelling | | shorter | generate | Condense | | longer | generate | Expand | | zap | generate / format | Free-text instruction via command | | structure | format | Improve document flow | | headings | format | Normalize heading levels | | lists | format | Clean up list markdown | | cleanup | format | Fix spacing and syntax |

Configure per environment:

  • generateEndpoint / formatEndpoint on EditorRoot, or
  • Demo env vars: AI_HARNESS, AI_GENERATE_ENDPOINT, AI_FORMAT_ENDPOINT, AI_ENDPOINT_AUTHORIZATION

Default content

Use EMPTY_EDITOR_CONTENT when no document is provided. Pass initialContent to hydrate from props or your persistence layer.

Exports

  • Editor primitives: EditorRoot, EditorContent, EditorBubble, EditorBubbleItem, EditorToolbar, EditorCommand, EditorCommandItem, EditorCommandList, EditorCommandEmpty, useEditor
  • Reader: MarkdownReader, MarkdownReaderProps
  • Slash / command extensions: Command, createCommandExtension, createSuggestionItems, renderItems, handleCommandNavigation
  • Markdown: getSelectionMarkdown, getDocumentMarkdown, getPrevTextMarkdown, MarkdownExtension
  • URL safety: safeHref, safeSrc
  • AI: AIConfigProvider, useAIConfig, AIHighlight, addAIHighlight, removeAIHighlight, request types
  • Media: UploadImagesPlugin, handleImagePaste, handleImageDrop, ImageResizer, UpdatedImage
  • Tiptap extensions: StarterKit, TiptapLink, TiptapUnderline, Mathematics, Youtube, Twitter, Table, TableRow, TableCell, TableHeader, and more — see package exports

See apps/web for the full Tailwind reference implementation with toolbar, bubble menu, slash commands, AI panels, and reader view.