plane/apps/app/components/tiptap/bubble-menu/node-selector.tsx

126 lines
4.0 KiB
TypeScript
Raw Normal View History

feat : Tiptap integration (#1832) * remirror instances commented out to avoid prosemirror conflicts * styles migrated for remirror to tiptap transition * added bubblemenu support with extensions * fixed css for task lists and code with syntax highlighting * added support for slash command * fixed bubble menu to match styles and added better seperation in UI * saving with debounce logic added and it's stored in backend * added migration support by updating to html * Image uploads done * improved file structure and delete image function implemented * Integrated tiptap with Issue Modal * added additional props and Tiptap Integration with Comments * added tiptap integration with user activity feeds * added ref control support and bubble menu support for readonly editor * added tiptap support for plane pages * added tiptap support to gpt assistant modal (yet to be tested) * removed remirror instances and cleaned up code * improved code structure for extracting props in Tiptap * fixing ts errors for next build * fixing node ts error for Horizontal Rule * added ts fix for node types * temp fix * temp fix * added min height for issue description in modal * added resolutions to prosemirror-model version * trying pnpm overrides * explicitly added prosemirror deps * bugfixes * removed extra gap at the top and moved saved indicator to the bottom * fix: slash command scroll position * chore: update custom css variables * matched theme colours * fixed gpt-assistant modal * updated yarn lock * added debounced updates for the title and removed saved state after timeout * added css animations for saved state * build fixes and remove remirror instances * minor commenting fixes --------- Co-authored-by: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com> Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-08-15 09:34:46 +00:00
import { Editor } from "@tiptap/core";
import {
Check,
ChevronDown,
Heading1,
Heading2,
Heading3,
TextQuote,
ListOrdered,
TextIcon,
Code,
CheckSquare,
} from "lucide-react";
import { Dispatch, FC, SetStateAction } from "react";
import { BubbleMenuItem } from "../bubble-menu";
import { cn } from "../utils";
interface NodeSelectorProps {
editor: Editor;
isOpen: boolean;
setIsOpen: Dispatch<SetStateAction<boolean>>;
}
export const NodeSelector: FC<NodeSelectorProps> = ({ editor, isOpen, setIsOpen }) => {
const items: BubbleMenuItem[] = [
{
name: "Text",
icon: TextIcon,
command: () => editor.chain().focus().toggleNode("paragraph", "paragraph").run(),
isActive: () =>
editor.isActive("paragraph") &&
!editor.isActive("bulletList") &&
!editor.isActive("orderedList"),
},
{
name: "H1",
icon: Heading1,
command: () => editor.chain().focus().toggleHeading({ level: 1 }).run(),
isActive: () => editor.isActive("heading", { level: 1 }),
},
{
name: "H2",
icon: Heading2,
command: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
isActive: () => editor.isActive("heading", { level: 2 }),
},
{
name: "H3",
icon: Heading3,
command: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
isActive: () => editor.isActive("heading", { level: 3 }),
},
{
name: "To-do List",
icon: CheckSquare,
command: () => editor.chain().focus().toggleTaskList().run(),
isActive: () => editor.isActive("taskItem"),
},
{
name: "Bullet List",
icon: ListOrdered,
command: () => editor.chain().focus().toggleBulletList().run(),
isActive: () => editor.isActive("bulletList"),
},
{
name: "Numbered List",
icon: ListOrdered,
command: () => editor.chain().focus().toggleOrderedList().run(),
isActive: () => editor.isActive("orderedList"),
},
{
name: "Quote",
icon: TextQuote,
command: () =>
editor.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
isActive: () => editor.isActive("blockquote"),
},
{
name: "Code",
icon: Code,
command: () => editor.chain().focus().toggleCodeBlock().run(),
isActive: () => editor.isActive("codeBlock"),
},
];
const activeItem = items.filter((item) => item.isActive()).pop() ?? {
name: "Multiple",
};
return (
<div className="relative h-full">
<button
className="flex h-full items-center gap-1 whitespace-nowrap p-2 text-sm font-medium text-custom-text-300 hover:bg-custom-primary-100/5 active:bg-custom-primary-100/5"
onClick={() => setIsOpen(!isOpen)}
>
<span>{activeItem?.name}</span>
<ChevronDown className="h-4 w-4" />
</button>
{isOpen && (
<section className="fixed top-full z-[99999] mt-1 flex w-48 flex-col overflow-hidden rounded border border-custom-border-300 bg-custom-background-100 p-1 shadow-xl animate-in fade-in slide-in-from-top-1">
{items.map((item, index) => (
<button
key={index}
onClick={() => {
item.command();
setIsOpen(false);
}}
className={cn("flex items-center justify-between rounded-sm px-2 py-1 text-sm text-custom-text-200 hover:bg-custom-primary-100/5 hover:text-custom-text-100", { "bg-custom-primary-100/5 text-custom-text-100": activeItem.name === item.name })}
>
<div className="flex items-center space-x-2">
<div className="rounded-sm border border-custom-border-300 p-1">
<item.icon className="h-3 w-3" />
</div>
<span>{item.name}</span>
</div>
{activeItem.name === item.name && <Check className="h-4 w-4" />}
</button>
))}
</section>
)}
</div>
);
};