From 3ebc43b5a054048fa49ac4cdc8a462de6ad1c62f Mon Sep 17 00:00:00 2001 From: Allison Fister Date: Wed, 1 Jan 2025 19:52:18 -0500 Subject: [PATCH] basic editor --- package.json | 4 +- src/app/page.tsx | 39 +++++++++++++----- src/lib/content.ts | 14 +++++++ src/lib/extensions.ts | 95 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 src/lib/content.ts create mode 100644 src/lib/extensions.ts diff --git a/package.json b/package.json index c1a7171..71de888 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "clsx": "^2.1.1", "lucide-react": "^0.469.0", "next": "15.1.2", - "novel": "^0.5.0", + "novel": "0.5.0", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^2.6.0", @@ -33,6 +33,6 @@ "eslint-config-next": "15.1.2", "postcss": "^8", "tailwindcss": "^3.4.1", - "typescript": "^5" + "typescript": "^5.7.2" } } diff --git a/src/app/page.tsx b/src/app/page.tsx index 2b152ee..241e375 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,20 +1,40 @@ 'use client'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { - EditorBubble, EditorContent, - EditorRoot + EditorRoot, + type JSONContent, } from "novel"; +import { defaultEditorContent } from '@/lib/content'; +import { defaultExtensions } from '@/lib/extensions'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; -import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetTrigger, +} from '@/components/ui/sheet'; import { Menu } from 'lucide-react'; export default function Home() { const [repoUrl, setRepoUrl] = useState(''); const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const [initialContent, setInitialContent] = useState(null); + + const extensions = [...defaultExtensions]; + + useEffect(() => { + const content = window.localStorage.getItem("novel-content"); + if (content) setInitialContent(JSON.parse(content)); + else setInitialContent(defaultEditorContent); + }, []); + + if (!initialContent) return null; + return (
{/* Navbar */} @@ -27,11 +47,10 @@ export default function Home() { + + Repository History + {/* Sidebar content */} -
-

Repository History

- {/* Add repository history here */} -
@@ -50,10 +69,10 @@ export default function Home() {
-
diff --git a/src/lib/content.ts b/src/lib/content.ts new file mode 100644 index 0000000..39f3a8c --- /dev/null +++ b/src/lib/content.ts @@ -0,0 +1,14 @@ +export const defaultEditorContent = { + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Text", + }, + ], + }, + ], + }; diff --git a/src/lib/extensions.ts b/src/lib/extensions.ts new file mode 100644 index 0000000..8207433 --- /dev/null +++ b/src/lib/extensions.ts @@ -0,0 +1,95 @@ +/* From https://novel.sh/docs/guides/tailwind/extensions */ + +import { + TiptapImage, + TiptapLink, + UpdatedImage, + TaskList, + TaskItem, + HorizontalRule, + StarterKit, + Placeholder, + } from "novel/extensions"; + + import { cx } from "class-variance-authority"; + + // TODO I am using cx here to get tailwind autocomplete working, idk if someone else can write a regex to just capture the class key in objects + + // You can overwrite the placeholder with your own configuration + const placeholder = Placeholder; + const tiptapLink = TiptapLink.configure({ + HTMLAttributes: { + class: cx( + "text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer", + ), + }, + }); + + const taskList = TaskList.configure({ + HTMLAttributes: { + class: cx("not-prose pl-2"), + }, + }); + const taskItem = TaskItem.configure({ + HTMLAttributes: { + class: cx("flex items-start my-4"), + }, + nested: true, + }); + + const horizontalRule = HorizontalRule.configure({ + HTMLAttributes: { + class: cx("mt-4 mb-6 border-t border-muted-foreground"), + }, + }); + + const starterKit = StarterKit.configure({ + bulletList: { + HTMLAttributes: { + class: cx("list-disc list-outside leading-3 -mt-2"), + }, + }, + orderedList: { + HTMLAttributes: { + class: cx("list-decimal list-outside leading-3 -mt-2"), + }, + }, + listItem: { + HTMLAttributes: { + class: cx("leading-normal -mb-2"), + }, + }, + blockquote: { + HTMLAttributes: { + class: cx("border-l-4 border-primary"), + }, + }, + codeBlock: { + HTMLAttributes: { + class: cx("rounded-sm bg-muted border p-5 font-mono font-medium"), + }, + }, + code: { + HTMLAttributes: { + class: cx("rounded-md bg-muted px-1.5 py-1 font-mono font-medium"), + spellcheck: "false", + }, + }, + horizontalRule: false, + dropcursor: { + color: "#DBEAFE", + width: 4, + }, + gapcursor: false, + }); + + export const defaultExtensions = [ + starterKit, + placeholder, + tiptapLink, + TiptapImage, + UpdatedImage, + taskList, + taskItem, + horizontalRule, + ];