npm.io
0.0.12 • Published 8m ago

@import-ai/omnibox-editor

Licence
MIT
Version
0.0.12
Deps
44
Size
1.5 MB
Vulns
0
Weekly
0

@import-ai/omnibox-editor

A production-ready React block editor built on Tiptap. It ships a complete editor UI, Markdown/JSON conversion helpers, table editing, image upload, math, diagrams, table of contents, optional AI writing hooks, and optional Yjs collaboration support.

Features

  • React component with a batteries-included Tiptap editor experience
  • Editable and read-only rendering modes
  • Markdown input, Tiptap JSON input, and Markdown export helpers
  • GitHub-like Markdown styling for headings, lists, links, code, blockquotes, tables, images, and math
  • Notion-like table editing with row/column handles, context menus, resizing, merge/split, sorting, alignment, and fit-to-width actions
  • Image upload pipeline with progress, abort, success, error, size, and count controls
  • KaTeX math rendering and in-editor formula editing
  • Mermaid/ECharts-style diagram rendering from code blocks
  • Optional table of contents sidebar
  • Optional AI writing integration through host-provided handlers
  • Optional Yjs collaboration and collaborative cursors
  • English and Simplified Chinese UI copy with translation overrides

Installation

npm install @import-ai/omnibox-editor
pnpm add @import-ai/omnibox-editor
yarn add @import-ai/omnibox-editor

react and react-dom are peer dependencies and must be installed by the consuming app.

npm install react react-dom

Quick Start

Import the component and the compiled CSS once in your app.

import { OmniboxEditor } from "@import-ai/omnibox-editor"
import "@import-ai/omnibox-editor/style.css"

export function App() {
  return (
    <OmniboxEditor
      placeholder="Start writing..."
      onUpdate={({ json, html, markdown }) => {
        console.log(json, html, markdown)
      }}
    />
  )
}

Use Tiptap JSON as the canonical format in production. It preserves editor-specific structures such as tables, math, images, alignment, colors, and custom nodes more reliably than plain Markdown.

import { OmniboxEditor, contentToMarkdown } from "@import-ai/omnibox-editor"
import "@import-ai/omnibox-editor/style.css"

export function ResourceEditor({
  initialContent,
  saveResource,
}: {
  initialContent: string
  saveResource: (content: unknown) => Promise<void>
}) {
  return (
    <OmniboxEditor
      content={initialContent}
      onUpdate={({ json }) => {
        void saveResource(json)
      }}
    />
  )
}

export function exportMarkdown(savedContent: unknown) {
  return contentToMarkdown(savedContent as Parameters<typeof contentToMarkdown>[0], {
    debug: false,
  })
}

For backward compatibility, content accepts both existing Markdown strings and serialized JSON strings. New writes should store json from onUpdate.

Basic Usage

Markdown Input
<OmniboxEditor content={"# Hello\n\nThis content starts as Markdown."} />
Tiptap JSON Input
<OmniboxEditor
  content={{
    type: "doc",
    content: [
      {
        type: "heading",
        attrs: { level: 1 },
        content: [{ type: "text", text: "Hello" }],
      },
    ],
  }}
/>
Serialized JSON String Input
<OmniboxEditor content={JSON.stringify(savedTiptapJson)} />
Read-Only Rendering
<OmniboxEditor content={savedContent} editable={false} showHeader={false} />
Embedded Mode

Use variant="embedded" when the editor is rendered inside an existing page shell or panel.

<OmniboxEditor
  variant="embedded"
  content={savedContent}
  contentWidth="100%"
  showHeader={false}
  showToc={false}
/>

Component API

import type { OmniboxEditorProps } from "@import-ai/omnibox-editor"
Prop Type Default Description
content Content | string demo content Initial or externally controlled content. Accepts Markdown, serialized JSON, Tiptap JSON documents, or Tiptap node arrays.
editable boolean true Enables editing. Set to false for read-only rendering.
placeholder string locale placeholder Placeholder shown in empty editable blocks.
onUpdate (payload) => void undefined Called after editor content changes. Use payload.json as the preferred saved value.
imageUpload UploadFunction built-in object URL fallback Custom image uploader. Should return the final image URL.
imageUploadMaxSize number package default Maximum image size in bytes.
imageUploadLimit number 3 Maximum number of images accepted per upload operation.
onImageUploadError (error: Error) => void logs to console Called when image upload fails.
onImageUploadSuccess (url: string) => void undefined Called when image upload succeeds.
linkBase string undefined Base URL used to resolve relative links and image sources while parsing Markdown/content.
locale "en" | "zh-CN" | string "en" Built-in UI locale.
translations OmniboxEditorTranslations undefined Overrides individual UI labels.
theme "light" | "dark" undefined Applies package dark-mode classes when set to "dark".
variant "page" | "embedded" "page" Page mode shows the default editor chrome; embedded mode is more compact.
contentWidth number | string CSS default Sets the editor content width. Numbers are treated as pixels.
showHeader boolean true in page mode Controls the top editor header.
showToc boolean true in page mode Controls the table of contents sidebar.
mentionUsers OmniboxEditorMentionUser[] undefined Users shown in the mention dropdown.
user OmniboxEditorCollaborationUser undefined Current user identity for local UI and collaboration fallback.
collaboration false | object undefined Yjs collaboration config.
ai boolean | OmniboxEditorAiConfig undefined Enables the AI UI and optionally provides an AI submit handler.

