diff --git a/apps/web/app/(canvas)/canvas/page.tsx b/apps/web/app/(canvas)/canvas/page.tsx
index c24e2c73..ea3cee1b 100644
--- a/apps/web/app/(canvas)/canvas/page.tsx
+++ b/apps/web/app/(canvas)/canvas/page.tsx
@@ -1,17 +1,12 @@
"use client";
-// import Canvas from "./_components/canvas";
import { Canvas } from "../canvas";
import React, { useState } from "react";
-// import ReactTextareaAutosize from "react-textarea-autosize";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
-import {
- DragSvg,
- SettingsSvg,
- LinkSvg,
- ThreeDBlock,
- TextLoadingSvg,
-} from "../svg";
+import { SettingsIcon, DragIcon } from "@repo/ui/icons";
+import DraggableComponentsContainer from "@repo/ui/components/canvas/draggableComponent";
+import { AutocompleteIcon, blockIcon } from "@repo/ui/icons";
+import Image from "next/image";
function page() {
const [value, setValue] = useState("");
@@ -45,7 +40,7 @@ function page() {
>
Change Filters
-
+
{visible ? (
-
-
-
-
- Nvidia will most likely create monopoly in software
- industry as they are already largest player in GPU
- hardware by 20...
-
-
-
-
-
-
-
- Nvidia currently dominates the GPU hardware market,
- with a market share over 97%. This has led some to
- argue...
-
-
-
- From space: GPU GOATS
-
-
-
-
-
-
-
-
- Nvidia currently dominates the GPU hardware market,
- with a market share over 97%. This has led some to
- argue...
-
-
-
- Page url:
- https://www.cnbc.com/2024/05/23/nvidia-keeps-hitting-records-can-investors-still-buy-the-stock.html?&qsearchterm=nvidia
-
-
-
-
+
) : (
Need more space to show!
)}
@@ -112,7 +67,7 @@ function page() {
-
+
{/* */}
@@ -129,4 +84,26 @@ function page() {
);
}
+const content= [
+ {
+ content: "Nvidia currently dominates the GPU hardware market, with a market share over 97%. This has led some to argue...",
+ icon: AutocompleteIcon,
+ iconAlt: "Autocomplete",
+ extraInfo: "Page Url: https://www.cnbc.com/2024/05/23/nvidia-keeps-hitting-records-can-investors-still-buy-the-stock.html?&qsearchterm=nvidia",
+ },
+ {
+ content: "Nvidia currently dominates the GPU hardware market, with a market share over 97%. This has led some to argue...",
+ icon: blockIcon,
+ iconAlt: "Autocomplete",
+ extraInfo: "Page Url: https://www.cnbc.com/2024/05/23/nvidia-keeps-hitting-records-can-investors-still-buy-the-stock.html?&qsearchterm=nvidia",
+ },
+
+]
+
+function SidePanel(){
+ return (
+
+ )
+}
+
export default page;
diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx
index 035e8e3f..f40c22e4 100644
--- a/apps/web/app/(dash)/menu.tsx
+++ b/apps/web/app/(dash)/menu.tsx
@@ -1,6 +1,6 @@
import React from "react";
import Image from "next/image";
-import { MemoriesIcon, ExploreIcon, HistoryIcon } from "@repo/ui/icons";
+import { MemoriesIcon, ExploreIcon, HistoryIcon, CanvasIcon } from "@repo/ui/icons";
import Link from "next/link";
function Menu() {
@@ -16,9 +16,9 @@ function Menu() {
url: "/explore",
},
{
- icon: HistoryIcon,
- text: "History",
- url: "/history",
+ icon: CanvasIcon,
+ text: "Canvas",
+ url: "/canvas",
},
];
diff --git a/apps/web/app/(editor)/ai.md b/apps/web/app/(editor)/ai.md
deleted file mode 100644
index 1528a7bd..00000000
--- a/apps/web/app/(editor)/ai.md
+++ /dev/null
@@ -1,43 +0,0 @@
-## to access the editor
-```
-import { useEditor } from "novel";
-const editor = useEditor()
-```
-
-## to get previous text
-```
-import { getPrevText } from "novel/utils";
-const pos = editor.state.selection.from;
-const text = getPrevText(editor, pos);
-```
-
-## selected content into markdown format
-```
-const slice = editor.state.selection.content();
-const text = editor.storage.markdown.serializer.serialize(slice.content);
-```
-
-## replace Selection
-```
-const selection = editor.view.state.selection;
-editor.chain().focus()
- .insertContentAt(
- {
- from: selection.from,
- to: selection.to,
- },
- completion,
- )
- .run();
-```
-
-
-## to insert after
-```
-const selection = editor.view.state.selection;
-editor
- .chain()
- .focus()
- .insertContentAt(selection.to + 1, completion)
- .run();
-```
diff --git a/apps/web/app/(editor)/components/aigenerate.tsx b/apps/web/app/(editor)/components/aigenerate.tsx
deleted file mode 100644
index de9b2a3f..00000000
--- a/apps/web/app/(editor)/components/aigenerate.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import React, { useEffect, useRef, useState } from "react";
-import Magic from "./ui/magic";
-import CrazySpinner from "./ui/crazy-spinner";
-import Asksvg from "./ui/asksvg";
-import Rewritesvg from "./ui/rewritesvg";
-import Translatesvg from "./ui/translatesvg";
-import Autocompletesvg from "./ui/autocompletesvg";
-import { motion, AnimatePresence } from "framer-motion";
-import type { Editor } from "@tiptap/core";
-import { useEditor } from "novel";
-import { NodeSelection } from "prosemirror-state";
-
-function Aigenerate() {
- const [visible, setVisible] = useState(false);
- const [generating, setGenerating] = useState(false);
-
- const { editor } = useEditor();
- const setGeneratingfn = (v: boolean) => setGenerating(v);
-
- return (
-
-
{
- setVisible(!visible);
- if (visible) editor?.commands.unsetAIHighlight();
- }}
- className={`select-none relative z-[70] rounded-3xl text-[#369DFD] bg-[#21303D] px-4 py-3 text-sm flex gap-2 items-center font-medium whitespace-nowrap overflow-hidden transition-[width] w-[6.25rem] ${visible && "w-[10.55rem]"}`}
- >
-
- {visible && generating ? (
- <>
- Generating
- >
- ) : visible ? (
- <>Press Commands>
- ) : (
- <>Ask AI>
- )}
-
-
- {/* TODO: handle Editor not initalised, maybe with a loading state. */}
-
-
-
-
- );
-}
-
-export default Aigenerate;
-
-const options = [
- <>
-
- Translate
- >,
- <>
-
- Change Tone
- >,
- <>
-
- Ask Gemini
- >,
- <>
-
- Auto Complete
- >,
-];
-
-function ToolBar({
- editor,
- setGeneratingfn,
-}: {
- editor: Editor;
- setGeneratingfn: (v: boolean) => void;
-}) {
- const [index, setIndex] = useState(0);
-
- return (
-
- {options.map((item, idx) => (
-
setIndex(idx)}
- >
-
- {index === idx && (
-
- 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={{
- opacity: 1,
- transition: { duration: 0.15 },
- }}
- exit={{
- opacity: 0,
- transition: { duration: 0.15, delay: 0.2 },
- }}
- />
- )}
-
-
- {item}
-
-
- ))}
-
- );
-}
-
-async function AigenerateContent({
- idx,
- editor,
- setGeneratingfn,
-}: {
- idx: number;
- editor: Editor;
- setGeneratingfn: (v: boolean) => void;
-}) {
- setGeneratingfn(true);
-
- const { from, to } = editor.view.state.selection;
-
- const slice = editor.state.selection.content();
- const text = editor.storage.markdown.serializer.serialize(slice.content);
-
- const request = [
- "Translate to hindi written in english, do not write anything else",
- "change tone, improve the way be more formal",
- "ask, answer the question",
- "continue this, minimum 80 characters, do not repeat just continue don't use ... to denote start",
- ];
-
- const resp = await fetch("/api/editorai", {
- method: "POST",
- body: JSON.stringify({
- context: text,
- request: request[idx],
- }),
- });
-
- const reader = resp.body?.getReader();
- let done = false;
- let position = to;
- while (!done && reader) {
- const { value, done: d } = await reader.read();
- done = d;
-
- const decoded = new TextDecoder().decode(value);
- console.log(decoded);
- editor
- .chain()
- .focus()
- .insertContentAt(position + 1, decoded)
- .run();
- position += decoded.length;
- }
-
- setGeneratingfn(false);
-}
diff --git a/apps/web/app/(editor)/components/editorcommands.tsx b/apps/web/app/(editor)/components/editorcommands.tsx
deleted file mode 100644
index 8e80df46..00000000
--- a/apps/web/app/(editor)/components/editorcommands.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-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/extensions.ts b/apps/web/app/(editor)/components/extensions.ts
deleted file mode 100644
index 0c581154..00000000
--- a/apps/web/app/(editor)/components/extensions.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import {
- AIHighlight,
- CharacterCount,
- CodeBlockLowlight,
- GlobalDragHandle,
- HorizontalRule,
- Placeholder,
- StarterKit,
- TaskItem,
- TaskList,
- TiptapImage,
- TiptapLink,
- UpdatedImage,
- Youtube,
-} from "novel/extensions";
-import { UploadImagesPlugin } from "novel/plugins";
-
-import { cx } from "class-variance-authority";
-import { common, createLowlight } from "lowlight";
-
-//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
-const aiHighlight = AIHighlight;
-//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 tiptapImage = TiptapImage.extend({
- addProseMirrorPlugins() {
- return [
- UploadImagesPlugin({
- imageClass: cx("opacity-40 rounded-lg border border-stone-200"),
- }),
- ];
- },
-}).configure({
- allowBase64: true,
- HTMLAttributes: {
- class: cx("rounded-lg border border-muted"),
- },
-});
-
-const updatedImage = UpdatedImage.configure({
- HTMLAttributes: {
- class: cx("rounded-lg border border-muted"),
- },
-});
-
-const taskList = TaskList.configure({
- HTMLAttributes: {
- class: cx("not-prose pl-2 "),
- },
-});
-const taskItem = TaskItem.configure({
- HTMLAttributes: {
- class: cx("flex gap-2 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-md bg-muted text-muted-foreground 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,
-});
-
-const codeBlockLowlight = CodeBlockLowlight.configure({
- // configure lowlight: common / all / use highlightJS in case there is a need to specify certain language grammars only
- // common: covers 37 language grammars which should be good enough in most cases
- lowlight: createLowlight(common),
-});
-
-const youtube = Youtube.configure({
- HTMLAttributes: {
- class: cx("rounded-lg border border-muted"),
- },
- inline: false,
-});
-
-const characterCount = CharacterCount.configure();
-
-export const defaultExtensions = [
- starterKit,
- placeholder,
- tiptapLink,
- tiptapImage,
- updatedImage,
- taskList,
- taskItem,
- horizontalRule,
- aiHighlight,
- codeBlockLowlight,
- youtube,
- characterCount,
- GlobalDragHandle,
-];
diff --git a/apps/web/app/(editor)/components/image-upload.ts b/apps/web/app/(editor)/components/image-upload.ts
deleted file mode 100644
index d10be168..00000000
--- a/apps/web/app/(editor)/components/image-upload.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { createImageUpload } from "novel/plugins";
-import { toast } from "sonner";
-
-const onUpload = (file: File) => {
- //Endpoint: to upload the image
- const promise = fetch("", {
- method: "POST",
- body: file,
- });
-
- return new Promise((resolve, reject) => {
- toast.promise(
- promise.then(async (res) => {
- if (res.status === 200) {
- const { url } = (await res.json()) as { url: string };
- const image = new Image();
- image.src = url;
- image.onload = () => {
- resolve(url);
- };
- } else {
- throw new Error("Error uploading image. Please try again.");
- }
- }),
- {
- loading: "Uploading image...",
- success: "Image uploaded successfully.",
- error: (e) => {
- reject(e);
- return e.message;
- },
- },
- );
- });
-};
-
-export const uploadFn = createImageUpload({
- onUpload,
- validateFn: (file) => {
- if (!file.type.includes("image/")) {
- toast.error("File type not supported.");
- return false;
- }
- if (file.size / 1024 / 1024 > 20) {
- toast.error("File size too big (max 20MB).");
- return false;
- }
- return true;
- },
-});
diff --git a/apps/web/app/(editor)/components/selectors/bgcolor-selector.tsx b/apps/web/app/(editor)/components/selectors/bgcolor-selector.tsx
deleted file mode 100644
index 77da0f03..00000000
--- a/apps/web/app/(editor)/components/selectors/bgcolor-selector.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { Check, ChevronDown } from "lucide-react";
-import { EditorBubbleItem, useEditor } from "novel";
-
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-export interface BubbleColorMenuItem {
- name: string;
- color: string;
-}
-
-const HIGHLIGHT_COLORS: BubbleColorMenuItem[] = [
- {
- name: "Default",
- color: "var(--novel-highlight-default)",
- },
- {
- name: "Purple",
- color: "var(--novel-highlight-purple)",
- },
- {
- name: "Red",
- color: "var(--novel-highlight-red)",
- },
- {
- name: "Yellow",
- color: "var(--novel-highlight-yellow)",
- },
- {
- name: "Blue",
- color: "var(--novel-highlight-blue)",
- },
- {
- name: "Green",
- color: "var(--novel-highlight-green)",
- },
- {
- name: "Orange",
- color: "var(--novel-highlight-orange)",
- },
- {
- name: "Pink",
- color: "var(--novel-highlight-pink)",
- },
- {
- name: "Gray",
- color: "var(--novel-highlight-gray)",
- },
-];
-
-interface ColorSelectorProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export const BgColorSelector = ({ open, onOpenChange }: ColorSelectorProps) => {
- const { editor } = useEditor();
-
- if (!editor) return null;
-
- const activeHighlightItem = HIGHLIGHT_COLORS.find(({ color }) => editor.isActive("highlight", { color }));
-
- return (
-
-
-
-
-
-
-
-
Background
- {HIGHLIGHT_COLORS.map(({ name, color }) => (
-
{
- editor.commands.unsetHighlight();
- name !== "Default" && editor.commands.setHighlight({ color });
- }}
- className="flex cursor-pointer items-center justify-between px-2 py-1 text-sm hover:bg-[#21303D]"
- >
-
- {editor.isActive("highlight", { color }) && }
-
- ))}
-
-
-
- );
-};
diff --git a/apps/web/app/(editor)/components/selectors/color-selector.tsx b/apps/web/app/(editor)/components/selectors/color-selector.tsx
deleted file mode 100644
index 557c4255..00000000
--- a/apps/web/app/(editor)/components/selectors/color-selector.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import { Check, ChevronDown } from "lucide-react";
-import { EditorBubbleItem, useEditor } from "novel";
-
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-export interface BubbleColorMenuItem {
- name: string;
- color: string;
-}
-
-const TEXT_COLORS: BubbleColorMenuItem[] = [
- {
- name: "Default",
- color: "var(--novel-black)",
- },
- {
- name: "Purple",
- color: "#9333EA",
- },
- {
- name: "Red",
- color: "#E00000",
- },
- {
- name: "Yellow",
- color: "#EAB308",
- },
- {
- name: "Blue",
- color: "#2563EB",
- },
- {
- name: "Green",
- color: "#008A00",
- },
- {
- name: "Orange",
- color: "#FFA500",
- },
- {
- name: "Pink",
- color: "#BA4081",
- },
- {
- name: "Gray",
- color: "#A8A29E",
- },
-];
-
-
-interface ColorSelectorProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export const ColorSelector = ({ open, onOpenChange }: ColorSelectorProps) => {
- const { editor } = useEditor();
-
- if (!editor) return null;
- const activeColorItem = TEXT_COLORS.find(({ color }) => editor.isActive("textStyle", { color }));
-
- return (
-
-
-
-
-
-
-
-
Color
- {TEXT_COLORS.map(({ name, color }) => (
-
{
- editor.commands.unsetColor();
- name !== "Default" &&
- editor
- .chain()
- .focus()
- .setColor(color || "")
- .run();
- }}
- className="flex cursor-pointer items-center justify-between px-2 py-1 text-sm hover:bg-accent"
- >
-
-
- ))}
-
-
-
- );
-};
diff --git a/apps/web/app/(editor)/components/selectors/link-selector.tsx b/apps/web/app/(editor)/components/selectors/link-selector.tsx
deleted file mode 100644
index 3dc28266..00000000
--- a/apps/web/app/(editor)/components/selectors/link-selector.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-import { cn } from "@repo/ui/lib/utils";
-import { Check, Trash } from "lucide-react";
-import { useEditor } from "novel";
-import { useEffect, useRef } from "react";
-
-export function isValidUrl(url: string) {
- try {
- new URL(url);
- return true;
- } catch (_e) {
- return false;
- }
-}
-export function getUrlFromString(str: string) {
- if (isValidUrl(str)) return str;
- try {
- if (str.includes(".") && !str.includes(" ")) {
- return new URL(`https://${str}`).toString();
- }
- } catch (_e) {
- return null;
- }
-}
-interface LinkSelectorProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export const LinkSelector = ({ open, onOpenChange }: LinkSelectorProps) => {
- const inputRef = useRef(null);
- const { editor } = useEditor();
-
- // Autofocus on input by default
- useEffect(() => {
- inputRef.current?.focus();
- });
- if (!editor) return null;
-
- return (
-
-
-
-
-
-
-
-
- );
-};
diff --git a/apps/web/app/(editor)/components/selectors/node-selector.tsx b/apps/web/app/(editor)/components/selectors/node-selector.tsx
deleted file mode 100644
index c6092b68..00000000
--- a/apps/web/app/(editor)/components/selectors/node-selector.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import {
- Check,
- CheckSquare,
- ChevronDown,
- Code,
- Heading1,
- Heading2,
- Heading3,
- ListOrdered,
- type LucideIcon,
- TextIcon,
- TextQuote,
-} from "lucide-react";
-import { EditorBubbleItem, useEditor } from "novel";
-
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-
-export type SelectorItem = {
- name: string;
- icon: LucideIcon;
- command: (editor: ReturnType["editor"]) => void;
- isActive: (editor: ReturnType["editor"]) => boolean;
-};
-
-const items: SelectorItem[] = [
- {
- name: "Text",
- icon: TextIcon,
- command: (editor) => editor.chain().focus().clearNodes().run(),
- // I feel like there has to be a more efficient way to do this – feel free to PR if you know how!
- isActive: (editor) =>
- editor.isActive("paragraph") && !editor.isActive("bulletList") && !editor.isActive("orderedList"),
- },
- {
- name: "Heading 1",
- icon: Heading1,
- command: (editor) => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
- isActive: (editor) => editor.isActive("heading", { level: 1 }),
- },
- {
- name: "Heading 2",
- icon: Heading2,
- command: (editor) => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
- isActive: (editor) => editor.isActive("heading", { level: 2 }),
- },
- {
- name: "Heading 3",
- icon: Heading3,
- command: (editor) => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
- isActive: (editor) => editor.isActive("heading", { level: 3 }),
- },
- {
- name: "To-do List",
- icon: CheckSquare,
- command: (editor) => editor.chain().focus().clearNodes().toggleTaskList().run(),
- isActive: (editor) => editor.isActive("taskItem"),
- },
- {
- name: "Bullet List",
- icon: ListOrdered,
- command: (editor) => editor.chain().focus().clearNodes().toggleBulletList().run(),
- isActive: (editor) => editor.isActive("bulletList"),
- },
- {
- name: "Numbered List",
- icon: ListOrdered,
- command: (editor) => editor.chain().focus().clearNodes().toggleOrderedList().run(),
- isActive: (editor) => editor.isActive("orderedList"),
- },
- {
- name: "Quote",
- icon: TextQuote,
- command: (editor) => editor.chain().focus().clearNodes().toggleBlockquote().run(),
- isActive: (editor) => editor.isActive("blockquote"),
- },
- {
- name: "Code",
- icon: Code,
- command: (editor) => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
- isActive: (editor) => editor.isActive("codeBlock"),
- },
-];
-interface NodeSelectorProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export const NodeSelector = ({ open, onOpenChange }: NodeSelectorProps) => {
- const { editor } = useEditor();
- if (!editor) return null;
- const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? {
- name: "Multiple",
- };
-
- return (
-
-
-
-
-
- {items.map((item) => (
- {
- item.command(editor);
- onOpenChange(false);
- }}
- className="flex cursor-pointer items-center justify-between rounded-sm px-2 py-1 text-sm hover:bg-accent"
- >
-
- {activeItem.name === item.name && }
-
- ))}
-
-
- );
-};
diff --git a/apps/web/app/(editor)/components/selectors/text-buttons.tsx b/apps/web/app/(editor)/components/selectors/text-buttons.tsx
deleted file mode 100644
index f75d7b3c..00000000
--- a/apps/web/app/(editor)/components/selectors/text-buttons.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-import { BoldIcon, CodeIcon, ItalicIcon, StrikethroughIcon, UnderlineIcon } from "lucide-react";
-import { EditorBubbleItem, useEditor } from "novel";
-import type { SelectorItem } from "./node-selector";
-import { cn } from "@repo/ui/lib/utils";
-
-export const TextButtons = () => {
- const { editor } = useEditor();
- if (!editor) return null;
- const items: SelectorItem[] = [
- {
- name: "bold",
- isActive: (editor) => editor.isActive("bold"),
- command: (editor) => editor.chain().focus().toggleBold().run(),
- icon: BoldIcon,
- },
- {
- name: "italic",
- isActive: (editor) => editor.isActive("italic"),
- command: (editor) => editor.chain().focus().toggleItalic().run(),
- icon: ItalicIcon,
- },
- {
- name: "underline",
- isActive: (editor) => editor.isActive("underline"),
- command: (editor) => editor.chain().focus().toggleUnderline().run(),
- icon: UnderlineIcon,
- },
- {
- name: "strike",
- isActive: (editor) => editor.isActive("strike"),
- command: (editor) => editor.chain().focus().toggleStrike().run(),
- icon: StrikethroughIcon,
- },
- {
- name: "code",
- isActive: (editor) => editor.isActive("code"),
- command: (editor) => editor.chain().focus().toggleCode().run(),
- icon: CodeIcon,
- },
- ];
- return (
-
- {items.map((item) => (
- {
- item.command(editor);
- }}
- >
-
-
- ))}
-
- );
-};
diff --git a/apps/web/app/(editor)/components/slash-command.tsx b/apps/web/app/(editor)/components/slash-command.tsx
deleted file mode 100644
index 1bfb1690..00000000
--- a/apps/web/app/(editor)/components/slash-command.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import {
- CheckSquare,
- Code,
- Heading1,
- Heading2,
- Heading3,
- ImageIcon,
- List,
- ListOrdered,
- MessageSquarePlus,
- Text,
- TextQuote,
- Youtube
-} from "lucide-react";
-import { createSuggestionItems } from "novel/extensions";
-import { Command, renderItems } from "novel/extensions";
-import { uploadFn } from "./image-upload";
-
-export const suggestionItems = createSuggestionItems([
- {
- title: "Send Feedback",
- description: "Let us know how we can improve.",
- icon: ,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).run();
- window.open("/feedback", "_blank");
- },
- },
- {
- title: "Text",
- description: "Just start typing with plain text.",
- searchTerms: ["p", "paragraph"],
- icon: ,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
- },
- },
- {
- title: "To-do List",
- description: "Track tasks with a to-do list.",
- searchTerms: ["todo", "task", "list", "check", "checkbox"],
- icon: ,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).toggleTaskList().run();
- },
- },
- {
- title: "Heading 1",
- description: "Big section heading.",
- searchTerms: ["title", "big", "large"],
- icon: ,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
- },
- },
- {
- title: "Heading 2",
- description: "Medium section heading.",
- searchTerms: ["subtitle", "medium"],
- icon: ,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
- },
- },
- {
- title: "Heading 3",
- description: "Small section heading.",
- searchTerms: ["subtitle", "small"],
- icon: ,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
- },
- },
- {
- title: "Bullet List",
- description: "Create a simple bullet list.",
- searchTerms: ["unordered", "point"],
- icon:
,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).toggleBulletList().run();
- },
- },
- {
- title: "Numbered List",
- description: "Create a list with numbering.",
- searchTerms: ["ordered"],
- icon: ,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).toggleOrderedList().run();
- },
- },
- {
- title: "Quote",
- description: "Capture a quote.",
- searchTerms: ["blockquote"],
- icon: ,
- command: ({ editor, range }) =>
- editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
- },
- {
- title: "Code",
- description: "Capture a code snippet.",
- searchTerms: ["codeblock"],
- icon: ,
- command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
- },
- // {
- // title: "Image",
- // description: "Upload an image from your computer.",
- // searchTerms: ["photo", "picture", "media"],
- // icon: ,
- // command: ({ editor, range }) => {
- // editor.chain().focus().deleteRange(range).run();
- // // upload image
- // const input = document.createElement("input");
- // input.type = "file";
- // input.accept = "image/*";
- // input.onchange = async () => {
- // if (input.files?.length) {
- // const file = input.files[0];
- // const pos = editor.view.state.selection.from;
- // uploadFn(file, editor.view, pos);
- // }
- // };
- // input.click();
- // },
- // },
- // {
- // title: "Youtube",
- // description: "Embed a Youtube video.",
- // searchTerms: ["video", "youtube", "embed"],
- // icon: ,
- // command: ({ editor, range }) => {
- // const videoLink = prompt("Please enter Youtube Video Link");
- // //From https://regexr.com/3dj5t
- // const ytregex = new RegExp(
- // /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/,
- // );
-
- // if (ytregex.test(videoLink)) {
- // editor
- // .chain()
- // .focus()
- // .deleteRange(range)
- // .setYoutubeVideo({
- // src: videoLink,
- // })
- // .run();
- // } else {
- // if (videoLink !== null) {
- // alert("Please enter a correct Youtube Video Link");
- // }
- // }
- // },
- // },
-]);
-
-export const slashCommand = Command.configure({
- suggestion: {
- items: () => suggestionItems,
- render: renderItems,
- },
-});
diff --git a/apps/web/app/(editor)/components/topbar.tsx b/apps/web/app/(editor)/components/topbar.tsx
deleted file mode 100644
index 49b5179c..00000000
--- a/apps/web/app/(editor)/components/topbar.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-"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/ui/asksvg.tsx b/apps/web/app/(editor)/components/ui/asksvg.tsx
deleted file mode 100644
index aa38fe08..00000000
--- a/apps/web/app/(editor)/components/ui/asksvg.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react'
-
-function Asksvg() {
- return (
-
-
- )
-}
-
-export default Asksvg
\ No newline at end of file
diff --git a/apps/web/app/(editor)/components/ui/autocompletesvg.tsx b/apps/web/app/(editor)/components/ui/autocompletesvg.tsx
deleted file mode 100644
index c433fcad..00000000
--- a/apps/web/app/(editor)/components/ui/autocompletesvg.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react'
-
-function Autocompletesvg() {
- return (
-
-
- )
-}
-
-export default Autocompletesvg
\ No newline at end of file
diff --git a/apps/web/app/(editor)/components/ui/crazy-spinner.tsx b/apps/web/app/(editor)/components/ui/crazy-spinner.tsx
deleted file mode 100644
index 2e95deee..00000000
--- a/apps/web/app/(editor)/components/ui/crazy-spinner.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-const CrazySpinner = () => {
- return (
-
- );
-};
-
-export default CrazySpinner;
diff --git a/apps/web/app/(editor)/components/ui/magic.tsx b/apps/web/app/(editor)/components/ui/magic.tsx
deleted file mode 100644
index 04dce39e..00000000
--- a/apps/web/app/(editor)/components/ui/magic.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-export default function Magic({ className }: { className: string }) {
- return (
-
-
- );
-}
diff --git a/apps/web/app/(editor)/components/ui/rewritesvg.tsx b/apps/web/app/(editor)/components/ui/rewritesvg.tsx
deleted file mode 100644
index fad9eb90..00000000
--- a/apps/web/app/(editor)/components/ui/rewritesvg.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react'
-
-function Rewritesvg() {
- return (
-
- )
-}
-
-export default Rewritesvg
\ No newline at end of file
diff --git a/apps/web/app/(editor)/components/ui/translatesvg.tsx b/apps/web/app/(editor)/components/ui/translatesvg.tsx
deleted file mode 100644
index cde82da6..00000000
--- a/apps/web/app/(editor)/components/ui/translatesvg.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react'
-
-function Translatesvg() {
- return (
-
-
- )
-}
-
-export default Translatesvg
\ No newline at end of file
diff --git a/apps/web/app/(editor)/editor.tsx b/apps/web/app/(editor)/editor.tsx
deleted file mode 100644
index f7f9a098..00000000
--- a/apps/web/app/(editor)/editor.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-"use client";
-import { defaultEditorContent } from "./lib/content";
-import { EditorContent, EditorRoot, type JSONContent } from "novel";
-import { ImageResizer } from "novel/extensions";
-import { useEffect, useState } from "react";
-import { defaultExtensions } from "./components/extensions";
-
-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 Editor = () => {
- const [initialContent, setInitialContent] = useState(
- null,
- );
- const [saveStatus, setSaveStatus] = useState("Saved");
- const [charsCount, setCharsCount] = useState();
- const [visible, setVisible] = useState(true);
-
- useEffect(() => {
- if (typeof window === "undefined") return;
- const content = window.localStorage.getItem("novel-content");
- if (content) setInitialContent(JSON.parse(content));
- else setInitialContent(defaultEditorContent);
- }, []);
-
- if (!initialContent) return <>Loading...>;
-
- return (
-
-
-
- {
- Updates({ editor, setCharsCount, setSaveStatus });
- }}
- slotAfter={}
- >
-
-
-
-
-
- );
-};
-
-export default Editor;
diff --git a/apps/web/app/(editor)/editor/page.tsx b/apps/web/app/(editor)/editor/page.tsx
deleted file mode 100644
index d0298065..00000000
--- a/apps/web/app/(editor)/editor/page.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import TailwindAdvancedEditor from "../editor";
-export default function Page() {
- return (
-
-
-
- );
-}
diff --git a/apps/web/app/(editor)/layout.tsx b/apps/web/app/(editor)/layout.tsx
deleted file mode 100644
index 1bf97715..00000000
--- a/apps/web/app/(editor)/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import "./styles/prosemirror.css";
-import "./styles/globals.css"
-import type { ReactNode } from "react";
-
-
-export default function RootLayout({ children }: { children: ReactNode }) {
- return (
-
- {children}
-
- );
-}
diff --git a/apps/web/app/(editor)/lib/content.ts b/apps/web/app/(editor)/lib/content.ts
deleted file mode 100644
index 6464cfa1..00000000
--- a/apps/web/app/(editor)/lib/content.ts
+++ /dev/null
@@ -1,231 +0,0 @@
-export const defaultEditorContent = {
- type: "doc",
- content: [
- {
- type: "heading",
- attrs: { level: 2 },
- content: [{ type: "text", text: "Introducing Novel" }],
- },
- {
- type: "paragraph",
- content: [
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://github.com/steven-tey/novel",
- target: "_blank",
- },
- },
- ],
- text: "Novel",
- },
- {
- type: "text",
- text: " is a Notion-style WYSIWYG editor with AI-powered autocompletion. Built with ",
- },
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://tiptap.dev/",
- target: "_blank",
- },
- },
- ],
- text: "Tiptap",
- },
- { type: "text", text: " + " },
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://sdk.vercel.ai/docs",
- target: "_blank",
- },
- },
- ],
- text: "Vercel AI SDK",
- },
- { type: "text", text: "." },
- ],
- },
- {
- type: "heading",
- attrs: { level: 3 },
- content: [{ type: "text", text: "Installation" }],
- },
- {
- type: "codeBlock",
- attrs: { language: null },
- content: [{ type: "text", text: "npm i novel" }],
- },
- {
- type: "heading",
- attrs: { level: 3 },
- content: [{ type: "text", text: "Usage" }],
- },
- {
- type: "codeBlock",
- attrs: { language: null },
- content: [
- {
- type: "text",
- text: 'import { Editor } from "novel";\n\nexport default function App() {\n return (\n \n )\n}',
- },
- ],
- },
- {
- type: "heading",
- attrs: { level: 3 },
- content: [{ type: "text", text: "Features" }],
- },
- {
- type: "orderedList",
- attrs: { tight: true, start: 1 },
- content: [
- {
- type: "listItem",
- content: [
- {
- type: "paragraph",
- content: [{ type: "text", text: "Slash menu & bubble menu" }],
- },
- ],
- },
- {
- type: "listItem",
- content: [
- {
- type: "paragraph",
- content: [
- { type: "text", text: "AI autocomplete (type " },
- { type: "text", marks: [{ type: "code" }], text: "++" },
- {
- type: "text",
- text: " to activate, or select from slash menu)",
- },
- ],
- },
- ],
- },
- {
- type: "listItem",
- content: [
- {
- type: "paragraph",
- content: [
- {
- type: "text",
- text: "Image uploads (drag & drop / copy & paste, or select from slash menu) ",
- },
- ],
- },
- ],
- },
- ],
- },
- {
- type: "image",
- attrs: {
- src: "https://public.blob.vercel-storage.com/pJrjXbdONOnAeZAZ/banner-2wQk82qTwyVgvlhTW21GIkWgqPGD2C.png",
- alt: "banner.png",
- title: "banner.png",
- width: null,
- height: null,
- },
- },
- { type: "horizontalRule" },
- {
- type: "heading",
- attrs: { level: 3 },
- content: [{ type: "text", text: "Learn more" }],
- },
- {
- type: "taskList",
- content: [
- {
- type: "taskItem",
- attrs: { checked: false },
- content: [
- {
- type: "paragraph",
- content: [
- { type: "text", text: "Star us on " },
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://github.com/steven-tey/novel",
- target: "_blank",
- },
- },
- ],
- text: "GitHub",
- },
- ],
- },
- ],
- },
- {
- type: "taskItem",
- attrs: { checked: false },
- content: [
- {
- type: "paragraph",
- content: [
- { type: "text", text: "Install the " },
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://www.npmjs.com/package/novel",
- target: "_blank",
- },
- },
- ],
- text: "NPM package",
- },
- ],
- },
- ],
- },
- {
- type: "taskItem",
- attrs: { checked: false },
- content: [
- {
- type: "paragraph",
- content: [
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://vercel.com/templates/next.js/novel",
- target: "_blank",
- },
- },
- ],
- text: "Deploy your own",
- },
- { type: "text", text: " to Vercel" },
- ],
- },
- ],
- },
- ],
- },
- ],
-};
diff --git a/apps/web/app/(editor)/lib/debouncedsave.ts b/apps/web/app/(editor)/lib/debouncedsave.ts
deleted file mode 100644
index 6490c6c4..00000000
--- a/apps/web/app/(editor)/lib/debouncedsave.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-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
deleted file mode 100644
index 00d89264..00000000
--- a/apps/web/app/(editor)/lib/editorprops.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-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)/lib/use-local-storage.ts b/apps/web/app/(editor)/lib/use-local-storage.ts
deleted file mode 100644
index 5f2ebeb9..00000000
--- a/apps/web/app/(editor)/lib/use-local-storage.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { useEffect, useState } from "react";
-
-const useLocalStorage = (
- key: string,
- initialValue: T,
- // eslint-disable-next-line no-unused-vars
-): [T, (value: T) => void] => {
- const [storedValue, setStoredValue] = useState(initialValue);
-
- useEffect(() => {
- // Retrieve from localStorage
- const item = window.localStorage.getItem(key);
- if (item) {
- setStoredValue(JSON.parse(item));
- }
- }, [key]);
-
- const setValue = (value: T) => {
- // Save state
- setStoredValue(value);
- // Save to localStorage
- window.localStorage.setItem(key, JSON.stringify(value));
- };
- return [storedValue, setValue];
-};
-
-export default useLocalStorage;
diff --git a/apps/web/app/(editor)/styles/globals.css b/apps/web/app/(editor)/styles/globals.css
deleted file mode 100644
index 336e2dae..00000000
--- a/apps/web/app/(editor)/styles/globals.css
+++ /dev/null
@@ -1,168 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-@layer base {
- :root {
- --background: 0 0% 100%;
- --foreground: 222.2 84% 4.9%;
-
- --card: 0 0% 100%;
- --card-foreground: 222.2 84% 4.9%;
-
- --popover: 0 0% 100%;
- --popover-foreground: 222.2 84% 4.9%;
-
- --primary: 222.2 47.4% 11.2%;
- --primary-foreground: 210 40% 98%;
-
- --secondary: 210 40% 96.1%;
- --secondary-foreground: 222.2 47.4% 11.2%;
-
- --muted: 210 40% 96.1%;
- --muted-foreground: 215.4 16.3% 46.9%;
-
- --accent: 210 40% 96.1%;
- --accent-foreground: 222.2 47.4% 11.2%;
-
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 210 40% 98%;
-
- --border: 214.3 31.8% 91.4%;
- --input: 214.3 31.8% 91.4%;
- --ring: 222.2 84% 4.9%;
-
- --radius: 0.5rem;
-
- --novel-highlight-default: #ffffff;
- --novel-highlight-purple: #f6f3f8;
- --novel-highlight-red: #fdebeb;
- --novel-highlight-yellow: #fbf4a2;
- --novel-highlight-blue: #c1ecf9;
- --novel-highlight-green: #acf79f;
- --novel-highlight-orange: #faebdd;
- --novel-highlight-pink: #faf1f5;
- --novel-highlight-gray: #f1f1ef;
- }
-
- .dark {
- --background: #171B1F;
- --foreground: 210 40% 98%;
-
- --card: 222.2 84% 4.9%;
- --card-foreground: 210 40% 98%;
-
- --popover: 222.2 84% 4.9%;
- --popover-foreground: 210 40% 98%;
-
- --primary: 210 40% 98%;
- --primary-foreground: 222.2 47.4% 11.2%;
-
- --secondary: 217.2 32.6% 17.5%;
- --secondary-foreground: 210 40% 98%;
-
- --muted: 217.2 32.6% 17.5%;
- --muted-foreground: 215 20.2% 65.1%;
-
- --accent: 217.2 32.6% 17.5%;
- --accent-foreground: 210 40% 98%;
-
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 210 40% 98%;
-
- --border: 217.2 32.6% 17.5%;
- --input: 217.2 32.6% 17.5%;
- --ring: 212.7 26.8% 83.9%;
-
- --novel-highlight-default: #000000;
- --novel-highlight-purple: #3f2c4b;
- --novel-highlight-red: #5c1a1a;
- --novel-highlight-yellow: #5c4b1a;
- --novel-highlight-blue: #1a3d5c;
- --novel-highlight-green: #1a5c20;
- --novel-highlight-orange: #5c3a1a;
- --novel-highlight-pink: #5c1a3a;
- --novel-highlight-gray: #3a3a3a;
- }
-}
-
-@layer base {
- * {
- @apply border-border;
- }
- body {
- @apply bg-background text-foreground;
- }
-}
-
-
-pre {
- background: #0d0d0d;
- border-radius: 0.5rem;
- color: #fff;
- font-family: "JetBrainsMono", monospace;
- padding: 0.75rem 1rem;
-
- code {
- background: none;
- color: inherit;
- font-size: 0.8rem;
- padding: 0;
- }
-
- .hljs-comment,
- .hljs-quote {
- color: #616161;
- }
-
- .hljs-variable,
- .hljs-template-variable,
- .hljs-attribute,
- .hljs-tag,
- .hljs-name,
- .hljs-regexp,
- .hljs-link,
- .hljs-name,
- .hljs-selector-id,
- .hljs-selector-class {
- color: #f98181;
- }
-
- .hljs-number,
- .hljs-meta,
- .hljs-built_in,
- .hljs-builtin-name,
- .hljs-literal,
- .hljs-type,
- .hljs-params {
- color: #fbbc88;
- }
-
- .hljs-string,
- .hljs-symbol,
- .hljs-bullet {
- color: #b9f18d;
- }
-
- .hljs-title,
- .hljs-section {
- color: #faf594;
- }
-
- .hljs-keyword,
- .hljs-selector-tag {
- color: #70cff8;
- }
-
- .hljs-emphasis {
- font-style: italic;
- }
-
- .hljs-strong {
- font-weight: 700;
- }
-}
-::-webkit-scrollbar{
- width: 0;
-}
-
diff --git a/apps/web/app/(editor)/styles/prosemirror.css b/apps/web/app/(editor)/styles/prosemirror.css
deleted file mode 100644
index 7298c98b..00000000
--- a/apps/web/app/(editor)/styles/prosemirror.css
+++ /dev/null
@@ -1,203 +0,0 @@
-.ProseMirror {
- @apply p-12 px-8 sm:px-12;
-}
-
-.ProseMirror .is-editor-empty:first-child::before {
- content: attr(data-placeholder);
- float: left;
- color: hsl(var(--muted-foreground));
- pointer-events: none;
- height: 0;
-}
-.ProseMirror .is-empty::before {
- content: attr(data-placeholder);
- float: left;
- color: hsl(var(--muted-foreground));
- pointer-events: none;
- height: 0;
-}
-
-/* Custom image styles */
-
-.ProseMirror img {
- transition: filter 0.1s ease-in-out;
-
- &:hover {
- cursor: pointer;
- filter: brightness(90%);
- }
-
- &.ProseMirror-selectednode {
- outline: 3px solid #5abbf7;
- filter: brightness(90%);
- }
-}
-
-.img-placeholder {
- position: relative;
-
- &:before {
- content: "";
- box-sizing: border-box;
- position: absolute;
- top: 50%;
- left: 50%;
- width: 36px;
- height: 36px;
- border-radius: 50%;
- border: 3px solid var(--novel-stone-200);
- border-top-color: var(--novel-stone-800);
- animation: spinning 0.6s linear infinite;
- }
-}
-
-@keyframes spinning {
- to {
- transform: rotate(360deg);
- }
-}
-
-/* Custom TODO list checkboxes – shoutout to this awesome tutorial: https://moderncss.dev/pure-css-custom-checkbox-style/ */
-
-ul[data-type="taskList"] li > label {
- margin-right: 0.2rem;
- user-select: none;
-}
-
-@media screen and (max-width: 768px) {
- ul[data-type="taskList"] li > label {
- margin-right: 0.5rem;
- }
-}
-
-ul[data-type="taskList"] li > label input[type="checkbox"] {
- -webkit-appearance: none;
- appearance: none;
- background-color: hsl(var(--background));
- margin: 0;
- cursor: pointer;
- width: 1.2em;
- height: 1.2em;
- position: relative;
- top: 5px;
- border: 2px solid hsl(var(--border));
- margin-right: 0.3rem;
- display: grid;
- place-content: center;
-
- &:hover {
- background-color: hsl(var(--accent));
- }
-
- &:active {
- background-color: hsl(var(--accent));
- }
-
- &::before {
- content: "";
- width: 0.65em;
- height: 0.65em;
- transform: scale(0);
- transition: 120ms transform ease-in-out;
- box-shadow: inset 1em 1em;
- transform-origin: center;
- clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
- }
-
- &:checked::before {
- transform: scale(1);
- }
-}
-
-ul[data-type="taskList"] li[data-checked="true"] > div > p {
- color: var(--muted-foreground);
- text-decoration: line-through;
- text-decoration-thickness: 2px;
-}
-
-/* Overwrite tippy-box original max-width */
-
-.tippy-box {
- max-width: 400px !important;
-}
-
-.ProseMirror:not(.dragging) .ProseMirror-selectednode {
- outline: none !important;
- background-color: var(--novel-highlight-blue);
- transition: background-color 0.2s;
- box-shadow: none;
-}
-
-.drag-handle {
- position: fixed;
- opacity: 1;
- transition: opacity ease-in 0.2s;
- border-radius: 0.25rem;
-
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(0, 0, 0, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
- background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem);
- background-repeat: no-repeat;
- background-position: center;
- width: 1.2rem;
- height: 1.5rem;
- z-index: 50;
- cursor: grab;
-
- &:hover {
- background-color: var(--novel-stone-100);
- transition: background-color 0.2s;
- }
-
- &:active {
- background-color: var(--novel-stone-200);
- transition: background-color 0.2s;
- cursor: grabbing;
- }
-
- &.hide {
- opacity: 0;
- pointer-events: none;
- }
-
- @media screen and (max-width: 600px) {
- display: none;
- pointer-events: none;
- }
-}
-
-.dark .drag-handle {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(255, 255, 255, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
-}
-
-/* Custom Youtube Video CSS */
-iframe {
- border: 8px solid #ffd00027;
- border-radius: 4px;
- min-width: 200px;
- min-height: 200px;
- display: block;
- outline: 0px solid transparent;
-}
-
-div[data-youtube-video] > iframe {
- cursor: move;
- aspect-ratio: 16 / 9;
- width: 100%;
-}
-
-.ProseMirror-selectednode iframe {
- transition: outline 0.15s;
- outline: 6px solid #fbbf24;
-}
-
-@media only screen and (max-width: 480px) {
- div[data-youtube-video] > iframe {
- max-height: 50px;
- }
-}
-
-@media only screen and (max-width: 720px) {
- div[data-youtube-video] > iframe {
- max-height: 100px;
- }
-}
\ No newline at end of file
diff --git a/packages/ui/components/canvas/draggableComponent.tsx b/packages/ui/components/canvas/draggableComponent.tsx
new file mode 100644
index 00000000..da3c42c7
--- /dev/null
+++ b/packages/ui/components/canvas/draggableComponent.tsx
@@ -0,0 +1,72 @@
+import Image from "next/image";
+import { useRef, useState } from "react";
+
+interface DraggableComponentsProps {
+ content: string;
+ extraInfo?: string;
+ icon: string;
+ iconAlt: string;
+}
+
+export default function DraggableComponentsContainer({
+ content,
+}: {
+ content: DraggableComponentsProps[];
+}) {
+ return (
+
+ {content.map((i) => {
+ return (
+
+ );
+ })}
+
+ );
+}
+
+function DraggableComponents({
+ content,
+ extraInfo,
+ icon,
+ iconAlt,
+}: DraggableComponentsProps) {
+ const [isDragging, setIsDragging] = useState(false);
+ const containerRef = useRef(null);
+
+ const handleDragStart = (event: React.DragEvent) => {
+ setIsDragging(true);
+ if (containerRef.current) {
+ // Serialize the children as a string for dataTransfer
+ const childrenHtml = containerRef.current.innerHTML;
+ event.dataTransfer.setData("text/html", childrenHtml);
+ }
+ };
+
+ const handleDragEnd = () => {
+ setIsDragging(false);
+ };
+
+
+ return (
+
+
+
+
+
{content}
+
+
{extraInfo}
+
+
+ );
+}
diff --git a/packages/ui/icons/autocomplete.svg b/packages/ui/icons/autocomplete.svg
new file mode 100644
index 00000000..a4452242
--- /dev/null
+++ b/packages/ui/icons/autocomplete.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/packages/ui/icons/block.svg b/packages/ui/icons/block.svg
new file mode 100644
index 00000000..ca4cc20e
--- /dev/null
+++ b/packages/ui/icons/block.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/packages/ui/icons/canvas.svg b/packages/ui/icons/canvas.svg
new file mode 100644
index 00000000..529c75de
--- /dev/null
+++ b/packages/ui/icons/canvas.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/web/app/(canvas)/svg.tsx b/packages/ui/icons/drag.svg
similarity index 51%
rename from apps/web/app/(canvas)/svg.tsx
rename to packages/ui/icons/drag.svg
index bae4e614..366a4cb0 100644
--- a/apps/web/app/(canvas)/svg.tsx
+++ b/packages/ui/icons/drag.svg
@@ -1,24 +1,3 @@
-export function SettingsSvg() {
- return (
-
- );
-}
-
-export function DragSvg() {
- return (
-
- );
-}
-
-export function TextLoadingSvg() {
- return (
-
- );
-}
-
-export function ThreeDBlock() {
- return (
-
- );
-}
-
-export function LinkSvg() {
- return (
-
- );
-}
+
\ No newline at end of file
diff --git a/packages/ui/icons/index.ts b/packages/ui/icons/index.ts
index 7788c20f..88d4ebc0 100644
--- a/packages/ui/icons/index.ts
+++ b/packages/ui/icons/index.ts
@@ -8,6 +8,13 @@ import SelectIcon from "./select.svg";
import SearchIcon from "./search.svg";
import NextIcon from "./nextarrow.svg";
import UrlIcon from "./url.svg";
+import CanvasIcon from "./canvas.svg";
+import blockIcon from "./block.svg";
+import LinkIcon from "./link.svg";
+import AutocompleteIcon from "./autocomplete.svg";
+import BlockIcon from "./block.svg";
+import DragIcon from "./drag.svg";
+import SettingsIcon from "./settings.svg";
export {
AddIcon,
@@ -19,5 +26,12 @@ export {
SelectIcon,
SearchIcon,
NextIcon,
- UrlIcon
+ UrlIcon,
+ CanvasIcon,
+ blockIcon,
+ LinkIcon,
+ AutocompleteIcon,
+ BlockIcon,
+ DragIcon,
+ SettingsIcon
};
diff --git a/packages/ui/icons/link.svg b/packages/ui/icons/link.svg
new file mode 100644
index 00000000..25e2d04e
--- /dev/null
+++ b/packages/ui/icons/link.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/packages/ui/icons/settings.svg b/packages/ui/icons/settings.svg
new file mode 100644
index 00000000..2059b976
--- /dev/null
+++ b/packages/ui/icons/settings.svg
@@ -0,0 +1,14 @@
+
\ No newline at end of file