diff --git a/apps/web/package.json b/apps/web/package.json index 675009fd..be20fba7 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -27,6 +27,7 @@ "framer-motion": "^11.0.24", "lucide-react": "^0.338.0", "next": "14.1.0", + "novel": "0.1.22", "react": "^18", "react-dom": "^18", "tailwind-merge": "^2.2.1", diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 69b46387..23caee5b 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -57,8 +57,12 @@ body { padding-bottom: 15dvh; } -.chat-answer pre { - @apply bg-rgray-3 border-rgray-5 my-5 rounded-md border p-3 text-sm; +.chat-answer pre { + @apply bg-rgray-3 rounded-md border border-rgray-5 p-3 text-sm my-5; +} + +.novel-editor pre { + @apply bg-rgray-3 rounded-md border border-rgray-5 p-4 text-sm text-rgray-11; } .chat-answer h1 { @@ -66,5 +70,52 @@ body { } .chat-answer img { - @apply my-5 rounded-md font-medium; + @apply rounded-md font-medium my-5; +} + +.tippy-box { + @apply bg-rgray-3 text-rgray-11 border border-rgray-5 rounded-md py-0; +} + +.tippy-content #slash-command { + @apply text-rgray-11 bg-transparent border-none; +} + +#slash-command button { + @apply text-rgray-11 py-2; +} + +#slash-command button div:first-child { + @apply text-rgray-11 bg-rgray-4 border-rgray-5 ; +} + +#slash-command button.novel-bg-stone-100 { + @apply bg-rgray-1; +} + +.novel-editor [data-type=taskList] > li { + @apply my-0; +} + +.novel-editor input[type=checkbox] { + @apply accent-rgray-4 rounded-md; + + background: var(--gray-4) !important; + border: 1px solid var(--gray-10) !important; +} + +.novel-editor .is-editor-empty::before { + content: 'Press \'/\' for commands' !important; +} + +.novel-editor h1 { + @apply text-2xl; +} + +.novel-editor h2 { + @apply text-xl; +} + +.novel-editor h3 { + @apply text-lg; } diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx deleted file mode 100644 index f19deeb5..00000000 --- a/apps/web/src/components/Sidebar.tsx +++ /dev/null @@ -1,145 +0,0 @@ -"use client"; -import { StoredContent } from "@/server/db/schema"; -import { - Plus, - MoreHorizontal, - ArrowUpRight, - Edit3, - Trash2, -} from "lucide-react"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "./ui/dropdown-menu"; - -import { useState, useEffect, useRef } from "react"; - -export default function Sidebar() { - const websites: StoredContent[] = [ - { - id: 1, - content: "", - title: "Visual Studio Code", - url: "https://code.visualstudio.com", - description: "", - image: "https://code.visualstudio.com/favicon.ico", - baseUrl: "https://code.visualstudio.com", - savedAt: new Date(), - }, - { - id: 1, - content: "", - title: "yxshv/vscode: An unofficial remake of vscode's landing page", - url: "https://github.com/yxshv/vscode", - description: "", - image: "https://github.com/favicon.ico", - baseUrl: "https://github.com", - savedAt: new Date(), - }, - ]; - - return ( - - ); -} - -export const ListItem: React.FC<{ item: StoredContent }> = ({ item }) => { - const [isEditing, setIsEditing] = useState(false); - const editInputRef = useRef(null); - - useEffect(() => { - if (isEditing) { - setTimeout(() => { - editInputRef.current?.focus(); - }, 500); - } - }, [isEditing]); - - return ( -
- isEditing && e.preventDefault()} - className="flex w-[90%] items-center gap-2 focus:outline-none" - > - {isEditing ? ( - - ) : ( - <> - {item.title - - - )} - {isEditing ? ( - setIsEditing(false)} - onKeyDown={(e) => e.key === "Escape" && setIsEditing(false)} - /> - ) : ( - - {item.title ?? "Untitled website"} - - )} - - - - - - - window.open(item.url)}> - - Open - - { - setIsEditing(true); - }} - > - - Edit - - - - Delete - - - -
- ); -}; diff --git a/apps/web/src/components/Sidebar/AddMemoryDialog.tsx b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx new file mode 100644 index 00000000..5a1d92f0 --- /dev/null +++ b/apps/web/src/components/Sidebar/AddMemoryDialog.tsx @@ -0,0 +1,64 @@ +import { Editor } from "novel"; +import { + DialogClose, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../ui/dialog"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { useRef } from "react"; + +export function AddMemoryPage() { + return ( + <> + + Add a web page to memory + + This will take you the web page you are trying to add to memory, where + the extension will save the page to memory + + + + + + + Add + + + Cancel + + + + ); +} + +export function NoteAddPage() { + return ( + <> + + + + + Add + + + Cancel + + + + ); +} diff --git a/apps/web/src/components/Sidebar/MemoriesBar.tsx b/apps/web/src/components/Sidebar/MemoriesBar.tsx index d7d8b5b5..83984233 100644 --- a/apps/web/src/components/Sidebar/MemoriesBar.tsx +++ b/apps/web/src/components/Sidebar/MemoriesBar.tsx @@ -1,3 +1,4 @@ +import { Editor } from "novel"; import { useAutoAnimate } from "@formkit/auto-animate/react"; import { MemoryWithImage, @@ -22,7 +23,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "../ui/dropdown-menu"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Variant, useAnimate, motion } from "framer-motion"; import { useMemory } from "@/contexts/MemoryContext"; import { SpaceIcon } from "@/assets/Memories"; @@ -38,6 +39,8 @@ import { import { Label } from "../ui/label"; import useViewport from "@/hooks/useViewport"; import useTouchHold from "@/hooks/useTouchHold"; +import { DialogTrigger } from "@radix-ui/react-dialog"; +import { AddMemoryPage, NoteAddPage } from "./AddMemoryDialog"; export function MemoriesBar() { const [parent, enableAnimations] = useAutoAnimate(); @@ -59,38 +62,43 @@ export function MemoriesBar() { />
- - - - - - { - setIsDropdownOpen(false); - setAddMemoryState("page"); - }} - > - - Page to Memory - - - - Note - - - - Space - - - + + + + + + e.preventDefault()}> + + { + setAddMemoryState("page"); + }} + > + + Page to Memory + + + + { + setAddMemoryState("note"); + }} + > + + Note + + + + + Space + + + +
-
void; + type: "page" | "note" | "space" | null; + children?: React.ReactNode | React.ReactNode[]; + isOpen: boolean; }) { return ( - <> - onStateChange(open ? "page" : null)} + + {children} + { + e.preventDefault(); + const novel = document.querySelector('[contenteditable="true"]') as + | HTMLDivElement + | undefined; + if (novel) { + novel.autofocus = false; + novel.onfocus = () => { + ( + document.querySelector("[data-modal-autofocus]") as + | HTMLInputElement + | undefined + )?.focus(); + novel.onfocus = null; + }; + } + ( + document.querySelector("[data-modal-autofocus]") as + | HTMLInputElement + | undefined + )?.focus(); + }} + className="w-max max-w-[auto]" > - - - Add a web page to memory - - This will take you the web page you are trying to add to memory, - where the extension will save the page to memory - - - - - - - Add - - - Cancel - - - - - - - - Add a web page to memory - - This will take you the web page you are trying to add to memory, - where the extension will save the page to memory - - - - - - - Add - - - Cancel - - - - - + {type === "page" && } + {type === "note" && } + + ); }