Update Payload

onUpdate receives the live editor instance plus multiple content formats.

import type { OmniboxEditorUpdatePayload } from "@import-ai/omnibox-editor"

type OmniboxEditorUpdatePayload = {
  editor: Editor
  json: JSONContent
  html: string
  markdown: string
}

Recommended usage:

  • Save json to your backend.
  • Use markdown for export/download/copy workflows.
  • Use html only when your application explicitly needs HTML output.
  • Debounce or autosave outside the package if the backend should not receive every keystroke.

Markdown and JSON Helpers

The package exports conversion helpers for import/export workflows.

import {
  contentToMarkdown,
  contentToTiptapJson,
  htmlTableToTiptapNode,
  markdownToTiptapJson,
  markdownWithHtmlTablesToTiptapJson,
  tiptapJsonToMarkdown,
  type MarkdownParseOptions,
  type TiptapJsonContent,
} from "@import-ai/omnibox-editor"
API Description
markdownToTiptapJson(markdown, options?) Parses Markdown into a Tiptap JSON document using Tiptap's official MarkdownManager, then applies package normalization for links, images, tables, and diagrams.
markdownWithHtmlTablesToTiptapJson(markdown, options?) Parses Markdown that may contain raw HTML <table> blocks. HTML tables are converted into Tiptap table nodes.
contentToTiptapJson(content, options?) Accepts Markdown, serialized JSON, or JSON content and returns a normalized Tiptap JSON document. Use this when the input format is unknown.
tiptapJsonToMarkdown(json, options?) Serializes Tiptap JSON back to Markdown. Merged tables are emitted as HTML tables when Markdown tables cannot represent them safely.
contentToMarkdown(content, options?) Accepts Markdown, serialized JSON, or JSON content and returns Markdown. Use this for export from saved server content.
htmlTableToTiptapNode(html) Converts a single HTML table string into a Tiptap table node.
Markdown Parse Options
type MarkdownParseOptions = {
  debug?: boolean
  linkBase?: string
}
Option Default Description
debug true Logs conversion input/output to the console. Set debug: false in production import/export paths.
linkBase undefined Resolves relative links and image sources against a base URL.

Production example:

const json = contentToTiptapJson(serverContent, {
  linkBase: "https://example.com/resources/current-folder",
  debug: false,
})

const markdown = contentToMarkdown(json, { debug: false })

Image Upload

Provide imageUpload to connect the editor to your storage service.

import { OmniboxEditor, type UploadFunction } from "@import-ai/omnibox-editor"
import "@import-ai/omnibox-editor/style.css"

const uploadImage: UploadFunction = async (file, onProgress, abortSignal) => {
  const formData = new FormData()
  formData.append("file", file)

  const response = await fetch("/api/uploads", {
    method: "POST",
    body: formData,
    signal: abortSignal,
  })

  if (!response.ok) {
    throw new Error("Image upload failed")
  }

  onProgress?.({ progress: 100 })

  const result = (await response.json()) as { url: string }
  return result.url
}

export function Editor() {
  return (
    <OmniboxEditor
      imageUpload={uploadImage}
      imageUploadMaxSize={10 * 1024 * 1024}
      imageUploadLimit={5}
      onImageUploadError={(error) => {
        console.error(error)
      }}
    />
  )
}

The upload function must resolve with the final URL that should be stored in the document.

AI Integration

The package renders the AI UI only when ai is enabled. The host application owns the actual model call.

import type { OmniboxEditorAiSubmitPayload } from "@import-ai/omnibox-editor"

async function handleAiSubmit({
  action,
  prompt,
  signal,
  onChunk,
}: OmniboxEditorAiSubmitPayload) {
  const response = await fetch("/api/ai/write", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ action, prompt }),
    signal,
  })

  if (!response.ok || !response.body) {
    throw new Error("AI request failed")
  }

  const reader = response.body.getReader()
  const decoder = new TextDecoder()

  while (true) {
    const { done, value } = await reader.read()
    if (done) break
    onChunk?.(decoder.decode(value))
  }
}

