Desishub Lessons
Next JS and TypeScript
Novel Editor

Novel Editor v1 Setup

Novel (opens in a new tab) is a Notion-style WYSIWYG editor with AI-powered autocompletion. Built with Tiptap (opens in a new tab) + Vercel AI SDK (opens in a new tab).

Installation

Novel has been updated to newer versions but to follow along install the "novel": "^0.1.22", version: Checkout the documentation for the latest version : Novel docs (opens in a new tab)

npm i novel@0.1.22

Basic Usage

import { Editor } from "novel";
 
export default function App() {
  return <Editor />;
}

Advanced Usage

Create a component to dispay the editor

import NovelEditor from "@/components/global/FormInputs/NovelEditor";
 
export default function App() {
  const [content, setContent] = useState<string | undefined>(initialContent);
  return (
    <div className="">
      <NovelEditor
        content={content}
        setContent={setContent}
        title="Product Content"
      />
    </div>
  );
}

Create the NovelEditor Component

import React from "react";
import { Editor } from "novel";
import { type Editor as TipTapEditor } from "@tiptap/core";
import { Card, CardContent } from "@/components/ui/card";
 
type NovelEditorProps = {
  setContent: any;
  title: string;
  content: string | undefined;
};
export default function NovelEditor({
  setContent,
  content,
  title,
}: NovelEditorProps) {
  return (
    <Card className="">
      <CardContent>
        <h2 className="pt-4 pb-3">{title}</h2>
        <Editor
          defaultValue={{
            type: "doc",
            content: [],
            // content: content as JSONContent[] | undefined,
          }}
          onDebouncedUpdate={(editor?: TipTapEditor) => {
            setContent(editor?.getHTML());
          }}
          disableLocalStorage={true}
          className="rounded-md border shadow-none"
        />
      </CardContent>
    </Card>
  );
}

Render the HTML

npm i html-react-parser
 

Create the Component to render the parsed html

"use client";
import React from "react";
import parse from "html-react-parser";
export default function ProductContent({
  codeString,
}: {
  codeString: string | null | undefined;
}) {
  return <div className="parsed-html">{parse(`${codeString}`)}</div>;
}

Add the Styles to rendered html

.parsed-html h1 {
  @apply text-2xl font-bold my-4;
}
 
.parsed-html h2 {
  @apply text-xl font-bold my-4;
}
 
.parsed-html h3 {
  @apply text-lg font-bold my-4;
}
 
.parsed-html h4 {
  @apply text-base font-bold my-4;
}
 
.parsed-html p {
  @apply text-base leading-relaxed my-2;
}
 
.parsed-html img {
  @apply max-w-full h-auto my-4 block;
}
 
.parsed-html code {
  @apply bg-gray-100 px-2 py-1 rounded font-mono;
}
 
.parsed-html ul {
  @apply list-disc my-4 pl-8;
}
 
.parsed-html ul li {
  @apply my-2;
}
 
.parsed-html ol {
  @apply list-decimal my-4 pl-8;
}
 
.parsed-html ol li {
  @apply my-2;
}
 
.parsed-html blockquote {
  @apply border-l-4 border-gray-300 pl-4 my-4 text-gray-600 italic;
}
 
.parsed-html pre {
  @apply bg-gray-100 p-4 rounded overflow-x-auto font-mono;
}
 
.parsed-html a {
  @apply text-blue-600 no-underline hover:underline;
}
 
.parsed-html table {
  @apply border-collapse w-full my-4;
}
 
.parsed-html th,
.parsed-html td {
  @apply border border-gray-300 px-2 py-1 text-left;
}
 
.parsed-html th {
  @apply bg-gray-100 font-bold;
}
 
/* .novel-list-disc {
  list-style: circle;
} */