Novel Headless Editor Framework

repository·main·Indexed 12 days ago

https://github.com/steven-tey/novel

A headless editor framework built on Tiptap that provides specialized components for advanced features like bubble menus, slash commands, and AI-powered text generation. It includes pre-configured Tiptap extensions for Tailwind CSS, support for KaTeX mathematics, and integrated image upload capabilities via Vercel Blob.

Tokens
13.6K
Snippets
58
Records
63
Agent score
94%

What's inside Novel

  1. Understand the default editor content structure

    main

    The editor uses a JSON-based document format (compatible with Tiptap/ProseMirror) to represent content. The defaultEditorContent object provides a template that includes various node types such as heading, paragraph, codeBlock, orderedList, taskList, image, twitter, and math. This structure can be used to initialize the editor with pre-defined content.

    {
      "type": "doc",
      "content": [
        {
          "type": "heading",
          "attrs": { "level": 2 },
          "content": [{ "type": "text", "text": "Introducing Novel" }]
        },
        {
          "type": "paragraph",
          "content": [
            { "type": "text", "text": "Novel is a Notion-style WYSIWYG editor..." }
          ]
        }
      ]
    }
  2. Configure the image upload API with Vercel Blob

    main

    The image upload functionality in Novel relies on a Next.js Edge Runtime API route that uses @vercel/blob to store files. To enable this endpoint, you must provide a BLOB_READ_WRITE_TOKEN in your environment variables.

    When making a POST request to this endpoint, the server expects:

    • The file content in the request body.
    • An x-vercel-filename header to specify the filename (defaults to file.txt).
    • A content-type header to determine the file extension (e.g., image/png results in a .png suffix).

    The API returns a JSON response containing the blob metadata, including the URL to the uploaded file.

    export const runtime = "edge";
    
    // Required Environment Variable:
    // BLOB_READ_WRITE_TOKEN
  3. Use pre-configured Novel extensions

    main

    The packages/headless/src/extensions/index.ts entrypoint exports a curated set of Tiptap extensions pre-configured for the Novel editor experience. Instead of configuring each Tiptap extension manually, you can import these pre-configured versions to ensure consistent behavior for features like placeholders, markdown handling, and highlighting.

    Key pre-configured extensions include:

    • Placeholder: Configured to show Heading X for heading nodes and Press '/' for commands for other nodes.
    • HighlightExtension: Configured with multicolor: true.
    • MarkdownExtension: Configured with html: false and transformCopiedText: true.
    • HorizontalRule: An extended version of the standard horizontal rule that supports markdown-style input rules (e.g., ---, ___, or ***).

    Other available extensions include StarterKit, TaskItem, TaskList, TiptapImage, TiptapUnderline, TextStyle, Color, Youtube, Twitter, Mathematics, ImageResizer, and SlashCommand (via ai-highlight or slash-command exports).

    import {
      Placeholder,
      HighlightExtension,
      MarkdownExtension,
      HorizontalRule,
      StarterKit,
      // ... other extensions
    } from "@novel/headless/extensions";
    
    // Use them in your Tiptap editor configuration
    const editor = useEditor({
      extensions: [
        StarterKit,
        Placeholder,
        HighlightExtension,
        MarkdownExtension,
        HorizontalRule,
      ],
    });
  4. Set up the Novel editor with EditorRoot and EditorContent

    main

    To use the Novel editor, you must wrap your editor components in EditorRoot to provide the necessary state management (via jotai) and command tunneling. Inside EditorRoot, use EditorContent to render the actual Tiptap-based editor instance.

    EditorRoot initializes the novelStore and sets up the EditorCommandTunnelContext required for editor commands to function.

    EditorContent is a wrapper around Tiptap's EditorProvider. It accepts initialContent as JSONContent to pre-populate the editor and allows for custom styling via className or passing additional EditorProviderProps.

    import { EditorRoot, EditorContent } from "@novel/headless";
    
    function MyEditor() {
      return (
        <EditorRoot>
          <EditorContent 
            initialContent={myJsonContent} 
            className="my-editor-class"
          />
        </EditorRoot>
      );
    }
  5. Configure the Mathematics extension

    main

    The Mathematics extension provides support for LaTeX mathematical symbols using KaTeX. When implementing this extension, you must import the KaTeX CSS for proper rendering:

    import 'katex/dist/katex.min.css';

    Configuration Options

    OptionTypeDefaultDescription
    shouldRender(state: EditorState, pos: number) => boolean(state, pos) => { ... }Determines if LaTeX decorations should render. By default, it returns false if the expression is inside a codeBlock or not in a text block.
    katexOptionsKatexOptions{ throwOnError: false }Configuration options passed directly to katex.renderToString. See KaTeX documentation for available keys.
    HTMLAttributesRecord<string, any>{}Custom HTML attributes to be applied to the rendered math element.
    import { Mathematics } from '@novel/headless'; // Adjust import path based on your setup
    
    const extension = Mathematics.configure({
      katexOptions: {
        throwOnError: false,
      },
      HTMLAttributes: {
        class: 'my-custom-math-class',
      },
    });
  6. Configure OpenAI for AI features

    main

    To enable AI-powered capabilities in Novel, you must provide an OpenAI API key. You can optionally specify a custom base URL if you are using a proxy or a compatible alternative provider.

    • OPENAI_API_KEY: Your OpenAI API key (required).
    • OPENAI_BASE_URL: The base URL for the OpenAI API (optional, defaults to https://api.openai.com/v1).
    OPENAI_API_KEY=
    OPENAI_BASE_URL=
  7. Configure environment variables for AI generation

    main

    To use the AI generation endpoint, the following environment variables must be configured:

    • OPENAI_API_KEY: Required for authenticating with OpenAI. If missing or empty, the API returns a 400 Bad Request error.
    • KV_REST_API_URL and KV_REST_API_TOKEN: If these are provided, the endpoint enables rate limiting via Upstash Ratelimit and Vercel KV.

    Note: The endpoint is configured to run on the edge runtime.

  8. Configure Vercel Blob for image uploads

    main

    To allow users to upload images within the editor, configure Vercel Blob storage using the following environment variable:

    • BLOB_READ_WRITE_TOKEN: Your Vercel Blob read/write token (optional, required for image upload functionality).
    BLOB_READ_WRITE_TOKEN=
  9. Configure Vercel KV for rate limiting

    main

    To enable rate limiting (e.g., to prevent abuse of AI features), configure Vercel KV using these environment variables:

    • KV_REST_API_URL: The REST API URL for your Vercel KV instance (optional).
    • KV_REST_API_TOKEN: The REST API token for your Vercel KV instance (optional).
    KV_REST_API_URL=
    KV_REST_API_TOKEN=
  10. Configure the Twitter extension

    main

    The Twitter extension can be customized using the TwitterOptions interface when initializing the extension.

    Key options include:

    • addPasteHandler (boolean): Enables or disables the automatic conversion of Twitter/X URLs pasted into the editor into tweet embeds. Defaults to true.
    • inline (boolean): Determines if the tweet node is treated as an inline element or a block element. Defaults to false.
    • origin (string): The origin of the tweet. Defaults to ''.
    • HTMLAttributes (Record<string, any>): Custom HTML attributes to be applied to the tweet node.
    Twitter.configure({
      addPasteHandler: true,
      inline: false,
      origin: 'https://example.com',
      HTMLAttributes: {
        class: 'my-custom-tweet-class'
      }
    })
  11. Troubleshoot AI generation errors

    main

    Common errors when interacting with the /api/generate endpoint:

    • 400 Bad Request: Missing OPENAI_API_KEY - make sure to add it to your .env file. This occurs if the OPENAI_API_KEY environment variable is not set.
    • 429 Too Many Requests: You have reached your request limit for the day. This occurs if rate limiting is enabled (via Vercel KV) and the user has exceeded the sliding window limit (configured as 50 requests per day). The response includes headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset for client-side handling.