<OmniboxEditor
  ai={{
    enabled: true,
    onSubmit: handleAiSubmit,
  }}
/>

onSubmit can stream plain Markdown through onChunk, or provide structured Tiptap JSON through onContent / onContentPreview.

Collaboration

Collaboration uses Yjs through Tiptap's collaboration extensions. Pass a Yjs document and, optionally, a provider with awareness support.

import * as Y from "yjs"
import { OmniboxEditor } from "@import-ai/omnibox-editor"
import "@import-ai/omnibox-editor/style.css"

const document = new Y.Doc()

<OmniboxEditor
  collaboration={{
    document,
    provider,
    user: {
      id: "user-1",
      name: "Ada",
      color: "#3b82f6",
      avatar: "https://example.com/avatar.png",
    },
  }}
/>

When collaboration is enabled, the package disables StarterKit undo/redo and does not sync external content prop updates after initialization. Treat the Yjs document as the source of truth.

Mentions

<OmniboxEditor
  mentionUsers={[
    {
      id: "user-1",
      name: "Ada Lovelace",
      color: "#3b82f6",
      avatar: "https://example.com/avatar.png",
      position: "Engineer",
    },
  ]}
/>

Internationalization

Use locale for the built-in language, and translations for targeted overrides.

<OmniboxEditor
  locale="zh-CN"
  translations={{
    placeholder: "开始写点什么...",
    uploadFailed: "图片上传失败",
  }}
/>

You can also read the merged labels:

import { getEditorTranslations } from "@import-ai/omnibox-editor"

const labels = getEditorTranslations("zh-CN", {
  placeholder: "开始写点什么...",
})

Styling

The package ships compiled CSS at:

import "@import-ai/omnibox-editor/style.css"

The CSS includes editor layout, toolbar UI, table controls, GitHub-like Markdown rules, image states, KaTeX styles, and component variables. It is generated from Tailwind CSS v4 layers and does not require Sass.

Set dark mode with the component prop:

<OmniboxEditor theme="dark" />

Constrain content width with contentWidth:

<OmniboxEditor contentWidth={860} />
<OmniboxEditor contentWidth="min(100%, 920px)" />

Exported API

Components
export { OmniboxEditor }
Conversion Helpers
export {
  contentToMarkdown,
  contentToTiptapJson,
  htmlTableToTiptapNode,
  markdownToTiptapJson,
  markdownWithHtmlTablesToTiptapJson,
  tiptapJsonToMarkdown,
}
Utilities
export { getEditorTranslations }
Types
export type {
  EditorProviderProps,
  MarkdownParseOptions,
  OmniboxEditorAiAction,
  OmniboxEditorAiConfig,
  OmniboxEditorAiFeature,
  OmniboxEditorAiSubmitPayload,
  OmniboxEditorCollaborationConfig,
  OmniboxEditorCollaborationProvider,
  OmniboxEditorCollaborationUser,
  OmniboxEditorLocale,
  OmniboxEditorMentionUser,
  OmniboxEditorProps,
  OmniboxEditorTheme,
  OmniboxEditorTranslations,
  OmniboxEditorUpdatePayload,
  TiptapJsonContent,
  UploadFunction,
}

Production Checklist

  • Import @import-ai/omnibox-editor/style.css once at the application entry.
  • Save onUpdate().json as the canonical document value.
  • Use contentToMarkdown(content, { debug: false }) for Markdown export.
  • Set debug: false in conversion helpers used by production code.
  • Provide a real imageUpload implementation before enabling image uploads for users.
  • Enforce file size, MIME type, authentication, authorization, and malware scanning on the upload API.
  • Debounce autosave outside the editor if you persist on onUpdate.
  • Use editable={false} for read-only views.
  • Pass linkBase when rendering legacy Markdown with relative links or images.
  • Keep collaboration state in Yjs when collaboration is enabled.
  • Keep AI credentials and model calls on your server; never expose secrets in the browser.

Browser and SSR Notes

@import-ai/omnibox-editor is a browser-facing React component. In SSR frameworks, render it only on the client.

Next.js example:

"use client"

import { OmniboxEditor } from "@import-ai/omnibox-editor"
import "@import-ai/omnibox-editor/style.css"

export default function EditorPage() {
  return <OmniboxEditor />
}

Local Development

From the repository root:

pnpm install
pnpm --filter @import-ai/omnibox-editor test
pnpm --filter @import-ai/omnibox-editor build

Create a local tarball:

pnpm --filter @import-ai/omnibox-editor pack:local

License

MIT

Keywords