+
{options.map((item, idx) => (
{index === idx && (
AigenerateContent({idx, editor,setGeneratingfn })}
- className="absolute inset-0 block h-full w-full rounded-xl bg-[#33393D]"
+ onClick={() =>
+ AigenerateContent({ idx, editor, setGeneratingfn })
+ }
+ className="absolute select-none inset-0 block h-full w-full rounded-xl bg-[#33393D]"
layoutId="hoverBackground"
initial={{ opacity: 0 }}
animate={{
@@ -109,36 +115,63 @@ function ToolBar({editor, setGeneratingfn}: {editor:Editor, setGeneratingfn: (v
/>
)}
+
{item}
+
))}
);
}
-async function AigenerateContent({idx, editor, setGeneratingfn}:{idx:number, editor:Editor, setGeneratingfn: (v:boolean) => void}){
- setGeneratingfn(true);
- const from = editor.view.state.selection.from
- const to = editor.view.state.selection.to
- editor.chain().setTextSelection({from, to}).run();
- editor.commands.setAIHighlight({color: "#0538A2"})
- const query = editor.state.doc.textBetween(from, to, " $$$ ")
- console.log("hello there!", from, to)
- setGeneratingfn(false);
+async function AigenerateContent({
+ idx,
+ editor,
+ setGeneratingfn,
+}: {
+ idx: number;
+ editor: Editor;
+ setGeneratingfn: (v: boolean) => void;
+}) {
+ setGeneratingfn(true);
- // const genAI = new GoogleGenerativeAI("AIzaSyDGwJCP9SH5gryyvh65LJ6xTZ0SOdNvzyY");
- // const model = genAI.getGenerativeModel({ model: "gemini-pro"});
+ const {from, to} = editor.view.state.selection;
+ const content = editor.view.state.selection.content();
+ content.content.forEach((v, i)=> {
+ v.forEach((v, i)=> {
+ console.log(v.text)
+ })
+ })
-
+ const transaction = editor.state.tr
+ transaction.replaceRange(from, to, content)
- // const result = (await model.generateContent(`${ty}, ${query}`)).response.text();
+ editor.view.dispatch(transaction)
- // .insertContentAt(
- // {
- // from: from,
- // to: to,
- // },
- // result,
- // )
- // .run();
+ // console.log(content)
+ // content.map((v, i)=> console.log(v.content))
+
+ // const fragment = Fragment.fromArray(content);
+
+ // console.log(fragment)
+
+ // editor.view.state.selection.content().content.append(content)
+
+ setGeneratingfn(false);
+
+
+
+ // const genAI = new GoogleGenerativeAI("AIzaSyDGwJCP9SH5gryyvh65LJ6xTZ0SOdNvzyY");
+ // const model = genAI.getGenerativeModel({ model: "gemini-pro"});
+
+ // const result = (await model.generateContent(`${ty}, ${query}`)).response.text();
+
+ // .insertContentAt(
+ // {
+ // from: from,
+ // to: to,
+ // },
+ // result,
+ // )
+ // .run();
}
diff --git a/apps/web/app/(editor)/components/editorcommands.tsx b/apps/web/app/(editor)/components/editorcommands.tsx
new file mode 100644
index 00000000..8e80df46
--- /dev/null
+++ b/apps/web/app/(editor)/components/editorcommands.tsx
@@ -0,0 +1,85 @@
+import React, { useState } from "react";
+import {
+ EditorBubble,
+ EditorCommand,
+ EditorCommandEmpty,
+ EditorCommandItem,
+ EditorCommandList,
+} from "novel";
+import { suggestionItems } from "./slash-command";
+import { Separator } from "@repo/ui/shadcn/separator";
+import { NodeSelector } from "./selectors/node-selector";
+import { LinkSelector } from "./selectors/link-selector";
+import { TextButtons } from "./selectors/text-buttons";
+import { ColorSelector } from "./selectors/color-selector";
+import { BgColorSelector } from "./selectors/bgcolor-selector";
+
+function EditorCommands() {
+ return (
+ <>
+
+
+ >
+ );
+}
+
+function SlashCommand() {
+ return (
+
+
+ No results
+
+
+ {suggestionItems.map((item) => (
+ item.command(val)}
+ className="flex w-full items-center space-x-4 rounded-md px-4 py-3 text-left text-sm hover:bg-accent aria-selected:bg-[#21303D] group/command"
+ key={item.title}
+ >
+
+ {item.icon}
+
+
+
+ {item.title}
+
+
+ {item.description}
+
+
+
+ ))}
+
+
+ );
+}
+
+function PopupMenu() {
+ const [openNode, setOpenNode] = useState(false);
+ const [openColor, setOpenColor] = useState(false);
+ const [openBgColor, setOpenBgColor] = useState(false);
+ const [openLink, setOpenLink] = useState(false);
+ const [openMenu, setOpenMenu] = useState(false);
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default EditorCommands;
diff --git a/apps/web/app/(editor)/components/image-upload.ts b/apps/web/app/(editor)/components/image-upload.ts
index 0cf93a40..d10be168 100644
--- a/apps/web/app/(editor)/components/image-upload.ts
+++ b/apps/web/app/(editor)/components/image-upload.ts
@@ -2,32 +2,22 @@ import { createImageUpload } from "novel/plugins";
import { toast } from "sonner";
const onUpload = (file: File) => {
- const promise = fetch("/api/upload", {
+ //Endpoint: to upload the image
+ const promise = fetch("", {
method: "POST",
- headers: {
- "content-type": file?.type || "application/octet-stream",
- "x-vercel-filename": file?.name || "image.png",
- },
body: file,
});
return new Promise((resolve, reject) => {
toast.promise(
promise.then(async (res) => {
- // Successfully uploaded image
if (res.status === 200) {
const { url } = (await res.json()) as { url: string };
- // preload the image
const image = new Image();
image.src = url;
image.onload = () => {
resolve(url);
};
- // No blob store configured
- } else if (res.status === 401) {
- resolve(file);
- throw new Error("`BLOB_READ_WRITE_TOKEN` environment variable not found, reading image locally instead.");
- // Unknown error
} else {
throw new Error("Error uploading image. Please try again.");
}
diff --git a/apps/web/app/(editor)/components/tailwind/generative/ai-completion-command.tsx b/apps/web/app/(editor)/components/tailwind/generative/ai-completion-command.tsx
deleted file mode 100644
index 7d37b74a..00000000
--- a/apps/web/app/(editor)/components/tailwind/generative/ai-completion-command.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { CommandGroup, CommandItem, CommandSeparator } from "../ui/command";
-import { useEditor } from "novel";
-import { Check, TextQuote, TrashIcon } from "lucide-react";
-
-const AICompletionCommands = ({
- completion,
- onDiscard,
-}: {
- completion: string;
- onDiscard: () => void;
-}) => {
- const { editor } = useEditor();
- return (
- <>
-
- {
- const selection = editor.view.state.selection;
-
- editor
- .chain()
- .focus()
- .insertContentAt(
- {
- from: selection.from,
- to: selection.to,
- },
- completion,
- )
- .run();
- }}
- >
-
- Replace selection
-
- {
- const selection = editor.view.state.selection;
- editor
- .chain()
- .focus()
- .insertContentAt(selection.to + 1, completion)
- .run();
- }}
- >
-
- Insert below
-
-
-
-
-
-
-
- Discard
-
-
- >
- );
-};
-
-export default AICompletionCommands;
diff --git a/apps/web/app/(editor)/components/tailwind/generative/ai-selector-commands.tsx b/apps/web/app/(editor)/components/tailwind/generative/ai-selector-commands.tsx
deleted file mode 100644
index dad106b0..00000000
--- a/apps/web/app/(editor)/components/tailwind/generative/ai-selector-commands.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import { ArrowDownWideNarrow, CheckCheck, RefreshCcwDot, StepForward, WrapText } from "lucide-react";
-import { useEditor } from "novel";
-import { getPrevText } from "novel/utils";
-import { CommandGroup, CommandItem, CommandSeparator } from "../ui/command";
-
-const options = [
- {
- value: "improve",
- label: "Improve writing",
- icon: RefreshCcwDot,
- },
-
- {
- value: "fix",
- label: "Fix grammar",
- icon: CheckCheck,
- },
- {
- value: "shorter",
- label: "Make shorter",
- icon: ArrowDownWideNarrow,
- },
- {
- value: "longer",
- label: "Make longer",
- icon: WrapText,
- },
-];
-
-interface AISelectorCommandsProps {
- onSelect: (value: string, option: string) => void;
-}
-
-const AISelectorCommands = ({ onSelect }: AISelectorCommandsProps) => {
- const { editor } = useEditor();
-
- return (
- <>
-
- {options.map((option) => (
- {
- const slice = editor.state.selection.content();
- const text = editor.storage.markdown.serializer.serialize(slice.content);
- onSelect(text, value);
- }}
- className="flex gap-2 px-4"
- key={option.value}
- value={option.value}
- >
-
- {option.label}
-
- ))}
-
-
-
- {
- const pos = editor.state.selection.from;
-
- const text = getPrevText(editor, pos);
- onSelect(text, "continue");
- }}
- value="continue"
- className="gap-2 px-4"
- >
-
- Continue writing
-
-
- >
- );
-};
-
-export default AISelectorCommands;
diff --git a/apps/web/app/(editor)/components/tailwind/generative/ai-selector.tsx b/apps/web/app/(editor)/components/tailwind/generative/ai-selector.tsx
deleted file mode 100644
index 88ca1681..00000000
--- a/apps/web/app/(editor)/components/tailwind/generative/ai-selector.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-"use client";
-
-import { Command, CommandInput } from "@/components/tailwind/ui/command";
-
-import { useCompletion } from "ai/react";
-import { ArrowUp } from "lucide-react";
-import { useEditor } from "novel";
-import { addAIHighlight } from "novel/extensions";
-import { useState } from "react";
-import Markdown from "react-markdown";
-import { toast } from "sonner";
-import { Button } from "../ui/button";
-import CrazySpinner from "../ui/icons/crazy-spinner";
-import Magic from "../ui/icons/magic";
-import { ScrollArea } from "../ui/scroll-area";
-import AICompletionCommands from "./ai-completion-command";
-import AISelectorCommands from "./ai-selector-commands";
-//TODO: I think it makes more sense to create a custom Tiptap extension for this functionality https://tiptap.dev/docs/editor/ai/introduction
-
-interface AISelectorProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function AISelector({ onOpenChange }: AISelectorProps) {
- const { editor } = useEditor();
- const [inputValue, setInputValue] = useState("");
-
- const { completion, complete, isLoading } = useCompletion({
- // id: "novel",
- api: "/api/generate",
- onResponse: (response) => {
- if (response.status === 429) {
- toast.error("You have reached your request limit for the day.");
- return;
- }
- },
- onError: (e) => {
- toast.error(e.message);
- },
- });
-
- const hasCompletion = completion.length > 0;
-
- return (
-
- {hasCompletion && (
-
- )}
-
- {isLoading && (
-
-
- AI is thinking
-
-
-
-
- )}
- {!isLoading && (
- <>
-
-
addAIHighlight(editor)}
- />
-
-
- {hasCompletion ? (
- {
- editor.chain().unsetHighlight().focus().run();
- onOpenChange(false);
- }}
- completion={completion}
- />
- ) : (
- complete(value, { body: { option } })} />
- )}
- >
- )}
-
- );
-}
diff --git a/apps/web/app/(editor)/components/tailwind/ui/button.tsx b/apps/web/app/(editor)/components/tailwind/ui/button.tsx
deleted file mode 100644
index 9995a5e8..00000000
--- a/apps/web/app/(editor)/components/tailwind/ui/button.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@repo/ui/lib/utils";
-
-const buttonVariants = cva(
- "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
- outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
- ghost: "hover:bg-accent hover:text-accent-foreground",
- link: "text-primary underline-offset-4 hover:underline",
- },
- size: {
- default: "h-10 px-4 py-2",
- sm: "h-9 rounded-md px-3",
- lg: "h-11 rounded-md px-8",
- icon: "h-10 w-10",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- },
-);
-
-export interface ButtonProps
- extends React.ButtonHTMLAttributes
,
- VariantProps {
- asChild?: boolean;
-}
-
-const Button = React.forwardRef(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button";
- return ;
- },
-);
-Button.displayName = "Button";
-
-export { Button, buttonVariants };
diff --git a/apps/web/app/(editor)/components/tailwind/ui/command.tsx b/apps/web/app/(editor)/components/tailwind/ui/command.tsx
deleted file mode 100644
index 751bbc53..00000000
--- a/apps/web/app/(editor)/components/tailwind/ui/command.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-"use client";
-
-import * as React from "react";
-import type { DialogProps } from "@radix-ui/react-dialog";
-import { Command as CommandPrimitive } from "cmdk";
-
-import { cn } from "@repo/ui/lib/utils";
-import { Dialog, DialogContent } from "@repo/ui/shadcn/dialog";
-import Magic from "./icons/magic";
-
-const Command = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-Command.displayName = CommandPrimitive.displayName;
-
-interface CommandDialogProps extends DialogProps {}
-
-const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
- return (
-
- );
-};
-
-const CommandInput = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-
-));
-
-CommandInput.displayName = CommandPrimitive.Input.displayName;
-
-const CommandList = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-
-CommandList.displayName = CommandPrimitive.List.displayName;
-
-const CommandEmpty = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->((props, ref) => );
-
-CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
-
-const CommandGroup = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-
-CommandGroup.displayName = CommandPrimitive.Group.displayName;
-
-const CommandSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
-
-const CommandItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-
-CommandItem.displayName = CommandPrimitive.Item.displayName;
-
-const CommandShortcut = ({ className, ...props }: React.HTMLAttributes) => {
- return ;
-};
-CommandShortcut.displayName = "CommandShortcut";
-
-export {
- Command,
- CommandDialog,
- CommandInput,
- CommandList,
- CommandEmpty,
- CommandGroup,
- CommandItem,
- CommandShortcut,
- CommandSeparator,
-};
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/font-default.tsx b/apps/web/app/(editor)/components/tailwind/ui/icons/font-default.tsx
deleted file mode 100644
index bf29e4b5..00000000
--- a/apps/web/app/(editor)/components/tailwind/ui/icons/font-default.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-export default function FontDefault({ className }: { className?: string }) {
- return (
-
- );
-}
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/font-mono.tsx b/apps/web/app/(editor)/components/tailwind/ui/icons/font-mono.tsx
deleted file mode 100644
index 6e7b301b..00000000
--- a/apps/web/app/(editor)/components/tailwind/ui/icons/font-mono.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-export default function FontMono({ className }: { className?: string }) {
- return (
-
- );
-}
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/font-serif.tsx b/apps/web/app/(editor)/components/tailwind/ui/icons/font-serif.tsx
deleted file mode 100644
index 92225a36..00000000
--- a/apps/web/app/(editor)/components/tailwind/ui/icons/font-serif.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-export default function FontSerif({ className }: { className?: string }) {
- return (
-
- );
-}
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/index.tsx b/apps/web/app/(editor)/components/tailwind/ui/icons/index.tsx
deleted file mode 100644
index 5b284462..00000000
--- a/apps/web/app/(editor)/components/tailwind/ui/icons/index.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as FontDefault } from "./font-default";
-export { default as FontSerif } from "./font-serif";
-export { default as FontMono } from "./font-mono";
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/loading-circle.tsx b/apps/web/app/(editor)/components/tailwind/ui/icons/loading-circle.tsx
deleted file mode 100644
index 2a638117..00000000
--- a/apps/web/app/(editor)/components/tailwind/ui/icons/loading-circle.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-export default function LoadingCircle({ dimensions }: { dimensions?: string }) {
- return (
-
- );
-}
diff --git a/apps/web/app/(editor)/components/tailwind/ui/popover.tsx b/apps/web/app/(editor)/components/tailwind/ui/popover.tsx
deleted file mode 100644
index 0bf7e696..00000000
--- a/apps/web/app/(editor)/components/tailwind/ui/popover.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as PopoverPrimitive from "@radix-ui/react-popover";
-
-import { cn } from "@repo/ui/lib/utils";
-
-const Popover = PopoverPrimitive.Root;
-
-const PopoverTrigger = PopoverPrimitive.Trigger;
-
-const PopoverContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
-
-
-
-));
-PopoverContent.displayName = PopoverPrimitive.Content.displayName;
-
-export { Popover, PopoverTrigger, PopoverContent };
diff --git a/apps/web/app/(editor)/components/tailwind/ui/scroll-area.tsx b/apps/web/app/(editor)/components/tailwind/ui/scroll-area.tsx
deleted file mode 100644
index eb6c1f59..00000000
--- a/apps/web/app/(editor)/components/tailwind/ui/scroll-area.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
-
-import { cn } from "@repo/ui/lib/utils";
-
-const ScrollArea = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
- {children}
-
-
-
-));
-ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
-
-const ScrollBar = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, orientation = "vertical", ...props }, ref) => (
-
-
-
-));
-ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
-
-export { ScrollArea, ScrollBar };
diff --git a/apps/web/app/(editor)/components/topbar.tsx b/apps/web/app/(editor)/components/topbar.tsx
new file mode 100644
index 00000000..1178f9a0
--- /dev/null
+++ b/apps/web/app/(editor)/components/topbar.tsx
@@ -0,0 +1,86 @@
+"use client";
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+import {
+ AnimatePresence,
+ useMotionValueEvent,
+ useScroll,
+ motion,
+} from "framer-motion";
+import React, { useState } from "react";
+
+function Topbar({charsCount, saveStatus}: {charsCount: number | undefined; saveStatus: string}) {
+ const [visible, setVisible] = useState(true);
+
+ const { scrollYProgress } = useScroll();
+ useMotionValueEvent(scrollYProgress, "change", (current) => {
+ if (typeof current === "number") {
+ let direction = current! - scrollYProgress.getPrevious()!;
+
+ if (direction < 0 || direction === 1) {
+ setVisible(true);
+ } else {
+ setVisible(false);
+ }
+ }
+ });
+ return (
+
+
+
+
+
+ Untitled
+
+
+ {saveStatus}
+
+ {charsCount && (
+
+ {`${charsCount} words`}
+
+ )}
+
+
+
+
+ );
+}
+
+export default Topbar;
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/asksvg.tsx b/apps/web/app/(editor)/components/ui/asksvg.tsx
similarity index 100%
rename from apps/web/app/(editor)/components/tailwind/ui/icons/asksvg.tsx
rename to apps/web/app/(editor)/components/ui/asksvg.tsx
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/autocompletesvg.tsx b/apps/web/app/(editor)/components/ui/autocompletesvg.tsx
similarity index 100%
rename from apps/web/app/(editor)/components/tailwind/ui/icons/autocompletesvg.tsx
rename to apps/web/app/(editor)/components/ui/autocompletesvg.tsx
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/crazy-spinner.tsx b/apps/web/app/(editor)/components/ui/crazy-spinner.tsx
similarity index 100%
rename from apps/web/app/(editor)/components/tailwind/ui/icons/crazy-spinner.tsx
rename to apps/web/app/(editor)/components/ui/crazy-spinner.tsx
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/magic.tsx b/apps/web/app/(editor)/components/ui/magic.tsx
similarity index 100%
rename from apps/web/app/(editor)/components/tailwind/ui/icons/magic.tsx
rename to apps/web/app/(editor)/components/ui/magic.tsx
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/rewritesvg.tsx b/apps/web/app/(editor)/components/ui/rewritesvg.tsx
similarity index 100%
rename from apps/web/app/(editor)/components/tailwind/ui/icons/rewritesvg.tsx
rename to apps/web/app/(editor)/components/ui/rewritesvg.tsx
diff --git a/apps/web/app/(editor)/components/tailwind/ui/icons/translatesvg.tsx b/apps/web/app/(editor)/components/ui/translatesvg.tsx
similarity index 100%
rename from apps/web/app/(editor)/components/tailwind/ui/icons/translatesvg.tsx
rename to apps/web/app/(editor)/components/ui/translatesvg.tsx
diff --git a/apps/web/app/(editor)/editor.tsx b/apps/web/app/(editor)/editor.tsx
index 140207e0..5b4a60ce 100644
--- a/apps/web/app/(editor)/editor.tsx
+++ b/apps/web/app/(editor)/editor.tsx
@@ -1,66 +1,25 @@
"use client";
import { defaultEditorContent } from "./lib/content";
-import {
- EditorCommand,
- EditorCommandEmpty,
- EditorCommandItem,
- EditorCommandList,
- EditorContent,
- type EditorInstance,
- EditorRoot,
- type JSONContent,
- EditorBubble,
-} from "novel";
-import { ImageResizer, handleCommandNavigation } from "novel/extensions";
+import { EditorContent, EditorRoot, type JSONContent } from "novel";
+import { ImageResizer } from "novel/extensions";
import { useEffect, useState } from "react";
-import { useDebouncedCallback } from "use-debounce";
import { defaultExtensions } from "./components/extensions";
-import { ColorSelector } from "./components/selectors/color-selector";
-import { LinkSelector } from "./components/selectors/link-selector";
-import { NodeSelector } from "./components/selectors/node-selector";
-import { Separator } from "./components/tailwind/ui/separator";
-import { TextButtons } from "./components/selectors/text-buttons";
-import { BgColorSelector } from "./components/selectors/bgcolor-selector";
-import { handleImageDrop, handleImagePaste } from "novel/plugins";
-import { uploadFn } from "./components/image-upload";
-import { slashCommand, suggestionItems } from "./components/slash-command";
+import { slashCommand } from "./components/slash-command";
+import { Updates } from "./lib/debouncedsave";
+import { editorProps } from "./lib/editorprops";
+import EditorCommands from "./components/editorcommands";
import Aigenerate from "./components/aigenerate";
+import { useMotionValueEvent, useScroll } from "framer-motion";
+import Topbar from "./components/topbar";
-const hljs = require('highlight.js');
-
-const extensions = [...defaultExtensions, slashCommand];
-
-const TailwindAdvancedEditor = () => {
- const [initialContent, setInitialContent] = useState(null);
+const Editor = () => {
+ const [initialContent, setInitialContent] = useState(
+ null
+ );
const [saveStatus, setSaveStatus] = useState("Saved");
const [charsCount, setCharsCount] = useState();
-
- const [openNode, setOpenNode] = useState(false);
- const [openColor, setOpenColor] = useState(false);
- const [openBgColor, setOpenBgColor] = useState(false);
- const [openLink, setOpenLink] = useState(false);
- const [openAI, setOpenAI] = useState(false);
-
- //Apply Codeblock Highlighting on the HTML from editor.getHTML()
- const highlightCodeblocks = (content: string) => {
- const doc = new DOMParser().parseFromString(content, 'text/html');
- doc.querySelectorAll('pre code').forEach((el) => {
- // @ts-ignore
- // https://highlightjs.readthedocs.io/en/latest/api.html?highlight=highlightElement#highlightelement
- hljs.highlightElement(el);
- });
- return new XMLSerializer().serializeToString(doc);
- };
-
- const debouncedUpdates = useDebouncedCallback(async (editor: EditorInstance) => {
- const json = editor.getJSON();
- setCharsCount(editor.storage.characterCount.words());
- window.localStorage.setItem("html-content", highlightCodeblocks(editor.getHTML()));
- window.localStorage.setItem("novel-content", JSON.stringify(json));
- window.localStorage.setItem("markdown", editor.storage.markdown.getMarkdown());
- setSaveStatus("Saved");
- }, 500);
+ const [visible, setVisible] = useState(true);
useEffect(() => {
const content = window.localStorage.getItem("novel-content");
@@ -72,74 +31,24 @@ const TailwindAdvancedEditor = () => {
return (
-
-
Untitled - {saveStatus}
-
+
handleCommandNavigation(event),
- },
- handlePaste: (view, event) => handleImagePaste(view, event, uploadFn),
- handleDrop: (view, event, _slice, moved) => handleImageDrop(view, event, moved, uploadFn),
- attributes: {
- class:
- "prose prose-lg dark:prose-invert prose-headings:font-title font-default focus:outline-none max-w-full",
- },
- }}
+ extensions={[...defaultExtensions, slashCommand]}
+ className="min-h-[55vh] mt-[8vh] w-full max-w-screen-xl bg-[#171B1F] mb-[40vh]"
+ editorProps={editorProps}
onUpdate={({ editor }) => {
- debouncedUpdates(editor);
- setSaveStatus("Unsaved");
-
+ Updates({ editor, setCharsCount, setSaveStatus });
}}
slotAfter={}
>
+
-
- No results
-
- {suggestionItems.map((item) => (
- item.command(val)}
- className="flex w-full items-center space-x-4 rounded-md px-4 py-3 text-left text-sm hover:bg-accent aria-selected:bg-[#21303D] group/command"
- key={item.title}
- >
-
- {item.icon}
-
-
-
{item.title}
-
{item.description}
-
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
);
};
-export default TailwindAdvancedEditor;
+export default Editor;
diff --git a/apps/web/app/(editor)/lib/debouncedsave.ts b/apps/web/app/(editor)/lib/debouncedsave.ts
new file mode 100644
index 00000000..6490c6c4
--- /dev/null
+++ b/apps/web/app/(editor)/lib/debouncedsave.ts
@@ -0,0 +1,20 @@
+import hljs from 'highlight.js'
+import { debounce } from 'tldraw';
+import { useDebouncedCallback } from "use-debounce";
+
+export const Updates = debounce(({editor, setCharsCount, setSaveStatus})=> {
+ const json = editor.getJSON();
+ setCharsCount(editor.storage.characterCount.words());
+ window.localStorage.setItem("html-content", highlightCodeblocks(editor.getHTML()));
+ window.localStorage.setItem("novel-content", JSON.stringify(json));
+ window.localStorage.setItem("markdown", editor.storage.markdown.getMarkdown());
+ setSaveStatus("Saved");
+}, 500)
+
+export const highlightCodeblocks = (content: string) => {
+ const doc = new DOMParser().parseFromString(content, 'text/html');
+ doc.querySelectorAll('pre code').forEach((el) => {
+ hljs.highlightElement(el);
+ });
+ return new XMLSerializer().serializeToString(doc);
+};
\ No newline at end of file
diff --git a/apps/web/app/(editor)/lib/editorprops.ts b/apps/web/app/(editor)/lib/editorprops.ts
new file mode 100644
index 00000000..00d89264
--- /dev/null
+++ b/apps/web/app/(editor)/lib/editorprops.ts
@@ -0,0 +1,16 @@
+import { handleCommandNavigation } from "novel/extensions";
+import { handleImageDrop, handleImagePaste } from "novel/plugins";
+import { uploadFn } from "../components/image-upload";
+import { EditorView } from "prosemirror-view";
+
+export const editorProps = {
+ handleDOMEvents: {
+ keydown: (_view: EditorView, event: KeyboardEvent) => handleCommandNavigation(event),
+ },
+ handlePaste: (view: EditorView, event: ClipboardEvent) => handleImagePaste(view, event, uploadFn),
+ handleDrop: (view: EditorView, event: DragEvent, slice, moved:boolean) => handleImageDrop(view, event, moved, uploadFn),
+ attributes: {
+ class:
+ "prose prose-lg dark:prose-invert prose-headings:font-title font-default focus:outline-none max-w-full",
+ },
+}
\ No newline at end of file
diff --git a/apps/web/app/(editor)/styles/globals.css b/apps/web/app/(editor)/styles/globals.css
index 0b9378f3..336e2dae 100644
--- a/apps/web/app/(editor)/styles/globals.css
+++ b/apps/web/app/(editor)/styles/globals.css
@@ -162,8 +162,7 @@ pre {
font-weight: 700;
}
}
-
-::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb:hover, ::-webkit-scrollbar-track {
- background: transparent;
+::-webkit-scrollbar{
+ width: 0;
}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 8d7cd5ea..1a5d6e18 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -2,6 +2,7 @@ import "@repo/tailwind-config/globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
+import { Toaster } from "@repo/ui/shadcn/toaster";
const inter = Inter({ subsets: ["latin"] });
@@ -66,7 +67,10 @@ export default function RootLayout({
*/}
{/* TODO: when lightmode support is added, remove the 'dark' class from the body tag */}
- {children}
+
+ {children}
+
+